From 9be8d9eb184527b860e4c204c0ce2347275d2641 Mon Sep 17 00:00:00 2001 From: Roch Blonndiaux Date: Mon, 13 Mar 2023 16:41:14 +0100 Subject: [PATCH 1/3] Optimization concept --- .../mmoitems/manager/WorldGenManager.java | 179 +++++++++++------- 1 file changed, 106 insertions(+), 73 deletions(-) diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java index 06d88b29..b369a747 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java @@ -4,6 +4,7 @@ import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.block.CustomBlock; import net.Indyuce.mmoitems.api.block.WorldGenTemplate; +import net.Indyuce.mmoitems.util.Pair; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -13,99 +14,131 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.scheduler.BukkitRunnable; import java.util.HashMap; import java.util.Map; +import java.util.Queue; import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; public class WorldGenManager implements Listener, Reloadable { - private final Map templates = new HashMap<>(); + private final Map templates = new HashMap<>(); - /* - * maps a custom block to the world generator template so that it is later - * easier to access all the blocks which must be placed when generating a - * world. - */ - private final Map assigned = new HashMap<>(); + /* + * maps a custom block to the world generator template so that it is later + * easier to access all the blocks which must be placed when generating a + * world. + */ + private final Map assigned = new HashMap<>(); + private final Queue> modificationsQueue = new ConcurrentLinkedQueue<>(); - private static final BlockFace[] faces = { BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP }; - private static final Random random = new Random(); + private static final BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP}; + private static final Random random = new Random(); - public WorldGenManager() { + public WorldGenManager() { + /* + * load the worldGenManager even if world gen is not enabled so that if + * admins temporarily disable it, there is no console error spam saying + * MI could not find corresponding gen template in config + */ + reload(); - /* - * load the worldGenManager even if world gen is not enabled so that if - * admins temporarily disable it, there is no console error spam saying - * MI could not find corresponding gen template in config - */ - reload(); + if (MMOItems.plugin.getLanguage().worldGenEnabled) + Bukkit.getPluginManager().registerEvents(this, MMOItems.plugin); + } - if (MMOItems.plugin.getLanguage().worldGenEnabled) - Bukkit.getPluginManager().registerEvents(this, MMOItems.plugin); - } + public WorldGenTemplate getOrThrow(String id) { + Validate.isTrue(templates.containsKey(id), "Could not find gen template with ID '" + id + "'"); - public WorldGenTemplate getOrThrow(String id) { - Validate.isTrue(templates.containsKey(id), "Could not find gen template with ID '" + id + "'"); + return templates.get(id); + } - return templates.get(id); - } + /* + * it is mandatory to call this function after registering the custom block + * if you want the custom block to be spawning in the worlds + */ + public void assign(CustomBlock block, WorldGenTemplate template) { + Validate.notNull(template, "Cannot assign a null template to a custom block"); - /* - * it is mandatory to call this function after registering the custom block - * if you want the custom block to be spawning in the worlds - */ - public void assign(CustomBlock block, WorldGenTemplate template) { - Validate.notNull(template, "Cannot assign a null template to a custom block"); + assigned.put(block, template); + } - assigned.put(block, template); - } + @EventHandler + public void onChunkLoad(ChunkLoadEvent e) { + if (e.isNewChunk()) + return; - @EventHandler - public void a(ChunkLoadEvent event) { - if(event.isNewChunk()) { - Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> assigned.forEach((block, template) -> { - if(!template.canGenerateInWorld(event.getWorld())) { - return; - } - if(random.nextDouble() < template.getChunkChance()) - for(int i = 0; i < template.getVeinCount(); i++) { - int y = random.nextInt(template.getMaxDepth() - template.getMinDepth() + 1) + template.getMinDepth(); - Location generatePoint = event.getChunk().getBlock(random.nextInt(16), y, random.nextInt(16)).getLocation(); + if (e.isAsynchronous()) + generate(e); + else + Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> generate(e)); + } - if(template.canGenerate(generatePoint)) { - Block modify = generatePoint.getWorld().getBlockAt(generatePoint); + private void generate(ChunkLoadEvent e) { + assigned.entrySet() + .stream() + .filter(entry -> entry.getValue().canGenerateInWorld(e.getWorld())) + .forEach(entry -> { + final CustomBlock block = entry.getKey(); + final WorldGenTemplate template = entry.getValue(); + if (random.nextDouble() > template.getChunkChance()) + return; - for(int j = 0; j < template.getVeinSize(); j++) { - if(template.canReplace(modify.getType())) { - final Block fModify = modify; - Bukkit.getScheduler().runTask(MMOItems.plugin, () -> { - fModify.setType(block.getState().getType(), false); - fModify.setBlockData(block.getState().getBlockData(), false); - }); - } + for (int i = 0; i < template.getVeinCount(); i++) { + int y = random.nextInt(template.getMaxDepth() - template.getMinDepth() + 1) + template.getMinDepth(); + Location generatePoint = e.getChunk().getBlock(random.nextInt(16), y, random.nextInt(16)).getLocation(); - BlockFace nextFace = faces[random.nextInt(faces.length)]; - modify = modify.getRelative(nextFace); - } - } - } - })); - } - } + if (!template.canGenerate(generatePoint) || generatePoint.getWorld() == null) + continue; + Block modify = generatePoint.getWorld().getBlockAt(generatePoint); - public void reload() { - assigned.clear(); - templates.clear(); + for (int j = 0; j < template.getVeinSize(); j++) { + if (template.canReplace(modify.getType())) + this.modificationsQueue.add(Pair.of(modify.getLocation(), block)); + BlockFace nextFace = faces[random.nextInt(faces.length)]; + modify = modify.getRelative(nextFace); + } + } + }); - FileConfiguration config = new ConfigFile("gen-templates").getConfig(); - for(String key : config.getKeys(false)) { - try { - WorldGenTemplate template = new WorldGenTemplate(config.getConfigurationSection(key)); - templates.put(template.getId(), template); - } catch (IllegalArgumentException exception) { - MMOItems.plugin.getLogger().log(Level.WARNING, "An error occurred when loading gen template '" + key + "': " + exception.getMessage()); - } - } - } + + new BukkitRunnable() { + @Override + public void run() { + if (modificationsQueue.isEmpty()) { + this.cancel(); + return; + } + Pair pair = modificationsQueue.poll(); + + if (Bukkit.isPrimaryThread()) + setBlockData(pair.getKey().getBlock(), pair.getValue()); + else + Bukkit.getScheduler().runTask(MMOItems.plugin, () -> setBlockData(pair.getKey().getBlock(), pair.getValue())); + } + }.runTaskTimer(MMOItems.plugin, 0, 5); + } + + + private void setBlockData(Block fModify, CustomBlock block) { + fModify.setType(block.getState().getType(), false); + fModify.setBlockData(block.getState().getBlockData(), false); + } + + public void reload() { + assigned.clear(); + templates.clear(); + + FileConfiguration config = new ConfigFile("gen-templates").getConfig(); + for (String key : config.getKeys(false)) { + try { + WorldGenTemplate template = new WorldGenTemplate(config.getConfigurationSection(key)); + templates.put(template.getId(), template); + } catch (IllegalArgumentException exception) { + MMOItems.plugin.getLogger().log(Level.WARNING, "An error occurred when loading gen template '" + key + "': " + exception.getMessage()); + } + } + } } From 7f0b29fd2852f396a7342b0aac0bfaf6afb7fd69 Mon Sep 17 00:00:00 2001 From: Roch Blonndiaux Date: Mon, 13 Mar 2023 17:59:27 +0100 Subject: [PATCH 2/3] Final optimization --- .../java/net/Indyuce/mmoitems/MMOItems.java | 4 +- .../listener/WorldGenerationListener.java | 27 +++++++ .../mmoitems/manager/WorldGenManager.java | 74 +++++++++---------- .../tasks/CustomBlocksPopulateTask.java | 72 ++++++++++++++++++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java create mode 100644 MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java index 69428310..64617b2e 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java @@ -254,7 +254,6 @@ public class MMOItems extends JavaPlugin { @Override public void onDisable() { - // Support for early plugin disabling if (!hasLoadedSuccessfully) return; @@ -269,6 +268,9 @@ public class MMOItems extends JavaPlugin { for (Player player : Bukkit.getOnlinePlayers()) if (player.getOpenInventory() != null && player.getOpenInventory().getTopInventory().getHolder() instanceof PluginInventory) player.closeInventory(); + + // WorldGen + this.worldGenManager.unload(); } public String getPrefix() { diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java new file mode 100644 index 00000000..165e3b20 --- /dev/null +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmoitems.listener; + +import net.Indyuce.mmoitems.manager.WorldGenManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkLoadEvent; + +/** + * mmoitems + * 13/03/2023 + * + * @author Roch Blondiaux (Kiwix). + */ +public class WorldGenerationListener implements Listener { + + private final WorldGenManager manager; + + public WorldGenerationListener(WorldGenManager manager) { + this.manager = manager; + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent e) { + if (!e.isNewChunk()) return; + manager.populate(e); + } +} diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java index b369a747..7fd41ebd 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java @@ -4,6 +4,8 @@ import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.block.CustomBlock; import net.Indyuce.mmoitems.api.block.WorldGenTemplate; +import net.Indyuce.mmoitems.listener.WorldGenerationListener; +import net.Indyuce.mmoitems.tasks.CustomBlocksPopulateTask; import net.Indyuce.mmoitems.util.Pair; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -11,10 +13,10 @@ import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; +import org.bukkit.event.HandlerList; import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; @@ -23,7 +25,7 @@ import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; -public class WorldGenManager implements Listener, Reloadable { +public class WorldGenManager implements Reloadable { private final Map templates = new HashMap<>(); /* @@ -37,6 +39,9 @@ public class WorldGenManager implements Listener, Reloadable { private static final BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP}; private static final Random random = new Random(); + private WorldGenerationListener listener; + private CustomBlocksPopulateTask task; + public WorldGenManager() { /* * load the worldGenManager even if world gen is not enabled so that if @@ -44,9 +49,6 @@ public class WorldGenManager implements Listener, Reloadable { * MI could not find corresponding gen template in config */ reload(); - - if (MMOItems.plugin.getLanguage().worldGenEnabled) - Bukkit.getPluginManager().registerEvents(this, MMOItems.plugin); } public WorldGenTemplate getOrThrow(String id) { @@ -65,18 +67,17 @@ public class WorldGenManager implements Listener, Reloadable { assigned.put(block, template); } - @EventHandler - public void onChunkLoad(ChunkLoadEvent e) { - if (e.isNewChunk()) - return; - - if (e.isAsynchronous()) - generate(e); - else - Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> generate(e)); + public void populate(@NotNull ChunkLoadEvent e) { + Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> { + preprocess(e); + if (task != null && task.isRunning()) + return; + task = new CustomBlocksPopulateTask(this); + task.start(); + }); } - private void generate(ChunkLoadEvent e) { + private @Blocking void preprocess(ChunkLoadEvent e) { assigned.entrySet() .stream() .filter(entry -> entry.getValue().canGenerateInWorld(e.getWorld())) @@ -102,34 +103,20 @@ public class WorldGenManager implements Listener, Reloadable { } } }); - - - new BukkitRunnable() { - @Override - public void run() { - if (modificationsQueue.isEmpty()) { - this.cancel(); - return; - } - Pair pair = modificationsQueue.poll(); - - if (Bukkit.isPrimaryThread()) - setBlockData(pair.getKey().getBlock(), pair.getValue()); - else - Bukkit.getScheduler().runTask(MMOItems.plugin, () -> setBlockData(pair.getKey().getBlock(), pair.getValue())); - } - }.runTaskTimer(MMOItems.plugin, 0, 5); } - - private void setBlockData(Block fModify, CustomBlock block) { - fModify.setType(block.getState().getType(), false); - fModify.setBlockData(block.getState().getBlockData(), false); + public Queue> getModificationsQueue() { + return modificationsQueue; } public void reload() { + // Listener + if (listener != null) + HandlerList.unregisterAll(listener); + assigned.clear(); templates.clear(); + modificationsQueue.clear(); FileConfiguration config = new ConfigFile("gen-templates").getConfig(); for (String key : config.getKeys(false)) { @@ -140,5 +127,16 @@ public class WorldGenManager implements Listener, Reloadable { MMOItems.plugin.getLogger().log(Level.WARNING, "An error occurred when loading gen template '" + key + "': " + exception.getMessage()); } } + + // Listeners + if (MMOItems.plugin.getLanguage().worldGenEnabled) + Bukkit.getPluginManager().registerEvents(listener = new WorldGenerationListener(this), MMOItems.plugin); + } + + public void unload() { + if (listener != null) + HandlerList.unregisterAll(listener); + if (task != null) + task.stop(); } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java new file mode 100644 index 00000000..d9630e05 --- /dev/null +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java @@ -0,0 +1,72 @@ +package net.Indyuce.mmoitems.tasks; + +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.block.CustomBlock; +import net.Indyuce.mmoitems.manager.WorldGenManager; +import net.Indyuce.mmoitems.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Queue; + +/** + * mmoitems + * 13/03/2023 + * + * @author Roch Blondiaux (Kiwix). + */ +public class CustomBlocksPopulateTask extends BukkitRunnable { + + private final WorldGenManager manager; + private boolean running = false; + + public CustomBlocksPopulateTask(WorldGenManager manager) { + this.manager = manager; + } + + @Override + public void run() { + final Queue> modificationsQueue = manager.getModificationsQueue(); + final Pair pair = modificationsQueue.poll(); + + // If the queue is empty, cancel the task + if (pair == null) { + this.stop(); + return; + } + + // If the chunk is not loaded, skip it + if (!pair.getKey().getChunk().isLoaded()) + return; + + // If the block is already modified, skip it + if (pair.getKey().getBlock().getBlockData().equals(pair.getValue().getState().getBlockData())) + return; + + // Change the block + Bukkit.getScheduler().runTask(MMOItems.plugin, () -> setBlockData(pair.getKey().getBlock(), pair.getValue())); + } + + private void setBlockData(Block fModify, CustomBlock block) { + fModify.setType(block.getState().getType(), false); + fModify.setBlockData(block.getState().getBlockData(), false); + } + + public void start() { + if (running) return; + running = true; + this.runTaskTimerAsynchronously(MMOItems.plugin, 0, 1); + } + + public void stop() { + if (!running) return; + running = false; + this.cancel(); + } + + public boolean isRunning() { + return running; + } +} From b138c0dcc9fa1c511743a948c19dc225f8208f62 Mon Sep 17 00:00:00 2001 From: Roch Blonndiaux Date: Tue, 14 Mar 2023 15:47:16 +0100 Subject: [PATCH 3/3] Some more optimization & runnable -> block populator --- .../java/net/Indyuce/mmoitems/MMOItems.java | 2 +- .../mmoitems/api/block/WorldGenTemplate.java | 259 ++++++++---------- .../mmoitems/api/world/MMOBlockPopulator.java | 75 +++++ .../listener/WorldGenerationListener.java | 21 +- .../mmoitems/manager/WorldGenManager.java | 76 +---- .../tasks/CustomBlocksPopulateTask.java | 72 ----- 6 files changed, 222 insertions(+), 283 deletions(-) create mode 100644 MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/world/MMOBlockPopulator.java delete mode 100644 MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java index 64617b2e..c4b542fa 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/MMOItems.java @@ -168,7 +168,7 @@ public class MMOItems extends JavaPlugin { dropTableManager = new DropTableManager(); worldGenManager = new WorldGenManager(); blockManager = new BlockManager(); - statManager.reload(false); + statManager.reload(false); PluginUtils.hookDependencyIfPresent("Vault", u -> vaultSupport = new VaultSupport()); diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/block/WorldGenTemplate.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/block/WorldGenTemplate.java index 52497a54..25255e39 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/block/WorldGenTemplate.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/block/WorldGenTemplate.java @@ -1,171 +1,138 @@ package net.Indyuce.mmoitems.api.block; -import java.util.ArrayList; -import java.util.List; - import org.apache.commons.lang.Validate; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.block.Biome; +import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.configuration.ConfigurationSection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class WorldGenTemplate { - private final String id; - private final double chunkChance; - private final int minDepth, maxDepth, veinSize, veinCount; + private final String id; + private final double chunkChance; + private final int minDepth, maxDepth, veinSize, veinCount; - private final List replaceable = new ArrayList<>(); - private final List bordering = new ArrayList<>(); - private final List notBordering = new ArrayList<>(); - private final List worldWhitelist = new ArrayList<>(), worldBlacklist = new ArrayList<>(); - private final List biomeWhitelist = new ArrayList<>(), biomeBlacklist = new ArrayList<>(); - private final boolean slimeChunk; + private final List replaceable = new ArrayList<>(); + private final List bordering = new ArrayList<>(); + private final List notBordering = new ArrayList<>(); + private final List worldWhitelist = new ArrayList<>(), worldBlacklist = new ArrayList<>(); + private final List biomeWhitelist = new ArrayList<>(), biomeBlacklist = new ArrayList<>(); + private final boolean slimeChunk; - public WorldGenTemplate(ConfigurationSection config) { - Validate.notNull(config, "Could not read gen template config"); + public WorldGenTemplate(ConfigurationSection config) { + Validate.notNull(config, "Could not read gen template config"); - id = config.getName().toLowerCase().replace(" ", "-").replace("_", "-"); - config.getStringList("replace").forEach(str -> replaceable.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); - config.getStringList("bordering").forEach(str -> bordering.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); - config.getStringList("not-bordering").forEach(str -> notBordering.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); - - for (String world : config.getStringList("worlds")) { - (world.startsWith("!") ? worldBlacklist : worldWhitelist).add(world.toLowerCase().replace("_", "-")); - } - for (String biome : config.getStringList("biomes")) { - (biome.startsWith("!") ? biomeBlacklist : biomeWhitelist).add(biome.toUpperCase().replace("-", "_").replace(" ", "_")); - } - chunkChance = config.getDouble("chunk-chance"); - slimeChunk = config.getBoolean("slime-chunk", false); + id = config.getName().toLowerCase().replace(" ", "-").replace("_", "-"); + config.getStringList("replace").forEach(str -> replaceable.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); + config.getStringList("bordering").forEach(str -> bordering.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); + config.getStringList("not-bordering").forEach(str -> notBordering.add(Material.valueOf(str.toUpperCase().replace("-", "_").replace(" ", "_")))); - String[] depth = config.getString("depth").split("="); - minDepth = Integer.parseInt(depth[0]); - maxDepth = Integer.parseInt(depth[1]); - - //Validate.isTrue(minDepth >= 0, "Min depth must be greater than 0"); - //Validate.isTrue(maxDepth < 256, "Max depth must be at most 255"); - - veinSize = config.getInt("vein-size"); - veinCount = config.getInt("vein-count"); - - Validate.isTrue(veinSize > 0 && veinCount > 0, "Vein size and count must be at least 1"); - } - - public String getId() { - return id; - } - - public double getChunkChance() { - return chunkChance; - } - - public int getVeinSize() { - return veinSize; - } - - public int getVeinCount() { - return veinCount; - } - - public int getMinDepth() { - return minDepth; - } - - public int getMaxDepth() { - return maxDepth; - } - - public boolean canGenerateInWorld(World w) { - // check world list - String world = w.getName().toLowerCase().replace("_", "-"); - if (!worldWhitelist.isEmpty() && !worldWhitelist.contains(world)) { - return false; + for (String world : config.getStringList("worlds")) { + (world.startsWith("!") ? worldBlacklist : worldWhitelist).add(world.toLowerCase().replace("_", "-")); } - if (!worldBlacklist.isEmpty() && worldBlacklist.contains(world)) { - return false; + for (String biome : config.getStringList("biomes")) { + (biome.startsWith("!") ? biomeBlacklist : biomeWhitelist).add(biome.toUpperCase().replace("-", "_").replace(" ", "_")); } + chunkChance = config.getDouble("chunk-chance"); + slimeChunk = config.getBoolean("slime-chunk", false); + + String[] depth = config.getString("depth").split("="); + minDepth = Integer.parseInt(depth[0]); + maxDepth = Integer.parseInt(depth[1]); + + //Validate.isTrue(minDepth >= 0, "Min depth must be greater than 0"); + //Validate.isTrue(maxDepth < 256, "Max depth must be at most 255"); + + veinSize = config.getInt("vein-size"); + veinCount = config.getInt("vein-count"); + + Validate.isTrue(veinSize > 0 && veinCount > 0, "Vein size and count must be at least 1"); + } + + public String getId() { + return id; + } + + public double getChunkChance() { + return chunkChance; + } + + public int getVeinSize() { + return veinSize; + } + + public int getVeinCount() { + return veinCount; + } + + public int getMinDepth() { + return minDepth; + } + + public int getMaxDepth() { + return maxDepth; + } + + public boolean canGenerateInWorld(String worldName) { + // check world list + String world = worldName.toLowerCase().replace("_", "-"); + if (!worldWhitelist.isEmpty() && !worldWhitelist.contains(world)) + return false; + return worldBlacklist.isEmpty() || !worldBlacklist.contains(world); + } + + public boolean canGenerate(Location pos) { + + // check biome list + Biome biome = pos.getWorld().getBiome(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + if ((!biomeWhitelist.isEmpty() && !biomeWhitelist.contains(biome.name())) + || (!biomeBlacklist.isEmpty() && biomeBlacklist.contains(biome.name()))) + return false; + + // check extra options + if (slimeChunk && !pos.getChunk().isSlimeChunk()) + return false; + + if (!bordering.isEmpty()) { + if (!checkIfBorderingBlocks(pos)) + return false; + } + + if (!notBordering.isEmpty()) + return checkIfNotBorderingBlocks(pos); + + // can generate if no restrictions applied return true; - } - - public boolean canGenerate(Location pos) { + } - // check biome list - Biome biome = pos.getWorld().getBiome(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); - if (!biomeWhitelist.isEmpty() && !biomeWhitelist.contains(biome.name())) { - return false; - } - if (!biomeBlacklist.isEmpty() && biomeBlacklist.contains(biome.name())) { - return false; - } - - // check extra options - if (slimeChunk && !pos.getChunk().isSlimeChunk()) { - return false; - } - - if(!bordering.isEmpty()) { - if(!checkIfBorderingBlocks(pos)) { - return false; - } - } - - if(!notBordering.isEmpty()) { - return checkIfNotBorderingBlocks(pos); - } - - // can generate if no restrictions applied - return true; - } + public boolean canReplace(Material type) { + return replaceable.isEmpty() || replaceable.contains(type); + } - public boolean canReplace(Material type) { - return replaceable.isEmpty() || replaceable.contains(type); - } - - public boolean canBorder(Material type) { + public boolean canBorder(Material type) { return bordering.isEmpty() || bordering.contains(type); } - + public boolean checkIfBorderingBlocks(Location pos) { - if(!canBorder(pos.getBlock().getRelative(BlockFace.NORTH).getType())) { - return false; - } - if(!canBorder(pos.getBlock().getRelative(BlockFace.EAST).getType())) { - return false; - } - if(!canBorder(pos.getBlock().getRelative(BlockFace.SOUTH).getType())) { - return false; - } - if(!canBorder(pos.getBlock().getRelative(BlockFace.WEST).getType())) { - return false; - } - if(!canBorder(pos.getBlock().getRelative(BlockFace.UP).getType())) { - return false; - } - return canBorder(pos.getBlock().getRelative(BlockFace.DOWN).getType()); - } - - public boolean canNotBorder(Material type) { + return Arrays.stream(BlockFace.values()) + .map(pos.getBlock()::getRelative) + .map(Block::getType) + .allMatch(this::canBorder); + } + + public boolean canNotBorder(Material type) { return !notBordering.isEmpty() && notBordering.contains(type); } - - public boolean checkIfNotBorderingBlocks(Location pos) { - if(canNotBorder(pos.getBlock().getRelative(BlockFace.NORTH).getType())) { - return false; - } - if(canNotBorder(pos.getBlock().getRelative(BlockFace.EAST).getType())) { - return false; - } - if(canNotBorder(pos.getBlock().getRelative(BlockFace.SOUTH).getType())) { - return false; - } - if(canNotBorder(pos.getBlock().getRelative(BlockFace.WEST).getType())) { - return false; - } - if(canNotBorder(pos.getBlock().getRelative(BlockFace.UP).getType())) { - return false; - } - return !canNotBorder(pos.getBlock().getRelative(BlockFace.DOWN).getType()); - } + + public boolean checkIfNotBorderingBlocks(Location pos) { + return Arrays.stream(BlockFace.values()) + .map(pos.getBlock()::getRelative) + .map(Block::getType) + .allMatch(this::canNotBorder); + } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/world/MMOBlockPopulator.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/world/MMOBlockPopulator.java new file mode 100644 index 00000000..74139abf --- /dev/null +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/world/MMOBlockPopulator.java @@ -0,0 +1,75 @@ +package net.Indyuce.mmoitems.api.world; + +import net.Indyuce.mmoitems.api.block.CustomBlock; +import net.Indyuce.mmoitems.api.block.WorldGenTemplate; +import net.Indyuce.mmoitems.manager.WorldGenManager; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.LimitedRegion; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Random; + +/** + * mmoitems + * 14/03/2023 + * + * @author Roch Blondiaux (Kiwix). + */ +public class MMOBlockPopulator extends BlockPopulator { + + private static final BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP}; + + private final WorldGenManager manager; + private final World world; + + public MMOBlockPopulator(World world, WorldGenManager manager) { + this.manager = manager; + this.world = world; + } + + @Override + public void populate(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull LimitedRegion limitedRegion) { + final Map assigned = manager.assigned(); + assigned.entrySet() + .stream() + .filter(entry -> entry.getValue().canGenerateInWorld(worldInfo.getName())) + .filter(entry -> entry.getValue().getMinDepth() >= worldInfo.getMinHeight()) + .filter(entry -> entry.getValue().getMaxDepth() <= worldInfo.getMaxHeight()) + .forEach(entry -> { + final CustomBlock block = entry.getKey(); + final WorldGenTemplate template = entry.getValue(); + if (random.nextDouble() > template.getChunkChance()) + return; + + for (int i = 0; i < template.getVeinCount(); i++) { + int x = chunkX * 16 + random.nextInt(16); + int y = random.nextInt(template.getMaxDepth() - template.getMinDepth() + 1) + template.getMinDepth(); + int z = chunkZ * 16 + random.nextInt(16); + Location generatePoint = new Location(world, x, y, z); + + if (!template.canGenerate(generatePoint) || generatePoint.getWorld() == null) + continue; + Block modify = generatePoint.getWorld().getBlockAt(generatePoint); + + // MMOItems.log("Generating " + block.getId() + " at x: " + generatePoint.getBlockX() + " y: " + generatePoint.getBlockY() + " z: " + generatePoint.getBlockZ()); + for (int j = 0; j < template.getVeinSize(); j++) { + if (!limitedRegion.isInRegion(modify.getLocation())) + continue; + if (template.canReplace(limitedRegion.getType(modify.getLocation()))) { + limitedRegion.setType(modify.getLocation(), block.getState().getType()); + limitedRegion.setBlockData(modify.getLocation(), block.getState().getBlockData()); + } + BlockFace nextFace = faces[random.nextInt(faces.length)]; + modify = modify.getRelative(nextFace); + } + } + }); + } + +} diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java index 165e3b20..907bd3a5 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/listener/WorldGenerationListener.java @@ -1,10 +1,15 @@ package net.Indyuce.mmoitems.listener; +import net.Indyuce.mmoitems.api.world.MMOBlockPopulator; import net.Indyuce.mmoitems.manager.WorldGenManager; +import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkLoadEvent; +import java.util.HashMap; +import java.util.Map; + /** * mmoitems * 13/03/2023 @@ -19,9 +24,21 @@ public class WorldGenerationListener implements Listener { this.manager = manager; } +// @EventHandler +// public void onWorldInit(WorldInitEvent e) { +// MMOItems.log("Initializing world " + e.getWorld().getName()); +// final World world = e.getWorld(); +// world.getPopulators().add(manager.populator(world)); +// } + + private final Map populatorMap = new HashMap<>(); + @EventHandler public void onChunkLoad(ChunkLoadEvent e) { - if (!e.isNewChunk()) return; - manager.populate(e); + final World world = e.getWorld(); + if (!e.isNewChunk() || populatorMap.containsKey(world.getName())) return; + MMOBlockPopulator populator = manager.populator(world); + world.getPopulators().add(populator); + populatorMap.put(world.getName(), populator); } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java index 7fd41ebd..ef17ec6c 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/WorldGenManager.java @@ -4,25 +4,17 @@ import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.block.CustomBlock; import net.Indyuce.mmoitems.api.block.WorldGenTemplate; +import net.Indyuce.mmoitems.api.world.MMOBlockPopulator; import net.Indyuce.mmoitems.listener.WorldGenerationListener; -import net.Indyuce.mmoitems.tasks.CustomBlocksPopulateTask; -import net.Indyuce.mmoitems.util.Pair; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; +import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.HandlerList; -import org.bukkit.event.world.ChunkLoadEvent; -import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; -import java.util.Queue; -import java.util.Random; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; public class WorldGenManager implements Reloadable { @@ -34,13 +26,8 @@ public class WorldGenManager implements Reloadable { * world. */ private final Map assigned = new HashMap<>(); - private final Queue> modificationsQueue = new ConcurrentLinkedQueue<>(); - - private static final BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP}; - private static final Random random = new Random(); private WorldGenerationListener listener; - private CustomBlocksPopulateTask task; public WorldGenManager() { /* @@ -67,48 +54,6 @@ public class WorldGenManager implements Reloadable { assigned.put(block, template); } - public void populate(@NotNull ChunkLoadEvent e) { - Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> { - preprocess(e); - if (task != null && task.isRunning()) - return; - task = new CustomBlocksPopulateTask(this); - task.start(); - }); - } - - private @Blocking void preprocess(ChunkLoadEvent e) { - assigned.entrySet() - .stream() - .filter(entry -> entry.getValue().canGenerateInWorld(e.getWorld())) - .forEach(entry -> { - final CustomBlock block = entry.getKey(); - final WorldGenTemplate template = entry.getValue(); - if (random.nextDouble() > template.getChunkChance()) - return; - - for (int i = 0; i < template.getVeinCount(); i++) { - int y = random.nextInt(template.getMaxDepth() - template.getMinDepth() + 1) + template.getMinDepth(); - Location generatePoint = e.getChunk().getBlock(random.nextInt(16), y, random.nextInt(16)).getLocation(); - - if (!template.canGenerate(generatePoint) || generatePoint.getWorld() == null) - continue; - Block modify = generatePoint.getWorld().getBlockAt(generatePoint); - - for (int j = 0; j < template.getVeinSize(); j++) { - if (template.canReplace(modify.getType())) - this.modificationsQueue.add(Pair.of(modify.getLocation(), block)); - BlockFace nextFace = faces[random.nextInt(faces.length)]; - modify = modify.getRelative(nextFace); - } - } - }); - } - - public Queue> getModificationsQueue() { - return modificationsQueue; - } - public void reload() { // Listener if (listener != null) @@ -116,7 +61,6 @@ public class WorldGenManager implements Reloadable { assigned.clear(); templates.clear(); - modificationsQueue.clear(); FileConfiguration config = new ConfigFile("gen-templates").getConfig(); for (String key : config.getKeys(false)) { @@ -129,14 +73,22 @@ public class WorldGenManager implements Reloadable { } // Listeners - if (MMOItems.plugin.getLanguage().worldGenEnabled) - Bukkit.getPluginManager().registerEvents(listener = new WorldGenerationListener(this), MMOItems.plugin); + if (MMOItems.plugin.getLanguage().worldGenEnabled) { + listener = new WorldGenerationListener(this); + Bukkit.getPluginManager().registerEvents(listener, MMOItems.plugin); + } } public void unload() { if (listener != null) HandlerList.unregisterAll(listener); - if (task != null) - task.stop(); + } + + public MMOBlockPopulator populator(@NotNull World world) { + return new MMOBlockPopulator(world, this); + } + + public Map assigned() { + return assigned; } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java deleted file mode 100644 index d9630e05..00000000 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/tasks/CustomBlocksPopulateTask.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.Indyuce.mmoitems.tasks; - -import net.Indyuce.mmoitems.MMOItems; -import net.Indyuce.mmoitems.api.block.CustomBlock; -import net.Indyuce.mmoitems.manager.WorldGenManager; -import net.Indyuce.mmoitems.util.Pair; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.Queue; - -/** - * mmoitems - * 13/03/2023 - * - * @author Roch Blondiaux (Kiwix). - */ -public class CustomBlocksPopulateTask extends BukkitRunnable { - - private final WorldGenManager manager; - private boolean running = false; - - public CustomBlocksPopulateTask(WorldGenManager manager) { - this.manager = manager; - } - - @Override - public void run() { - final Queue> modificationsQueue = manager.getModificationsQueue(); - final Pair pair = modificationsQueue.poll(); - - // If the queue is empty, cancel the task - if (pair == null) { - this.stop(); - return; - } - - // If the chunk is not loaded, skip it - if (!pair.getKey().getChunk().isLoaded()) - return; - - // If the block is already modified, skip it - if (pair.getKey().getBlock().getBlockData().equals(pair.getValue().getState().getBlockData())) - return; - - // Change the block - Bukkit.getScheduler().runTask(MMOItems.plugin, () -> setBlockData(pair.getKey().getBlock(), pair.getValue())); - } - - private void setBlockData(Block fModify, CustomBlock block) { - fModify.setType(block.getState().getType(), false); - fModify.setBlockData(block.getState().getBlockData(), false); - } - - public void start() { - if (running) return; - running = true; - this.runTaskTimerAsynchronously(MMOItems.plugin, 0, 1); - } - - public void stop() { - if (!running) return; - running = false; - this.cancel(); - } - - public boolean isRunning() { - return running; - } -}