From 72d311a706f22b2c0ca0bba3ab2d41b901ecfe6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:36:54 +0000 Subject: [PATCH 01/18] Initial plan From fcc6560e662bc452cae894b5ef1761cfaf69c819 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:44:33 +0000 Subject: [PATCH 02/18] feat: add client-side item lore event bridge Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../negative/engine/paper/PaperPlugin.java | 16 ++ .../paper/event/ClientItemLoreEvent.java | 69 +++++ .../event/ClientItemLorePacketBridge.java | 252 ++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java create mode 100644 paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java diff --git a/paper/src/main/java/games/negative/engine/paper/PaperPlugin.java b/paper/src/main/java/games/negative/engine/paper/PaperPlugin.java index 775ba17..5cccbd6 100644 --- a/paper/src/main/java/games/negative/engine/paper/PaperPlugin.java +++ b/paper/src/main/java/games/negative/engine/paper/PaperPlugin.java @@ -2,6 +2,7 @@ import games.negative.engine.Plugin; import games.negative.engine.message.util.MiniMessageUtil; +import games.negative.engine.paper.event.ClientItemLorePacketBridge; import games.negative.engine.paper.scheduler.Scheduler; import games.negative.engine.state.Reloadable; import games.negative.moss.paper.MossPaper; @@ -20,6 +21,8 @@ @Slf4j public abstract class PaperPlugin extends MossPaper implements Plugin { + private ClientItemLorePacketBridge clientItemLorePacketBridge; + @Override public void loadInitialComponents(AnnotationConfigApplicationContext context) { super.loadInitialComponents(context); @@ -37,6 +40,19 @@ public void onEnable() { listener -> getServer().getPluginManager().registerEvents(listener, this), (listener, e) -> log.error("Failed to register listener: {}", listener.getClass().getSimpleName(), e) ); + + this.clientItemLorePacketBridge = new ClientItemLorePacketBridge(this); + this.clientItemLorePacketBridge.enable(); + } + + @Override + public void onDisable() { + if (this.clientItemLorePacketBridge != null) { + this.clientItemLorePacketBridge.disable(); + this.clientItemLorePacketBridge = null; + } + + super.onDisable(); } @Override diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java new file mode 100644 index 0000000..0207665 --- /dev/null +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java @@ -0,0 +1,69 @@ +package games.negative.engine.paper.event; + +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fired when a client-bound item packet is about to be sent to a player. + * Changes made through this event only affect the client view of the item lore. + */ +public final class ClientItemLoreEvent extends PlayerEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final int windowId; + private final int slot; + private final ItemStack originalItem; + private List lore; + + public ClientItemLoreEvent(Player player, int windowId, int slot, ItemStack item) { + super(player); + this.windowId = windowId; + this.slot = slot; + this.originalItem = item.clone(); + + ItemMeta meta = item.getItemMeta(); + List currentLore = meta == null ? List.of() : meta.lore(); + this.lore = currentLore == null ? new ArrayList<>() : new ArrayList<>(currentLore); + } + + public int getWindowId() { + return windowId; + } + + public int getSlot() { + return slot; + } + + public ItemStack getOriginalItem() { + return originalItem.clone(); + } + + public List getLore() { + return lore; + } + + public void setLore(List lore) { + this.lore = lore == null ? new ArrayList<>() : new ArrayList<>(lore); + } + + public void clearLore() { + this.lore.clear(); + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java new file mode 100644 index 0000000..99aff64 --- /dev/null +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -0,0 +1,252 @@ +package games.negative.engine.paper.event; + +import lombok.extern.slf4j.Slf4j; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Optional PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. + */ +@Slf4j +public final class ClientItemLorePacketBridge { + + private static final int CARRIED_ITEM_SLOT = -1; + + private final Plugin plugin; + + private Object eventManager; + private Object listenerHandle; + + public ClientItemLorePacketBridge(Plugin plugin) { + this.plugin = plugin; + } + + public void enable() { + if (listenerHandle != null || !isPacketEventsPresent()) return; + + try { + ClassLoader classLoader = getPacketEventsClassLoader(); + + Class packetEventsClass = Class.forName("com.github.retrooper.packetevents.PacketEvents", false, classLoader); + Class packetListenerClass = Class.forName("com.github.retrooper.packetevents.event.PacketListener", false, classLoader); + Class priorityClass = Class.forName("com.github.retrooper.packetevents.event.PacketListenerPriority", false, classLoader); + + Object api = packetEventsClass.getMethod("getAPI").invoke(null); + this.eventManager = api.getClass().getMethod("getEventManager").invoke(api); + + InvocationHandler handler = (proxy, method, args) -> { + if ("onPacketSend".equals(method.getName()) && args != null && args.length == 1) { + onPacketSend(args[0]); + } + + return null; + }; + + Object listener = Proxy.newProxyInstance(packetListenerClass.getClassLoader(), new Class[]{packetListenerClass}, handler); + Object priority = Enum.valueOf((Class) priorityClass.asSubclass(Enum.class), "NORMAL"); + + this.listenerHandle = eventManager.getClass() + .getMethod("registerListener", packetListenerClass, priorityClass) + .invoke(eventManager, listener, priority); + + log.info("Enabled PacketEvents client item lore bridge"); + } catch (ReflectiveOperationException exception) { + this.eventManager = null; + this.listenerHandle = null; + log.warn("Failed to enable PacketEvents client item lore bridge", exception); + } + } + + public void disable() { + if (eventManager == null || listenerHandle == null) return; + + try { + Class listenerCommonClass = Class.forName( + "com.github.retrooper.packetevents.event.PacketListenerCommon", + false, + getPacketEventsClassLoader() + ); + + eventManager.getClass() + .getMethod("unregisterListener", listenerCommonClass) + .invoke(eventManager, listenerHandle); + } catch (ReflectiveOperationException exception) { + log.warn("Failed to disable PacketEvents client item lore bridge", exception); + } finally { + this.eventManager = null; + this.listenerHandle = null; + } + } + + private void onPacketSend(Object packetSendEvent) { + if (ClientItemLoreEvent.getHandlerList().getRegisteredListeners().length == 0) return; + + try { + Object packetType = packetSendEvent.getClass().getMethod("getPacketType").invoke(packetSendEvent); + if (!(packetType instanceof Enum packetTypeEnum)) return; + + Object playerObject = packetSendEvent.getClass().getMethod("getPlayer").invoke(packetSendEvent); + if (!(playerObject instanceof Player player)) return; + + switch (packetTypeEnum.name()) { + case "SET_SLOT" -> handleSetSlot(packetSendEvent, player); + case "WINDOW_ITEMS" -> handleWindowItems(packetSendEvent, player); + default -> { + } + } + } catch (ReflectiveOperationException exception) { + log.warn("Failed to process outgoing item packet", exception); + } + } + + private void handleSetSlot(Object packetSendEvent, Player player) throws ReflectiveOperationException { + ClassLoader classLoader = getPacketEventsClassLoader(); + Class packetSendEventClass = Class.forName( + "com.github.retrooper.packetevents.event.PacketSendEvent", + false, + classLoader + ); + Class wrapperClass = Class.forName( + "com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot", + false, + classLoader + ); + Class packetItemClass = Class.forName( + "com.github.retrooper.packetevents.protocol.item.ItemStack", + false, + classLoader + ); + + Object wrapper = wrapperClass.getConstructor(packetSendEventClass).newInstance(packetSendEvent); + int windowId = (int) wrapperClass.getMethod("getWindowId").invoke(wrapper); + int slot = (int) wrapperClass.getMethod("getSlot").invoke(wrapper); + Object packetItem = wrapperClass.getMethod("getItem").invoke(wrapper); + + Object updatedItem = applyLore(player, windowId, slot, packetItem); + if (updatedItem != null) { + wrapperClass.getMethod("setItem", packetItemClass).invoke(wrapper, updatedItem); + } + } + + private void handleWindowItems(Object packetSendEvent, Player player) throws ReflectiveOperationException { + ClassLoader classLoader = getPacketEventsClassLoader(); + Class packetSendEventClass = Class.forName( + "com.github.retrooper.packetevents.event.PacketSendEvent", + false, + classLoader + ); + Class wrapperClass = Class.forName( + "com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems", + false, + classLoader + ); + Class packetItemClass = Class.forName( + "com.github.retrooper.packetevents.protocol.item.ItemStack", + false, + classLoader + ); + + Object wrapper = wrapperClass.getConstructor(packetSendEventClass).newInstance(packetSendEvent); + int windowId = (int) wrapperClass.getMethod("getWindowId").invoke(wrapper); + + List items = (List) wrapperClass.getMethod("getItems").invoke(wrapper); + List modifiedItems = new ArrayList<>(items.size()); + for (int slot = 0; slot < items.size(); slot++) { + Object updatedItem = applyLore(player, windowId, slot, items.get(slot)); + modifiedItems.add(updatedItem == null ? items.get(slot) : updatedItem); + } + wrapperClass.getMethod("setItems", List.class).invoke(wrapper, modifiedItems); + + Optional carriedItem = (Optional) wrapperClass.getMethod("getCarriedItem").invoke(wrapper); + if (carriedItem.isPresent()) { + Object updatedCarriedItem = applyLore(player, windowId, CARRIED_ITEM_SLOT, carriedItem.get()); + if (updatedCarriedItem != null) { + wrapperClass.getMethod("setCarriedItem", packetItemClass).invoke(wrapper, updatedCarriedItem); + } + } + } + + private Object applyLore(Player player, int windowId, int slot, Object packetItem) throws ReflectiveOperationException { + if (packetItem == null) return null; + + ItemStack bukkitItem = toBukkitItem(packetItem); + if (bukkitItem == null || bukkitItem.getType().isAir()) return null; + + ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem); + plugin.getServer().getPluginManager().callEvent(event); + + ItemStack clientItem = bukkitItem.clone(); + ItemMeta meta = clientItem.getItemMeta(); + if (meta == null) return null; + + List lore = event.getLore(); + meta.lore(lore); + clientItem.setItemMeta(meta); + + return fromBukkitItem(clientItem); + } + + private ItemStack toBukkitItem(Object packetItem) throws ReflectiveOperationException { + Class packetItemClass = Class.forName( + "com.github.retrooper.packetevents.protocol.item.ItemStack", + false, + getPacketEventsClassLoader() + ); + + Class conversionUtilClass = Class.forName( + "io.github.retrooper.packetevents.util.SpigotConversionUtil", + false, + getPacketEventsClassLoader() + ); + + return (ItemStack) conversionUtilClass + .getMethod("toBukkitItemStack", packetItemClass) + .invoke(null, packetItem); + } + + private Object fromBukkitItem(ItemStack itemStack) throws ReflectiveOperationException { + Class conversionUtilClass = Class.forName( + "io.github.retrooper.packetevents.util.SpigotConversionUtil", + false, + getPacketEventsClassLoader() + ); + + return conversionUtilClass + .getMethod("fromBukkitItemStack", ItemStack.class) + .invoke(null, itemStack); + } + + private boolean isPacketEventsPresent() { + try { + ClassLoader classLoader = getPacketEventsClassLoader(); + Class.forName("com.github.retrooper.packetevents.PacketEvents", false, classLoader); + Class.forName("io.github.retrooper.packetevents.util.SpigotConversionUtil", false, classLoader); + return true; + } catch (ClassNotFoundException exception) { + return false; + } + } + + private ClassLoader getPacketEventsClassLoader() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); + + Plugin packetEventsPlugin = pluginManager.getPlugin("packetevents"); + if (packetEventsPlugin == null) { + packetEventsPlugin = pluginManager.getPlugin("PacketEvents"); + } + + return packetEventsPlugin == null + ? plugin.getClass().getClassLoader() + : packetEventsPlugin.getClass().getClassLoader(); + } +} From 3b609dc7ceac8592818c4e86a744bbe843ee2226 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:47:04 +0000 Subject: [PATCH 03/18] fix: harden PacketEvents lore bridge proxy Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- gradlew | 0 .../paper/event/ClientItemLoreEvent.java | 8 ++- .../event/ClientItemLorePacketBridge.java | 62 +++++++++++++++++-- 3 files changed, 64 insertions(+), 6 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java index 0207665..90b1dec 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java @@ -27,7 +27,7 @@ public ClientItemLoreEvent(Player player, int windowId, int slot, ItemStack item super(player); this.windowId = windowId; this.slot = slot; - this.originalItem = item.clone(); + this.originalItem = item; ItemMeta meta = item.getItemMeta(); List currentLore = meta == null ? List.of() : meta.lore(); @@ -47,13 +47,17 @@ public ItemStack getOriginalItem() { } public List getLore() { - return lore; + return List.copyOf(lore); } public void setLore(List lore) { this.lore = lore == null ? new ArrayList<>() : new ArrayList<>(lore); } + public void addLoreLine(Component line) { + this.lore.add(line); + } + public void clearLore() { this.lore.clear(); } diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 99aff64..f290d41 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -9,6 +9,7 @@ import org.bukkit.plugin.PluginManager; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.MethodHandles; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; @@ -45,11 +46,19 @@ public void enable() { this.eventManager = api.getClass().getMethod("getEventManager").invoke(api); InvocationHandler handler = (proxy, method, args) -> { + if (method.getDeclaringClass() == Object.class) { + return handleObjectMethod(proxy, method.getName(), args); + } + if ("onPacketSend".equals(method.getName()) && args != null && args.length == 1) { onPacketSend(args[0]); } - return null; + if (method.isDefault()) { + return invokeDefaultMethod(proxy, method, args); + } + + return defaultValue(method.getReturnType()); }; Object listener = Proxy.newProxyInstance(packetListenerClass.getClassLoader(), new Class[]{packetListenerClass}, handler); @@ -101,8 +110,6 @@ private void onPacketSend(Object packetSendEvent) { switch (packetTypeEnum.name()) { case "SET_SLOT" -> handleSetSlot(packetSendEvent, player); case "WINDOW_ITEMS" -> handleWindowItems(packetSendEvent, player); - default -> { - } } } catch (ReflectiveOperationException exception) { log.warn("Failed to process outgoing item packet", exception); @@ -182,14 +189,19 @@ private Object applyLore(Player player, int windowId, int slot, Object packetIte ItemStack bukkitItem = toBukkitItem(packetItem); if (bukkitItem == null || bukkitItem.getType().isAir()) return null; + List originalLore = readLore(bukkitItem); ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem); plugin.getServer().getPluginManager().callEvent(event); + List lore = event.getLore(); + if (lore.equals(originalLore)) { + return packetItem; + } + ItemStack clientItem = bukkitItem.clone(); ItemMeta meta = clientItem.getItemMeta(); if (meta == null) return null; - List lore = event.getLore(); meta.lore(lore); clientItem.setItemMeta(meta); @@ -249,4 +261,46 @@ private ClassLoader getPacketEventsClassLoader() { ? plugin.getClass().getClassLoader() : packetEventsPlugin.getClass().getClassLoader(); } + + private List readLore(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + return List.of(); + } + + List lore = meta.lore(); + return lore == null ? List.of() : List.copyOf(lore); + } + + private Object invokeDefaultMethod(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable { + return MethodHandles.privateLookupIn(method.getDeclaringClass(), MethodHandles.lookup()) + .unreflectSpecial(method, method.getDeclaringClass()) + .bindTo(proxy) + .invokeWithArguments(args == null ? new Object[0] : args); + } + + private Object handleObjectMethod(Object proxy, String methodName, Object[] args) { + return switch (methodName) { + case "toString" -> getClass().getSimpleName(); + case "hashCode" -> System.identityHashCode(proxy); + case "equals" -> proxy == (args == null || args.length == 0 ? null : args[0]); + default -> null; + }; + } + + private Object defaultValue(Class returnType) { + if (!returnType.isPrimitive()) { + return null; + } + + if (returnType == boolean.class) return false; + if (returnType == char.class) return '\0'; + if (returnType == byte.class) return (byte) 0; + if (returnType == short.class) return (short) 0; + if (returnType == int.class) return 0; + if (returnType == long.class) return 0L; + if (returnType == float.class) return 0F; + if (returnType == double.class) return 0D; + return null; + } } From 697fc2c39af4aaf0d4a4622007f461e07c27aa1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:47:17 +0000 Subject: [PATCH 04/18] chore: revert gradlew permission change Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 gradlew diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 From 034a588587eaa0a5c753c49e107c7a6d7296dfd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:48:57 +0000 Subject: [PATCH 05/18] docs: clarify lore event mutation flow Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- gradlew | 0 .../negative/engine/paper/event/ClientItemLoreEvent.java | 4 ++++ .../engine/paper/event/ClientItemLorePacketBridge.java | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java index 90b1dec..c604ba1 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java @@ -46,6 +46,10 @@ public ItemStack getOriginalItem() { return originalItem.clone(); } + /** + * Returns an immutable snapshot of the current client-side lore. + * Use {@link #setLore(List)}, {@link #addLoreLine(Component)}, or {@link #clearLore()} to change it. + */ public List getLore() { return List.copyOf(lore); } diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index f290d41..57b65a8 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -200,7 +200,10 @@ private Object applyLore(Player player, int windowId, int slot, Object packetIte ItemStack clientItem = bukkitItem.clone(); ItemMeta meta = clientItem.getItemMeta(); - if (meta == null) return null; + if (meta == null) { + log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType()); + return packetItem; + } meta.lore(lore); clientItem.setItemMeta(meta); From d60dd0d0fbabd4d2ed31d2c2d240eadb128063d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:49:10 +0000 Subject: [PATCH 06/18] chore: restore gradlew permissions Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 gradlew diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 From aafc0676fb5d536940c1456624474539b27a7427 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:50:01 +0000 Subject: [PATCH 07/18] Add PacketEvents-backed client-only item lore event Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/8822c59a-59a9-45ee-a72d-512375ae2f3f Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 044e233d9b28b685a7b1001c3b0a75e512310d7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:58:59 +0000 Subject: [PATCH 08/18] refactor: use direct PacketEvents API Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/d295e053-2e1a-41c9-96a5-35b0484b0876 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- build.gradle.kts | 6 +- paper/build.gradle.kts | 5 +- .../event/ClientItemLorePacketBridge.java | 289 +++++------------- 3 files changed, 79 insertions(+), 221 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1836acd..41aacd9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,10 @@ subprojects { maven("https://repo.negative.games/repository/maven-releases/") maven("https://repo.negative.games/repository/maven-snapshots/") + // CodeMC + maven("https://repo.codemc.io/repository/maven-releases/") + maven("https://repo.codemc.io/repository/maven-snapshots/") + // PaperMC maven("https://repo.papermc.io/repository/maven-public/") @@ -47,4 +51,4 @@ subprojects { } } } -} \ No newline at end of file +} diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index e29b0e9..50bf74b 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -29,6 +29,9 @@ dependencies { // PlaceholderAPI compileOnly("me.clip:placeholderapi:2.11.7") + // PacketEvents + compileOnly("com.github.retrooper:packetevents-spigot:2.11.2") + // Spring & Jakarta compileOnly("org.springframework:spring-context:6.2.13") compileOnly("jakarta.annotation:jakarta.annotation-api:3.0.0") @@ -89,4 +92,4 @@ publishing { } } } -} \ No newline at end of file +} diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 57b65a8..74cb541 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -1,192 +1,130 @@ package games.negative.engine.paper.event; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketListenerCommon; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginManager; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.MethodHandles; -import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Optional; /** - * Optional PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. + * PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. */ @Slf4j -public final class ClientItemLorePacketBridge { +public final class ClientItemLorePacketBridge extends PacketListenerAbstract { private static final int CARRIED_ITEM_SLOT = -1; private final Plugin plugin; - - private Object eventManager; - private Object listenerHandle; + private PacketListenerCommon listenerHandle; public ClientItemLorePacketBridge(Plugin plugin) { this.plugin = plugin; } public void enable() { - if (listenerHandle != null || !isPacketEventsPresent()) return; - - try { - ClassLoader classLoader = getPacketEventsClassLoader(); - - Class packetEventsClass = Class.forName("com.github.retrooper.packetevents.PacketEvents", false, classLoader); - Class packetListenerClass = Class.forName("com.github.retrooper.packetevents.event.PacketListener", false, classLoader); - Class priorityClass = Class.forName("com.github.retrooper.packetevents.event.PacketListenerPriority", false, classLoader); - - Object api = packetEventsClass.getMethod("getAPI").invoke(null); - this.eventManager = api.getClass().getMethod("getEventManager").invoke(api); - - InvocationHandler handler = (proxy, method, args) -> { - if (method.getDeclaringClass() == Object.class) { - return handleObjectMethod(proxy, method.getName(), args); - } - - if ("onPacketSend".equals(method.getName()) && args != null && args.length == 1) { - onPacketSend(args[0]); - } - - if (method.isDefault()) { - return invokeDefaultMethod(proxy, method, args); - } - - return defaultValue(method.getReturnType()); - }; - - Object listener = Proxy.newProxyInstance(packetListenerClass.getClassLoader(), new Class[]{packetListenerClass}, handler); - Object priority = Enum.valueOf((Class) priorityClass.asSubclass(Enum.class), "NORMAL"); - - this.listenerHandle = eventManager.getClass() - .getMethod("registerListener", packetListenerClass, priorityClass) - .invoke(eventManager, listener, priority); - - log.info("Enabled PacketEvents client item lore bridge"); - } catch (ReflectiveOperationException exception) { - this.eventManager = null; - this.listenerHandle = null; - log.warn("Failed to enable PacketEvents client item lore bridge", exception); + if (listenerHandle != null) return; + if (PacketEvents.getAPI() == null) { + log.warn("PacketEvents API is unavailable; client item lore bridge was not registered"); + return; } + + this.listenerHandle = PacketEvents.getAPI().getEventManager().registerListener(this); + log.info("Enabled PacketEvents client item lore bridge"); } public void disable() { - if (eventManager == null || listenerHandle == null) return; - - try { - Class listenerCommonClass = Class.forName( - "com.github.retrooper.packetevents.event.PacketListenerCommon", - false, - getPacketEventsClassLoader() - ); - - eventManager.getClass() - .getMethod("unregisterListener", listenerCommonClass) - .invoke(eventManager, listenerHandle); - } catch (ReflectiveOperationException exception) { - log.warn("Failed to disable PacketEvents client item lore bridge", exception); - } finally { - this.eventManager = null; + if (listenerHandle == null) return; + if (PacketEvents.getAPI() == null) { this.listenerHandle = null; + return; } + + PacketEvents.getAPI().getEventManager().unregisterListener(listenerHandle); + this.listenerHandle = null; } - private void onPacketSend(Object packetSendEvent) { + @Override + public void onPacketSend(PacketSendEvent event) { if (ClientItemLoreEvent.getHandlerList().getRegisteredListeners().length == 0) return; + if (!(event.getPlayer() instanceof Player player)) return; - try { - Object packetType = packetSendEvent.getClass().getMethod("getPacketType").invoke(packetSendEvent); - if (!(packetType instanceof Enum packetTypeEnum)) return; - - Object playerObject = packetSendEvent.getClass().getMethod("getPlayer").invoke(packetSendEvent); - if (!(playerObject instanceof Player player)) return; + if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { + handleSetSlot(event, player); + return; + } - switch (packetTypeEnum.name()) { - case "SET_SLOT" -> handleSetSlot(packetSendEvent, player); - case "WINDOW_ITEMS" -> handleWindowItems(packetSendEvent, player); - } - } catch (ReflectiveOperationException exception) { - log.warn("Failed to process outgoing item packet", exception); + if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) { + handleWindowItems(event, player); } } - private void handleSetSlot(Object packetSendEvent, Player player) throws ReflectiveOperationException { - ClassLoader classLoader = getPacketEventsClassLoader(); - Class packetSendEventClass = Class.forName( - "com.github.retrooper.packetevents.event.PacketSendEvent", - false, - classLoader - ); - Class wrapperClass = Class.forName( - "com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot", - false, - classLoader + private void handleSetSlot(PacketSendEvent event, Player player) { + WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event); + com.github.retrooper.packetevents.protocol.item.ItemStack updatedItem = applyLore( + player, + wrapper.getWindowId(), + wrapper.getSlot(), + wrapper.getItem() ); - Class packetItemClass = Class.forName( - "com.github.retrooper.packetevents.protocol.item.ItemStack", - false, - classLoader - ); - - Object wrapper = wrapperClass.getConstructor(packetSendEventClass).newInstance(packetSendEvent); - int windowId = (int) wrapperClass.getMethod("getWindowId").invoke(wrapper); - int slot = (int) wrapperClass.getMethod("getSlot").invoke(wrapper); - Object packetItem = wrapperClass.getMethod("getItem").invoke(wrapper); - Object updatedItem = applyLore(player, windowId, slot, packetItem); if (updatedItem != null) { - wrapperClass.getMethod("setItem", packetItemClass).invoke(wrapper, updatedItem); + wrapper.setItem(updatedItem); } } - private void handleWindowItems(Object packetSendEvent, Player player) throws ReflectiveOperationException { - ClassLoader classLoader = getPacketEventsClassLoader(); - Class packetSendEventClass = Class.forName( - "com.github.retrooper.packetevents.event.PacketSendEvent", - false, - classLoader - ); - Class wrapperClass = Class.forName( - "com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems", - false, - classLoader - ); - Class packetItemClass = Class.forName( - "com.github.retrooper.packetevents.protocol.item.ItemStack", - false, - classLoader - ); - - Object wrapper = wrapperClass.getConstructor(packetSendEventClass).newInstance(packetSendEvent); - int windowId = (int) wrapperClass.getMethod("getWindowId").invoke(wrapper); + private void handleWindowItems(PacketSendEvent event, Player player) { + WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); - List items = (List) wrapperClass.getMethod("getItems").invoke(wrapper); - List modifiedItems = new ArrayList<>(items.size()); + List items = wrapper.getItems(); + List modifiedItems = new ArrayList<>(items.size()); for (int slot = 0; slot < items.size(); slot++) { - Object updatedItem = applyLore(player, windowId, slot, items.get(slot)); - modifiedItems.add(updatedItem == null ? items.get(slot) : updatedItem); + com.github.retrooper.packetevents.protocol.item.ItemStack originalItem = items.get(slot); + com.github.retrooper.packetevents.protocol.item.ItemStack updatedItem = applyLore( + player, + wrapper.getWindowId(), + slot, + originalItem + ); + modifiedItems.add(updatedItem == null ? originalItem : updatedItem); } - wrapperClass.getMethod("setItems", List.class).invoke(wrapper, modifiedItems); + wrapper.setItems(modifiedItems); - Optional carriedItem = (Optional) wrapperClass.getMethod("getCarriedItem").invoke(wrapper); + Optional carriedItem = wrapper.getCarriedItem(); if (carriedItem.isPresent()) { - Object updatedCarriedItem = applyLore(player, windowId, CARRIED_ITEM_SLOT, carriedItem.get()); + com.github.retrooper.packetevents.protocol.item.ItemStack updatedCarriedItem = applyLore( + player, + wrapper.getWindowId(), + CARRIED_ITEM_SLOT, + carriedItem.get() + ); if (updatedCarriedItem != null) { - wrapperClass.getMethod("setCarriedItem", packetItemClass).invoke(wrapper, updatedCarriedItem); + wrapper.setCarriedItem(updatedCarriedItem); } } } - private Object applyLore(Player player, int windowId, int slot, Object packetItem) throws ReflectiveOperationException { + private com.github.retrooper.packetevents.protocol.item.ItemStack applyLore( + Player player, + int windowId, + int slot, + com.github.retrooper.packetevents.protocol.item.ItemStack packetItem + ) { if (packetItem == null) return null; - ItemStack bukkitItem = toBukkitItem(packetItem); + ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); if (bukkitItem == null || bukkitItem.getType().isAir()) return null; List originalLore = readLore(bukkitItem); @@ -207,62 +145,7 @@ private Object applyLore(Player player, int windowId, int slot, Object packetIte meta.lore(lore); clientItem.setItemMeta(meta); - - return fromBukkitItem(clientItem); - } - - private ItemStack toBukkitItem(Object packetItem) throws ReflectiveOperationException { - Class packetItemClass = Class.forName( - "com.github.retrooper.packetevents.protocol.item.ItemStack", - false, - getPacketEventsClassLoader() - ); - - Class conversionUtilClass = Class.forName( - "io.github.retrooper.packetevents.util.SpigotConversionUtil", - false, - getPacketEventsClassLoader() - ); - - return (ItemStack) conversionUtilClass - .getMethod("toBukkitItemStack", packetItemClass) - .invoke(null, packetItem); - } - - private Object fromBukkitItem(ItemStack itemStack) throws ReflectiveOperationException { - Class conversionUtilClass = Class.forName( - "io.github.retrooper.packetevents.util.SpigotConversionUtil", - false, - getPacketEventsClassLoader() - ); - - return conversionUtilClass - .getMethod("fromBukkitItemStack", ItemStack.class) - .invoke(null, itemStack); - } - - private boolean isPacketEventsPresent() { - try { - ClassLoader classLoader = getPacketEventsClassLoader(); - Class.forName("com.github.retrooper.packetevents.PacketEvents", false, classLoader); - Class.forName("io.github.retrooper.packetevents.util.SpigotConversionUtil", false, classLoader); - return true; - } catch (ClassNotFoundException exception) { - return false; - } - } - - private ClassLoader getPacketEventsClassLoader() { - PluginManager pluginManager = plugin.getServer().getPluginManager(); - - Plugin packetEventsPlugin = pluginManager.getPlugin("packetevents"); - if (packetEventsPlugin == null) { - packetEventsPlugin = pluginManager.getPlugin("PacketEvents"); - } - - return packetEventsPlugin == null - ? plugin.getClass().getClassLoader() - : packetEventsPlugin.getClass().getClassLoader(); + return SpigotConversionUtil.fromBukkitItemStack(clientItem); } private List readLore(ItemStack itemStack) { @@ -274,36 +157,4 @@ private List readLore(ItemStack itemStack) { List lore = meta.lore(); return lore == null ? List.of() : List.copyOf(lore); } - - private Object invokeDefaultMethod(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable { - return MethodHandles.privateLookupIn(method.getDeclaringClass(), MethodHandles.lookup()) - .unreflectSpecial(method, method.getDeclaringClass()) - .bindTo(proxy) - .invokeWithArguments(args == null ? new Object[0] : args); - } - - private Object handleObjectMethod(Object proxy, String methodName, Object[] args) { - return switch (methodName) { - case "toString" -> getClass().getSimpleName(); - case "hashCode" -> System.identityHashCode(proxy); - case "equals" -> proxy == (args == null || args.length == 0 ? null : args[0]); - default -> null; - }; - } - - private Object defaultValue(Class returnType) { - if (!returnType.isPrimitive()) { - return null; - } - - if (returnType == boolean.class) return false; - if (returnType == char.class) return '\0'; - if (returnType == byte.class) return (byte) 0; - if (returnType == short.class) return (short) 0; - if (returnType == int.class) return 0; - if (returnType == long.class) return 0L; - if (returnType == float.class) return 0F; - if (returnType == double.class) return 0D; - return null; - } } From 63cde04dbb4d92fefc72b6d943ce9a8e7550bc3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:02:23 +0000 Subject: [PATCH 09/18] style: polish PacketEvents bridge cleanup Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/d295e053-2e1a-41c9-96a5-35b0484b0876 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../event/ClientItemLorePacketBridge.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 74cb541..d6c3742 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -4,6 +4,7 @@ import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketListenerCommon; import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.item.ItemStack; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; @@ -11,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; @@ -28,32 +28,33 @@ public final class ClientItemLorePacketBridge extends PacketListenerAbstract { private static final int CARRIED_ITEM_SLOT = -1; private final Plugin plugin; - private PacketListenerCommon listenerHandle; + private PacketListenerCommon registeredListener; public ClientItemLorePacketBridge(Plugin plugin) { + super(); this.plugin = plugin; } public void enable() { - if (listenerHandle != null) return; + if (registeredListener != null) return; if (PacketEvents.getAPI() == null) { log.warn("PacketEvents API is unavailable; client item lore bridge was not registered"); return; } - this.listenerHandle = PacketEvents.getAPI().getEventManager().registerListener(this); + this.registeredListener = PacketEvents.getAPI().getEventManager().registerListener(this); log.info("Enabled PacketEvents client item lore bridge"); } public void disable() { - if (listenerHandle == null) return; + if (registeredListener == null) return; if (PacketEvents.getAPI() == null) { - this.listenerHandle = null; + this.registeredListener = null; return; } - PacketEvents.getAPI().getEventManager().unregisterListener(listenerHandle); - this.listenerHandle = null; + PacketEvents.getAPI().getEventManager().unregisterListener(registeredListener); + this.registeredListener = null; } @Override @@ -73,7 +74,7 @@ public void onPacketSend(PacketSendEvent event) { private void handleSetSlot(PacketSendEvent event, Player player) { WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event); - com.github.retrooper.packetevents.protocol.item.ItemStack updatedItem = applyLore( + ItemStack updatedItem = applyLore( player, wrapper.getWindowId(), wrapper.getSlot(), @@ -88,11 +89,11 @@ private void handleSetSlot(PacketSendEvent event, Player player) { private void handleWindowItems(PacketSendEvent event, Player player) { WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); - List items = wrapper.getItems(); - List modifiedItems = new ArrayList<>(items.size()); + List items = wrapper.getItems(); + List modifiedItems = new ArrayList<>(items.size()); for (int slot = 0; slot < items.size(); slot++) { - com.github.retrooper.packetevents.protocol.item.ItemStack originalItem = items.get(slot); - com.github.retrooper.packetevents.protocol.item.ItemStack updatedItem = applyLore( + ItemStack originalItem = items.get(slot); + ItemStack updatedItem = applyLore( player, wrapper.getWindowId(), slot, @@ -102,9 +103,9 @@ private void handleWindowItems(PacketSendEvent event, Player player) { } wrapper.setItems(modifiedItems); - Optional carriedItem = wrapper.getCarriedItem(); + Optional carriedItem = wrapper.getCarriedItem(); if (carriedItem.isPresent()) { - com.github.retrooper.packetevents.protocol.item.ItemStack updatedCarriedItem = applyLore( + ItemStack updatedCarriedItem = applyLore( player, wrapper.getWindowId(), CARRIED_ITEM_SLOT, @@ -116,15 +117,15 @@ private void handleWindowItems(PacketSendEvent event, Player player) { } } - private com.github.retrooper.packetevents.protocol.item.ItemStack applyLore( + private ItemStack applyLore( Player player, int windowId, int slot, - com.github.retrooper.packetevents.protocol.item.ItemStack packetItem + ItemStack packetItem ) { if (packetItem == null) return null; - ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); + org.bukkit.inventory.ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); if (bukkitItem == null || bukkitItem.getType().isAir()) return null; List originalLore = readLore(bukkitItem); @@ -136,7 +137,7 @@ private com.github.retrooper.packetevents.protocol.item.ItemStack applyLore( return packetItem; } - ItemStack clientItem = bukkitItem.clone(); + org.bukkit.inventory.ItemStack clientItem = bukkitItem.clone(); ItemMeta meta = clientItem.getItemMeta(); if (meta == null) { log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType()); @@ -148,7 +149,7 @@ private com.github.retrooper.packetevents.protocol.item.ItemStack applyLore( return SpigotConversionUtil.fromBukkitItemStack(clientItem); } - private List readLore(ItemStack itemStack) { + private List readLore(org.bukkit.inventory.ItemStack itemStack) { ItemMeta meta = itemStack.getItemMeta(); if (meta == null) { return List.of(); From 64e73da6a1ff9cbf5040edf84008d6cdfc3354f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:03:05 +0000 Subject: [PATCH 10/18] docs: clarify direct PacketEvents bridge Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/d295e053-2e1a-41c9-96a5-35b0484b0876 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../negative/engine/paper/event/ClientItemLorePacketBridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index d6c3742..42ae57d 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -20,7 +20,7 @@ import java.util.Optional; /** - * PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. + * Direct PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. */ @Slf4j public final class ClientItemLorePacketBridge extends PacketListenerAbstract { From 416bbc06bb92e72b504ab2464a18e582efb19402 Mon Sep 17 00:00:00 2001 From: Eric <60104846+ericlmao@users.noreply.github.com> Date: Fri, 3 Apr 2026 20:54:41 -0300 Subject: [PATCH 11/18] Update paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../engine/paper/event/ClientItemLorePacketBridge.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 42ae57d..7bc8de4 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -134,14 +134,14 @@ private ItemStack applyLore( List lore = event.getLore(); if (lore.equals(originalLore)) { - return packetItem; + return null; } org.bukkit.inventory.ItemStack clientItem = bukkitItem.clone(); ItemMeta meta = clientItem.getItemMeta(); if (meta == null) { log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType()); - return packetItem; + return null; } meta.lore(lore); From f3377e3114ad68106e6682a04cfe2fb2119e6d46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:00:30 +0000 Subject: [PATCH 12/18] fix: lazily load PacketEvents lore bridge Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/9184cf9a-05f1-48d2-bf80-cb7d6d220afb Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../paper/event/ClientItemLoreBridge.java | 8 + .../event/ClientItemLorePacketBridge.java | 161 ++++------------- ...acketEventsClientItemLorePacketBridge.java | 163 ++++++++++++++++++ 3 files changed, 202 insertions(+), 130 deletions(-) create mode 100644 paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java create mode 100644 paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java new file mode 100644 index 0000000..d8670d3 --- /dev/null +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java @@ -0,0 +1,8 @@ +package games.negative.engine.paper.event; + +interface ClientItemLoreBridge { + + void enable(); + + void disable(); +} diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 7bc8de4..944f93d 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -1,161 +1,62 @@ package games.negative.engine.paper.event; -import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketListenerCommon; -import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.protocol.item.ItemStack; -import com.github.retrooper.packetevents.protocol.packettype.PacketType; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; -import io.github.retrooper.packetevents.util.SpigotConversionUtil; import lombok.extern.slf4j.Slf4j; -import net.kyori.adventure.text.Component; -import org.bukkit.entity.Player; -import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import org.bukkit.plugin.PluginManager; /** - * Direct PacketEvents bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. + * Optional loader for PacketEvents-backed client item lore handling. */ @Slf4j -public final class ClientItemLorePacketBridge extends PacketListenerAbstract { +public final class ClientItemLorePacketBridge { - private static final int CARRIED_ITEM_SLOT = -1; + private static final String PACKET_EVENTS_CLASS = "com.github.retrooper.packetevents.PacketEvents"; + private static final String IMPLEMENTATION_CLASS = "games.negative.engine.paper.event.PacketEventsClientItemLorePacketBridge"; private final Plugin plugin; - private PacketListenerCommon registeredListener; + private ClientItemLoreBridge delegate; public ClientItemLorePacketBridge(Plugin plugin) { - super(); this.plugin = plugin; } public void enable() { - if (registeredListener != null) return; - if (PacketEvents.getAPI() == null) { - log.warn("PacketEvents API is unavailable; client item lore bridge was not registered"); - return; - } - - this.registeredListener = PacketEvents.getAPI().getEventManager().registerListener(this); - log.info("Enabled PacketEvents client item lore bridge"); - } - - public void disable() { - if (registeredListener == null) return; - if (PacketEvents.getAPI() == null) { - this.registeredListener = null; - return; - } + if (delegate != null || !isPacketEventsPresent()) return; - PacketEvents.getAPI().getEventManager().unregisterListener(registeredListener); - this.registeredListener = null; - } - - @Override - public void onPacketSend(PacketSendEvent event) { - if (ClientItemLoreEvent.getHandlerList().getRegisteredListeners().length == 0) return; - if (!(event.getPlayer() instanceof Player player)) return; - - if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { - handleSetSlot(event, player); - return; - } - - if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) { - handleWindowItems(event, player); - } - } + try { + Class implementationClass = Class.forName(IMPLEMENTATION_CLASS, true, plugin.getClass().getClassLoader()); + Object instance = implementationClass.getConstructor(Plugin.class).newInstance(plugin); - private void handleSetSlot(PacketSendEvent event, Player player) { - WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event); - ItemStack updatedItem = applyLore( - player, - wrapper.getWindowId(), - wrapper.getSlot(), - wrapper.getItem() - ); + if (!(instance instanceof ClientItemLoreBridge bridge)) { + log.warn("PacketEvents client item lore bridge implementation does not implement ClientItemLoreBridge"); + return; + } - if (updatedItem != null) { - wrapper.setItem(updatedItem); + bridge.enable(); + this.delegate = bridge; + } catch (ReflectiveOperationException | LinkageError exception) { + log.warn("Failed to initialize PacketEvents client item lore bridge", exception); } } - private void handleWindowItems(PacketSendEvent event, Player player) { - WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); - - List items = wrapper.getItems(); - List modifiedItems = new ArrayList<>(items.size()); - for (int slot = 0; slot < items.size(); slot++) { - ItemStack originalItem = items.get(slot); - ItemStack updatedItem = applyLore( - player, - wrapper.getWindowId(), - slot, - originalItem - ); - modifiedItems.add(updatedItem == null ? originalItem : updatedItem); - } - wrapper.setItems(modifiedItems); + public void disable() { + if (delegate == null) return; - Optional carriedItem = wrapper.getCarriedItem(); - if (carriedItem.isPresent()) { - ItemStack updatedCarriedItem = applyLore( - player, - wrapper.getWindowId(), - CARRIED_ITEM_SLOT, - carriedItem.get() - ); - if (updatedCarriedItem != null) { - wrapper.setCarriedItem(updatedCarriedItem); - } - } + delegate.disable(); + delegate = null; } - private ItemStack applyLore( - Player player, - int windowId, - int slot, - ItemStack packetItem - ) { - if (packetItem == null) return null; - - org.bukkit.inventory.ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); - if (bukkitItem == null || bukkitItem.getType().isAir()) return null; - - List originalLore = readLore(bukkitItem); - ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem); - plugin.getServer().getPluginManager().callEvent(event); - - List lore = event.getLore(); - if (lore.equals(originalLore)) { - return null; + private boolean isPacketEventsPresent() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); + if (pluginManager.getPlugin("packetevents") == null && pluginManager.getPlugin("PacketEvents") == null) { + return false; } - org.bukkit.inventory.ItemStack clientItem = bukkitItem.clone(); - ItemMeta meta = clientItem.getItemMeta(); - if (meta == null) { - log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType()); - return null; + try { + Class.forName(PACKET_EVENTS_CLASS, false, plugin.getClass().getClassLoader()); + return true; + } catch (ClassNotFoundException exception) { + return false; } - - meta.lore(lore); - clientItem.setItemMeta(meta); - return SpigotConversionUtil.fromBukkitItemStack(clientItem); - } - - private List readLore(org.bukkit.inventory.ItemStack itemStack) { - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) { - return List.of(); - } - - List lore = meta.lore(); - return lore == null ? List.of() : List.copyOf(lore); } } diff --git a/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java new file mode 100644 index 0000000..0db0bb8 --- /dev/null +++ b/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java @@ -0,0 +1,163 @@ +package games.negative.engine.paper.event; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketListenerCommon; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import lombok.extern.slf4j.Slf4j; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * PacketEvents-specific bridge that fires {@link ClientItemLoreEvent} for outgoing item packets. + */ +@Slf4j +public final class PacketEventsClientItemLorePacketBridge extends PacketListenerAbstract implements ClientItemLoreBridge { + + private static final int CARRIED_ITEM_SLOT = -1; + + private final Plugin plugin; + private PacketListenerCommon registeredListener; + + public PacketEventsClientItemLorePacketBridge(Plugin plugin) { + super(); + this.plugin = plugin; + } + + @Override + public void enable() { + if (registeredListener != null) return; + if (PacketEvents.getAPI() == null) { + log.warn("PacketEvents API is unavailable; client item lore bridge was not registered"); + return; + } + + this.registeredListener = PacketEvents.getAPI().getEventManager().registerListener(this); + log.info("Enabled PacketEvents client item lore bridge"); + } + + @Override + public void disable() { + if (registeredListener == null) return; + if (PacketEvents.getAPI() == null) { + this.registeredListener = null; + return; + } + + PacketEvents.getAPI().getEventManager().unregisterListener(registeredListener); + this.registeredListener = null; + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (ClientItemLoreEvent.getHandlerList().getRegisteredListeners().length == 0) return; + if (!(event.getPlayer() instanceof Player player)) return; + + if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { + handleSetSlot(event, player); + return; + } + + if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) { + handleWindowItems(event, player); + } + } + + private void handleSetSlot(PacketSendEvent event, Player player) { + WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event); + ItemStack updatedItem = applyLore( + player, + wrapper.getWindowId(), + wrapper.getSlot(), + wrapper.getItem() + ); + + if (updatedItem != null) { + wrapper.setItem(updatedItem); + } + } + + private void handleWindowItems(PacketSendEvent event, Player player) { + WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); + + List items = wrapper.getItems(); + List modifiedItems = new ArrayList<>(items.size()); + for (int slot = 0; slot < items.size(); slot++) { + ItemStack originalItem = items.get(slot); + ItemStack updatedItem = applyLore( + player, + wrapper.getWindowId(), + slot, + originalItem + ); + modifiedItems.add(updatedItem == null ? originalItem : updatedItem); + } + wrapper.setItems(modifiedItems); + + Optional carriedItem = wrapper.getCarriedItem(); + if (carriedItem.isPresent()) { + ItemStack updatedCarriedItem = applyLore( + player, + wrapper.getWindowId(), + CARRIED_ITEM_SLOT, + carriedItem.get() + ); + if (updatedCarriedItem != null) { + wrapper.setCarriedItem(updatedCarriedItem); + } + } + } + + private ItemStack applyLore( + Player player, + int windowId, + int slot, + ItemStack packetItem + ) { + if (packetItem == null) return null; + + org.bukkit.inventory.ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); + if (bukkitItem == null || bukkitItem.getType().isAir()) return null; + + List originalLore = readLore(bukkitItem); + ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem); + plugin.getServer().getPluginManager().callEvent(event); + + List lore = event.getLore(); + if (lore.equals(originalLore)) { + return null; + } + + org.bukkit.inventory.ItemStack clientItem = bukkitItem.clone(); + ItemMeta meta = clientItem.getItemMeta(); + if (meta == null) { + log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType()); + return null; + } + + meta.lore(lore); + clientItem.setItemMeta(meta); + return SpigotConversionUtil.fromBukkitItemStack(clientItem); + } + + private List readLore(org.bukkit.inventory.ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + return List.of(); + } + + List lore = meta.lore(); + return lore == null ? List.of() : List.copyOf(lore); + } +} From 6db09c629c3347deae9a97cb4e6dd4aed05e9a91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:04:55 +0000 Subject: [PATCH 13/18] refactor: make PacketEvents bridge optional again Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/9184cf9a-05f1-48d2-bf80-cb7d6d220afb Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../paper/event/ClientItemLoreBridge.java | 2 +- .../paper/event/ClientItemLoreEvent.java | 8 ++++++ .../event/ClientItemLorePacketBridge.java | 25 +++++++++++++++---- ...acketEventsClientItemLorePacketBridge.java | 17 ++----------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java index d8670d3..3014209 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreBridge.java @@ -1,6 +1,6 @@ package games.negative.engine.paper.event; -interface ClientItemLoreBridge { +public interface ClientItemLoreBridge { void enable(); diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java index c604ba1..0e1ad96 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLoreEvent.java @@ -22,6 +22,7 @@ public final class ClientItemLoreEvent extends PlayerEvent { private final int slot; private final ItemStack originalItem; private List lore; + private boolean modified; public ClientItemLoreEvent(Player player, int windowId, int slot, ItemStack item) { super(player); @@ -56,14 +57,21 @@ public List getLore() { public void setLore(List lore) { this.lore = lore == null ? new ArrayList<>() : new ArrayList<>(lore); + this.modified = true; } public void addLoreLine(Component line) { this.lore.add(line); + this.modified = true; } public void clearLore() { this.lore.clear(); + this.modified = true; + } + + boolean isModified() { + return modified; } @Override diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 944f93d..25a7fee 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -12,6 +12,7 @@ public final class ClientItemLorePacketBridge { private static final String PACKET_EVENTS_CLASS = "com.github.retrooper.packetevents.PacketEvents"; private static final String IMPLEMENTATION_CLASS = "games.negative.engine.paper.event.PacketEventsClientItemLorePacketBridge"; + private static final String[] PACKET_EVENTS_PLUGIN_NAMES = {"packetevents", "PacketEvents"}; private final Plugin plugin; private ClientItemLoreBridge delegate; @@ -21,13 +22,17 @@ public ClientItemLorePacketBridge(Plugin plugin) { } public void enable() { - if (delegate != null || !isPacketEventsPresent()) return; + if (delegate != null) return; + if (!isPacketEventsPresent()) { + log.info("PacketEvents not present; client item lore bridge will remain disabled"); + return; + } try { Class implementationClass = Class.forName(IMPLEMENTATION_CLASS, true, plugin.getClass().getClassLoader()); - Object instance = implementationClass.getConstructor(Plugin.class).newInstance(plugin); + Object implementation = implementationClass.getConstructor(Plugin.class).newInstance(plugin); - if (!(instance instanceof ClientItemLoreBridge bridge)) { + if (!(implementation instanceof ClientItemLoreBridge bridge)) { log.warn("PacketEvents client item lore bridge implementation does not implement ClientItemLoreBridge"); return; } @@ -47,8 +52,7 @@ public void disable() { } private boolean isPacketEventsPresent() { - PluginManager pluginManager = plugin.getServer().getPluginManager(); - if (pluginManager.getPlugin("packetevents") == null && pluginManager.getPlugin("PacketEvents") == null) { + if (getPacketEventsPlugin() == null) { return false; } @@ -59,4 +63,15 @@ private boolean isPacketEventsPresent() { return false; } } + + private Plugin getPacketEventsPlugin() { + PluginManager pluginManager = plugin.getServer().getPluginManager(); + for (String pluginName : PACKET_EVENTS_PLUGIN_NAMES) { + Plugin packetEventsPlugin = pluginManager.getPlugin(pluginName); + if (packetEventsPlugin != null) { + return packetEventsPlugin; + } + } + return null; + } } diff --git a/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java index 0db0bb8..2db808f 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/PacketEventsClientItemLorePacketBridge.java @@ -61,7 +61,6 @@ public void disable() { @Override public void onPacketSend(PacketSendEvent event) { - if (ClientItemLoreEvent.getHandlerList().getRegisteredListeners().length == 0) return; if (!(event.getPlayer() instanceof Player player)) return; if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { @@ -130,12 +129,10 @@ private ItemStack applyLore( org.bukkit.inventory.ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem); if (bukkitItem == null || bukkitItem.getType().isAir()) return null; - List originalLore = readLore(bukkitItem); ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem); plugin.getServer().getPluginManager().callEvent(event); - List lore = event.getLore(); - if (lore.equals(originalLore)) { + if (!event.isModified()) { return null; } @@ -146,18 +143,8 @@ private ItemStack applyLore( return null; } - meta.lore(lore); + meta.lore(event.getLore()); clientItem.setItemMeta(meta); return SpigotConversionUtil.fromBukkitItemStack(clientItem); } - - private List readLore(org.bukkit.inventory.ItemStack itemStack) { - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) { - return List.of(); - } - - List lore = meta.lore(); - return lore == null ? List.of() : List.copyOf(lore); - } } From b587bdb40aaf92d66e52303241352aa611d2c473 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:13:04 +0000 Subject: [PATCH 14/18] refactor: remove PacketEvents reflection loader Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/3328740a-1875-4942-b701-9c0abcbefeb1 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../event/ClientItemLorePacketBridge.java | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 25a7fee..9c31497 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -10,8 +10,6 @@ @Slf4j public final class ClientItemLorePacketBridge { - private static final String PACKET_EVENTS_CLASS = "com.github.retrooper.packetevents.PacketEvents"; - private static final String IMPLEMENTATION_CLASS = "games.negative.engine.paper.event.PacketEventsClientItemLorePacketBridge"; private static final String[] PACKET_EVENTS_PLUGIN_NAMES = {"packetevents", "PacketEvents"}; private final Plugin plugin; @@ -29,17 +27,10 @@ public void enable() { } try { - Class implementationClass = Class.forName(IMPLEMENTATION_CLASS, true, plugin.getClass().getClassLoader()); - Object implementation = implementationClass.getConstructor(Plugin.class).newInstance(plugin); - - if (!(implementation instanceof ClientItemLoreBridge bridge)) { - log.warn("PacketEvents client item lore bridge implementation does not implement ClientItemLoreBridge"); - return; - } - + ClientItemLoreBridge bridge = new PacketEventsClientItemLorePacketBridge(plugin); bridge.enable(); this.delegate = bridge; - } catch (ReflectiveOperationException | LinkageError exception) { + } catch (LinkageError exception) { log.warn("Failed to initialize PacketEvents client item lore bridge", exception); } } @@ -52,16 +43,7 @@ public void disable() { } private boolean isPacketEventsPresent() { - if (getPacketEventsPlugin() == null) { - return false; - } - - try { - Class.forName(PACKET_EVENTS_CLASS, false, plugin.getClass().getClassLoader()); - return true; - } catch (ClassNotFoundException exception) { - return false; - } + return getPacketEventsPlugin() != null; } private Plugin getPacketEventsPlugin() { From b2d4fc87d1ff7848565bdfd93879d0c8c6162ce1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:14:49 +0000 Subject: [PATCH 15/18] chore: clarify PacketEvents bridge linkage logs Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/3328740a-1875-4942-b701-9c0abcbefeb1 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../engine/paper/event/ClientItemLorePacketBridge.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 9c31497..2c0c23e 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -30,8 +30,10 @@ public void enable() { ClientItemLoreBridge bridge = new PacketEventsClientItemLorePacketBridge(plugin); bridge.enable(); this.delegate = bridge; + } catch (NoClassDefFoundError exception) { + log.warn("PacketEvents is installed but required runtime classes are unavailable; client item lore bridge was not enabled", exception); } catch (LinkageError exception) { - log.warn("Failed to initialize PacketEvents client item lore bridge", exception); + log.warn("Failed to initialize PacketEvents client item lore bridge due to a linkage problem", exception); } } From 2b8fd80faa6e1a3010f918a56d471a149f0c5fb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:16:18 +0000 Subject: [PATCH 16/18] chore: refine PacketEvents loader warning Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/3328740a-1875-4942-b701-9c0abcbefeb1 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../negative/engine/paper/event/ClientItemLorePacketBridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 2c0c23e..2785af5 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -31,7 +31,7 @@ public void enable() { bridge.enable(); this.delegate = bridge; } catch (NoClassDefFoundError exception) { - log.warn("PacketEvents is installed but required runtime classes are unavailable; client item lore bridge was not enabled", exception); + log.warn("Failed to initialize PacketEvents client item lore bridge; required classes are unavailable", exception); } catch (LinkageError exception) { log.warn("Failed to initialize PacketEvents client item lore bridge due to a linkage problem", exception); } From 972a9c496afdece536cf7c95ca2f2b5830276a27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:16:57 +0000 Subject: [PATCH 17/18] chore: clarify optional PacketEvents warning Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/3328740a-1875-4942-b701-9c0abcbefeb1 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- .../negative/engine/paper/event/ClientItemLorePacketBridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java index 2785af5..22051f0 100644 --- a/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java +++ b/paper/src/main/java/games/negative/engine/paper/event/ClientItemLorePacketBridge.java @@ -31,7 +31,7 @@ public void enable() { bridge.enable(); this.delegate = bridge; } catch (NoClassDefFoundError exception) { - log.warn("Failed to initialize PacketEvents client item lore bridge; required classes are unavailable", exception); + log.warn("Failed to initialize optional PacketEvents client item lore bridge; runtime classes are unavailable", exception); } catch (LinkageError exception) { log.warn("Failed to initialize PacketEvents client item lore bridge due to a linkage problem", exception); } From b8c10bec846bf54858ae74128fe2cb4cbc44efff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:23:16 +0000 Subject: [PATCH 18/18] chore: bump paper module version to 1.2.0 Agent-Logs-Url: https://github.com/negative-games/plugin-engine/sessions/3a26caa5-0cd4-4424-bdcf-3b45a6d52b26 Co-authored-by: ericlmao <60104846+ericlmao@users.noreply.github.com> --- paper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index 50bf74b..aa9e3b5 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -6,7 +6,7 @@ plugins { var id = "plugin-engine-paper" var domain = "games.negative.engine" -var apiVersion = "1.1.0" +var apiVersion = "1.2.0" repositories { mavenCentral()