@@ -98,20 +98,15 @@ private void generateInitialTrees() {
9898
9999 // Create a temporary BiomeManager for biome queries during initial generation
100100 // This ensures server-side tree generation respects biome boundaries
101- wagemaker .uk .biome .BiomeManager biomeManager = null ;
102- boolean useBiomeAwareness = true ;
101+ wagemaker .uk .biome .BiomeManager biomeMgr = getBiomeManager () ;
102+ boolean useBiomeAwareness = ( biomeMgr != null ) ;
103103
104- try {
105- biomeManager = new wagemaker .uk .biome .BiomeManager ();
106- biomeManager .initialize ();
104+ if (useBiomeAwareness ) {
107105 System .out .println ("BiomeManager initialized successfully in WorldState (thread: " + Thread .currentThread ().getName () + ")" );
108- } catch ( Exception e ) {
106+ } else {
109107 // BiomeManager initialization failed (likely due to OpenGL context issues on background thread)
110108 // Fall back to generating trees without biome awareness
111- System .err .println ("BiomeManager initialization failed in WorldState, generating trees without biome awareness: " + e .getMessage ());
112- e .printStackTrace ();
113- useBiomeAwareness = false ;
114- biomeManager = null ;
109+ System .err .println ("BiomeManager unavailable in WorldState, generating trees without biome awareness" );
115110 }
116111
117112 // Generate trees in a 5000x5000 area around spawn (-2500 to +2500)
@@ -150,11 +145,14 @@ private void generateInitialTrees() {
150145 treeY = y + offsetY ;
151146
152147 // Check biome type first to determine minimum distance
153- wagemaker .uk .biome .BiomeType biomeCheck = null ;
154- if (useBiomeAwareness && biomeManager != null ) {
155- biomeCheck = biomeManager .getBiomeAtPosition (treeX , treeY );
148+ float minDistance = 192f ; // Default for grass
149+
150+ if (useBiomeAwareness ) {
151+ wagemaker .uk .biome .BiomeType biomeCheck = biomeMgr .getBiomeAtPosition (treeX , treeY );
152+ if (biomeCheck == wagemaker .uk .biome .BiomeType .SAND ) {
153+ minDistance = 50f ;
154+ }
156155 }
157- float minDistance = (biomeCheck == wagemaker .uk .biome .BiomeType .SAND ) ? 50f : 192f ;
158156
159157 // Check if any tree is too close (192px for grass, 50px for sand)
160158 if (!isTreeTooClose (treeX , treeY , minDistance )) {
@@ -178,8 +176,13 @@ private void generateInitialTrees() {
178176 // This ensures server generates the same biome-specific trees as clients
179177 TreeType treeType = null ;
180178
181- if (useBiomeAwareness && biomeManager != null ) {
182- wagemaker .uk .biome .BiomeType biome = biomeManager .getBiomeAtPosition (treeX , treeY );
179+ if (useBiomeAwareness && biomeMgr != null ) {
180+ wagemaker .uk .biome .BiomeType biome = biomeMgr .getBiomeAtPosition (treeX , treeY );
181+
182+ // BIOME RULE 1: Nothing spawns in water
183+ if (biome == wagemaker .uk .biome .BiomeType .WATER ) {
184+ continue ;
185+ }
183186
184187 // STEP 5: Generate tree based on biome type (SAME LOGIC AS CLIENT)
185188 if (biome == wagemaker .uk .biome .BiomeType .SAND ) {
@@ -189,10 +192,6 @@ private void generateInitialTrees() {
189192 }
190193 } else {
191194 // Grass biomes: adjusted tree type distribution
192- // SmallTree: 42.5% (increased by 30% from 32.5%)
193- // AppleTree: 12.5% (reduced by 50% from 25%)
194- // CoconutTree: 32.5% (unchanged)
195- // BananaTree: 12.5% (reduced by 50% from 25%)
196195 float treeTypeRoll = random .nextFloat ();
197196
198197 if (treeTypeRoll < 0.425f ) {
@@ -234,11 +233,6 @@ private void generateInitialTrees() {
234233 }
235234 }
236235
237- // Clean up temporary BiomeManager
238- if (biomeManager != null ) {
239- biomeManager .dispose ();
240- }
241-
242236 if (useBiomeAwareness ) {
243237 System .out .println ("Generated " + trees .size () + " initial biome-specific trees for world seed: " + worldSeed );
244238 } else {
@@ -311,41 +305,46 @@ public TreeState generateTreeAt(int x, int y) {
311305 // Check if position has a planted sapling
312306 if (plantedTrees .containsKey (key ) || plantedBamboos .containsKey (key ) ||
313307 plantedBananaTrees .containsKey (key ) || plantedAppleTrees .containsKey (key )) {
314- System .out .println ("[DEBUG] Skipping generation at " + key + " - sapling exists" );
308+ // System.out.println("[DEBUG] Skipping generation at " + key + " - sapling exists");
315309 return null ;
316310 }
317311
318312 // Check if position was cleared (prevents immediate regeneration)
319313 if (clearedPositions .contains (key )) {
320- System .out .println ("[DEBUG] Skipping generation at " + key + " - position cleared" );
314+ // System.out.println("[DEBUG] Skipping generation at " + key + " - position cleared");
321315 return null ;
322316 }
323317
324318 // Use deterministic random seed (same as client-side generation)
325319 java .util .Random random = new java .util .Random ();
326320 random .setSeed (worldSeed + x * 31L + y * 17L );
327321
328- // Check spawn probability (2% chance, increased by 30% )
322+ // Check spawn probability (2.6 % chance)
329323 if (random .nextFloat () < 0.026f ) {
330324 // Add random offset to break grid pattern (±32px in each direction)
331325 // Try multiple times to find a position without overlapping trees
332326 float treeX = 0 , treeY = 0 ;
333327 boolean validPosition = false ;
334328 int maxAttempts = 5 ;
335329
330+ wagemaker .uk .biome .BiomeManager biomeMgr = getBiomeManager ();
331+ boolean useBiomeAwareness = (biomeMgr != null );
332+
336333 for (int attempt = 0 ; attempt < maxAttempts ; attempt ++) {
337334 float offsetX = (random .nextFloat () - 0.5f ) * 64 ; // -32 to +32
338335 float offsetY = (random .nextFloat () - 0.5f ) * 64 ; // -32 to +32
339336 treeX = x + offsetX ;
340337 treeY = y + offsetY ;
341338
342339 // Check biome type first to determine minimum distance
343- wagemaker .uk .biome .BiomeManager tempBiomeManager = new wagemaker .uk .biome .BiomeManager ();
344- tempBiomeManager .initialize ();
345- wagemaker .uk .biome .BiomeType biomeCheck = tempBiomeManager .getBiomeAtPosition (treeX , treeY );
346- tempBiomeManager .dispose ();
340+ float minDistance = 192f ; // Default for grass
347341
348- float minDistance = (biomeCheck == wagemaker .uk .biome .BiomeType .SAND ) ? 50f : 192f ;
342+ if (useBiomeAwareness ) {
343+ wagemaker .uk .biome .BiomeType biomeCheck = biomeMgr .getBiomeAtPosition (treeX , treeY );
344+ if (biomeCheck == wagemaker .uk .biome .BiomeType .SAND ) {
345+ minDistance = 50f ;
346+ }
347+ }
349348
350349 // Check if any tree is too close (192px for grass, 50px for sand)
351350 if (!isTreeTooClose (treeX , treeY , minDistance )) {
@@ -360,36 +359,31 @@ public TreeState generateTreeAt(int x, int y) {
360359 }
361360
362361 // Don't spawn trees too close to spawn point (within 200px)
363- // This is deterministic based on coordinates only
364362 float distanceFromSpawn = (float ) Math .sqrt (treeX * treeX + treeY * treeY );
365363 if (distanceFromSpawn < 200 ) {
366364 return null ;
367365 }
368366
369- // Determine tree type using biome-aware logic (if available)
367+ // Determine tree type
370368 TreeType treeType = null ;
371369
372- // Try to use biome manager for tree type determination
373- try {
374- wagemaker .uk .biome .BiomeManager biomeManager = new wagemaker .uk .biome .BiomeManager ();
375- biomeManager .initialize ();
376- wagemaker .uk .biome .BiomeType biome = biomeManager .getBiomeAtPosition (treeX , treeY );
377- biomeManager .dispose ();
370+ if (useBiomeAwareness ) {
371+ wagemaker .uk .biome .BiomeType biome = biomeMgr .getBiomeAtPosition (treeX , treeY );
372+
373+ // BIOME RULE 1: Nothing spawns in water
374+ if (biome == wagemaker .uk .biome .BiomeType .WATER ) {
375+ return null ;
376+ }
378377
379378 if (biome == wagemaker .uk .biome .BiomeType .SAND ) {
380379 // Sand biomes: bamboo trees with 30% spawn rate (reduced by 70%)
381380 if (random .nextFloat () < 0.3f ) {
382381 treeType = TreeType .BAMBOO ;
383382 }
384383 } else {
385- // Grass biomes: increased tree type distribution
386- // Normalized from 130% to 100% while maintaining 30% increase ratios
387- // SmallTree: 42.5% (55.25/1.3 = 42.5%)
388- // AppleTree: 12.5% (16.25/1.3 = 12.5%)
389- // CoconutTree: 32.5% (42.25/1.3 = 32.5%)
390- // BananaTree: 12.5% (16.25/1.3 = 12.5%)
391- // Note: Overall spawn rate increased by 30% to achieve desired effect
384+ // Grass biomes: adjusted tree type distribution
392385 float treeTypeRoll = random .nextFloat ();
386+
393387 if (treeTypeRoll < 0.425f ) {
394388 treeType = TreeType .SMALL ;
395389 } else if (treeTypeRoll < 0.55f ) {
@@ -400,9 +394,10 @@ public TreeState generateTreeAt(int x, int y) {
400394 treeType = TreeType .BANANA ;
401395 }
402396 }
403- } catch ( Exception e ) {
404- // Fallback: generate without biome awareness
397+ } else {
398+ // Fallback: generate trees without biome awareness
405399 float treeTypeRoll = random .nextFloat ();
400+
406401 if (treeTypeRoll < 0.2f ) {
407402 treeType = TreeType .SMALL ;
408403 } else if (treeTypeRoll < 0.4f ) {
@@ -464,17 +459,15 @@ public StoneState generateStoneAt(int x, int y, float playerX, float playerY) {
464459 return null ;
465460 }
466461
467- // Only spawn stones on sand biomes
468- try {
469- wagemaker .uk .biome .BiomeManager biomeManager = new wagemaker .uk .biome .BiomeManager ();
470- biomeManager .initialize ();
471- wagemaker .uk .biome .BiomeType biome = biomeManager .getBiomeAtPosition (stoneX , stoneY );
472- biomeManager .dispose ();
473-
462+ // Only spawn stones on sand biomes (never in water or grass)
463+ wagemaker .uk .biome .BiomeManager biomeMgr = getBiomeManager ();
464+ if (biomeMgr != null ) {
465+ wagemaker .uk .biome .BiomeType biome = biomeMgr .getBiomeAtPosition (stoneX , stoneY );
474466 if (biome != wagemaker .uk .biome .BiomeType .SAND ) {
475467 return null ;
476468 }
477- } catch (Exception e ) {
469+ } else {
470+ // If biome manager fails, do not spawn stones to be safe
478471 return null ;
479472 }
480473
@@ -494,6 +487,30 @@ public StoneState generateStoneAt(int x, int y, float playerX, float playerY) {
494487 }
495488
496489
490+ private transient wagemaker .uk .biome .BiomeManager biomeManager ;
491+
492+ /**
493+ * Gets or initializes the persistent BiomeManager instance.
494+ * Use this method instead of creating new instances to ensure performance and consistency.
495+ * Handles server-side initialization where OpenGL might be missing.
496+ */
497+ private wagemaker .uk .biome .BiomeManager getBiomeManager () {
498+ if (biomeManager == null ) {
499+ try {
500+ biomeManager = new wagemaker .uk .biome .BiomeManager ();
501+ biomeManager .initialize ();
502+ } catch (Exception e ) {
503+ System .err .println ("WorldState: Failed to initialize BiomeManager: " + e .getMessage ());
504+ // Try to force initialization if possible, or just leave it null
505+ // BiomeManager usually handles its own headless fallback in initialize()
506+ // so if it throws here, something is very wrong.
507+ // We'll leave it null so logic falls back to non-biome behavior somewhat safely (though desync risk remains)
508+ // Ideally BiomeManager.initialize() should act as a safe barrier.
509+ }
510+ }
511+ return biomeManager ;
512+ }
513+
497514 /**
498515 * Creates a complete snapshot of the current world state.
499516 * This is used when a new client connects to get the full state.
0 commit comments