From 3639c3c48822ec848b1681cd7bafde49a9c5cad7 Mon Sep 17 00:00:00 2001 From: HarvelsX Date: Wed, 15 Dec 2021 00:31:44 +0300 Subject: [PATCH] Add native sync advancements without toast, announces, rewards; --- .../william278/husksync/HuskSyncBukkit.java | 7 +- .../husksync/bukkit/config/ConfigLoader.java | 3 + .../husksync/bukkit/util/PlayerSetter.java | 69 +++++++++- .../bukkit/util/nms/AdvancementUtils.java | 121 ++++++++++++++++++ .../husksync/bukkit/util/nms/EntityUtils.java | 26 ++++ .../util/nms/MinecraftVersionUtils.java | 42 ++++++ .../java/me/william278/husksync/Settings.java | 3 + .../husksync/util/ThrowSupplier.java | 13 ++ 8 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java create mode 100644 bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/EntityUtils.java create mode 100644 bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/MinecraftVersionUtils.java create mode 100644 common/src/main/java/me/william278/husksync/util/ThrowSupplier.java diff --git a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java index 494feb15..ad801328 100644 --- a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java +++ b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java @@ -1,16 +1,15 @@ package me.william278.husksync; -import me.william278.husksync.bukkit.util.BukkitUpdateChecker; -import me.william278.husksync.bukkit.util.PlayerSetter; import me.william278.husksync.bukkit.config.ConfigLoader; import me.william278.husksync.bukkit.data.BukkitDataCache; -import me.william278.husksync.bukkit.listener.BukkitRedisListener; import me.william278.husksync.bukkit.listener.BukkitEventListener; +import me.william278.husksync.bukkit.listener.BukkitRedisListener; +import me.william278.husksync.bukkit.util.BukkitUpdateChecker; +import me.william278.husksync.bukkit.util.PlayerSetter; import me.william278.husksync.redis.RedisMessage; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java b/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java index 9b0264ee..b6dfbddd 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java @@ -24,6 +24,9 @@ public class ConfigLoader { Settings.syncAdvancements = config.getBoolean("synchronisation_settings.advancements", true); Settings.syncLocation = config.getBoolean("synchronisation_settings.location", false); Settings.syncFlight = config.getBoolean("synchronisation_settings.flight", false); + + // Future + Settings.useNativeImplementation = config.getBoolean("useNativeImplementation", false); } } 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 0b5c3670..33ee1660 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 @@ -6,6 +6,7 @@ import me.william278.husksync.Settings; import me.william278.husksync.api.events.SyncCompleteEvent; import me.william278.husksync.api.events.SyncEvent; import me.william278.husksync.bukkit.data.DataSerializer; +import me.william278.husksync.bukkit.util.nms.AdvancementUtils; import me.william278.husksync.redis.RedisMessage; import org.bukkit.*; import org.bukkit.advancement.Advancement; @@ -19,10 +20,9 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Objects; -import java.util.UUID; +import java.time.Instant; +import java.time.Period; +import java.util.*; import java.util.logging.Level; public class PlayerSetter { @@ -160,7 +160,25 @@ public class PlayerSetter { // Set the player's data from the PlayerData try { if (Settings.syncAdvancements) { - setPlayerAdvancements(player, DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()), data); + ArrayList advancementRecords + = DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()); + + if (Settings.useNativeImplementation) { + try { + nativeSyncPlayerAdvancements(player, advancementRecords); + } catch (Exception e) { + 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()); + + Settings.useNativeImplementation = false; + setPlayerAdvancements(player, advancementRecords, data); + plugin.getLogger().fine(e.toString()); + e.printStackTrace(); + } + } + else setPlayerAdvancements(player, advancementRecords, data); } if (Settings.syncInventories) { setPlayerInventory(player, DataSerializer.deserializeInventory(data.getSerializedInventory())); @@ -260,6 +278,47 @@ public class PlayerSetter { } } + private static void nativeSyncPlayerAdvancements(final Player player, final List advancementRecords) { + final Object playerAdvancements = AdvancementUtils.getPlayerAdvancements(player); + + // Clear + AdvancementUtils.clearPlayerAdvancementsMap(playerAdvancements); + + final Map> syncAdvancementMap = new HashMap<>(); + advancementRecords.forEach(advancementRecord -> { + NamespacedKey namespacedKey = Objects.requireNonNull( + NamespacedKey.fromString(advancementRecord.advancementKey()), + "Invalid Namespaced key of " + advancementRecord.advancementKey() + ); + + Advancement bukkitAdvancement = Bukkit.getAdvancement(namespacedKey); + if (bukkitAdvancement == null) { + // todo: write logging + return; + } + + syncAdvancementMap.put( + AdvancementUtils.getHandle(bukkitAdvancement), + advancementRecord.awardedAdvancementCriteria() + ); + }); + + // todo: sync date of get advancement + Date date = Date.from(Instant.now().minus(Period.ofWeeks(1))); + + syncAdvancementMap.forEach((advancement, criteriaList) -> { + Map nativeCriteriaMap = new HashMap<>(); + criteriaList.forEach(criteria -> + nativeCriteriaMap.put(criteria, AdvancementUtils.newCriterionProgress(date)) + ); + Object nativeAdvancementProgress = AdvancementUtils.newAdvancementProgress(nativeCriteriaMap); + + AdvancementUtils.startProgress(playerAdvancements, advancement, nativeAdvancementProgress); + }); + + AdvancementUtils.ensureAllVisible(playerAdvancements); + } + /** * Update a player's advancements and progress to match the advancementData * 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 new file mode 100644 index 00000000..b7be6122 --- /dev/null +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/AdvancementUtils.java @@ -0,0 +1,121 @@ +package me.william278.husksync.bukkit.util.nms; + +import me.william278.husksync.util.ThrowSupplier; +import org.bukkit.advancement.Advancement; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; + +public class AdvancementUtils { + + private final static Field PLAYER_ADVANCEMENTS_MAP; + private final static Field PLAYER_ADVANCEMENTS; + private final static Field CRITERIA_MAP; + private final static Field CRITERIA_DATE; + + 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; + + static { + Class SERVER_PLAYER = MinecraftVersionUtils.getMinecraftClass("level.EntityPlayer"); + PLAYER_ADVANCEMENTS = ThrowSupplier.get(() -> SERVER_PLAYER.getDeclaredField("cs")); + PLAYER_ADVANCEMENTS.setAccessible(true); + + Class CRAFT_ADVANCEMENT = MinecraftVersionUtils.getBukkitClass("advancement.CraftAdvancement"); + GET_HANDLE = ThrowSupplier.get(() -> CRAFT_ADVANCEMENT.getDeclaredMethod("getHandle")); + + ADVANCEMENT_PROGRESS = ThrowSupplier.get(() -> Class.forName("net.minecraft.advancements.AdvancementProgress")); + CRITERIA_MAP = ThrowSupplier.get(() -> ADVANCEMENT_PROGRESS.getDeclaredField("a")); + CRITERIA_MAP.setAccessible(true); + + CRITERION_PROGRESS = ThrowSupplier.get(() -> Class.forName("net.minecraft.advancements.CriterionProgress")); + CRITERIA_DATE = ThrowSupplier.get(() -> CRITERION_PROGRESS.getDeclaredField("b")); + CRITERIA_DATE.setAccessible(true); + + 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_ADVANCEMENTS_MAP.setAccessible(true); + + START_PROGRESS = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredMethod("a", ADVANCEMENT, ADVANCEMENT_PROGRESS)); + START_PROGRESS.setAccessible(true); + + ENSURE_ALL_VISIBLE = ThrowSupplier.get(() -> PLAYER_ADVANCEMENTS.getDeclaredMethod("c")); + ENSURE_ALL_VISIBLE.setAccessible(true); + } + + public static Object getPlayerAdvancements (Player player) { + Object nativePlayer = EntityUtils.getHandle(player); + try { + return PLAYER_ADVANCEMENTS.get(nativePlayer); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static void clearPlayerAdvancementsMap (final Object playerAdvancement) { + try { + ((Map) PLAYER_ADVANCEMENTS_MAP.get(playerAdvancement)) + .clear(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static Object getHandle (Advancement advancement) { + try { + return GET_HANDLE.invoke(advancement); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static Object newCriterionProgress (final Date date) { + try { + Object nativeCriterionProgress = CRITERION_PROGRESS.getDeclaredConstructor().newInstance(); + CRITERIA_DATE.set(nativeCriterionProgress, date); + return nativeCriterionProgress; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static Object newAdvancementProgress (final Map criteria) { + try { + Object nativeAdvancementProgress = ADVANCEMENT_PROGRESS.getDeclaredConstructor().newInstance(); + + final Map criteriaMap = (Map) CRITERIA_MAP.get(nativeAdvancementProgress); + criteriaMap.putAll(criteria); + + return nativeAdvancementProgress; + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static void startProgress (final Object playerAdvancements, final Object advancement, final Object advancementProgress) { + try { + START_PROGRESS.invoke(playerAdvancements, advancement, advancementProgress); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static void ensureAllVisible (final Object playerAdvancements) { + try { + ENSURE_ALL_VISIBLE.invoke(playerAdvancements); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/EntityUtils.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/EntityUtils.java new file mode 100644 index 00000000..d02e8bcc --- /dev/null +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/EntityUtils.java @@ -0,0 +1,26 @@ +package me.william278.husksync.bukkit.util.nms; + +import me.william278.husksync.util.ThrowSupplier; +import org.bukkit.entity.LivingEntity; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class EntityUtils { + + private final static Method GET_HANDLE; + + static { + final Class CRAFT_ENTITY = MinecraftVersionUtils.getBukkitClass("entity.CraftEntity"); + GET_HANDLE = ThrowSupplier.get(() -> CRAFT_ENTITY.getDeclaredMethod("getHandle")); + } + + public static Object getHandle (LivingEntity livingEntity) throws RuntimeException { + try { + return GET_HANDLE.invoke(livingEntity); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/MinecraftVersionUtils.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/MinecraftVersionUtils.java new file mode 100644 index 00000000..82babcc2 --- /dev/null +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/nms/MinecraftVersionUtils.java @@ -0,0 +1,42 @@ +package me.william278.husksync.bukkit.util.nms; + +import me.william278.husksync.util.ThrowSupplier; +import org.bukkit.Bukkit; + +public class MinecraftVersionUtils { + + public final static String CRAFTBUKKIT_PACKAGE_PATH = Bukkit.getServer().getClass().getPackage().getName(); + + public final static String PACKAGE_VERSION = CRAFTBUKKIT_PACKAGE_PATH.split("\\.")[3]; + public final static String SERVER_VERSION = Bukkit.getBukkitVersion().split("-")[0]; + + public final static String MINECRAFT_PACKAGE = compare("1.17") < 0 ? + "net.minecraft.server.".concat(PACKAGE_VERSION) : "net.minecraft.server"; + + public static int compare(String version) { + if(version == null) return 1; + + String[] as = SERVER_VERSION.split("\\."); + String[] bs = version.split("\\."); + + int length = Math.max(as.length, bs.length); + for(int i = 0; i < length; i++) { + int a = i < as.length ? Integer.parseInt(as[i]) : 0; + int b = i < bs.length ? Integer.parseInt(bs[i]) : 0; + + if(a < b) return -1; + if(a > b) return 1; + } + + return 0; + } + + public static Class getBukkitClass(String path) { + return ThrowSupplier.get(() -> Class.forName(CRAFTBUKKIT_PACKAGE_PATH.concat(".").concat(path))); + } + + public static Class getMinecraftClass(String path) { + return ThrowSupplier.get(() -> Class.forName(MINECRAFT_PACKAGE.concat(".").concat(path))); + } + +} diff --git a/common/src/main/java/me/william278/husksync/Settings.java b/common/src/main/java/me/william278/husksync/Settings.java index eabeed4f..e54f1f3a 100644 --- a/common/src/main/java/me/william278/husksync/Settings.java +++ b/common/src/main/java/me/william278/husksync/Settings.java @@ -67,6 +67,9 @@ public class Settings { public static boolean syncLocation; public static boolean syncFlight; + // Future + public static boolean useNativeImplementation; + // This Cluster ID public static String cluster; diff --git a/common/src/main/java/me/william278/husksync/util/ThrowSupplier.java b/common/src/main/java/me/william278/husksync/util/ThrowSupplier.java new file mode 100644 index 00000000..e7eba2a9 --- /dev/null +++ b/common/src/main/java/me/william278/husksync/util/ThrowSupplier.java @@ -0,0 +1,13 @@ +package me.william278.husksync.util; + +public interface ThrowSupplier { + T get() throws Exception; + + static A get(ThrowSupplier supplier) { + try { + return supplier.get(); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } +}