mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-27 10:39:11 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a31c3c48f7 | ||
|
|
0e96374a03 | ||
|
|
24545563fa | ||
|
|
a9ea4d34e5 | ||
|
|
11453393d4 | ||
|
|
1d850a9ddb | ||
|
|
5b90b3d006 | ||
|
|
d85ec65384 | ||
|
|
9d681db030 | ||
|
|
c5c2dde0bf | ||
|
|
807bffe9aa | ||
|
|
a1956c6822 | ||
|
|
321dccb0b5 | ||
|
|
1015c50802 | ||
|
|
c5e759390b | ||
|
|
883695b0b0 | ||
|
|
879aef471a | ||
|
|
64c81a9a5a | ||
|
|
b61a9a7bc3 | ||
|
|
3f725eb40c | ||
|
|
8f1e4a5198 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -55,12 +55,12 @@ jobs:
|
|||||||
paper-1.21.1
|
paper-1.21.1
|
||||||
paper-1.21.4
|
paper-1.21.4
|
||||||
paper-1.21.5
|
paper-1.21.5
|
||||||
paper-1.21.7
|
paper-1.21.8
|
||||||
fabric-1.20.1
|
fabric-1.20.1
|
||||||
fabric-1.21.1
|
fabric-1.21.1
|
||||||
fabric-1.21.4
|
fabric-1.21.4
|
||||||
fabric-1.21.5
|
fabric-1.21.5
|
||||||
fabric-1.21.7
|
fabric-1.21.8
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
@@ -77,20 +77,20 @@ jobs:
|
|||||||
Paper 1.21.1
|
Paper 1.21.1
|
||||||
Paper 1.21.4
|
Paper 1.21.4
|
||||||
Paper 1.21.5
|
Paper 1.21.5
|
||||||
Paper 1.21.7
|
Paper 1.21.8
|
||||||
Fabric 1.20.1
|
Fabric 1.20.1
|
||||||
Fabric 1.21.1
|
Fabric 1.21.1
|
||||||
Fabric 1.21.4
|
Fabric 1.21.4
|
||||||
Fabric 1.21.5
|
Fabric 1.21.5
|
||||||
Fabric 1.21.7
|
Fabric 1.21.8
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.20.1.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.1.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.4.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.5.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.5.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.7.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.8.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.4.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.5.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.5.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.7.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.8.jar
|
||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -44,12 +44,12 @@ jobs:
|
|||||||
paper-1.21.1
|
paper-1.21.1
|
||||||
paper-1.21.4
|
paper-1.21.4
|
||||||
paper-1.21.5
|
paper-1.21.5
|
||||||
paper-1.21.7
|
paper-1.21.8
|
||||||
fabric-1.20.1
|
fabric-1.20.1
|
||||||
fabric-1.21.1
|
fabric-1.21.1
|
||||||
fabric-1.21.4
|
fabric-1.21.4
|
||||||
fabric-1.21.5
|
fabric-1.21.5
|
||||||
fabric-1.21.7
|
fabric-1.21.8
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
Paper 1.21.1
|
Paper 1.21.1
|
||||||
Paper 1.21.4
|
Paper 1.21.4
|
||||||
Paper 1.21.5
|
Paper 1.21.5
|
||||||
Paper 1.21.7
|
Paper 1.21.8
|
||||||
Fabric 1.20.1
|
Fabric 1.20.1
|
||||||
Fabric 1.21.1
|
Fabric 1.21.1
|
||||||
Fabric 1.21.4
|
Fabric 1.21.4
|
||||||
Fabric 1.21.5
|
Fabric 1.21.5
|
||||||
Fabric 1.21.7
|
Fabric 1.21.8
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.7.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.8.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Fabric-${{ 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-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.7.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.8.jar
|
||||||
2
.github/workflows/update_docs.yml
vendored
2
.github/workflows/update_docs.yml
vendored
@@ -20,6 +20,6 @@ jobs:
|
|||||||
- name: 'Checkout for CI 🛎️'
|
- name: 'Checkout for CI 🛎️'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: 'Push Docs to Github Wiki 📄️'
|
- name: 'Push Docs to Github Wiki 📄️'
|
||||||
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
uses: Andrew-Chen-Wang/github-wiki-action@v5
|
||||||
with:
|
with:
|
||||||
path: 'docs'
|
path: 'docs'
|
||||||
@@ -48,7 +48,7 @@ HuskSync supports the following [compatible versions](https://william278.net/doc
|
|||||||
|
|
||||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||||
| 1.21.7 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
| 1.21.7/8 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||||
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
||||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.gradleup.shadow' version '8.3.7'
|
id 'com.gradleup.shadow' version '8.3.8'
|
||||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
|
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
|
||||||
id 'gg.essential.multi-version.root' apply false
|
id 'gg.essential.multi-version.root' apply false
|
||||||
@@ -89,7 +89,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(platform("org.junit:junit-bom:5.13.2"))
|
testImplementation(platform("org.junit:junit-bom:5.13.3"))
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
minecraft_version_range=1.20.1
|
||||||
minecraft_version_numeric=12001
|
minecraft_version_numeric=12001
|
||||||
minecraft_api_version=1.20
|
minecraft_api_version=1.20
|
||||||
paper_api_version=1.20.1-R0.1-SNAPSHOT
|
paper_api_version=1.20.1-R0.1-SNAPSHOT
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
minecraft_version_range=1.21.1
|
||||||
minecraft_version_numeric=12101
|
minecraft_version_numeric=12101
|
||||||
minecraft_api_version=1.21
|
minecraft_api_version=1.21
|
||||||
paper_api_version=1.21.1-R0.1-SNAPSHOT
|
paper_api_version=1.21.1-R0.1-SNAPSHOT
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
minecraft_version_range=1.21.4
|
||||||
minecraft_version_numeric=12104
|
minecraft_version_numeric=12104
|
||||||
minecraft_api_version=1.21
|
minecraft_api_version=1.21
|
||||||
paper_api_version=1.21.4-R0.1-SNAPSHOT
|
paper_api_version=1.21.4-R0.1-SNAPSHOT
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
minecraft_version_range=1.21.5
|
||||||
minecraft_version_numeric=12105
|
minecraft_version_numeric=12105
|
||||||
minecraft_api_version=1.21
|
minecraft_api_version=1.21
|
||||||
paper_api_version=1.21.5-R0.1-SNAPSHOT
|
paper_api_version=1.21.5-R0.1-SNAPSHOT
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
minecraft_version_numeric=12107
|
|
||||||
minecraft_api_version=1.21
|
|
||||||
paper_api_version=1.21.7-R0.1-SNAPSHOT
|
|
||||||
4
bukkit/1.21.8/gradle.properties
Normal file
4
bukkit/1.21.8/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
minecraft_version_range=>=1.21.7 <=1.21.8
|
||||||
|
minecraft_version_numeric=12108
|
||||||
|
minecraft_api_version=1.21
|
||||||
|
paper_api_version=1.21.8-R0.1-SNAPSHOT
|
||||||
@@ -8,9 +8,9 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
implementation project(path: ':common')
|
||||||
|
|
||||||
implementation 'net.william278.uniform:uniform-bukkit:1.3.8'
|
implementation 'net.william278.uniform:uniform-bukkit:1.3.9'
|
||||||
implementation 'net.william278.uniform:uniform-paper:1.3.8'
|
implementation 'net.william278.uniform:uniform-paper:1.3.9'
|
||||||
implementation 'net.william278.toilet:toilet-bukkit:1.0.15'
|
implementation 'net.william278.toilet:toilet-bukkit:1.0.16'
|
||||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||||
implementation 'net.william278:hsldataconverter:1.0'
|
implementation 'net.william278:hsldataconverter:1.0'
|
||||||
implementation 'net.william278:mapdataapi:2.0'
|
implementation 'net.william278:mapdataapi:2.0'
|
||||||
@@ -18,17 +18,17 @@ dependencies {
|
|||||||
implementation 'net.kyori:adventure-platform-bukkit:4.4.0'
|
implementation 'net.kyori:adventure-platform-bukkit:4.4.0'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.12'
|
implementation 'dev.triumphteam:triumph-gui:3.1.12'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.15.1'
|
implementation 'de.tr7zw:item-nbt-api:2.15.2-SNAPSHOT'
|
||||||
|
|
||||||
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
|
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
|
||||||
compileOnly 'com.github.retrooper:packetevents-spigot:2.8.0'
|
compileOnly 'com.github.retrooper:packetevents-spigot:2.9.4'
|
||||||
compileOnly 'com.github.dmulloy2:ProtocolLib:5.3.0'
|
compileOnly 'com.github.dmulloy2:ProtocolLib:5.3.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||||
compileOnly 'commons-io:commons-io:2.19.0'
|
compileOnly 'commons-io:commons-io:2.20.0'
|
||||||
compileOnly 'org.json:json:20250517'
|
compileOnly 'org.json:json:20250517'
|
||||||
compileOnly 'net.william278:minedown:1.8.2'
|
compileOnly 'net.william278:minedown:1.8.2'
|
||||||
compileOnly 'de.exlll:configlib-yaml:4.6.1'
|
compileOnly 'de.exlll:configlib-yaml:4.6.1'
|
||||||
compileOnly 'com.zaxxer:HikariCP:6.3.0'
|
compileOnly 'com.zaxxer:HikariCP:7.0.1'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
@@ -42,6 +42,7 @@ processResources {
|
|||||||
version: version,
|
version: version,
|
||||||
paper_api_version: paper_api_version,
|
paper_api_version: paper_api_version,
|
||||||
minecraft_version: project.name,
|
minecraft_version: project.name,
|
||||||
|
minecraft_version_range: minecraft_version_range,
|
||||||
minecraft_api_version: minecraft_api_version
|
minecraft_api_version: minecraft_api_version
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import com.google.common.collect.Maps;
|
|||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import de.tr7zw.changeme.nbtapi.NBT;
|
import de.tr7zw.changeme.nbtapi.NBT;
|
||||||
import de.tr7zw.changeme.nbtapi.utils.DataFixerUtil;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -49,6 +48,7 @@ import net.william278.husksync.event.BukkitEventDispatcher;
|
|||||||
import net.william278.husksync.hook.PlanHook;
|
import net.william278.husksync.hook.PlanHook;
|
||||||
import net.william278.husksync.listener.BukkitEventListener;
|
import net.william278.husksync.listener.BukkitEventListener;
|
||||||
import net.william278.husksync.listener.LockedHandler;
|
import net.william278.husksync.listener.LockedHandler;
|
||||||
|
import net.william278.husksync.maps.BukkitMapHandler;
|
||||||
import net.william278.husksync.migrator.LegacyMigrator;
|
import net.william278.husksync.migrator.LegacyMigrator;
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.migrator.MpdbMigrator;
|
import net.william278.husksync.migrator.MpdbMigrator;
|
||||||
@@ -57,7 +57,6 @@ import net.william278.husksync.sync.DataSyncer;
|
|||||||
import net.william278.husksync.user.BukkitUser;
|
import net.william278.husksync.user.BukkitUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.util.BukkitLegacyConverter;
|
import net.william278.husksync.util.BukkitLegacyConverter;
|
||||||
import net.william278.husksync.maps.BukkitMapHandler;
|
|
||||||
import net.william278.husksync.util.BukkitTask;
|
import net.william278.husksync.util.BukkitTask;
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
import net.william278.toilet.BukkitToilet;
|
import net.william278.toilet.BukkitToilet;
|
||||||
@@ -344,25 +343,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return Version.fromString(getServer().getBukkitVersion());
|
return Version.fromString(getServer().getBukkitVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDataVersion(@NotNull Version mcVersion) {
|
|
||||||
return switch (mcVersion.toStringWithoutMetadata()) {
|
|
||||||
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5;
|
|
||||||
case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1;
|
|
||||||
case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2;
|
|
||||||
case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2;
|
|
||||||
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
|
|
||||||
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
|
||||||
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
|
||||||
case "1.21", "1.21.1" -> DataFixerUtil.VERSION1_21;
|
|
||||||
case "1.21.2" -> DataFixerUtil.VERSION1_21_2;
|
|
||||||
case "1.21.3" -> DataFixerUtil.VERSION1_21_3;
|
|
||||||
case "1.21.4" -> DataFixerUtil.VERSION1_21_4;
|
|
||||||
case "1.21.5" -> DataFixerUtil.VERSION1_21_5;
|
|
||||||
case "1.21.6" -> 4435;
|
|
||||||
default -> DataFixerUtil.getCurrentVersion();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getPlatformType() {
|
public String getPlatformType() {
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
||||||
if (!toAward.isEmpty()
|
if (!toAward.isEmpty()
|
||||||
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
||||||
player.setLevel(expLevel);
|
player.setLevel(expLevel);
|
||||||
player.setExp(expProgress);
|
player.setExp(expProgress);
|
||||||
}
|
}
|
||||||
@@ -486,8 +486,13 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull Map<String, Map<String, Integer>> map) {
|
@NotNull Map<String, Map<String, Integer>> map) {
|
||||||
registry.forEach(i -> {
|
registry.forEach(i -> {
|
||||||
try {
|
try {
|
||||||
final int stat = i instanceof Material m ? p.getStatistic(id, m) :
|
int stat = 0;
|
||||||
(i instanceof EntityType e ? p.getStatistic(id, e) : -1);
|
if (i instanceof Material mat && ((id.getType() == Statistic.Type.BLOCK && mat.isBlock())
|
||||||
|
|| (id.getType() == Statistic.Type.ITEM && mat.isItem()))) {
|
||||||
|
stat = p.getStatistic(id, mat);
|
||||||
|
} else if (i instanceof EntityType ent && id.getType() == Statistic.Type.ENTITY) {
|
||||||
|
stat = p.getStatistic(id, ent);
|
||||||
|
}
|
||||||
if (stat != 0) {
|
if (stat != 0) {
|
||||||
map.compute(id.getKey().getKey(), (k, v) -> v == null ? Maps.newHashMap() : v)
|
map.compute(id.getKey().getKey(), (k, v) -> v == null ? Maps.newHashMap() : v)
|
||||||
.put(i.getKey().getKey(), stat);
|
.put(i.getKey().getKey(), stat);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ import java.awt.*;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# File used for checking Minecraft server compatibility with this version of HuskSync
|
# File used for checking Minecraft server compatibility with this version of HuskSync
|
||||||
minecraft_version: '${minecraft_version}'
|
minecraft_version_range: '${minecraft_version_range}'
|
||||||
@@ -3,8 +3,8 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'commons-io:commons-io:2.19.0'
|
api 'commons-io:commons-io:2.20.0'
|
||||||
api 'org.apache.commons:commons-text:1.13.1'
|
api 'org.apache.commons:commons-text:1.14.0'
|
||||||
api 'net.william278:minedown:1.8.2'
|
api 'net.william278:minedown:1.8.2'
|
||||||
api 'net.william278:mapdataapi:2.0'
|
api 'net.william278:mapdataapi:2.0'
|
||||||
api 'org.json:json:20250517'
|
api 'org.json:json:20250517'
|
||||||
@@ -13,13 +13,13 @@ dependencies {
|
|||||||
api 'de.exlll:configlib-yaml:4.6.1'
|
api 'de.exlll:configlib-yaml:4.6.1'
|
||||||
api 'net.william278:paginedown:1.1.2'
|
api 'net.william278:paginedown:1.1.2'
|
||||||
api 'net.william278:DesertWell:2.0.4'
|
api 'net.william278:DesertWell:2.0.4'
|
||||||
api('com.zaxxer:HikariCP:6.3.0') {
|
api('com.zaxxer:HikariCP:7.0.1') {
|
||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.15'
|
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.16'
|
||||||
|
|
||||||
compileOnly 'net.william278.uniform:uniform-common:1.3.8'
|
compileOnly 'net.william278.uniform:uniform-common:1.3.9'
|
||||||
compileOnly 'com.mojang:brigadier:1.1.8'
|
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||||
compileOnly 'org.jetbrains:annotations:26.0.2'
|
compileOnly 'org.jetbrains:annotations:26.0.2'
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ import net.william278.husksync.redis.RedisManager;
|
|||||||
import net.william278.husksync.sync.DataSyncer;
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import net.william278.husksync.user.ConsoleUser;
|
import net.william278.husksync.user.ConsoleUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.util.CompatibilityChecker;
|
import net.william278.husksync.util.*;
|
||||||
import net.william278.husksync.util.DumpProvider;
|
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
|
||||||
import net.william278.husksync.util.Task;
|
|
||||||
import net.william278.uniform.Uniform;
|
import net.william278.uniform.Uniform;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ import java.util.logging.Level;
|
|||||||
* Abstract implementation of the HuskSync plugin.
|
* Abstract implementation of the HuskSync plugin.
|
||||||
*/
|
*/
|
||||||
public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider, SerializerRegistry,
|
public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider, SerializerRegistry,
|
||||||
CompatibilityChecker, DumpProvider {
|
CompatibilityChecker, DumpProvider, DataVersionSupplier {
|
||||||
|
|
||||||
int SPIGOT_RESOURCE_ID = 97144;
|
int SPIGOT_RESOURCE_ID = 97144;
|
||||||
|
|
||||||
@@ -252,14 +249,6 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
@NotNull
|
@NotNull
|
||||||
Version getMinecraftVersion();
|
Version getMinecraftVersion();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data version for a Minecraft version
|
|
||||||
*
|
|
||||||
* @param minecraftVersion the Minecraft version
|
|
||||||
* @return the data version int
|
|
||||||
*/
|
|
||||||
int getDataVersion(@NotNull Version minecraftVersion);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the platform type
|
* Returns the platform type
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -174,7 +174,45 @@ public class Settings {
|
|||||||
private int database = 0;
|
private int database = 0;
|
||||||
private String user = "";
|
private String user = "";
|
||||||
private String password = "";
|
private String password = "";
|
||||||
|
|
||||||
|
@Comment("Use SSL/TLS for encrypted connections.")
|
||||||
private boolean useSsl = false;
|
private boolean useSsl = false;
|
||||||
|
|
||||||
|
@Comment("Connection timeout in milliseconds.")
|
||||||
|
private int connectionTimeout = 2000;
|
||||||
|
|
||||||
|
@Comment("Socket (read/write) timeout in milliseconds.")
|
||||||
|
private int socketTimeout = 2000;
|
||||||
|
|
||||||
|
@Comment("Max number of connections in the pool.")
|
||||||
|
private int maxTotalConnections = 50;
|
||||||
|
|
||||||
|
@Comment("Max number of idle connections in the pool.")
|
||||||
|
private int maxIdleConnections = 8;
|
||||||
|
|
||||||
|
@Comment("Min number of idle connections in the pool.")
|
||||||
|
private int minIdleConnections = 2;
|
||||||
|
|
||||||
|
@Comment("Enable health checks when borrowing connections from the pool.")
|
||||||
|
private boolean testOnBorrow = true;
|
||||||
|
|
||||||
|
@Comment("Enable health checks when returning connections to the pool.")
|
||||||
|
private boolean testOnReturn = true;
|
||||||
|
|
||||||
|
@Comment("Enable periodic idle connection health checks.")
|
||||||
|
private boolean testWhileIdle = true;
|
||||||
|
|
||||||
|
@Comment("Min evictable idle time (ms) before a connection is eligible for eviction.")
|
||||||
|
private long minEvictableIdleTimeMillis = 60000;
|
||||||
|
|
||||||
|
@Comment("Time (ms) between eviction runs.")
|
||||||
|
private long timeBetweenEvictionRunsMillis = 30000;
|
||||||
|
|
||||||
|
@Comment("Number of retries for commands when connection fails.")
|
||||||
|
private int maxRetries = 3;
|
||||||
|
|
||||||
|
@Comment("Base backoff time in ms for retries (exponential backoff multiplier).")
|
||||||
|
private int retryBackoffMillis = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Comment("Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!")
|
@Comment("Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!")
|
||||||
@@ -327,7 +365,7 @@ public class Settings {
|
|||||||
private Map<String, String> eventPriorities = EventListener.ListenerType.getDefaults();
|
private Map<String, String> eventPriorities = EventListener.ListenerType.getDefaults();
|
||||||
|
|
||||||
@Comment("Enable check-in petitions for data syncing (don't change this unless you know what you're doing)")
|
@Comment("Enable check-in petitions for data syncing (don't change this unless you know what you're doing)")
|
||||||
private boolean checkinPetitions = true;
|
private boolean checkinPetitions = false;
|
||||||
|
|
||||||
public boolean doAutoPin(@NotNull DataSnapshot.SaveCause cause) {
|
public boolean doAutoPin(@NotNull DataSnapshot.SaveCause cause) {
|
||||||
return autoPinnedSaveCauses.contains(cause.name());
|
return autoPinnedSaveCauses.contains(cause.name());
|
||||||
|
|||||||
@@ -297,9 +297,9 @@ public class PostgresDatabase extends Database {
|
|||||||
public int getUnpinnedSnapshotCount(@NotNull User user) {
|
public int getUnpinnedSnapshotCount(@NotNull User user) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT COUNT(`version_uuid`)
|
SELECT COUNT(version_uuid)
|
||||||
FROM `%user_data_table%`
|
FROM %user_data_table%
|
||||||
WHERE `player_uuid`=? AND `pinned`=false;"""))) {
|
WHERE player_uuid=? AND pinned=false;"""))) {
|
||||||
statement.setString(1, user.getUuid().toString());
|
statement.setString(1, user.getUuid().toString());
|
||||||
final ResultSet resultSet = statement.executeQuery();
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
if (resultSet.next()) {
|
if (resultSet.next()) {
|
||||||
|
|||||||
@@ -62,9 +62,11 @@ public class RedisManager extends JedisPubSub {
|
|||||||
/**
|
/**
|
||||||
* Initialize Redis connection pool
|
* Initialize Redis connection pool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Blocking
|
@Blocking
|
||||||
public void initialize() throws IllegalStateException {
|
public void initialize() throws IllegalStateException {
|
||||||
final Settings.RedisSettings.RedisCredentials credentials = plugin.getSettings().getRedis().getCredentials();
|
final Settings.RedisSettings.RedisCredentials credentials = plugin.getSettings().getRedis().getCredentials();
|
||||||
|
|
||||||
final String user = credentials.getUser();
|
final String user = credentials.getUser();
|
||||||
final String password = credentials.getPassword();
|
final String password = credentials.getPassword();
|
||||||
final String host = credentials.getHost();
|
final String host = credentials.getHost();
|
||||||
@@ -72,44 +74,50 @@ public class RedisManager extends JedisPubSub {
|
|||||||
final int database = credentials.getDatabase();
|
final int database = credentials.getDatabase();
|
||||||
final boolean useSSL = credentials.isUseSsl();
|
final boolean useSSL = credentials.isUseSsl();
|
||||||
|
|
||||||
// Create the jedis pool
|
// Configure JedisPoolConfig
|
||||||
final JedisPoolConfig config = new JedisPoolConfig();
|
final JedisPoolConfig config = new JedisPoolConfig();
|
||||||
config.setMaxIdle(0);
|
config.setMaxTotal(credentials.getMaxTotalConnections());
|
||||||
config.setTestOnBorrow(true);
|
config.setMaxIdle(credentials.getMaxIdleConnections());
|
||||||
config.setTestOnReturn(true);
|
config.setMinIdle(credentials.getMinIdleConnections());
|
||||||
|
config.setTestOnBorrow(credentials.isTestOnBorrow());
|
||||||
|
config.setTestOnReturn(credentials.isTestOnReturn());
|
||||||
|
config.setTestWhileIdle(credentials.isTestWhileIdle());
|
||||||
|
config.setMinEvictableIdleTimeMillis(credentials.getMinEvictableIdleTimeMillis());
|
||||||
|
config.setTimeBetweenEvictionRunsMillis(credentials.getTimeBetweenEvictionRunsMillis());
|
||||||
|
|
||||||
final Settings.RedisSettings.RedisSentinel sentinel = plugin.getSettings().getRedis().getSentinel();
|
final Settings.RedisSettings.RedisSentinel sentinel = plugin.getSettings().getRedis().getSentinel();
|
||||||
Set<String> redisSentinelNodes = new HashSet<>(sentinel.getNodes());
|
Set<String> redisSentinelNodes = new HashSet<>(sentinel.getNodes());
|
||||||
|
|
||||||
if (redisSentinelNodes.isEmpty()) {
|
if (redisSentinelNodes.isEmpty()) {
|
||||||
|
// Standalone Redis setup
|
||||||
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
|
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
|
||||||
.ssl(useSSL)
|
.ssl(useSSL)
|
||||||
.database(database)
|
.database(database)
|
||||||
.timeoutMillis(0);
|
.timeoutMillis(credentials.getConnectionTimeout()) // connection and socket timeout combined
|
||||||
|
.user(user.isEmpty() ? null : user)
|
||||||
if (!user.isEmpty()) {
|
.password(password.isEmpty() ? null : password);
|
||||||
clientConfigBuilder.user(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password.isEmpty()) {
|
|
||||||
clientConfigBuilder.password(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.jedisPool = new JedisPool(config, new HostAndPort(host, port), clientConfigBuilder.build());
|
this.jedisPool = new JedisPool(config, new HostAndPort(host, port), clientConfigBuilder.build());
|
||||||
} else {
|
} else {
|
||||||
final String sentinelPassword = sentinel.getPassword();
|
final String sentinelPassword = sentinel.getPassword();
|
||||||
this.jedisPool = new JedisSentinelPool(sentinel.getMaster(), redisSentinelNodes, password.isEmpty()
|
this.jedisPool = new JedisSentinelPool(
|
||||||
? null : password, sentinelPassword.isEmpty() ? null : sentinelPassword);
|
sentinel.getMaster(),
|
||||||
|
redisSentinelNodes,
|
||||||
|
config,
|
||||||
|
credentials.getConnectionTimeout(),
|
||||||
|
credentials.getSocketTimeout(),
|
||||||
|
password.isEmpty() ? null : password,
|
||||||
|
sentinelPassword.isEmpty() ? null : sentinelPassword,
|
||||||
|
database);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping the server to check the connection
|
try (var jedis = jedisPool.getResource()) {
|
||||||
try {
|
jedis.ping();
|
||||||
jedisPool.getResource().ping();
|
|
||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
throw new IllegalStateException("Failed to establish connection with Redis. "
|
throw new IllegalStateException("Failed to establish connection with Redis. " +
|
||||||
+ "Please check the supplied credentials in the config file", e);
|
"Please check the supplied credentials in the config file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe using a thread (rather than a task)
|
|
||||||
enabled = true;
|
enabled = true;
|
||||||
new Thread(this::subscribe, "husksync:redis_subscriber").start();
|
new Thread(this::subscribe, "husksync:redis_subscriber").start();
|
||||||
}
|
}
|
||||||
@@ -126,8 +134,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
this,
|
this,
|
||||||
Arrays.stream(RedisMessage.Type.values())
|
Arrays.stream(RedisMessage.Type.values())
|
||||||
.map(type -> type.getMessageChannel(clusterId))
|
.map(type -> type.getMessageChannel(clusterId))
|
||||||
.toArray(String[]::new)
|
.toArray(String[]::new));
|
||||||
);
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// Thread was unlocked due error
|
// Thread was unlocked due error
|
||||||
onThreadUnlock(t);
|
onThreadUnlock(t);
|
||||||
@@ -175,20 +182,19 @@ public class RedisManager extends JedisPubSub {
|
|||||||
user -> {
|
user -> {
|
||||||
plugin.lockPlayer(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
try {
|
try {
|
||||||
final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin, redisMessage.getPayload());
|
final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin,
|
||||||
|
redisMessage.getPayload());
|
||||||
user.applySnapshot(data, DataSnapshot.UpdateCause.UPDATED);
|
user.applySnapshot(data, DataSnapshot.UpdateCause.UPDATED);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred updating user data from Redis", e);
|
plugin.log(Level.SEVERE, "An exception occurred updating user data from Redis", e);
|
||||||
user.completeSync(false, DataSnapshot.UpdateCause.UPDATED, plugin);
|
user.completeSync(false, DataSnapshot.UpdateCause.UPDATED, plugin);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
case REQUEST_USER_DATA -> redisMessage.getTargetUser(plugin).ifPresent(
|
case REQUEST_USER_DATA -> redisMessage.getTargetUser(plugin).ifPresent(
|
||||||
user -> RedisMessage.create(
|
user -> RedisMessage.create(
|
||||||
UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)),
|
UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)),
|
||||||
user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin)
|
user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin))
|
||||||
).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA)
|
.dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA));
|
||||||
);
|
|
||||||
case CHECK_IN_PETITION -> {
|
case CHECK_IN_PETITION -> {
|
||||||
if (!redisMessage.isTargetServer(plugin)
|
if (!redisMessage.isTargetServer(plugin)
|
||||||
|| !plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
|| !plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
||||||
@@ -199,7 +205,8 @@ public class RedisManager extends JedisPubSub {
|
|||||||
boolean online = plugin.getDisconnectingPlayers().contains(user.getUuid())
|
boolean online = plugin.getDisconnectingPlayers().contains(user.getUuid())
|
||||||
|| plugin.getOnlineUser(user.getUuid()).isEmpty();
|
|| plugin.getOnlineUser(user.getUuid()).isEmpty();
|
||||||
if (!online && !plugin.isLocked(user.getUuid())) {
|
if (!online && !plugin.isLocked(user.getUuid())) {
|
||||||
plugin.debug("[%s] Received check-in petition for online/unlocked user, ignoring".formatted(user.getName()));
|
plugin.debug("[%s] Received check-in petition for online/unlocked user, ignoring"
|
||||||
|
.formatted(user.getName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getRedisManager().setUserCheckedOut(user, false);
|
plugin.getRedisManager().setUserCheckedOut(user, false);
|
||||||
@@ -252,31 +259,30 @@ public class RedisManager extends JedisPubSub {
|
|||||||
redisMessage.dispatch(plugin, RedisMessage.Type.CHECK_IN_PETITION);
|
redisMessage.dispatch(plugin, RedisMessage.Type.CHECK_IN_PETITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Optional<DataSnapshot.Packed>> getOnlineUserData(@NotNull UUID requestId, @NotNull User user,
|
public CompletableFuture<Optional<DataSnapshot.Packed>> getOnlineUserData(@NotNull UUID requestId,
|
||||||
@NotNull DataSnapshot.SaveCause saveCause) {
|
@NotNull User user,
|
||||||
|
@NotNull DataSnapshot.SaveCause saveCause) {
|
||||||
return plugin.getOnlineUser(user.getUuid())
|
return plugin.getOnlineUser(user.getUuid())
|
||||||
.map(online -> CompletableFuture.completedFuture(
|
.map(online -> CompletableFuture.completedFuture(
|
||||||
Optional.of(online.createSnapshot(saveCause)))
|
Optional.of(online.createSnapshot(saveCause))))
|
||||||
)
|
|
||||||
.orElse(this.getNetworkedUserData(requestId, user));
|
.orElse(this.getNetworkedUserData(requestId, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request a user's dat x-server
|
// Request a user's dat x-server
|
||||||
private CompletableFuture<Optional<DataSnapshot.Packed>> getNetworkedUserData(@NotNull UUID requestId, @NotNull User user) {
|
private CompletableFuture<Optional<DataSnapshot.Packed>> getNetworkedUserData(@NotNull UUID requestId,
|
||||||
|
@NotNull User user) {
|
||||||
final CompletableFuture<Optional<DataSnapshot.Packed>> future = new CompletableFuture<>();
|
final CompletableFuture<Optional<DataSnapshot.Packed>> future = new CompletableFuture<>();
|
||||||
pendingRequests.put(requestId, future);
|
pendingRequests.put(requestId, future);
|
||||||
plugin.runAsync(() -> {
|
plugin.runAsync(() -> {
|
||||||
final RedisMessage redisMessage = RedisMessage.create(
|
final RedisMessage redisMessage = RedisMessage.create(
|
||||||
user.getUuid(),
|
user.getUuid(),
|
||||||
requestId.toString().getBytes(StandardCharsets.UTF_8)
|
requestId.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
);
|
|
||||||
redisMessage.dispatch(plugin, RedisMessage.Type.REQUEST_USER_DATA);
|
redisMessage.dispatch(plugin, RedisMessage.Type.REQUEST_USER_DATA);
|
||||||
});
|
});
|
||||||
return future
|
return future
|
||||||
.orTimeout(
|
.orTimeout(
|
||||||
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds(),
|
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds(),
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS)
|
||||||
)
|
|
||||||
.exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
pendingRequests.remove(requestId);
|
pendingRequests.remove(requestId);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -290,8 +296,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedis.setex(
|
jedis.setex(
|
||||||
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId),
|
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId),
|
||||||
RedisKeyType.TTL_1_YEAR,
|
RedisKeyType.TTL_1_YEAR,
|
||||||
data.asBytes(plugin)
|
data.asBytes(plugin));
|
||||||
);
|
|
||||||
plugin.debug(String.format("[%s] Set %s key on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
plugin.debug(String.format("[%s] Set %s key on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting user data on Redis", e);
|
plugin.log(Level.SEVERE, "An exception occurred setting user data on Redis", e);
|
||||||
@@ -302,8 +307,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
public void clearUserData(@NotNull User user) {
|
public void clearUserData(@NotNull User user) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis.del(
|
jedis.del(
|
||||||
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId)
|
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId));
|
||||||
);
|
|
||||||
plugin.debug(String.format("[%s] Cleared %s on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
plugin.debug(String.format("[%s] Cleared %s on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred clearing user data on Redis", e);
|
plugin.log(Level.SEVERE, "An exception occurred clearing user data on Redis", e);
|
||||||
@@ -317,8 +321,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
if (checkedOut) {
|
if (checkedOut) {
|
||||||
jedis.set(
|
jedis.set(
|
||||||
key.getBytes(StandardCharsets.UTF_8),
|
key.getBytes(StandardCharsets.UTF_8),
|
||||||
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
plugin.getServerName().getBytes(StandardCharsets.UTF_8));
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
if (jedis.del(key.getBytes(StandardCharsets.UTF_8)) == 0) {
|
if (jedis.del(key.getBytes(StandardCharsets.UTF_8)) == 0) {
|
||||||
plugin.debug(String.format("[%s] %s key not set on Redis when attempting removal (%s)",
|
plugin.debug(String.format("[%s] %s key not set on Redis when attempting removal (%s)",
|
||||||
@@ -382,8 +385,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedis.setex(
|
jedis.setex(
|
||||||
getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId),
|
getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId),
|
||||||
RedisKeyType.TTL_10_SECONDS,
|
RedisKeyType.TTL_10_SECONDS,
|
||||||
new byte[0]
|
new byte[0]);
|
||||||
);
|
|
||||||
plugin.debug(String.format("[%s] Set %s key to Redis",
|
plugin.debug(String.format("[%s] Set %s key to Redis",
|
||||||
user.getName(), RedisKeyType.SERVER_SWITCH));
|
user.getName(), RedisKeyType.SERVER_SWITCH));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -395,7 +397,8 @@ public class RedisManager extends JedisPubSub {
|
|||||||
* Fetch a user's data from Redis and consume the key if found
|
* Fetch a user's data from Redis and consume the key if found
|
||||||
*
|
*
|
||||||
* @param user The user to fetch data for
|
* @param user The user to fetch data for
|
||||||
* @return The user's data, if it's present on the database. Otherwise, an empty optional.
|
* @return The user's data, if it's present on the database. Otherwise, an empty
|
||||||
|
* optional.
|
||||||
*/
|
*/
|
||||||
@Blocking
|
@Blocking
|
||||||
public Optional<DataSnapshot.Packed> getUserData(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getUserData(@NotNull User user) {
|
||||||
@@ -476,13 +479,11 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedis.setex(
|
jedis.setex(
|
||||||
getMapIdKey(fromServer, fromId, toServer, clusterId),
|
getMapIdKey(fromServer, fromId, toServer, clusterId),
|
||||||
RedisKeyType.TTL_1_YEAR,
|
RedisKeyType.TTL_1_YEAR,
|
||||||
String.valueOf(toId).getBytes(StandardCharsets.UTF_8)
|
String.valueOf(toId).getBytes(StandardCharsets.UTF_8));
|
||||||
);
|
|
||||||
jedis.setex(
|
jedis.setex(
|
||||||
getReversedMapIdKey(toServer, toId, clusterId),
|
getReversedMapIdKey(toServer, toId, clusterId),
|
||||||
RedisKeyType.TTL_1_YEAR,
|
RedisKeyType.TTL_1_YEAR,
|
||||||
String.format("%s:%s", fromServer, fromId).getBytes(StandardCharsets.UTF_8)
|
String.format("%s:%s", fromServer, fromId).getBytes(StandardCharsets.UTF_8));
|
||||||
);
|
|
||||||
plugin.debug(String.format("Bound map %s:%s -> %s:%s on Redis", fromServer, fromId, toServer, toId));
|
plugin.debug(String.format("Bound map %s:%s -> %s:%s on Redis", fromServer, fromId, toServer, toId));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred binding map ids on Redis", e);
|
plugin.log(Level.SEVERE, "An exception occurred binding map ids on Redis", e);
|
||||||
@@ -534,8 +535,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedis.setex(
|
jedis.setex(
|
||||||
getMapDataKey(serverName, mapId, clusterId),
|
getMapDataKey(serverName, mapId, clusterId),
|
||||||
RedisKeyType.TTL_1_YEAR,
|
RedisKeyType.TTL_1_YEAR,
|
||||||
data
|
data);
|
||||||
);
|
|
||||||
plugin.debug(String.format("Set map data %s:%s on Redis", serverName, mapId));
|
plugin.debug(String.format("Set map data %s:%s on Redis", serverName, mapId));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting map data on Redis", e);
|
plugin.log(Level.SEVERE, "An exception occurred setting map data on Redis", e);
|
||||||
@@ -581,16 +581,20 @@ public class RedisManager extends JedisPubSub {
|
|||||||
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid);
|
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getMapIdKey(@NotNull String fromServer, int fromId, @NotNull String toServer, @NotNull String clusterId) {
|
private static byte[] getMapIdKey(@NotNull String fromServer, int fromId, @NotNull String toServer,
|
||||||
return String.format("%s:%s:%s:%s", RedisKeyType.MAP_ID.getKeyPrefix(clusterId), fromServer, fromId, toServer).getBytes(StandardCharsets.UTF_8);
|
@NotNull String clusterId) {
|
||||||
|
return String.format("%s:%s:%s:%s", RedisKeyType.MAP_ID.getKeyPrefix(clusterId), fromServer, fromId, toServer)
|
||||||
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getReversedMapIdKey(@NotNull String toServer, int toId, @NotNull String clusterId) {
|
private static byte[] getReversedMapIdKey(@NotNull String toServer, int toId, @NotNull String clusterId) {
|
||||||
return String.format("%s:%s:%s", RedisKeyType.MAP_ID_REVERSED.getKeyPrefix(clusterId), toServer, toId).getBytes(StandardCharsets.UTF_8);
|
return String.format("%s:%s:%s", RedisKeyType.MAP_ID_REVERSED.getKeyPrefix(clusterId), toServer, toId)
|
||||||
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getMapDataKey(@NotNull String serverName, int mapId, @NotNull String clusterId) {
|
private static byte[] getMapDataKey(@NotNull String serverName, int mapId, @NotNull String clusterId) {
|
||||||
return String.format("%s:%s:%s", RedisKeyType.MAP_DATA.getKeyPrefix(clusterId), serverName, mapId).getBytes(StandardCharsets.UTF_8);
|
return String.format("%s:%s:%s", RedisKeyType.MAP_DATA.getKeyPrefix(clusterId), serverName, mapId)
|
||||||
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ package net.william278.husksync.util;
|
|||||||
import de.exlll.configlib.Configuration;
|
import de.exlll.configlib.Configuration;
|
||||||
import de.exlll.configlib.YamlConfigurationProperties;
|
import de.exlll.configlib.YamlConfigurationProperties;
|
||||||
import de.exlll.configlib.YamlConfigurationStore;
|
import de.exlll.configlib.YamlConfigurationStore;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Objects;
|
import java.util.function.BiFunction;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static net.william278.husksync.config.ConfigProvider.YAML_CONFIGURATION_PROPERTIES;
|
import static net.william278.husksync.config.ConfigProvider.YAML_CONFIGURATION_PROPERTIES;
|
||||||
@@ -38,23 +39,22 @@ public interface CompatibilityChecker {
|
|||||||
|
|
||||||
default void checkCompatibility() throws HuskSync.FailedToLoadException {
|
default void checkCompatibility() throws HuskSync.FailedToLoadException {
|
||||||
final YamlConfigurationProperties p = YAML_CONFIGURATION_PROPERTIES.build();
|
final YamlConfigurationProperties p = YAML_CONFIGURATION_PROPERTIES.build();
|
||||||
final Version compatible;
|
final CompatibilityConfig compat;
|
||||||
|
|
||||||
// Load compatibility file
|
// Load compatibility file
|
||||||
try (InputStream input = getResource(COMPATIBILITY_FILE)) {
|
try (InputStream input = getResource(COMPATIBILITY_FILE)) {
|
||||||
final CompatibilityConfig compat = new YamlConfigurationStore<>(CompatibilityConfig.class, p).read(input);
|
compat = new YamlConfigurationStore<>(CompatibilityConfig.class, p).read(input);
|
||||||
compatible = Objects.requireNonNull(compat.getCompatibleWith());
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
getPlugin().log(Level.WARNING, "Failed to load compatibility config, skipping check.", e);
|
getPlugin().log(Level.WARNING, "Failed to load compatibility config, skipping check.", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check compatibility
|
// Check compatibility
|
||||||
if (compatible.compareTo(getPlugin().getMinecraftVersion()) != 0) {
|
if (!compat.isCompatibleWith(getPlugin().getMinecraftVersion())) {
|
||||||
throw new HuskSync.FailedToLoadException("""
|
throw new HuskSync.FailedToLoadException("""
|
||||||
Incompatible Minecraft version. This version of HuskSync is designed for Minecraft %s.
|
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)."""
|
Please download the correct version of HuskSync for your server's Minecraft version (%s)."""
|
||||||
.formatted(compatible.toString(), getPlugin().getMinecraftVersion().toString()));
|
.formatted(compat.minecraftVersionRange(), getPlugin().getMinecraftVersion().toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +64,38 @@ public interface CompatibilityChecker {
|
|||||||
HuskSync getPlugin();
|
HuskSync getPlugin();
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
record CompatibilityConfig(@NotNull String minecraftVersion) {
|
record CompatibilityConfig(@NotNull String minecraftVersionRange) {
|
||||||
|
|
||||||
@NotNull
|
@AllArgsConstructor
|
||||||
public Version getCompatibleWith() {
|
enum ExpressionType {
|
||||||
return Version.fromString(minecraftVersion);
|
GTE(">=", (v, s) -> v.compareTo(Version.fromString(s.substring(2))) >= 0),
|
||||||
|
LTE("<=", (v, s) -> v.compareTo(Version.fromString(s.substring(2))) <= 0),
|
||||||
|
GT(">", (v, s) -> v.compareTo(Version.fromString(s.substring(1))) > 0),
|
||||||
|
LT("<", (v, s) -> v.compareTo(Version.fromString(s.substring(1))) < 0),
|
||||||
|
NOT("!", (v, s) -> v.compareTo(Version.fromString(s.substring(1))) != 0),
|
||||||
|
E("=", (v, s) -> v.compareTo(Version.fromString(s.substring(1))) == 0);
|
||||||
|
|
||||||
|
private final String match;
|
||||||
|
private final BiFunction<Version, String, Boolean> function;
|
||||||
|
|
||||||
|
private static boolean check(@NotNull String versionRange, @NotNull Version mcVer) {
|
||||||
|
boolean passes = true;
|
||||||
|
versions:
|
||||||
|
for (String exp : versionRange.split(" ")) {
|
||||||
|
for (ExpressionType type : values()) {
|
||||||
|
if (exp.trim().startsWith(type.match)) {
|
||||||
|
passes = passes && type.function.apply(mcVer, exp.trim());
|
||||||
|
continue versions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
passes = passes && mcVer.compareTo(Version.fromString(exp.trim())) == 0;
|
||||||
|
}
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompatibleWith(@NotNull Version version) {
|
||||||
|
return ExpressionType.check(minecraftVersionRange, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.william278.desertwell.util.Version;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface DataVersionSupplier {
|
||||||
|
|
||||||
|
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;
|
||||||
|
int VERSION1_20_4 = 3700;
|
||||||
|
int VERSION1_20_5 = 3837;
|
||||||
|
int VERSION1_21_1 = 3955;
|
||||||
|
int VERSION1_21_3 = 4082;
|
||||||
|
int VERSION1_21_4 = 4189;
|
||||||
|
int VERSION1_21_5 = 4323;
|
||||||
|
int VERSION1_21_6 = 4435;
|
||||||
|
int VERSION1_21_7 = 4438;
|
||||||
|
int VERSION1_21_8 = 4438;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data version for a Minecraft version
|
||||||
|
*
|
||||||
|
* @param mcVersion the Minecraft version
|
||||||
|
* @return the data version int
|
||||||
|
*/
|
||||||
|
default int getDataVersion(@NotNull Version mcVersion) {
|
||||||
|
return switch (mcVersion.toStringWithoutMetadata()) {
|
||||||
|
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
|
||||||
|
case "1.17", "1.17.1" -> VERSION1_17_1;
|
||||||
|
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
|
||||||
|
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
|
||||||
|
case "1.19.4" -> VERSION1_19_4;
|
||||||
|
case "1.20", "1.20.1" -> VERSION1_20_1;
|
||||||
|
case "1.20.2" -> VERSION1_20_2;
|
||||||
|
case "1.20.4" -> VERSION1_20_4;
|
||||||
|
case "1.20.5", "1.20.6" -> VERSION1_20_5;
|
||||||
|
case "1.21", "1.21.1" -> VERSION1_21_1;
|
||||||
|
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
||||||
|
case "1.21.4" -> VERSION1_21_4;
|
||||||
|
case "1.21.5" -> VERSION1_21_5;
|
||||||
|
case "1.21.6" -> VERSION1_21_6;
|
||||||
|
case "1.21.7" -> VERSION1_21_7;
|
||||||
|
default -> VERSION1_21_8; // Latest supported version
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
HuskSync getPlugin();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.william278.desertwell.util.Version;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
|
||||||
|
@DisplayName("Compatibility Checker Tests")
|
||||||
|
public class CompatibilityCheckerTests {
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "Ver: {0}, Range: {1}")
|
||||||
|
@DisplayName("Test Compatibility Checker")
|
||||||
|
@CsvSource({
|
||||||
|
"1.20.1, 1.21.1, false",
|
||||||
|
"1.21.1, 1.20.1, false",
|
||||||
|
"1.7.2, 1.21.5, false",
|
||||||
|
"1.19.4, 1.21.1, false",
|
||||||
|
"1.21.3, 1.21.3, true",
|
||||||
|
"1.20.1, 1.20.1, true",
|
||||||
|
"1.21.7, 1.21.7, true",
|
||||||
|
"1.21.8, >=1.21.7, true",
|
||||||
|
"1.21.8, >1.21.7, true",
|
||||||
|
"1.0, <1.21.7, true",
|
||||||
|
"1.17.1, !1.17.1, false",
|
||||||
|
"1.21.7, '>=1.21.7 <=1.21.8', true",
|
||||||
|
"1.21.8, '>=1.21.7 <=1.21.8', true",
|
||||||
|
"1.21.5, '>=1.21.7 <=1.21.8', false",
|
||||||
|
})
|
||||||
|
public void testCompatibilityChecker(@NotNull String mcVer, @NotNull String range, boolean exp) {
|
||||||
|
final Version version = Version.fromString(mcVer);
|
||||||
|
Assertions.assertNotNull(version, "Version should not be null");
|
||||||
|
|
||||||
|
final CompatibilityChecker.CompatibilityConfig config = new CompatibilityChecker.CompatibilityConfig(range);
|
||||||
|
Assertions.assertEquals(exp, config.isCompatibleWith(version), "Checker should return " + exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ HuskSync supports the following versions of Minecraft. Since v3.7, you must down
|
|||||||
|
|
||||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||||
| 1.21.7 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
| 1.21.7/8 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||||
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
||||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.20.1+build.10:v2
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.20.1+build.10:v2
|
||||||
|
|
||||||
|
minecraft_version_range='1.20.1'
|
||||||
|
|
||||||
fabric_loader_version=0.15.11
|
fabric_loader_version=0.15.11
|
||||||
fabric_api_version=0.92.2+1.20.1
|
fabric_api_version=0.92.2+1.20.1
|
||||||
fabric_permissions_api_version=0.2-SNAPSHOT
|
fabric_permissions_api_version=0.2-SNAPSHOT
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.1+build.3:v2
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.1+build.3:v2
|
||||||
|
|
||||||
|
minecraft_version_range='1.21.1'
|
||||||
|
|
||||||
fabric_loader_version=0.16.10
|
fabric_loader_version=0.16.10
|
||||||
fabric_api_version=0.107.0+1.21.1
|
fabric_api_version=0.107.0+1.21.1
|
||||||
fabric_permissions_api_version=0.3.1
|
fabric_permissions_api_version=0.3.1
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.4+build.4:v2
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.4+build.4:v2
|
||||||
|
|
||||||
|
minecraft_version_range='1.21.4'
|
||||||
|
|
||||||
fabric_loader_version=0.16.10
|
fabric_loader_version=0.16.10
|
||||||
fabric_api_version=0.116.1+1.21.4
|
fabric_api_version=0.116.1+1.21.4
|
||||||
fabric_permissions_api_version=0.3.3
|
fabric_permissions_api_version=0.3.3
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.5+build.1:v2
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.5+build.1:v2
|
||||||
|
|
||||||
|
minecraft_version_range='1.21.5'
|
||||||
|
|
||||||
fabric_loader_version=0.16.14
|
fabric_loader_version=0.16.14
|
||||||
fabric_api_version=0.122.0+1.21.5
|
fabric_api_version=0.122.0+1.21.5
|
||||||
fabric_permissions_api_version=0.3.3
|
fabric_permissions_api_version=0.3.3
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.7+build.2:v2
|
|
||||||
|
|
||||||
fabric_loader_version=0.16.14
|
|
||||||
fabric_api_version=0.128.1+1.21.7
|
|
||||||
fabric_permissions_api_version=0.4.1
|
|
||||||
fabric_adventure_platform_version=6.5.0-SNAPSHOT
|
|
||||||
fabric_sgui_version=1.10.0+1.21.6
|
|
||||||
9
fabric/1.21.8/gradle.properties
Normal file
9
fabric/1.21.8/gradle.properties
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.8+build.1:v2
|
||||||
|
|
||||||
|
minecraft_version_range=>=1.21.7 <=1.21.8
|
||||||
|
|
||||||
|
fabric_loader_version=0.17.2
|
||||||
|
fabric_api_version=0.131.0+1.21.8
|
||||||
|
fabric_permissions_api_version=0.4.1
|
||||||
|
fabric_adventure_platform_version=6.6.0
|
||||||
|
fabric_sgui_version=1.10.0+1.21.6
|
||||||
@@ -14,13 +14,13 @@ dependencies {
|
|||||||
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}")
|
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}")
|
||||||
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
||||||
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
|
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
|
||||||
modImplementation include("net.william278.uniform:uniform-fabric:1.3.8+${project.name}")
|
modImplementation include("net.william278.uniform:uniform-fabric:1.3.9+${project.name}")
|
||||||
modImplementation include("net.william278.toilet:toilet-fabric:1.0.15+${project.name}")
|
modImplementation include("net.william278.toilet:toilet-fabric:1.0.16+${project.name}")
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
// Manually include config deps due to the way including api deps works
|
// Manually include config deps due to the way including api deps works
|
||||||
implementation include("de.exlll:configlib-core:4.6.1")
|
implementation include("de.exlll:configlib-core:4.6.1")
|
||||||
implementation include("org.snakeyaml:snakeyaml-engine:2.9")
|
implementation include("org.snakeyaml:snakeyaml-engine:2.10")
|
||||||
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
||||||
|
|
||||||
// Include driver deps due to no runtime dep loading support
|
// Include driver deps due to no runtime dep loading support
|
||||||
@@ -49,7 +49,8 @@ processResources {
|
|||||||
expand([
|
expand([
|
||||||
version: version,
|
version: version,
|
||||||
fabric_loader_version: fabric_loader_version,
|
fabric_loader_version: fabric_loader_version,
|
||||||
fabric_minecraft_version: project.name
|
fabric_minecraft_version: project.name,
|
||||||
|
minecraft_version_range: minecraft_version_range
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.21.7
|
1.21.8
|
||||||
@@ -3,15 +3,15 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preprocess {
|
preprocess {
|
||||||
def fabric12107 = createNode("1.21.7", 12107, "yarn")
|
def fabric12108 = createNode("1.21.8", 12108, "yarn")
|
||||||
def fabric12105 = createNode("1.21.5", 12105, "yarn")
|
def fabric12105 = createNode("1.21.5", 12105, "yarn")
|
||||||
def fabric12104 = createNode("1.21.4", 12104, "yarn")
|
def fabric12104 = createNode("1.21.4", 12104, "yarn")
|
||||||
def fabric12101 = createNode("1.21.1", 12101, "yarn")
|
def fabric12101 = createNode("1.21.1", 12101, "yarn")
|
||||||
def fabric12001 = createNode("1.20.1", 12001, "yarn")
|
def fabric12001 = createNode("1.20.1", 12001, "yarn")
|
||||||
|
|
||||||
strictExtraMappings.set(true)
|
strictExtraMappings.set(true)
|
||||||
fabric12105.link(fabric12107, null)
|
fabric12105.link(fabric12108, null)
|
||||||
fabric12104.link(fabric12107, null)
|
fabric12104.link(fabric12108, null)
|
||||||
fabric12101.link(fabric12107, null)
|
fabric12101.link(fabric12108, null)
|
||||||
fabric12001.link(fabric12107, null)
|
fabric12001.link(fabric12108, null)
|
||||||
}
|
}
|
||||||
@@ -88,22 +88,6 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
|
|
||||||
private static final String PLATFORM_TYPE_ID = "fabric";
|
private static final String PLATFORM_TYPE_ID = "fabric";
|
||||||
|
|
||||||
private static final int VERSION1_16_5 = 2586;
|
|
||||||
private static final int VERSION1_17_1 = 2730;
|
|
||||||
private static final int VERSION1_18_2 = 2975;
|
|
||||||
private static final int VERSION1_19_2 = 3120;
|
|
||||||
private static final int VERSION1_19_4 = 3337;
|
|
||||||
private static final int VERSION1_20_1 = 3465;
|
|
||||||
private static final int VERSION1_20_2 = 3578;
|
|
||||||
private static final int VERSION1_20_4 = 3700;
|
|
||||||
private static final int VERSION1_20_5 = 3837;
|
|
||||||
private static final int VERSION1_21_1 = 3955;
|
|
||||||
private static final int VERSION1_21_3 = 4082;
|
|
||||||
private static final int VERSION1_21_4 = 4189;
|
|
||||||
private static final int VERSION1_21_5 = 4323;
|
|
||||||
private static final int VERSION1_21_6 = 4435;
|
|
||||||
private static final int VERSION1_21_7 = 4438;
|
|
||||||
|
|
||||||
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
||||||
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||||
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
||||||
@@ -374,37 +358,6 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
return Version.fromString(minecraftServer.getVersion());
|
return Version.fromString(minecraftServer.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDataVersion(@NotNull Version mcVersion) {
|
|
||||||
return switch (mcVersion.toStringWithoutMetadata()) {
|
|
||||||
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
|
|
||||||
case "1.17", "1.17.1" -> VERSION1_17_1;
|
|
||||||
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
|
|
||||||
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
|
|
||||||
case "1.19.4" -> VERSION1_19_4;
|
|
||||||
case "1.20", "1.20.1" -> VERSION1_20_1;
|
|
||||||
case "1.20.2" -> VERSION1_20_2;
|
|
||||||
case "1.20.4" -> VERSION1_20_4;
|
|
||||||
case "1.20.5", "1.20.6" -> VERSION1_20_5;
|
|
||||||
case "1.21", "1.21.1" -> VERSION1_21_1;
|
|
||||||
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
|
||||||
case "1.21.4" -> VERSION1_21_4;
|
|
||||||
case "1.21.5" -> VERSION1_21_5;
|
|
||||||
case "1.21.6" -> VERSION1_21_6;
|
|
||||||
case "1.21.7" -> VERSION1_21_7;
|
|
||||||
//#if MC==12107
|
|
||||||
default -> VERSION1_21_7;
|
|
||||||
//#elseif MC==12105
|
|
||||||
//$$ default -> VERSION1_21_5;
|
|
||||||
//#elseif MC==12104
|
|
||||||
//$$ default -> VERSION1_21_4;
|
|
||||||
//#elseif MC==12101
|
|
||||||
//$$ default -> VERSION1_21_1;
|
|
||||||
//#elseif MC==12001
|
|
||||||
//$$ default -> VERSION1_20_1;
|
|
||||||
//#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getPlatformType() {
|
public String getPlatformType() {
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ public abstract class FabricSerializer {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager reg) {
|
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager reg) {
|
||||||
try {
|
try {
|
||||||
//#if MC>=12107
|
//#if MC>=12108
|
||||||
return (NbtCompound) ItemStack.CODEC.encodeStart(reg.getOps(NbtOps.INSTANCE), item).getOrThrow();
|
return (NbtCompound) ItemStack.CODEC.encodeStart(reg.getOps(NbtOps.INSTANCE), item).getOrThrow();
|
||||||
//#elseif MC>=12104
|
//#elseif MC>=12104
|
||||||
//$$ return (NbtCompound) item.toNbt(reg);
|
//$$ return (NbtCompound) item.toNbt(reg);
|
||||||
@@ -292,7 +292,7 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager reg) {
|
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager reg) {
|
||||||
//#if MC>=12107
|
//#if MC>=12108
|
||||||
final @Nullable ItemStack stack = ItemStack.CODEC.decode(reg.getOps(NbtOps.INSTANCE), item).getOrThrow().getFirst();
|
final @Nullable ItemStack stack = ItemStack.CODEC.decode(reg.getOps(NbtOps.INSTANCE), item).getOrThrow().getFirst();
|
||||||
//#elseif MC>12001
|
//#elseif MC>12001
|
||||||
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt(reg, item).orElse(null);
|
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt(reg, item).orElse(null);
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# File used for checking Minecraft server compatibility with this version of HuskSync
|
# File used for checking Minecraft server compatibility with this version of HuskSync
|
||||||
minecraft_version: '${fabric_minecraft_version}'
|
minecraft_version_range: '${minecraft_version_range}'
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=${fabric_loader_version}",
|
"fabricloader": ">=${fabric_loader_version}",
|
||||||
"minecraft": "${fabric_minecraft_version}",
|
"minecraft": "${minecraft_version_range}",
|
||||||
"fabric-api": "*"
|
"fabric-api": "*"
|
||||||
},
|
},
|
||||||
"suggests": {
|
"suggests": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ org.gradle.daemon=true
|
|||||||
javaVersion=21
|
javaVersion=21
|
||||||
|
|
||||||
# Plugin metadata
|
# Plugin metadata
|
||||||
plugin_version=3.8.6
|
plugin_version=3.8.7
|
||||||
plugin_archive=husksync
|
plugin_archive=husksync
|
||||||
plugin_description=A modern, cross-server player data synchronization system
|
plugin_description=A modern, cross-server player data synchronization system
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user