From eb660f9f1310fe0a1a8fd1bf316a0ef811406b0b Mon Sep 17 00:00:00 2001 From: erikradovan Date: Sat, 11 Apr 2026 20:08:56 +0200 Subject: [PATCH] Mining system rework --- pom.xml | 10 + .../xyz/soukup/ecoCraftCore/EcoCraftCore.java | 1 + .../ecoCraftCore/mines/MineCommand.java | 19 +- .../ecoCraftCore/mines/MineManager.java | 712 +----------------- .../ecoCraftCore/mines/MineWorldManager.java | 78 +- src/main/resources/config.yml | 37 +- src/main/resources/messages.yml | 1 + src/main/resources/plugin.yml | 1 + 8 files changed, 109 insertions(+), 750 deletions(-) diff --git a/pom.xml b/pom.xml index 59f0843..b340140 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,10 @@ + + enginehub + https://maven.enginehub.org/repo/ + is-releases https://repo.infernalsuite.com/repository/maven-releases/ @@ -133,6 +137,12 @@ 1.21.10-R0.1-SNAPSHOT provided + + com.sk89q.worldedit + worldedit-bukkit + 7.3.12 + provided + com.github.stefvanschie.inventoryframework IF diff --git a/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java b/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java index 057db04..a9516f6 100644 --- a/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java +++ b/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java @@ -43,6 +43,7 @@ public final class EcoCraftCore extends JavaPlugin { public void onEnable() { this.getLogger().info("plugin starting out"); plugin = this; + saveDefaultConfig(); try { Messages.init(); diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java index 25cd2f4..eb521c4 100644 --- a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java +++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java @@ -6,6 +6,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import xyz.soukup.ecoCraftCore.messages.Messages; @@ -26,14 +27,15 @@ public class MineCommand { } private static int regenerateMines(CommandContext context) { + CommandSender sender = context.getSource().getSender(); World world = MineWorldManager.getWorld(); if (world == null) { - Messages.send(context.getSource().getSender(), "mine.error.no-world"); + Messages.send(sender, "mine.error.no-world"); return 0; } - Messages.send(context.getSource().getSender(), "mine.regenerating"); + Messages.send(sender, "mine.regenerating"); // Teleport all players out of the mine world first for (Player p : world.getPlayers()) { @@ -42,7 +44,14 @@ public class MineCommand { } // Delete old world, create fresh one, then generate - MineWorldManager.recreateWorld(newWorld -> MineManager.regenerate(newWorld)); + MineWorldManager.recreateWorld(newWorld -> { + boolean success = MineManager.regenerate(newWorld); + if (success) { + Messages.send(sender, "mine.regenerate-complete"); + } else { + Messages.send(sender, "mine.error.regenerate-failed"); + } + }); return 1; } @@ -57,6 +66,10 @@ public class MineCommand { } Location spawn = MineWorldManager.getSpawnLocation(); + if (spawn == null) { + Messages.send(player, "mine.error.no-world"); + return 0; + } player.teleport(spawn); Messages.send(player, "mine.teleporting"); return 1; diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java index 614575d..168cc97 100644 --- a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java +++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java @@ -1,696 +1,62 @@ package xyz.soukup.ecoCraftCore.mines; -import org.bukkit.Bukkit; -import org.bukkit.Material; +import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; - -import java.util.*; +import xyz.soukup.ecoCraftCore.mines.generation.MineGenerationConfig; +import xyz.soukup.ecoCraftCore.mines.generation.MineGenerator; +import xyz.soukup.ecoCraftCore.mines.generation.MineRoomLibrary; import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; -public class MineManager { - - private static final int TUNNEL_RADIUS = 5; // 11x11 = radius 5 from center - private static final int MIN_BRANCH_SIZE = 30; - private static final int FIRST_BRANCH_MIN_SIZE = 20; - private static final int FIRST_BRANCH_SPLIT_DELAY = 12; - private static final int MAX_BRANCH_SIZE = 80; - private static final double BASE_SPLIT_INCREMENT = 0.004; - private static final double SPLIT_DECAY_PER_BRANCH = 0.0004; - private static final int BLOCKS_PER_TICK = 800; - - // Transition zone: how many blocks before/after a border we blend - private static final int TRANSITION_RANGE = 8; - - private static final Random random = new Random(); - - private static final BlockFace[] DIRECTIONS = { - BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN, BlockFace.UP - }; - - // --- Biome definitions --- - - public enum MineBiome { - NORMAL, DEEP, SUPER_DEEP - } - - // Fill progression per biome - private static final Material[] NORMAL_FILLS = {Material.STONE, Material.ANDESITE, Material.COBBLESTONE, Material.TUFF}; - private static final int[] NORMAL_THRESHOLDS = {0, 30, 60, 90}; - - private static final Material[] DEEP_FILLS = {Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.CRACKED_DEEPSLATE_TILES}; - private static final int[] DEEP_THRESHOLDS = {0, 40, 80}; - - private static final Material[] SUPER_DEEP_FILLS = {Material.SMOOTH_BASALT, Material.BLACKSTONE, Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, Material.GILDED_BLACKSTONE}; - private static final int[] SUPER_DEEP_THRESHOLDS = {0, 40, 80, 120}; - - // Special inline blocks (placed during generation, not ores) - private static final double DIRT_CHANCE = 0.04; - private static final double GRAVEL_CHANCE = 0.05; - private static final double INFESTED_CHANCE = 0.008; - private static final double MUD_CHANCE_SUPER_DEEP = 0.03; - private static final double LAVA_CHANCE_SUPER_DEEP = 0.05; - - // Deep biome lava: starts low and increases through the fills - private static final double LAVA_CHANCE_DEEP_MIN = 0.005; - private static final double LAVA_CHANCE_DEEP_MAX = 0.04; - - // --- Ore tables per fill type (rolled on break) --- - - public record OreEntry(Material material, double weight) {} - - private static final Map ORE_TABLES = new LinkedHashMap<>(); - - static { - ORE_TABLES.put(Material.STONE, new OreEntry[]{ - new OreEntry(Material.COPPER_ORE, 1.0), - }); - ORE_TABLES.put(Material.ANDESITE, new OreEntry[]{ - new OreEntry(Material.COAL_ORE, 1.0), - }); - ORE_TABLES.put(Material.COBBLESTONE, new OreEntry[]{ - new OreEntry(Material.IRON_ORE, 1.0), - }); - ORE_TABLES.put(Material.TUFF, new OreEntry[]{ - new OreEntry(Material.GOLD_ORE, 1.0), - }); - ORE_TABLES.put(Material.DEEPSLATE, new OreEntry[]{ - new OreEntry(Material.DEEPSLATE_EMERALD_ORE, 0.4), - new OreEntry(Material.DEEPSLATE_LAPIS_ORE, 0.6), - }); - ORE_TABLES.put(Material.COBBLED_DEEPSLATE, new OreEntry[]{ - new OreEntry(Material.DEEPSLATE_REDSTONE_ORE, 0.6), - new OreEntry(Material.RAW_COPPER_BLOCK, 0.4), - }); - ORE_TABLES.put(Material.CRACKED_DEEPSLATE_TILES, new OreEntry[]{ - new OreEntry(Material.DEEPSLATE_DIAMOND_ORE, 0.6), - new OreEntry(Material.RAW_IRON_BLOCK, 0.4), - }); - ORE_TABLES.put(Material.SMOOTH_BASALT, new OreEntry[]{ - new OreEntry(Material.COAL_BLOCK, 1.0), - }); - ORE_TABLES.put(Material.BLACKSTONE, new OreEntry[]{ - new OreEntry(Material.RAW_GOLD_BLOCK, 1.0), - }); - ORE_TABLES.put(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, new OreEntry[]{ - new OreEntry(Material.ANCIENT_DEBRIS, 1.0), - }); - ORE_TABLES.put(Material.GILDED_BLACKSTONE, new OreEntry[]{ - new OreEntry(Material.GOLD_BLOCK, 1.0), - }); - } - - // Ore chance depending on how far this fill is from the detected zone - // Index 0 = current fill, 1 = previous fill (one step lighter), 2 = three fills back, 3+ = any other - private static final double ORE_CHANCE_CURRENT = 0.05; - private static final double ORE_CHANCE_PREVIOUS = 0.10; - private static final double ORE_CHANCE_THREE_BACK = 0.02; - private static final double ORE_CHANCE_OTHER = 0.01; - - // Ordered list of ALL fills from shallowest to deepest (used for distance calculations) - private static final List ALL_FILLS_ORDERED = new ArrayList<>(); - - static { - Collections.addAll(ALL_FILLS_ORDERED, NORMAL_FILLS); - Collections.addAll(ALL_FILLS_ORDERED, DEEP_FILLS); - Collections.addAll(ALL_FILLS_ORDERED, SUPER_DEEP_FILLS); - } - - // --- All known fill materials (for checking in MineWorldManager) --- - - private static final Set ALL_FILL_MATERIALS = new HashSet<>(ALL_FILLS_ORDERED); - - public static boolean isFillMaterial(Material material) { - return ALL_FILL_MATERIALS.contains(material); - } - - // --- Biome Y tracking --- - - private static int normalStartY = 0; - private static int deepStartY = 0; - private static int superDeepStartY = 0; - - // --- Branch data class --- - - private static class Branch { - final BlockFace direction; - final int startX, startY, startZ; - final int distanceFromStart; - final int maxSize; - final boolean isFirst; - final MineBiome biome; - double chanceToSplit; - - int endX, endY, endZ; - - Branch(BlockFace direction, int startX, int startY, int startZ, - int distanceFromStart, int maxSize, boolean isFirst, MineBiome biome) { - this.direction = direction; - this.startX = startX; - this.startY = startY; - this.startZ = startZ; - this.distanceFromStart = distanceFromStart; - this.maxSize = maxSize; - this.isFirst = isFirst; - this.biome = biome; - this.chanceToSplit = 0; - this.endX = startX; - this.endY = startY; - this.endZ = startZ; - } - } - - private static class BlockPlacement { - final int x, y, z; - final Material material; - - BlockPlacement(int x, int y, int z, Material material) { - this.x = x; - this.y = y; - this.z = z; - this.material = material; - } - } - - // ===================== MAIN ENTRY POINT ===================== - - public static void regenerate(World world) { - int startY = world.getMaxHeight() - 11; - int minY = world.getMinHeight() + 5; - int maxY = world.getMaxHeight(); - int worldMinY = world.getMinHeight(); - - normalStartY = startY; - - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - Set fillPositions = new HashSet<>(); - List fillPlacements = new ArrayList<>(); - - // ===== Biome 1: NORMAL ===== - plugin.getLogger().info("[MineGen] === Starting NORMAL biome generation ==="); - List normalBranches = new ArrayList<>(); - int[] branchCount = {0}; - generateBiomeBranches(MineBiome.NORMAL, 0, startY, 0, startY, minY, - fillPlacements, fillPositions, normalBranches, branchCount); - plugin.getLogger().info("[MineGen] NORMAL done: " + fillPlacements.size() + " fill blocks, branches=" + branchCount[0]); - - // Find lowest Y from NORMAL - Branch lowestNormal = findLowestBranch(normalBranches); - int deepX = lowestNormal != null ? lowestNormal.endX : 0; - int deepY = lowestNormal != null ? lowestNormal.endY : startY - 100; - int deepZ = lowestNormal != null ? lowestNormal.endZ : 0; - deepStartY = deepY; - - // ===== Biome 2: DEEP ===== - plugin.getLogger().info("[MineGen] === Starting DEEP biome generation at Y=" + deepY + " ==="); - List deepBranches = new ArrayList<>(); - generateBiomeBranches(MineBiome.DEEP, deepX, deepY, deepZ, deepY, minY, - fillPlacements, fillPositions, deepBranches, branchCount); - plugin.getLogger().info("[MineGen] DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]); - - // Find lowest Y from DEEP - Branch lowestDeep = findLowestBranch(deepBranches); - int sdX = lowestDeep != null ? lowestDeep.endX : deepX; - int sdY = lowestDeep != null ? lowestDeep.endY : deepY - 100; - int sdZ = lowestDeep != null ? lowestDeep.endZ : deepZ; - superDeepStartY = sdY; - - // ===== Biome 3: SUPER_DEEP ===== - plugin.getLogger().info("[MineGen] === Starting SUPER_DEEP biome generation at Y=" + sdY + " ==="); - List superDeepBranches = new ArrayList<>(); - generateBiomeBranches(MineBiome.SUPER_DEEP, sdX, sdY, sdZ, sdY, minY, - fillPlacements, fillPositions, superDeepBranches, branchCount); - plugin.getLogger().info("[MineGen] SUPER_DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]); - - // ===== Walls ===== - List wallPlacements = new ArrayList<>(); - generateWalls(fillPositions, wallPlacements, startY); - plugin.getLogger().info("[MineGen] Walls done: " + wallPlacements.size() + " bedrock blocks."); - - // ===== Schedule placement on main thread ===== - Bukkit.getScheduler().runTask(plugin, () -> - scheduleBlockPlacements(world, fillPlacements, wallPlacements, startY, worldMinY, maxY)); - }); - } - - // ===================== BIOME BRANCH GENERATION ===================== - - private static void generateBiomeBranches(MineBiome biome, int startX, int startY, int startZ, - int ceilingY, int minY, - List placements, Set fillPositions, - List completedBranches, int[] branchCount) { - Deque queue = new ArrayDeque<>(); - - // Reset branch count for this biome so split chance starts fresh - int[] biomeBranchCount = {0}; - - int firstSize = Math.max(FIRST_BRANCH_MIN_SIZE, MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE)); - Branch initial = new Branch(BlockFace.DOWN, startX, startY, startZ, 0, firstSize, true, biome); - queue.add(initial); - biomeBranchCount[0]++; - branchCount[0]++; - - plugin.getLogger().info("[MineGen] [" + biome + "] Starting at (" + startX + "," + startY + "," + startZ + "), minY=" + minY); - - while (!queue.isEmpty()) { - Branch branch = queue.pollFirst(); - double currentIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (biomeBranchCount[0] * SPLIT_DECAY_PER_BRANCH)); - plugin.getLogger().info("[MineGen] [" + biome + "] Processing branch dir=" + branch.direction - + " start=(" + branch.startX + "," + branch.startY + "," + branch.startZ + ")" - + " dist=" + branch.distanceFromStart + " maxSize=" + branch.maxSize - + " first=" + branch.isFirst - + " queued=" + queue.size() + " fillBlocks=" + fillPositions.size() - + " splitIncrement=" + String.format("%.5f", currentIncrement)); - - generateSingleBranch(branch, queue, placements, fillPositions, ceilingY, minY, biomeBranchCount); - completedBranches.add(branch); - } - - plugin.getLogger().info("[MineGen] [" + biome + "] Complete. Branches=" + completedBranches.size()); - } - - private static void generateSingleBranch(Branch branch, Deque queue, List placements, - Set fillPositions, int ceilingY, int minY, int[] branchCount) { - int cx = branch.startX; - int cy = branch.startY; - int cz = branch.startZ; - - for (int step = 0; step < branch.maxSize; step++) { - int distance = branch.distanceFromStart + step; - - generateCrossSection(cx, cy, cz, branch.direction, distance, branch.biome, placements, fillPositions); - - branch.endX = cx; - branch.endY = cy; - branch.endZ = cz; - - // First branch: skip split logic for the first 12 blocks - if (branch.isFirst && step < FIRST_BRANCH_SPLIT_DELAY) { - cx += branch.direction.getModX(); - cy += branch.direction.getModY(); - cz += branch.direction.getModZ(); - if (cy < minY || cy > ceilingY + 5) break; - continue; - } - - double splitIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (branchCount[0] * SPLIT_DECAY_PER_BRANCH)); - - if (splitIncrement <= 0) { - cx += branch.direction.getModX(); - cy += branch.direction.getModY(); - cz += branch.direction.getModZ(); - if (cy < minY || cy > ceilingY + 5) break; - continue; - } - - branch.chanceToSplit += splitIncrement; - - if (random.nextDouble() < branch.chanceToSplit) { - branch.chanceToSplit = 0; - - int splitCount = 1 + random.nextInt(4); - List available = getAvailableDirections(branch.direction); - Collections.shuffle(available, random); - - int created = 0; - for (BlockFace dir : available) { - if (created >= splitCount) break; - - int branchMaxSize = MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE); - - if (dir == BlockFace.UP && cy + branchMaxSize > ceilingY) continue; - if (dir == BlockFace.DOWN && cy - branchMaxSize < minY) continue; - - queue.add(new Branch(dir, cx, cy, cz, distance, branchMaxSize, false, branch.biome)); - branchCount[0]++; - created++; - } - plugin.getLogger().info("[MineGen] [" + branch.biome + "] Split at step=" + step - + " pos=(" + cx + "," + cy + "," + cz + ") created=" + created - + " total=" + branchCount[0]); - } - - cx += branch.direction.getModX(); - cy += branch.direction.getModY(); - cz += branch.direction.getModZ(); - - if (cy < minY || cy > ceilingY + 5) break; - } - } - - private static Branch findLowestBranch(List branches) { - Branch lowest = null; - for (Branch b : branches) { - if (lowest == null || b.endY < lowest.endY) { - lowest = b; - } - } - return lowest; - } - - private static List getAvailableDirections(BlockFace current) { - List dirs = new ArrayList<>(); - BlockFace opposite = current.getOppositeFace(); - for (BlockFace dir : DIRECTIONS) { - if (dir != opposite && dir != current) { - dirs.add(dir); - } - } - return dirs; - } - - // ===================== CROSS SECTION GENERATION ===================== - - private static void generateCrossSection(int cx, int cy, int cz, BlockFace direction, int distance, - MineBiome biome, List placements, Set fillPositions) { - int[][] offsets = getPerpOffsets(direction); - - for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) { - for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) { - int bx = cx + offsets[0][0] * a + offsets[1][0] * b; - int by = cy + offsets[0][1] * a + offsets[1][1] * b; - int bz = cz + offsets[0][2] * a + offsets[1][2] * b; - - long key = posKey(bx, by, bz); - if (!fillPositions.add(key)) continue; - - Material mat = pickFillForDistance(distance, biome); - placements.add(new BlockPlacement(bx, by, bz, mat)); - } - } - } - - // ===================== FILL SELECTION (no ores during generation) ===================== - - private static Material pickFillForDistance(int distance, MineBiome biome) { - Material[] fills = getFillsForBiome(biome); - int[] thresholds = getThresholdsForBiome(biome); - - // Determine primary fill - Material primaryFill = fills[0]; - for (int i = thresholds.length - 1; i >= 0; i--) { - if (distance >= thresholds[i]) { - primaryFill = fills[i]; - break; - } - } - - // Check transition zone - Material transitionFill = getTransitionFill(distance, fills, thresholds); - if (transitionFill != null && transitionFill != primaryFill) { - int borderDist = getDistanceToBorder(distance, thresholds); - double blendRatio = 0.5 * (1.0 - (double) Math.abs(borderDist) / TRANSITION_RANGE); - if (random.nextDouble() < blendRatio) { - primaryFill = transitionFill; - } - } - - // Roll for special inline blocks (dirt, gravel, lava, mud, infested) - Material special = rollSpecialBlock(biome, primaryFill, distance); - if (special != null) return special; - - return primaryFill; - } - - private static Material rollSpecialBlock(MineBiome biome, Material fill, int distance) { - double roll = random.nextDouble(); - switch (biome) { - case NORMAL: - if (roll < DIRT_CHANCE) return Material.DIRT; - roll -= DIRT_CHANCE; - if (roll < GRAVEL_CHANCE) return Material.GRAVEL; - roll -= GRAVEL_CHANCE; - // Rare infested variants - if (roll < INFESTED_CHANCE) { - return switch (fill) { - case STONE -> Material.INFESTED_STONE; - case COBBLESTONE -> Material.INFESTED_COBBLESTONE; - default -> null; // andesite, tuff don't have infested variants - }; - } - break; - case DEEP: - // Lava in ALL deep fills, chance increases with distance through the biome - // Deep thresholds max out at 80+ (cracked_deepslate_tiles) - // Total deep distance range is roughly 0-120 - double deepProgress = Math.min(1.0, distance / 120.0); - double lavaChance = LAVA_CHANCE_DEEP_MIN + (LAVA_CHANCE_DEEP_MAX - LAVA_CHANCE_DEEP_MIN) * deepProgress; - if (roll < lavaChance) return Material.LAVA; - break; - case SUPER_DEEP: - if (roll < MUD_CHANCE_SUPER_DEEP) return Material.MUD; - roll -= MUD_CHANCE_SUPER_DEEP; - if (roll < LAVA_CHANCE_SUPER_DEEP) return Material.LAVA; - break; - } - return null; - } - - private static Material getTransitionFill(int distance, Material[] fills, int[] thresholds) { - for (int i = 1; i < thresholds.length; i++) { - int border = thresholds[i]; - int diff = distance - border; - if (Math.abs(diff) <= TRANSITION_RANGE) { - return (diff < 0) ? fills[i] : fills[i - 1]; - } - } - return null; - } - - private static int getDistanceToBorder(int distance, int[] thresholds) { - int closest = Integer.MAX_VALUE; - for (int i = 1; i < thresholds.length; i++) { - int diff = distance - thresholds[i]; - if (Math.abs(diff) < Math.abs(closest)) { - closest = diff; - } - } - return closest; - } - - private static Material[] getFillsForBiome(MineBiome biome) { - return switch (biome) { - case NORMAL -> NORMAL_FILLS; - case DEEP -> DEEP_FILLS; - case SUPER_DEEP -> SUPER_DEEP_FILLS; - }; - } - - private static int[] getThresholdsForBiome(MineBiome biome) { - return switch (biome) { - case NORMAL -> NORMAL_THRESHOLDS; - case DEEP -> DEEP_THRESHOLDS; - case SUPER_DEEP -> SUPER_DEEP_THRESHOLDS; - }; - } - - // ===================== ORE-ON-BREAK SYSTEM (called from MineWorldManager) ===================== - - /** - * Detects the deepest fill material among the 6 surrounding blocks to determine - * which fill zone the player is in. Then rolls for an ore based on the broken block's - * position in the fill sequence relative to the detected zone. - * - * Ore chances: - * 5% for ores from the current (detected) fill - * 10% for ores from the previous fill (one step lighter) - * 2% for ores from three fills back - * 1% for any other fill's ores - * - * Returns null if no ore was rolled. - */ - public static Material rollOreOnBreak(Block brokenBlock, Material brokenType) { - // Detect the deepest fill in the 6 surrounding blocks - Material detectedFill = detectDeepestSurroundingFill(brokenBlock); - if (detectedFill == null) { - // Fallback: use the broken block itself if it's a fill - detectedFill = isFillMaterial(brokenType) ? brokenType : Material.STONE; - } - - int detectedIndex = ALL_FILLS_ORDERED.indexOf(detectedFill); - int brokenIndex = ALL_FILLS_ORDERED.indexOf(brokenType); +public final class MineManager { - // If the broken block isn't a fill material, no ore roll - if (brokenIndex < 0) return null; - - // How many steps back from the detected fill is this broken block? - // detectedIndex is the deepest (highest index), brokenIndex is where we are in the sequence - int stepsBack = detectedIndex - brokenIndex; - - // Determine ore chance based on distance from detected fill - double chance; - if (stepsBack == 0) { - chance = ORE_CHANCE_CURRENT; // 5% - breaking the detected fill itself - } else if (stepsBack == 1) { - chance = ORE_CHANCE_PREVIOUS; // 10% - one step lighter - } else if (stepsBack == 2) { - chance = ORE_CHANCE_THREE_BACK; // 2% - two steps lighter - } else { - chance = ORE_CHANCE_OTHER; // 1% - anything else - } - - // Roll for ore - if (random.nextDouble() >= chance) return null; - - // Pick a random ore from the broken block's ore table - OreEntry[] oreTable = ORE_TABLES.get(brokenType); - if (oreTable == null || oreTable.length == 0) return null; - - return pickWeightedOre(oreTable); + private MineManager() { } - /** - * Looks at the 6 blocks surrounding the broken block and finds the deepest fill material. - */ - private static Material detectDeepestSurroundingFill(Block block) { - Material deepest = null; - int deepestIndex = -1; - - for (BlockFace face : DIRECTIONS) { - Block neighbor = block.getRelative(face); - Material type = neighbor.getType(); + public static boolean regenerate(World world) { + MineGenerationConfig generationConfig = MineGenerationConfig.fromConfiguration(plugin.getConfig(), world); + MineRoomLibrary roomLibrary = MineRoomLibrary.load( + plugin.getDataFolder(), + generationConfig.roomsDirectory(), + plugin.getLogger() + ); - int index = ALL_FILLS_ORDERED.indexOf(type); - if (index > deepestIndex) { - deepestIndex = index; - deepest = type; - } + if (roomLibrary.isEmpty()) { + plugin.getLogger().severe("[MineGen] No valid room prefabs were loaded from: " + + roomLibrary.roomsDirectory().getAbsolutePath()); + return false; } - return deepest; - } + MineGenerator.Result generationResult = new MineGenerator( + world, + generationConfig, + roomLibrary, + plugin.getLogger() + ).generate(); - /** - * Picks a random ore from the table using weights. - */ - private static Material pickWeightedOre(OreEntry[] table) { - double totalWeight = 0; - for (OreEntry e : table) totalWeight += e.weight; - - double roll = random.nextDouble() * totalWeight; - double cumulative = 0; - for (OreEntry e : table) { - cumulative += e.weight; - if (roll < cumulative) return e.material; + if (!generationResult.success()) { + return false; } - return table[table.length - 1].material; - } - // ===================== PERPENDICULAR AXES ===================== + Location spawn = getConfiguredSpawnLocation(world); + world.setSpawnLocation(spawn); - private static int[][] getPerpOffsets(BlockFace dir) { - return switch (dir) { - case UP, DOWN -> new int[][]{{1, 0, 0}, {0, 0, 1}}; - case NORTH, SOUTH -> new int[][]{{1, 0, 0}, {0, 1, 0}}; - case EAST, WEST -> new int[][]{{0, 0, 1}, {0, 1, 0}}; - default -> new int[][]{{1, 0, 0}, {0, 0, 1}}; - }; + plugin.getLogger().info("[MineGen] Mine generated with " + generationResult.roomsPlaced() + " rooms."); + return true; } - // ===================== WALL GENERATION ===================== - - private static void generateWalls(Set fillPositions, List wallPlacements, int startY) { - Set wallPositions = new HashSet<>(); - plugin.getLogger().info("[MineGen] Generating bedrock walls for " + fillPositions.size() + " fill positions..."); - - for (long key : fillPositions) { - int x = decodeX(key); - int y = decodeY(key); - int z = decodeZ(key); - - for (BlockFace face : DIRECTIONS) { - int nx = x + face.getModX(); - int ny = y + face.getModY(); - int nz = z + face.getModZ(); - - long neighborKey = posKey(nx, ny, nz); - - if (!fillPositions.contains(neighborKey)) { - if (ny > startY) continue; - - if (wallPositions.add(neighborKey)) { - wallPlacements.add(new BlockPlacement(nx, ny, nz, Material.BEDROCK)); - } - } - } - } - } - - // ===================== BLOCK PLACEMENT SCHEDULING ===================== - - private static void scheduleBlockPlacements(World world, List fillPlacements, - List wallPlacements, - int startY, int minY, int maxY) { - // Three-phase placement: 1) Dirt placeholder 2) Bedrock walls 3) Actual fill - List allPlacements = new ArrayList<>(); + public static Location getConfiguredSpawnLocation(World world) { + Location fallback = new Location(world, 5.5, world.getMaxHeight() - 9, 5.5, 0f, 0f); - // Phase A: Dirt placeholders at all fill positions - List dirtPlaceholders = new ArrayList<>(fillPlacements.size()); - for (BlockPlacement bp : fillPlacements) { - dirtPlaceholders.add(new BlockPlacement(bp.x, bp.y, bp.z, Material.DIRT)); + if (!plugin.getConfig().isConfigurationSection("mine.spawn")) { + return fallback; } - allPlacements.addAll(dirtPlaceholders); - - // Phase B: Bedrock walls - allPlacements.addAll(wallPlacements); - - // Phase C: Actual fill (overwrites dirt) - allPlacements.addAll(fillPlacements); - - // Air blocks above the entrance opening - for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) { - for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) { - allPlacements.add(new BlockPlacement(a, startY + 1, b, Material.AIR)); - } - } - - int totalBlocks = allPlacements.size(); - int tickDelay = 0; - - plugin.getLogger().info("[MineGen] Scheduling " + totalBlocks + " block placements (" - + dirtPlaceholders.size() + " placeholder + " + wallPlacements.size() + " walls + " - + fillPlacements.size() + " fill) in batches of " + BLOCKS_PER_TICK); - - for (int i = 0; i < totalBlocks; i += BLOCKS_PER_TICK) { - int from = i; - int to = Math.min(i + BLOCKS_PER_TICK, totalBlocks); - - Bukkit.getScheduler().runTaskLater(plugin, () -> { - for (int j = from; j < to; j++) { - BlockPlacement bp = allPlacements.get(j); - if (bp.y < minY || bp.y >= maxY) continue; - world.getBlockAt(bp.x, bp.y, bp.z).setType(bp.material, false); - } - }, tickDelay); - - tickDelay++; - } - - int finalDelay = tickDelay; - Bukkit.getScheduler().runTaskLater(plugin, - () -> plugin.getLogger().info("Mine regeneration complete. " + totalBlocks + " blocks placed."), - finalDelay + 2); - } - - // ===================== POSITION ENCODING/DECODING ===================== - - private static long posKey(int x, int y, int z) { - return ((long) (x + 30000000) & 0x3FFFFFF) - | (((long) (y + 2048) & 0xFFF) << 26) - | (((long) (z + 30000000) & 0x3FFFFFF) << 38); - } - - private static int decodeX(long key) { - return (int) (key & 0x3FFFFFF) - 30000000; - } - - private static int decodeY(long key) { - return (int) ((key >> 26) & 0xFFF) - 2048; - } - private static int decodeZ(long key) { - return (int) ((key >> 38) & 0x3FFFFFF) - 30000000; + double x = plugin.getConfig().getDouble("mine.spawn.x", fallback.getX()); + double y = plugin.getConfig().getDouble("mine.spawn.y", fallback.getY()); + double z = plugin.getConfig().getDouble("mine.spawn.z", fallback.getZ()); + float yaw = (float) plugin.getConfig().getDouble("mine.spawn.yaw", 0.0); + float pitch = (float) plugin.getConfig().getDouble("mine.spawn.pitch", 0.0); + return new Location(world, x, y, z, yaw, pitch); } } diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java index 38bb233..67612bb 100644 --- a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java +++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java @@ -15,8 +15,6 @@ import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerItemDamageEvent; import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import xyz.soukup.ecoCraftCore.database.objects.Island; @@ -70,6 +68,9 @@ public class MineWorldManager implements Listener { mineWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); mineWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); mineWorld.setTime(6000); + if (plugin.getConfig().getBoolean("mine.paste-on-startup", false)) { + MineManager.regenerate(mineWorld); + } plugin.getLogger().info("Mine world loaded successfully."); } }); @@ -86,8 +87,7 @@ public class MineWorldManager implements Listener { public static Location getSpawnLocation() { if (mineWorld == null) return null; - int startY = mineWorld.getMaxHeight() - 10; - return new Location(mineWorld, 5, startY + 1, 5); + return MineManager.getConfiguredSpawnLocation(mineWorld); } /** @@ -220,7 +220,7 @@ public class MineWorldManager implements Listener { event.blockList().removeIf(block -> block.getType() == Material.BEDROCK); } - // Block break logic: bedrock unbreakable, ore-on-break for ALL fills, mining sequence + // Block break logic: bedrock walls are unbreakable. @EventHandler public void onBlockBreak(BlockBreakEvent event) { if (!isInMineWorld(event.getPlayer())) return; @@ -230,75 +230,7 @@ public class MineWorldManager implements Listener { // Bedrock is completely unbreakable (mine walls) if (type == Material.BEDROCK) { event.setCancelled(true); - return; } - - // For any fill material (at any stage of the mining sequence), roll for ore first. - // The ore replaces the block as another mining step — player then breaks the ore normally. - if (MineManager.isFillMaterial(type)) { - Material ore = MineManager.rollOreOnBreak(event.getBlock(), type); - if (ore != null) { - event.setCancelled(true); - event.getBlock().setType(ore); - event.getBlock().getState().update(true); - applyToolDamage(event); - return; - } - } - - // No ore rolled — apply normal mining sequence (deeper fills degrade into lighter fills) - Material nextTier = getNextTier(type); - if (nextTier != null) { - event.setCancelled(true); - event.getBlock().setType(nextTier); - event.getBlock().getState().update(true); - applyToolDamage(event); - return; - } - - // Terminal fill (stone) or ores — break normally - } - - /** - * Applies doubled durability damage to the player's tool when we cancel a break event. - * Only applies if the held item actually has durability. - */ - private void applyToolDamage(BlockBreakEvent event) { - ItemStack tool = event.getPlayer().getInventory().getItemInMainHand(); - if (tool.getType().isAir() || tool.getType().getMaxDurability() <= 0) return; - if (!(tool.getItemMeta() instanceof Damageable damageable)) return; - - damageable.setDamage(damageable.getDamage() + 2); // doubled durability - tool.setItemMeta(damageable); - - if (damageable.getDamage() >= tool.getType().getMaxDurability()) { - event.getPlayer().getInventory().setItemInMainHand(null); - event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f); - } - } - - /** - * Mining sequence: deeper fills degrade into lighter fills. - * Returns null when the block should break normally (terminal fill = stone). - * - * Chain: gilded_blackstone -> cracked_polished_blackstone_bricks -> blackstone -> smooth_basalt - * -> cracked_deepslate_tiles -> cobbled_deepslate -> deepslate -> tuff -> cobblestone - * -> andesite -> stone -> null (breaks normally) - */ - private Material getNextTier(Material material) { - return switch (material) { - case GILDED_BLACKSTONE -> Material.CRACKED_POLISHED_BLACKSTONE_BRICKS; - case CRACKED_POLISHED_BLACKSTONE_BRICKS -> Material.BLACKSTONE; - case BLACKSTONE -> Material.SMOOTH_BASALT; - case SMOOTH_BASALT -> Material.CRACKED_DEEPSLATE_TILES; - case CRACKED_DEEPSLATE_TILES -> Material.COBBLED_DEEPSLATE; - case COBBLED_DEEPSLATE -> Material.DEEPSLATE; - case DEEPSLATE -> Material.TUFF; - case TUFF -> Material.COBBLESTONE; - case COBBLESTONE -> Material.ANDESITE; - case ANDESITE -> Material.STONE; - default -> null; // Stone and non-fill blocks break normally - }; } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index af80369..734212b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -9,4 +9,39 @@ database: password: "ecc" database: "ecc" cache: - save-interval: 6000 \ No newline at end of file + save-interval: 6000 + +mine: + paste-on-startup: false + spawn: + x: 5.5 + y: 311.0 + z: 5.5 + yaw: 0.0 + pitch: 0.0 + generator: + rooms-directory: "rooms" + starter-room: "ST15" + default-biome: "ST" + room-size: 16 + max-depth: 64 + max-rooms: 320 + ignore-air: false + origin: + x: 0 + y: 310 + z: 0 + depth-biomes: + - biome: "ST" + min-depth: 0 + - biome: "DS" + min-depth: 20 + - biome: "MS" + min-depth: 40 + natural-biomes: + - biome: "DS" + chance: 0.08 + length: 6 + - biome: "MS" + chance: 0.05 + length: 5 diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 14d8aae..5615dbc 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -72,4 +72,5 @@ mine: teleported-out: "Byl jsi teleportován z dolů kvůli regeneraci." error: no-world: "Důlní svět není načtený." + regenerate-failed: "Regenerace dolů selhala. Zkontroluj room prefab soubory a server log." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5868d64..959004c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,3 +2,4 @@ name: EcoCraftCore version: '1.0-SNAPSHOT' main: xyz.soukup.ecoCraftCore.EcoCraftCore api-version: '1.21' +depend: [WorldEdit]