Skip to content

Commit 2084dd8

Browse files
committed
BUG fixed spawn tree in water
1 parent f02fc55 commit 2084dd8

2 files changed

Lines changed: 126 additions & 63 deletions

File tree

src/main/java/wagemaker/uk/network/GameServer.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -584,15 +584,61 @@ private void generateChunksAroundPosition(float centerX, float centerY) {
584584
int endX = ((int)centerX / chunkSize + chunksRadius) * chunkSize;
585585
int endY = ((int)centerY / chunkSize + chunksRadius) * chunkSize;
586586

587+
Map<String, TreeState> newTrees = new HashMap<>();
588+
int newStonesCount = 0;
589+
587590
for (int x = startX; x <= endX; x += chunkSize) {
588591
for (int y = startY; y <= endY; y += chunkSize) {
589-
// Generate tree at this position (if it should exist)
590-
// This populates the server's WorldState only
591-
worldState.generateTreeAt(x, y);
592+
String key = x + "," + y;
593+
594+
// Generate tree at this position (only if it doesn't already exist)
595+
// We check existence first to avoid re-broadcasting existing trees
596+
if (!worldState.getTrees().containsKey(key)) {
597+
TreeState newTree = worldState.generateTreeAt(x, y);
598+
// generateTreeAt returns the tree if it was just created OR if it already existed (race condition)
599+
// We only want to broadcast if we are relatively sure it's new, or forcing a sync.
600+
// Given the outer check, if we get a tree back, it's likely new or we missed it.
601+
if (newTree != null) {
602+
newTrees.put(newTree.getTreeId(), newTree);
603+
}
604+
}
592605

593-
// Generate stone at this position (if it should exist)
594-
worldState.generateStoneAt(x, y, centerX, centerY);
606+
// Generate stone at this position (only if it doesn't already exist)
607+
if (!worldState.getStones().containsKey(key)) {
608+
StoneState newStone = worldState.generateStoneAt(x, y, centerX, centerY);
609+
if (newStone != null) {
610+
// Broadcast new stone immediately
611+
StoneCreatedMessage stoneMsg = new StoneCreatedMessage(
612+
"server",
613+
newStone.getStoneId(),
614+
newStone.getX(),
615+
newStone.getY(),
616+
newStone.getHealth()
617+
);
618+
broadcastToAll(stoneMsg);
619+
newStonesCount++;
620+
}
621+
}
595622
}
596623
}
624+
625+
// Broadcast new trees in a batch update
626+
if (!newTrees.isEmpty()) {
627+
System.out.println("[GameServer] Generated " + newTrees.size() + " new trees around (" + centerX + "," + centerY + ")");
628+
629+
WorldStateUpdateMessage updateMsg = new WorldStateUpdateMessage(
630+
"server",
631+
new HashMap<>(), // no player updates
632+
newTrees, // tree updates
633+
new HashMap<>(), // no item updates
634+
new HashMap<>() // no fence updates
635+
);
636+
637+
broadcastToAll(updateMsg);
638+
}
639+
640+
if (newStonesCount > 0) {
641+
System.out.println("[GameServer] Generated " + newStonesCount + " new stones around (" + centerX + "," + centerY + ")");
642+
}
597643
}
598644
}

src/main/java/wagemaker/uk/network/WorldState.java

Lines changed: 75 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)