2019-01-22 00:44:01 +01:00
|
|
|
package world.bentobox.greenhouses.managers;
|
|
|
|
|
2021-08-01 08:10:07 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Objects;
|
|
|
|
import java.util.Random;
|
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
import org.bukkit.Material;
|
2023-01-02 07:29:02 +01:00
|
|
|
import org.bukkit.Tag;
|
2021-04-19 16:26:43 +02:00
|
|
|
import org.bukkit.World;
|
2019-01-22 00:44:01 +01:00
|
|
|
import org.bukkit.block.Block;
|
2019-01-24 18:18:05 +01:00
|
|
|
import org.bukkit.block.BlockFace;
|
2019-01-22 00:44:01 +01:00
|
|
|
import org.bukkit.block.Hopper;
|
|
|
|
import org.bukkit.inventory.ItemStack;
|
|
|
|
import org.bukkit.scheduler.BukkitTask;
|
2021-12-31 22:32:03 +01:00
|
|
|
import org.bukkit.util.BoundingBox;
|
2021-01-10 20:57:02 +01:00
|
|
|
import org.bukkit.util.NumberConversions;
|
2019-01-22 00:44:01 +01:00
|
|
|
|
|
|
|
import world.bentobox.greenhouses.Greenhouses;
|
|
|
|
import world.bentobox.greenhouses.data.Greenhouse;
|
2021-04-19 16:26:43 +02:00
|
|
|
import world.bentobox.greenhouses.greenhouse.BiomeRecipe;
|
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
/**
|
|
|
|
* Runs the ecosystem for a greenhouse
|
|
|
|
* @author tastybento
|
|
|
|
*
|
|
|
|
*/
|
2019-07-08 00:45:47 +02:00
|
|
|
public class EcoSystemManager {
|
2019-01-22 00:44:01 +01:00
|
|
|
|
2019-09-15 03:33:54 +02:00
|
|
|
private static final int PLANTS_PER_BONEMEAL = 6;
|
2019-11-01 05:36:05 +01:00
|
|
|
private static final String MINUTES = " minutes";
|
2019-01-26 17:38:13 +01:00
|
|
|
private final Greenhouses addon;
|
|
|
|
private final GreenhouseManager g;
|
2019-01-22 00:44:01 +01:00
|
|
|
private BukkitTask plantTask;
|
|
|
|
private BukkitTask mobTask;
|
|
|
|
private BukkitTask blockTask;
|
|
|
|
private BukkitTask ecoTask;
|
|
|
|
|
|
|
|
public EcoSystemManager(Greenhouses addon, GreenhouseManager greenhouseManager) {
|
|
|
|
this.addon = addon;
|
|
|
|
this.g = greenhouseManager;
|
|
|
|
}
|
|
|
|
|
2020-08-23 02:55:38 +02:00
|
|
|
/**
|
|
|
|
* Kick off schedulers
|
|
|
|
*/
|
|
|
|
void setup() {
|
2019-01-22 00:44:01 +01:00
|
|
|
// Kick off flower growing
|
2019-11-01 05:36:05 +01:00
|
|
|
long plantTick = addon.getSettings().getPlantTick() * 60 * 20L; // In minutes
|
2019-01-22 00:44:01 +01:00
|
|
|
if (plantTick > 0) {
|
2019-11-01 05:36:05 +01:00
|
|
|
addon.log("Kicking off flower growing scheduler every " + addon.getSettings().getPlantTick() + MINUTES);
|
2019-01-25 04:11:59 +01:00
|
|
|
plantTask = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> g.getMap().getGreenhouses().forEach(this::growPlants), 80L, plantTick);
|
2019-01-22 00:44:01 +01:00
|
|
|
} else {
|
|
|
|
addon.log("Flower growth disabled.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kick block conversion growing
|
2021-04-19 16:26:43 +02:00
|
|
|
long blockTick = addon.getSettings().getBlockTick() * 60 * 20L; // In minutes
|
2019-01-22 00:44:01 +01:00
|
|
|
|
|
|
|
if (blockTick > 0) {
|
2019-11-01 05:36:05 +01:00
|
|
|
addon.log("Kicking off block conversion scheduler every " + addon.getSettings().getBlockTick() + MINUTES);
|
2019-01-25 04:11:59 +01:00
|
|
|
blockTask = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> g.getMap().getGreenhouses().forEach(this::convertBlocks), 60L, blockTick);
|
2019-01-22 00:44:01 +01:00
|
|
|
} else {
|
|
|
|
addon.log("Block conversion disabled.");
|
|
|
|
}
|
|
|
|
// Kick off g/h verification
|
2019-11-01 05:36:05 +01:00
|
|
|
long ecoTick = addon.getSettings().getEcoTick() * 60 * 20L; // In minutes
|
2019-01-22 00:44:01 +01:00
|
|
|
if (ecoTick > 0) {
|
2019-11-01 05:36:05 +01:00
|
|
|
addon.log("Kicking off greenhouse verify scheduler every " + addon.getSettings().getEcoTick() + MINUTES);
|
2019-01-25 04:11:59 +01:00
|
|
|
ecoTask = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> g.getMap().getGreenhouses().forEach(this::verify), ecoTick, ecoTick);
|
2019-01-22 00:44:01 +01:00
|
|
|
|
|
|
|
} else {
|
|
|
|
addon.log("Greenhouse verification disabled.");
|
|
|
|
}
|
|
|
|
// Kick off mob population
|
2019-11-01 05:36:05 +01:00
|
|
|
long mobTick = addon.getSettings().getMobTick() * 60 * 20L; // In minutes
|
2019-01-22 00:44:01 +01:00
|
|
|
if (mobTick > 0) {
|
2019-11-01 05:36:05 +01:00
|
|
|
addon.log("Kicking off mob populator scheduler every " + addon.getSettings().getMobTick() + MINUTES);
|
2019-01-25 04:11:59 +01:00
|
|
|
mobTask = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> g.getMap().getGreenhouses().forEach(this::addMobs), 120L, mobTick);
|
2019-01-22 00:44:01 +01:00
|
|
|
} else {
|
|
|
|
addon.log("Mob disabled.");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-01-25 04:11:59 +01:00
|
|
|
private void convertBlocks(Greenhouse gh) {
|
2021-12-31 22:32:03 +01:00
|
|
|
final World world = gh.getWorld();
|
|
|
|
final BoundingBox bb = gh.getBoundingBox();
|
2021-08-02 01:17:12 +02:00
|
|
|
if(world == null || gh.getLocation() == null || gh.getLocation().getWorld() == null
|
2021-12-31 22:32:03 +01:00
|
|
|
|| !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMaxX()) >> 4, ((int) bb.getMaxZ()) >> 4)
|
|
|
|
|| !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMinX()) >> 4, ((int) bb.getMinZ()) >> 4)){
|
2020-01-13 19:34:44 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-12-31 22:32:03 +01:00
|
|
|
final BoundingBox ibb = gh.getInternalBoundingBox();
|
|
|
|
int gh_min_x = NumberConversions.floor(ibb.getMinX());
|
|
|
|
int gh_max_x = NumberConversions.floor(ibb.getMaxX());
|
2021-09-25 20:04:01 +02:00
|
|
|
int gh_min_y = NumberConversions.floor(gh.getBoundingBox().getMinY()); // Note: this gets the floor
|
2021-12-31 22:32:03 +01:00
|
|
|
int gh_max_y = NumberConversions.floor(ibb.getMaxY());
|
|
|
|
int gh_min_z = NumberConversions.floor(ibb.getMinZ());
|
|
|
|
int gh_max_z = NumberConversions.floor(ibb.getMaxZ());
|
2021-04-19 16:26:43 +02:00
|
|
|
BiomeRecipe biomeRecipe = gh.getBiomeRecipe();
|
|
|
|
|
|
|
|
for (int x = gh_min_x; x < gh_max_x; x++) {
|
|
|
|
for (int z = gh_min_z; z < gh_max_z; z++) {
|
|
|
|
for (int y = gh_min_y; y < gh_max_y; y++) {
|
|
|
|
Block b = world.getBlockAt(x, y, z);
|
|
|
|
|
|
|
|
if(!b.isEmpty()) {
|
|
|
|
biomeRecipe.convertBlock(b);
|
|
|
|
}
|
2020-08-23 02:55:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-25 04:11:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void verify(Greenhouse gh) {
|
2021-08-02 01:17:12 +02:00
|
|
|
if(gh.getLocation() == null || gh.getLocation().getWorld() == null
|
|
|
|
|| !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)){
|
|
|
|
// Skipping verify for unloaded greenhouse
|
2020-01-13 19:34:44 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-01-17 17:41:50 +01:00
|
|
|
gh.getBiomeRecipe().checkRecipe(gh).thenAccept(rs -> {
|
|
|
|
if (!rs.isEmpty()) {
|
|
|
|
addon.log("Greenhouse failed verification at " + gh.getLocation());
|
|
|
|
g.removeGreenhouse(gh);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-01-25 04:11:59 +01:00
|
|
|
}
|
|
|
|
|
2023-03-02 03:30:35 +01:00
|
|
|
/**
|
|
|
|
* Try to spawn mobs in a greenhouse
|
|
|
|
* @param gh greenhouse
|
|
|
|
* @return true if mobs were spawned, false if not
|
|
|
|
*/
|
|
|
|
boolean addMobs(Greenhouse gh) {
|
2021-12-31 22:32:03 +01:00
|
|
|
final BoundingBox bb = gh.getBoundingBox();
|
2021-08-02 01:17:12 +02:00
|
|
|
if(gh.getLocation() == null || gh.getLocation().getWorld() == null || gh.getWorld() == null
|
2023-03-02 03:30:35 +01:00
|
|
|
|| !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMaxX()) >> 4, ((int) bb.getMaxZ()) >> 4)
|
|
|
|
|| !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMinX()) >> 4, ((int) bb.getMinZ()) >> 4)){
|
2021-08-02 01:17:12 +02:00
|
|
|
// Skipping addmobs for unloaded greenhouse
|
2023-03-02 03:30:35 +01:00
|
|
|
return false;
|
2020-01-13 19:34:44 +01:00
|
|
|
}
|
2019-01-25 04:11:59 +01:00
|
|
|
if (gh.getBiomeRecipe().noMobs()) {
|
2023-03-02 03:30:35 +01:00
|
|
|
return false;
|
2019-01-25 04:11:59 +01:00
|
|
|
}
|
2019-11-01 22:50:20 +01:00
|
|
|
// Check greenhouse chunks are loaded
|
2021-12-31 22:32:03 +01:00
|
|
|
for (double blockX = bb.getMinX(); blockX < bb.getMaxX(); blockX+=16) {
|
|
|
|
for (double blockZ = bb.getMinZ(); blockZ < bb.getMaxZ(); blockZ+=16) {
|
2019-07-08 16:53:03 +02:00
|
|
|
int chunkX = (int)(blockX / 16);
|
|
|
|
int chunkZ = (int)(blockZ / 16);
|
|
|
|
if (!gh.getWorld().isChunkLoaded(chunkX, chunkZ)) {
|
2023-03-02 03:30:35 +01:00
|
|
|
return false;
|
2019-07-08 16:53:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-01 22:50:20 +01:00
|
|
|
// Count the entities in the greenhouse
|
|
|
|
long sum = gh.getWorld().getEntities().stream()
|
|
|
|
.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
|
2021-09-18 19:12:47 +02:00
|
|
|
List<GrowthBlock> list = new ArrayList<>(getAvailableBlocks(gh, false));
|
2019-11-01 22:50:20 +01:00
|
|
|
Collections.shuffle(list, new Random(System.currentTimeMillis()));
|
2021-09-18 19:12:47 +02:00
|
|
|
Iterator<GrowthBlock> it = list.iterator();
|
2019-11-01 22:50:20 +01:00
|
|
|
// Check if the greenhouse is full
|
2023-03-01 17:26:34 +01:00
|
|
|
if (sum >= gh.getBiomeRecipe().getMaxMob()) {
|
2023-03-02 03:30:35 +01:00
|
|
|
return false;
|
2023-03-01 17:26:34 +01:00
|
|
|
}
|
2019-11-01 22:50:20 +01:00
|
|
|
while (it.hasNext() && (sum == 0 || gh.getArea() / sum >= gh.getBiomeRecipe().getMobLimit())) {
|
|
|
|
// Spawn something if chance says so
|
2021-09-18 19:12:47 +02:00
|
|
|
if (gh.getBiomeRecipe().spawnMob(it.next().block())) {
|
2019-11-01 22:50:20 +01:00
|
|
|
// Add a mob to the sum in the greenhouse
|
|
|
|
sum++;
|
2019-01-25 04:11:59 +01:00
|
|
|
}
|
2019-11-01 22:50:20 +01:00
|
|
|
}
|
2023-03-02 03:30:35 +01:00
|
|
|
return sum > 0;
|
2019-01-25 04:11:59 +01:00
|
|
|
}
|
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
/**
|
2019-01-25 04:11:59 +01:00
|
|
|
* Grow plants in the greenhouse
|
2019-01-22 00:44:01 +01:00
|
|
|
* @param gh - greenhouse
|
|
|
|
*/
|
|
|
|
private void growPlants(Greenhouse gh) {
|
2021-12-31 22:32:03 +01:00
|
|
|
final BoundingBox bb = gh.getBoundingBox();
|
2021-08-02 01:17:12 +02:00
|
|
|
if (gh.getLocation() == null || gh.getLocation().getWorld() == null
|
2021-12-31 22:32:03 +01:00
|
|
|
|| !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMaxX()) >> 4, ((int) bb.getMaxZ()) >> 4) || !gh.getLocation().getWorld().isChunkLoaded(((int) bb.getMinX()) >> 4, ((int) bb.getMinZ()) >> 4)){
|
2021-08-02 01:17:12 +02:00
|
|
|
//Skipping growplants for unloaded greenhouse
|
2020-01-13 19:34:44 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-01-22 00:44:01 +01:00
|
|
|
int bonemeal = getBoneMeal(gh);
|
|
|
|
if (bonemeal > 0) {
|
|
|
|
// Get a list of all available blocks
|
2022-12-30 03:59:50 +01:00
|
|
|
List<GrowthBlock> list = getAvailableBlocks(gh, false);
|
2021-01-12 02:12:18 +01:00
|
|
|
Collections.shuffle(list);
|
2022-12-30 03:59:50 +01:00
|
|
|
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();
|
2019-09-15 03:33:54 +02:00
|
|
|
if (plantsGrown > 0) {
|
|
|
|
setBoneMeal(gh, bonemeal - (int)Math.ceil((double)plantsGrown / PLANTS_PER_BONEMEAL ));
|
|
|
|
}
|
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a hopper's bone meal to this value
|
|
|
|
* @param gh - greenhouse
|
|
|
|
* @param value - value to set
|
|
|
|
*/
|
|
|
|
private void setBoneMeal(Greenhouse gh, int value) {
|
|
|
|
Hopper hopper = getHopper(gh);
|
|
|
|
if (hopper != null) {
|
|
|
|
hopper.getInventory().remove(Material.BONE_MEAL);
|
|
|
|
hopper.getInventory().addItem(new ItemStack(Material.BONE_MEAL, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-12-30 03:59:50 +01:00
|
|
|
public record GrowthBlock(Block block, Boolean floor) {}
|
2021-09-18 19:12:47 +02:00
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
/**
|
2020-08-23 02:55:38 +02:00
|
|
|
* Get a list of the lowest level blocks inside the greenhouse. May be air, liquid or plants.
|
2023-01-02 07:29:02 +01:00
|
|
|
* These blocks sit just above solid blocks. Leaves are ignored too.
|
2019-01-22 00:44:01 +01:00
|
|
|
* @param gh - greenhouse
|
2021-04-19 16:26:43 +02:00
|
|
|
* @param ignoreLiquid - true if liquid blocks should be treated like air blocks
|
2019-01-22 00:44:01 +01:00
|
|
|
* @return List of blocks
|
|
|
|
*/
|
2021-09-18 19:12:47 +02:00
|
|
|
protected List<GrowthBlock> getAvailableBlocks(Greenhouse gh, boolean ignoreLiquid) {
|
2021-12-31 22:32:03 +01:00
|
|
|
final BoundingBox bb = gh.getBoundingBox();
|
|
|
|
final BoundingBox ibb = gh.getInternalBoundingBox();
|
2021-09-18 19:12:47 +02:00
|
|
|
List<GrowthBlock> result = new ArrayList<>();
|
2021-08-02 01:17:12 +02:00
|
|
|
if (gh.getWorld() == null) return result;
|
2021-12-31 22:32:03 +01:00
|
|
|
for (double x = ibb.getMinX(); x < ibb.getMaxX(); x++) {
|
|
|
|
for (double z = ibb.getMinZ(); z < ibb.getMaxZ(); z++) {
|
|
|
|
for (double y = ibb.getMaxY() - 1; y >= bb.getMinY(); y--) {
|
2021-01-10 20:57:02 +01:00
|
|
|
Block b = gh.getWorld().getBlockAt(NumberConversions.floor(x), NumberConversions.floor(y), NumberConversions.floor(z));
|
2023-06-05 02:36:08 +02:00
|
|
|
if (checkBlock(result, b, ignoreLiquid)) {
|
|
|
|
break;
|
2019-01-22 00:44:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-05 02:36:08 +02:00
|
|
|
private boolean checkBlock(List<GrowthBlock> result, Block b, boolean ignoreLiquid) {
|
|
|
|
// Check floor blocks
|
|
|
|
if (!ignoreLiquid) {
|
|
|
|
// Check ceiling blocks
|
|
|
|
if (b.isEmpty() && !b.getRelative(BlockFace.UP).isEmpty()) {
|
|
|
|
result.add(new GrowthBlock(b, false));
|
|
|
|
}
|
|
|
|
if (!b.isEmpty() && !Tag.LEAVES.isTagged(b.getType())
|
|
|
|
&& (b.getRelative(BlockFace.UP).isEmpty()
|
|
|
|
|| b.getRelative(BlockFace.UP).isPassable()
|
|
|
|
|| Tag.LEAVES.isTagged(b.getRelative(BlockFace.UP).getType())
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!b.isEmpty() && !b.isLiquid() && b.getRelative(BlockFace.UP).isLiquid()) {
|
|
|
|
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2021-09-18 19:12:47 +02:00
|
|
|
|
2019-01-22 00:44:01 +01:00
|
|
|
private int getBoneMeal(Greenhouse gh) {
|
|
|
|
Hopper hopper = getHopper(gh);
|
|
|
|
if (hopper == null || !hopper.getInventory().contains(Material.BONE_MEAL)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return Arrays.stream(hopper.getInventory().getContents()).filter(Objects::nonNull)
|
|
|
|
.filter(i -> i.getType().equals(Material.BONE_MEAL))
|
|
|
|
.mapToInt(ItemStack::getAmount).sum();
|
|
|
|
}
|
|
|
|
|
2021-08-02 01:17:12 +02:00
|
|
|
/**
|
|
|
|
* Get the hopper
|
|
|
|
* @param gh greenhouse
|
|
|
|
* @return hopper block or null if it does not exist
|
|
|
|
*/
|
2019-01-22 00:44:01 +01:00
|
|
|
private Hopper getHopper(Greenhouse gh) {
|
2021-02-13 19:30:05 +01:00
|
|
|
// Check if the hopper block is still a hopper
|
2021-08-02 01:17:12 +02:00
|
|
|
if (gh.getRoofHopperLocation() == null || !gh.getRoofHopperLocation().getBlock().getType().equals(Material.HOPPER)) {
|
2019-01-22 00:44:01 +01:00
|
|
|
gh.setRoofHopperLocation(null);
|
|
|
|
return null;
|
|
|
|
}
|
2019-01-23 05:54:43 +01:00
|
|
|
return (Hopper)gh.getRoofHopperLocation().getBlock().getState();
|
2019-01-22 00:44:01 +01:00
|
|
|
}
|
|
|
|
|
2020-08-23 02:55:38 +02:00
|
|
|
/**
|
|
|
|
* Cancel all the scheduled tasks
|
|
|
|
*/
|
2019-01-22 00:44:01 +01:00
|
|
|
public void cancel() {
|
|
|
|
plantTask.cancel();
|
|
|
|
mobTask.cancel();
|
|
|
|
blockTask.cancel();
|
|
|
|
ecoTask.cancel();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|