@ -3,6 +3,7 @@ package xyz.soukup.ecoCraftCore.mines;
import org.bukkit.Bukkit ;
import org.bukkit.Material ;
import org.bukkit.World ;
import org.bukkit.block.Block ;
import org.bukkit.block.BlockFace ;
import java.util.* ;
@ -12,21 +13,14 @@ 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 = 15 ;
private static final int FIRST_BRANCH_MIN_SIZE = 1 2;
private static final int FIRST_BRANCH_SPLIT_DELAY = 12 ; // first branch: no split chance until after 12 blocks
private static final int MAX_BRANCH_SIZE = 5 0;
private static final double BASE_SPLIT_INCREMENT = 0 . 01 ;
private static final double SPLIT_DECAY_PER_BRANCH = 0 . 000125 ;
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 = 8 0;
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 ;
// Biome boundaries (distance thresholds)
private static final int STONE_END = 40 ;
private static final int TUFF_END = 80 ;
private static final int DEEPSLATE_END = 120 ;
private static final int BASALT_END = 160 ;
// Blackstone: 160+
// Transition zone: how many blocks before/after a border we blend
private static final int TRANSITION_RANGE = 8 ;
@ -36,48 +30,123 @@ public class MineManager {
BlockFace . NORTH , BlockFace . SOUTH , BlockFace . EAST , BlockFace . WEST , BlockFace . DOWN , BlockFace . UP
} ;
// --- Biome ore table s ---
// --- Biome definition s ---
private record OreEntry ( Material material , double chance ) { }
public enum MineBiome {
NORMAL , DEEP , SUPER_DEEP
}
private static final OreEntry [ ] STONE_ORES = {
new OreEntry ( Material . GRAVEL , 0 . 08 ) ,
new OreEntry ( Material . COAL_ORE , 0 . 05 ) ,
new OreEntry ( Material . COPPER_ORE , 0 . 04 ) ,
} ;
// 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 OreEntry [ ] TUFF_ORES = {
new OreEntry ( Material . IRON_ORE , 0 . 04 ) ,
new OreEntry ( Material . GOLD_ORE , 0 . 025 ) ,
new OreEntry ( Material . LAPIS_ORE , 0 . 02 ) ,
} ;
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 OreEntry [ ] DEEPSLATE_ORES = {
new OreEntry ( Material . DEEPSLATE_DIAMOND_ORE , 0 . 03 ) ,
new OreEntry ( Material . DEEPSLATE_EMERALD_ORE , 0 . 015 ) ,
new OreEntry ( Material . DEEPSLATE_REDSTONE_ORE , 0 . 02 ) ,
} ;
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 } ;
private static final OreEntry [ ] BASALT_ORES = {
new OreEntry ( Material . RAW_IRON_BLOCK , 0 . 015 ) ,
new OreEntry ( Material . RAW_COPPER_BLOCK , 0 . 015 ) ,
} ;
// 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 ;
private static final OreEntry [ ] BLACKSTONE_ORES = {
new OreEntry ( Material . ANCIENT_DEBRIS , 0 . 003 ) ,
new OreEntry ( Material . RAW_GOLD_BLOCK , 0 . 01 ) ,
} ;
// 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 < Material , OreEntry [ ] > 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 < Material > 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 < Material > 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 ---
// 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 ;
Branch ( BlockFace direction , int startX , int startY , int startZ , int distanceFromStart , int maxSize , boolean isFirst ) {
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 ;
@ -85,7 +154,11 @@ public class MineManager {
this . distanceFromStart = distanceFromStart ;
this . maxSize = maxSize ;
this . isFirst = isFirst ;
this . biome = biome ;
this . chanceToSplit = 0 ;
this . endX = startX ;
this . endY = startY ;
this . endZ = startZ ;
}
}
@ -101,63 +174,105 @@ public class MineManager {
}
}
// ===================== 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 ( ) ;
// Run the heavy data generation async, then schedule block placement on main thread
normalStartY = startY ;
Bukkit . getScheduler ( ) . runTaskAsynchronously ( plugin , ( ) - > {
Set < Long > fillPositions = new HashSet < > ( ) ;
List < BlockPlacement > fillPlacements = new ArrayList < > ( ) ;
// Phase 1: Generate tunnel fill data
generateBranches ( fillPlacements , fillPositions , startY , minY ) ;
plugin . getLogger ( ) . info ( "Mine generation phase 1 done: " + fillPlacements . size ( ) + " fill blocks, " + fillPositions . size ( ) + " unique positions." ) ;
// Phase 2: Generate wall data
// ===== Biome 1: NORMAL =====
plugin . getLogger ( ) . info ( "[MineGen] === Starting NORMAL biome generation ===" ) ;
List < Branch > 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 < Branch > 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 < Branch > 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 < BlockPlacement > wallPlacements = new ArrayList < > ( ) ;
generateWalls ( fillPositions , wallPlacements , startY ) ;
plugin . getLogger ( ) . info ( "[MineGen] Walls done: " + wallPlacements . size ( ) + " bedrock blocks." ) ;
plugin . getLogger ( ) . info ( "Mine generation phase 2 done: " + wallPlacements . size ( ) + " wall blocks." ) ;
// Phase 3: Schedule block placement on main thread in batches
// ===== Schedule placement on main thread =====
Bukkit . getScheduler ( ) . runTask ( plugin , ( ) - >
scheduleBlockPlacements ( world , fillPlacements , wallPlacements , startY , worldMinY , maxY ) ) ;
} ) ;
}
private static void generateBranches ( List < BlockPlacement > placements , Set < Long > fillPositions , int startY , int minY ) {
// ===================== BIOME BRANCH GENERATION =====================
private static void generateBiomeBranches ( MineBiome biome , int startX , int startY , int startZ ,
int ceilingY , int minY ,
List < BlockPlacement > placements , Set < Long > fillPositions ,
List < Branch > completedBranches , int [ ] branchCount ) {
Deque < Branch > queue = new ArrayDeque < > ( ) ;
// Shared mutable counter: [0] = total branches ever created
int [ ] branchCount = { 0 } ;
// 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 , 0 , startY , 0 , 0 , firstSize , true ) ;
Branch initial = new Branch ( BlockFace . DOWN , startX , startY , startZ , 0 , firstSize , true , biom e ) ;
queue . add ( initial ) ;
biomeBranchCount [ 0 ] + + ;
branchCount [ 0 ] + + ;
plugin . getLogger ( ) . info ( "[MineGen] Starting generation at Y=" + startY + ", minY=" + minY ) ;
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 - ( branchCount [ 0 ] * SPLIT_DECAY_PER_BRANCH ) ) ;
plugin . getLogger ( ) . info ( "[MineGen] Processing branch #" + branchCount [ 0 ]
+ " dir=" + branch . direction + " start=(" + branch . startX + "," + branch . startY + "," + branch . startZ + ")"
double currentIncrement = Math . max ( 0 , BASE_SPLIT_INCREMENT - ( biomeB ranchCount [ 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 , startY , minY , branchCount ) ;
generateSingleBranch ( branch , queue , placements , fillPositions , ceilingY , minY , biomeBranchCount ) ;
completedBranches . add ( branch ) ;
}
plugin . getLogger ( ) . info ( "[MineGen] Branch generation complete. Total branches=" + branchCount [ 0 ] + " fillBlocks=" + fillPositions . size ( ) + " placement s=" + placement s . size ( ) ) ;
plugin . getLogger ( ) . info ( "[MineGen] [" + biome + "] Complete. Branche s=" + com pletedBr an ch es. size ( ) ) ;
}
private static void generateSingleBranch ( Branch branch , Deque < Branch > queue , List < BlockPlacement > placements ,
Set < Long > fillPositions , int start Y, int minY , int [ ] branchCount ) {
Set < Long > fillPositions , int ceiling Y, int minY , int [ ] branchCount ) {
int cx = branch . startX ;
int cy = branch . startY ;
int cz = branch . startZ ;
@ -165,27 +280,28 @@ public class MineManager {
for ( int step = 0 ; step < branch . maxSize ; step + + ) {
int distance = branch . distanceFromStart + step ;
// Generate the 11x11 cross section at this position
generateCrossSection ( cx , cy , cz , branch . direction , distance , placements , fillPositions ) ;
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 > start Y + 5 ) break ;
if ( cy < minY | | cy > ceiling Y + 5 ) break ;
continue ;
}
// Calculate the current split increment based on how many branches exist
double splitIncrement = Math . max ( 0 , BASE_SPLIT_INCREMENT - ( branchCount [ 0 ] * SPLIT_DECAY_PER_BRANCH ) ) ;
// If increment has reached 0, no more splitting is possible — skip the roll entirely
if ( splitIncrement < = 0 ) {
cx + = branch . direction . getModX ( ) ;
cy + = branch . direction . getModY ( ) ;
cz + = branch . direction . getModZ ( ) ;
if ( cy < minY | | cy > start Y + 5 ) break ;
if ( cy < minY | | cy > ceiling Y + 5 ) break ;
continue ;
}
@ -204,34 +320,36 @@ public class MineManager {
int branchMaxSize = MIN_BRANCH_SIZE + random . nextInt ( MAX_BRANCH_SIZE - MIN_BRANCH_SIZE ) ;
// Skip UP if it would go above startY
if ( dir = = BlockFace . UP & & cy + branchMaxSize > startY ) {
plugin . getLogger ( ) . info ( "[MineGen] Skipped UP branch at Y=" + cy + " (would exceed startY=" + startY + ")" ) ;
continue ;
}
// Skip DOWN if it would go below minY
if ( dir = = BlockFace . DOWN & & cy - branchMaxSize < minY ) {
plugin . getLogger ( ) . info ( "[MineGen] Skipped DOWN branch at Y=" + cy + " (would go below minY=" + minY + ")" ) ;
continue ;
}
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 ) ) ;
queue . add ( new Branch ( dir , cx , cy , cz , distance , branchMaxSize , false , branch . biome ) ) ;
branchCount [ 0 ] + + ;
created + + ;
}
plugin . getLogger ( ) . info ( "[MineGen] Split at step=" + step + " pos=(" + cx + "," + cy + "," + cz + ") created=" + created + " branches, total=" + branchCount [ 0 ] + " splitInc=" + String . format ( "%.5f" , splitIncrement ) ) ;
plugin . getLogger ( ) . info ( "[MineGen] [" + branch . biome + "] Split at step=" + step
+ " pos=(" + cx + "," + cy + "," + cz + ") created=" + created
+ " total=" + branchCount [ 0 ] ) ;
}
// Advance position along the branch direction
cx + = branch . direction . getModX ( ) ;
cy + = branch . direction . getModY ( ) ;
cz + = branch . direction . getModZ ( ) ;
if ( cy < minY | | cy > start Y + 5 ) break ;
if ( cy < minY | | cy > ceiling Y + 5 ) break ;
}
}
private static Branch findLowestBranch ( List < Branch > branches ) {
Branch lowest = null ;
for ( Branch b : branches ) {
if ( lowest = = null | | b . endY < lowest . endY ) {
lowest = b ;
}
}
return lowest ;
}
private static List < BlockFace > getAvailableDirections ( BlockFace current ) {
List < BlockFace > dirs = new ArrayList < > ( ) ;
BlockFace opposite = current . getOppositeFace ( ) ;
@ -243,8 +361,10 @@ public class MineManager {
return dirs ;
}
// ===================== CROSS SECTION GENERATION =====================
private static void generateCrossSection ( int cx , int cy , int cz , BlockFace direction , int distance ,
List < BlockPlacement > placements , Set < Long > fillPositions ) {
MineBiome biome , List < BlockPlacement > placements , Set < Long > fillPositions ) {
int [ ] [ ] offsets = getPerpOffsets ( direction ) ;
for ( int a = - TUNNEL_RADIUS ; a < = TUNNEL_RADIUS ; a + + ) {
@ -256,117 +376,209 @@ public class MineManager {
long key = posKey ( bx , by , bz ) ;
if ( ! fillPositions . add ( key ) ) continue ;
Material mat = pickBlock ForDistance ( distance ) ;
Material mat = pickFill ForDistance ( distance , biom e ) ;
placements . add ( new BlockPlacement ( bx , by , bz , mat ) ) ;
}
}
}
// --- Biome fill + ore + transition logic ---
private static Material pickBlockForDistance ( int distance ) {
// Determine primary biome and check if we're in a transition zone
Material primaryFill = getPrimaryFill ( distance ) ;
OreEntry [ ] primaryOres = getOreTable ( primaryFill ) ;
// ===================== FILL SELECTION (no ores during generation) =====================
// Check transition: are we near a biome border?
int [ ] border = getNearestBorder ( distanc e) ;
// border[0] = border distance threshold, border[1] = signed distance to border (negative = before, positive = after)
private static Material pickFillForDistance ( int distance , MineBiome biome ) {
Material [ ] fills = getFillsForBiome ( biome ) ;
int [ ] thresholds = getThresholdsForBiome ( biome ) ;
if ( border ! = null & & Math . abs ( border [ 1 ] ) < = TRANSITION_RANGE ) {
// We're in a transition zone — blend with the neighboring biome
Material neighborFill = ( border [ 1 ] < = 0 )
? getNextFill ( primaryFill ) // approaching next biome
: getPrevFill ( primaryFill ) ; // just crossed into this biome from previous
OreEntry [ ] neighborOres = getOreTable ( neighborFill ) ;
// Blend ratio: 0.0 at edge of transition, 0.5 at the border itself
double blendRatio = 0 . 5 * ( 1 . 0 - ( double ) Math . abs ( border [ 1 ] ) / TRANSITION_RANGE ) ;
// Determine primary fill
Material primaryFill = fills [ 0 ] ;
for ( int i = thresholds . length - 1 ; i > = 0 ; i - - ) {
if ( distance > = thresholds [ i ] ) {
primaryFill = fills [ i ] ;
break ;
}
}
// Roll for neighbor fill block
// 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 ) {
// Use neighbor biome's fill + ores
Material ore = rollOreTable ( neighborOres ) ;
return ( ore ! = null ) ? ore : neighborFill ;
primaryFill = transitionFill ;
}
}
// Primary biome: roll for ore
Material ore = rollOreTable ( primaryOres ) ;
return ( ore ! = null ) ? ore : primaryFill ;
// 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 rollOreTable ( OreEntry [ ] tabl e ) {
private static Material rollSpecialBlock ( MineBiome biome , Material fill , int distanc e ) {
double roll = random . nextDouble ( ) ;
double cumulative = 0 ;
for ( OreEntry entry : table ) {
cumulative + = entry . chance ;
if ( roll < cumulative ) return entry . material ;
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 getPrimaryFill ( int distance ) {
if ( distance < STONE_END ) return Material . STONE ;
if ( distance < TUFF_END ) return Material . TUFF ;
if ( distance < DEEPSLATE_END ) return Material . DEEPSLATE ;
if ( distance < BASALT_END ) return Material . SMOOTH_BASALT ;
return Material . BLACKSTONE ;
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 OreEntry [ ] getOreTable ( Material fill ) {
return switch ( fill ) {
case STONE - > STONE_ORES ;
case TUFF - > TUFF_ORES ;
case DEEPSLATE - > DEEPSLATE_ORES ;
case SMOOTH_BASALT - > BASALT_ORES ;
case BLACKSTONE - > BLACKSTONE_ORES ;
default - > STONE_ORES ;
} ;
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 getNextFill ( Material fill ) {
return switch ( fill ) {
case STONE - > Material . TUFF ;
case TUFF - > Material . DEEPSLATE ;
case DEEPSLATE - > Material . SMOOTH_BASALT ;
case SMOOTH_BASALT - > Material . BLACKSTONE ;
default - > Material . BLACKSTONE ; // blackstone has no next
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 Material getPrevFill ( Material fill ) {
return switch ( fill ) {
case TUFF - > Material . STONE ;
case DEEPSLATE - > Material . TUFF ;
case SMOOTH_BASALT - > Material . DEEPSLATE ;
case BLACKSTONE - > Material . SMOOTH_BASALT ;
default - > Material . STONE ; // stone has no prev
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 ) ;
// 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 ) ;
}
/ * *
* Returns the nearest biome border info , or null if not near any .
* Result : [ 0 ] = border distance , [ 1 ] = signed distance to border ( negative = before border , positive = after )
* Looks at the 6 blocks surrounding the broken block and finds the deepest fill material .
* /
private static int [ ] getNearestBorder ( int distance ) {
int [ ] borders = { STONE_END , TUFF_END , DEEPSLATE_END , BASALT_END } ;
int closestDist = Integer . MAX_VALUE ;
int closestBorder = - 1 ;
for ( int b : borders ) {
int diff = distance - b ;
if ( Math . abs ( diff ) < Math . abs ( closestDist ) ) {
closestDist = diff ;
closestBorder = b ;
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 ( ) ;
int index = ALL_FILLS_ORDERED . indexOf ( type ) ;
if ( index > deepestIndex ) {
deepestIndex = index ;
deepest = type ;
}
}
if ( closestBorder = = - 1 | | Math . abs ( closestDist ) > TRANSITION_RANGE ) return null ;
return new int [ ] { closestBorder , closestDist } ;
return deepest ;
}
/ * *
* 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 ;
}
return table [ table . length - 1 ] . material ;
}
// --- Perpendicular axes ---
// ===================== PERPENDICULAR AXES =====================
private static int [ ] [ ] getPerpOffsets ( BlockFace dir ) {
return switch ( dir ) {
@ -377,11 +589,11 @@ public class MineManager {
} ;
}
// --- Wall generation ---
// ===================== WALL GENERATION =====================
private static void generateWalls ( Set < Long > fillPositions , List < BlockPlacement > wallPlacements , int startY ) {
Set < Long > wallPositions = new HashSet < > ( ) ;
plugin . getLogger ( ) . info ( "[MineGen] Generating walls for " + fillPositions . size ( ) + " fill positions..." ) ;
plugin . getLogger ( ) . info ( "[MineGen] Generating bedrock walls for " + fillPositions . size ( ) + " fill positions..." ) ;
for ( long key : fillPositions ) {
int x = decodeX ( key ) ;
@ -399,21 +611,34 @@ public class MineManager {
if ( ny > startY ) continue ;
if ( wallPositions . add ( neighborKey ) ) {
wallPlacements . add ( new BlockPlacement ( nx , ny , nz , Material . POLISHED_BLACKSTONE ) ) ;
wallPlacements . add ( new BlockPlacement ( nx , ny , nz , Material . BEDROCK ) ) ;
}
}
}
}
}
// --- Block placement scheduling ---
// ===================== BLOCK PLACEMENT SCHEDULING =====================
private static void scheduleBlockPlacements ( World world , List < BlockPlacement > fillPlacements ,
List < BlockPlacement > wallPlacements , int startY , int minY , int maxY ) {
List < BlockPlacement > allPlacements = new ArrayList < > ( fillPlacements . size ( ) + wallPlacements . size ( ) + 200 ) ;
allPlacements . addAll ( fillPlacements ) ;
List < BlockPlacement > wallPlacements ,
int startY , int minY , int maxY ) {
// Three-phase placement: 1) Dirt placeholder 2) Bedrock walls 3) Actual fill
List < BlockPlacement > allPlacements = new ArrayList < > ( ) ;
// Phase A: Dirt placeholders at all fill positions
List < BlockPlacement > dirtPlaceholders = new ArrayList < > ( fillPlacements . size ( ) ) ;
for ( BlockPlacement bp : fillPlacements ) {
dirtPlaceholders . add ( new BlockPlacement ( bp . x , bp . y , bp . z , Material . DIRT ) ) ;
}
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 + + ) {
@ -424,7 +649,9 @@ public class MineManager {
int totalBlocks = allPlacements . size ( ) ;
int tickDelay = 0 ;
plugin . getLogger ( ) . info ( "[MineGen] Scheduling " + totalBlocks + " block placements (" + fillPlacements . size ( ) + " fill + " + wallPlacements . size ( ) + " walls) in batches of " + BLOCKS_PER_TICK ) ;
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 ;
@ -447,7 +674,7 @@ public class MineManager {
finalDelay + 2 ) ;
}
// --- Position encoding/decoding ---
// ===================== POSITION ENCODING/DECODING =====================
private static long posKey ( int x , int y , int z ) {
return ( ( long ) ( x + 30000000 ) & 0x3FFFFFF )