diff --git a/pom.xml b/pom.xml index d209470..c580a63 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,12 @@ 5.14.0 test + + org.junit.platform + junit-platform-launcher + 1.14.0 + test + org.mockbukkit.mockbukkit mockbukkit-v1.21 diff --git a/src/main/java/net/tfminecraft/companionpets/PetsPlugin.java b/src/main/java/net/tfminecraft/companionpets/PetsPlugin.java index f7b3dd7..159b2c3 100644 --- a/src/main/java/net/tfminecraft/companionpets/PetsPlugin.java +++ b/src/main/java/net/tfminecraft/companionpets/PetsPlugin.java @@ -252,7 +252,8 @@ private void reloadSettings(CommandSender sender) { return; } for (var player : Bukkit.getOnlinePlayers()) { - if (player.getOpenInventory().getTopInventory().getHolder() instanceof MenuHolder || player.getOpenInventory().getTopInventory().getHolder() instanceof net.tfminecraft.companionpets.staff.StaffMenuHolder) player.closeInventory(); + var holder = player.getOpenInventory().getTopInventory().getHolder(false); + if (holder instanceof MenuHolder || holder instanceof net.tfminecraft.companionpets.staff.StaffMenuHolder) player.closeInventory(); } actions.clearInteractions(); actions.holograms().clear(); diff --git a/src/main/java/net/tfminecraft/companionpets/gui/PetMenus.java b/src/main/java/net/tfminecraft/companionpets/gui/PetMenus.java index e3621a6..5306ac3 100644 --- a/src/main/java/net/tfminecraft/companionpets/gui/PetMenus.java +++ b/src/main/java/net/tfminecraft/companionpets/gui/PetMenus.java @@ -77,7 +77,7 @@ public void openCare(org.bukkit.entity.Player player, Pet pet, boolean petHouseB } public void refreshCare(org.bukkit.entity.Player player, Pet pet) { - if (!(player.getOpenInventory().getTopInventory().getHolder() instanceof MenuHolder holder) + if (!(player.getOpenInventory().getTopInventory().getHolder(false) instanceof MenuHolder holder) || holder.kind() != MenuHolder.Kind.CARE || !pet.id().equals(holder.petId())) { return; @@ -87,7 +87,7 @@ public void refreshCare(org.bukkit.entity.Player player, Pet pet) { private void fillCare(org.bukkit.entity.Player player, Inventory inventory, Pet pet) { fillProfile(inventory, pet, runtime.owns(pet, player), - inventory.getHolder() instanceof MenuHolder holder && holder.petHouseBack()); + inventory.getHolder(false) instanceof MenuHolder holder && holder.petHouseBack()); } public void fillInformation(Inventory inventory, Pet pet) { @@ -334,7 +334,7 @@ public void openTricks(org.bukkit.entity.Player player, Pet pet, String word, in private void inheritPetHouseNavigation(org.bukkit.entity.Player player, MenuHolder holder) { Inventory previousInventory = player.getOpenInventory().getTopInventory(); - if (previousInventory != null && previousInventory.getHolder() instanceof MenuHolder previous + if (previousInventory != null && previousInventory.getHolder(false) instanceof MenuHolder previous && holder.petId().equals(previous.petId())) { holder.petHouseBack(previous.petHouseBack()); holder.petHousePage(previous.petHousePage()); diff --git a/src/main/java/net/tfminecraft/companionpets/listen/PetListener.java b/src/main/java/net/tfminecraft/companionpets/listen/PetListener.java index 50d43f6..592fe4c 100644 --- a/src/main/java/net/tfminecraft/companionpets/listen/PetListener.java +++ b/src/main/java/net/tfminecraft/companionpets/listen/PetListener.java @@ -138,7 +138,7 @@ public void onChat(AsyncPlayerChatEvent event) { @EventHandler public void onClick(InventoryClickEvent event) { - InventoryHolder holder = event.getView().getTopInventory().getHolder(); + InventoryHolder holder = event.getView().getTopInventory().getHolder(false); if (!(holder instanceof MenuHolder menu) || !(event.getWhoClicked() instanceof Player player)) { return; } @@ -152,14 +152,14 @@ public void onClick(InventoryClickEvent event) { @EventHandler public void onDrag(InventoryDragEvent event) { - if (event.getView().getTopInventory().getHolder() instanceof MenuHolder) { + if (event.getView().getTopInventory().getHolder(false) instanceof MenuHolder) { event.setCancelled(true); } } @EventHandler public void onClose(InventoryCloseEvent event) { - if (event.getInventory().getHolder() instanceof MenuHolder menu + if (event.getInventory().getHolder(false) instanceof MenuHolder menu && menu.kind() == MenuHolder.Kind.TRICK && !menu.navigating() && event.getPlayer() instanceof Player player) { diff --git a/src/main/java/net/tfminecraft/companionpets/ownership/CharacterLifecycle.java b/src/main/java/net/tfminecraft/companionpets/ownership/CharacterLifecycle.java index 007b619..1358314 100644 --- a/src/main/java/net/tfminecraft/companionpets/ownership/CharacterLifecycle.java +++ b/src/main/java/net/tfminecraft/companionpets/ownership/CharacterLifecycle.java @@ -20,7 +20,7 @@ public void end(CharacterSessionEndEvent event) { Player player = Bukkit.getPlayer(event.getSession().ref().ownerId()); if (player != null) { actions.ownerSessionChanged(player); - if (player.getOpenInventory().getTopInventory().getHolder() instanceof MenuHolder) player.closeInventory(); + if (player.getOpenInventory().getTopInventory().getHolder(false) instanceof MenuHolder) player.closeInventory(); } runtime.sessions().clearPlayer(event.getSession().ref().ownerId()); } diff --git a/src/main/java/net/tfminecraft/companionpets/staff/StaffMenus.java b/src/main/java/net/tfminecraft/companionpets/staff/StaffMenus.java index c554d2b..ae3ac34 100644 --- a/src/main/java/net/tfminecraft/companionpets/staff/StaffMenus.java +++ b/src/main/java/net/tfminecraft/companionpets/staff/StaffMenus.java @@ -101,7 +101,7 @@ private static ItemStack named(ItemStack source, String title, String... lore) { } @EventHandler public void onClick(InventoryClickEvent event) { - if (!(event.getView().getTopInventory().getHolder() instanceof StaffMenuHolder holder)) return; + if (!(event.getView().getTopInventory().getHolder(false) instanceof StaffMenuHolder holder)) return; event.setCancelled(true); if (!(event.getWhoClicked() instanceof Player player) || !holder.viewer().equals(player.getUniqueId())) return; if (event.getClickedInventory() != event.getView().getTopInventory() || !(event.getClick().isLeftClick() || event.getClick().isRightClick()) @@ -109,6 +109,6 @@ private static ItemStack named(ItemStack source, String title, String... lore) { if (allowed(player, "list")) holder.click(event.getSlot(), event.getClick().isRightClick()); } @EventHandler public void onDrag(InventoryDragEvent event) { - if (event.getView().getTopInventory().getHolder() instanceof StaffMenuHolder) event.setCancelled(true); + if (event.getView().getTopInventory().getHolder(false) instanceof StaffMenuHolder) event.setCancelled(true); } } diff --git a/src/test/java/net/tfminecraft/companionpets/testutil/HolderLookupPatch.java b/src/test/java/net/tfminecraft/companionpets/testutil/HolderLookupPatch.java new file mode 100644 index 0000000..52367a9 --- /dev/null +++ b/src/test/java/net/tfminecraft/companionpets/testutil/HolderLookupPatch.java @@ -0,0 +1,91 @@ +package net.tfminecraft.companionpets.testutil; + +import static net.bytebuddy.matcher.ElementMatchers.is; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import java.util.concurrent.atomic.AtomicBoolean; + +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.utility.JavaModule; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.mockbukkit.mockbukkit.inventory.InventoryMock; + +/** + * MockBukkit throws on Paper's {@code getHolder(false)}. Its inventories hold no block snapshot, + * so the patch answers with the plain holder, which is what Paper returns for plugin GUIs. + */ +public final class HolderLookupPatch implements LauncherSessionListener { + private static boolean installed; + + @Override + public void launcherSessionOpened(LauncherSession session) { + install(); + } + + /** Patches {@link InventoryMock#getHolder(boolean)} once per test JVM. */ + public static synchronized void install() { + if (installed) { + return; + } + AtomicBoolean patched = new AtomicBoolean(); + AtomicBoolean rejected = new AtomicBoolean(); + new AgentBuilder.Default() + .disableClassFormatChanges() + .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) + .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE) + .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) + .with(new AgentBuilder.Listener.Adapter() { + @Override + public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module, + boolean loaded, DynamicType dynamicType) { + if (type.getName().equals(InventoryMock.class.getName())) { + patched.set(true); + } + } + + // The JVM can still reject the new bytes after onTransformation. + @Override + public void onError(String typeName, ClassLoader loader, JavaModule module, boolean loaded, + Throwable throwable) { + if (typeName.equals(InventoryMock.class.getName())) { + rejected.set(true); + } + } + }) + .type(is(InventoryMock.class)) + .transform((builder, type, loader, module, domain) -> builder.visit(Advice.to(PlainHolder.class) + .on(named("getHolder").and(takesArguments(boolean.class))))) + .installOn(ByteBuddyAgent.install()); + // A failed retransformation only reaches the listeners, so check it took before marking it done. + if (!patched.get() || rejected.get()) { + throw new IllegalStateException("Could not patch InventoryMock#getHolder(boolean)"); + } + installed = true; + } + + /** Skips the throwing body and returns {@code getHolder()} instead. */ + static final class PlainHolder { + private PlainHolder() { + } + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean skip() { + return true; + } + + @Advice.OnMethodExit + static void exit(@Advice.This Inventory inventory, + @Advice.Return(readOnly = false) InventoryHolder holder) { + holder = inventory.getHolder(); + } + } +} diff --git a/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 0000000..390a242 --- /dev/null +++ b/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +net.tfminecraft.companionpets.testutil.HolderLookupPatch