Enables water plants to be grown under water.

Fixes #93
This commit is contained in:
tastybento 2022-12-29 18:59:50 -08:00
parent e5c62047ff
commit 1f0b579d5a
5 changed files with 110 additions and 41 deletions

View File

@ -2,6 +2,7 @@ package world.bentobox.greenhouses.greenhouse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -24,6 +25,7 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Bisected; import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.block.data.type.GlowLichen; import org.bukkit.block.data.type.GlowLichen;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
@ -35,6 +37,7 @@ import com.google.common.base.Enums;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
import world.bentobox.greenhouses.Greenhouses; import world.bentobox.greenhouses.Greenhouses;
import world.bentobox.greenhouses.data.Greenhouse; import world.bentobox.greenhouses.data.Greenhouse;
@ -61,12 +64,26 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
} }
private static final List<BlockFace> ADJ_BLOCKS = Arrays.asList( BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP, BlockFace.WEST); private static final List<BlockFace> ADJ_BLOCKS = Arrays.asList( BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP, BlockFace.WEST);
private static final List<Material> UNDERWATER_PLANTS;
static {
List<Material> m = new ArrayList<>();
Arrays.stream(Material.values()).filter(c -> c.name().contains("CORAL")).forEach(m::add);
m.add(Material.SEA_LANTERN);
m.add(Material.SEA_PICKLE);
m.add(Material.SEAGRASS);
m.add(Material.KELP);
m.add(Material.GLOW_LICHEN);
UNDERWATER_PLANTS = Collections.unmodifiableList(m);
}
// Content requirements // Content requirements
// Material, Type, Qty. There can be more than one type of material required // Material, Type, Qty. There can be more than one type of material required
private final Map<Material, Integer> requiredBlocks = new EnumMap<>(Material.class); private final Map<Material, Integer> requiredBlocks = new EnumMap<>(Material.class);
// Plants /**
* Tree map of plants
*/
private final TreeMap<Double, GreenhousePlant> plantTree = new TreeMap<>(); private final TreeMap<Double, GreenhousePlant> plantTree = new TreeMap<>();
private final TreeMap<Double, GreenhousePlant> underwaterPlants = new TreeMap<>();
// Mobs // Mobs
// Entity Type, Material to Spawn on, Probability // Entity Type, Material to Spawn on, Probability
@ -149,11 +166,12 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
*/ */
public boolean addPlants(Material plantMaterial, double plantProbability, Material plantGrowOn) { public boolean addPlants(Material plantMaterial, double plantProbability, Material plantGrowOn) {
double probability = plantProbability/100; double probability = plantProbability/100;
TreeMap<Double, GreenhousePlant> map = UNDERWATER_PLANTS.contains(plantMaterial) ? underwaterPlants : plantTree;
// Add up all the probabilities in the list so far // Add up all the probabilities in the list so far
double lastProb = plantTree.isEmpty() ? 0D : plantTree.lastKey(); double lastProb = map.isEmpty() ? 0D : map.lastKey();
if ((1D - lastProb) >= probability) { if ((1D - lastProb) >= probability) {
// Add to probability tree // Add to probability tree
plantTree.put(lastProb + probability, new GreenhousePlant(plantMaterial, plantGrowOn)); map.put(lastProb + probability, new GreenhousePlant(plantMaterial, plantGrowOn));
} else { } else {
addon.logError("Plant chances add up to > 100% in " + type.toString() + " biome recipe! Skipping " + plantMaterial.toString()); addon.logError("Plant chances add up to > 100% in " + type.toString() + " biome recipe! Skipping " + plantMaterial.toString());
return false; return false;
@ -358,7 +376,9 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
Location spawnLoc = b.getLocation().clone().add(new Vector(0.5, 0, 0.5)); Location spawnLoc = b.getLocation().clone().add(new Vector(0.5, 0, 0.5));
return getRandomMob() return getRandomMob()
// Check if the spawn on block matches, if it exists // Check if the spawn on block matches, if it exists
.filter(m -> Optional.of(m.mobSpawnOn()).map(b.getRelative(BlockFace.DOWN).getType()::equals).orElse(true)) .filter(m -> Optional.of(m.mobSpawnOn())
.map(b.getRelative(BlockFace.DOWN).getType()::equals)
.orElse(true))
// If spawn occurs, check if it can fit inside greenhouse // If spawn occurs, check if it can fit inside greenhouse
.map(m -> { .map(m -> {
Entity entity = b.getWorld().spawnEntity(spawnLoc, m.mobType()); Entity entity = b.getWorld().spawnEntity(spawnLoc, m.mobType());
@ -409,11 +429,11 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
return key == null ? Optional.empty() : Optional.ofNullable(mobTree.get(key)); return key == null ? Optional.empty() : Optional.ofNullable(mobTree.get(key));
} }
private Optional<GreenhousePlant> getRandomPlant() { private Optional<GreenhousePlant> getRandomPlant(boolean underwater) {
// Grow a random plant that can grow // Grow a random plant that can grow
double r = random.nextDouble(); double r = random.nextDouble();
Double key = plantTree.ceilingKey(r); Double key = underwater ? underwaterPlants.ceilingKey(r) : plantTree.ceilingKey(r);
return key == null ? Optional.empty() : Optional.ofNullable(plantTree.get(key)); return key == null ? Optional.empty() : Optional.ofNullable(underwater ? underwaterPlants.get(key) : plantTree.get(key));
} }
/** /**
@ -433,20 +453,17 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
/** /**
* Plants a plant on block bl if it makes sense. * Plants a plant on block bl if it makes sense.
* @param block - block that can have growth * @param block - block that can have growth
* @param underwater - if the block is underwater or not
* @return true if successful * @return true if successful
*/ */
public boolean growPlant(GrowthBlock block) { public boolean growPlant(GrowthBlock block, boolean underwater) {
Block bl = block.block(); Block bl = block.block();
if (!bl.isEmpty()) { return getRandomPlant(underwater).map(p -> {
return false;
}
return getRandomPlant().map(p -> {
if (bl.getY() != 0 && canGrowOn(block, p)) { if (bl.getY() != 0 && canGrowOn(block, p)) {
if (!plantIt(bl, p)) { if (plantIt(bl, p)) {
return false; bl.getWorld().spawnParticle(Particle.SNOWBALL, bl.getLocation(), 10, 2, 2, 2);
return true;
} }
bl.getWorld().spawnParticle(Particle.SNOWBALL, bl.getLocation(), 10, 2, 2, 2);
return true;
} }
return false; return false;
}).orElse(false); }).orElse(false);
@ -459,6 +476,7 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
* @return true if successful, false if not * @return true if successful, false if not
*/ */
private boolean plantIt(Block bl, GreenhousePlant p) { private boolean plantIt(Block bl, GreenhousePlant p) {
boolean underwater = bl.getType().equals(Material.WATER);
BlockData dataBottom = p.plantMaterial().createBlockData(); BlockData dataBottom = p.plantMaterial().createBlockData();
// Check if this is a double-height plant // Check if this is a double-height plant
if (dataBottom instanceof Bisected bi) { if (dataBottom instanceof Bisected bi) {
@ -475,8 +493,13 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
} else if (p.plantMaterial().equals(Material.GLOW_LICHEN)) { } else if (p.plantMaterial().equals(Material.GLOW_LICHEN)) {
return placeLichen(bl); return placeLichen(bl);
} else { } else {
// Single height plant if (dataBottom instanceof Waterlogged wl) {
bl.setBlockData(dataBottom, false); wl.setWaterlogged(underwater);
bl.setBlockData(wl, false);
} else {
// Single height plant
bl.setBlockData(dataBottom, false);
}
} }
return true; return true;
} }
@ -528,12 +551,31 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
return false; return false;
} }
/**
* Checks if a particular plant can group at the location of a block
* @param block - block being checked
* @param p - greenhouse plant
* @return true if it can be grown otherwise false
*/
private boolean canGrowOn(GrowthBlock block, GreenhousePlant p) { private boolean canGrowOn(GrowthBlock block, GreenhousePlant p) {
// Ceiling plants can only grow on ceiling blocks // Ceiling plants can only grow on ceiling blocks
if (CEILING_PLANTS.contains(p.plantMaterial()) && Boolean.TRUE.equals(block.floor())) { if (CEILING_PLANTS.contains(p.plantMaterial()) && Boolean.TRUE.equals(block.floor())) {
return false; return false;
} }
return p.plantGrownOn().equals(block.block().getRelative(Boolean.TRUE.equals(block.floor()) ? BlockFace.DOWN : BlockFace.UP).getType()); // Underwater plants can only be placed in water and regular plants cannot be placed in water
if (block.block().getType().equals(Material.WATER)) {
BentoBox.getInstance().logDebug("Water block");
if (!UNDERWATER_PLANTS.contains(p.plantMaterial())) {
return false;
}
BentoBox.getInstance().logDebug("Water plant");
} else if (UNDERWATER_PLANTS.contains(p.plantMaterial())) {
return false;
}
return p.plantGrownOn().equals(block.block().getRelative(Boolean.TRUE.equals(block.floor()) ?
BlockFace.DOWN :
BlockFace.UP).getType());
} }
/** /**

View File

@ -183,9 +183,13 @@ public class EcoSystemManager {
int bonemeal = getBoneMeal(gh); int bonemeal = getBoneMeal(gh);
if (bonemeal > 0) { if (bonemeal > 0) {
// Get a list of all available blocks // Get a list of all available blocks
List<GrowthBlock> list = getAvailableBlocks(gh, true); List<GrowthBlock> list = getAvailableBlocks(gh, false);
Collections.shuffle(list); Collections.shuffle(list);
int plantsGrown = list.stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl) ? 1 : 0).sum(); int plantsGrown = list.stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl, false) ? 1 : 0).sum();
// Underwater plants
list = getAvailableBlocks(gh, true);
Collections.shuffle(list);
plantsGrown += list.stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl, true) ? 1 : 0).sum();
if (plantsGrown > 0) { if (plantsGrown > 0) {
setBoneMeal(gh, bonemeal - (int)Math.ceil((double)plantsGrown / PLANTS_PER_BONEMEAL )); setBoneMeal(gh, bonemeal - (int)Math.ceil((double)plantsGrown / PLANTS_PER_BONEMEAL ));
} }
@ -208,6 +212,7 @@ public class EcoSystemManager {
} }
public record GrowthBlock(Block block, Boolean floor) {}
/** /**
* Get a list of the lowest level blocks inside the greenhouse. May be air, liquid or plants. * Get a list of the lowest level blocks inside the greenhouse. May be air, liquid or plants.
@ -225,26 +230,31 @@ public class EcoSystemManager {
for (double z = ibb.getMinZ(); z < ibb.getMaxZ(); z++) { for (double z = ibb.getMinZ(); z < ibb.getMaxZ(); z++) {
for (double y = ibb.getMaxY() - 1; y >= bb.getMinY(); y--) { for (double y = ibb.getMaxY() - 1; y >= bb.getMinY(); y--) {
Block b = gh.getWorld().getBlockAt(NumberConversions.floor(x), NumberConversions.floor(y), NumberConversions.floor(z)); Block b = gh.getWorld().getBlockAt(NumberConversions.floor(x), NumberConversions.floor(y), NumberConversions.floor(z));
// Check ceiling blocks
if (b.isEmpty() && !b.getRelative(BlockFace.UP).isEmpty()) {
result.add(new GrowthBlock(b, false));
}
// Check floor blocks // Check floor blocks
if (!(b.isEmpty() || (ignoreLiquid && b.isLiquid())) if (!ignoreLiquid) {
&& (b.getRelative(BlockFace.UP).isEmpty() // Check ceiling blocks
|| (b.getRelative(BlockFace.UP).isPassable() && !b.isLiquid()) if (b.isEmpty() && !b.getRelative(BlockFace.UP).isEmpty()) {
|| (ignoreLiquid && b.isLiquid() && b.getRelative(BlockFace.UP).isPassable()))) { result.add(new GrowthBlock(b, false));
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true)); }
break; if (!b.isEmpty() && (b.getRelative(BlockFace.UP).isEmpty() || b.getRelative(BlockFace.UP).isPassable())) {
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true));
break;
}
} else {
if (!b.isEmpty() && !b.isLiquid() && b.getRelative(BlockFace.UP).isLiquid()) {
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true));
break;
}
} }
} }
} }
} }
return result; return result;
} }
public record GrowthBlock(Block block, Boolean floor) {}
private int getBoneMeal(Greenhouse gh) { private int getBoneMeal(Greenhouse gh) {
Hopper hopper = getHopper(gh); Hopper hopper = getHopper(gh);

View File

@ -1,8 +1,14 @@
package world.bentobox.greenhouses.managers; package world.bentobox.greenhouses.managers;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;

View File

@ -611,7 +611,7 @@ public class BiomeRecipeTest {
@Test @Test
public void testGrowPlantNotAir() { public void testGrowPlantNotAir() {
when(block.getType()).thenReturn(Material.SOUL_SAND); when(block.getType()).thenReturn(Material.SOUL_SAND);
assertFalse(br.growPlant(new GrowthBlock(block, true))); assertFalse(br.growPlant(new GrowthBlock(block, true), false));
} }
/** /**
@ -621,7 +621,7 @@ public class BiomeRecipeTest {
public void testGrowPlantNoPlants() { public void testGrowPlantNoPlants() {
when(block.getType()).thenReturn(Material.AIR); when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true); when(block.isEmpty()).thenReturn(true);
assertFalse(br.growPlant(new GrowthBlock(block, true))); assertFalse(br.growPlant(new GrowthBlock(block, true), false));
} }
/** /**
@ -633,7 +633,7 @@ public class BiomeRecipeTest {
when(block.getType()).thenReturn(Material.AIR); when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true); when(block.isEmpty()).thenReturn(true);
assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK)); assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK));
assertFalse(br.growPlant(new GrowthBlock(block, true))); assertFalse(br.growPlant(new GrowthBlock(block, true), false));
} }
/** /**
@ -649,7 +649,7 @@ public class BiomeRecipeTest {
when(block.getRelative(any())).thenReturn(ob); when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK)); assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK));
assertTrue(br.growPlant(new GrowthBlock(block, true))); assertTrue(br.growPlant(new GrowthBlock(block, true), false));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble()); verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(block).setBlockData(eq(bd), eq(false)); verify(block).setBlockData(eq(bd), eq(false));
} }
@ -667,7 +667,7 @@ public class BiomeRecipeTest {
when(block.getRelative(any())).thenReturn(ob); when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS)); assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS));
assertTrue(br.growPlant(new GrowthBlock(block, false))); assertTrue(br.growPlant(new GrowthBlock(block, false), false));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble()); verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(block).setBlockData(eq(bd), eq(false)); verify(block).setBlockData(eq(bd), eq(false));
} }
@ -686,7 +686,7 @@ public class BiomeRecipeTest {
when(block.getRelative(any())).thenReturn(ob); when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS)); assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS));
// Not a ceiling block // Not a ceiling block
assertFalse(br.growPlant(new GrowthBlock(block, true))); assertFalse(br.growPlant(new GrowthBlock(block, true), false));
} }
/** /**
@ -704,7 +704,7 @@ public class BiomeRecipeTest {
when(block.getRelative(BlockFace.DOWN)).thenReturn(ob); when(block.getRelative(BlockFace.DOWN)).thenReturn(ob);
when(block.getRelative(BlockFace.UP)).thenReturn(block); when(block.getRelative(BlockFace.UP)).thenReturn(block);
assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK)); assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK));
assertTrue(br.growPlant(new GrowthBlock(block, true))); assertTrue(br.growPlant(new GrowthBlock(block, true), false));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble()); verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(bisected).setHalf(eq(Half.BOTTOM)); verify(bisected).setHalf(eq(Half.BOTTOM));
verify(bisected).setHalf(eq(Half.TOP)); verify(bisected).setHalf(eq(Half.TOP));
@ -725,7 +725,7 @@ public class BiomeRecipeTest {
when(block.getRelative(eq(BlockFace.DOWN))).thenReturn(ob); when(block.getRelative(eq(BlockFace.DOWN))).thenReturn(ob);
when(block.getRelative(eq(BlockFace.UP))).thenReturn(ob); when(block.getRelative(eq(BlockFace.UP))).thenReturn(ob);
assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK)); assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK));
assertFalse(br.growPlant(new GrowthBlock(block, true))); assertFalse(br.growPlant(new GrowthBlock(block, true), false));
} }
/** /**

View File

@ -123,6 +123,17 @@ public class EcoSystemManagerTest {
when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(liquid); when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(liquid); when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(liquid);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false); List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(16, result.size());
}
/**
* Test method for {@link world.bentobox.greenhouses.managers.EcoSystemManager#getAvailableBlocks(Greenhouse, boolean)}.
*/
@Test
public void testGetAvailableBlocksAllLiquid2() {
when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(liquid);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, true);
assertEquals(0, result.size()); assertEquals(0, result.size());
} }