diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListener.java new file mode 100644 index 000000000..379c44de3 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListener.java @@ -0,0 +1,41 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityTeleportEvent; + +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.lists.Flags; + +/** + * Prevents pets from teleporting to islands unless + * the owner is a member of the island. + * @author tastybento + * @since 1.16.0 + */ +public class PetTeleportListener extends FlagListener { + + /** + * Prevents pets teleporting + * @param e - event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPetTeleport(final EntityTeleportEvent e) { + if (e.getTo() == null + || !getIWM().inWorld(e.getFrom()) + || !Flags.PETS_STAY_AT_HOME.isSetForWorld(e.getFrom().getWorld()) + || !(e.getEntity() instanceof Tameable) + ) return; + Tameable t = (Tameable)e.getEntity(); + if (t.isTamed() && t.getOwner() != null) { + // Get where the pet is going + e.setCancelled(getIslands().getProtectedIslandAt(e.getTo()) + // Not home island + .map(i -> !i.getMemberSet().contains(t.getOwner().getUniqueId())) + // Not any island + .orElse(true)); + } + } + +} diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index f9aab8cd8..5b07a3bac 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -57,6 +57,7 @@ import world.bentobox.bentobox.listeners.flags.worldsettings.NaturalSpawningOuts import world.bentobox.bentobox.listeners.flags.worldsettings.ObsidianScoopingListener; import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineGrowthListener; import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineRedstoneListener; +import world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener; import world.bentobox.bentobox.listeners.flags.worldsettings.PistonPushListener; import world.bentobox.bentobox.listeners.flags.worldsettings.RemoveMobsListener; import world.bentobox.bentobox.listeners.flags.worldsettings.SpawnerSpawnEggsListener; @@ -516,6 +517,13 @@ public final class Flags { */ public static final Flag SPAWNER_SPAWN_EGGS = new Flag.Builder("SPAWNER_SPAWN_EGGS", Material.SPAWNER).listener(new SpawnerSpawnEggsListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** + * Keeps pets on the player's island. + * @since 1.16.0 + * @see PetTeleportListener + */ + public static final Flag PETS_STAY_AT_HOME = new Flag.Builder("PETS_STAY_AT_HOME", Material.TROPICAL_FISH).listener(new PetTeleportListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 53b0f035e..4d4dd2a5c 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1110,6 +1110,13 @@ protection: &a May help reduce lag. &a Does not affect spawn island. name: "Offline Redstone" + PETS_STAY_AT_HOME: + description: |- + &a When active, tamed pets + &a can only go to and + &a cannot leave the owner's + &a home island. + name: "Pets Stay At Home" PISTON_PUSH: description: |- &a Enable this to prevent diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java index ad2cc6141..c4d8aafce 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java @@ -27,6 +27,8 @@ import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.reflect.Whitebox; +import com.google.common.collect.ImmutableSet; + import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.configuration.WorldSettings; @@ -44,6 +46,14 @@ import world.bentobox.bentobox.managers.PlayersManager; import world.bentobox.bentobox.util.Util; /** + * Common items for testing. Don't forget to use super.setUp()! + * + * Sets up BentoBox plugin, pluginManager and ItemFactory. + * Location, world, playersManager and player. + * IWM, Addon and WorldSettings. IslandManager with one + * island with protection and nothing allowed by default. + * Owner of island is player with same UUID. + * Locales, placeholders. * @author tastybento * */ @@ -128,7 +138,7 @@ public abstract class AbstractCommonSetup { when(island.isAllowed(any())).thenReturn(false); when(island.isAllowed(any(), any())).thenReturn(false); when(island.getOwner()).thenReturn(uuid); - + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); // Enable reporting from Flags class MetadataValue mdv = new FixedMetadataValue(plugin, "_why_debug"); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListenerTest.java new file mode 100644 index 000000000..c67538860 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/PetTeleportListenerTest.java @@ -0,0 +1,159 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import static org.mockito.Mockito.*; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Tameable; +import org.bukkit.event.entity.EntityTeleportEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class, Util.class}) +public class PetTeleportListenerTest extends AbstractCommonSetup { + + private PetTeleportListener ptl; + @Mock + private Tameable tamed; + @Mock + private AnimalTamer tamer; + + /** + * @throws java.lang.Exception + */ + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + when(tamed.isTamed()).thenReturn(true); + when(tamed.getOwner()).thenReturn(tamer); + when(tamer.getUniqueId()).thenReturn(uuid); + ptl = (PetTeleportListener) Flags.PETS_STAY_AT_HOME.getListener().get(); + ptl.setPlugin(plugin); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportNotTameable() { + EntityTeleportEvent e = new EntityTeleportEvent(player, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportNullTo() { + EntityTeleportEvent e = new EntityTeleportEvent(player, location, null); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportWrongWorld() { + when(iwm.inWorld(location)).thenReturn(false); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagNotSet() { + Flags.PETS_STAY_AT_HOME.setSetting(world, false); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagSetGoingHome() { + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagSetNoIsland() { + Location l = mock(Location.class); + when(im.getProtectedIslandAt(l)).thenReturn(Optional.empty()); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, l); + ptl.onPetTeleport(e); + assertTrue(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagSetNotHome() { + Location l = mock(Location.class); + Island otherIsland = mock(Island.class); + when(otherIsland.getMemberSet()).thenReturn(ImmutableSet.of()); + when(im.getProtectedIslandAt(l)).thenReturn(Optional.of(otherIsland )); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, l); + ptl.onPetTeleport(e); + assertTrue(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagSetTamedButNoOwner() { + when(tamed.getOwner()).thenReturn(null); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener#onPetTeleport(org.bukkit.event.entity.EntityTeleportEvent)}. + */ + @Test + public void testOnPetTeleportFlagSetNotTamed() { + when(tamed.isTamed()).thenReturn(false); + EntityTeleportEvent e = new EntityTeleportEvent(tamed, location, location); + ptl.onPetTeleport(e); + assertFalse(e.isCancelled()); + } + +}