diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java index d1388a5e5..ae8222c90 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java @@ -527,21 +527,19 @@ public class CommandObject implements DirectorExecutor { return; } - if (WandSVC.isHoldingIrisWand(player)) { - Location[] g = WandSVC.getCuboid(player); + Location[] g = WandSVC.getCuboid(player); - if (g == null) { - return; - } - - if (!here) { - // TODO: WARNING HEIGHT - g[0] = player.getTargetBlock(null, 256).getLocation().clone(); - } else { - g[0] = player.getLocation().getBlock().getLocation().clone().add(0, -1, 0); - } - player.getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1])); + if (g == null) { + return; } + + if (!here) { + // TODO: WARNING HEIGHT + g[0] = player.getTargetBlock(null, 256).getLocation().clone(); + } else { + g[0] = player.getLocation().getBlock().getLocation().clone().add(0, -1, 0); + } + player.getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1])); }); } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java index 3f17536ab..927dbaf06 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java @@ -27,11 +27,9 @@ import org.bukkit.Particle; import org.bukkit.Sound; import org.bukkit.World; import art.arcane.iris.Iris; -import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.edit.DustRevealer; -import art.arcane.iris.core.link.WorldEditLink; import art.arcane.iris.core.wand.WandSelection; import art.arcane.iris.engine.object.IrisObject; import art.arcane.iris.util.project.matter.WorldMatter; @@ -333,26 +331,14 @@ public class WandSVC implements IrisService { } public static Location[] getCuboid(Player p) { - if (isHoldingIrisWand(p)) { + if (isHoldingWand(p)) { return getCuboidFromItem(p.getInventory().getItemInMainHand()); } - if (IrisSettings.get().getWorld().worldEditWandCUI) { - Cuboid c = WorldEditLink.getSelection(p); - - if (c != null) { - return new Location[]{c.getLowerNE(), c.getUpperSW()}; - } - } - return null; } public static boolean isHoldingWand(Player p) { - return isHoldingIrisWand(p) || (IrisSettings.get().getWorld().worldEditWandCUI && WorldEditLink.getSelection(p) != null); - } - - public static boolean isHoldingIrisWand(Player p) { ItemStack is = p.getInventory().getItemInMainHand(); return is != null && isWand(is); } @@ -567,7 +553,7 @@ public class WandSVC implements IrisService { if (e.getHand() != EquipmentSlot.HAND) return; try { - if (isHoldingIrisWand(e.getPlayer())) { + if (isHoldingWand(e.getPlayer())) { activePlayers.put(e.getPlayer().getUniqueId(), e.getPlayer()); if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { e.setCancelled(true); diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java index 35ef6b7f5..57aab383b 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java @@ -123,7 +123,7 @@ public class WandSelection { p.spawnParticle(REDSTONE, x, y, z, 0, 0, 0, 0, 1, new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()), - (float) dist * 3f)); + Math.clamp((float) dist * 3f, 0.01f, 4f))); } private static double square(double value) { diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/service/WandSVCTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/service/WandSVCTest.java new file mode 100644 index 000000000..5c26c1abd --- /dev/null +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/service/WandSVCTest.java @@ -0,0 +1,108 @@ +package art.arcane.iris.core.service; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.link.WorldEditLink; +import art.arcane.volmlib.util.bukkit.WorldIdentity; +import art.arcane.volmlib.util.data.Cuboid; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class WandSVCTest { + private Iris previousPlugin; + private Player player; + private PlayerInventory inventory; + + @Before + public void installPlugin() { + previousPlugin = Iris.instance; + Iris plugin = mock(Iris.class); + when(plugin.namespace()).thenReturn("iris"); + Iris.instance = plugin; + player = mock(Player.class); + inventory = mock(PlayerInventory.class); + when(player.getInventory()).thenReturn(inventory); + } + + @After + public void restorePlugin() { + Iris.instance = previousPlugin; + } + + @Test + public void ignoresWorldEditSelectionWhileHoldingAnOrdinaryAxe() { + ItemStack axe = wandItem(false); + when(inventory.getItemInMainHand()).thenReturn(axe); + Cuboid selection = mock(Cuboid.class); + + try (MockedStatic worldEdit = mockStatic(WorldEditLink.class)) { + worldEdit.when(() -> WorldEditLink.getSelection(player)).thenReturn(selection); + + assertFalse(WandSVC.isHoldingWand(player)); + assertNull(WandSVC.getCuboid(player)); + } + } + + @Test + public void ignoresWorldEditSelectionWithAnEmptyHand() { + Cuboid selection = mock(Cuboid.class); + + try (MockedStatic worldEdit = mockStatic(WorldEditLink.class)) { + worldEdit.when(() -> WorldEditLink.getSelection(player)).thenReturn(selection); + + assertFalse(WandSVC.isHoldingWand(player)); + assertNull(WandSVC.getCuboid(player)); + } + } + + @Test + public void readsSelectionFromAnIrisMarkedWand() { + ItemStack wand = wandItem(true); + when(inventory.getItemInMainHand()).thenReturn(wand); + World world = mock(World.class); + + try (MockedStatic worlds = mockStatic(WorldIdentity.class)) { + worlds.when(() -> WorldIdentity.resolve("minecraft:overworld")).thenReturn(Optional.of(world)); + + assertTrue(WandSVC.isHoldingWand(player)); + assertArrayEquals(new Location[]{new Location(world, 1D, 64D, 2D), new Location(world, 3D, 68D, 4D)}, + WandSVC.getCuboid(player)); + } + } + + private ItemStack wandItem(boolean marked) { + ItemStack item = mock(ItemStack.class); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer data = mock(PersistentDataContainer.class); + when(item.getType()).thenReturn(marked ? Material.BLAZE_ROD : Material.WOODEN_AXE); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(data); + when(meta.getLore()).thenReturn(List.of("1,64,2 in minecraft:overworld", "3,68,4 in minecraft:overworld")); + if (marked) { + when(data.get(new NamespacedKey("iris", "wand"), PersistentDataType.BYTE)).thenReturn((byte) 1); + } + return item; + } +} diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/wand/WandSelectionTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/wand/WandSelectionTest.java new file mode 100644 index 000000000..3feaf8cdf --- /dev/null +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/wand/WandSelectionTest.java @@ -0,0 +1,72 @@ +package art.arcane.iris.core.wand; + +import art.arcane.iris.util.common.data.registry.RegistryUtil; +import art.arcane.volmlib.util.data.Cuboid; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class WandSelectionTest { + @Test + public void capsEmittedDustSizeAtLongDistances() { + for (double distance : new double[]{64D, 128D, 255.9D}) { + Player player = drawAt(distance); + ArgumentCaptor options = ArgumentCaptor.forClass(Particle.DustOptions.class); + + verify(player, atLeastOnce()).spawnParticle(eq(Particle.DUST), anyDouble(), anyDouble(), anyDouble(), + eq(0), eq(0D), eq(0D), eq(0D), eq(1D), options.capture()); + + for (Particle.DustOptions dust : options.getAllValues()) { + assertEquals("Dust size at distance " + distance, 4f, dust.getSize(), 0f); + } + } + } + + @Test + public void retainsNearbyDustSize() { + Player player = drawAt(0D); + ArgumentCaptor options = ArgumentCaptor.forClass(Particle.DustOptions.class); + + verify(player, atLeastOnce()).spawnParticle(eq(Particle.DUST), eq(0D), eq(0D), eq(0D), + eq(0), eq(0D), eq(0D), eq(0D), eq(1D), options.capture()); + + for (Particle.DustOptions dust : options.getAllValues()) { + assertEquals(0.375f, dust.getSize(), 0f); + } + } + + private Player drawAt(double distance) { + Player player = mock(Player.class); + World world = mock(World.class); + Cuboid cuboid = mock(Cuboid.class); + when(player.getLocation()).thenReturn(new Location(world, distance, 0D, 0D)); + when(cuboid.getWorld()).thenReturn(world); + + try (MockedStatic math = mockStatic(M.class, CALLS_REAL_METHODS); + MockedStatic registry = mockStatic(RegistryUtil.class)) { + math.when(() -> M.r(anyDouble())).thenReturn(false); + registry.when(() -> RegistryUtil.find(Particle.class, "crit_magic", "crit")).thenReturn(Particle.CRIT); + registry.when(() -> RegistryUtil.find(Particle.class, "redstone", "dust")).thenReturn(Particle.DUST); + registry.when(() -> RegistryUtil.find(Particle.class, "item_crack", "item")).thenReturn(Particle.ITEM); + + new WandSelection(cuboid, player).draw(); + } + + return player; + } +} diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index 395d2b666..6b690d310 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -199,7 +199,6 @@ public class IrisSettings { public double targetSpawnEntitiesPerChunk = 0.95; public boolean markerEntitySpawningSystem = true; public boolean effectSystem = true; - public boolean worldEditWandCUI = true; public boolean globalPregenCache = false; }