diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java index 3a8a7de2..2235d252 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java @@ -16,6 +16,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; +import java.time.Instant; import java.util.*; public class DataSerializer { @@ -207,17 +208,23 @@ public class DataSerializer { playerLocation.getYaw(), playerLocation.getPitch(), player.getWorld().getName(), player.getWorld().getEnvironment())); } - public record PlayerLocation(double x, double y, double z, float yaw, float pitch, - String worldName, World.Environment environment) implements Serializable { - } - @SuppressWarnings("unchecked") // Ignore the unchecked cast here - public static ArrayList deserializeAdvancementData(String serializedAdvancementData) throws IOException { + public static List deserializeAdvancementData(String serializedAdvancementData) throws IOException { if (serializedAdvancementData.isEmpty()) { return new ArrayList<>(); } try { - return (ArrayList) RedisMessage.deserialize(serializedAdvancementData); + List deserialize = (List) RedisMessage.deserialize(serializedAdvancementData); + + if (!deserialize.isEmpty() && deserialize.get(0) instanceof AdvancementRecord) { + deserialize = ((List) deserialize).stream() + .map(o -> new AdvancementRecordDate( + o.advancementKey, + o.awardedAdvancementCriteria + )).toList(); + } + + return (List) deserialize; } catch (ClassNotFoundException e) { throw new IOException("Unable to decode class type.", e); } @@ -225,22 +232,21 @@ public class DataSerializer { public static String getSerializedAdvancements(Player player) throws IOException { Iterator serverAdvancements = Bukkit.getServer().advancementIterator(); - ArrayList advancementData = new ArrayList<>(); + ArrayList advancementData = new ArrayList<>(); while (serverAdvancements.hasNext()) { final AdvancementProgress progress = player.getAdvancementProgress(serverAdvancements.next()); final NamespacedKey advancementKey = progress.getAdvancement().getKey(); - final ArrayList awardedCriteria = new ArrayList<>(progress.getAwardedCriteria()); - advancementData.add(new DataSerializer.AdvancementRecord(advancementKey.getNamespace() + ":" + advancementKey.getKey(), awardedCriteria)); + + final Map awardedCriteria = new HashMap<>(); + progress.getAwardedCriteria().forEach(s -> awardedCriteria.put(s, progress.getDateAwarded(s))); + + advancementData.add(new DataSerializer.AdvancementRecordDate(advancementKey.getNamespace() + ":" + advancementKey.getKey(), awardedCriteria)); } return RedisMessage.serialize(advancementData); } - public record AdvancementRecord(String advancementKey, - ArrayList awardedAdvancementCriteria) implements Serializable { - } - public static DataSerializer.StatisticData deserializeStatisticData(String serializedStatisticData) throws IOException { if (serializedStatisticData.isEmpty()) { return new DataSerializer.StatisticData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()); @@ -288,6 +294,22 @@ public class DataSerializer { return RedisMessage.serialize(statisticData); } + public record PlayerLocation(double x, double y, double z, float yaw, float pitch, + String worldName, World.Environment environment) implements Serializable { + } + + public record AdvancementRecord(String advancementKey, + ArrayList awardedAdvancementCriteria) implements Serializable { + } + + public record AdvancementRecordDate(String key, Map criteriaMap) implements Serializable { + AdvancementRecordDate(String key, List criteriaList) { + this(key, new HashMap<>() {{ + criteriaList.forEach(s -> put(s, Date.from(Instant.EPOCH))); + }}); + } + } + public record StatisticData(HashMap untypedStatisticValues, HashMap> blockStatisticValues, HashMap> itemStatisticValues, diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java index d16273c3..c9e18838 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java @@ -20,8 +20,6 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import java.io.IOException; -import java.time.Instant; -import java.time.Period; import java.util.*; import java.util.logging.Level; @@ -160,7 +158,7 @@ public class PlayerSetter { // Set the player's data from the PlayerData try { if (Settings.syncAdvancements) { - ArrayList advancementRecords + List advancementRecords = DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()); if (Settings.useNativeImplementation) { @@ -171,7 +169,7 @@ public class PlayerSetter { plugin.getLogger().log(Level.WARNING, "Your server does not support a native implementation of achievements synchronization"); plugin.getLogger().log(Level.WARNING, - "Your server version {0}. Please disable using native implementation!", Bukkit.getVersion()); + "Your server version is {0}. Please disable using native implementation!", Bukkit.getVersion()); Settings.useNativeImplementation = false; setPlayerAdvancements(player, advancementRecords, data); @@ -280,16 +278,17 @@ public class PlayerSetter { } } - private static void nativeSyncPlayerAdvancements(final Player player, final List advancementRecords) { + private static void nativeSyncPlayerAdvancements(final Player player, final List advancementRecords) { final Object playerAdvancements = AdvancementUtils.getPlayerAdvancements(player); // Clear - AdvancementUtils.clearPlayerAdvancementsMap(playerAdvancements); + AdvancementUtils.clearPlayerAdvancements(playerAdvancements); + AdvancementUtils.clearVisibleAdvancements(playerAdvancements); advancementRecords.forEach(advancementRecord -> { NamespacedKey namespacedKey = Objects.requireNonNull( - NamespacedKey.fromString(advancementRecord.advancementKey()), - "Invalid Namespaced key of " + advancementRecord.advancementKey() + NamespacedKey.fromString(advancementRecord.key()), + "Invalid Namespaced key of " + advancementRecord.key() ); Advancement bukkitAdvancement = Bukkit.getAdvancement(namespacedKey); @@ -298,26 +297,21 @@ public class PlayerSetter { return; } - // todo: sync date of get advancement - Date date = Date.from(Instant.now().minus(Period.ofWeeks(1))); - Object advancement = AdvancementUtils.getHandle(bukkitAdvancement); - List criteriaList = advancementRecord.awardedAdvancementCriteria(); + Map criteriaList = advancementRecord.criteriaMap(); { Map nativeCriteriaMap = new HashMap<>(); - criteriaList.forEach(criteria -> + criteriaList.forEach((criteria, date) -> nativeCriteriaMap.put(criteria, AdvancementUtils.newCriterionProgress(date)) ); Object nativeAdvancementProgress = AdvancementUtils.newAdvancementProgress(nativeCriteriaMap); AdvancementUtils.startProgress(playerAdvancements, advancement, nativeAdvancementProgress); - } }); - synchronized (playerAdvancements) { - AdvancementUtils.markPlayerAdvancementsFirst(playerAdvancements); - AdvancementUtils.ensureAllVisible(playerAdvancements); + AdvancementUtils.ensureAllVisible(playerAdvancements); // Set all completed advancement is visible + AdvancementUtils.markPlayerAdvancementsFirst(playerAdvancements); // Mark the sending of visible advancement as the first } } @@ -327,7 +321,7 @@ public class PlayerSetter { * @param player The player to set the advancements of * @param advancementData The ArrayList of {@link DataSerializer.AdvancementRecord}s to set */ - private static void setPlayerAdvancements(Player player, ArrayList advancementData, PlayerData data) { + private static void setPlayerAdvancements(Player player, List advancementData, PlayerData data) { // Temporarily disable advancement announcing if needed boolean announceAdvancementUpdate = false; if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) { @@ -345,13 +339,13 @@ public class PlayerSetter { boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update Advancement advancement = serverAdvancements.next(); AdvancementProgress playerProgress = player.getAdvancementProgress(advancement); - for (DataSerializer.AdvancementRecord record : advancementData) { + for (DataSerializer.AdvancementRecordDate record : advancementData) { // If the advancement is one on the data - if (record.advancementKey().equals(advancement.getKey().getNamespace() + ":" + advancement.getKey().getKey())) { + if (record.key().equals(advancement.getKey().getNamespace() + ":" + advancement.getKey().getKey())) { // Award all criteria that the player does not have that they do on the cache ArrayList currentlyAwardedCriteria = new ArrayList<>(playerProgress.getAwardedCriteria()); - for (String awardCriteria : record.awardedAdvancementCriteria()) { + for (String awardCriteria : record.criteriaMap().keySet()) { if (!playerProgress.getAwardedCriteria().contains(awardCriteria)) { Bukkit.getScheduler().runTask(plugin, () -> player.getAdvancementProgress(advancement).awardCriteria(awardCriteria)); correctExperienceCheck = true; diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java index 4d4b40ec..b9f696a3 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java @@ -9,19 +9,20 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.Map; +import java.util.Set; public class AdvancementUtils { + public final static Class PLAYER_ADVANCEMENT; private final static Field PLAYER_ADVANCEMENTS_MAP; + private final static Field PLAYER_VISIBLE_SET; private final static Field PLAYER_ADVANCEMENTS; private final static Field CRITERIA_MAP; private final static Field CRITERIA_DATE; private final static Field IS_FIRST_PACKET; - private final static Method GET_HANDLE; private final static Method START_PROGRESS; private final static Method ENSURE_ALL_VISIBLE; - private final static Class ADVANCEMENT_PROGRESS; private final static Class CRITERION_PROGRESS; @@ -43,21 +44,24 @@ public class AdvancementUtils { Class ADVANCEMENT = ThrowSupplier.get(() -> Class.forName("net.minecraft.advancements.Advancement")); - Class PLAYER_ADVANCEMENTS = MinecraftVersionUtils.getMinecraftClass("AdvancementDataPlayer"); - PLAYER_ADVANCEMENTS_MAP = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredField("h")); + PLAYER_ADVANCEMENT = MinecraftVersionUtils.getMinecraftClass("AdvancementDataPlayer"); + PLAYER_ADVANCEMENTS_MAP = ThrowSupplier.get(() -> PLAYER_ADVANCEMENT.getDeclaredField("h")); PLAYER_ADVANCEMENTS_MAP.setAccessible(true); - START_PROGRESS = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredMethod("a", ADVANCEMENT, ADVANCEMENT_PROGRESS)); + PLAYER_VISIBLE_SET = ThrowSupplier.get(() -> PLAYER_ADVANCEMENT.getDeclaredField("i")); + PLAYER_VISIBLE_SET.setAccessible(true); + + START_PROGRESS = ThrowSupplier.get(() -> PLAYER_ADVANCEMENT.getDeclaredMethod("a", ADVANCEMENT, ADVANCEMENT_PROGRESS)); START_PROGRESS.setAccessible(true); - ENSURE_ALL_VISIBLE = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredMethod("c")); + ENSURE_ALL_VISIBLE = ThrowSupplier.get(() -> PLAYER_ADVANCEMENT.getDeclaredMethod("c")); ENSURE_ALL_VISIBLE.setAccessible(true); - IS_FIRST_PACKET = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredField("n")); + IS_FIRST_PACKET = ThrowSupplier.get(() -> PLAYER_ADVANCEMENT.getDeclaredField("n")); IS_FIRST_PACKET.setAccessible(true); } - public static void markPlayerAdvancementsFirst(Object playerAdvancements) { + public static void markPlayerAdvancementsFirst(final Object playerAdvancements) { try { IS_FIRST_PACKET.set(playerAdvancements, true); } catch (IllegalAccessException e) { @@ -74,7 +78,7 @@ public class AdvancementUtils { } } - public static void clearPlayerAdvancementsMap(final Object playerAdvancement) { + public static void clearPlayerAdvancements(final Object playerAdvancement) { try { ((Map) PLAYER_ADVANCEMENTS_MAP.get(playerAdvancement)) .clear(); @@ -130,4 +134,12 @@ public class AdvancementUtils { } } + public static void clearVisibleAdvancements(final Object playerAdvancements) { + try { + ((Set) PLAYER_VISIBLE_SET.get(playerAdvancements)) + .clear(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage(), e); + } + } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java index 270e3ce7..8a79c465 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java @@ -119,7 +119,7 @@ public class BungeeRedisListener extends RedisListener { if (player.isConnected()) { new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), - RedisMessage.serialize(playerData)) + serializedPlayerData) .send(); // Send synchronisation complete message