9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-24 09:09:18 +00:00

Compare commits

...

89 Commits
3.6.7 ... 3.7.1

Author SHA1 Message Date
William278
bc1f983684 build: actually checkout 1.21.1 on 1.21.1 2024-11-14 17:11:27 +00:00
William278
31eb747c55 build: fix release script 2024-11-14 17:00:17 +00:00
William278
e8facf52ce fix: correct env in release file 2024-11-14 15:42:35 +00:00
William278
5ee4bdd644 fix: fix broken sendPacket mixin on Fabric 2024-11-14 15:33:26 +00:00
William278
92c371e201 fix: disable "other" gui actions.
Fixes an issue where you could double-click stack to collect unstacked items without edit perms.

Unfortunately this breaks pick block on creative without edit permission, but this is  considered a necessary compromise.
2024-11-14 15:06:46 +00:00
William278
d27278454a feat: warn if server name matches default, close #314 2024-11-14 14:58:43 +00:00
William278
16780c149c build: bump deps 2024-11-14 14:54:50 +00:00
William278
0445ba63bc fix: use correct map render logic, close #406 2024-11-14 14:38:11 +00:00
dependabot[bot]
b6aefd6f57 ci: bump mikepenz/action-junit-report from 4 to 5 (#412)
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 4 to 5.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/v4...v5)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 23:47:27 +00:00
William
f803af0225 feat: support newer Map data format, close #406 2024-11-04 21:49:24 +00:00
William
2675f4a377 fix: Paper event listener not registering events 2024-11-04 19:44:53 +00:00
William
03341c981f build: bump nbt-api to 2.14.0 2024-11-04 19:29:52 +00:00
William
38cc654167 fix: fixup some compiler warns 2024-11-01 22:44:29 +00:00
William
b347a8d060 fix: API not publishing, close #399 2024-11-01 22:32:03 +00:00
William
8733b86b45 [ci skip]: build 1.21.1 ver 2024-11-01 14:31:56 +00:00
William
eda8e72633 build: target Minecraft 1.21.3 2024-10-31 23:52:09 +00:00
William
c942a015d1 feat: start 1.21.3 2024-10-31 20:57:27 +00:00
William
c00265f1f9 fix: add getPlayerCustomDataStore impl on Fabric 2024-10-27 00:14:20 +01:00
dependabot[bot]
e303984dcf deps: bump org.jetbrains:annotations from 26.0.0 to 26.0.1 (#407)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 26.0.0 to 26.0.1.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/26.0.0...26.0.1)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 11:40:45 +01:00
Preva1l
b449b5dee6 fix: cache save causes, fix Fabric data save on shutdown (#405)
Co-authored-by: seeruk <wright.elliot@gmail.com>
2024-10-17 16:21:42 +01:00
dependabot[bot]
48f8c0c967 deps: bump org.jetbrains:annotations from 25.0.0 to 26.0.0 (#400)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 25.0.0 to 26.0.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/25.0.0...26.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 16:20:30 +01:00
dependabot[bot]
f88c4c3e2c deps: bump org.ajoberstar.grgit from 5.2.2 to 5.3.0 (#401)
Bumps [org.ajoberstar.grgit](https://github.com/ajoberstar/grgit) from 5.2.2 to 5.3.0.
- [Release notes](https://github.com/ajoberstar/grgit/releases)
- [Commits](https://github.com/ajoberstar/grgit/compare/5.2.2...5.3.0)

---
updated-dependencies:
- dependency-name: org.ajoberstar.grgit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 16:20:23 +01:00
dependabot[bot]
e6273fa9a0 deps: bump org.junit.jupiter:junit-jupiter-params from 5.11.1 to 5.11.2 (#402)
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.11.1 to 5.11.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 16:20:09 +01:00
dependabot[bot]
1ba5585d0d deps: bump org.junit.jupiter:junit-jupiter-api from 5.11.1 to 5.11.2 (#395)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.11.1 to 5.11.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 12:31:29 +01:00
dependabot[bot]
73547371ae deps: bump org.junit.jupiter:junit-jupiter-engine from 5.11.1 to 5.11.2 (#394)
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.11.1 to 5.11.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 12:31:21 +01:00
dependabot[bot]
fca6825394 deps: bump org.jetbrains:annotations from 24.1.0 to 25.0.0 (#396)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.1.0 to 25.0.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.1.0...25.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 12:31:15 +01:00
dependabot[bot]
53af114f44 deps: bump com.gradleup.shadow from 8.3.2 to 8.3.3 (#397)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.2 to 8.3.3.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.2...8.3.3)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 12:31:07 +01:00
William
311cc85c92 docs: document incompatibility with /restart 2024-10-05 17:09:14 +01:00
William
099a258cf8 docs: update compatibility table 2024-10-03 15:06:22 +01:00
William
480f59a166 build: fix build, getEnv -> getProperty 2024-10-03 15:03:53 +01:00
William
45c2f5350f Merge remote-tracking branch 'origin/master' 2024-10-03 14:58:25 +01:00
William
ed88d77852 build: bump guava, junit and HikariCP 2024-10-03 14:58:12 +01:00
dependabot[bot]
e7fc9f015e deps: bump com.zaxxer:HikariCP from 5.1.0 to 6.0.0 (#388)
Bumps [com.zaxxer:HikariCP](https://github.com/brettwooldridge/HikariCP) from 5.1.0 to 6.0.0.
- [Changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES)
- [Commits](https://github.com/brettwooldridge/HikariCP/compare/HikariCP-5.1.0...HikariCP-6.0.0)

---
updated-dependencies:
- dependency-name: com.zaxxer:HikariCP
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 14:57:06 +01:00
William
cabde9e8d8 fix: version_uuid instead of id in PSQL rotateSnapshots 2024-09-29 15:33:32 +01:00
William
4df7d2def4 fix: missing placeholder %s in postgres 2024-09-29 14:58:09 +01:00
William
59ed77c169 fix: Compat issues with Postgres queries, close #383 2024-09-29 14:32:57 +01:00
William
53da3bd40c docs: update README workflow badge 2024-09-29 14:21:13 +01:00
William
abdf8223fc refactor: adjust postgres statements 2024-09-29 13:35:28 +01:00
William
a5efeecad3 fix: NPE on startup due to compat check, close #384 2024-09-29 13:17:42 +01:00
William
4d26b24d13 Merge remote-tracking branch 'origin/master' 2024-09-28 19:01:53 +01:00
William
29b3a60c64 fix: sync default valued attributes 2024-09-28 19:01:46 +01:00
dependabot[bot]
da894f57c4 deps: bump com.gradleup.shadow from 8.3.0 to 8.3.2 (#380)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.0 to 8.3.2.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.0...8.3.2)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 14:33:23 +01:00
dependabot[bot]
1bd703641b deps: bump org.bstats:bstats-bukkit from 3.0.3 to 3.1.0 (#379)
Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.3 to 3.1.0.
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 14:33:14 +01:00
dependabot[bot]
1b1d4c8e8d deps: bump commons-io:commons-io from 2.16.1 to 2.17.0 (#381)
Bumps commons-io:commons-io from 2.16.1 to 2.17.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-28 14:33:07 +01:00
Coded
842ec0e28d feat: add create_tables config option to disable automatic DDL operations (#377)
* Add config option for creating tables

* Move createTables config to a better position
2024-09-28 14:32:53 +01:00
William
2d5648408e build: account for differences in commit version names 2024-09-05 19:14:38 +01:00
William
41b3240741 ci: force copy files, make dir first 2024-09-05 17:50:11 +01:00
William
bc03e8f3e3 ci: recursive copy target jar folder 2024-09-05 17:46:11 +01:00
William
86799f4c08 ci: try copying outputs first 2024-09-05 17:43:06 +01:00
William
a3e004cf71 ci: adjust output paths 2024-09-05 17:37:43 +01:00
William
a7aeb1de21 fix: remove debug logging 2024-09-05 17:35:20 +01:00
William
1a703102c3 ci: More adjustments to CI script 2024-09-05 17:26:23 +01:00
William
368c68f42b fix: item and block stats not syncing, close #362 2024-09-05 17:25:03 +01:00
William
e191713bdc fix: attribute syncing setting bad base value 2024-09-05 17:07:50 +01:00
William
1604338498 ci: Use correct environment variable for workspace 2024-09-05 14:18:31 +01:00
William
c223797bf4 ci: Adjust glob build file output paths 2024-09-05 14:12:21 +01:00
William
9b10adc8e4 ci: fix version checking paths 2024-09-05 14:05:11 +01:00
William
5935f1ab5f ci: fix build version checking 2024-09-05 14:00:49 +01:00
William
3455b10a20 docs: update compatibility matrix 2024-09-05 13:58:32 +01:00
William
34e08b712d build: update CI to publish all active versions 2024-09-05 13:49:12 +01:00
William
605d314a58 refactor: make attributes allow-listed instead of deny-listed
This is a better default - as a number of attributes are primarily synced through other means (potions, items), or were applied from a context-sensitive action that does not warrant syncing across server contexts (sprinting, flying)
2024-09-05 13:37:53 +01:00
dependabot[bot]
daaf5147a7 deps: bump org.bstats:bstats-bukkit from 3.0.2 to 3.0.3 (#370)
Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 22:20:26 +01:00
William
50eb9a7543 feat: add legacy upgrade command 2024-08-26 16:00:21 +01:00
William
7d8ef7b6b3 refactor: update map persistence logic
Stop using deprecated methods
2024-08-26 15:32:28 +01:00
William
347d2d0a8f refactor: adjust modifier slot group methods 2024-08-26 14:10:02 +01:00
William
bd560fcc99 refactor: use getter and setter for payload bytes in RedisMessage 2024-08-26 14:08:49 +01:00
William
b68aedc99a fix: correct compat version check 2024-08-26 14:08:25 +01:00
William
47373d8974 refactor: lock user before updating user data on command 2024-08-26 14:08:11 +01:00
William
a57b8df994 refactor: clean up 1.21.1 compatibility issues
Close #368
2024-08-26 13:44:19 +01:00
William
17235637a5 fix: exception loading compatibility config 2024-08-26 12:35:21 +01:00
William
cd5abd5a65 build(deps): bump dependencies 2024-08-25 21:05:17 +01:00
William
5c6631cdcf docs: clarify release channels in compat table 2024-08-25 21:03:19 +01:00
William
621afcd5c6 feat: support Minecraft 1.21.1 on Fabric 2024-08-25 20:58:26 +01:00
William
112a974a6c feat: Target Minecraft 1.21.1 with new release system 2024-08-25 20:45:25 +01:00
William
f9d46b4aff Merge remote-tracking branch 'origin/master' 2024-08-25 20:38:31 +01:00
William
dfd828bca1 feat: introduce new versioning & Minecraft compatibility system 2024-08-25 20:07:04 +01:00
dependabot[bot]
2df9fd897a deps: bump net.kyori:adventure-platform-bukkit from 4.3.3 to 4.3.4 (#360)
Bumps [net.kyori:adventure-platform-bukkit](https://github.com/KyoriPowered/adventure-platform) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/KyoriPowered/adventure-platform/releases)
- [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.3...v4.3.4)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-platform-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 13:39:00 +01:00
dependabot[bot]
ff2531539e deps: bump net.kyori:adventure-platform-api from 4.3.3 to 4.3.4 (#361)
Bumps [net.kyori:adventure-platform-api](https://github.com/KyoriPowered/adventure-platform) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/KyoriPowered/adventure-platform/releases)
- [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.3...v4.3.4)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-platform-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 13:38:50 +01:00
William
52ec138273 fix: suppress map cursor paper exception 2024-08-11 12:52:23 +01:00
William
0f7a866652 test: bump test deps to 1.21.1 2024-08-11 12:25:43 +01:00
William
eeb52ac41e deps: bump item-nbt-api to 2.13.2
support MC 1.21.1
2024-08-11 12:25:03 +01:00
Dong Heon Hee
f456443da0 Merge branch 'master' into master 2024-08-05 07:39:13 +09:00
동헌희
fc7330213a fix: build error due to rebase with master branch 2024-07-25 00:15:15 +09:00
동헌희
d8272ba52d docs: update minimum required mc&jvm version 2024-07-23 08:50:32 +09:00
동헌희
315f0eeb2f ci: Update jvm version(21) 2024-07-23 08:50:32 +09:00
동헌희
8e83617ac4 ci: Update required jvm version(21) 2024-07-23 08:50:32 +09:00
동헌희
212bb0beb8 fix: Forgot to update upgradeItemStacks 2024-07-23 08:50:32 +09:00
동헌희
c16231b12b feat: Support fabric with 1.21 2024-07-23 08:50:32 +09:00
동헌희
93f7294859 fix: Breaking chage in fabric API 2024-07-23 08:50:32 +09:00
60 changed files with 1050 additions and 552 deletions

70
.github/workflows/ci_1.20.1.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: CI Tests
on:
push:
branches: [ 'minecraft/1.20.1' ]
paths-ignore:
- 'docs/**'
- 'workflows/**'
- 'README.md'
permissions:
contents: read
checks: write
jobs:
build:
name: 'Build - 1.20.1'
runs-on: ubuntu-latest
steps:
- name: 'Setup JDK 21 📦'
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: 'Setup Gradle 8.8 🏗️'
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.8'
- name: 'Checkout for CI 🛎️'
uses: actions/checkout@v4
with:
ref: 'minecraft/1.20.1'
- name: '[Current - 1.20.1] Build 🛎️'
run: |
./gradlew clean build publish
env:
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: 'Publish Test Report 📊'
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Continue on failure
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: 'Fetch Version String 📝'
run: |
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
id: fetch-version
- name: 'Set Version Variable 📝'
run: |
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
- name: 'Publish to William278.net 🚀'
uses: WiIIiam278/bones-publish-action@v1
with:
api-key: ${{ secrets.BONES_API_KEY }}
project: 'husksync'
channel: 'alpha'
version: ${{ env.version_name }}
changelog: ${{ github.event.head_commit.message }}
distro-names: |
paper-1.20.1
fabric-1.20.1
distro-groups: |
paper
fabric
distro-descriptions: |
Paper 1.20.1
Fabric 1.20.1
files: |
target/HuskSync-Paper-${{ env.version_name }}+mc.1.20.1.jar
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar

70
.github/workflows/ci_1.21.1.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: CI Tests
on:
push:
branches: [ 'minecraft/1.21.1' ]
paths-ignore:
- 'docs/**'
- 'workflows/**'
- 'README.md'
permissions:
contents: read
checks: write
jobs:
build:
name: 'Build - 1.21.1'
runs-on: ubuntu-latest
steps:
- name: 'Setup JDK 21 📦'
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: 'Setup Gradle 8.8 🏗️'
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.8'
- name: 'Checkout for CI 🛎️'
uses: actions/checkout@v4
with:
ref: 'minecraft/1.21.1'
- name: '[Current - 1.21.1] Build 🛎️'
run: |
./gradlew clean build publish
env:
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: 'Publish Test Report 📊'
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Continue on failure
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: 'Fetch Version String 📝'
run: |
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
id: fetch-version
- name: 'Set Version Variable 📝'
run: |
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
- name: 'Publish to William278.net 🚀'
uses: WiIIiam278/bones-publish-action@v1
with:
api-key: ${{ secrets.BONES_API_KEY }}
project: 'husksync'
channel: 'alpha'
version: ${{ env.version_name }}
changelog: ${{ github.event.head_commit.message }}
distro-names: |
paper-1.21.1
fabric-1.21.1
distro-groups: |
paper
fabric
distro-descriptions: |
Paper 1.21.1
Fabric 1.21.1
files: |
target/HuskSync-Paper-${{ env.version_name }}+mc.1.21.1.jar
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar

View File

@@ -14,32 +14,36 @@ permissions:
jobs:
build:
name: 'Build - 1.21.3'
runs-on: ubuntu-latest
steps:
- name: 'Checkout for CI 🛎️'
uses: actions/checkout@v4
- name: 'Set up JDK 17 📦'
- name: 'Setup JDK 21 📦'
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: 'Build with Gradle 🏗️'
uses: gradle/gradle-build-action@v3
- name: 'Setup Gradle 8.8 🏗️'
uses: gradle/actions/setup-gradle@v4
with:
arguments: build test publish
gradle-version: '8.8'
- name: 'Checkout for CI 🛎️'
uses: actions/checkout@v4
- name: '[Current - 1.21.3] Build 🛎️'
run: |
./gradlew clean build publish
env:
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: 'Publish Test Report 📊'
uses: mikepenz/action-junit-report@v4
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Continue on failure
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: 'Fetch Version Name 📝'
- name: 'Fetch Version String 📝'
run: |
echo "::set-output name=VERSION_NAME::$(${{github.workspace}}/gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
id: fetch-version
- name: Get Version
- name: 'Set Version Variable 📝'
run: |
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
- name: 'Publish to William278.net 🚀'
@@ -51,14 +55,14 @@ jobs:
version: ${{ env.version_name }}
changelog: ${{ github.event.head_commit.message }}
distro-names: |
paper
fabric-1.20.1
paper-1.21.3
fabric-1.21.3
distro-groups: |
paper
fabric
distro-descriptions: |
Paper
Fabric 1.20.1
Paper 1.21.3
Fabric 1.21.3
files: |
target/HuskSync-Paper-${{ env.version_name }}.jar
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
target/HuskSync-Paper-${{ env.version_name }}+mc.1.21.3.jar
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.3.jar

View File

@@ -14,17 +14,17 @@ jobs:
steps:
- name: 'Checkout for CI 🛎'
uses: actions/checkout@v4
- name: 'Set up JDK 17 📦'
- name: 'Set up JDK 21 📦'
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: 'Build with Gradle 🏗️'
uses: gradle/gradle-build-action@v3
with:
arguments: test
- name: 'Publish Test Report 📊'
uses: mikepenz/action-junit-report@v4
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Continue on failure
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

View File

@@ -8,26 +8,65 @@ permissions:
contents: read
checks: write
jobs:
build:
name: 'Publish Release'
runs-on: ubuntu-latest
steps:
- name: 'Checkout for CI 🛎️'
uses: actions/checkout@v4
- name: 'Set up JDK 17 📦'
- name: 'Setup JDK 21 📦'
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: 'Build with Gradle 🏗️'
uses: gradle/gradle-build-action@v3
- name: 'Setup Gradle 8.8 🏗️'
uses: gradle/actions/setup-gradle@v4
with:
arguments: build test publish
gradle-version: '8.8'
- name: '[Current - 1.21.3] Checkout for CI 🛎️'
uses: actions/checkout@v4
with:
path: '1_21_3'
- name: '[Non-LTS - 1.21.1] Checkout for CI 🛎️'
uses: actions/checkout@v4
with:
ref: 'minecraft/1.21.1'
path: '1_21_1'
- name: '[LTS - 1.20.1] Checkout for CI 🛎️'
uses: actions/checkout@v4
with:
ref: 'minecraft/1.20.1'
path: '1_20_1'
- name: '[Current - 1.21.3] Build 🛎️'
run: |
mkdir target
cd 1_21_3
./gradlew clean build publish -Dforce-hide-version-meta=1
cp -rf target/* ../target/
cd ..
env:
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: '[Non-LTS - 1.21.1] Build 🛎️'
run: |
cd 1_21_1
./gradlew clean build publish -Dforce-hide-version-meta=1
cp -rf target/* ../target/
cd ..
env:
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: '[LTS - 1.20.1] Build 🛎️'
run: |
cd 1_20_1
./gradlew clean build publish -Dforce-hide-version-meta=1
cp -rf target/* ../target/
cd ..
env:
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: 'Publish Test Report 📊'
uses: mikepenz/action-junit-report@v4
uses: mikepenz/action-junit-report@v5
if: success() || failure() # Continue on failure
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
@@ -40,14 +79,30 @@ jobs:
version: ${{ github.event.release.tag_name }}
changelog: ${{ github.event.release.body }}
distro-names: |
paper
paper-1.21.3
fabric-1.21.3
paper-1.21.1
fabric-1.21.1
paper-1.20.1
fabric-1.20.1
distro-groups: |
paper
fabric
paper
fabric
paper
fabric
distro-descriptions: |
Paper
Paper 1.21.3
Fabric 1.21.3
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 }}.jar
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.21.3.jar
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.3.jar
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.21.1.jar
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.20.1.jar
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar

View File

@@ -13,7 +13,8 @@ permissions:
contents: write
jobs:
deploy-wiki:
update-docs:
name: 'Update Docs'
runs-on: ubuntu-latest
steps:
- name: 'Checkout for CI 🛎️'

View File

@@ -1,8 +1,8 @@
<!--suppress ALL -->
<p align="center">
<img src="images/banner.png" alt="HuskSync" />
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci.yml?branch=master&logo=github"/>
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci_master.yml">
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci_master.yml?branch=master&logo=github"/>
</a>
<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" />
@@ -43,8 +43,28 @@
**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.3 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **December 2024** (Non-LTS) |
| 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) &ndash; Supported for up to 12-18 months
* Non-Long Term Support (Non-LTS) &ndash; Supported for 3-6 months
Verify your purchase on Discord and [Download HuskSync](https://william278.net/project/husksync#download) for your server.
## 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.
2. Start, then stop every server to let HuskSync generate the config file.
@@ -52,7 +72,7 @@ Requires a MySQL/Mongo/PostgreSQL database, a Redis (v5.0+) server and a network
4. Start every server again and synchronization will begin.
## 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
./gradlew clean build

View File

@@ -1,10 +1,10 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.gradleup.shadow' version '8.3.4'
id 'org.cadixdev.licenser' version '0.6.1' apply false
id 'fabric-loom' version '1.7-SNAPSHOT' apply false
id 'org.ajoberstar.grgit' version '5.2.2'
id 'fabric-loom' version "$fabric_loom_version" apply false
id 'org.ajoberstar.grgit' version '5.3.0'
id 'maven-publish'
id 'java'
}
@@ -18,6 +18,7 @@ ext {
set 'version', version.toString()
set 'description', description.toString()
set 'minecraft_version', minecraft_version.toString()
set 'jedis_version', jedis_version.toString()
set 'mysql_driver_version', mysql_driver_version.toString()
set 'mariadb_driver_version', mariadb_driver_version.toString()
@@ -58,12 +59,13 @@ publishing {
}
allprojects {
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.gradleup.shadow'
apply plugin: 'org.cadixdev.licenser'
apply plugin: 'java'
compileJava.options.encoding = 'UTF-8'
compileJava.options.release.set 17
compileJava.options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
compileJava.options.release.set Integer.parseInt(rootProject.ext.javaVersion)
javadoc.options.encoding = 'UTF-8'
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
@@ -73,6 +75,7 @@ allprojects {
maven { url 'https://repo.william278.net/releases/' }
maven { url 'https://oss.sonatype.org/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.codemc.io/repository/maven-public/' }
maven { url 'https://repo.minebench.de/' }
@@ -83,9 +86,9 @@ allprojects {
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.3'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.3'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.3'
}
test {
@@ -125,9 +128,9 @@ subprojects {
archiveClassifier.set('')
}
// Append the Minecraft to the version for Fabric projects
if (project.name == 'fabric') {
version += "+mc.${fabric_minecraft_version}"
// Append the compatible Minecraft version to the version
if (['bukkit', 'paper', 'fabric'].contains(project.name)) {
version += "+mc.${minecraft_version}"
}
// API publishing
@@ -163,7 +166,7 @@ subprojects {
mavenJavaBukkit(MavenPublication) {
groupId = 'net.william278.husksync'
artifactId = 'husksync-bukkit'
version = "$rootProject.version"
version = "$rootProject.version+${minecraft_version}"
artifact shadowJar
artifact sourcesJar
artifact javadocJar
@@ -176,7 +179,7 @@ subprojects {
mavenJavaFabric(MavenPublication) {
groupId = 'net.william278.husksync'
artifactId = 'husksync-fabric'
version = "$rootProject.version+${fabric_minecraft_version}"
version = "$rootProject.version+${minecraft_version}"
artifact remapJar
artifact sourcesJar
artifact javadocJar
@@ -190,10 +193,15 @@ subprojects {
clean.delete "$rootDir/target"
}
logger.lifecycle("Building HuskSync ${version} by William278")
logger.lifecycle("Building HuskSync ${version} by William278 for Minecraft ${minecraft_version}")
@SuppressWarnings('GrMethodMayBeStatic')
def versionMetadata() {
// If the force-hide-version-meta environment variable is set, return ''
if (System.getProperty('force-hide-version-meta') != null) {
return ''
}
// Require grgit
if (grgit == null) {
return '-unknown'

View File

@@ -1,25 +1,25 @@
dependencies {
implementation project(path: ':common')
implementation 'net.william278.uniform:uniform-bukkit:1.2.1'
implementation 'net.william278.uniform:uniform-bukkit:1.2.2'
implementation 'net.william278:mpdbdataconverter:1.0.1'
implementation 'net.william278:hsldataconverter:1.0'
implementation 'net.william278:mapdataapi:1.0.3'
implementation 'org.bstats:bstats-bukkit:3.0.2'
implementation 'net.kyori:adventure-platform-bukkit:4.3.3'
implementation 'net.william278:mapdataapi:2.0'
implementation 'org.bstats:bstats-bukkit:3.1.0'
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
implementation 'dev.triumphteam:triumph-gui:3.1.10'
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
implementation 'de.tr7zw:item-nbt-api:2.13.1'
implementation 'de.tr7zw:item-nbt-api:2.14.0'
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.comphenix.protocol:ProtocolLib:5.1.0'
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 'net.william278:minedown:1.8.2'
compileOnly 'de.exlll:configlib-yaml:4.5.0'
compileOnly 'com.zaxxer:HikariCP:5.1.0'
compileOnly 'com.zaxxer:HikariCP:6.1.0'
compileOnly 'net.william278:DesertWell:2.0.4'
compileOnly 'net.william278:AdvancementAPI:97a9583413'
compileOnly "redis.clients:jedis:$jedis_version"

View File

@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import de.tr7zw.changeme.nbtapi.utils.DataFixerUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -78,6 +79,7 @@ import java.util.stream.Collectors;
@Getter
@NoArgsConstructor
@SuppressWarnings("unchecked")
public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.Supplier,
BukkitEventDispatcher, BukkitMapPersister {
@@ -127,6 +129,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
loadSettings();
loadLocales();
loadServer();
validateConfigFiles();
});
this.eventListener = createEventListener();
@@ -137,6 +140,9 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
public void onEnable() {
this.audiences = BukkitAudiences.create(this);
// Check compatibility
checkCompatibility();
// Register commands
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));
@@ -327,6 +333,21 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
return Version.fromString(getServer().getBukkitVersion());
}
public int getDataVersion(@NotNull Version mcVersion) {
return switch (mcVersion.toStringWithoutMetadata()) {
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5;
case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1;
case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2;
case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2;
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
case "1.21", "1.21.1" -> DataFixerUtil.VERSION1_21;
case "1.21.2", "1.21.3" -> DataFixerUtil.VERSION1_21_2;
default -> DataFixerUtil.getCurrentVersion();
};
}
@NotNull
@Override
public String getPlatformType() {

View File

@@ -25,9 +25,7 @@ import com.google.gson.annotations.SerializedName;
import de.tr7zw.changeme.nbtapi.NBTCompound;
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
import lombok.*;
import net.kyori.adventure.util.TriState;
import net.william278.desertwell.util.ThrowingConsumer;
import net.william278.desertwell.util.Version;
import net.william278.husksync.BukkitHuskSync;
import net.william278.husksync.HuskSync;
import net.william278.husksync.adapter.Adaptable;
@@ -37,9 +35,10 @@ import org.bukkit.*;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.EquipmentSlotGroup;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.potion.PotionEffect;
@@ -49,7 +48,6 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import org.jetbrains.annotations.Unmodifiable;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -157,20 +155,17 @@ public abstract class BukkitData implements Data {
this.clearInventoryCraftingSlots(player);
player.setItemOnCursor(null);
player.getInventory().setContents(plugin.setMapViews(getContents()));
player.updateInventory();
player.getInventory().setHeldItemSlot(heldItemSlot);
//noinspection UnstableApiUsage
player.updateInventory();
}
private void clearInventoryCraftingSlots(@NotNull Player player) {
try {
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
if (inventory.getType() == InventoryType.CRAFTING) {
for (int slot = 0; slot < 5; slot++) {
inventory.setItem(slot, null);
}
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
if (inventory.getType() == InventoryType.CRAFTING) {
for (int slot = 0; slot < 5; slot++) {
inventory.setItem(slot, null);
}
} catch (Throwable e) {
// Ignore any exceptions
}
}
@@ -283,7 +278,7 @@ public abstract class BukkitData implements Data {
public List<Effect> getActiveEffects() {
return effects.stream()
.map(potionEffect -> new Effect(
potionEffect.getType().getName().toLowerCase(Locale.ENGLISH),
potionEffect.getType().getKey().toString(),
potionEffect.getAmplifier(),
potionEffect.getDuration(),
potionEffect.isAmbient(),
@@ -458,9 +453,10 @@ public abstract class BukkitData implements Data {
Registry.STATISTIC.forEach(id -> {
switch (id.getType()) {
case UNTYPED -> addStatistic(player, id, generic);
case BLOCK -> addMaterialStatistic(player, id, blocks, true);
case ITEM -> addMaterialStatistic(player, id, items, false);
case ENTITY -> addEntityStatistic(player, id, entities);
// Todo - Future - Use BLOCK and ITEM registries when API stabilizes
case BLOCK -> addStatistic(player, id, Registry.MATERIAL, blocks);
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);
@@ -481,43 +477,31 @@ public abstract class BukkitData implements Data {
}
}
private static void addMaterialStatistic(@NotNull Player p, @NotNull Statistic id,
@NotNull Map<String, Map<String, Integer>> map, boolean isBlock) {
Registry.MATERIAL.forEach(material -> {
if ((material.isBlock() && !isBlock) || (material.isItem() && isBlock)) {
return;
}
final int stat = p.getStatistic(id, material);
if (stat != 0) {
map.computeIfAbsent(id.getKey().getKey(), k -> Maps.newHashMap())
.put(material.getKey().getKey(), stat);
}
});
}
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);
private static <R extends Keyed> void addStatistic(@NotNull Player p, @NotNull Statistic id,
@NotNull Registry<R> registry,
@NotNull Map<String, Map<String, Integer>> map) {
registry.forEach(i -> {
try {
final int stat = i instanceof Material m ? p.getStatistic(id, m) :
(i instanceof EntityType e ? p.getStatistic(id, e) : -1);
if (stat != 0) {
map.compute(id.getKey().getKey(), (k, v) -> v == null ? Maps.newHashMap() : v)
.put(i.getKey().getKey(), stat);
}
} catch (IllegalStateException ignored) {
}
});
}
@Override
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) {
genericStatistics.forEach((id, v) -> applyStat(user, id, Statistic.Type.UNTYPED, v));
blockStatistics.forEach((id, m) -> m.forEach((b, v) -> applyStat(user, id, Statistic.Type.BLOCK, v, b)));
itemStatistics.forEach((id, m) -> m.forEach((i, v) -> applyStat(user, id, Statistic.Type.ITEM, v, i)));
entityStatistics.forEach((id, m) -> m.forEach((e, v) -> applyStat(user, id, Statistic.Type.ENTITY, v, e)));
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync p) {
genericStatistics.forEach((k, v) -> applyStat(p, user, k, Statistic.Type.UNTYPED, v));
blockStatistics.forEach((k, m) -> m.forEach((b, v) -> applyStat(p, user, k, Statistic.Type.BLOCK, v, b)));
itemStatistics.forEach((k, m) -> m.forEach((i, v) -> applyStat(p, user, k, Statistic.Type.ITEM, v, i)));
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) {
final Player player = ((BukkitUser) user).getPlayer();
final Statistic stat = matchStatistic(id);
@@ -531,7 +515,8 @@ public abstract class BukkitData implements Data {
case BLOCK, ITEM -> player.setStatistic(stat, Objects.requireNonNull(matchMaterial(key[0])), value);
case ENTITY -> player.setStatistic(stat, Objects.requireNonNull(matchEntityType(key[0])), value);
}
} catch (Throwable ignored) {
} catch (Throwable a) {
plugin.log(Level.WARNING, "Failed to apply statistic " + id, a);
}
}
@@ -566,13 +551,9 @@ public abstract class BukkitData implements Data {
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("UnstableApiUsage")
public static class Attributes extends BukkitData implements Data.Attributes, Adaptable {
private static final String EQUIPMENT_SLOT_GROUP = "org.bukkit.inventory.EquipmentSlotGroup";
private static final String EQUIPMENT_SLOT_GROUP$ANY = "ANY";
private static final String EQUIPMENT_SLOT$getGroup = "getGroup";
private static TriState USE_KEYED_MODIFIERS = TriState.NOT_SET;
private List<Attribute> attributes;
@NotNull
@@ -581,9 +562,8 @@ public abstract class BukkitData implements Data {
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
Registry.ATTRIBUTE.forEach(id -> {
final AttributeInstance instance = player.getAttribute(id);
if (instance == null || Double.compare(instance.getValue(), instance.getDefaultValue()) == 0
|| settings.isIgnoredAttribute(id.getKey().toString())) {
return; // We don't sync unmodified or disabled attributes
if (settings.isIgnoredAttribute(id.getKey().toString()) || instance == null) {
return; // We don't sync attributes not marked as to be synced
}
attributes.add(adapt(instance, settings));
});
@@ -610,6 +590,7 @@ public abstract class BukkitData implements Data {
instance.getBaseValue(),
instance.getModifiers().stream()
.filter(modifier -> !settings.isIgnoredModifier(modifier.getName()))
.filter(modifier -> modifier.getSlotGroup() != EquipmentSlotGroup.ANY)
.map(BukkitData.Attributes::adapt).collect(Collectors.toSet())
);
}
@@ -617,89 +598,47 @@ public abstract class BukkitData implements Data {
@NotNull
private static Modifier adapt(@NotNull AttributeModifier modifier) {
return new Modifier(
getModifierId(modifier),
modifier.getName(),
modifier.getKey().toString(),
modifier.getAmount(),
modifier.getOperation().ordinal(),
modifier.getSlot() != null ? modifier.getSlot().ordinal() : -1
modifier.getSlotGroup().toString()
);
}
@Nullable
private static UUID getModifierId(@NotNull AttributeModifier modifier) {
try {
return modifier.getUniqueId();
} catch (Throwable e) {
return null;
}
}
private static boolean useKeyedModifiers(@NotNull HuskSync plugin) {
if (USE_KEYED_MODIFIERS == TriState.NOT_SET) {
boolean is1_21 = plugin.getMinecraftVersion().compareTo(Version.fromString("1.21")) >= 0;
USE_KEYED_MODIFIERS = TriState.byBoolean(is1_21);
return is1_21;
}
return Boolean.TRUE.equals(USE_KEYED_MODIFIERS.toBoolean());
}
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute,
@NotNull HuskSync plugin) {
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute) {
if (instance == null) {
return;
}
instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue());
instance.getModifiers().forEach(instance::removeModifier);
instance.setBaseValue(attribute == null ? instance.getValue() : attribute.baseValue());
if (attribute != null) {
attribute.modifiers().stream()
.filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName)
.noneMatch(n -> n.equals(mod.name())))
.distinct()
.filter(mod -> useKeyedModifiers(plugin) == !mod.hasUuid())
.forEach(mod -> instance.addModifier(adapt(mod, plugin)));
.distinct().filter(mod -> !mod.hasUuid())
.forEach(mod -> instance.addModifier(adapt(mod)));
}
}
@SuppressWarnings("JavaReflectionMemberAccess")
@NotNull
private static AttributeModifier adapt(@NotNull Modifier modifier, @NotNull HuskSync plugin) {
final int slotId = modifier.equipmentSlot();
if (useKeyedModifiers(plugin)) {
try {
// Reflexively create a modern keyed attribute modifier instance. Remove in favor of API long-term.
final EquipmentSlot slot = slotId != -1 ? EquipmentSlot.values()[slotId] : null;
final Class<?> slotGroup = Class.forName(EQUIPMENT_SLOT_GROUP);
final String modifierName = modifier.name() == null ? modifier.uuid().toString() : modifier.name();
final NamespacedKey modifierKey = Objects.requireNonNull(NamespacedKey.fromString(modifierName),
"Modifier key returned null");
final Constructor<AttributeModifier> constructor = AttributeModifier.class.getDeclaredConstructor(
NamespacedKey.class, double.class, AttributeModifier.Operation.class, slotGroup);
return constructor.newInstance(
modifierKey,
modifier.amount(),
AttributeModifier.Operation.values()[modifier.operationType()],
slot == null ? slotGroup.getField(EQUIPMENT_SLOT_GROUP$ANY).get(null)
: EquipmentSlot.class.getDeclaredMethod(EQUIPMENT_SLOT$getGroup).invoke(slot)
);
} catch (Throwable e) {
plugin.log(Level.WARNING, "Error reflectively creating keyed attribute modifier", e);
USE_KEYED_MODIFIERS = TriState.FALSE;
}
}
private static AttributeModifier adapt(@NotNull Modifier modifier) {
return new AttributeModifier(
modifier.uuid(),
modifier.name(),
Objects.requireNonNull(NamespacedKey.fromString(modifier.name())),
modifier.amount(),
AttributeModifier.Operation.values()[modifier.operationType()],
slotId != -1 ? EquipmentSlot.values()[slotId] : null
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 {
Registry.ATTRIBUTE.forEach(id -> applyAttribute(
user.getPlayer().getAttribute(id), getAttribute(id).orElse(null), plugin
));
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));
});
}
}

View File

@@ -153,21 +153,11 @@ public class BukkitSerializer {
@NotNull
private ReadWriteNBT upgradeItemData(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion)
throws NoSuchFieldException, IllegalAccessException {
return DataFixerUtil.fixUpItemData(tag, getDataVersion(mcVersion), DataFixerUtil.getCurrentVersion());
}
private int getDataVersion(@NotNull Version mcVersion) {
return switch (mcVersion.toStringWithoutMetadata()) {
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5;
case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1;
case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2;
case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2;
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
case "1.21" -> DataFixerUtil.VERSION1_21;
default -> DataFixerUtil.getCurrentVersion();
};
return DataFixerUtil.fixUpItemData(
tag,
getPlugin().getDataVersion(mcVersion),
DataFixerUtil.getCurrentVersion()
);
}
@NotNull
@@ -229,7 +219,7 @@ public class BukkitSerializer {
@Override
public BukkitData.PersistentData deserialize(@NotNull String serialized) throws DeserializationException {
return BukkitData.PersistentData.from(new NBTContainer(serialized));
return BukkitData.PersistentData.from((NBTContainer) NBT.parseNBT(serialized));
}
@NotNull

View File

@@ -78,7 +78,7 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder {
if (!editable) {
builder.disableAllInteractions();
}
final StorageGui gui = builder.enableOtherActions()
final StorageGui gui = builder
.apply(a -> a.getInventory().setContents(contents))
.title(title.toComponent()).create();
gui.setCloseGuiAction((close) -> onClose.accept(BukkitData.Items.ItemArray.adapt(

View File

@@ -51,7 +51,7 @@ public final class BukkitKeyedAdapter {
@Nullable
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) {

View File

@@ -31,7 +31,7 @@ import net.william278.mapdataapi.MapData;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.Container;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
@@ -96,7 +96,7 @@ public interface BukkitMapPersister {
}
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
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);
b.setBlockState(box);
}
@@ -122,7 +122,8 @@ public interface BukkitMapPersister {
}
// Render the map
final PersistentMapCanvas canvas = new PersistentMapCanvas(view);
final int dataVersion = getPlugin().getDataVersion(getPlugin().getMinecraftVersion());
final PersistentMapCanvas canvas = new PersistentMapCanvas(view, dataVersion);
for (MapRenderer renderer : view.getRenderers()) {
renderer.render(view, canvas, delegateRenderer);
getPlugin().debug(String.format("Rendered locked map canvas to view (#%s)", view.getId()));
@@ -140,6 +141,7 @@ public interface BukkitMapPersister {
@NotNull
private ItemStack applyMapView(@NotNull ItemStack map) {
final int dataVersion = getPlugin().getDataVersion(getPlugin().getMinecraftVersion());
final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta());
NBT.get(map, nbt -> {
if (!nbt.hasTag(MAP_DATA_KEY)) {
@@ -178,8 +180,9 @@ public interface BukkitMapPersister {
final MapData canvasData;
try {
getPlugin().debug("Deserializing map data from NBT and generating view...");
canvasData = MapData.fromByteArray(Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY),
"Map pixel data is null"));
canvasData = MapData.fromByteArray(
dataVersion,
Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY), "Pixel data null!"));
} catch (Throwable e) {
getPlugin().log(Level.WARNING, "Failed to deserialize map data from NBT", e);
return;
@@ -276,7 +279,7 @@ public interface BukkitMapPersister {
@NotNull
private static World getDefaultMapWorld() {
final World world = Bukkit.getWorlds().get(0);
final World world = Bukkit.getWorlds().getFirst();
if (world == null) {
throw new IllegalStateException("No worlds are loaded on the server!");
}
@@ -294,6 +297,7 @@ public interface BukkitMapPersister {
/**
* A {@link MapRenderer} that can be used to render persistently serialized {@link MapData} to a {@link MapView}
*/
@SuppressWarnings("deprecation")
class PersistentMapRenderer extends MapRenderer {
private final MapData canvasData;
@@ -355,13 +359,16 @@ public interface BukkitMapPersister {
/**
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
*/
@SuppressWarnings("deprecation")
class PersistentMapCanvas implements MapCanvas {
private final int mapDataVersion;
private final MapView mapView;
private final int[][] pixels = new int[128][128];
private MapCursorCollection cursors;
private PersistentMapCanvas(@NotNull MapView mapView) {
private PersistentMapCanvas(@NotNull MapView mapView, int mapDataVersion) {
this.mapDataVersion = mapDataVersion;
this.mapView = mapView;
}
@@ -383,18 +390,38 @@ public interface BukkitMapPersister {
}
@Override
@Deprecated
public void setPixel(int x, int y, byte color) {
pixels[x][y] = color;
}
@Override
@Deprecated
public byte getPixel(int x, int y) {
return (byte) pixels[x][y];
}
@Override
@Deprecated
public byte getBasePixel(int x, int y) {
return getPixel(x, y);
return (byte) pixels[x][y];
}
@Override
public void setPixelColor(int x, int y, @Nullable Color color) {
pixels[x][y] = color == null ? -1 : MapPalette.matchColor(color);
}
@Nullable
@Override
public Color getPixelColor(int x, int y) {
return MapPalette.getColor((byte) pixels[x][y]);
}
@NotNull
@Override
public Color getBasePixelColor(int x, int y) {
return MapPalette.getColor((byte) pixels[x][y]);
}
@Override
@@ -427,7 +454,7 @@ public interface BukkitMapPersister {
final String BANNER_PREFIX = "banner_";
for (int i = 0; i < getCursors().size(); 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)) {
banners.add(new MapBanner(
type.replaceAll(BANNER_PREFIX, ""),
@@ -437,8 +464,9 @@ public interface BukkitMapPersister {
cursor.getY()
));
}
}
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
return MapData.fromPixels(mapDataVersion, pixels, getDimension(), (byte) 2, banners, List.of());
}
}

View File

@@ -3,7 +3,7 @@ plugins {
}
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 'net.william278:minedown:1.8.2'
api 'org.json:json:20240303'
@@ -12,17 +12,17 @@ dependencies {
api 'de.exlll:configlib-yaml:4.5.0'
api 'net.william278:paginedown:1.1.2'
api 'net.william278:DesertWell:2.0.4'
api('com.zaxxer:HikariCP:5.1.0') {
api('com.zaxxer:HikariCP:6.1.0') {
exclude module: 'slf4j-api'
}
compileOnly 'net.william278.uniform:uniform-common:1.2.1'
compileOnly 'net.william278.uniform:uniform-common:1.2.2'
compileOnly 'com.mojang:brigadier:1.1.8'
compileOnly 'org.projectlombok:lombok:1.18.34'
compileOnly 'org.jetbrains:annotations:24.1.0'
compileOnly 'org.jetbrains:annotations:26.0.1'
compileOnly 'net.kyori:adventure-api:4.17.0'
compileOnly 'net.kyori:adventure-platform-api:4.3.3'
compileOnly 'com.google.guava:guava:33.2.1-jre'
compileOnly 'net.kyori:adventure-platform-api:4.3.4'
compileOnly 'com.google.guava:guava:33.3.1-jre'
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
compileOnly "redis.clients:jedis:$jedis_version"
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
@@ -33,10 +33,10 @@ dependencies {
testImplementation "redis.clients:jedis:$jedis_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'
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
testCompileOnly 'org.jetbrains:annotations:24.1.0'
testCompileOnly 'org.jetbrains:annotations:26.0.1'
annotationProcessor 'org.projectlombok:lombok:1.18.34'
}

View File

@@ -39,6 +39,7 @@ import net.william278.husksync.redis.RedisManager;
import net.william278.husksync.sync.DataSyncer;
import net.william278.husksync.user.ConsoleUser;
import net.william278.husksync.user.OnlineUser;
import net.william278.husksync.util.CompatibilityChecker;
import net.william278.husksync.util.LegacyConverter;
import net.william278.husksync.util.Task;
import net.william278.uniform.Uniform;
@@ -52,7 +53,8 @@ import java.util.logging.Level;
/**
* 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;
@@ -247,6 +249,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
@NotNull
Version getMinecraftVersion();
/**
* Returns the data version for a Minecraft version
*
* @param minecraftVersion the Minecraft version
* @return the data version int
*/
int getDataVersion(@NotNull Version minecraftVersion);
/**
* Returns the platform type
*
@@ -330,15 +340,19 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
private static final String FORMAT = """
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):
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
3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)
4) Check the error below for more details
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);
}

View File

@@ -30,9 +30,11 @@ import net.kyori.adventure.text.format.TextColor;
import net.william278.desertwell.about.AboutMenu;
import net.william278.desertwell.util.UpdateChecker;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.DataSnapshot;
import net.william278.husksync.database.Database;
import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.user.CommandUser;
import net.william278.husksync.util.LegacyConverter;
import net.william278.uniform.BaseCommand;
import net.william278.uniform.CommandProvider;
import net.william278.uniform.Permission;
@@ -40,8 +42,10 @@ import net.william278.uniform.element.ArgumentElement;
import org.apache.commons.text.WordUtils;
import org.jetbrains.annotations.NotNull;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -96,6 +100,7 @@ public class HuskSyncCommand extends PluginCommand {
command.addSubCommand("status", needsOp("status"), status());
command.addSubCommand("reload", needsOp("reload"), reload());
command.addSubCommand("update", needsOp("update"), update());
command.addSubCommand("forceupgrade", forceUpgrade());
command.addSubCommand("migrate", migrate());
}
@@ -182,6 +187,35 @@ public class HuskSyncCommand extends PluginCommand {
};
}
@NotNull
private CommandProvider forceUpgrade() {
return (sub) -> {
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
sub.setDefaultExecutor((ctx) -> {
final LegacyConverter converter = plugin.getLegacyConverter().orElse(null);
if (converter == null) {
return;
}
plugin.runAsync(() -> {
final Database database = plugin.getDatabase();
plugin.log(Level.INFO, "Beginning forced legacy data upgrade for all users...");
database.getAllUsers().forEach(user -> database.getLatestSnapshot(user).ifPresent(snapshot -> {
final DataSnapshot.Packed upgraded = converter.convert(
snapshot.asBytes(plugin),
UUID.randomUUID(),
OffsetDateTime.now()
);
upgraded.setSaveCause(DataSnapshot.SaveCause.CONVERTED_FROM_V2);
plugin.getDatabase().addSnapshot(user, upgraded);
plugin.getRedisManager().clearUserData(user);
}));
plugin.log(Level.INFO, "Legacy data upgrade complete!");
});
});
};
}
@NotNull
private <S> ArgumentElement<S, Migrator> migrator() {
return new ArgumentElement<>("migrator", reader -> {

View File

@@ -131,6 +131,15 @@ public interface ConfigProvider {
));
}
default void validateConfigFiles() {
// Validate server name is default
if (getServerName().equals("server")) {
getPlugin().log(Level.WARNING, "The server name set in ~/plugins/HuskSync/server.yml appears to" +
"be unchanged from the default (currently set to: \"server\"). Please check that this value has" +
"been updated to match the case-sensitive ID of this server in your proxy config file!");
}
}
/**
* Get a plugin resource
*

View File

@@ -141,6 +141,9 @@ public class Settings {
@Getter(AccessLevel.NONE)
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
public String getTableName(@NotNull Database.TableName tableName) {
return tableNames.getOrDefault(tableName.name().toLowerCase(Locale.ENGLISH), tableName.getDefaultName());
@@ -275,12 +278,19 @@ public class Settings {
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class AttributeSettings {
@Comment({"Which attributes should not be saved when syncing users. Supports wildcard matching.",
@Comment({"Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.",
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"})
@Getter(AccessLevel.NONE)
private List<String> ignoredAttributes = new ArrayList<>(List.of(""));
private List<String> syncedAttributes = new ArrayList<>(List.of(
"minecraft:generic.max_health", "minecraft:max_health",
"minecraft:generic.max_absorption", "minecraft:max_absorption",
"minecraft:generic.luck", "minecraft:luck",
"minecraft:generic.scale", "minecraft:scale",
"minecraft:generic.step_height", "minecraft:step_height",
"minecraft:generic.gravity", "minecraft:gravity"
));
@Comment({"Which modifiers should not be saved when syncing users. Supports wildcard matching.",
@Comment({"Which attribute modifiers should be saved. Supports wildcard matching.",
"(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"})
@Getter(AccessLevel.NONE)
private List<String> ignoredModifiers = new ArrayList<>(List.of(
@@ -298,7 +308,7 @@ public class Settings {
}
public boolean isIgnoredAttribute(@NotNull String attribute) {
return ignoredAttributes.stream().anyMatch(wildcard -> matchesWildcard(wildcard, attribute));
return syncedAttributes.stream().noneMatch(wildcard -> matchesWildcard(wildcard, attribute));
}
public boolean isIgnoredModifier(@NotNull String modifier) {

View File

@@ -22,9 +22,8 @@ package net.william278.husksync.data;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import net.kyori.adventure.key.Key;
import net.william278.husksync.HuskSync;
@@ -132,7 +131,7 @@ public interface Data {
/**
* 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 duration the duration of the potion effect
* @param isAmbient whether the potion effect is ambient
@@ -341,42 +340,60 @@ public interface Data {
@Getter
@Accessors(fluent = true)
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
final class Modifier {
final static String ANY_EQUIPMENT_SLOT_GROUP = "any";
@Getter(AccessLevel.NONE)
@Nullable
@SerializedName("uuid")
private UUID uuid;
private UUID uuid = null;
// Since 1.21.1: Name, amount, operation, slotGroup
@SerializedName("name")
private String name;
@SerializedName("amount")
private double amount;
@SerializedName("operation")
private int operationType;
private int operation;
@SerializedName("equipment_slot")
@Deprecated(since = "3.7")
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.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;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Modifier other) {
if (uuid == null || other.uuid == null) {
return name.equals(other.name);
if (uuid != null && other.uuid != null) {
return uuid.equals(other.uuid);
}
return uuid.equals(other.uuid);
return name.equals(other.name);
}
return super.equals(obj);
}
public double modify(double value) {
return switch (operationType) {
return switch (operation) {
case 0 -> value + amount;
case 1 -> value * amount;
case 2 -> value * (1 + amount);

View File

@@ -913,6 +913,8 @@ public class DataSnapshot {
private final boolean fireDataSaveEvent;
private static Map<String, SaveCause> registry;
/**
* Get or create a {@link SaveCause} from a name
*
@@ -921,7 +923,7 @@ public class DataSnapshot {
*/
@NotNull
public static SaveCause of(@NotNull String name) {
return new SaveCause(name.length() > 32 ? name.substring(0, 31) : name, true);
return of(name,true);
}
/**
@@ -933,7 +935,14 @@ public class DataSnapshot {
*/
@NotNull
public static SaveCause of(@NotNull String name, boolean firesSaveEvent) {
return new SaveCause(name.length() > 32 ? name.substring(0, 31) : name, firesSaveEvent);
name = name.length() > 32 ? name.substring(0, 31) : name;
if (registry == null) registry = new HashMap<>();
if (registry.containsKey(name)) return registry.get(name);
SaveCause cause = new SaveCause(name, firesSaveEvent);
registry.put(cause.name(), cause);
return cause;
}
@NotNull
@@ -944,11 +953,10 @@ public class DataSnapshot {
}
@NotNull
@ApiStatus.Obsolete
public static SaveCause[] values() {
return new SaveCause[]{
DISCONNECT, WORLD_SAVE, DEATH, SERVER_SHUTDOWN, INVENTORY_COMMAND, ENDERCHEST_COMMAND,
BACKUP_RESTORE, API, MPDB_MIGRATION, LEGACY_MIGRATION, CONVERTED_FROM_V2
};
if (registry == null) registry = new HashMap<>();
return registry.values().toArray(new SaveCause[0]);
}
}

View File

@@ -107,6 +107,14 @@ public abstract class Database {
@Blocking
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.

View File

@@ -57,11 +57,6 @@ public class MongoDbDatabase extends Database {
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
public void initialize() throws IllegalStateException {
final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials();
@@ -69,6 +64,10 @@ public class MongoDbDatabase extends Database {
ConnectionString URI = createConnectionURI(credentials);
mongoConnectionHandler = new MongoConnectionHandler(URI, credentials.getDatabase());
mongoCollectionHelper = new MongoCollectionHelper(mongoConnectionHandler);
// Check config for if tables should be created
if (!plugin.getSettings().getDatabase().isCreateTables()) return;
if (mongoCollectionHelper.getCollection(usersTable) == null) {
mongoCollectionHelper.createCollection(usersTable);
}
@@ -94,11 +93,6 @@ public class MongoDbDatabase extends Database {
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
@Override
public void ensureUser(@NotNull User user) {
@@ -136,12 +130,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Get a player by their Minecraft account {@link UUID}
*
* @param uuid Minecraft account {@link UUID} of the {@link User} to get
* @return An optional with the {@link User} present if they exist
*/
@Blocking
@Override
public Optional<User> getUser(@NotNull UUID uuid) {
@@ -158,12 +146,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Get a user by their username (<i>case-insensitive</i>)
*
* @param username Username of the {@link User} to get (<i>case-insensitive</i>)
* @return An optional with the {@link User} present if they exist
*/
@Blocking
@Override
public Optional<User> getUserByName(@NotNull String username) {
@@ -181,12 +163,24 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Get the latest data snapshot for a user.
*
* @param user The user to get data for
* @return an optional containing the {@link DataSnapshot}, if it exists, or an empty optional if it does not
*/
@Override
@NotNull
public List<User> getAllUsers() {
final List<User> users = Lists.newArrayList();
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
@Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
@@ -209,12 +203,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Get all {@link DataSnapshot} entries for a user from the database.
*
* @param user The user to get data for
* @return The list of a user's {@link DataSnapshot} entries
*/
@Blocking
@Override
@NotNull
@@ -238,13 +226,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Gets a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
*
* @param user The user to get data for
* @param versionUuid The UUID of the {@link DataSnapshot} entry to get
* @return An optional containing the {@link DataSnapshot}, if it exists
*/
@Blocking
@Override
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
@@ -266,12 +247,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
*
* @param user The user to prune data for
* @implNote Data snapshots marked as {@code pinned} are exempt from rotation
*/
@Blocking
@Override
protected void rotateSnapshots(@NotNull User user) {
@@ -297,12 +272,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Deletes a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
*
* @param user The user to get data for
* @param versionUuid The UUID of the {@link DataSnapshot} entry to delete
*/
@Blocking
@Override
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
@@ -320,14 +289,6 @@ public class MongoDbDatabase extends Database {
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
@Override
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
@@ -352,12 +313,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* <b>Internal</b> - Create user data in the database
*
* @param user The user to add data for
* @param data The {@link DataSnapshot} to set.
*/
@Blocking
@Override
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
@@ -374,12 +329,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Update a saved {@link DataSnapshot} by given version UUID
*
* @param user The user whose data snapshot
* @param data The {@link DataSnapshot} to update
*/
@Blocking
@Override
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
@@ -396,10 +345,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Wipes <b>all</b> {@link User} entries from the database.
* <b>This should only be used when preparing tables for a data migration.</b>
*/
@Blocking
@Override
public void wipeDatabase() {
@@ -410,9 +355,6 @@ public class MongoDbDatabase extends Database {
}
}
/**
* Close the database connection
*/
@Override
public void terminate() {
if (mongoConnectionHandler != null) {

View File

@@ -115,6 +115,9 @@ public class MySqlDatabase extends Database {
);
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
try (Connection connection = dataSource.getConnection()) {
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
@@ -218,6 +221,27 @@ public class MySqlDatabase extends Database {
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
@Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {

View File

@@ -51,12 +51,6 @@ public class PostgresDatabase extends Database {
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
@NotNull
private Connection getConnection() throws SQLException {
@@ -114,6 +108,9 @@ public class PostgresDatabase extends Database {
);
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
try (Connection connection = dataSource.getConnection()) {
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
@@ -140,9 +137,9 @@ public class PostgresDatabase extends Database {
// Update a user's name if it has changed in the database
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
UPDATE "%users_table%"
SET "username"=?
WHERE "uuid"=?"""))) {
UPDATE %users_table%
SET username=?
WHERE uuid=?;"""))) {
statement.setString(1, user.getUsername());
statement.setObject(2, existingUser.getUuid());
@@ -158,7 +155,7 @@ public class PostgresDatabase extends Database {
// Insert new player data into the database
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
INSERT INTO "%users_table%" ("uuid","username")
INSERT INTO %users_table% (uuid,username)
VALUES (?,?);"""))) {
statement.setObject(1, user.getUuid());
@@ -177,9 +174,9 @@ public class PostgresDatabase extends Database {
public Optional<User> getUser(@NotNull UUID uuid) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT "uuid", "username"
FROM "%users_table%"
WHERE "uuid"=?"""))) {
SELECT uuid, username
FROM %users_table%
WHERE uuid=?;"""))) {
statement.setObject(1, uuid);
@@ -200,9 +197,9 @@ public class PostgresDatabase extends Database {
public Optional<User> getUserByName(@NotNull String username) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT "uuid", "username"
FROM "%users_table%"
WHERE "username"=?"""))) {
SELECT uuid, username
FROM %users_table%
WHERE username=?;"""))) {
statement.setString(1, username);
final ResultSet resultSet = statement.executeQuery();
@@ -217,15 +214,37 @@ public class PostgresDatabase extends Database {
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
@Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT "version_uuid", "timestamp", "data"
FROM "%user_data_table%"
WHERE "player_uuid"=?
ORDER BY "timestamp" DESC
SELECT version_uuid, timestamp, data
FROM %user_data_table%
WHERE player_uuid=?
ORDER BY timestamp DESC
LIMIT 1;"""))) {
statement.setObject(1, user.getUuid());
final ResultSet resultSet = statement.executeQuery();
@@ -251,10 +270,10 @@ public class PostgresDatabase extends Database {
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT "version_uuid", "timestamp", "data"
FROM "%user_data_table%"
WHERE "player_uuid"=?
ORDER BY "timestamp" DESC;"""))) {
SELECT version_uuid, timestamp, data
FROM %user_data_table%
WHERE player_uuid=?
ORDER BY timestamp DESC;"""))) {
statement.setObject(1, user.getUuid());
final ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
@@ -278,10 +297,10 @@ public class PostgresDatabase extends Database {
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT "version_uuid", "timestamp", "data"
FROM "%user_data_table%"
WHERE "player_uuid"=? AND "version_uuid"=?
ORDER BY "timestamp" DESC
SELECT version_uuid, timestamp, data
FROM %user_data_table%
WHERE player_uuid=? AND version_uuid=?
ORDER BY timestamp DESC
LIMIT 1;"""))) {
statement.setObject(1, user.getUuid());
statement.setObject(2, versionUuid);
@@ -309,11 +328,16 @@ public class PostgresDatabase extends Database {
if (unpinnedUserData.size() > maxSnapshots) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
DELETE FROM "%user_data_table%"
WHERE "player_uuid"=?
AND "pinned" = FALSE
ORDER BY "timestamp" ASC
LIMIT %entry_count%;""".replace("%entry_count%",
WITH cte AS (
SELECT version_uuid
FROM %user_data_table%
WHERE player_uuid=?
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))))) {
statement.setObject(1, user.getUuid());
statement.executeUpdate();
@@ -329,11 +353,10 @@ public class PostgresDatabase extends Database {
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
DELETE FROM "%user_data_table%"
WHERE "player_uuid"=? AND "version_uuid"=?
LIMIT 1;"""))) {
DELETE FROM %user_data_table%
WHERE player_uuid=? AND version_uuid=?;"""))) {
statement.setObject(1, user.getUuid());
statement.setString(2, versionUuid.toString());
statement.setObject(2, versionUuid);
return statement.executeUpdate() > 0;
}
} catch (SQLException e) {
@@ -347,12 +370,12 @@ public class PostgresDatabase extends Database {
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
DELETE FROM "%user_data_table%"
WHERE "player_uuid"=? AND "timestamp" = (
SELECT "timestamp"
FROM "%user_data_table%"
WHERE "player_uuid"=? AND "timestamp" > ? AND "pinned" = FALSE
ORDER BY "timestamp" ASC
DELETE FROM %user_data_table%
WHERE player_uuid=? AND timestamp = (
SELECT timestamp
FROM %user_data_table%
WHERE player_uuid=? AND timestamp > ? AND pinned=FALSE
ORDER BY timestamp ASC
LIMIT 1
);"""))) {
statement.setObject(1, user.getUuid());
@@ -370,8 +393,8 @@ public class PostgresDatabase extends Database {
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
INSERT INTO "%user_data_table%"
("player_uuid","version_uuid","timestamp","save_cause","pinned","data")
INSERT INTO %user_data_table%
(player_uuid,version_uuid,timestamp,save_cause,pinned,data)
VALUES (?,?,?,?,?,?);"""))) {
statement.setObject(1, user.getUuid());
statement.setObject(2, data.getId());
@@ -391,9 +414,9 @@ public class PostgresDatabase extends Database {
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
UPDATE "%user_data_table%"
SET "save_cause"=?,"pinned"=?,"data"=?
WHERE "player_uuid"=? AND "version_uuid"=?
UPDATE %user_data_table%
SET save_cause=?,pinned=?,data=?
WHERE player_uuid=? AND version_uuid=?
LIMIT 1;"""))) {
statement.setString(1, data.getSaveCause().name());
statement.setBoolean(2, data.isPinned());
@@ -411,7 +434,7 @@ public class PostgresDatabase extends Database {
public void wipeDatabase() {
try (Connection connection = getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(formatStatementTables("DELETE FROM \"%user_data_table%\";"));
statement.executeUpdate(formatStatementTables("DELETE FROM %user_data_table%;"));
}
} catch (SQLException e) {
plugin.log(Level.SEVERE, "Failed to wipe the database", e);

View File

@@ -159,6 +159,7 @@ public class RedisManager extends JedisPubSub {
switch (messageType) {
case UPDATE_USER_DATA -> plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent(
user -> {
plugin.lockPlayer(user.getUuid());
try {
final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin, redisMessage.getPayload());
user.applySnapshot(data, DataSnapshot.UpdateCause.UPDATED);

View File

@@ -21,6 +21,8 @@ package net.william278.husksync.redis;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
import net.william278.husksync.HuskSync;
import net.william278.husksync.adapter.Adaptable;
import org.jetbrains.annotations.NotNull;
@@ -34,6 +36,8 @@ public class RedisMessage implements Adaptable {
@SerializedName("target_uuid")
private UUID targetUuid;
@Getter
@Setter
@SerializedName("payload")
private byte[] payload;
@@ -72,14 +76,6 @@ public class RedisMessage implements Adaptable {
this.targetUuid = targetUuid;
}
public byte[] getPayload() {
return payload;
}
public void setPayload(byte[] payload) {
this.payload = payload;
}
public enum Type {
UPDATE_USER_DATA,

View File

@@ -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);
}
}
}

View File

@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -82,7 +83,7 @@ public class DataDumper {
@NotNull
public String toWeb() {
try {
final URL url = new URL(LOGS_SITE_ENDPOINT);
final URL url = URI.create(LOGS_SITE_ENDPOINT).toURL();
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);

View File

@@ -0,0 +1,2 @@
# File used for checking Minecraft server compatibility with this version of HuskSync
minecraft_version: '${minecraft_version}'

View File

@@ -53,12 +53,12 @@ Add the repository to your `pom.xml` as per below. You can alternatively specify
</repository>
</repositories>
```
Add the dependency to your `pom.xml` as per below. Replace `VERSION` with the latest version of HuskSync (without the v): ![Latest version](https://img.shields.io/github/v/tag/WiIIiam278/HuskSync?color=%23282828&label=%20&style=flat-square). 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): ![Latest version](https://img.shields.io/github/v/tag/WiIIiam278/HuskSync?color=%23282828&label=%20&style=flat-square). 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
<dependency>
<groupId>net.william278.husksync</groupId>
<artifactId>husksync-PLATFORM</artifactId>
<version>VERSION</version>
<version>HUSKSYNC_VERSION+MINECRAFT_VERSION</version>
<scope>provided</scope>
</dependency>
```
@@ -76,11 +76,11 @@ allprojects {
}
}
```
Add the dependency as per below. Replace `VERSION` with the latest version of HuskSync (without the v): ![Latest version](https://img.shields.io/github/v/tag/WiIIiam278/HuskSync?color=%23282828&label=%20&style=flat-square). 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): ![Latest version](https://img.shields.io/github/v/tag/WiIIiam278/HuskSync?color=%23282828&label=%20&style=flat-square). 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
dependencies {
compileOnly 'net.william278.husksync:husksync-PLATFORM:VERSION'
compileOnly 'net.william278.husksync:husksync-PLATFORM:HUSKSYNC_VERSION+MINECRAFT_VERSION'
}
```
</details>

28
docs/Compatibility.md Normal file
View File

@@ -0,0 +1,28 @@
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.3 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **December 2024** (Non-LTS) |
| 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) &ndash; Supported for up to 12-18 months
* Non-Long Term Support (Non-LTS) &ndash; Supported for 3-6 months
## Incompatible versions
This plugin does not support the following software-Minecraft version combinations. The plugin will fail to load if you attempt to run it with these versions. Apologies for the inconvenience.
| Minecraft | Server Software | Notes |
|-------------------|-------------------------------------------|----------------------------------------|
| 1.19.4 | Only: `Purpur, Pufferfish`&dagger; | Older Paper builds also not supported. |
| 1.19.3 | Only: `Paper, Purpur, Pufferfish`&dagger; | 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 |
&dagger;Further downstream forks of this server software are also affected.

View File

@@ -136,10 +136,22 @@ synchronization:
- '*'
# Configuration for how to sync attributes
attributes:
# Which attributes should not be saved when syncing users. Supports wildcard matching.
# Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
ignored_attributes: []
# Which modifiers should not be saved when syncing users. Supports wildcard matching.
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

View File

@@ -3,6 +3,7 @@ Welcome! This is the plugin documentation for HuskSync v3.x+. Please click throu
## Guides
* 📚 [[Setup]]
* ⚠️ [[Compatibility]]
* 📄 [[Config File]]
* 🔗 [[Troubleshooting]]
* ↪️ [[Data Rotation]]

View File

@@ -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
> **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+)
* **OR** a MariaDB, PostrgreSQL or MongoDB database, which are also supported
* A Redis Database (v5.0+) &mdash; 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+)
* **OR** a network of Fabric servers, connected by a Fabric proxy (Minecraft v1.20.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 Velocity-based proxy
## Setup Instructions
### 1. Install the jar

View File

@@ -25,3 +25,17 @@ If you are hosting your Redis server on the same node as your servers, you need
### MySQL connection problems on Pterodactyl
If you have more than one MySQL server connected to your panel, you may need to set `useSSL=true` in the parameters.
### Issues with player data going out of sync during a server restart
This can happen due to the way in which your server restarts. If your server uses either:
* `/restart` (this is a weird Spigot command that uses legacy bash scripting)
* ANY restart plugin, e.g. UltimateAutoRestart (these basically execute an API-called restart using the same legacy bash logic as per above)
These are **not compatible** with HuskSync in most cases due to the way in which this causes restart servers causing shutdown logic to process in strange and unpredictable orders, usually before HuskSync has had a chance to scan and perform its shutdown logic. To safely restart your server, please use:
* A Pterodactyl task to perform a Restart. This executes the Power Action program stopcode (and then execute the startup command when the container has terminated)
* A cronjob to send a stop command / Power Action program stopcode, listen for the service to fully terminate, and then execute your startup command
* For manual restarts, executing `/stop` and starting your server up with the startup command is totally fine.
It's not a great idea to use a plugin to handle restarts. Plugins are only able to operate when your server is turned on and must rely on scripts which don't safely shutdown servers when restarting.

View File

@@ -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`&dagger; | Older Paper builds also not supported. |
| 1.19.3 | Only: `Paper, Purpur, Pufferfish`&dagger; | 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 |
&dagger;Further downstream forks of this server software are also affected.

View File

@@ -1,5 +1,6 @@
## Guides
* 📚 [[Setup]]
* ⚠️ [[Compatibility]]
* 📄 [[Config File]]
* 🔗 [[Troubleshooting]]
* ↪️ [[Data Rotation]]

View File

@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '1.7-SNAPSHOT'
id 'fabric-loom' version "$fabric_loom_version"
}
apply plugin: 'fabric-loom'
@@ -11,14 +11,14 @@ repositories {
}
dependencies {
minecraft "com.mojang:minecraft:${fabric_minecraft_version}"
minecraft "com.mojang:minecraft:${minecraft_version}"
mappings "net.fabricmc:yarn:${fabric_yarn_mappings}:v2"
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("eu.pb4:sgui:${sgui_version}")
modImplementation include('net.william278.uniform:uniform-fabric:1.2.1+1.20.1')
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
modImplementation include('net.william278.uniform:uniform-fabric:1.2.2+1.21.3')
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
implementation include('org.apache.commons:commons-pool2:2.12.0')
@@ -28,7 +28,7 @@ dependencies {
implementation include("org.postgresql:postgresql:$postgres_driver_version")
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
compileOnly 'org.jetbrains:annotations:24.1.0'
compileOnly 'org.jetbrains:annotations:26.0.1'
compileOnly 'net.william278:DesertWell:2.0.4'
compileOnly 'org.projectlombok:lombok:1.18.34'

View File

@@ -32,7 +32,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
import net.minecraft.server.MinecraftServer;
import net.william278.desertwell.util.Version;
import net.william278.husksync.adapter.DataAdapter;
@@ -78,11 +78,24 @@ import java.util.logging.Level;
@Getter
@NoArgsConstructor
@SuppressWarnings("unchecked")
public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, FabricTask.Supplier,
FabricEventDispatcher {
private static final String PLATFORM_TYPE_ID = "fabric";
private static final int VERSION1_16_5 = 2586;
private static final int VERSION1_17_1 = 2730;
private static final int VERSION1_18_2 = 2975;
private static final int VERSION1_19_2 = 3120;
private static final int VERSION1_19_4 = 3337;
private static final int VERSION1_20_1 = 3465;
private static final int VERSION1_20_2 = 3578;
private static final int VERSION1_20_4 = 3700;
private static final int VERSION1_20_5 = 3837;
private static final int VERSION1_21_1 = 3955;
private static final int VERSION1_21_3 = 4082; // Current
private final TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
);
@@ -125,6 +138,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
loadSettings();
loadLocales();
loadServer();
validateConfigFiles();
});
// Register commands
@@ -142,7 +156,10 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
private void onEnable() {
// Initial plugin setup
this.audiences = FabricServerAudiences.of(minecraftServer);
this.audiences = MinecraftServerAudiences.of(minecraftServer);
// Check compatibility
checkCompatibility();
// Prepare data adapter
initialize("data adapter", (plugin) -> {
@@ -211,7 +228,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
log(Level.WARNING, """
**************
WARNING:
HuskSync for Fabric is still in an alpha state and is
not considered production ready.
**************""");
@@ -271,6 +288,15 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
return FabricUniform.getInstance(mod.getMetadata().getId());
}
@NotNull
@Override
public Map<Identifier, Data> getPlayerCustomDataStore(@NotNull OnlineUser user) {
return playerCustomDataStore.compute(
user.getUuid(),
(uuid, data) -> data == null ? Maps.newHashMap() : data
);
}
@Override
@Nullable
public InputStream getResource(@NotNull String name) {
@@ -333,6 +359,24 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
return Version.fromString(minecraftServer.getVersion());
}
@NotNull
public int getDataVersion(@NotNull Version mcVersion) {
return switch (mcVersion.toStringWithoutMetadata()) {
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
case "1.17", "1.17.1" -> VERSION1_17_1;
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
case "1.19.4" -> VERSION1_19_4;
case "1.20", "1.20.1" -> VERSION1_20_1;
case "1.20.2" -> VERSION1_20_2;
case "1.20.4" -> VERSION1_20_4;
case "1.20.5", "1.20.6" -> VERSION1_20_5;
case "1.21", "1.21.1" -> VERSION1_21_1;
case "1.21.2", "1.21.3" -> VERSION1_21_3;
default -> VERSION1_21_3; // Current supported ver
};
}
@NotNull
@Override
public String getPlatformType() {

View File

@@ -24,10 +24,9 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.minecraft.advancement.AdvancementProgress;
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.EntityAttributeInstance;
import net.minecraft.entity.attribute.EntityAttributeModifier;
@@ -35,20 +34,22 @@ import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.player.HungerManager;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.stat.StatType;
import net.minecraft.stat.Stats;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.TeleportTarget;
import net.william278.desertwell.util.ThrowingConsumer;
import net.william278.husksync.FabricHuskSync;
import net.william278.husksync.HuskSync;
import net.william278.husksync.adapter.Adaptable;
import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings;
import net.william278.husksync.mixins.HungerManagerMixin;
import net.william278.husksync.user.FabricUser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -87,13 +88,10 @@ public abstract class FabricData implements Data {
stack.getItem().toString(),
stack.getCount(),
stack.getName().getString(),
Optional.ofNullable(stack.getSubNbt(ItemStack.DISPLAY_KEY))
.flatMap(display -> Optional.ofNullable(display.get(ItemStack.LORE_KEY))
.map(lore -> ((List<String>) lore).stream().toList())) //todo check this is ok
.orElse(null),
stack.getEnchantments().stream()
.map(element -> EnchantmentHelper.getIdFromNbt((NbtCompound) element))
.filter(Objects::nonNull).map(Identifier::toString)
stack.getComponents().get(DataComponentTypes.LORE).lines().stream().map(Text::getString).toList(),
stack.getEnchantments().getEnchantments().stream()
.map(RegistryEntry::getIdAsString)
.filter(Objects::nonNull)
.toList()
) : null)
.toArray(Stack[]::new);
@@ -160,7 +158,7 @@ public abstract class FabricData implements Data {
@Override
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
final ServerPlayerEntity player = user.getPlayer();
player.playerScreenHandler.clearCraftingSlots();
player.playerScreenHandler.getCraftingInput().clear();
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
final ItemStack[] items = getContents();
for (int slot = 0; slot < player.getInventory().size(); slot++) {
@@ -248,7 +246,7 @@ public abstract class FabricData implements Data {
.map(effect -> {
final StatusEffect type = matchEffectType(effect.type());
return type != null ? new StatusEffectInstance(
type,
RegistryEntry.of(type),
effect.duration(),
effect.amplifier(),
effect.isAmbient(),
@@ -270,9 +268,10 @@ public abstract class FabricData implements Data {
@Override
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
final ServerPlayerEntity player = user.getPlayer();
final List<StatusEffect> effectsToRemove = player.getActiveStatusEffects().entrySet().stream()
.filter(e -> !e.getValue().isAmbient()).map(Map.Entry::getKey).toList();
effectsToRemove.forEach(player::removeStatusEffect);
//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);
}
@@ -282,7 +281,7 @@ public abstract class FabricData implements Data {
public List<Effect> getActiveEffects() {
return effects.stream()
.map(potionEffect -> {
final String key = getEffectId(potionEffect.getEffectType());
final String key = getEffectId(potionEffect.getEffectType().value());
return key != null ? new Effect(
key,
potionEffect.getAmplifier(),
@@ -310,16 +309,16 @@ public abstract class FabricData implements Data {
public static FabricData.Advancements adapt(@NotNull ServerPlayerEntity player) {
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
final List<Advancement> advancements = Lists.newArrayList();
forEachAdvancement(server, advancement -> {
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancement);
forEachAdvancementEntry(server, advancementEntry -> {
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancementEntry);
final Map<String, Date> awardedCriteria = Maps.newHashMap();
advancementProgress.getObtainedCriteria().forEach((criteria) -> awardedCriteria.put(criteria,
advancementProgress.getEarliestProgressObtainDate()));
Date.from(advancementProgress.getEarliestProgressObtainDate())));
// Only save the advancement if criteria has been completed
if (!awardedCriteria.isEmpty()) {
advancements.add(Advancement.adapt(advancement.getId().toString(), awardedCriteria));
advancements.add(Advancement.adapt(advancementEntry.id().asString(), awardedCriteria));
}
});
return new FabricData.Advancements(advancements);
@@ -334,10 +333,10 @@ public abstract class FabricData implements Data {
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
final ServerPlayerEntity player = user.getPlayer();
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
plugin.runAsync(() -> forEachAdvancement(server, advancement -> {
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancement);
plugin.runAsync(() -> forEachAdvancementEntry(server, advancementEntry -> {
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancementEntry);
final Optional<Advancement> record = completed.stream()
.filter(r -> r.getKey().equals(advancement.getId().toString()))
.filter(r -> r.getKey().equals(advancementEntry.id().toString()))
.findFirst();
if (record.isEmpty()) {
return;
@@ -346,7 +345,7 @@ public abstract class FabricData implements Data {
final Map<String, Date> criteria = record.get().getCompletedCriteria();
final List<String> awarded = Lists.newArrayList(progress.getObtainedCriteria());
this.setAdvancement(
plugin, advancement, player, user,
plugin, advancementEntry, player, user,
criteria.keySet().stream().filter(key -> !awarded.contains(key)).toList(),
awarded.stream().filter(key -> !criteria.containsKey(key)).toList()
);
@@ -354,7 +353,7 @@ public abstract class FabricData implements Data {
}
private void setAdvancement(@NotNull FabricHuskSync plugin,
@NotNull net.minecraft.advancement.Advancement advancement,
@NotNull net.minecraft.advancement.AdvancementEntry advancementEntry,
@NotNull ServerPlayerEntity player,
@NotNull FabricUser user,
@NotNull List<String> toAward,
@@ -366,8 +365,8 @@ public abstract class FabricData implements Data {
// Award and revoke advancement criteria
final PlayerAdvancementTracker progress = player.getAdvancementTracker();
toAward.forEach(a -> progress.grantCriterion(advancement, a));
toRevoke.forEach(r -> progress.revokeCriterion(advancement, r));
toAward.forEach(a -> progress.grantCriterion(advancementEntry, a));
toRevoke.forEach(r -> progress.revokeCriterion(advancementEntry, r));
// Restore player exp level & progress
if (!toAward.isEmpty()
@@ -378,9 +377,9 @@ public abstract class FabricData implements Data {
});
}
// Performs a consuming function for every advancement registered on the server
private static void forEachAdvancement(@NotNull MinecraftServer server,
@NotNull ThrowingConsumer<net.minecraft.advancement.Advancement> con) {
// Performs a consuming function for every advancement entry registered on the server
private static void forEachAdvancementEntry(@NotNull MinecraftServer server,
@NotNull ThrowingConsumer<net.minecraft.advancement.AdvancementEntry> con) {
server.getAdvancementLoader().getAdvancements().forEach(con);
}
@@ -423,9 +422,9 @@ public abstract class FabricData implements Data {
player.getWorld(), "World is null"
).getRegistryKey().getValue().toString(),
UUID.nameUUIDFromBytes(
player.getWorld().getDimensionKey().getValue().toString().getBytes()
player.getWorld().getDimensionEntry().getIdAsString().getBytes()
),
player.getWorld().getDimensionKey().getValue().toString()
player.getWorld().getDimensionEntry().getIdAsString()
)
);
}
@@ -436,18 +435,15 @@ public abstract class FabricData implements Data {
final MinecraftServer server = plugin.getMinecraftServer();
try {
player.dismountVehicle();
FabricDimensions.teleport(
player,
server.getWorld(server.getWorldRegistryKeys().stream()
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
.findFirst().orElseThrow(
() -> new IllegalStateException("Invalid world")
)),
player.teleportTo(
new TeleportTarget(
new Vec3d(x, y, z),
Vec3d.ZERO,
yaw,
pitch
server.getWorld(server.getWorldRegistryKeys().stream()
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
.findFirst().orElseThrow(
() -> new IllegalStateException("Invalid world")
)),
player,
TeleportTarget.NO_OP
)
);
} catch (Throwable e) {
@@ -576,19 +572,19 @@ public abstract class FabricData implements Data {
@NotNull
public static FabricData.Attributes adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
final List<Attribute> attributes = Lists.newArrayList();
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
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);
if (instance == null || key == null) {
if (instance == null || key == null || settings.isIgnoredAttribute(key.asString())) {
return;
}
final Set<Modifier> modifiers = Sets.newHashSet();
instance.getModifiers().forEach(modifier -> modifiers.add(new Modifier(
modifier.getId(),
modifier.getName(),
modifier.getValue(),
modifier.getOperation().getId(),
-1
modifier.id().toString(),
modifier.value(),
modifier.operation().getId(),
Modifier.ANY_EQUIPMENT_SLOT_GROUP
)));
attributes.add(new Attribute(
key.toString(),
@@ -615,10 +611,17 @@ public abstract class FabricData implements Data {
@Override
protected void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
Registries.ATTRIBUTE.forEach(id -> applyAttribute(
user.getPlayer().getAttributeInstance(id),
getAttribute(id).orElse(null)
));
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
Registries.ATTRIBUTE.forEach(id -> {
final Identifier key = Registries.ATTRIBUTE.getId(id);
if (key == null || settings.isIgnoredAttribute(key.toString())) {
return;
}
applyAttribute(
user.getPlayer().getAttributeInstance(RegistryEntry.of(id)),
getAttribute(id).orElse(null)
);
});
}
@@ -627,14 +630,13 @@ public abstract class FabricData implements Data {
if (instance == null) {
return;
}
instance.setBaseValue(attribute == null ? instance.getAttribute().getDefaultValue() : attribute.baseValue());
instance.getModifiers().forEach(instance::removeModifier);
instance.setBaseValue(attribute == null ? instance.getValue() : attribute.baseValue());
if (attribute != null) {
attribute.modifiers().forEach(modifier -> instance.addPersistentModifier(new EntityAttributeModifier(
modifier.uuid(),
modifier.name(),
attribute.modifiers().forEach(modifier -> instance.addTemporaryModifier(new EntityAttributeModifier(
Identifier.of(modifier.uuid().toString()),
modifier.amount(),
EntityAttributeModifier.Operation.fromId(modifier.operationType())
EntityAttributeModifier.Operation.ID_TO_VALUE.apply(modifier.operation())
)));
}
}
@@ -692,7 +694,7 @@ public abstract class FabricData implements Data {
@NotNull
public static FabricData.Hunger adapt(@NotNull ServerPlayerEntity player) {
final HungerManager hunger = player.getHungerManager();
return from(hunger.getFoodLevel(), hunger.getSaturationLevel(), hunger.getExhaustion());
return from(hunger.getFoodLevel(), hunger.getSaturationLevel(), ((HungerManagerMixin) hunger).getExhaustion());
}
@NotNull
@@ -706,7 +708,7 @@ public abstract class FabricData implements Data {
final HungerManager hunger = player.getHungerManager();
hunger.setFoodLevel(foodLevel);
hunger.setSaturationLevel(saturation);
hunger.setExhaustion(exhaustion);
((HungerManagerMixin) hunger).setExhaustion(exhaustion);
}
}

View File

@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
import net.minecraft.datafixer.TypeReferences;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.*;
import net.minecraft.registry.DynamicRegistryManager;
import net.william278.desertwell.util.Version;
import net.william278.husksync.FabricHuskSync;
import net.william278.husksync.HuskSync;
@@ -94,7 +95,7 @@ public abstract class FabricSerializer {
public String serialize(@NotNull FabricData.Items.Inventory data) throws SerializationException {
try {
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());
return root.toString();
} catch (Throwable e) {
@@ -132,7 +133,7 @@ public abstract class FabricSerializer {
@Override
public String serialize(@NotNull FabricData.Items.EnderChest data) throws SerializationException {
try {
return serializeItemArray(data.getContents()).toString();
return serializeItemArray(data.getContents(), (FabricHuskSync) getPlugin()).toString();
} catch (Throwable e) {
throw new SerializationException("Failed to serialize ender chest item NBT to string", e);
}
@@ -141,17 +142,6 @@ public abstract class FabricSerializer {
private interface ItemDeserializer {
int VERSION1_16_5 = 2586;
int VERSION1_17_1 = 2730;
int VERSION1_18_2 = 2975;
int VERSION1_19_2 = 3120;
int VERSION1_19_4 = 3337;
int VERSION1_20_1 = 3465;
int VERSION1_20_2 = 3578; // Future
int VERSION1_20_4 = 3700; // Future
int VERSION1_20_5 = 3837; // Future
int VERSION1_21 = 3953; // Future
@NotNull
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
try {
@@ -161,9 +151,10 @@ public abstract class FabricSerializer {
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
itemList.forEach(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));
return contents;
@@ -174,18 +165,18 @@ public abstract class FabricSerializer {
// Serialize items slot-by-slot
@NotNull
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items) {
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items, @NotNull FabricHuskSync plugin) {
final NbtCompound container = new NbtCompound();
container.putInt("size", items.length);
final NbtList itemList = new NbtList();
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
for (int i = 0; i < items.length; i++) {
final ItemStack item = items[i];
if (item == null || item.isEmpty()) {
continue;
}
NbtCompound entry = new NbtCompound();
NbtCompound entry = (NbtCompound) item.toNbt(registryManager);
entry.putInt("Slot", i);
item.writeNbt(entry);
itemList.add(entry);
}
container.put(ITEMS_TAG, itemList);
@@ -198,6 +189,7 @@ public abstract class FabricSerializer {
final int size = items.getInt("size");
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
final ItemStack[] itemStacks = new ItemStack[size];
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
Arrays.fill(itemStacks, ItemStack.EMPTY);
for (int i = 0; i < size; i++) {
if (list.getCompound(i) == null) {
@@ -205,38 +197,21 @@ public abstract class FabricSerializer {
}
final NbtCompound compound = list.getCompound(i);
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;
}
@NotNull
@SuppressWarnings({"rawtypes", "unchecked"}) // For NBTOps lookup
private NbtCompound upgradeItemData(@NotNull NbtCompound tag, @NotNull Version mcVersion,
@NotNull FabricHuskSync plugin) {
return (NbtCompound) plugin.getMinecraftServer().getDataFixer().update(
TypeReferences.ITEM_STACK, new Dynamic<Object>((DynamicOps) NbtOps.INSTANCE, tag),
getDataVersion(mcVersion), getDataVersion(plugin.getMinecraftVersion())
plugin.getDataVersion(mcVersion), plugin.getDataVersion(plugin.getMinecraftVersion())
).getValue();
}
private int getDataVersion(@NotNull Version mcVersion) {
return switch (mcVersion.toStringWithoutMetadata()) {
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
case "1.17", "1.17.1" -> VERSION1_17_1;
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
case "1.19.4" -> VERSION1_19_4;
case "1.20", "1.20.1" -> VERSION1_20_1;
case "1.20.2" -> VERSION1_20_2; // Future
case "1.20.4" -> VERSION1_20_4; // Future
case "1.20.5", "1.20.6" -> VERSION1_20_5; // Future
case "1.21" -> VERSION1_21; // Future
default -> VERSION1_20_1; // Current supported ver
};
}
}
public static class PotionEffects extends FabricSerializer implements Serializer<FabricData.PotionEffects> {

View File

@@ -104,7 +104,7 @@ public interface FabricUserDataHolder extends UserDataHolder {
@Override
default Optional<Data.Items.EnderChest> getEnderChest() {
return Optional.of(FabricData.Items.EnderChest.adapt(
getPlayer().getEnderChestInventory().stacks
getPlayer().getEnderChestInventory().getHeldStacks()
));
}

View File

@@ -39,7 +39,6 @@ import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.BlockPos;
@@ -124,9 +123,8 @@ public class FabricEventListener extends EventListener implements LockedHandler
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
}
private TypedActionResult<ItemStack> handleItemInteract(PlayerEntity player, World world, Hand hand) {
ItemStack stackInHand = player.getStackInHand(hand);
return (cancelPlayerEvent(player.getUuid())) ? TypedActionResult.fail(stackInHand) : TypedActionResult.pass(stackInHand);
private ActionResult handleItemInteract(PlayerEntity player, World world, Hand hand) {
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
}
private boolean handleBlockBreak(World world, PlayerEntity player, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) {

View File

@@ -0,0 +1,35 @@
/*
* This file is part of HuskSync, licensed under the Apache License 2.0.
*
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.william278.husksync.mixins;
import net.minecraft.entity.player.HungerManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(HungerManager.class)
public interface HungerManagerMixin {
@Accessor
float getExhaustion();
@Accessor("exhaustion")
void setExhaustion(float exhaustion);
}

View File

@@ -20,9 +20,11 @@
package net.william278.husksync.mixins;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.network.ServerPlayerEntity;
import net.william278.husksync.event.PlayerDeathDropsCallback;
import org.jetbrains.annotations.NotNull;
@@ -54,7 +56,7 @@ public class PlayerEntityMixin {
final @Nullable ItemStack @NotNull [] toKeep = new ItemStack[inventory.size()];
for (int i = 0; i < inventory.size(); ++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;
continue;
}
@@ -69,7 +71,7 @@ public class PlayerEntityMixin {
final @Nullable ItemStack @NotNull [] toDrop = new ItemStack[inventory.size()];
for (int i = 0; i < inventory.size(); ++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;
continue;
}

View File

@@ -34,6 +34,7 @@ import net.william278.husksync.event.ItemDropCallback;
import net.william278.husksync.event.PlayerCommandCallback;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -44,19 +45,21 @@ public abstract class ServerPlayNetworkHandlerMixin {
@Shadow
public ServerPlayerEntity player;
@Shadow
public abstract void sendPacket(Packet<?> packet);
@Unique
private void sendToPlayer(Packet<?> packet) {
this.player.networkHandler.sendPacket(packet);
}
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
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);
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
if (result == ActionResult.FAIL) {
ci.cancel();
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(
-2,
1,
player.getInventory().getSlotWithStack(stack),
@@ -69,30 +72,34 @@ public abstract class ServerPlayNetworkHandlerMixin {
@Inject(method = "onClickSlot", at = @At("HEAD"), cancellable = true)
public void onClickSlot(ClickSlotC2SPacket packet, CallbackInfo ci) {
int slot = packet.getSlot();
if (slot < 0) return;
if (slot < 0) {
return;
}
ItemStack stack = this.player.getInventory().getStack(slot);
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
if (result == ActionResult.FAIL) {
ci.cancel();
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
}
}
@Inject(method = "onCreativeInventoryAction", at = @At("HEAD"), cancellable = true)
public void onCreativeInventoryAction(CreativeInventoryActionC2SPacket packet, CallbackInfo ci) {
int slot = packet.getSlot();
if (slot < 0) return;
int slot = packet.slot();
if (slot < 0) {
return;
}
ItemStack stack = this.player.getInventory().getStack(slot);
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
if (result == ActionResult.FAIL) {
ci.cancel();
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
}
}

View File

@@ -36,7 +36,7 @@ public class ServerWorldMixin {
@Shadow
private MinecraftServer server;
@Inject(method = "saveLevel", at = @At("HEAD"))
@Inject(method = "savePersistentState", at = @At("HEAD"))
public void saveLevel(CallbackInfo ci) {
if (server.isStopping() || server.isStopped()) {
return;

View File

@@ -25,7 +25,7 @@ import eu.pb4.sgui.api.elements.GuiElementInterface;
import eu.pb4.sgui.api.gui.SimpleGui;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
@@ -102,7 +102,7 @@ public class FabricUser extends OnlineUser implements FabricUserDataHolder {
this.editable = editable;
// Set title, items
this.setTitle(((FabricServerAudiences) plugin.getAudiences()).toNative(title.toComponent()));
this.setTitle(((MinecraftServerAudiences) plugin.getAudiences()).asNative(title.toComponent()));
this.setLockPlayerInventory(!editable);
for (int i = 0; i < size; i++) {
final ItemStack item = items.getContents()[i];

View File

@@ -40,7 +40,7 @@
},
"depends": {
"fabricloader": ">=${fabric_loader_version}",
"minecraft": ">=${fabric_minecraft_version}",
"minecraft": ">=${minecraft_version}",
"fabric-api": "*"
},
"suggests": {

View File

@@ -4,6 +4,7 @@
"package": "net.william278.husksync.mixins",
"compatibilityLevel": "JAVA_17",
"server": [
"HungerManagerMixin",
"ItemEntityMixin",
"PlayerEntityMixin",
"ServerPlayerEntityMixin",

View File

@@ -1,23 +1,31 @@
# Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.daemon=true
javaVersion=17
javaVersion=21
plugin_version=3.6.7
# Plugin settings
plugin_version=3.7.1
minecraft_version=1.21.3
plugin_archive=husksync
plugin_description=A modern, cross-server player data synchronization system
jedis_version=5.1.4
mysql_driver_version=9.0.0
mariadb_driver_version=3.4.1
# Drivers
jedis_version=5.2.0
mysql_driver_version=9.1.0
mariadb_driver_version=3.5.0
postgres_driver_version=42.7.3
mongodb_driver_version=5.1.2
snappy_version=1.1.10.6
fabric_minecraft_version=1.20.1
fabric_loader_version=0.15.11
fabric_yarn_mappings=1.20.1+build.10
fabric_api_version=0.92.2+1.20.1
adventure_platform_fabric_version=5.9.0
fabric_permissions_api_version=0.2-SNAPSHOT
sgui_version=1.2.2+1.20
# Spigot/Paper build settings
bukkit_spigot_api=1.21.3-R0.1-SNAPSHOT
bukkit_paper_api=1.21.3-R0.1-SNAPSHOT
# Fabric build settings
fabric_loom_version=1.8-SNAPSHOT
fabric_loader_version=0.16.9
fabric_yarn_mappings=1.21.3+build.2
fabric_api_version=0.107.0+1.21.3
fabric_adventure_platform_version=6.1.0
fabric_permissions_api_version=0.3.3
fabric_sgui_version=1.7.2+1.21.2

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

2
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.

View File

@@ -1,15 +1,15 @@
plugins {
id 'xyz.jpenilla.run-paper' version '2.3.0'
id 'xyz.jpenilla.run-paper' version '2.3.1'
}
dependencies {
implementation project(':bukkit')
compileOnly project(':common')
implementation 'net.william278.uniform:uniform-paper:1.2.1'
implementation 'net.william278.uniform:uniform-paper:1.2.2'
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT'
compileOnly 'org.jetbrains:annotations:24.1.0'
compileOnly "io.papermc.paper:paper-api:${bukkit_paper_api}"
compileOnly 'org.jetbrains:annotations:26.0.1'
compileOnly 'org.projectlombok:lombok:1.18.34'
annotationProcessor 'org.projectlombok:lombok:1.18.34'
@@ -49,10 +49,6 @@ shadowJar {
tasks {
runServer {
minecraftVersion('1.21')
downloadPlugins {
url('https://download.luckperms.net/1549/bukkit/loader/LuckPerms-Bukkit-5.4.134.jar')
}
minecraftVersion('1.21.3')
}
}

View File

@@ -20,6 +20,7 @@
package net.william278.husksync;
import net.kyori.adventure.audience.Audience;
import net.william278.desertwell.util.Version;
import net.william278.husksync.listener.BukkitEventListener;
import net.william278.husksync.listener.PaperEventListener;
import net.william278.uniform.Uniform;
@@ -29,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@SuppressWarnings("unused")
@SuppressWarnings({"unchecked", "unused"})
public class PaperHuskSync extends BukkitHuskSync {
@NotNull
@@ -45,6 +46,12 @@ public class PaperHuskSync extends BukkitHuskSync {
return player == null || !player.isOnline() ? Audience.empty() : player;
}
@NotNull
@Override
public Version getMinecraftVersion() {
return Version.fromString(getServer().getMinecraftVersion());
}
@Override
@NotNull
public Uniform getUniform() {

View File

@@ -43,6 +43,12 @@ public class PaperEventListener extends BukkitEventListener {
super(plugin);
}
@Override
public void onEnable() {
getPlugin().getServer().getPluginManager().registerEvents(this, getPlugin());
lockedHandler.onEnable();
}
@Override
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
// If the player is locked or the plugin disabling, clear their drops

View File

@@ -13,7 +13,7 @@ from tqdm import tqdm
class Parameters:
root_dir = './servers/'
proxy_version = "1.21"
minecraft_version = '1.21'
minecraft_version = '1.21.3'
eula_agreement = 'true'
backend_names = ['alpha', 'beta']