diff --git a/pom.xml b/pom.xml index 10d06d3..c814b6d 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.1.0 + 2.1.1 BentoBoxWorld_Boxed bentobox-world diff --git a/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java b/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java index 552e8d0..c90fd99 100644 --- a/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/EnderPearlListener.java @@ -2,8 +2,10 @@ package world.bentobox.boxed.listeners; import java.io.IOException; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Sound; +import org.bukkit.World; import org.bukkit.block.BlockFace; import org.bukkit.entity.EnderPearl; import org.bukkit.entity.EntityType; @@ -12,8 +14,11 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; import world.bentobox.boxed.Boxed; /** @@ -32,34 +37,93 @@ public class EnderPearlListener implements Listener { this.addon = addon; } - + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent e) { + if (!addon.inWorld(e.getFrom()) || !e.getPlayer().getGameMode().equals(GameMode.SURVIVAL) + || (e.getTo() != null && !addon.inWorld(e.getTo())) + || addon.getIslands().getSpawn(e.getFrom().getWorld()).map(spawn -> spawn.onIsland(e.getTo())).orElse(false) + ) { + return; + } + + User u = User.getInstance(e.getPlayer()); + // If the to is outside the box, cancel it + if (e.getTo() != null) { + Island i = addon.getIslands().getIsland(e.getFrom().getWorld(), u); + if (i == null || !i.onIsland(e.getTo())) { + u.sendMessage("boxed.general.errors.no-teleport-outside"); + e.setCancelled(true); + return; + } + } + } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onEnderPearlLand(ProjectileHitEvent e) { if (!e.getEntityType().equals(EntityType.ENDER_PEARL) || e.getHitBlock() == null - || !addon.inWorld(e.getHitBlock().getLocation())) { + || !addon.inWorld(e.getHitBlock().getLocation()) + || !Boxed.ALLOW_MOVE_BOX.isSetForWorld(e.getHitBlock().getWorld()) + ) { return; - } + } + // Moving box is allowed Location l = e.getHitBlock().getRelative(BlockFace.UP).getLocation(); - EnderPearl ep = (EnderPearl)e.getEntity(); - if (ep.getShooter() instanceof Player player) { + World w = e.getHitBlock().getWorld(); + if (e.getEntity() instanceof EnderPearl ep && ep.getShooter() instanceof Player player) { User u = User.getInstance(player); - addon.getIslands().getIslandAt(l).ifPresent(i -> { - // Check flag - if (i.isAllowed(u, Boxed.MOVE_BOX) && addon.getIslands().isSafeLocation(l)) { - // Reset home locations - i.getMemberSet().forEach(uuid -> addon.getIslands().setHomeLocation(uuid, l)); - try { - i.setProtectionCenter(l); - i.setSpawnPoint(l.getWorld().getEnvironment(), l); - u.getPlayer().playSound(l, Sound.ENTITY_GENERIC_EXPLODE, 2F, 2F); - } catch (IOException e1) { - addon.logError("Could not move box " + e1.getMessage()); - } + // Check if enderpearl is inside or outside the box + // Get user's box + Island is = addon.getIslands().getIsland(w, u); + if (is == null) { + return; // Nothing to do + } + + // Get the box that the player is in + addon.getIslands().getIslandAt(u.getLocation()).ifPresent(fromIsland -> { + // Check that it is their box + if (!is.getUniqueId().equals(fromIsland.getUniqueId())) { + return; } + // Find where the pearl landed + addon.getIslands().getIslandAt(l).ifPresentOrElse(toIsland -> { + if (fromIsland.getUniqueId().equals(toIsland.getUniqueId())) { + if (!toIsland.onIsland(l)) { + // Moving is allowed + moveBox(u, fromIsland, l); + Util.teleportAsync(player, l); + return; + } + } else { + // Different box. This is never allowed. Cancel the throw + e.setCancelled(true); + u.sendMessage("boxed.general.errors.no-teleport-outside"); + return; + } + }, () -> { + // No box. This is never allowed. Cancel the throw + e.setCancelled(true); + u.sendMessage("boxed.general.errors.no-teleport-outside"); + return; + }); + }); } } + + + private void moveBox(User u, Island fromIsland, Location l) { + // Reset home locations + fromIsland.getMemberSet().forEach(uuid -> addon.getIslands().setHomeLocation(uuid, l)); + try { + fromIsland.setProtectionCenter(l); + fromIsland.setSpawnPoint(l.getWorld().getEnvironment(), l); + u.getPlayer().playSound(l, Sound.ENTITY_GENERIC_EXPLODE, 2F, 2F); + } catch (IOException e1) { + addon.logError("Could not move box " + e1.getMessage()); + } + + } + } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 4c603c4..05d1b92 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -15,6 +15,7 @@ boxed: already-have-island: '&c You already have a box!' no-safe-location: '&c No safe location found in box!' not-owner: '&c You are not the owner of your team!' + no-teleport-outside: "&c You cannot teleport outside of your box" commands: boxed: help: @@ -305,6 +306,12 @@ boxed: deleted-island: '&a Area at &e [xyz] &a has been successfully regenerated.' protection: flags: + ALLOW_MOVE_BOX: + name: Box moving + description: |- + &a Allow players to move + &a their box by throwing + &a enderpearls ELYTRA: description: Toggle use ENDERMAN_GRIEFING: diff --git a/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java new file mode 100644 index 0000000..0522a4f --- /dev/null +++ b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java @@ -0,0 +1,400 @@ +package world.bentobox.boxed.listeners; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.util.Util; +import world.bentobox.boxed.Boxed; +import world.bentobox.boxed.Settings; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class, User.class, Util.class }) +public class EnderPearlListenerTest { + + @Mock + private BentoBox plugin; + @Mock + private Boxed addon; + @Mock + private Player player; + @Mock + private Location from; + @Mock + private Location to; + @Mock + private World world; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Island anotherIsland; + @Mock + private Island spawn; + @Mock + private User user; + @Mock + private EnderPearl projectile; + @Mock + private Block hitBlock; + + private Settings settings; + private EnderPearlListener epl; + @Mock + private IslandWorldManager iwm; + + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + + // Set up plugin + plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + + when(plugin.getIWM()).thenReturn(iwm); + + + + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + + PowerMockito.mockStatic(User.class, Mockito.RETURNS_MOCKS); + when(User.getInstance(any(Player.class))).thenReturn(user); + // Settings + settings = new Settings(); + when(addon.getSettings()).thenReturn(settings); + when(iwm.getWorldSettings(world)).thenReturn(settings); + when(iwm.inWorld(world)).thenReturn(true); + + // Locations + when(to.getWorld()).thenReturn(world); + when(from.getWorld()).thenReturn(world); + when(from.toVector()).thenReturn(new Vector(1,2,3)); + when(to.toVector()).thenReturn(new Vector(6,7,8)); + when(world.getEnvironment()).thenReturn(Environment.NORMAL); + + // In game world + when(addon.inWorld(any(World.class))).thenReturn(true); + when(addon.inWorld(any(Location.class))).thenReturn(true); + + // User + when(user.getPlayer()).thenReturn(player); + when(player.getGameMode()).thenReturn(GameMode.SURVIVAL); + when(player.getWorld()).thenReturn(world); + when(user.getMetaData(anyString())).thenReturn(Optional.empty()); // No meta data + when(player.getLocation()).thenReturn(from); + when(user.getLocation()).thenReturn(from); + + // Islands + when(island.onIsland(any())).thenReturn(true); // Default on island + when(im.getIsland(world, user)).thenReturn(island); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(addon.getIslands()).thenReturn(im); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getUniqueId()).thenReturn("uniqueID"); + when(island.getProtectionCenter()).thenReturn(from); + when(island.getProtectionBoundingBox()).thenReturn(BoundingBox.of(new Vector(0,0,0), new Vector(50,50,50))); + when(island.getRange()).thenReturn(3); + when(im.isSafeLocation(any())).thenReturn(true); // safe for now + when(island.getPlayersOnIsland()).thenReturn(List.of(player)); + when(im.getSpawn(any())).thenReturn(Optional.of(spawn)); + when(island.onIsland(from)).thenReturn(true); + when(island.onIsland(to)).thenReturn(false); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(UUID.randomUUID())); + // Another island + when(anotherIsland.getUniqueId()).thenReturn("another_uniqueID"); + + // Projectiles + when(projectile.getType()).thenReturn(EntityType.ENDER_PEARL); + when(projectile.getShooter()).thenReturn(player); + when(hitBlock.getLocation()).thenReturn(to); + when(hitBlock.getWorld()).thenReturn(world); + when(hitBlock.getRelative(BlockFace.UP)).thenReturn(hitBlock); + Boxed.ALLOW_MOVE_BOX.setSetting(world, true); + + epl = new EnderPearlListener(addon); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#EnderPearlListener(world.bentobox.boxed.Boxed)}. + */ + @Test + public void testEnderPearlListener() { + assertNotNull(epl); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testOnPlayerTeleportNotAllowed() { + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + epl.onPlayerTeleport(e); + assertTrue(e.isCancelled()); + verify(user).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testOnPlayerTeleportNotSurvival() { + when(player.getGameMode()).thenReturn(GameMode.CREATIVE); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + epl.onPlayerTeleport(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testOnPlayerTeleportNullTo() { + when(player.getGameMode()).thenReturn(GameMode.CREATIVE); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, null, TeleportCause.CHORUS_FRUIT); + epl.onPlayerTeleport(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testOnPlayerTeleportToSpawn() { + when(spawn.onIsland(any())).thenReturn(true); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + epl.onPlayerTeleport(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testOnPlayerTeleportNotInWorldAllowed() { + when(addon.inWorld(any(World.class))).thenReturn(false); + when(addon.inWorld(any(Location.class))).thenReturn(false); + PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT); + epl.onPlayerTeleport(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandNotEnderPearl() throws IOException { + when(projectile.getType()).thenReturn(EntityType.ARROW); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandNullHitBlock() throws IOException { + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, null, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandNotInWorld() throws IOException { + when(addon.inWorld(to)).thenReturn(false); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandNotMovingBox() throws IOException { + Boxed.ALLOW_MOVE_BOX.setSetting(world, false); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandNonHuman() throws IOException { + Creeper creeper = mock(Creeper.class); + when(projectile.getShooter()).thenReturn(creeper); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandUserHasNoIsland() throws IOException { + when(im.getIsland(world, user)).thenReturn(null); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verifyFailure(); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlNotOnIslandWhenThrowing() throws IOException { + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verifyFailure(); + } + + private void verifyFailure() throws IOException { + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verify(im, never()).setHomeLocation(any(UUID.class), any()); + verify(island, never()).setProtectionCenter(any()); + verify(island, never()).setSpawnPoint(any(), any()); + verify(player, never()).playSound(any(Location.class), any(Sound.class), anyFloat(), anyFloat()); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlLandHuman() throws IOException { + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(user, never()).sendMessage("boxed.general.errors.no-teleport-outside"); + verify(im).setHomeLocation(any(UUID.class), eq(to)); + verify(island).setProtectionCenter(to); + verify(island).setSpawnPoint(Environment.NORMAL, to); + verify(player).playSound(to, Sound.ENTITY_GENERIC_EXPLODE, 2F, 2F); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlThrewToDifferentIsland() throws IOException { + when(im.getIslandAt(eq(to))).thenReturn(Optional.of(anotherIsland)); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertTrue(e.isCancelled()); + verify(user).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlThrewToNonIsland() throws IOException { + when(im.getIslandAt(eq(to))).thenReturn(Optional.empty()); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertTrue(e.isCancelled()); + verify(user).sendMessage("boxed.general.errors.no-teleport-outside"); + } + + /** + * Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onEnderPearlLand(org.bukkit.event.entity.ProjectileHitEvent)}. + * @throws IOException + */ + @Test + public void testOnEnderPearlCannotSetProtectionCenter() throws IOException { + doThrow(IOException.class).when(island).setProtectionCenter(to); + ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP); + epl.onEnderPearlLand(e); + assertFalse(e.isCancelled()); + verify(addon).logError("Could not move box null"); + } +}