#+feature dynamic-literals package game import "core:fmt" import "core:math" import "core:math/noise" BIOME_SCALE: f64 : 1 biome_list := map[u32]Biome { 0 = grasslands_biome, 1 = forest_biome, 2 = desert_biome, 3 = lake_biome, } BiomeType :: enum { GRASSLAND, FOREST, LAKE, DESERT, } Biome :: struct { id: u32, name: string, type: BiomeType, fauna_color: [4]u8, valid_structures: [dynamic]u32, } // Define biome constants grasslands_biome := Biome { id = 0, name = "Grasslands", type = .GRASSLAND, fauna_color = {50, 120, 25, 255}, valid_structures = {}, } forest_biome := Biome { id = 1, name = "Forest", type = .FOREST, fauna_color = {30, 80, 20, 255}, valid_structures = {}, } desert_biome := Biome { id = 2, name = "Desert", type = .DESERT, fauna_color = {200, 180, 100, 255}, valid_structures = {}, } lake_biome := Biome { id = 3, name = "Lake", type = .LAKE, fauna_color = {0, 50, 150, 255}, valid_structures = {}, } get_biome_from_id :: proc(id: u32) -> Biome { return biome_list[id] } get_biome_type :: proc(world_pos: Vec2i, seed: i64) -> Biome { // Use multiple noise scales for different features continent_scale := 0.0008 // Very large scale features (continents) region_scale := 0.007 // Medium scale features (regions) local_scale := 0.025 // Local variations // Use different seed offsets for each noise layer continent_seed := seed region_seed := seed + 10000 moisture_seed := seed + 20000 temperature_seed := seed + 30000 // Generate base continent shapes continent := noise.noise_2d( continent_seed, {f64(world_pos.x) * continent_scale, f64(world_pos.y) * continent_scale}, ) // Amplify to get more defined continents continent = math.pow(continent * 0.5 + 0.5, 1.5) * 2.0 - 1.0 // Generate regional variations region := noise.noise_2d( region_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, ) // Generate moisture and temperature maps for biome determination moisture := noise.noise_2d( moisture_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, ) temperature := noise.noise_2d( temperature_seed, {f64(world_pos.x) * region_scale, f64(world_pos.y) * region_scale}, ) // Adjust temperature to create larger hot regions // This skews the distribution to have more areas with higher temperature // temperature = math.pow(temperature * 0.5 + 0.5, 0.8) * 2.0 - 1.0 // Local variations (small details) local_var := noise.noise_2d(seed, {f64(world_pos.x) * local_scale, f64(world_pos.y) * local_scale}) * 0.1 // Combine all factors with proper weighting elevation := continent * 0.7 + region * 0.3 + local_var // Convert noise values to 0-1 range for easier thresholding normalized_elevation := elevation * 0.5 + 0.5 normalized_moisture := moisture * 0.5 + 0.5 normalized_temperature := temperature * 0.5 + 0.5 if normalized_elevation < 0.3 { return lake_biome } if normalized_temperature > 0.7 && normalized_moisture < 0.2 { return desert_biome } // Forests need moderate to high moisture if normalized_moisture > 0.55 { return forest_biome } // Default to grasslands return grasslands_biome } // Improved chunk generation that considers neighboring chunks generate_chunk :: proc(pos: Vec2i, seed: i64) -> Chunk { chunk := Chunk { position = pos, } // Store the biome for this chunk for consistency chunk_center := Vec2i{pos.x * CHUNK_SIZE + CHUNK_SIZE / 2, pos.y * CHUNK_SIZE + CHUNK_SIZE / 2} biome := get_biome_type(chunk_center, seed) chunk.biome_id = biome.id // Generate each tile, allowing for biome blending at edges for x in 0 ..< CHUNK_SIZE { for y in 0 ..< CHUNK_SIZE { world_x := pos.x * CHUNK_SIZE + x world_y := pos.y * CHUNK_SIZE + y world_pos := Vec2i{world_x, world_y} // Check the tile's specific biome (for transitions) tile_biome := get_biome_type(world_pos, seed) // Calculate distances to chunk edges for potential blending edge_dist_x := min(x, CHUNK_SIZE - 1 - x) edge_dist_y := min(y, CHUNK_SIZE - 1 - y) edge_dist := min(edge_dist_x, edge_dist_y) // Blend between chunk biome and tile biome near edges // for smoother transitions between chunks biome_to_use := biome if edge_dist < 4 { // Within 4 tiles of chunk edge blend_factor := f32(edge_dist) / 4.0 // Simple way to blend biomes - just pick one based on blend factor // For a more sophisticated approach, you could actually blend features if hash_noise(world_x, world_y, seed) > blend_factor { biome_to_use = tile_biome } } chunk.tiles[x][y] = generate_tile(world_pos, seed, biome_to_use) } } return chunk } // Improved tile generation with biome transition support generate_tile :: proc(pos: Vec2i, seed: i64, biome: Biome) -> Tile { hash_value := hash_noise(pos.x, pos.y, seed) // Use multiple noise scales for natural-looking features large_scale := 0.025 medium_scale := 0.07 small_scale := 0.20 large_noise := noise.noise_2d(seed, {f64(pos.x) * large_scale, f64(pos.y) * large_scale}) medium_noise := noise.noise_2d( seed + 5000, {f64(pos.x) * medium_scale, f64(pos.y) * medium_scale}, ) small_noise := noise.noise_2d( seed + 10000, {f64(pos.x) * small_scale, f64(pos.y) * small_scale}, ) // Combine noise at different scales combined_noise := large_noise * 0.6 + medium_noise * 0.3 + small_noise * 0.1 // Different biomes use the noise differently switch biome.type { case .GRASSLAND: if combined_noise > 0.8 { return tree_tile } else if combined_noise > 0.2 { return grass_tile } else { return nothing_tile } case .FOREST: if combined_noise > 0.75 { return double_tree_tile } else if combined_noise > 0.4 { return tree_tile } else if combined_noise > 0.0 { return grass_tile } else { return nothing_tile } case .DESERT: cactus_noise := medium_noise * 0.5 + 0.5 // Normalize to 0-1 if cactus_noise > 0.8 && hash_value > 0.65 { return cactus_tile } else if combined_noise > 0.85 { return dead_bush_tile } else { return nothing_tile } case .LAKE: // Lakes can have different depths if combined_noise > 0.7 { return shallow_water_tile // You'd need to define this } else { return water_tile } case: return nothing_tile } }