Merge branch '752-gen-templates-custom-block-spawning-doesn-t-work-at-all' into 'master'

World generation optimized

See merge request phoenix-dvpmt/mmoitems!57
This commit is contained in:
Jules 2023-03-24 23:04:47 +00:00
commit 724eb97b90
5 changed files with 301 additions and 230 deletions

View File

@ -168,7 +168,7 @@ public class MMOItems extends JavaPlugin {
dropTableManager = new DropTableManager(); dropTableManager = new DropTableManager();
worldGenManager = new WorldGenManager(); worldGenManager = new WorldGenManager();
blockManager = new BlockManager(); blockManager = new BlockManager();
statManager.reload(false); statManager.reload(false);
PluginUtils.hookDependencyIfPresent("Vault", u -> vaultSupport = new VaultSupport()); PluginUtils.hookDependencyIfPresent("Vault", u -> vaultSupport = new VaultSupport());
@ -254,7 +254,6 @@ public class MMOItems extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
// Support for early plugin disabling // Support for early plugin disabling
if (!hasLoadedSuccessfully) if (!hasLoadedSuccessfully)
return; return;
@ -269,6 +268,9 @@ public class MMOItems extends JavaPlugin {
for (Player player : Bukkit.getOnlinePlayers()) for (Player player : Bukkit.getOnlinePlayers())
if (player.getOpenInventory() != null && player.getOpenInventory().getTopInventory().getHolder() instanceof PluginInventory) if (player.getOpenInventory() != null && player.getOpenInventory().getTopInventory().getHolder() instanceof PluginInventory)
player.closeInventory(); player.closeInventory();
// WorldGen
this.worldGenManager.unload();
} }
public String getPrefix() { public String getPrefix() {

View File

@ -1,171 +1,138 @@
package net.Indyuce.mmoitems.api.block; package net.Indyuce.mmoitems.api.block;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class WorldGenTemplate { public class WorldGenTemplate {
private final String id; private final String id;
private final double chunkChance; private final double chunkChance;
private final int minDepth, maxDepth, veinSize, veinCount; private final int minDepth, maxDepth, veinSize, veinCount;
private final List<Material> replaceable = new ArrayList<>(); private final List<Material> replaceable = new ArrayList<>();
private final List<Material> bordering = new ArrayList<>(); private final List<Material> bordering = new ArrayList<>();
private final List<Material> notBordering = new ArrayList<>(); private final List<Material> notBordering = new ArrayList<>();
private final List<String> worldWhitelist = new ArrayList<>(), worldBlacklist = new ArrayList<>(); private final List<String> worldWhitelist = new ArrayList<>(), worldBlacklist = new ArrayList<>();
private final List<String> biomeWhitelist = new ArrayList<>(), biomeBlacklist = new ArrayList<>(); private final List<String> biomeWhitelist = new ArrayList<>(), biomeBlacklist = new ArrayList<>();
private final boolean slimeChunk; private final boolean slimeChunk;
public WorldGenTemplate(ConfigurationSection config) { public WorldGenTemplate(ConfigurationSection config) {
Validate.notNull(config, "Could not read gen template config"); Validate.notNull(config, "Could not read gen template config");
id = config.getName().toLowerCase().replace(" ", "-").replace("_", "-"); id = config.getName().toLowerCase().replace(" ", "-").replace("_", "-");
config.getStringList("replace").forEach(str -> replaceable.add(Material.valueOf(str.toUpperCase().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("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(" ", "_")))); 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);
String[] depth = config.getString("depth").split("="); for (String world : config.getStringList("worlds")) {
minDepth = Integer.parseInt(depth[0]); (world.startsWith("!") ? worldBlacklist : worldWhitelist).add(world.toLowerCase().replace("_", "-"));
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;
} }
if (!worldBlacklist.isEmpty() && worldBlacklist.contains(world)) { for (String biome : config.getStringList("biomes")) {
return false; (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; return true;
} }
public boolean canGenerate(Location pos) {
// check biome list public boolean canReplace(Material type) {
Biome biome = pos.getWorld().getBiome(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); return replaceable.isEmpty() || replaceable.contains(type);
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) { public boolean canBorder(Material type) {
return replaceable.isEmpty() || replaceable.contains(type);
}
public boolean canBorder(Material type) {
return bordering.isEmpty() || bordering.contains(type); return bordering.isEmpty() || bordering.contains(type);
} }
public boolean checkIfBorderingBlocks(Location pos) { public boolean checkIfBorderingBlocks(Location pos) {
if(!canBorder(pos.getBlock().getRelative(BlockFace.NORTH).getType())) { return Arrays.stream(BlockFace.values())
return false; .map(pos.getBlock()::getRelative)
} .map(Block::getType)
if(!canBorder(pos.getBlock().getRelative(BlockFace.EAST).getType())) { .allMatch(this::canBorder);
return false; }
}
if(!canBorder(pos.getBlock().getRelative(BlockFace.SOUTH).getType())) { public boolean canNotBorder(Material type) {
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 !notBordering.isEmpty() && notBordering.contains(type); return !notBordering.isEmpty() && notBordering.contains(type);
} }
public boolean checkIfNotBorderingBlocks(Location pos) { public boolean checkIfNotBorderingBlocks(Location pos) {
if(canNotBorder(pos.getBlock().getRelative(BlockFace.NORTH).getType())) { return Arrays.stream(BlockFace.values())
return false; .map(pos.getBlock()::getRelative)
} .map(Block::getType)
if(canNotBorder(pos.getBlock().getRelative(BlockFace.EAST).getType())) { .allMatch(this::canNotBorder);
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());
}
} }

View File

@ -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<CustomBlock, WorldGenTemplate> 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);
}
}
});
}
}

View File

@ -0,0 +1,44 @@
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
*
* @author Roch Blondiaux (Kiwix).
*/
public class WorldGenerationListener implements Listener {
private final WorldGenManager manager;
public WorldGenerationListener(WorldGenManager manager) {
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<String, MMOBlockPopulator> populatorMap = new HashMap<>();
@EventHandler
public void onChunkLoad(ChunkLoadEvent 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);
}
}

View File

@ -4,108 +4,91 @@ import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.block.CustomBlock; import net.Indyuce.mmoitems.api.block.CustomBlock;
import net.Indyuce.mmoitems.api.block.WorldGenTemplate; import net.Indyuce.mmoitems.api.block.WorldGenTemplate;
import net.Indyuce.mmoitems.api.world.MMOBlockPopulator;
import net.Indyuce.mmoitems.listener.WorldGenerationListener;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull;
import org.bukkit.event.world.ChunkLoadEvent;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.logging.Level; import java.util.logging.Level;
public class WorldGenManager implements Listener, Reloadable { public class WorldGenManager implements Reloadable {
private final Map<String, WorldGenTemplate> templates = new HashMap<>(); private final Map<String, WorldGenTemplate> templates = new HashMap<>();
/* /*
* maps a custom block to the world generator template so that it is later * 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 * easier to access all the blocks which must be placed when generating a
* world. * world.
*/ */
private final Map<CustomBlock, WorldGenTemplate> assigned = new HashMap<>(); private final Map<CustomBlock, WorldGenTemplate> assigned = new HashMap<>();
private static final BlockFace[] faces = { BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST, BlockFace.DOWN, BlockFace.UP }; private WorldGenerationListener listener;
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();
}
/* public WorldGenTemplate getOrThrow(String id) {
* load the worldGenManager even if world gen is not enabled so that if Validate.isTrue(templates.containsKey(id), "Could not find gen template with ID '" + id + "'");
* 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) return templates.get(id);
Bukkit.getPluginManager().registerEvents(this, MMOItems.plugin); }
}
public WorldGenTemplate getOrThrow(String id) { /*
Validate.isTrue(templates.containsKey(id), "Could not find gen template with ID '" + 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");
return templates.get(id); assigned.put(block, template);
} }
/* public void reload() {
* it is mandatory to call this function after registering the custom block // Listener
* if you want the custom block to be spawning in the worlds if (listener != null)
*/ HandlerList.unregisterAll(listener);
public void assign(CustomBlock block, WorldGenTemplate template) {
Validate.notNull(template, "Cannot assign a null template to a custom block");
assigned.put(block, template); assigned.clear();
} templates.clear();
@EventHandler FileConfiguration config = new ConfigFile("gen-templates").getConfig();
public void a(ChunkLoadEvent event) { for (String key : config.getKeys(false)) {
if(event.isNewChunk()) { try {
Bukkit.getScheduler().runTaskAsynchronously(MMOItems.plugin, () -> assigned.forEach((block, template) -> { WorldGenTemplate template = new WorldGenTemplate(config.getConfigurationSection(key));
if(!template.canGenerateInWorld(event.getWorld())) { templates.put(template.getId(), template);
return; } catch (IllegalArgumentException exception) {
} MMOItems.plugin.getLogger().log(Level.WARNING, "An error occurred when loading gen template '" + key + "': " + exception.getMessage());
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(template.canGenerate(generatePoint)) { // Listeners
Block modify = generatePoint.getWorld().getBlockAt(generatePoint); if (MMOItems.plugin.getLanguage().worldGenEnabled) {
listener = new WorldGenerationListener(this);
Bukkit.getPluginManager().registerEvents(listener, MMOItems.plugin);
}
}
for(int j = 0; j < template.getVeinSize(); j++) { public void unload() {
if(template.canReplace(modify.getType())) { if (listener != null)
final Block fModify = modify; HandlerList.unregisterAll(listener);
Bukkit.getScheduler().runTask(MMOItems.plugin, () -> { }
fModify.setType(block.getState().getType(), false);
fModify.setBlockData(block.getState().getBlockData(), false);
});
}
BlockFace nextFace = faces[random.nextInt(faces.length)]; public MMOBlockPopulator populator(@NotNull World world) {
modify = modify.getRelative(nextFace); return new MMOBlockPopulator(world, this);
} }
}
}
}));
}
}
public void reload() { public Map<CustomBlock, WorldGenTemplate> assigned() {
assigned.clear(); return assigned;
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());
}
}
}
} }