From 9d85e37e1b194899634132e167d1098ea39b4927 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 22 Aug 2020 17:55:38 -0700 Subject: [PATCH] Perform block conversions in 3D. Fixes https://github.com/BentoBoxWorld/Greenhouses/issues/59 --- .../bentobox/greenhouses/data/Greenhouse.java | 1 - .../greenhouses/greenhouse/BiomeRecipe.java | 4 +- .../managers/EcoSystemManager.java | 35 ++-- .../managers/GreenhouseManager.java | 1 + .../managers/EcoSystemManagerTest.java | 169 ++++++++++++++++++ 5 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 src/test/java/world/bentobox/greenhouses/managers/EcoSystemManagerTest.java diff --git a/src/main/java/world/bentobox/greenhouses/data/Greenhouse.java b/src/main/java/world/bentobox/greenhouses/data/Greenhouse.java index 67b20ab..0b143b6 100644 --- a/src/main/java/world/bentobox/greenhouses/data/Greenhouse.java +++ b/src/main/java/world/bentobox/greenhouses/data/Greenhouse.java @@ -223,5 +223,4 @@ public class Greenhouse implements DataObject { public Map getMissingBlocks() { return missingBlocks; } - } diff --git a/src/main/java/world/bentobox/greenhouses/greenhouse/BiomeRecipe.java b/src/main/java/world/bentobox/greenhouses/greenhouse/BiomeRecipe.java index f309a16..588c453 100644 --- a/src/main/java/world/bentobox/greenhouses/greenhouse/BiomeRecipe.java +++ b/src/main/java/world/bentobox/greenhouses/greenhouse/BiomeRecipe.java @@ -42,7 +42,7 @@ public class BiomeRecipe implements Comparable { private String name; private String friendlyName; - private final List adjBlocks = Arrays.asList( BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP, BlockFace.WEST); + private static final List ADJ_BLOCKS = Arrays.asList( BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP, BlockFace.WEST); // Content requirements // Material, Type, Qty. There can be more than one type of material required @@ -221,7 +221,7 @@ public class BiomeRecipe implements Comparable { .filter(bc -> random.nextDouble() < bc.getProbability()) .forEach(bc -> { // Check if the block is in the right area, up, down, n,s,e,w - if (adjBlocks.stream().map(b::getRelative).map(Block::getType).anyMatch(m -> bc.getLocalMaterial() == null || m == bc.getLocalMaterial())) { + if (ADJ_BLOCKS.stream().map(b::getRelative).map(Block::getType).anyMatch(m -> bc.getLocalMaterial() == null || m == bc.getLocalMaterial())) { // Convert! b.setType(bc.getNewMaterial()); } diff --git a/src/main/java/world/bentobox/greenhouses/managers/EcoSystemManager.java b/src/main/java/world/bentobox/greenhouses/managers/EcoSystemManager.java index 4a7c866..8cd4c48 100644 --- a/src/main/java/world/bentobox/greenhouses/managers/EcoSystemManager.java +++ b/src/main/java/world/bentobox/greenhouses/managers/EcoSystemManager.java @@ -37,10 +37,12 @@ public class EcoSystemManager { public EcoSystemManager(Greenhouses addon, GreenhouseManager greenhouseManager) { this.addon = addon; this.g = greenhouseManager; - setup(); } - private void setup() { + /** + * Kick off schedulers + */ + void setup() { // Kick off flower growing long plantTick = addon.getSettings().getPlantTick() * 60 * 20L; // In minutes if (plantTick > 0) { @@ -81,10 +83,16 @@ public class EcoSystemManager { private void convertBlocks(Greenhouse gh) { if(!gh.getLocation().getWorld().isChunkLoaded(((int) gh.getBoundingBox().getMaxX()) >> 4, ((int) gh.getBoundingBox().getMaxZ()) >> 4) || !gh.getLocation().getWorld().isChunkLoaded(((int) gh.getBoundingBox().getMinX()) >> 4, ((int) gh.getBoundingBox().getMinZ()) >> 4)){ - //addon.log("Skipping convertblock for unloaded greenhouse at " + gh.getLocation()); return; } - getAvailableBlocks(gh).stream().map(b -> b.getRelative(BlockFace.DOWN)).forEach(gh.getBiomeRecipe()::convertBlock); + for (int x = (int)gh.getBoundingBox().getMinX() + 1; x < (int)gh.getBoundingBox().getMaxX(); x++) { + for (int z = (int)gh.getBoundingBox().getMinZ() + 1; z < (int)gh.getBoundingBox().getMaxZ(); z++) { + for (int y = (int)gh.getBoundingBox().getMaxY() - 2; y >= (int)gh.getBoundingBox().getMinY() && y > 0; y--) { + Block b = gh.getWorld().getBlockAt(x, y, z).getRelative(BlockFace.DOWN); + if (!b.isEmpty()) gh.getBiomeRecipe().convertBlock(b); + } + } + } } private void verify(Greenhouse gh) { @@ -121,7 +129,7 @@ public class EcoSystemManager { .filter(e -> gh.getBiomeRecipe().getMobTypes().contains(e.getType())) .filter(e -> gh.contains(e.getLocation())).count(); // Get the blocks in the greenhouse where spawning could occur - List list = new ArrayList<>(getAvailableBlocks(gh)); + List list = new ArrayList<>(getAvailableBlocks(gh, false)); Collections.shuffle(list, new Random(System.currentTimeMillis())); Iterator it = list.iterator(); // Check if the greenhouse is full @@ -146,7 +154,7 @@ public class EcoSystemManager { int bonemeal = getBoneMeal(gh); if (bonemeal > 0) { // Get a list of all available blocks - int plantsGrown = getAvailableBlocks(gh).stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl) ? 1 : 0).sum(); + int plantsGrown = getAvailableBlocks(gh, false).stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl) ? 1 : 0).sum(); if (plantsGrown > 0) { setBoneMeal(gh, bonemeal - (int)Math.ceil((double)plantsGrown / PLANTS_PER_BONEMEAL )); } @@ -170,17 +178,21 @@ public class EcoSystemManager { } /** - * Get a list of the lowest level air blocks inside the greenhouse + * Get a list of the lowest level blocks inside the greenhouse. May be air, liquid or plants. + * These blocks sit just above solid blocks * @param gh - greenhouse + * @param ignoreliquid - true if liquid blocks should be treated like air blocks * @return List of blocks */ - private List getAvailableBlocks(Greenhouse gh) { + List getAvailableBlocks(Greenhouse gh, boolean ignoreLiquid) { List result = new ArrayList<>(); for (int x = (int)gh.getBoundingBox().getMinX() + 1; x < (int)gh.getBoundingBox().getMaxX(); x++) { for (int z = (int)gh.getBoundingBox().getMinZ() + 1; z < (int)gh.getBoundingBox().getMaxZ(); z++) { for (int y = (int)gh.getBoundingBox().getMaxY() - 2; y >= (int)gh.getBoundingBox().getMinY(); y--) { - Block b = gh.getLocation().getWorld().getBlockAt(x, y, z); - if ((!b.isEmpty() && !b.isPassable()) && (b.getRelative(BlockFace.UP).isEmpty() || b.getRelative(BlockFace.UP).isPassable())) { + Block b = gh.getWorld().getBlockAt(x, y, z); + if ((!b.isEmpty() && !b.isPassable()) + && (b.getRelative(BlockFace.UP).isEmpty() || b.getRelative(BlockFace.UP).isPassable() + || (ignoreLiquid && b.getRelative(BlockFace.UP).isLiquid()))) { result.add(b.getRelative(BlockFace.UP)); break; } @@ -212,6 +224,9 @@ public class EcoSystemManager { return (Hopper)gh.getRoofHopperLocation().getBlock().getState(); } + /** + * Cancel all the scheduled tasks + */ public void cancel() { plantTask.cancel(); mobTask.cancel(); diff --git a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java index 365a235..f21cbc7 100644 --- a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java +++ b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java @@ -67,6 +67,7 @@ public class GreenhouseManager implements Listener { loadGreenhouses(); // Start ecosystems ecoMgr = new EcoSystemManager(addon, this); + ecoMgr.setup(); // Register listeners addon.registerListener(new SnowTracker(addon)); addon.registerListener(new GreenhouseEvents(addon)); diff --git a/src/test/java/world/bentobox/greenhouses/managers/EcoSystemManagerTest.java b/src/test/java/world/bentobox/greenhouses/managers/EcoSystemManagerTest.java new file mode 100644 index 0000000..07f5724 --- /dev/null +++ b/src/test/java/world/bentobox/greenhouses/managers/EcoSystemManagerTest.java @@ -0,0 +1,169 @@ +/** + * + */ +package world.bentobox.greenhouses.managers; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +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.List; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.junit.After; +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 world.bentobox.bentobox.BentoBox; +import world.bentobox.greenhouses.data.Greenhouse; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class}) +public class EcoSystemManagerTest { + + @Mock + private Greenhouse gh; + @Mock + private World world; + @Mock + private Block block; + @Mock + private Block air; + @Mock + private Block liquid; + @Mock + private Block plant; + + // CUT + private EcoSystemManager eco; + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // 4x4x4 greenhouse + BoundingBox bb = BoundingBox.of(new Vector(0,0,0), new Vector(5,5,5)); + when(gh.getBoundingBox()).thenReturn(bb); + // World + when(gh.getWorld()).thenReturn(world); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(block); + // Block + // Air + when(air.isEmpty()).thenReturn(true); + when(air.isPassable()).thenReturn(true); + when(air.getRelative(eq(BlockFace.UP))).thenReturn(air); + // Plant + when(plant.isPassable()).thenReturn(true); + when(plant.getRelative(eq(BlockFace.UP))).thenReturn(air); + // Liquid + when(liquid.isLiquid()).thenReturn(true); + when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(air); + // Default for block + when(block.getRelative(eq(BlockFace.UP))).thenReturn(air); + + eco = new EcoSystemManager(null, null); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksAirAboveBlock() { + List result = eco.getAvailableBlocks(gh, false); + assertEquals(16, result.size()); + assertEquals(air, result.get(0)); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksPlantAboveBlock() { + when(block.getRelative(eq(BlockFace.UP))).thenReturn(plant); + List result = eco.getAvailableBlocks(gh, false); + assertEquals(16, result.size()); + assertEquals(plant, result.get(0)); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksAllAir() { + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(air); + List result = eco.getAvailableBlocks(gh, false); + assertEquals(0, result.size()); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksAllLiquid() { + when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(liquid); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(liquid); + List result = eco.getAvailableBlocks(gh, false); + assertEquals(0, result.size()); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksAllPlant() { + when(plant.getRelative(eq(BlockFace.UP))).thenReturn(plant); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(plant); + List result = eco.getAvailableBlocks(gh, false); + assertEquals(0, result.size()); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksLiquidAboveBlockIgnoreLiquids() { + when(block.getRelative(eq(BlockFace.UP))).thenReturn(liquid); + List result = eco.getAvailableBlocks(gh, true); + assertEquals(16, result.size()); + assertEquals(liquid, result.get(0)); + } + + /** + * Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(world.bentobox.greenhouses.data.Greenhouse)}. + */ + @Test + public void testGetAvailableBlocksLiquidAboveBlock() { + when(block.getRelative(eq(BlockFace.UP))).thenReturn(liquid); + List result = eco.getAvailableBlocks(gh, false); + assertEquals(0, result.size()); + } +}