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

Compare commits

...

22 Commits
3.8 ... 3.8.2

Author SHA1 Message Date
William278
935aafa74a fix(fabric): fix issues with Fabric 1.21.5 2025-05-26 21:18:45 +01:00
William278
c51ba85f38 fix: updates for Fabric 1.21.5 2025-05-26 20:47:26 +01:00
William278
6a67d1bbe0 build: support Fabric 1.21.5 2025-05-26 20:23:02 +01:00
William278
20bc76a768 fix: /enderchest command not working 2025-05-26 20:13:23 +01:00
William278
6928f97dff build: bump Plan to 5.6-2965 2025-05-26 19:47:05 +01:00
William278
06742fb848 build(fabric): include config deps 2025-05-26 19:45:35 +01:00
dependabot[bot]
759983b000 deps: bump de.exlll:configlib-yaml from 4.5.0 to 4.6.1 (#508)
Bumps [de.exlll:configlib-yaml](https://github.com/Exlll/ConfigLib) from 4.5.0 to 4.6.1.
- [Release notes](https://github.com/Exlll/ConfigLib/releases)
- [Commits](https://github.com/Exlll/ConfigLib/compare/v4.5.0...v4.6.1)

---
updated-dependencies:
- dependency-name: de.exlll:configlib-yaml
  dependency-version: 4.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 19:44:02 +01:00
William278
5556e3b6ce build: bump to 3.8.2 2025-05-26 19:42:55 +01:00
William278
bcffcb1f64 deps: bump net.kyori:adventure-platform-api from 4.3.4 to 4.4.0 2025-05-26 19:42:45 +01:00
dependabot[bot]
fa77e6e418 deps: bump net.kyori:adventure-text-serializer-plain (#503)
Bumps [net.kyori:adventure-text-serializer-plain](https://github.com/KyoriPowered/adventure) from 4.20.0 to 4.21.0.
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.20.0...v4.21.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-serializer-plain
  dependency-version: 4.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 19:42:21 +01:00
dependabot[bot]
c8aa29c82f deps: bump net.william278.uniform:uniform-paper from 1.3.3 to 1.3.4 (#509)
Bumps [net.william278.uniform:uniform-paper](https://github.com/WiIIiam278/Uniform) from 1.3.3 to 1.3.4.
- [Release notes](https://github.com/WiIIiam278/Uniform/releases)
- [Commits](https://github.com/WiIIiam278/Uniform/compare/1.3.3...1.3.4)

---
updated-dependencies:
- dependency-name: net.william278.uniform:uniform-paper
  dependency-version: 1.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 19:42:14 +01:00
William278
51cf982359 fix: remove debug message in /inventory 2025-05-12 17:39:56 +01:00
William278
f6d860335f fix: ensure map view for ID is set when applying, close #498 2025-05-10 23:45:05 +01:00
William278
5cea4665a1 fix: don't update ItemMeta of empty containers, close #499
This causes a `minecraft:block_entity_data` component to be added to the item, which causes stuff not to stack.
2025-05-10 23:27:52 +01:00
William278
34b183a35e build: bump runtime deps 2025-05-10 22:55:35 +01:00
William278
61298c24bb refactor: improve data identifier map structure, fix #492 2025-05-10 13:57:03 +01:00
William278
af9d32895e build: bump dependencies 2025-05-10 13:40:21 +01:00
ilightwas
52fa67432c fix: enable the userdata view command with no snapshot uuid args (#491)
Documentation states it should show the latest snapshot
2025-05-10 13:39:04 +01:00
William278
404f18d81f fix: add mojang-mapped, preload NBT-API
improves compatibility with 1.21.5/paper
2025-04-21 00:17:08 +01:00
William278
9ee8ea1c84 feat: auto-upgrade legacy map data, close #490 2025-04-17 20:54:31 +01:00
William278
64f845e293 build: bump nbt-api to 2.15.0 2025-04-17 20:31:04 +01:00
William278
30d1acc67e test: bump test suite to 1.21.5 2025-04-16 18:37:26 +01:00
27 changed files with 301 additions and 113 deletions

View File

@@ -58,6 +58,7 @@ jobs:
fabric-1.20.1
fabric-1.21.1
fabric-1.21.4
fabric-1.21.5
distro-groups: |
paper
paper
@@ -66,6 +67,7 @@ jobs:
fabric
fabric
fabric
fabric
distro-descriptions: |
Paper 1.20.1
Paper 1.21.1
@@ -74,6 +76,7 @@ jobs:
Fabric 1.20.1
Fabric 1.21.1
Fabric 1.21.4
Fabric 1.21.5
files: |
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.20.1.jar
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.1.jar
@@ -81,4 +84,5 @@ jobs:
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.5.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

View File

@@ -47,6 +47,7 @@ jobs:
fabric-1.20.1
fabric-1.21.1
fabric-1.21.4
fabric-1.21.5
distro-groups: |
paper
paper
@@ -55,6 +56,7 @@ jobs:
fabric
fabric
fabric
fabric
distro-descriptions: |
Paper 1.20.1
Paper 1.21.1
@@ -63,6 +65,7 @@ jobs:
Fabric 1.20.1
Fabric 1.21.1
Fabric 1.21.4
Fabric 1.21.5
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
@@ -70,4 +73,5 @@ jobs:
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.5.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

View File

@@ -3,7 +3,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'com.gradleup.shadow' version '8.3.6'
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 'maven-publish'
@@ -89,7 +89,7 @@ allprojects {
}
dependencies {
testImplementation(platform("org.junit:junit-bom:5.12.1"))
testImplementation(platform("org.junit:junit-bom:5.12.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'
}
}

View File

@@ -9,16 +9,16 @@ 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-paper:1.3.4'
implementation 'net.william278.toilet:toilet-bukkit:1.0.13'
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 'dev.triumphteam:triumph-gui:3.1.11'
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.14.2-SNAPSHOT'
implementation 'de.tr7zw:item-nbt-api:2.15.0'
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
compileOnly 'com.github.retrooper:packetevents-spigot:2.7.0'
@@ -27,7 +27,7 @@ dependencies {
compileOnly 'commons-io:commons-io:2.19.0'
compileOnly 'org.json:json:20250107'
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")
}
}
}

View File

@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import de.tr7zw.changeme.nbtapi.NBT;
import de.tr7zw.changeme.nbtapi.utils.DataFixerUtil;
import lombok.AccessLevel;
import lombok.Getter;
@@ -92,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();
@@ -149,6 +148,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
// Check compatibility
checkCompatibility();
// Preload NBT-API
if (!NBT.preloadApi()) {
log(Level.SEVERE, "Failed to load NBT API. HuskSync will not be initialized!");
return;
}
// Register commands
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));

View File

@@ -22,6 +22,7 @@ package net.william278.husksync.maps;
import com.google.common.collect.Lists;
import de.tr7zw.changeme.nbtapi.NBT;
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
import de.tr7zw.changeme.nbtapi.iface.ReadableItemNBT;
import de.tr7zw.changeme.nbtapi.iface.ReadableNBT;
import net.william278.husksync.BukkitHuskSync;
import net.william278.husksync.redis.RedisManager;
@@ -43,7 +44,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.*;
import java.util.function.Function;
@@ -53,6 +56,8 @@ public interface BukkitMapHandler {
// The map used to store HuskSync data in ItemStack NBT
String MAP_DATA_KEY = "husksync:persisted_locked_map";
// The legacy map key used to store pixel data (3.7.3 and below)
String MAP_LEGACY_PIXEL_DATA_KEY = "husksync:canvas_data";
// Name of server the map originates from
String MAP_ORIGIN_KEY = "origin";
// Original map id
@@ -97,11 +102,12 @@ public interface BukkitMapHandler {
}
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
items[i] = function.apply(item);
} else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof Container box) {
} else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof Container box
&& !box.getInventory().isEmpty()) {
forEachMap(box.getInventory().getContents(), function);
b.setBlockState(box);
item.setItemMeta(b);
} else if (item.getItemMeta() instanceof BundleMeta bundle) {
} else if (item.getItemMeta() instanceof BundleMeta bundle && bundle.hasItems()) {
bundle.setItems(List.of(forEachMap(bundle.getItems().toArray(ItemStack[]::new), function)));
item.setItemMeta(bundle);
}
@@ -260,21 +266,29 @@ public interface BukkitMapHandler {
int newId = currentServerName.equals(originServerName)
? originalMapId : getBoundMapId(originServerName, originalMapId, currentServerName);
if (newId != -1) {
meta.setMapId(newId);
map.setItemMeta(meta);
getPlugin().debug(String.format("Map ID set to %s", newId));
return;
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));
}
// Read the pixel data and generate a map view otherwise
// Read the pixel data from the ItemStack and generate a map view otherwise
getPlugin().debug("Deserializing map data from NBT and generating view...");
final @Nullable Map.Entry<MapData, Boolean> readMapData = readMapData(originServerName, originalMapId);
@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;
}
// Add a renderer to the map with the data and save to file
final MapData canvasData = Objects.requireNonNull(readMapData, "Pixel data null!").getKey();
final MapView view = generateRenderedMap(canvasData);
meta.setMapView(view);
@@ -284,6 +298,7 @@ public interface BukkitMapHandler {
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));
});
@@ -295,16 +310,21 @@ public interface BukkitMapHandler {
return;
}
@Nullable final Map.Entry<MapData, Boolean> data = readMapData(getPlugin().getServerName(), view.getId());
// Read map data, or
@Nullable 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();
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()) {
// from this server, doesn't need tweaking
return;
}
@@ -420,6 +440,36 @@ public interface BukkitMapHandler {
);
}
// Legacy - read maps from item stacks
@Nullable
@Blocking
private Map.Entry<MapData, Boolean> readLegacyMapItemData(@NotNull ReadableItemNBT nbt) {
final int dataVer = getPlugin().getDataVersion(getPlugin().getMinecraftVersion());
try {
return new AbstractMap.SimpleImmutableEntry<>(MapData.fromByteArray(dataVer,
Objects.requireNonNull(nbt.getByteArray(MAP_LEGACY_PIXEL_DATA_KEY))), false);
} catch (IOException e) {
getPlugin().log(Level.WARNING, "Failed to read legacy map data", e);
return null;
}
}
// Legacy - read maps from files
@Nullable
private Map.Entry<MapData, Boolean> readLegacyMapFileData(int mapId) {
final Path path = getPlugin().getDataFolder().toPath().resolve("maps").resolve(mapId + ".dat");
final File file = path.toFile();
if (!file.exists()) {
return null;
}
try {
return new AbstractMap.SimpleImmutableEntry<>(MapData.fromNbt(file), false);
} catch (IOException e) {
getPlugin().log(Level.WARNING, "Failed to read legacy map file", e);
return null;
}
}
/**
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
*/

View File

@@ -10,24 +10,24 @@ dependencies {
api 'org.json:json:20250107'
api 'com.google.code.gson:gson:2.13.0'
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.13'
compileOnly 'net.william278.uniform:uniform-common:1.3.3'
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 'com.google.guava:guava:33.4.6-jre'
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
compileOnly 'net.kyori:adventure-platform-api:4.4.0'
compileOnly "net.kyori:adventure-text-serializer-plain:4.21.0"
compileOnly 'com.google.guava:guava:33.4.8-jre'
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"
@@ -37,9 +37,9 @@ dependencies {
testImplementation "redis.clients:jedis:$jedis_version"
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
testImplementation 'com.google.guava:guava:33.4.6-jre'
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
testImplementation 'com.google.guava:guava:33.4.8-jre'
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'

View File

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

View File

@@ -215,11 +215,17 @@ public class UserDataCommand extends PluginCommand {
@NotNull
private CommandProvider view() {
return (sub) -> sub.addSyntax((ctx) -> {
final User user = ctx.getArgument("username", User.class);
final UUID version = ctx.getArgument("version", UUID.class);
viewSnapshot(user(sub, ctx), user, version);
}, user("username"), versionUuid());
return (sub) -> {
sub.addSyntax((ctx) -> {
final User user = ctx.getArgument("username", User.class);
final UUID version = ctx.getArgument("version", UUID.class);
viewSnapshot(user(sub, ctx), user, version);
}, user("username"), versionUuid());
sub.addSyntax((ctx) -> {
final User user = ctx.getArgument("username", User.class);
viewLatestSnapshot(user(sub, ctx), user);
}, user("username"));
};
}
@NotNull

View File

@@ -31,7 +31,10 @@ public interface DataHolder {
Map<Identifier, Data> getData();
default Optional<? extends Data> getData(@NotNull Identifier id) {
return getData().entrySet().stream().filter(e -> e.getKey().equals(id)).map(Map.Entry::getValue).findFirst();
if (getData().containsKey(id)) {
return Optional.of(getData().get(id));
}
return Optional.empty();
}
default void setData(@NotNull Identifier identifier, @NotNull Data data) {

View File

@@ -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
));
}
@@ -406,7 +407,9 @@ public class DataSnapshot {
return deserialized.entrySet().stream()
.collect(Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> plugin.serializeData(entry.getKey(), entry.getValue())
entry -> plugin.serializeData(entry.getKey(), entry.getValue()),
(a, b) -> a,
HashMap::new
));
}
@@ -421,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
*
@@ -453,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();

View File

@@ -38,7 +38,7 @@ import java.util.stream.Stream;
* Identifiers of different types of {@link Data}s
*/
@Getter
public class Identifier {
public class Identifier implements Comparable<Identifier> {
// Namespace for built-in identifiers
private static final @KeyPattern String DEFAULT_NAMESPACE = "husksync";
@@ -276,6 +276,14 @@ public class Identifier {
return Map.entry(getKeyValue(), enabledByDefault);
}
// Comparable; always sort this Identifier after any dependencies
@Override
public int compareTo(@NotNull Identifier o) {
if (this.dependsOn(o)) return 1;
if (o.dependsOn(this)) return -1;
return this.key.compareTo(o.key);
}
/**
* Compares two identifiers based on their dependencies.
* <p>

View File

@@ -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

View File

@@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
* A holder of data in the form of {@link Data}s, which can be synced
@@ -46,7 +47,11 @@ public interface UserDataHolder extends DataHolder {
.filter(Identifier::isEnabled)
.map(id -> Map.entry(id, getData(id)))
.filter(data -> data.getValue().isPresent())
.collect(HashMap::new, (map, data) -> map.put(data.getKey(), data.getValue().get()), HashMap::putAll);
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get(),
(a, b) -> a, HashMap::new
));
}
/**
@@ -121,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;

View File

@@ -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

View 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-SNAPSHOT
fabric_sgui_version=1.9.0+1.21.5

View File

@@ -15,9 +15,15 @@ dependencies {
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.toilet:toilet-fabric:1.0.13+${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.7")
implementation include('org.apache.commons:commons-pool2:2.12.1')
// Include driver deps due to no runtime dep loading support
implementation include('org.apache.commons:commons-pool2:2.12.1')
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
implementation include("org.postgresql:postgresql:$postgres_driver_version")

View File

@@ -1 +1 @@
1.21.4
1.21.5

View File

@@ -3,11 +3,13 @@ plugins {
}
preprocess {
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)
fabric12104.link(fabric12105, null)
fabric12101.link(fabric12105, null)
fabric12001.link(fabric12105, null)
}

View File

@@ -102,9 +102,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
private static final int VERSION1_21_4 = 4189; // Current
private static final int VERSION1_21_5 = 4323;
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();

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -4,17 +4,17 @@ org.gradle.daemon=true
javaVersion=21
# Plugin metadata
plugin_version=3.8
plugin_version=3.8.2
plugin_archive=husksync
plugin_description=A modern, cross-server player data synchronization system
# General settings
jedis_version=5.2.0
mysql_driver_version=9.2.0
mariadb_driver_version=3.5.1
jedis_version=6.0.0
mysql_driver_version=9.3.0
mariadb_driver_version=3.5.3
postgres_driver_version=42.7.5
mongodb_driver_version=5.3.1
mongodb_driver_version=5.5.0
snappy_version=1.1.10.7
# Fabric settings
fabric_loom_version=1.9-SNAPSHOT
loom.ignoreDependencyLoomVersionValidation=true

View File

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