From 4f448ba28cc066543c7a4f3314febca2d0d08f11 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 9 Aug 2020 10:59:48 -0700 Subject: [PATCH 1/2] 1.14.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7326bf0..4515a2c 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 1.14.0 + 1.14.2 From c1515e8689b732a540c060f342b022760d4521ee Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 9 Aug 2020 11:00:01 -0700 Subject: [PATCH 2/2] Moves acid task to async. --- .../bentobox/acidisland/world/AcidTask.java | 142 ++++++------ .../acidisland/world/AcidTaskTest.java | 218 ++++++++++++++++-- 2 files changed, 268 insertions(+), 92 deletions(-) diff --git a/src/main/java/world/bentobox/acidisland/world/AcidTask.java b/src/main/java/world/bentobox/acidisland/world/AcidTask.java index 190796f..80a98f1 100644 --- a/src/main/java/world/bentobox/acidisland/world/AcidTask.java +++ b/src/main/java/world/bentobox/acidisland/world/AcidTask.java @@ -1,34 +1,34 @@ package world.bentobox.acidisland.world; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Map; import java.util.WeakHashMap; -import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Sound; -import org.bukkit.block.BlockFace; import org.bukkit.entity.Animals; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Item; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.MagmaCube; import org.bukkit.entity.Monster; +import org.bukkit.entity.WaterMob; +import org.bukkit.scheduler.BukkitTask; import world.bentobox.acidisland.AcidIsland; import world.bentobox.acidisland.listeners.AcidEffect; +import world.bentobox.bentobox.BentoBox; public class AcidTask { private final AcidIsland addon; - private static final List IMMUNE = Arrays.asList(EntityType.GUARDIAN, EntityType.ELDER_GUARDIAN, - EntityType.SQUID, EntityType.TURTLE, EntityType.POLAR_BEAR, EntityType.DROWNED); - private Set itemsInWater = Collections.newSetFromMap(new WeakHashMap()); - private int entityBurnTask = -1; - private int itemBurnTask = -1; + private static final List IMMUNE = Arrays.asList(EntityType.TURTLE, EntityType.POLAR_BEAR, EntityType.DROWNED); + private Map itemsInWater = new ConcurrentHashMap<>(); + private BukkitTask findMobsTask; /** * Runs repeating tasks to deliver acid damage to mobs, etc. @@ -36,85 +36,91 @@ public class AcidTask { */ public AcidTask(AcidIsland addon) { this.addon = addon; - burnEntities(); - runAcidItemRemovalTask(); + findMobsTask = Bukkit.getScheduler().runTaskTimerAsynchronously(addon.getPlugin(), () -> findEntities(), 0L, 20L); } - /** - * Start the entity burning task - */ - private void burnEntities() { - // This part will kill monsters if they fall into the water because it is acid - entityBurnTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(addon.getPlugin(), () -> getEntityStream() - // These entities are immune to acid - .filter(e -> !IMMUNE.contains(e.getType())) - // Only burn if the chunk is loaded - .filter(e -> e.getLocation().getChunk().isLoaded()) - .filter(w -> w.getLocation().getBlock().getType().equals(Material.WATER)) - .forEach(e -> { - if ((e instanceof Monster || e instanceof MagmaCube) && addon.getSettings().getAcidDamageMonster() > 0D) { - applyDamage((LivingEntity) e, addon.getSettings().getAcidDamageMonster()); - } else if ((e instanceof Animals) && addon.getSettings().getAcidDamageAnimal() > 0D - && (!e.getType().equals(EntityType.CHICKEN) || addon.getSettings().isAcidDamageChickens())) { - ((LivingEntity) e).damage(addon.getSettings().getAcidDamageAnimal()); + void findEntities() { + Map burnList = new WeakHashMap<>(); + for (Entity e : getEntityStream()) { + if (e instanceof Item || (!IMMUNE.contains(e.getType()) && !(e instanceof WaterMob))) { + int x = e.getLocation().getBlockX() >> 4; + int z = e.getLocation().getBlockZ() >> 4; + if (e.getWorld().isChunkLoaded(x,z)) { + if (e.getLocation().getBlock().getType().equals(Material.WATER)) { + if ((e instanceof Monster || e instanceof MagmaCube) && addon.getSettings().getAcidDamageMonster() > 0D) { + burnList.put(e, (long)addon.getSettings().getAcidDamageMonster()); + + } else if ((e instanceof Animals) && addon.getSettings().getAcidDamageAnimal() > 0D + && (!e.getType().equals(EntityType.CHICKEN) || addon.getSettings().isAcidDamageChickens())) { + burnList.put(e, (long)addon.getSettings().getAcidDamageAnimal()); + } else if (addon.getSettings().getAcidDestroyItemTime() > 0 && e instanceof Item) { + burnList.put(e, System.currentTimeMillis()); + } } - }), 0L, 20L); + } + } + } + // Remove any entities not on the burn list + itemsInWater.keySet().removeIf(i -> !burnList.keySet().contains(i)); + + if (!burnList.isEmpty()) { + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> + // Burn everything + burnList.forEach(this::applyDamage)); + } } - private void applyDamage(LivingEntity e, double damage) { - e.damage(Math.max(0, damage - damage * AcidEffect.getDamageReduced(e))); + void applyDamage(Entity e, long damage) { + if (e instanceof LivingEntity) { + ((LivingEntity)e).damage(Math.max(0, damage - damage * AcidEffect.getDamageReduced((LivingEntity)e))); + } else if (addon.getSettings().getAcidDestroyItemTime() > 0){ + // Item + if (e.getLocation().getBlock().getType().equals(Material.WATER)) { + itemsInWater.putIfAbsent(e, damage + addon.getSettings().getAcidDestroyItemTime() * 1000); + if (System.currentTimeMillis() > itemsInWater.get(e)) { + e.getWorld().playSound(e.getLocation(), Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); + e.remove(); + itemsInWater.remove(e); + } + } else { + itemsInWater.remove(e); + } + } } /** * @return a stream of all entities in this world and the nether and end if those are island worlds too. */ - private Stream getEntityStream() { - Stream entityStream = addon.getOverWorld().getEntities().stream(); + List getEntityStream() { + List entityStream = new ArrayList<>(addon.getOverWorld().getEntities()); // Nether and end if (addon.getSettings().isNetherGenerate() && addon.getSettings().isNetherIslands()) { - entityStream = Stream.concat(entityStream, addon.getNetherWorld().getEntities().stream()); + entityStream.addAll(addon.getNetherWorld().getEntities()); } if (addon.getSettings().isEndGenerate() && addon.getSettings().isEndIslands()) { - entityStream = Stream.concat(entityStream, addon.getEndWorld().getEntities().stream()); + entityStream.addAll(addon.getEndWorld().getEntities()); } return entityStream; } - /** - * Start the item removal in acid task - */ - private void runAcidItemRemovalTask() { - if (addon.getSettings().getAcidDestroyItemTime() <= 0) { - return; - } - itemBurnTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(addon.getPlugin(), () -> { - Set newItemsInWater = new HashSet<>(); - getEntityStream() - .filter(e -> e.getType().equals(EntityType.DROPPED_ITEM)) - .filter(e -> e.getLocation().getChunk().isLoaded()) - .filter(e -> e.getLocation().getBlock().getType().equals(Material.WATER) - || (e.getLocation().getY() > 0 && e.getLocation().getBlock().getRelative(BlockFace.DOWN).getType().equals(Material.WATER))) - .forEach(e -> { - if (itemsInWater.contains(e)) { - e.getWorld().playSound(e.getLocation(), Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); - e.remove(); - } else { - newItemsInWater.add(e); - } - }); - itemsInWater = newItemsInWater; - }, addon.getSettings().getAcidDestroyItemTime() * 20L, addon.getSettings().getAcidDestroyItemTime() * 20L); - } - /** * Cancel tasks running */ public void cancelTasks() { - if (entityBurnTask >= 0) { - Bukkit.getScheduler().cancelTask(entityBurnTask); - } - if (itemBurnTask >= 0) { - Bukkit.getScheduler().cancelTask(itemBurnTask); - } + if (findMobsTask != null) findMobsTask.cancel(); + } + + /** + * @return the itemsInWater + */ + Map getItemsInWater() { + return itemsInWater; + } + + /** + * @param itemsInWater the itemsInWater to set + */ + void setItemsInWater(Map itemsInWater) { + this.itemsInWater = itemsInWater; } } diff --git a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java index 1b80dac..00a0a8d 100644 --- a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java +++ b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java @@ -1,18 +1,43 @@ package world.bentobox.acidisland.world; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; +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.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Cod; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Item; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.Squid; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; 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; @@ -20,53 +45,198 @@ import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.acidisland.AISettings; import world.bentobox.acidisland.AcidIsland; +/** + * @author tastybento + * + */ @RunWith(PowerMockRunner.class) @PrepareForTest({Bukkit.class}) public class AcidTaskTest { - @Mock - private BukkitScheduler scheduler; - @Mock - private AISettings settings; @Mock private AcidIsland addon; + // CUT + private AcidTask at; + + private @Nullable AISettings settings; + + @Mock + private World world; + + private List mob; + + @Mock + private @Nullable World nether; + @Mock + private @Nullable World end; + + @Mock + private BukkitScheduler scheduler; + + @Mock + private BukkitTask task; + @Mock + private Location l; + + + /** + * @throws java.lang.Exception + */ @Before public void setUp() throws Exception { - PowerMockito.mockStatic(Bukkit.class); + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); when(Bukkit.getScheduler()).thenReturn(scheduler); - when(settings.getAcidDestroyItemTime()).thenReturn(0L); + when(scheduler.runTaskTimerAsynchronously(any(), any(Runnable.class), anyLong(), anyLong())).thenReturn(task); + + when(addon.getOverWorld()).thenReturn(world); + when(addon.getNetherWorld()).thenReturn(nether); + when(addon.getEndWorld()).thenReturn(end); + + when(world.isChunkLoaded(anyInt(), anyInt())).thenReturn(true); + + Block block = mock(Block.class); + when(block.getType()).thenReturn(Material.WATER); + when(l.getBlock()).thenReturn(block); + + + // Default squid + mob = new ArrayList<>(); + Squid squid = mock(Squid.class); + when(squid.getType()).thenReturn(EntityType.SQUID); + when(squid.getLocation()).thenReturn(l); + when(squid.getWorld()).thenReturn(world); + mob.add(squid); + Skeleton s = mock(Skeleton.class); + when(s.getLocation()).thenReturn(l); + when(s.getType()).thenReturn(EntityType.SKELETON); + when(s.getWorld()).thenReturn(world); + mob.add(s); + Cod c = mock(Cod.class); + when(c.getLocation()).thenReturn(l); + when(c.getType()).thenReturn(EntityType.COD); + when(c.getWorld()).thenReturn(world); + mob.add(mock(Cod.class)); + Item i = mock(Item.class); + when(i.getLocation()).thenReturn(l); + when(i.getType()).thenReturn(EntityType.DROPPED_ITEM); + when(i.getWorld()).thenReturn(world); + mob.add(i); + when(world.getEntities()).thenReturn(mob); + when(nether.getEntities()).thenReturn(mob); + when(end.getEntities()).thenReturn(mob); + + settings = new AISettings(); + settings.setNetherGenerate(true); + settings.setEndGenerate(true); + settings.setEndIslands(true); + settings.setNetherIslands(true); + settings.setAcidDestroyItemTime(1); + settings.setAcidDamageAnimal(10); + settings.setAcidDamageMonster(10); + settings.setAcidDamageChickens(true); when(addon.getSettings()).thenReturn(settings); + + at = new AcidTask(addon); } + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#AcidTask(world.bentobox.acidisland.AcidIsland)}. + */ @Test - public void testAcidTaskDoNotDestroyItems() { - new AcidTask(addon); - verify(scheduler).scheduleSyncRepeatingTask(any(), any(Runnable.class), eq(0L), eq(20L)); + public void testAcidTask() { + verify(scheduler).runTaskTimerAsynchronously(eq(null), any(Runnable.class), eq(0L), eq(20L)); } + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#findEntities()}. + */ @Test - public void testAcidTaskDestroyItems() { - when(settings.getAcidDestroyItemTime()).thenReturn(5L); - new AcidTask(addon); - verify(scheduler).scheduleSyncRepeatingTask(any(), any(Runnable.class), eq(0L), eq(20L)); - verify(scheduler).scheduleSyncRepeatingTask(any(), any(Runnable.class), eq(100L), eq(100L)); + public void testFindEntities() { + + at.findEntities(); + verify(scheduler).runTask(eq(null), any(Runnable.class)); } + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. + */ @Test - public void testAcidTaskCancelTasks() { - AcidTask task = new AcidTask(addon); - task.cancelTasks(); - verify(scheduler).cancelTask(anyInt()); + public void testApplyDamageRemoveItems() { + Item e = mock(Item.class); + when(e.getLocation()).thenReturn(l); + when(e.getWorld()).thenReturn(world); + // Put the item in the water + + Map map = new HashMap<>(); + map.put(e, 0L); + at.setItemsInWater(map); + at.applyDamage(e, 0); + + verify(world).playSound(eq(l), any(Sound.class), anyFloat(), anyFloat()); + verify(e).remove(); + assertTrue(map.isEmpty()); } + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. + */ @Test - public void testAcidTaskCancelBothTasks() { - when(settings.getAcidDestroyItemTime()).thenReturn(5L); - AcidTask task = new AcidTask(addon); - task.cancelTasks(); - verify(scheduler, times(2)).cancelTask(anyInt()); + public void testApplyDamageNoItemDamage() { + settings.setAcidDestroyItemTime(0L); + Item e = mock(Item.class); + at.applyDamage(e, 0); + + verify(world, never()).playSound(any(), any(Sound.class), anyFloat(), anyFloat()); + verify(e, never()).remove(); } + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. + */ + @Test + public void testApplyDamageKeepItems() { + Item e = mock(Item.class); + Location l = mock(Location.class); + Block block = mock(Block.class); + when(block.getType()).thenReturn(Material.AIR); + when(l.getBlock()).thenReturn(block); + when(e.getLocation()).thenReturn(l); + when(e.getWorld()).thenReturn(world); + // Put the item in the water + + Map map = new HashMap<>(); + map.put(e, 0L); + at.setItemsInWater(map); + at.applyDamage(e, 0); + + verify(world, never()).playSound(any(), any(Sound.class), anyFloat(), anyFloat()); + verify(e, never()).remove(); + assertTrue(map.isEmpty()); + } + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#getEntityStream()}. + */ + @Test + public void testGetEntityStream() { + List es = at.getEntityStream(); + assertEquals(12, es.size()); + } + + /** + * Test method for {@link world.bentobox.acidisland.world.AcidTask#cancelTasks()}. + */ + @Test + public void testCancelTasks() { + at.cancelTasks(); + verify(task).cancel(); + } }