diff --git a/game/debug_terrain_tools.odin b/game/debug_terrain_tools.odin new file mode 100644 index 0000000..f817fd7 --- /dev/null +++ b/game/debug_terrain_tools.odin @@ -0,0 +1,206 @@ +package game + +import "core:fmt" +import "core:math/noise" + +// Fixed desert finding procedure +find_desert :: proc(seed: i64) -> (found: bool, pos: Vec2i) { + search_radius := 1000 + step_size := 20 // Check every 20 blocks to speed up the search + + // Track how many desert tiles we find for debugging + desert_count := 0 + total_checked := 0 + last_desert_pos := Vec2i{0, 0} + + fmt.println("Searching for deserts with seed:", seed) + + for x := -search_radius; x < search_radius; x += step_size { + for y := -search_radius; y < search_radius; y += step_size { + pos := Vec2i{x, y} + biome := get_biome_type(pos, seed) + total_checked += 1 + + if biome.type == .DESERT { + desert_count += 1 + last_desert_pos = pos + fmt.println("Found desert at:", pos) + + if desert_count <= 5 { // Only report the first few to avoid spam + // Verify by checking adjacent tiles to confirm it's not just a single glitched tile + desert_size := 0 + check_radius := 3 + + for cx := -check_radius; cx <= check_radius; cx += 1 { + for cy := -check_radius; cy <= check_radius; cy += 1 { + check_pos := Vec2i{x + cx, y + cy} + check_biome := get_biome_type(check_pos, seed) + + if check_biome.type == .DESERT { + desert_size += 1 + } + } + } + + fmt.println(" Desert size (in 7x7 area):", desert_size, "out of", (check_radius*2+1)*(check_radius*2+1)) + } + } + } + } + + // Report desert statistics + desert_percentage := f32(desert_count) / f32(total_checked) * 100.0 + fmt.println("Desert statistics:") + fmt.println(" Total positions checked:", total_checked) + fmt.println(" Desert tiles found:", desert_count) + fmt.println(" Desert percentage:", desert_percentage, "%") + + if desert_count > 0 { + return true, last_desert_pos // Return the last desert found + } else { + fmt.println("No desert found within search radius") + return false, Vec2i{0, 0} + } +} + +// Create a biome distribution map to visualize the actual distribution +generate_biome_map :: proc(seed: i64, width: int, height: int) { + biome_counts := [BiomeType]int{} + total_tiles := width * height + + fmt.println("Generating biome distribution map", width, "x", height) + + // First pass - count biomes + for y := 0; y < height; y += 1 { + for x := 0; x < width; x += 1 { + // Use a different area of the world for better sampling + world_x := (x - width/2) * 20 + world_y := (y - height/2) * 20 + + biome := get_biome_type(Vec2i{world_x, world_y}, seed) + biome_counts[biome.type] += 1 + + // Print a character representing each biome for a ASCII map + if y % 5 == 0 && x % 5 == 0 { // Print sparse map to fit in console + c := '?' + switch biome.type { + case .DESERT: c = 'D' + case .GRASSLAND: c = 'G' + case .FOREST: c = 'F' + case .LAKE: c = 'L' + } + fmt.print(c) + } + } + if y % 5 == 0 { + fmt.println() + } + } + + // Print biome statistics + fmt.println("\nBiome Distribution:") + fmt.println(" Total area:", total_tiles, "tiles") + + for biome_type, count in biome_counts { + percentage := f32(count) / f32(total_tiles) * 100.0 + fmt.println(" ", biome_type, ":", count, "tiles (", percentage, "%)") + } +} + +// Debug the noise distribution directly +debug_noise_values :: proc(seed: i64) { + // Import math package at the top of your file + // import "core:math" + + // Collect some sample values to see the actual distribution + samples := 1000 + temp_values := make([dynamic]f64, 0, samples) + moisture_values := make([dynamic]f64, 0, samples) + + for i := 0; i < samples; i += 1 { + // Sample across a wide area + x := (i % 50) * 100 - 2500 + y := (i / 50) * 100 - 2500 + + // Generate values the same way as in get_biome_type + continent_scale := 0.001 + region_scale := 0.005 + + moisture_seed := seed + 20000 + temperature_seed := seed + 30000 + + // Get raw noise values + moisture := noise.noise_2d(moisture_seed, {f64(x) * region_scale, f64(y) * region_scale}) + temperature := noise.noise_2d(temperature_seed, {f64(x) * region_scale, f64(y) * region_scale}) + + // Apply the same transformations as in your get_biome_type function + // Remove this line if you don't have math imported, or replace with your own pow implementation + // temperature = math.pow(temperature * 0.5 + 0.5, 0.8) * 2.0 - 1.0 + + // Normalize to 0-1 range + normalized_moisture := f64(moisture * 0.5 + 0.5) + normalized_temperature := f64(temperature * 0.5 + 0.5) + + append_elem(&temp_values, normalized_temperature) + append_elem(&moisture_values, normalized_moisture) + } + + // Calculate statistics + temp_min, temp_max, temp_avg := 1.0, 0.0, 0.0 + moisture_min, moisture_max, moisture_avg := 1.0, 0.0, 0.0 + + for i := 0; i < samples; i += 1 { + temp := temp_values[i] + moisture := moisture_values[i] + + temp_avg += temp + moisture_avg += moisture + + temp_min = min(temp_min, temp) + temp_max = max(temp_max, temp) + moisture_min = min(moisture_min, moisture) + moisture_max = max(moisture_max, moisture) + } + + temp_avg /= f64(samples) + moisture_avg /= f64(samples) + + // Print statistics + fmt.println("Temperature values (normalized to 0-1):") + fmt.println(" Min:", temp_min, "Max:", temp_max, "Avg:", temp_avg) + fmt.println("Moisture values (normalized to 0-1):") + fmt.println(" Min:", moisture_min, "Max:", moisture_max, "Avg:", moisture_avg) + + // Count how many points would qualify as deserts with different thresholds + desert_count_strict := 0 + desert_count_medium := 0 + desert_count_loose := 0 + + for i := 0; i < samples; i += 1 { + temp := temp_values[i] + moisture := moisture_values[i] + + // Strict: temp > 0.55 && moisture < 0.4 + if temp > 0.55 && moisture < 0.4 { + desert_count_strict += 1 + } + + // Medium: temp > 0.4 && moisture < 0.6 + if temp > 0.4 && moisture < 0.6 { + desert_count_medium += 1 + } + + // Loose: temp > 0.3 || moisture < 0.4 + if temp > 0.3 || moisture < 0.4 { + desert_count_loose += 1 + } + } + + fmt.println("\nDesert qualification rates with different thresholds:") + fmt.println(" Strict (temp > 0.55 && moisture < 0.4):", + f32(desert_count_strict)/f32(samples)*100.0, "%") + fmt.println(" Medium (temp > 0.4 && moisture < 0.6):", + f32(desert_count_medium)/f32(samples)*100.0, "%") + fmt.println(" Loose (temp > 0.3 || moisture < 0.4):", + f32(desert_count_loose)/f32(samples)*100.0, "%") +} diff --git a/game/game b/game/game index cdf8827..00ad5e3 100755 Binary files a/game/game and b/game/game differ diff --git a/game/game.odin b/game/game.odin index 1d5b546..db07bab 100644 --- a/game/game.odin +++ b/game/game.odin @@ -24,34 +24,28 @@ main :: proc() { rl.SetTargetFPS(60) - player = { - position = {CELL_SIZE * 10, CELL_SIZE * 10}, + position = {CELL_SIZE * 0, CELL_SIZE * 0}, camera = { - zoom = 3, + zoom = 4, target = {player.position.x + (CELL_SIZE / 2), player.position.y + (CELL_SIZE / 2)}, offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2}, }, mode = .INTERACT, + speed = 1, } load_tilemap() defer unload_tilemap() - world = create_world("test_world") - - set_tile(&world, tree_tile, {400,400}) - + world = create_world("test_world", 10172020) save_world(&world) - game_loop() } game_loop :: proc() { - pos_string : string - pos_cstring : cstring for !rl.WindowShouldClose() { @@ -69,16 +63,23 @@ game_loop :: proc() { player_grid_pos := get_player_grid_position(&player) player_grid_pos_tile := get_world_tile(&world, vec2_to_vec2i(player_grid_pos)) - status_string := rl.TextFormat("POS: [%i,%i] : %v | MODE: %v", int(player_grid_pos.x), int(player_grid_pos.y), player_grid_pos_tile.type, player.mode) - + current_chunk := get_chunk_from_world_pos(&world, player_grid_pos) + status_string := rl.TextFormat("POS: [%i,%i] : %v | Chunk: [%i,%i] : %v | MODE: %v", int(player_grid_pos.x), int(player_grid_pos.y), player_grid_pos_tile.type, current_chunk.position.x, current_chunk.position.y, get_biome_from_id(current_chunk.biome_id).name, player.mode) + pos_string := rl.TextFormat("Actual pos: %v", player.position) rl.DrawText(status_string, 5, 25, 20, rl.RED) + // Debug: Draw collision check position + target_pos := player_grid_pos + chunk_pos := world_pos_to_chunk_pos(player_grid_pos) + local_pos := get_local_chunk_pos(vec2_to_vec2i(player_grid_pos)) + format_string := rl.TextFormat("Grid: (%.0f,%.0f) Chunk: (%d,%d) Local: (%d,%d)", + player_grid_pos.x, player_grid_pos.y, + chunk_pos.x, chunk_pos.y, + local_pos.x, local_pos.y) + rl.DrawText(format_string, 10, 45, 20, rl.YELLOW) rl.EndDrawing() } - - delete(pos_string) - delete(pos_cstring) } update :: proc() { diff --git a/game/math.odin b/game/math.odin index 2cb0de4..433058e 100644 --- a/game/math.odin +++ b/game/math.odin @@ -1,5 +1,7 @@ package game +import "core:math" + Vec2i :: struct { x: int, y: int, @@ -10,17 +12,15 @@ vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 { } vec2_to_vec2i :: proc(v2:[2]f32) -> Vec2i { - return {int(v2.x), int(v2.y)} + return {int(math.floor(v2.x)), int(math.floor(v2.y))} } -to_bytes :: proc(v: $T) -> [size_of(T)]u8 { - val := v - encoded_bytes := (^[size_of(T)]u8)(&val) - return encoded_bytes^ +hash_noise :: proc(x, y: int, seed: i64) -> f32 { + h: i64 = i64(x) * 374761393 + h *= i64(y) * 668265263 + h *= seed + h *= 3266489917 + h >>= 16 + return f32(h & 0xFFFF) / 65535.0 } -from_bytes :: proc($T:typeid, data: [size_of(T)]u8) -> T { - bytes := data - decoded_value := (^T)(&bytes)^ - return decoded_value -} diff --git a/game/player.odin b/game/player.odin index e3d5c05..e78f943 100644 --- a/game/player.odin +++ b/game/player.odin @@ -2,6 +2,7 @@ package game import rl "vendor:raylib" import "core:fmt" +import "core:math" CHUNK_UNLOAD_DISTANCE :: 3 @@ -10,6 +11,7 @@ Player :: struct { move_timer: f32, mode: InteractMode, camera: rl.Camera2D, + speed:f32 } InteractMode :: enum { @@ -31,55 +33,42 @@ player_update :: proc(p : ^Player, w: ^World) { handle_player_input(p,w) handle_player_camera(p) - if rl.IsKeyPressed(.SPACE) { - set_tile(w, tree_tile, vec2_to_vec2i(get_player_grid_position(p))) - } + // if rl.IsKeyPressed(.SPACE) { + // // set_tile(w, bricks_tile, vec2_to_vec2i(get_player_grid_position(p))) + // find_desert(w.seed) + // generate_biome_map(w.seed, 100, 100) + // } } @(private="file") player_update_chunks :: proc(p: ^Player, w: ^World) { + // Configurable view distance (in chunks) + VIEW_DISTANCE :: 2 + player_grid_pos := get_player_grid_position(p) current_player_chunk := get_chunk_from_world_pos(w, player_grid_pos) - - directions := [8]Vec2i{ - Vec2i{ 1, 0 }, Vec2i{ -1, 0 }, // Right, Left - Vec2i{ 0, 1 }, Vec2i{ 0, -1 }, // Down, Up - Vec2i{ 1, 1 }, Vec2i{ -1, -1 }, // Bottom-right, Top-left - Vec2i{ 1, -1 }, Vec2i{ -1, 1 }, // Top-right, Bottom-left - } - - // Always ensure the current chunk is loaded - get_chunk(w, current_player_chunk.position) - - // Load adjacent chunks - for dir in directions { - adjacent_pos := Vec2i{ - current_player_chunk.position.x + dir.x, - current_player_chunk.position.y + dir.y + + // Track which chunks should be loaded + chunks_to_keep := make(map[Vec2i]bool) + defer delete(chunks_to_keep) + + // Load chunks in a square around the player's current chunk + for y := -VIEW_DISTANCE; y <= VIEW_DISTANCE; y += 1 { + for x := -VIEW_DISTANCE; x <= VIEW_DISTANCE; x += 1 { + chunk_pos := Vec2i{ + current_player_chunk.position.x + x, + current_player_chunk.position.y + y, + } + + // Load the chunk and mark it to keep + get_chunk(w, chunk_pos) + chunks_to_keep[chunk_pos] = true } - - get_chunk(w, adjacent_pos) } - - // Unload non-adjacent chunks + + // Unload chunks outside the view distance for chunk_pos in w.chunks { - if chunk_pos == current_player_chunk.position { - continue - } - - is_adjacent := false - for dir in directions { - check_pos := Vec2i{ - current_player_chunk.position.x + dir.x, - current_player_chunk.position.y + dir.y - } - if chunk_pos == check_pos { - is_adjacent = true - break - } - } - - if !is_adjacent { + if !chunks_to_keep[chunk_pos] { unload_chunk(chunk_pos, w) } } @@ -88,18 +77,29 @@ player_update_chunks :: proc(p: ^Player, w: ^World) { @(private="file") handle_player_input :: proc(p:^Player, w:^World) { + + current_tile := get_world_tile(w, vec2_to_vec2i(get_player_grid_position(p))) // Movement - target_pos := get_player_grid_position(p) + dt := rl.GetFrameTime() - move_delay : f32 = 0.2 + move_delay : f32 = 0.2 / p.speed if p.move_timer > 0 { p.move_timer -= dt } + + + if current_tile.type == .WATER { + p.speed = 0.3 + } + else { + p.speed = 1 + } if p.move_timer <= 0 { + current_pos := get_player_grid_position(p) if rl.IsKeyDown(.D) { - target_pos.x += 1 - if !will_collide(w, target_pos) { + target_pos := rl.Vector2{current_pos.x + 1, current_pos.y} + if !will_collide(.RIGHT, p, w) { player.position.x += CELL_SIZE p.move_timer = move_delay player_update_chunks(p,w) @@ -107,8 +107,8 @@ handle_player_input :: proc(p:^Player, w:^World) { } if rl.IsKeyDown(.A) { - target_pos.x -= 1 - if !will_collide(w, target_pos) { + target_pos := rl.Vector2{current_pos.x - 1, current_pos.y} + if !will_collide(.LEFT, p, w) { player.position.x -= CELL_SIZE p.move_timer = move_delay player_update_chunks(p,w) @@ -116,8 +116,8 @@ handle_player_input :: proc(p:^Player, w:^World) { } if rl.IsKeyDown(.W) { - target_pos.y -= 1 - if !will_collide(w, target_pos) { + target_pos := rl.Vector2{current_pos.x, current_pos.y - 1} + if !will_collide(.UP, p, w) { player.position.y -= CELL_SIZE p.move_timer = move_delay player_update_chunks(p,w) @@ -125,8 +125,8 @@ handle_player_input :: proc(p:^Player, w:^World) { } if rl.IsKeyDown(.S) { - target_pos.y += 1 - if !will_collide(w, target_pos) { + target_pos := rl.Vector2{current_pos.x, current_pos.y + 1} + if !will_collide(.DOWN, p, w) { p.move_timer = move_delay player.position.y += CELL_SIZE player_update_chunks(p,w) @@ -192,29 +192,36 @@ get_tile_in_direction :: proc(direction:InteractDirection, p:^Player, w:^World) } get_player_grid_position :: proc(player:^Player) -> rl.Vector2 { - grid_pos_x := player.position.x / CELL_SIZE - grid_pos_y := player.position.y / CELL_SIZE + grid_pos_x := math.floor(player.position.x / CELL_SIZE) + grid_pos_y := math.floor(player.position.y / CELL_SIZE) return {grid_pos_x, grid_pos_y} } draw_player :: proc(player:^Player) { - draw_tile({27,0}, player.position, rl.DARKBLUE) + draw_tile({25,0}, player.position, {30,100,120,255}) + + // Debug: Draw player's grid cell + // player_grid_pos := get_player_grid_position(player) + // world_pos_x := player_grid_pos.x * CELL_SIZE + // world_pos_y := player_grid_pos.y * CELL_SIZE + // rl.DrawRectangleLines( + // i32(world_pos_x), + // i32(world_pos_y), + // i32(CELL_SIZE), + // i32(CELL_SIZE), + // rl.RED + // ) + + } +will_collide :: proc(direction:InteractDirection, p:^Player, w:^World) -> bool { + tile, pos := get_tile_in_direction(direction, p, w) -will_collide :: proc(w:^World, pos:rl.Vector2) -> bool { - world_grid_pos := vec2_to_vec2i(pos) - chunk_pos := world_pos_to_chunk_pos(pos) - local_pos := get_local_chunk_pos(world_grid_pos) - - chunk := get_chunk(w, chunk_pos) - tile := get_chunk_tile(chunk, local_pos) - - #partial switch tile.type { - case .SOLID: - return true - } + if tile.type == .SOLID { return true } return false } + + diff --git a/game/structures.odin b/game/structures.odin new file mode 100644 index 0000000..dab5414 --- /dev/null +++ b/game/structures.odin @@ -0,0 +1,14 @@ +package game + +Structure :: struct { + name:string, + tile_map:[dynamic][dynamic]Tile, + // Other data here later like NPCs and enemies? +} + +test_structure := Structure { + name = "Test", + tile_map = { + // Make a structure here????? + } +} diff --git a/game/terrain.odin b/game/terrain.odin new file mode 100644 index 0000000..b251834 --- /dev/null +++ b/game/terrain.odin @@ -0,0 +1,227 @@ +package game + +import "core:math/noise" +import "core:math" +import "core:fmt" + +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.. 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 + } +} + + diff --git a/game/tiles.odin b/game/tiles.odin index b18e1a6..fde97cf 100644 --- a/game/tiles.odin +++ b/game/tiles.odin @@ -15,11 +15,13 @@ TileType :: enum u8 { NOTHING, SOLID, FOLIAGE, + WATER, } ResourceType :: enum u8 { NOTHING, TREE, + BONE, } InteractionType :: enum u8 { @@ -28,18 +30,92 @@ InteractionType :: enum u8 { ENEMY, } -nothing_tile := Tile { - type = .FOLIAGE, - tilemap_pos = {1,2}, - color = {30,30,0,255}, + +// Premade Tiles +nothing_tile := Tile { // The most common tile, makes up the majority of the world. + type = .NOTHING, + tilemap_pos = {0,0}, + color = {0,0,0,255}, interaction = .NOTHING, resource = .NOTHING } -tree_tile := Tile { +grass_tile := Tile { // Common fauna, more dense in grasslands + type = .FOLIAGE, + tilemap_pos = {5,0}, + color = {50,120,25,255}, + interaction = .NOTHING, + resource = .NOTHING +} + +tree_tile := Tile { // Common grassland fauna, dense population in forests type = .SOLID, tilemap_pos = {0,1}, - color = {17,87,30,255}, + color = {10,60,15,255}, resource = .TREE, interaction = .RESOURCE, } + +double_tree_tile := Tile { // Only found in forests, densly packed + type = .SOLID, + tilemap_pos = {3,2}, + color = {10,60,15,255}, + resource = .TREE, + interaction = .RESOURCE, +} + +bricks_tile := Tile { // Unused, for now + type = .SOLID, + tilemap_pos = {10,17}, + color = {140,30,10,255}, + resource = .NOTHING, + interaction = .NOTHING, +} + +water_tile := Tile { // Only seen in bodies of water + type = .WATER, + tilemap_pos = {19,1}, + color = {5,10,70,255}, + resource = .NOTHING, + interaction = .NOTHING, +} + +shallow_water_tile := Tile { // Only seen in bodies of water + type = .WATER, + tilemap_pos = {19,1}, + color = {5,40,80,255}, + resource = .NOTHING, + interaction = .NOTHING, +} + +cactus_tile := Tile { // Common desert fauna + type = .SOLID, + tilemap_pos = {6,1}, + color = {5,40,0,255}, + resource = .NOTHING, + interaction = .NOTHING, +} + +double_cactus_tile := Tile { // Sparse desert fauna + type = .SOLID, + tilemap_pos = {7,1}, + color = {5,40,0,255}, + resource = .NOTHING, + interaction = .NOTHING, +} + +cow_skull_tile := Tile { // Rare chance of spawning in a desert + type = .SOLID, + tilemap_pos = {1,15}, + color = {200,200,200,255}, + resource = .BONE, + interaction = .RESOURCE, +} + +dead_bush_tile := Tile { // Common desert fauna + type = .FOLIAGE, + tilemap_pos = {6,2}, + color = {145,100,30,255}, + interaction = .NOTHING, + resource = .NOTHING +} diff --git a/game/world.odin b/game/world.odin index 5044b55..3744f70 100644 --- a/game/world.odin +++ b/game/world.odin @@ -5,6 +5,7 @@ import "core:fmt" import "core:os" import "core:path/filepath" import "core:mem" +import "core:math" CELL_SIZE :: 16 CHUNK_SIZE :: 32 @@ -12,15 +13,17 @@ WORLD_DATA_PATH :: "data/worlds" World :: struct { data_dir: string, - chunks: map[Vec2i]Chunk + chunks: map[Vec2i]Chunk, + seed: i64 } Chunk :: struct #packed { position: Vec2i, tiles: [CHUNK_SIZE][CHUNK_SIZE]Tile, + biome_id:u32, } -create_world :: proc(name:string) -> World { +create_world :: proc(name:string, seed:i64) -> World { data_dir := fmt.tprintf("%v/%v", WORLD_DATA_PATH, name) if !os.is_dir(data_dir) { fmt.printfln("Data dir: %v does not exist", data_dir) @@ -34,11 +37,12 @@ create_world :: proc(name:string) -> World { return World { data_dir = data_dir, - chunks = make(map[Vec2i]Chunk), + chunks = make(map[Vec2i]Chunk), + seed = seed } } -load_world :: proc(name:string) -> World { +load_world :: proc(name:string, seed:i64) -> World { dir := fmt.tprintf("%v/%v", WORLD_DATA_PATH, name) if !os.is_dir(dir) { panic("Couldnt load world") @@ -47,6 +51,7 @@ load_world :: proc(name:string) -> World { return World { data_dir = dir, chunks = make(map[Vec2i]Chunk), + seed = seed } } @@ -86,6 +91,9 @@ save_chunk :: proc(c:^Chunk, w:^World) { } } + // Biome ID + for byte in transmute([size_of(u32)]u8)c.biome_id {append(&data, byte)} + err := os.write_entire_file_or_err(filename, data[:]) } @@ -97,7 +105,7 @@ load_chunk :: proc(pos:Vec2i, w:^World) -> Chunk { data, err := os.read_entire_file_from_filename_or_err(filename) if err != nil { // fmt.printfln("No chunk %v found, generating new chunk", pos) - chunk := generate_chunk(pos) + chunk := generate_chunk(pos, w.seed) save_chunk(&chunk, w) return chunk } @@ -135,6 +143,10 @@ load_chunk :: proc(pos:Vec2i, w:^World) -> Chunk { } } + // Load Biome ID + mem.copy(transmute([^]u8)&chunk.biome_id, &data[offset], size_of(u32)) + offset += size_of(u32) + return chunk } @@ -147,20 +159,6 @@ unload_chunk :: proc(pos:Vec2i, w:^World) { } } -generate_chunk :: proc(pos:Vec2i) -> Chunk { - chunk := Chunk {position = pos} - - for x in 0.. ^Chunk { chunk, exists := w.chunks[chunk_pos] @@ -176,24 +174,37 @@ get_chunk_from_world_pos :: proc(w:^World, pos:rl.Vector2) -> ^Chunk { } world_pos_to_chunk_pos :: proc(pos:rl.Vector2) -> Vec2i { - chunk_pos := vec2_to_vec2i({pos.x / CHUNK_SIZE, pos.y / CHUNK_SIZE}) - return chunk_pos + + x := int(math.floor(pos.x / CHUNK_SIZE)) + y := int(math.floor(pos.y / CHUNK_SIZE)) + return Vec2i{x,y} } get_local_chunk_pos :: proc(pos:Vec2i) -> Vec2i { - return Vec2i { - (pos.x % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE, - (pos.y % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE, - } + x := (pos.x % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE + y := (pos.y % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE + + return Vec2i{x,y} } get_world_tile :: proc(w:^World, pos:Vec2i) -> ^Tile { - chunk_pos := world_pos_to_chunk_pos(vec2i_to_vec2(pos)) - local_pos := get_local_chunk_pos(pos) + chunk_x := int(math.floor(f32(pos.x) / f32(CHUNK_SIZE))) + chunk_y := int(math.floor(f32(pos.y) / f32(CHUNK_SIZE))) + chunk_pos := Vec2i{chunk_x, chunk_y} + + local_x := (pos.x % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE + local_y := (pos.y % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE + local_pos := Vec2i{local_x, local_y} chunk := get_chunk(w, chunk_pos) - return get_chunk_tile(chunk, local_pos) + + // chunk_pos := world_pos_to_chunk_pos(vec2i_to_vec2(pos)) + // local_pos := get_local_chunk_pos(pos) + // + // chunk := get_chunk(w, chunk_pos) + + // return get_chunk_tile(chunk, local_pos) } get_chunk_tile :: proc(c:^Chunk, pos:Vec2i) -> ^Tile {