mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 17:19:19 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3875447430 | ||
|
|
dce84f285d | ||
|
|
1314683eea | ||
|
|
6b1f89aab0 | ||
|
|
27e958a474 | ||
|
|
39ebd0dc4f | ||
|
|
2ada0497ec | ||
|
|
e9f2856040 | ||
|
|
6050c584c0 | ||
|
|
7ebf91bfae | ||
|
|
2d7799628a | ||
|
|
1627de732b | ||
|
|
fea882c642 | ||
|
|
8b749357f7 | ||
|
|
e4ff7e6d6c | ||
|
|
396630821f | ||
|
|
70f65d126b | ||
|
|
e8925a0d79 | ||
|
|
2a3cf9be7d | ||
|
|
971d3f5167 | ||
|
|
99da65a4d8 | ||
|
|
25744b4ef7 | ||
|
|
8f2d1c7298 | ||
|
|
a9aa93a682 | ||
|
|
ef340840ab | ||
|
|
cf08015961 | ||
|
|
cb09e0cfb2 | ||
|
|
554fac89c0 | ||
|
|
215bed9908 | ||
|
|
935aafa74a | ||
|
|
c51ba85f38 | ||
|
|
6a67d1bbe0 | ||
|
|
20bc76a768 | ||
|
|
6928f97dff | ||
|
|
06742fb848 | ||
|
|
759983b000 | ||
|
|
5556e3b6ce | ||
|
|
bcffcb1f64 | ||
|
|
fa77e6e418 | ||
|
|
c8aa29c82f | ||
|
|
51cf982359 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -55,14 +55,20 @@ jobs:
|
||||
paper-1.21.1
|
||||
paper-1.21.4
|
||||
paper-1.21.5
|
||||
paper-1.21.7
|
||||
fabric-1.20.1
|
||||
fabric-1.21.1
|
||||
fabric-1.21.4
|
||||
fabric-1.21.5
|
||||
fabric-1.21.7
|
||||
distro-groups: |
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
@@ -71,14 +77,20 @@ jobs:
|
||||
Paper 1.21.1
|
||||
Paper 1.21.4
|
||||
Paper 1.21.5
|
||||
Paper 1.21.7
|
||||
Fabric 1.20.1
|
||||
Fabric 1.21.1
|
||||
Fabric 1.21.4
|
||||
Fabric 1.21.5
|
||||
Fabric 1.21.7
|
||||
files: |
|
||||
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.4.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-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.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.7.jar
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -44,14 +44,20 @@ jobs:
|
||||
paper-1.21.1
|
||||
paper-1.21.4
|
||||
paper-1.21.5
|
||||
paper-1.21.7
|
||||
fabric-1.20.1
|
||||
fabric-1.21.1
|
||||
fabric-1.21.4
|
||||
fabric-1.21.5
|
||||
fabric-1.21.7
|
||||
distro-groups: |
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
paper
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
fabric
|
||||
@@ -60,14 +66,20 @@ jobs:
|
||||
Paper 1.21.1
|
||||
Paper 1.21.4
|
||||
Paper 1.21.5
|
||||
Paper 1.21.7
|
||||
Fabric 1.20.1
|
||||
Fabric 1.21.1
|
||||
Fabric 1.21.4
|
||||
Fabric 1.21.5
|
||||
Fabric 1.21.7
|
||||
files: |
|
||||
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.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.7.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.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.7.jar
|
||||
@@ -48,7 +48,9 @@ HuskSync supports the following [compatible versions](https://william278.net/doc
|
||||
|
||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||
| 1.21.7 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||
|
||||
10
build.gradle
10
build.gradle
@@ -1,11 +1,11 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'com.gradleup.shadow' version '8.3.6'
|
||||
id 'com.gradleup.shadow' version '8.3.7'
|
||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||
id 'fabric-loom' version "$fabric_loom_version" apply false
|
||||
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
|
||||
id 'gg.essential.multi-version.root' apply false
|
||||
id 'org.ajoberstar.grgit' version '5.3.0'
|
||||
id 'org.ajoberstar.grgit' version '5.3.2'
|
||||
id 'maven-publish'
|
||||
id 'java'
|
||||
}
|
||||
@@ -89,7 +89,7 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(platform("org.junit:junit-bom:5.12.2"))
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.2"))
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
||||
@@ -137,7 +137,7 @@ subprojects {
|
||||
version += "+mc.${project.name}"
|
||||
|
||||
if (project.parent?.name?.equals('fabric')) {
|
||||
apply plugin: 'fabric-loom'
|
||||
apply plugin: 'dev.architectury.loom'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
bukkit/1.21.7/gradle.properties
Normal file
3
bukkit/1.21.7/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
minecraft_version_numeric=12107
|
||||
minecraft_api_version=1.21
|
||||
paper_api_version=1.21.7-R0.1-SNAPSHOT
|
||||
@@ -8,26 +8,26 @@ plugins {
|
||||
dependencies {
|
||||
implementation project(path: ':common')
|
||||
|
||||
implementation 'net.william278.uniform:uniform-bukkit:1.3.3'
|
||||
implementation 'net.william278.uniform:uniform-paper:1.3.3'
|
||||
implementation 'net.william278.toilet:toilet-bukkit:1.0.12'
|
||||
implementation 'net.william278.uniform:uniform-bukkit:1.3.8'
|
||||
implementation 'net.william278.uniform:uniform-paper:1.3.8'
|
||||
implementation 'net.william278.toilet:toilet-bukkit:1.0.15'
|
||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||
implementation 'net.william278:hsldataconverter:1.0'
|
||||
implementation 'net.william278:mapdataapi:2.0'
|
||||
implementation 'org.bstats:bstats-bukkit:3.1.0'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.4.0'
|
||||
implementation 'dev.triumphteam:triumph-gui:3.1.12'
|
||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||
implementation 'de.tr7zw:item-nbt-api:2.15.0'
|
||||
implementation 'de.tr7zw:item-nbt-api:2.15.1'
|
||||
|
||||
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
|
||||
compileOnly 'com.github.retrooper:packetevents-spigot:2.7.0'
|
||||
compileOnly 'com.github.retrooper:packetevents-spigot:2.8.0'
|
||||
compileOnly 'com.github.dmulloy2:ProtocolLib:5.3.0'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||
compileOnly 'commons-io:commons-io:2.19.0'
|
||||
compileOnly 'org.json:json:20250107'
|
||||
compileOnly 'org.json:json:20250517'
|
||||
compileOnly 'net.william278:minedown:1.8.2'
|
||||
compileOnly 'de.exlll:configlib-yaml:4.5.0'
|
||||
compileOnly 'de.exlll:configlib-yaml:4.6.1'
|
||||
compileOnly 'com.zaxxer:HikariCP:6.3.0'
|
||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||
@@ -93,5 +93,9 @@ shadowJar {
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion(project.name)
|
||||
|
||||
downloadPlugins {
|
||||
github("plan-player-analytics", "Plan", "5.6.2965", "Plan-5.6-build-2965.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,9 +93,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
private static final int METRICS_ID = 13140;
|
||||
private static final String PLATFORM_TYPE_ID = "bukkit";
|
||||
|
||||
private final TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
|
||||
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
|
||||
);
|
||||
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
||||
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||
private final Map<Integer, MapView> mapViews = Maps.newConcurrentMap();
|
||||
private final List<Migrator> availableMigrators = Lists.newArrayList();
|
||||
@@ -356,9 +354,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
||||
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
||||
case "1.21", "1.21.1" -> DataFixerUtil.VERSION1_21;
|
||||
case "1.21.2", "1.21.3" -> DataFixerUtil.VERSION1_21_2;
|
||||
case "1.21.4" -> 4189;
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@@ -46,13 +47,20 @@ public class PaperHuskSyncLoader implements PluginLoader {
|
||||
resolveLibraries(classpathBuilder).stream()
|
||||
.map(DefaultArtifact::new)
|
||||
.forEach(artifact -> resolver.addDependency(new Dependency(artifact, null)));
|
||||
resolver.addRepository(new RemoteRepository.Builder(
|
||||
"maven", "default", "https://repo.maven.apache.org/maven2/"
|
||||
).build());
|
||||
resolver.addRepository(new RemoteRepository.Builder("maven", "default", getMavenUrl()).build());
|
||||
|
||||
classpathBuilder.addLibrary(resolver);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMavenUrl() {
|
||||
return Stream.of(
|
||||
System.getenv("PAPER_DEFAULT_CENTRAL_REPOSITORY"),
|
||||
System.getProperty("org.bukkit.plugin.java.LibraryLoader.centralURL"),
|
||||
"https://maven-central.storage-download.googleapis.com/maven2"
|
||||
).filter(Objects::nonNull).findFirst().orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> resolveLibraries(@NotNull PluginClasspathBuilder classpathBuilder) {
|
||||
try (InputStream input = getLibraryListFile()) {
|
||||
|
||||
@@ -135,7 +135,7 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onMapInitialize(@NotNull MapInitializeEvent event) {
|
||||
if (plugin.getSettings().getSynchronization().isPersistLockedMaps() && event.getMap().isLocked()) {
|
||||
getPlugin().runAsync(() -> ((BukkitHuskSync) plugin).renderPersistedMap(event.getMap()));
|
||||
getPlugin().runAsync(() -> ((BukkitHuskSync) plugin).renderInitializingLockedMap(event.getMap()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -254,111 +254,139 @@ public interface BukkitMapHandler {
|
||||
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ReadableNBT mapData = nbt.getCompound(MAP_DATA_KEY);
|
||||
if (mapData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine map ID
|
||||
final String originServerName = mapData.getString(MAP_ORIGIN_KEY);
|
||||
final String currentServerName = getPlugin().getServerName();
|
||||
final int originalMapId = mapData.getInteger(MAP_ID_KEY);
|
||||
int newId = currentServerName.equals(originServerName)
|
||||
? originalMapId : getBoundMapId(originServerName, originalMapId, currentServerName);
|
||||
// Server the map was originally created on, and the current server. If they match, isOrigin is true.
|
||||
final String originServer = mapData.getString(MAP_ORIGIN_KEY);
|
||||
final String currentServer = getPlugin().getServerName();
|
||||
final boolean isOrigin = currentServer.equals(originServer);
|
||||
|
||||
// Determine the map's ID on its origin server, and the new ID it should be bound to here.
|
||||
// Then, update the map item / data accordingly (re-rendering and caching the map if needed)
|
||||
final int originalId = mapData.getInteger(MAP_ID_KEY);
|
||||
int newId = isOrigin ? originalId : getBoundMapId(originServer, originalId, currentServer);
|
||||
if (newId != -1) {
|
||||
final MapView view = Bukkit.getMap(newId);
|
||||
if (view != null) {
|
||||
meta.setMapView(view);
|
||||
meta.setMapId(newId);
|
||||
map.setItemMeta(meta);
|
||||
getPlugin().debug(String.format("Map ID set to #%s", newId));
|
||||
return;
|
||||
}
|
||||
getPlugin().debug(String.format("Map ID #%s not saved on this server, creating...", newId));
|
||||
handleBoundMap(meta, nbt, originServer, originalId, newId, isOrigin);
|
||||
} else {
|
||||
handleUnboundMap(meta, nbt, originServer, originalId, currentServer);
|
||||
}
|
||||
|
||||
// Read the pixel data from the ItemStack and generate a map view otherwise
|
||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||
@Nullable Map.Entry<MapData, Boolean> readMapData = readMapData(originServerName, originalMapId);
|
||||
if (readMapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
||||
readMapData = readLegacyMapItemData(nbt);
|
||||
}
|
||||
|
||||
// If map data was found, add a renderer to the MapView
|
||||
if (readMapData == null) {
|
||||
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
||||
return;
|
||||
}
|
||||
final MapData canvasData = Objects.requireNonNull(readMapData, "Pixel data null!").getKey();
|
||||
final MapView view = generateRenderedMap(canvasData);
|
||||
meta.setMapView(view);
|
||||
map.setItemMeta(meta);
|
||||
|
||||
// Bind in the database & Redis
|
||||
final int id = view.getId();
|
||||
getRedisManager().bindMapIds(originServerName, originalMapId, currentServerName, id);
|
||||
getPlugin().getDatabase().setMapBinding(originServerName, originalMapId, currentServerName, id);
|
||||
meta.setMapId(id);
|
||||
|
||||
getPlugin().debug(String.format("Bound map to view (#%s) on server %s", id, currentServerName));
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
default void renderPersistedMap(@NotNull MapView view) {
|
||||
if (getMapView(view.getId()).isPresent()) {
|
||||
private void handleBoundMap(@NotNull MapMeta meta, @NotNull ReadableItemNBT nbt, @NotNull String originServer,
|
||||
int originalId, int newId, boolean isOrigin) {
|
||||
MapView view = Bukkit.getMap(newId);
|
||||
if (isOrigin && view != null) {
|
||||
meta.setMapView(view);
|
||||
getPlugin().debug("Map ID set to original ID #%s".formatted(newId));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read map data, or
|
||||
@Nullable Map.Entry<MapData, Boolean> data = readMapData(getPlugin().getServerName(), view.getId());
|
||||
Optional<MapView> optionalView = getMapView(newId);
|
||||
if (optionalView.isPresent()) {
|
||||
meta.setMapView(optionalView.get());
|
||||
getPlugin().debug("Map ID set to #%s".formatted(newId));
|
||||
return;
|
||||
}
|
||||
|
||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||
Map.Entry<MapData, Boolean> mapData = readMapData(originServer, originalId);
|
||||
if (mapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
||||
mapData = readLegacyMapItemData(nbt);
|
||||
}
|
||||
|
||||
if (mapData == null) {
|
||||
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
MapView newView = view != null ? view : Bukkit.createMap(getDefaultMapWorld());
|
||||
generateRenderedMap(Objects.requireNonNull(mapData).getKey(), newView);
|
||||
meta.setMapView(newView);
|
||||
}
|
||||
|
||||
private void handleUnboundMap(@NotNull MapMeta meta, @NotNull ReadableItemNBT nbt, @NotNull String originServer,
|
||||
int originalId, @NotNull String currentServer) {
|
||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||
Map.Entry<MapData, Boolean> mapData = readMapData(originServer, originalId);
|
||||
if (mapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
||||
mapData = readLegacyMapItemData(nbt);
|
||||
}
|
||||
|
||||
if (mapData == null) {
|
||||
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
final MapView view = generateRenderedMap(Objects.requireNonNull(mapData, "Pixel data null!").getKey());
|
||||
meta.setMapView(view);
|
||||
|
||||
final int id = view.getId();
|
||||
getRedisManager().bindMapIds(originServer, originalId, currentServer, id);
|
||||
getPlugin().getDatabase().setMapBinding(originServer, originalId, currentServer, id);
|
||||
|
||||
getPlugin().debug("Bound map to view (#%s) on server %s".formatted(id, currentServer));
|
||||
}
|
||||
|
||||
// Render a persisted locked map that is initializing (i.e. in an item frame)
|
||||
default void renderInitializingLockedMap(@NotNull MapView view) {
|
||||
if (view.isVirtual()) {
|
||||
return;
|
||||
}
|
||||
final Optional<MapView> optionalView = getMapView(view.getId());
|
||||
if (optionalView.isPresent()) {
|
||||
view.getRenderers().clear();
|
||||
view.getRenderers().addAll(optionalView.get().getRenderers());
|
||||
view.setLocked(true);
|
||||
view.setScale(MapView.Scale.NORMAL);
|
||||
view.setTrackingPosition(false);
|
||||
view.setUnlimitedTracking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Map.Entry<MapData, Boolean> data = readMapData(getPlugin().getServerName(), view.getId());
|
||||
if (data == null) {
|
||||
data = readLegacyMapFileData(view.getId());
|
||||
}
|
||||
|
||||
// Don't render maps with no data
|
||||
if (data == null) {
|
||||
final World world = view.getWorld() == null ? getDefaultMapWorld() : view.getWorld();
|
||||
World world = view.getWorld() == null ? getDefaultMapWorld() : view.getWorld();
|
||||
getPlugin().debug("Not rendering map: no data in DB for world %s, map #%s."
|
||||
.formatted(world.getName(), view.getId()));
|
||||
return;
|
||||
}
|
||||
// Don't render persisted maps on this server
|
||||
if (data.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MapData canvasData = data.getKey();
|
||||
|
||||
// Create a new map view renderer with the map data color at each pixel
|
||||
// use view.removeRenderer() to remove all this maps renderers
|
||||
view.getRenderers().forEach(view::removeRenderer);
|
||||
view.addRenderer(new PersistentMapRenderer(canvasData));
|
||||
view.setLocked(true);
|
||||
view.setScale(MapView.Scale.NORMAL);
|
||||
view.setTrackingPosition(false);
|
||||
view.setUnlimitedTracking(false);
|
||||
|
||||
// Set the view to the map
|
||||
setMapView(view);
|
||||
renderMapView(view, data.getKey());
|
||||
}
|
||||
|
||||
// Sets the renderer of a map, and returns the generated MapView
|
||||
@NotNull
|
||||
private MapView generateRenderedMap(@NotNull MapData canvasData) {
|
||||
final MapView view = Bukkit.createMap(getDefaultMapWorld());
|
||||
view.getRenderers().clear();
|
||||
return generateRenderedMap(canvasData, Bukkit.createMap(getDefaultMapWorld()));
|
||||
}
|
||||
|
||||
// Create a new map view renderer with the map data color at each pixel
|
||||
@NotNull
|
||||
private MapView generateRenderedMap(@NotNull MapData canvasData, @NotNull MapView view) {
|
||||
renderMapView(view, canvasData);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void renderMapView(@NotNull MapView view, @NotNull MapData canvasData) {
|
||||
view.getRenderers().clear();
|
||||
view.addRenderer(new PersistentMapRenderer(canvasData));
|
||||
view.setLocked(true);
|
||||
view.setScale(MapView.Scale.NORMAL);
|
||||
view.setTrackingPosition(false);
|
||||
view.setUnlimitedTracking(false);
|
||||
|
||||
// Set the view to the map and return it
|
||||
setMapView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -7,27 +7,27 @@ dependencies {
|
||||
api 'org.apache.commons:commons-text:1.13.1'
|
||||
api 'net.william278:minedown:1.8.2'
|
||||
api 'net.william278:mapdataapi:2.0'
|
||||
api 'org.json:json:20250107'
|
||||
api 'com.google.code.gson:gson:2.13.0'
|
||||
api 'org.json:json:20250517'
|
||||
api 'com.google.code.gson:gson:2.13.1'
|
||||
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
||||
api 'de.exlll:configlib-yaml:4.5.0'
|
||||
api 'de.exlll:configlib-yaml:4.6.1'
|
||||
api 'net.william278:paginedown:1.1.2'
|
||||
api 'net.william278:DesertWell:2.0.4'
|
||||
api('com.zaxxer:HikariCP:6.3.0') {
|
||||
exclude module: 'slf4j-api'
|
||||
}
|
||||
|
||||
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.12'
|
||||
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.15'
|
||||
|
||||
compileOnly 'net.william278.uniform:uniform-common:1.3.3'
|
||||
compileOnly 'net.william278.uniform:uniform-common:1.3.8'
|
||||
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||
compileOnly 'org.jetbrains:annotations:26.0.2'
|
||||
compileOnly 'net.kyori:adventure-api:4.20.0'
|
||||
compileOnly 'net.kyori:adventure-platform-api:4.3.4'
|
||||
compileOnly "net.kyori:adventure-text-serializer-plain:4.20.0"
|
||||
compileOnly 'net.kyori:adventure-api:4.23.0'
|
||||
compileOnly 'net.kyori:adventure-platform-api:4.4.0'
|
||||
compileOnly "net.kyori:adventure-text-serializer-plain:4.23.0"
|
||||
compileOnly 'com.google.guava:guava:33.4.8-jre'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.6.2965'
|
||||
compileOnly "redis.clients:jedis:$jedis_version"
|
||||
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
||||
compileOnly "org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version"
|
||||
@@ -38,8 +38,8 @@ dependencies {
|
||||
testImplementation "redis.clients:jedis:$jedis_version"
|
||||
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
||||
testImplementation 'com.google.guava:guava:33.4.8-jre'
|
||||
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
|
||||
testImplementation 'com.github.plan-player-analytics:Plan:5.6.2965'
|
||||
testCompileOnly 'de.exlll:configlib-yaml:4.6.1'
|
||||
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.38'
|
||||
|
||||
@@ -44,7 +44,6 @@ public class InventoryCommand extends ItemsCommand {
|
||||
@NotNull User user, boolean allowEdit) {
|
||||
final Optional<Data.Items.Inventory> optionalInventory = snapshot.getInventory();
|
||||
if (optionalInventory.isEmpty()) {
|
||||
viewer.sendMessage(new MineDown("what the FUCK is happening"));
|
||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||
.ifPresent(viewer::sendMessage);
|
||||
return;
|
||||
|
||||
@@ -150,7 +150,7 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
// 𝓡𝓮𝓭𝓲𝓼 settings
|
||||
// Redis settings
|
||||
@Comment("Redis settings")
|
||||
private RedisSettings redis = new RedisSettings();
|
||||
|
||||
@@ -159,7 +159,9 @@ public class Settings {
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class RedisSettings {
|
||||
|
||||
@Comment("Specify the credentials of your Redis server here. Set \"password\" to '' if you don't have one")
|
||||
@Comment({"Specify the credentials of your Redis server here.",
|
||||
"Set \"user\" to '' if you don't have one or would like to use the default user.",
|
||||
"Set \"password\" to '' if you don't have one."})
|
||||
private RedisCredentials credentials = new RedisCredentials();
|
||||
|
||||
@Getter
|
||||
@@ -168,6 +170,9 @@ public class Settings {
|
||||
public static class RedisCredentials {
|
||||
private String host = "localhost";
|
||||
private int port = 6379;
|
||||
@Comment("Only change the database if you know what you are doing. The default is 0.")
|
||||
private int database = 0;
|
||||
private String user = "";
|
||||
private String password = "";
|
||||
private boolean useSsl = false;
|
||||
}
|
||||
@@ -321,6 +326,9 @@ public class Settings {
|
||||
@Getter(AccessLevel.NONE)
|
||||
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)")
|
||||
private boolean checkinPetitions = true;
|
||||
|
||||
public boolean doAutoPin(@NotNull DataSnapshot.SaveCause cause) {
|
||||
return autoPinnedSaveCauses.contains(cause.name());
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ public class DataSnapshot {
|
||||
public static class Unpacked extends DataSnapshot implements DataHolder {
|
||||
|
||||
@Expose(serialize = false, deserialize = false)
|
||||
private final TreeMap<Identifier, Data> deserialized;
|
||||
private final Map<Identifier, Data> deserialized;
|
||||
|
||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||
@@ -381,7 +381,7 @@ public class DataSnapshot {
|
||||
}
|
||||
|
||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||
@NotNull String saveCause, @NotNull String serverName, @NotNull TreeMap<Identifier, Data> data,
|
||||
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<Identifier, Data> data,
|
||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||
super(id, pinned, timestamp, saveCause, serverName, Map.of(), minecraftVersion, platformType, formatVersion);
|
||||
this.deserialized = data;
|
||||
@@ -389,14 +389,15 @@ public class DataSnapshot {
|
||||
|
||||
@NotNull
|
||||
@ApiStatus.Internal
|
||||
private TreeMap<Identifier, Data> deserializeData(@NotNull HuskSync plugin) {
|
||||
private Map<Identifier, Data> deserializeData(@NotNull HuskSync plugin) {
|
||||
return data.entrySet().stream()
|
||||
.filter(e -> plugin.getIdentifier(e.getKey()).isPresent())
|
||||
.map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue()))
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> plugin.deserializeData(entry.getKey(), entry.getValue(), getMinecraftVersion()),
|
||||
(a, b) -> b, () -> Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR)
|
||||
(a, b) -> a,
|
||||
HashMap::new
|
||||
));
|
||||
}
|
||||
|
||||
@@ -423,6 +424,20 @@ public class DataSnapshot {
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sorted iterable of the snapshots the snapshot is holding
|
||||
*
|
||||
* @return The data map
|
||||
* @since 3.8.2
|
||||
*/
|
||||
@NotNull
|
||||
@ApiStatus.Internal
|
||||
public Iterable<Map.Entry<Identifier, Data>> getSortedIterable() {
|
||||
final TreeMap<Identifier, Data> tree = Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR);
|
||||
tree.putAll(deserialized);
|
||||
return tree.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack the {@link DataSnapshot} into a {@link DataSnapshot.Packed packed} snapshot
|
||||
*
|
||||
@@ -455,12 +470,12 @@ public class DataSnapshot {
|
||||
private String serverName;
|
||||
private boolean pinned;
|
||||
private OffsetDateTime timestamp;
|
||||
private final TreeMap<Identifier, Data> data;
|
||||
private final Map<Identifier, Data> data;
|
||||
|
||||
private Builder(@NotNull HuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
this.pinned = false;
|
||||
this.data = Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR);
|
||||
this.data = Maps.newHashMap();
|
||||
this.timestamp = OffsetDateTime.now();
|
||||
this.id = UUID.randomUUID();
|
||||
this.serverName = plugin.getServerName();
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface SerializerRegistry {
|
||||
|
||||
@@ -40,7 +41,7 @@ public interface SerializerRegistry {
|
||||
* @since 3.0
|
||||
*/
|
||||
@NotNull
|
||||
<T extends Data> TreeMap<Identifier, Serializer<T>> getSerializers();
|
||||
<T extends Data> Map<Identifier, Serializer<T>> getSerializers();
|
||||
|
||||
/**
|
||||
* Register a data serializer for the given {@link Identifier}
|
||||
@@ -87,8 +88,7 @@ public interface SerializerRegistry {
|
||||
* @since 3.0
|
||||
*/
|
||||
default Optional<Identifier> getIdentifier(@NotNull String key) {
|
||||
return getSerializers().keySet().stream()
|
||||
.filter(id -> id.getKey().asString().equals(key)).findFirst();
|
||||
return getSerializers().keySet().stream().filter(e -> e.toString().equals(key)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +99,7 @@ public interface SerializerRegistry {
|
||||
* @since 3.5.4
|
||||
*/
|
||||
default Optional<Serializer<Data>> getSerializer(@NotNull Identifier identifier) {
|
||||
return getSerializers().entrySet().stream()
|
||||
.filter(entry -> entry.getKey().getKey().equals(identifier.getKey()))
|
||||
.map(Map.Entry::getValue).findFirst();
|
||||
return Optional.ofNullable(getSerializers().get(identifier));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,14 +151,14 @@ public interface SerializerRegistry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of registered data types
|
||||
* Get the list of registered data types, in dependency order
|
||||
*
|
||||
* @return the set of registered data types
|
||||
* @return the list of registered data types
|
||||
* @since 3.0
|
||||
*/
|
||||
@NotNull
|
||||
default Set<Identifier> getRegisteredDataTypes() {
|
||||
return getSerializers().keySet();
|
||||
default List<Identifier> getRegisteredDataTypes() {
|
||||
return getSerializers().keySet().stream().sorted(DEPENDENCY_ORDER_COMPARATOR).toList();
|
||||
}
|
||||
|
||||
// Returns if a data type is available and enabled in the config
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -127,7 +126,7 @@ public interface UserDataHolder extends DataHolder {
|
||||
}
|
||||
|
||||
try {
|
||||
for (Map.Entry<Identifier, Data> entry : unpacked.getData().entrySet()) {
|
||||
for (Map.Entry<Identifier, Data> entry : unpacked.getSortedIterable()) {
|
||||
final Identifier identifier = entry.getKey();
|
||||
if (!identifier.isEnabled()) {
|
||||
continue;
|
||||
|
||||
@@ -65,9 +65,11 @@ public class RedisManager extends JedisPubSub {
|
||||
@Blocking
|
||||
public void initialize() throws IllegalStateException {
|
||||
final Settings.RedisSettings.RedisCredentials credentials = plugin.getSettings().getRedis().getCredentials();
|
||||
final String user = credentials.getUser();
|
||||
final String password = credentials.getPassword();
|
||||
final String host = credentials.getHost();
|
||||
final int port = credentials.getPort();
|
||||
final int database = credentials.getDatabase();
|
||||
final boolean useSSL = credentials.isUseSsl();
|
||||
|
||||
// Create the jedis pool
|
||||
@@ -79,9 +81,20 @@ public class RedisManager extends JedisPubSub {
|
||||
final Settings.RedisSettings.RedisSentinel sentinel = plugin.getSettings().getRedis().getSentinel();
|
||||
Set<String> redisSentinelNodes = new HashSet<>(sentinel.getNodes());
|
||||
if (redisSentinelNodes.isEmpty()) {
|
||||
this.jedisPool = password.isEmpty()
|
||||
? new JedisPool(config, host, port, 0, useSSL)
|
||||
: new JedisPool(config, host, port, 0, password, useSSL);
|
||||
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
|
||||
.ssl(useSSL)
|
||||
.database(database)
|
||||
.timeoutMillis(0);
|
||||
|
||||
if (!user.isEmpty()) {
|
||||
clientConfigBuilder.user(user);
|
||||
}
|
||||
|
||||
if (!password.isEmpty()) {
|
||||
clientConfigBuilder.password(password);
|
||||
}
|
||||
|
||||
this.jedisPool = new JedisPool(config, new HostAndPort(host, port), clientConfigBuilder.build());
|
||||
} else {
|
||||
final String sentinelPassword = sentinel.getPassword();
|
||||
this.jedisPool = new JedisSentinelPool(sentinel.getMaster(), redisSentinelNodes, password.isEmpty()
|
||||
@@ -177,7 +190,8 @@ public class RedisManager extends JedisPubSub {
|
||||
).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA)
|
||||
);
|
||||
case CHECK_IN_PETITION -> {
|
||||
if (!redisMessage.isTargetServer(plugin)) {
|
||||
if (!redisMessage.isTargetServer(plugin)
|
||||
|| !plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
||||
return;
|
||||
}
|
||||
final String payload = new String(redisMessage.getPayload(), StandardCharsets.UTF_8);
|
||||
|
||||
@@ -54,7 +54,9 @@ public class LockstepDataSyncer extends DataSyncer {
|
||||
// If they are checked out, ask the server to check them back in and return false
|
||||
final Optional<String> server = getRedis().getUserCheckedOut(user);
|
||||
if (server.isPresent() && !server.get().equals(plugin.getServerName())) {
|
||||
getRedis().petitionServerCheckin(server.get(), user);
|
||||
if (plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
||||
getRedis().petitionServerCheckin(server.get(), user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ public interface DumpProvider {
|
||||
Map.entry("Redis Version", StatusLine.REDIS_VERSION.getValue(getPlugin())),
|
||||
Map.entry("Redis Latency", StatusLine.REDIS_LATENCY.getValue(getPlugin())),
|
||||
Map.entry("Redis Sentinel", StatusLine.USING_REDIS_SENTINEL.getValue(getPlugin())),
|
||||
Map.entry("Redis Database", StatusLine.REDIS_DATABASE.getValue(getPlugin())),
|
||||
Map.entry("Redis User", StatusLine.USING_REDIS_USER.getValue(getPlugin())),
|
||||
Map.entry("Redis Password", StatusLine.USING_REDIS_PASSWORD.getValue(getPlugin())),
|
||||
Map.entry("Redis SSL", StatusLine.REDIS_USING_SSL.getValue(getPlugin())),
|
||||
Map.entry("Redis Local", StatusLine.IS_REDIS_LOCAL.getValue(getPlugin()))
|
||||
|
||||
@@ -60,6 +60,10 @@ public enum StatusLine {
|
||||
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
||||
!plugin.getSettings().getRedis().getSentinel().getMaster().isBlank()
|
||||
)),
|
||||
REDIS_DATABASE(plugin -> Component.text(plugin.getSettings().getRedis().getCredentials().getDatabase())),
|
||||
USING_REDIS_USER(plugin -> getBoolean(
|
||||
!plugin.getSettings().getRedis().getCredentials().getUser().isBlank()
|
||||
)),
|
||||
USING_REDIS_PASSWORD(plugin -> getBoolean(
|
||||
!plugin.getSettings().getRedis().getCredentials().getPassword().isBlank()
|
||||
)),
|
||||
|
||||
@@ -12,7 +12,7 @@ locales:
|
||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7バージョンタイムスタンプ:\n&8データの保存時期)'
|
||||
data_manager_pinned: '[※ ピン留めされたスナップショット](#d8ff2b show_text=&7ピン留め:\n&8このユーザーデータのスナップショットは自動的にローテーションされません。)'
|
||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存理由:\n&8データが保存された理由)'
|
||||
data_manager_server: '[☁ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||
data_manager_server: '[☁ %1%](#ff87b3-#f5538e show_text=&7サーバー:\n&8データが保存されたサーバー名)'
|
||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7スナップショットサイズ:\n&8スナップショットの推定ファイルサイズ(単位:KiB))\n'
|
||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7体力) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7空腹度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7経験値レベル) [🏹 %5%](dark_aqua show_text=&7ゲームモード)'
|
||||
data_manager_advancements_statistics: '[⭐ 進捗: %1%](color=#ffc43b-#f5c962 show_text=&7達成した進捗:\n&8%2%) [⌛ プレイ時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7ゲーム内のプレイ時間\n&8⚠ ゲーム内の統計に基づく)\n'
|
||||
@@ -22,8 +22,8 @@ locales:
|
||||
data_manager_advancements_preview_remaining: 'さらに %1% 件…'
|
||||
data_list_title: '[%1% のユーザーデータスナップショット:](#00fb9a) [(%4%件中](#00fb9a bold) [%2%-%3%件](#00fb9a)[)](#00fb9a)\n'
|
||||
data_list_item: '[%1%](gray show_text=&7%2% のユーザーデータスナップショット&8⚡ %4% run_command=/husksync:userdata view %2% %3%) [%5%](#d8ff2b show_text=&7ピン留め:\n&8ピン留めされたスナップショットは自動的にローテーションしません。 run_command=/husksync:userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7バージョンタイムスタンプ:&7\n&8データの保存時期\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7保存理由:\n&8データが保存された理由 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7スナップショットサイズ:&7\n&8スナップショットの推定ファイルサイズ (単位:KiB) run_command=/userdata view %2% %3%)'
|
||||
data_list_item_invalid: '[%1%](dark_gray show_text=&7User Data Snapshot for %2%\n&8⚡ %4% suggest_command=/userdata delete %2% %3%) [%5%](dark_gray show_text=&7Pinned:\n&8Pinned snapshots won''t be automatically rotated. suggest_command=/userdata delete %2% %3%) [%6% ⚑ %8% ⏏ %9%](gray strikethrough show_text=&#ff3300&Invalid Data Snapshot\n&#ff7e5e&Click to delete\n\n&7⚠ %10% suggest_command=/userdata delete %2% %3%)'
|
||||
data_saved: '[Successfully saved a snapshot of %1%''s current user data.](#00fb9a)'
|
||||
data_list_item_invalid: '[%1%](dark_gray show_text=&7%2% のユーザーデータスナップショット\n&8⚡ %4% suggest_command=/userdata delete %2% %3%) [%5%](dark_gray show_text=&7ピン留め:\n&8ピン留めされたスナップショットは自動的にローテーションしません。 suggest_command=/userdata delete %2% %3%) [%6% ⚑ %8% ⏏ %9%](gray strikethrough show_text=&#ff3300&無効なデータのスナップショット\n&#ff7e5e&クリックで消去\n\n&7⚠ %10% suggest_command=/userdata delete %2% %3%)'
|
||||
data_saved: '[%1% の現在のユーザーデータのスナップショットを正常に保存しました。](#00fb9a)'
|
||||
data_deleted: '[❌](#00fb9a) [%3%](#00fb9a show_text=&7Player UUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [の消去に成功しました。](#00fb9a)'
|
||||
data_restored: '[⏪](#00fb9a) [スナップショット](#00fb9a) [%3%](#00fb9a show_text=&7Version UUID:\n&8%4%) [から](#00fb9a) [%1%](#00fb9a show_text=&7Player UUID:\n&8%2%) [の現在のユーザーデータの復元に成功しました。](#00fb9a)'
|
||||
data_pinned: '[※](#00fb9a) [%3%](#00fb9a show_text=&7Player UUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [のピン留めに成功しました。](#00fb9a)'
|
||||
@@ -37,35 +37,35 @@ locales:
|
||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||
list_page_jumper_separator: ' '
|
||||
list_page_jumper_group_separator: '…'
|
||||
save_cause_disconnect: 'disconnect'
|
||||
save_cause_world_save: 'world save'
|
||||
save_cause_death: 'death'
|
||||
save_cause_server_shutdown: 'server shutdown'
|
||||
save_cause_save_command: 'save command'
|
||||
save_cause_dump_command: 'dump command'
|
||||
save_cause_inventory_command: 'inventory command'
|
||||
save_cause_enderchest_command: 'enderchest command'
|
||||
save_cause_backup_restore: 'backup restore'
|
||||
save_cause_disconnect: '切断'
|
||||
save_cause_world_save: 'ワールド保存'
|
||||
save_cause_death: '死亡'
|
||||
save_cause_server_shutdown: 'サーバーシャットダウン'
|
||||
save_cause_save_command: 'セーブコマンド'
|
||||
save_cause_dump_command: 'ダンプコマンド'
|
||||
save_cause_inventory_command: 'インベントリコマンド'
|
||||
save_cause_enderchest_command: 'エンダーチェストコマンド'
|
||||
save_cause_backup_restore: 'バックアップ復元'
|
||||
save_cause_api: 'API'
|
||||
save_cause_mpdb_migration: 'MPDB migration'
|
||||
save_cause_legacy_migration: 'legacy migration'
|
||||
save_cause_converted_from_v2: 'converted from v2'
|
||||
save_cause_mpdb_migration: 'MPDB移行'
|
||||
save_cause_legacy_migration: 'レガシー移行'
|
||||
save_cause_converted_from_v2: 'v2からの変換'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSyncの最新バージョンを実行しています(v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| HuskSyncの最新バージョンが更新されています: v%1% (実行中: v%2%).](#ff7e5e)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| HuskSyncの新バージョンが利用可能になりました: v%1% (実行中: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)\n[⚠ すべてのサーバーで設定ファイルが最新であることを確認してください!](#00fb9a)\n[設定の変更を有効にするには再起動が必要です。](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
system_dump_confirm: '[HuskSync](#00fb9a bold) [| Prepare a system dump? This will include:](#00fb9a)\n[• Your latest server logs and HuskSync config files](gray)\n[• Current plugin system status information](gray)\n[• Information about your Java & Minecraft server environment](gray)\n[• A list of other currently installed plugins](gray)\n[To confirm, use:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7Click to prepare dump run_command=/husksync dump confirm)'
|
||||
system_dump_started: '[HuskSync](#00fb9a bold) [| Preparing system status dump, please wait…](#00fb9a)'
|
||||
system_dump_ready: '[HuskSync](#00fb9a bold) [| System status dump prepared! Click to view:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&クリックでサジェスト suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
||||
error_invalid_data: '[Error:](#ff3300) [Failed to unpack user data as the snapshot is invalid or corrupt.](#ff7e5e) [(Details…)](gray show_text=&7⚠ %1%)'
|
||||
error_no_permission: '[Error:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
||||
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
||||
error_in_game_command_only: 'Error: そのコマンドはゲーム内でしか使えません。'
|
||||
error_no_data_to_display: '[Error:](#ff3300) [表示するユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||
error_invalid_version_uuid: '[Error:](#ff3300) [そのバージョンUUIDのユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| システムステータスレポート:](#00fb9a)'
|
||||
system_dump_confirm: '[HuskSync](#00fb9a bold) [| システムダンプを作成しますか? 含まれる内容:](#00fb9a)\n[• 最新のサーバーログと HuskSync の設定ファイル](gray)\n[• 現在のプラグインシステムの状態](gray)\n[• Java と Minecraft サーバー環境の情報](gray)\n[• 現在インストールされている他のプラグイン一覧](gray)\n[確認するには、次を実行してください:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7クリックでダンプを準備 run_command=/husksync dump confirm)'
|
||||
system_dump_started: '[HuskSync](#00fb9a bold) [| システムステータスダンプを準備中です。お待ちください…](#00fb9a)'
|
||||
system_dump_ready: '[HuskSync](#00fb9a bold) [| システムステータスダンプが準備できました!クリックで表示:](#00fb9a)'
|
||||
error_invalid_syntax: '[エラー:](#ff3300) [構文が正しくありません。使用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&クリックでサジェスト suggest_command=%1%)'
|
||||
error_invalid_player: '[エラー:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
||||
error_invalid_data: '[エラー:](#ff3300) [スナップショットが無効または破損しているため、ユーザーデータを展開できません。](#ff7e5e) [(詳細…)](gray show_text=&7⚠ %1%)'
|
||||
error_no_permission: '[エラー:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
||||
error_console_command_only: '[エラー:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
||||
error_in_game_command_only: 'エラー: そのコマンドはゲーム内でしか使えません。'
|
||||
error_no_data_to_display: '[エラー:](#ff3300) [表示するユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||
error_invalid_version_uuid: '[エラー:](#ff3300) [そのバージョンUUIDのユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||
husksync_command_description: 'HuskSyncプラグインを管理する'
|
||||
userdata_command_description: 'プレーヤーのユーザーデータを表示・管理・復元する'
|
||||
userdata_command_description: 'プレイヤーのユーザーデータを表示・管理・復元する'
|
||||
inventory_command_description: 'プレイヤーのインベントリを閲覧・編集する'
|
||||
enderchest_command_description: 'プレイヤーのエンダーチェストを閲覧・編集する'
|
||||
|
||||
@@ -23,7 +23,7 @@ locales:
|
||||
data_list_title: '[%1% 的玩家資料快照:](#00fb9a) [(%2%-%3% 共](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_item: '[%1%](gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7已標記:\n&8標記的快照將不會自動輪換。 run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7版本時間戳:\n&8資料儲存時間\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7儲存原因:\n&8觸發儲存的原因 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7快照大小:\n&8快照的預估檔案大小(KiB) run_command=/userdata view %2% %3%)'
|
||||
data_list_item_invalid: '[%1%](dark_gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% suggest_command=/userdata delete %2% %3%) [%5%](dark_gray show_text=&7已標記:\n&8標記的快照將不會自動輪換。 suggest_command=/userdata delete %2% %3%) [%6% ⚑ %8% ⏏ %9%](gray strikethrough show_text=&#ff3300&無效的資料快照\n&#ff7e5e&點擊刪除\n\n&7⚠ %10% suggest_command=/userdata delete %2% %3%)'
|
||||
data_saved: '[Successfully saved a snapshot of %1%''s current user data.](#00fb9a)'
|
||||
data_saved: '[✅ 成功儲存 %1% 的目前使用者資料快照。](#00fb9a)'
|
||||
data_deleted: '[❌ 成功刪除:](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||
data_restored: '[⏪ 成功將玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&8%2%)[的資料恢復為 快照:](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
||||
data_pinned: '[※ 成功標記](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||
@@ -41,8 +41,8 @@ locales:
|
||||
save_cause_world_save: '世界儲存'
|
||||
save_cause_death: '死亡'
|
||||
save_cause_server_shutdown: '伺服器關閉'
|
||||
save_cause_save_command: 'save command'
|
||||
save_cause_dump_command: 'dump command'
|
||||
save_cause_save_command: '儲存指令'
|
||||
save_cause_dump_command: '導出指令'
|
||||
save_cause_inventory_command: '背包指令'
|
||||
save_cause_enderchest_command: '終界箱指令'
|
||||
save_cause_backup_restore: '備份還原'
|
||||
@@ -54,9 +54,9 @@ locales:
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 配置和語言文件已重新加載。](#00fb9a)\n[⚠ 確保所有伺服器上的配置文件都是最新的!](#00fb9a)\n[重啟後配置變更才會生效。](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| 系統狀態報告:](#00fb9a)'
|
||||
system_dump_confirm: '[HuskSync](#00fb9a bold) [| Prepare a system dump? This will include:](#00fb9a)\n[• Your latest server logs and HuskSync config files](gray)\n[• Current plugin system status information](gray)\n[• Information about your Java & Minecraft server environment](gray)\n[• A list of other currently installed plugins](gray)\n[To confirm, use:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7Click to prepare dump run_command=/husksync dump confirm)'
|
||||
system_dump_started: '[HuskSync](#00fb9a bold) [| Preparing system status dump, please wait…](#00fb9a)'
|
||||
system_dump_ready: '[HuskSync](#00fb9a bold) [| System status dump prepared! Click to view:](#00fb9a)'
|
||||
system_dump_confirm: '[HuskSync](#00fb9a bold) [| 要產生系統狀態紀錄檔嗎?這將包含以下內容:](#00fb9a)\n[• 最近的伺服器日誌與 HuskSync 設定檔](gray)\n[• 插件目前的系統狀態資訊](gray)\n[• 有關您的 Java 與 Minecraft 伺服器環境的資訊](gray)\n[• 目前已安裝的其他插件清單](gray)\n[若要確認,請輸入:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7點擊以產生紀錄檔 run_command=/husksync dump confirm)'
|
||||
system_dump_started: '[HuskSync](#00fb9a bold) [| 正在產生系統狀態紀錄檔,請稍候…](#00fb9a)'
|
||||
system_dump_ready: '[HuskSync](#00fb9a bold) [| 系統狀態紀錄檔已完成!點擊以下連結以查看:](#00fb9a)'
|
||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&點擊建議 suggest_command=%1%)'
|
||||
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
||||
error_invalid_data: '[錯誤:](#ff3300) [無法解壓使用者資料,因為快照無效或已損壞。](#ff7e5e) [(詳細資訊…)](gray show_text=&7⚠ %1%)'
|
||||
|
||||
@@ -2,7 +2,9 @@ HuskSync supports the following versions of Minecraft. Since v3.7, you must down
|
||||
|
||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||
| 1.21.7 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||
| 1.21.6 | 3.8.5 | 21 | Paper | 🗃️ Archived (July 2025) |
|
||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||
@@ -32,5 +34,5 @@ This plugin does not support the following software-Minecraft version combinatio
|
||||
## Incompatible plugins / mods
|
||||
Please note the following plugins / mods can cause issues with HuskSync:
|
||||
|
||||
* Restart plugins / mods are not supported. These will cause [player data to not save correctly when your server restarts](troubleshooting#issues-with-player-data-going-out-of-sync-during-a-server-restart) due to the way these plugins utilise bash scripts. It's important to understand that restart plugins don't actually restart yur server, they just trigger some (often unstable) process-killing scripting logic to occur!
|
||||
* Restart plugins / mods are not supported. These will cause [player data to not save correctly when your server restarts](troubleshooting#issues-with-player-data-going-out-of-sync-during-a-server-restart) due to the way these plugins utilise bash scripts. It's important to understand that restart plugins don't actually restart your server, they just trigger some (often unstable) process-killing scripting logic to occur!
|
||||
* Combat logging plugins / mods are not supported. Some have built-in support for HuskSync and should work as expected, but for others you may wish to modify the [[Event Priorities]]
|
||||
@@ -65,10 +65,15 @@ database:
|
||||
user_data: husksync_user_data
|
||||
# Redis settings
|
||||
redis:
|
||||
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
||||
# Specify the credentials of your Redis server here.
|
||||
# Set "user" to '' if you don't have one or would like to use the default user.
|
||||
# Set "password" to '' if you don't have one.
|
||||
credentials:
|
||||
host: localhost
|
||||
port: 6379
|
||||
# Only change the database if you know what you are doing. The default is 0.
|
||||
database: 0
|
||||
user: ''
|
||||
password: ''
|
||||
use_ssl: false
|
||||
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
||||
|
||||
@@ -16,10 +16,15 @@ To configure Redis, navigate to your [`config.yml`](Config-File) file and modify
|
||||
```yaml
|
||||
# Redis settings
|
||||
redis:
|
||||
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
||||
# Specify the credentials of your Redis server here.
|
||||
# Set "user" to '' if you don't have one or would like to use the default user.
|
||||
# Set "password" to '' if you don't have one.
|
||||
credentials:
|
||||
host: localhost
|
||||
port: 6379
|
||||
# Only change the database if you know what you are doing. The default is 0.
|
||||
database: 0
|
||||
user: ''
|
||||
password: ''
|
||||
use_ssl: false
|
||||
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
||||
@@ -33,8 +38,9 @@ redis:
|
||||
</details>
|
||||
|
||||
### Credentials
|
||||
Enter the hostname, port, and default user password of your Redis server.
|
||||
Enter the hostname, port, user, and password of your Redis server.
|
||||
|
||||
If you don't have a Redis user, just use the default user password and leave the user field empty (`user: ''`).
|
||||
If your Redis default user doesn't have a password, leave the password field blank (`password: ''`') and the plugin will attempt to connect without a password.
|
||||
|
||||
### Default user password
|
||||
|
||||
@@ -26,6 +26,19 @@ If you are hosting your [[Redis]] server on the same node as your servers, you n
|
||||
### Database connection problems on Pterodactyl / Pelican
|
||||
If you have more than one [[Database]] server connected to your panel, you may need to set `useSSL=true` in the parameters.
|
||||
|
||||
### Unable to reset my server / wipe all player data
|
||||
The following steps are required to completely wipe all HuskSync data and prepare your server for a reset. HuskSync stores data in MySQL and caches it in Redis, so if you are experiencing players getting their items back when they shouldn't be it's because data wasn't cleared in one of the two.
|
||||
|
||||
- Turn OFF ALL Minecraft servers and proxy
|
||||
- Turn OFF your Redis server completely.
|
||||
- It MUST be completely offline.
|
||||
- Make sure data persistence is OFF and that it does not restore state following a reboot
|
||||
- Access your MySQL Database using MySQL Workbench or similar. DROP your `husksync` database, or at the very least DROP ALL husksync tables.
|
||||
- Double check you have done this
|
||||
- On ALL Minecraft servers, DELETE the `playerdata` and `advancement` data directories WITHIN EVERY WORLD folder
|
||||
- Deleting the world folders themselves works too if you are resetting these as well.
|
||||
- ONLY THEN can you finally re-start ALL servers
|
||||
|
||||
### 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:
|
||||
|
||||
@@ -38,4 +51,4 @@ These are **not compatible** with HuskSync in most cases due to the way in which
|
||||
* 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.
|
||||
It's not a great idea to use a plugin to handle restarts. Plugins are only able to operate when your server is turned on and must rely on scripts which don't safely shutdown servers when restarting.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.4+build.4:v2
|
||||
|
||||
fabric_loader_version=0.16.10
|
||||
fabric_api_version=0.115.0+1.21.4
|
||||
fabric_api_version=0.116.1+1.21.4
|
||||
fabric_permissions_api_version=0.3.3
|
||||
fabric_adventure_platform_version=6.2.0
|
||||
fabric_adventure_platform_version=6.3.0
|
||||
fabric_sgui_version=1.8.2+1.21.4
|
||||
7
fabric/1.21.5/gradle.properties
Normal file
7
fabric/1.21.5/gradle.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.5+build.1:v2
|
||||
|
||||
fabric_loader_version=0.16.14
|
||||
fabric_api_version=0.122.0+1.21.5
|
||||
fabric_permissions_api_version=0.3.3
|
||||
fabric_adventure_platform_version=6.4.0
|
||||
fabric_sgui_version=1.9.0+1.21.5
|
||||
7
fabric/1.21.7/gradle.properties
Normal file
7
fabric/1.21.7/gradle.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -14,16 +14,23 @@ dependencies {
|
||||
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:${fabric_sgui_version}")
|
||||
modImplementation include("net.william278.uniform:uniform-fabric:1.3.3+${project.name}")
|
||||
modImplementation include("net.william278.toilet:toilet-fabric:1.0.12+${project.name}")
|
||||
modImplementation include("net.william278.uniform:uniform-fabric:1.3.8+${project.name}")
|
||||
modImplementation include("net.william278.toilet:toilet-fabric:1.0.15+${project.name}")
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||
|
||||
// Manually include config deps due to the way including api deps works
|
||||
implementation include("de.exlll:configlib-core:4.6.1")
|
||||
implementation include("org.snakeyaml:snakeyaml-engine:2.9")
|
||||
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
||||
|
||||
// Include driver deps due to no runtime dep loading support
|
||||
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
||||
implementation include("org.postgresql:postgresql:$postgres_driver_version")
|
||||
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version")
|
||||
implementation include("redis.clients:jedis:$jedis_version")
|
||||
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
||||
implementation include("redis.clients:jedis:$jedis_version")
|
||||
implementation include("redis.clients.authentication:redis-authx-core:0.1.1-beta2") // Redis dep
|
||||
implementation include('org.apache.commons:commons-pool2:2.12.1') // Redis dep
|
||||
|
||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||
compileOnly 'org.jetbrains:annotations:26.0.2'
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.21.4
|
||||
1.21.7
|
||||
@@ -3,11 +3,15 @@ plugins {
|
||||
}
|
||||
|
||||
preprocess {
|
||||
def fabric12107 = createNode("1.21.7", 12107, "yarn")
|
||||
def fabric12105 = createNode("1.21.5", 12105, "yarn")
|
||||
def fabric12104 = createNode("1.21.4", 12104, "yarn")
|
||||
def fabric12101 = createNode("1.21.1", 12101, "yarn")
|
||||
def fabric12001 = createNode("1.20.1", 12001, "yarn")
|
||||
|
||||
strictExtraMappings.set(true)
|
||||
fabric12101.link(fabric12104, null)
|
||||
fabric12001.link(fabric12104, null)
|
||||
fabric12105.link(fabric12107, null)
|
||||
fabric12104.link(fabric12107, null)
|
||||
fabric12101.link(fabric12107, null)
|
||||
fabric12001.link(fabric12107, null)
|
||||
}
|
||||
@@ -99,12 +99,12 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
||||
private static final int VERSION1_20_5 = 3837;
|
||||
private static final int VERSION1_21_1 = 3955;
|
||||
private static final int VERSION1_21_3 = 4082;
|
||||
private static final int VERSION1_21_4 = 4189; // Current
|
||||
private 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 TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
|
||||
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
|
||||
);
|
||||
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
||||
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
||||
private final List<Migrator> availableMigrators = Lists.newArrayList();
|
||||
@@ -389,10 +389,14 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
||||
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
||||
case "1.21.4" -> VERSION1_21_4;
|
||||
case "1.21.5" -> VERSION1_21_5;
|
||||
//#if MC==12105
|
||||
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;
|
||||
//$$ default -> VERSION1_21_4;
|
||||
//#elseif MC==12101
|
||||
//$$ default -> VERSION1_21_1;
|
||||
//#elseif MC==12001
|
||||
|
||||
@@ -188,7 +188,11 @@ public abstract class FabricData implements Data {
|
||||
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
||||
player.getInventory().setStack(slot, items[slot] == null ? ItemStack.EMPTY : items[slot]);
|
||||
}
|
||||
player.getInventory().selectedSlot = heldItemSlot;
|
||||
//#if MC<12105
|
||||
//$$ player.getInventory().selectedSlot = heldItemSlot;
|
||||
//#else
|
||||
player.getInventory().setSelectedSlot(heldItemSlot);
|
||||
//#endif
|
||||
player.playerScreenHandler.sendContentUpdates();
|
||||
player.getInventory().updateItems();
|
||||
}
|
||||
@@ -552,13 +556,13 @@ public abstract class FabricData implements Data {
|
||||
// This is necessary to prevent weird re-mappings with Registry#getKey()
|
||||
//#if MC>0
|
||||
//$$ final Registry<?> registry = stat.getValue().getRegistry();
|
||||
//$$ final String registryId = registry.getKey().getValue().toString();
|
||||
//$$ final String registryId = registry.getKey().getValue().value();
|
||||
//$$ if (registryId.equals("custom_stat")) {
|
||||
//$$ return;
|
||||
//$$ }
|
||||
//#else
|
||||
final Registry<?> registry = stat.getValue().getRegistry();
|
||||
final String registryId = registry.getKey().getValue().toString();
|
||||
final String registryId = registry.getKey().getValue().value();
|
||||
if (registryId.equals("custom_stat")) {
|
||||
return;
|
||||
}
|
||||
@@ -888,7 +892,11 @@ public abstract class FabricData implements Data {
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||
user.getPlayer().changeGameMode(net.minecraft.world.GameMode.byName(gameMode));
|
||||
//#if MC<12105
|
||||
//$$ user.getPlayer().changeGameMode(net.minecraft.world.GameMode.byName(gameMode));
|
||||
//#else
|
||||
user.getPlayer().changeGameMode(net.minecraft.world.GameMode.byId(gameMode));
|
||||
//#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -76,17 +76,29 @@ public abstract class FabricSerializer {
|
||||
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||
final NbtCompound root;
|
||||
try {
|
||||
root = StringNbtReader.parse(serialized);
|
||||
//#if MC<12105
|
||||
//$$ root = StringNbtReader.parse(serialized);
|
||||
//#else
|
||||
root = StringNbtReader.readCompound(serialized);
|
||||
//#endif
|
||||
} catch (Throwable e) {
|
||||
throw new DeserializationException("Failed to read item NBT from string (%s)".formatted(serialized), e);
|
||||
}
|
||||
|
||||
// Deserialize the inventory data
|
||||
final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompound(ITEMS_TAG) : null;
|
||||
//#if MC<12105
|
||||
//$$ final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompound(ITEMS_TAG) : null;
|
||||
//$$ return FabricData.Items.Inventory.from(
|
||||
//$$ items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
||||
//$$ root.contains(HELD_ITEM_SLOT_TAG) ? root.getInt(HELD_ITEM_SLOT_TAG) : 0
|
||||
//$$ );
|
||||
//#else
|
||||
final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompoundOrEmpty(ITEMS_TAG) : null;
|
||||
return FabricData.Items.Inventory.from(
|
||||
items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
||||
root.contains(HELD_ITEM_SLOT_TAG) ? root.getInt(HELD_ITEM_SLOT_TAG) : 0
|
||||
items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
||||
root.getInt(HELD_ITEM_SLOT_TAG, 0)
|
||||
);
|
||||
//#endif
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,7 +133,11 @@ public abstract class FabricSerializer {
|
||||
throws DeserializationException {
|
||||
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||
try {
|
||||
final NbtCompound items = StringNbtReader.parse(serialized);
|
||||
//#if MC<12105
|
||||
//$$ final NbtCompound items = StringNbtReader.parse(serialized);
|
||||
//#else
|
||||
final NbtCompound items = StringNbtReader.readCompound(serialized);
|
||||
//#endif
|
||||
return FabricData.Items.EnderChest.adapt(getItems(items, dataMcVersion, plugin));
|
||||
} catch (Throwable e) {
|
||||
throw new DeserializationException("Failed to read item NBT from string (%s)".formatted(serialized), e);
|
||||
@@ -153,14 +169,26 @@ public abstract class FabricSerializer {
|
||||
return upgradeItemStacks(tag, mcVersion, plugin);
|
||||
}
|
||||
|
||||
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
||||
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||
//#if MC<12105
|
||||
//$$ final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
||||
//$$ final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||
//$$ itemList.forEach(element -> {
|
||||
//$$ final NbtCompound compound = (NbtCompound) element;
|
||||
//$$ contents[compound.getInt("Slot")] = decodeNbt(element, registryManager);
|
||||
//$$ });
|
||||
//#else
|
||||
final ItemStack[] contents = new ItemStack[tag.getInt("size", 0)];
|
||||
final NbtList itemList = tag.getListOrEmpty("items");
|
||||
itemList.forEach(element -> {
|
||||
final NbtCompound compound = (NbtCompound) element;
|
||||
contents[compound.getInt("Slot")] = decodeNbt(element, registryManager);
|
||||
int i = compound.getInt("Slot", -1);
|
||||
if (i >= 0) {
|
||||
contents[i] = decodeNbt(element, registryManager);
|
||||
}
|
||||
});
|
||||
plugin.debug(Arrays.toString(contents));
|
||||
//#endif
|
||||
|
||||
return contents;
|
||||
} catch (Throwable e) {
|
||||
throw new Serializer.DeserializationException("Failed to read item NBT string (%s)".formatted(tag), e);
|
||||
@@ -199,19 +227,37 @@ public abstract class FabricSerializer {
|
||||
@NotNull
|
||||
private ItemStack @NotNull [] upgradeItemStacks(@NotNull NbtCompound items, @NotNull Version mcVersion,
|
||||
@NotNull FabricHuskSync plugin) {
|
||||
final int size = items.getInt("size");
|
||||
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
|
||||
//#if MC<12105
|
||||
//$$ 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) {
|
||||
//$$ continue;
|
||||
//$$ }
|
||||
//$$ final NbtCompound compound = list.getCompound(i);
|
||||
//$$ final int slot = compound.getInt("Slot");
|
||||
//$$ itemStacks[slot] = decodeNbt(upgradeItemData(list.getCompound(i), mcVersion, plugin), registryManager);
|
||||
//$$ }
|
||||
//#else
|
||||
final int size = items.getInt("size", 0);
|
||||
final NbtList list = items.getListOrEmpty("items");
|
||||
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) {
|
||||
final NbtCompound compound = list.getCompoundOrEmpty(i);
|
||||
if (compound.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final NbtCompound compound = list.getCompound(i);
|
||||
final int slot = compound.getInt("Slot");
|
||||
itemStacks[slot] = decodeNbt(upgradeItemData(list.getCompound(i), mcVersion, plugin), registryManager);
|
||||
final int slot = compound.getInt("Slot", -1);
|
||||
if (slot >= 0) {
|
||||
itemStacks[slot] = decodeNbt(upgradeItemData(compound, mcVersion, plugin), registryManager);
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
return itemStacks;
|
||||
}
|
||||
|
||||
@@ -226,12 +272,14 @@ public abstract class FabricSerializer {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager registryManager) {
|
||||
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager reg) {
|
||||
try {
|
||||
//#if MC>=12104
|
||||
return (NbtCompound) item.toNbt(registryManager);
|
||||
//#if MC>=12107
|
||||
return (NbtCompound) ItemStack.CODEC.encodeStart(reg.getOps(NbtOps.INSTANCE), item).getOrThrow();
|
||||
//#elseif MC>=12104
|
||||
//$$ return (NbtCompound) item.toNbt(reg);
|
||||
//#elseif MC==12101
|
||||
//$$ return (NbtCompound) item.encode(registryManager);
|
||||
//$$ return (NbtCompound) item.encode(reg);
|
||||
//#elseif MC==12001
|
||||
//$$ final NbtCompound compound = new NbtCompound();
|
||||
//$$ item.writeNbt(compound);
|
||||
@@ -243,14 +291,16 @@ public abstract class FabricSerializer {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager registryManager) {
|
||||
//#if MC==12001
|
||||
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager reg) {
|
||||
//#if MC>=12107
|
||||
final @Nullable ItemStack stack = ItemStack.CODEC.decode(reg.getOps(NbtOps.INSTANCE), item).getOrThrow().getFirst();
|
||||
//#elseif MC>12001
|
||||
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt(reg, item).orElse(null);
|
||||
//#elseif MC==12001
|
||||
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt((NbtCompound) item);
|
||||
//#else
|
||||
final @Nullable ItemStack stack = ItemStack.fromNbt(registryManager, item).orElse(null);
|
||||
//#endif
|
||||
if (stack == null) {
|
||||
throw new IllegalStateException("Failed to decode item NBT (got null 'fromNbt'): (%s)".formatted(item));
|
||||
throw new IllegalStateException("Failed to decode item NBT (decode got null): (%s)".formatted(item));
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
@@ -79,27 +79,41 @@ public interface FabricUserDataHolder extends UserDataHolder {
|
||||
final PlayerInventory inventory = getPlayer().getInventory();
|
||||
return Optional.of(FabricData.Items.Inventory.from(
|
||||
getCombinedInventory(inventory),
|
||||
inventory.selectedSlot
|
||||
//#if MC<12105
|
||||
//$$ inventory.selectedSlot
|
||||
//#else
|
||||
inventory.getSelectedSlot()
|
||||
//#endif
|
||||
));
|
||||
}
|
||||
|
||||
// Gets the player's combined inventory; their inventory, plus offhand and armor.
|
||||
@Nullable
|
||||
private ItemStack @NotNull [] getCombinedInventory(@NotNull PlayerInventory inv) {
|
||||
final ItemStack[] combined = new ItemStack[inv.main.size() + inv.armor.size() + inv.offHand.size()];
|
||||
System.arraycopy(
|
||||
inv.main.toArray(new ItemStack[0]), 0, combined,
|
||||
0, inv.main.size()
|
||||
);
|
||||
System.arraycopy(
|
||||
inv.armor.toArray(new ItemStack[0]), 0, combined,
|
||||
inv.main.size(), inv.armor.size()
|
||||
);
|
||||
System.arraycopy(
|
||||
inv.offHand.toArray(new ItemStack[0]), 0, combined,
|
||||
inv.main.size() + inv.armor.size(), inv.offHand.size()
|
||||
);
|
||||
//#if MC<12105
|
||||
//$$ final ItemStack[] combined = new ItemStack[inv.main.size() + inv.armor.size() + inv.offHand.size()];
|
||||
//$$ System.arraycopy(
|
||||
//$$ inv.main.toArray(new ItemStack[0]), 0, combined,
|
||||
//$$ 0, inv.main.size()
|
||||
//$$ );
|
||||
//$$ System.arraycopy(
|
||||
//$$ inv.armor.toArray(new ItemStack[0]), 0, combined,
|
||||
//$$ inv.main.size(), inv.armor.size()
|
||||
//$$ );
|
||||
//$$ System.arraycopy(
|
||||
//$$ inv.offHand.toArray(new ItemStack[0]), 0, combined,
|
||||
//$$ inv.main.size() + inv.armor.size(), inv.offHand.size()
|
||||
//$$ );
|
||||
//$$ return combined;
|
||||
//#else
|
||||
final ItemStack[] combined = new ItemStack[inv.size()];
|
||||
int slot = 0;
|
||||
for (ItemStack itemStack : inv) {
|
||||
combined[slot] = itemStack;
|
||||
slot++;
|
||||
}
|
||||
return combined;
|
||||
//#endif
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -71,7 +71,11 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
||||
|
||||
@Inject(method = "onClickSlot", at = @At("HEAD"), cancellable = true)
|
||||
public void onClickSlot(ClickSlotC2SPacket packet, CallbackInfo ci) {
|
||||
int slot = packet.getSlot();
|
||||
//#if MC<12105
|
||||
//$$ int slot = packet.getSlot();
|
||||
//#else
|
||||
int slot = packet.slot();
|
||||
//#endif
|
||||
if (slot < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ org.gradle.daemon=true
|
||||
javaVersion=21
|
||||
|
||||
# Plugin metadata
|
||||
plugin_version=3.8.1
|
||||
plugin_version=3.8.6
|
||||
plugin_archive=husksync
|
||||
plugin_description=A modern, cross-server player data synchronization system
|
||||
|
||||
@@ -17,4 +17,4 @@ mongodb_driver_version=5.5.0
|
||||
snappy_version=1.1.10.7
|
||||
|
||||
# Fabric settings
|
||||
fabric_loom_version=1.9-SNAPSHOT
|
||||
loom.ignoreDependencyLoomVersionValidation=true
|
||||
@@ -66,6 +66,9 @@ redis:
|
||||
credentials:
|
||||
host: localhost
|
||||
port: 6379
|
||||
# Only change the database if you know what you are doing. The default is 0.
|
||||
database: 0
|
||||
user: ''
|
||||
password: ''
|
||||
use_ssl: false
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ certifi==2024.7.4
|
||||
charset-normalizer==3.2.0
|
||||
colorama==0.4.6
|
||||
idna==3.7
|
||||
requests==2.32.0
|
||||
requests==2.32.4
|
||||
tqdm==4.66.3
|
||||
urllib3==2.2.2
|
||||
urllib3==2.5.0
|
||||
|
||||
Reference in New Issue
Block a user