diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/TalismanActivateEvent.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/TalismanActivateEvent.java new file mode 100644 index 000000000..775691a0a --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/TalismanActivateEvent.java @@ -0,0 +1,96 @@ +package io.github.thebusybiscuit.slimefun4.api.events; + +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +import io.github.thebusybiscuit.slimefun4.implementation.items.magical.talismans.Talisman; + +/** + * This {@link PlayerEvent} is called when a {@link Player} activates a {@link Talisman} + * + * @author cworldstar + */ +public class TalismanActivateEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final Talisman talisman; + private final ItemStack talismanItemStack; + private boolean preventConsumption = false; + private boolean cancelled = false; + + /** + * @param player + * The {@link Player} who activated the talisman. + * + * @param talisman + * The {@link Talisman} that was activated. + * + * @param talismanItem + * The {@link ItemStack} corresponding to the Talisman. + */ + @ParametersAreNonnullByDefault + public TalismanActivateEvent(Player player, Talisman talisman, ItemStack talismanItem) { + super(player); + this.talisman = talisman; + this.talismanItemStack = talismanItem; + } + + /** + * @return The {@link Talisman} used. + */ + public @Nonnull Talisman getTalisman() { + return this.talisman; + } + + /** + * @return The {@link ItemStack} of the used {@link Talisman}. + */ + public @Nonnull ItemStack getTalismanItem() { + return this.talismanItemStack; + } + + /** + * Only applies if {@link Talisman#isConsumable()} is true. + * Defaults to false. + * + * @return Whether the {@link ItemStack} should not be consumed. + */ + public boolean preventsConsumption() { + return this.preventConsumption; + } + + /** + * Only applies if {@link Talisman#isConsumable()} is true. + * + * @param preventConsumption + * Whether the {@link ItemStack} should not be consumed. + */ + public void setPreventConsumption(boolean preventConsumption) { + this.preventConsumption = preventConsumption; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public @Nonnull HandlerList getHandlers() { + return getHandlerList(); + } + + public static @Nonnull HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/magical/talismans/Talisman.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/magical/talismans/Talisman.java index 7ecc6f6fa..9f97f1989 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/magical/talismans/Talisman.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/magical/talismans/Talisman.java @@ -10,6 +10,7 @@ import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; @@ -27,6 +28,7 @@ import org.bukkit.potion.PotionEffect; import io.github.bakedlibs.dough.items.CustomItemStack; import io.github.bakedlibs.dough.items.ItemUtils; +import io.github.thebusybiscuit.slimefun4.api.events.TalismanActivateEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; @@ -186,11 +188,15 @@ public class Talisman extends SlimefunItem { return false; } } else { - ItemStack enderTalisman = talisman.getEnderVariant(); + SlimefunItemStack enderTalismanItem = talisman.getEnderVariant(); + if (enderTalismanItem == null) { + return false; + } - if (SlimefunUtils.containsSimilarItem(p.getEnderChest(), enderTalisman, true)) { + EnderTalisman enderTalisman = enderTalismanItem.getItem(EnderTalisman.class); + if (enderTalisman != null && SlimefunUtils.containsSimilarItem(p.getEnderChest(), enderTalismanItem, true)) { if (talisman.canUse(p, true)) { - activateTalisman(e, p, p.getEnderChest(), talisman, enderTalisman, sendMessage); + activateTalisman(e, p, p.getEnderChest(), enderTalisman, enderTalismanItem, sendMessage); return true; } else { return false; @@ -203,12 +209,19 @@ public class Talisman extends SlimefunItem { @ParametersAreNonnullByDefault private static void activateTalisman(Event e, Player p, Inventory inv, Talisman talisman, ItemStack talismanItem, boolean sendMessage) { - consumeItem(inv, talisman, talismanItem); - applyTalismanEffects(p, talisman); - cancelEvent(e, talisman); + TalismanActivateEvent talismanEvent = new TalismanActivateEvent(p, talisman, talismanItem); + Bukkit.getPluginManager().callEvent(talismanEvent); + if (!talismanEvent.isCancelled()) { + if (!talismanEvent.preventsConsumption()) { + consumeItem(inv, talisman, talismanItem); + } - if (sendMessage) { - talisman.sendMessage(p); + applyTalismanEffects(p, talisman); + cancelEvent(e, talisman); + + if (sendMessage) { + talisman.sendMessage(p); + } } } diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestTalismanActivateEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestTalismanActivateEvent.java new file mode 100644 index 000000000..83b49fa23 --- /dev/null +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestTalismanActivateEvent.java @@ -0,0 +1,177 @@ +package io.github.thebusybiscuit.slimefun4.api.events; + +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.ServerMock; +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.implementation.SlimefunItems; +import io.github.thebusybiscuit.slimefun4.implementation.items.magical.talismans.Talisman; +import io.github.thebusybiscuit.slimefun4.implementation.listeners.TalismanListener; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class TestTalismanActivateEvent { + + private static ServerMock server; + private static Slimefun plugin; + private static Player player; + private static SlimefunItem talisman; + private static SlimefunItem enderTalisman; + + @BeforeAll + public static void load() { + server = MockBukkit.mock(); + plugin = MockBukkit.load(Slimefun.class); + + new TalismanListener(plugin); + + talisman = new Talisman(SlimefunItems.TALISMAN_ANVIL, new ItemStack[] {}, true, false, "anvil"); + talisman.register(plugin); + + enderTalisman = SlimefunItem.getById("ENDER_" + talisman.getId()); + + player = server.addPlayer(); + } + + @AfterAll + public static void unload() { + MockBukkit.unmock(); + } + + void activateAnvilTalisman(boolean enderVariant, boolean inEnderChest) { + player.getInventory().clear(); + player.getEnderChest().clear(); + + ItemStack talismanItem = enderVariant ? enderTalisman.getItem() : talisman.getItem(); + ItemStack breakableItem = new ItemStack(Material.IRON_PICKAXE); + + if (inEnderChest) { + player.getEnderChest().addItem(talismanItem); + } else { + player.getInventory().addItem(talismanItem); + } + + player.getInventory().setItemInMainHand(breakableItem); + + PlayerItemBreakEvent event = new PlayerItemBreakEvent(player, breakableItem); + server.getPluginManager().callEvent(event); + } + + @Test + @DisplayName("Test that TalismanActivateEvent is fired when an anvil talisman activates") + void testEventIsFired() { + // Assert the talisman activates in the inventory + activateAnvilTalisman(false, false); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, ignored -> true); + server.getPluginManager().clearEvents(); + + // Assert the talisman activates in the ender chest + activateAnvilTalisman(true, true); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, ignored -> true); + server.getPluginManager().clearEvents(); + // Assert the normal talisman does not activate in the ender chest + activateAnvilTalisman(false, true); + try { + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, ignored -> true); + } catch (AssertionError ignored) { + return; // This is expected; the event should not have fired + } + server.getPluginManager().clearEvents(); + + // Assert the ender talisman does not activate in the inventory + try { + activateAnvilTalisman(true, false); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, ignored -> true); + } catch (AssertionError ignored) { + return; // This is expected; the event should not have fired + } + server.getPluginManager().clearEvents(); + } + + @Test + @DisplayName("Test that the TalismanActivateEvent has the correct fields") + void testEventFields() { + // Assert the talisman activates in the inventory + activateAnvilTalisman(false, false); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertEquals(talisman, event.getTalisman()); + Assertions.assertEquals(talisman.getItem(), event.getTalismanItem()); + Assertions.assertEquals(player, event.getPlayer()); + return true; + }); + server.getPluginManager().clearEvents(); + + // Assert the talisman activates in the ender chest + activateAnvilTalisman(true, true); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertEquals(enderTalisman, event.getTalisman()); + Assertions.assertEquals(enderTalisman.getItem(), event.getTalismanItem()); + Assertions.assertEquals(player, event.getPlayer()); + return true; + }); + server.getPluginManager().clearEvents(); + } + + @Test + @DisplayName("Test that the TalismanActivateEvent can be cancelled") + void testEventCanBeCancelled() { + server.getPluginManager().registerEvents(new Listener() { + @EventHandler + public void onTalismanActivate(TalismanActivateEvent event) { + event.setCancelled(true); + } + }, plugin); + + // Assert the talisman activates in the inventory + activateAnvilTalisman(false, false); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertTrue(event.isCancelled()); + return true; + }); + server.getPluginManager().clearEvents(); + + // Assert the talisman activates in the ender chest + activateAnvilTalisman(true, true); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertTrue(event.isCancelled()); + return true; + }); + server.getPluginManager().clearEvents(); + } + + @Test + @DisplayName("Test that the TalismanActivateEvent can prevent consumption") + void testEventCanPreventConsumption() { + server.getPluginManager().registerEvents(new Listener() { + @EventHandler + public void onTalismanActivate(TalismanActivateEvent event) { + event.setPreventConsumption(true); + } + }, plugin); + + // Assert the talisman activates in the inventory + activateAnvilTalisman(false, false); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertTrue(event.preventsConsumption()); + return true; + }); + server.getPluginManager().clearEvents(); + + // Assert the talisman activates in the ender chest + activateAnvilTalisman(true, true); + server.getPluginManager().assertEventFired(TalismanActivateEvent.class, event -> { + Assertions.assertTrue(event.preventsConsumption()); + return true; + }); + server.getPluginManager().clearEvents(); + } +}