From 10f1a0956168800054982228927c065c7b8abbae Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Mon, 15 Jul 2024 00:05:54 +0100 Subject: [PATCH] fix(spigot): special pricing information being lost on recent MC versions Migrated to ProtocolLib's converter which uses the Bukkit API. Unfortunately, versions of the Bukkit API on 1.17 and below do not include all required fields, so villager translation support for them was dropped in this commit. Fixes #421 --- .../ProtocolLibListener.java | 78 ++++++------------- .../utils/ItemStackTranslationUtils.java | 2 +- 2 files changed, 25 insertions(+), 55 deletions(-) diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/ProtocolLibListener.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/ProtocolLibListener.java index 32f5722a..167f2a0e 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/ProtocolLibListener.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/ProtocolLibListener.java @@ -11,7 +11,6 @@ import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; @@ -34,7 +33,6 @@ import com.rexcantor64.triton.spigot.utils.WrappedComponentUtils; import com.rexcantor64.triton.spigot.wrappers.WrappedClientConfiguration; import com.rexcantor64.triton.utils.ComponentUtils; -import com.rexcantor64.triton.utils.ReflectionUtils; import com.rexcantor64.triton.wrappers.WrappedPlayerChatMessage; import lombok.val; import net.kyori.adventure.text.Component; @@ -68,15 +66,10 @@ @SuppressWarnings({"deprecation"}) public class ProtocolLibListener implements PacketListener { private final Class CONTAINER_PLAYER_CLASS; - private final Class MERCHANT_RECIPE_LIST_CLASS; - private final MethodAccessor CRAFT_MERCHANT_RECIPE_FROM_BUKKIT_METHOD; - private final MethodAccessor CRAFT_MERCHANT_RECIPE_TO_MINECRAFT_METHOD; private final Class BASE_COMPONENT_ARRAY_CLASS = BaseComponent[].class; private final Class ADVENTURE_COMPONENT_CLASS = Component.class; private final FieldAccessor PLAYER_ACTIVE_CONTAINER_FIELD; private final FieldAccessor PLAYER_INVENTORY_CONTAINER_FIELD; - private final String MERCHANT_RECIPE_SPECIAL_PRICE_FIELD; - private final String MERCHANT_RECIPE_DEMAND_FIELD; private final String SIGN_NBT_ID; private final HandlerFunction ASYNC_PASSTHROUGH = asAsync((_packet, _player) -> { @@ -95,26 +88,6 @@ public class ProtocolLibListener implements PacketListener { public ProtocolLibListener(SpigotTriton main, HandlerFunction.HandlerType... allowedTypes) { this.main = main; this.allowedTypes = Arrays.asList(allowedTypes); - if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) { // 1.14+ - MERCHANT_RECIPE_LIST_CLASS = MinecraftReflection.getMerchantRecipeList(); - } else { - MERCHANT_RECIPE_LIST_CLASS = null; - } - if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) { // 1.14+ - val craftMerchantRecipeClass = MinecraftReflection.getCraftBukkitClass("inventory.CraftMerchantRecipe"); - CRAFT_MERCHANT_RECIPE_FROM_BUKKIT_METHOD = Accessors.getMethodAccessor(craftMerchantRecipeClass, "fromBukkit", MerchantRecipe.class); - CRAFT_MERCHANT_RECIPE_TO_MINECRAFT_METHOD = Accessors.getMethodAccessor(craftMerchantRecipeClass, "toMinecraft"); - } else { - CRAFT_MERCHANT_RECIPE_FROM_BUKKIT_METHOD = null; - CRAFT_MERCHANT_RECIPE_TO_MINECRAFT_METHOD = null; - } - if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { // 1.17+ - MERCHANT_RECIPE_SPECIAL_PRICE_FIELD = "g"; - MERCHANT_RECIPE_DEMAND_FIELD = "h"; - } else { - MERCHANT_RECIPE_SPECIAL_PRICE_FIELD = "specialPrice"; - MERCHANT_RECIPE_DEMAND_FIELD = "demand"; - } if (MinecraftVersion.EXPLORATION_UPDATE.atOrAbove()) { // 1.11+ SIGN_NBT_ID = "minecraft:sign"; } else { @@ -200,8 +173,8 @@ private void setupPacketHandlers() { } packetHandlers.put(PacketType.Play.Server.WINDOW_ITEMS, asAsync(this::handleWindowItems)); packetHandlers.put(PacketType.Play.Server.SET_SLOT, asAsync(this::handleSetSlot)); - if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) { // 1.14+ - // Villager merchant interface redesign on 1.14 + if (MinecraftVersion.CAVES_CLIFFS_2.atOrAbove()) { // 1.18+ + // While the villager merchant interface redesign was on 1.14, the Bukkit API only has all fields on 1.18 packetHandlers.put(PacketType.Play.Server.OPEN_WINDOW_MERCHANT, asAsync(this::handleMerchantItems)); } @@ -606,37 +579,34 @@ private void handleSetSlot(PacketEvent packet, SpigotLanguagePlayer languagePlay packet.getPacket().getItemModifier().writeSafely(0, item); } - @SuppressWarnings({"unchecked"}) private void handleMerchantItems(PacketEvent packet, SpigotLanguagePlayer languagePlayer) { if (!main.getConfig().isItems()) return; - try { - ArrayList recipes = (ArrayList) packet.getPacket() - .getSpecificModifier(MERCHANT_RECIPE_LIST_CLASS).readSafely(0); - ArrayList newRecipes = (ArrayList) MERCHANT_RECIPE_LIST_CLASS.newInstance(); - for (val recipeObject : recipes) { - val recipe = (MerchantRecipe) ReflectionUtils.getMethod(recipeObject, "asBukkit"); - val originalSpecialPrice = ReflectionUtils.getDeclaredField(recipeObject, MERCHANT_RECIPE_SPECIAL_PRICE_FIELD); - val originalDemand = ReflectionUtils.getDeclaredField(recipeObject, MERCHANT_RECIPE_DEMAND_FIELD); - - val newRecipe = new MerchantRecipe(ItemStackTranslationUtils.translateItemStack(recipe.getResult() - .clone(), languagePlayer, false), recipe.getUses(), recipe.getMaxUses(), recipe - .hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier()); - - for (val ingredient : recipe.getIngredients()) { - newRecipe.addIngredient(ItemStackTranslationUtils.translateItemStack(ingredient.clone(), languagePlayer, false)); - } + val recipes = packet.getPacket().getMerchantRecipeLists().readSafely(0); + val newRecipes = new ArrayList(); + + for (val recipe : recipes) { + // Unfortunately this constructor does not exist in older Bukkit versions + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/5dca4a4b8455ba1ee8d3e4e36894f6dcc4b04555 + val newRecipe = new MerchantRecipe( + ItemStackTranslationUtils.translateItemStack(recipe.getResult().clone(), languagePlayer, false), + recipe.getUses(), + recipe.getMaxUses(), + recipe.hasExperienceReward(), + recipe.getVillagerExperience(), + recipe.getPriceMultiplier(), + recipe.getDemand(), + recipe.getSpecialPrice() + ); - Object newCraftRecipe = CRAFT_MERCHANT_RECIPE_FROM_BUKKIT_METHOD.invoke(null, newRecipe); - Object newNMSRecipe = CRAFT_MERCHANT_RECIPE_TO_MINECRAFT_METHOD.invoke(newCraftRecipe); - ReflectionUtils.setDeclaredField(newNMSRecipe, MERCHANT_RECIPE_SPECIAL_PRICE_FIELD, originalSpecialPrice); - ReflectionUtils.setDeclaredField(newNMSRecipe, MERCHANT_RECIPE_DEMAND_FIELD, originalDemand); - newRecipes.add(newNMSRecipe); + for (val ingredient : recipe.getIngredients()) { + newRecipe.addIngredient(ItemStackTranslationUtils.translateItemStack(ingredient.clone(), languagePlayer, false)); } - packet.getPacket().getModifier().writeSafely(1, newRecipes); - } catch (IllegalAccessException | InstantiationException e) { - Triton.get().getLogger().logError(e, "Failed to translate merchant items."); + + newRecipes.add(newRecipe); } + + packet.getPacket().getMerchantRecipeLists().writeSafely(0, newRecipes); } private void handleScoreboardTeam(PacketEvent packet, SpigotLanguagePlayer languagePlayer) { diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/utils/ItemStackTranslationUtils.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/utils/ItemStackTranslationUtils.java index 4aba40df..5de0d35a 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/utils/ItemStackTranslationUtils.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/utils/ItemStackTranslationUtils.java @@ -58,7 +58,7 @@ public class ItemStackTranslationUtils { * @param translateBooks Whether it should translate written books * @return The translated item stack, which may or may not be the same as the given parameter */ - @Contract("null, _, _ -> null") + @Contract("null, _, _ -> null; !null, _, _ -> !null") public static @Nullable ItemStack translateItemStack(@Nullable ItemStack item, @NotNull Localized languagePlayer, boolean translateBooks) { if (item == null || item.getType() == Material.AIR) { return item;