diff --git a/game/app.odin b/game/app.odin index c9bae8f..3fd3318 100644 --- a/game/app.odin +++ b/game/app.odin @@ -11,10 +11,10 @@ run :: proc() { world := init_world() defer { - delete(world.world_map.path) - delete(world.combat.towers) + delete(world.path) + delete(world.towers) delete(world.events) - delete(world.wave.spawner.recipe) + delete(world.spawner.recipe) } for !rl.WindowShouldClose() { diff --git a/game/enemy.odin b/game/enemy.odin index bac077b..ccceff4 100644 --- a/game/enemy.odin +++ b/game/enemy.odin @@ -59,8 +59,8 @@ get_enemy_archetype :: proc(kind: Enemy_Kind, wave: int) -> Enemy_Archetype { acquire_enemy :: proc(world: ^World) -> ^Enemy { // create an object pool with fixed slots to reuse instead of allocate/free every spawn for i in 0 ..< MAX_ENEMIES { - if !world.combat.enemies[i].active { - return &world.combat.enemies[i] + if !world.enemies[i].active { + return &world.enemies[i] } } @@ -68,14 +68,14 @@ acquire_enemy :: proc(world: ^World) -> ^Enemy { } spawn_enemy :: proc(world: ^World, kind: Enemy_Kind) { - if len(world.world_map.path) == 0 do return // don't spawn enemies if no paths exist + if len(world.path) == 0 do return // don't spawn enemies if no paths exist e := acquire_enemy(world) // grab a free slot if e == nil do return // if there are no slots, skil spawning - arch := get_enemy_archetype(kind, world.wave.current_wave) + arch := get_enemy_archetype(kind, world.wave) e^ = Enemy { - position = world.world_map.path[0], + position = world.path[0], health = arch.health, max_health = arch.health, base_speed = arch.speed, @@ -87,19 +87,19 @@ spawn_enemy :: proc(world: ^World, kind: Enemy_Kind) { } update_enemies :: proc(world: ^World, dt: f32) { - if len(world.world_map.path) < 2 do return + if len(world.path) < 2 do return for i in 0 ..< MAX_ENEMIES { - e := &world.combat.enemies[i] + e := &world.enemies[i] if !e.active do continue - if e.path_index >= len(world.world_map.path) { - world.player.base_health -= 1 + if e.path_index >= len(world.path) { + world.base_health -= 1 e.active = false continue } - target := world.world_map.path[e.path_index] + target := world.path[e.path_index] move_speed := e.base_speed if e.slow_timer > 0 { move_speed *= ICE_SLOW_FACTOR @@ -118,7 +118,7 @@ update_enemies :: proc(world: ^World, dt: f32) { active_enemy_count :: proc(world: ^World) -> int { count := 0 for i in 0 ..< MAX_ENEMIES { - if world.combat.enemies[i].active do count += 1 + if world.enemies[i].active do count += 1 } return count @@ -126,7 +126,7 @@ active_enemy_count :: proc(world: ^World) -> int { render_enemies :: proc(world: ^World) { for i in 0 ..< MAX_ENEMIES { - e := world.combat.enemies[i] + e := world.enemies[i] if !e.active do continue color := rl.MAROON size := 11 diff --git a/game/events.odin b/game/events.odin index 7d77917..b71b397 100644 --- a/game/events.odin +++ b/game/events.odin @@ -18,9 +18,9 @@ process_events :: proc(world: ^World) { for ev in world.events { switch ev.kind { case .Enemy_Killed: - world.economy.gold += ev.gold_reward + world.gold += ev.gold_reward case .Wave_Survived: - world.economy.gold += ev.gold_reward + world.gold += ev.gold_reward } } clear(&world.events) diff --git a/game/map.odin b/game/map.odin index ab34154..5a5375e 100644 --- a/game/map.odin +++ b/game/map.odin @@ -7,7 +7,7 @@ Tile_Kind :: enum { Build, } -World_Map :: struct { +Map :: struct { tiles: [MAP_H][MAP_W]Tile_Kind, } @@ -24,7 +24,7 @@ tile_color :: proc(kind: Tile_Kind) -> rl.Color { return rl.MAGENTA } -init_map :: proc(world_map: ^World_Map) { +init_map :: proc(world_map: ^Map) { for y in 0 ..< MAP_H { for x in 0 ..< MAP_W { world_map.tiles[y][x] = .Blocked @@ -48,16 +48,16 @@ init_map :: proc(world_map: ^World_Map) { } } -can_build_at :: proc(world_map: ^World_Map, gx, gy: int) -> bool { +can_build_at :: proc(world_map: ^Map, gx, gy: int) -> bool { if gx < 0 || gx >= MAP_W do return false if gy < 0 || gy >= MAP_H do return false return world_map.tiles[gy][gx] == .Build } -render_map :: proc(world_map: ^World_Map) { +render_map :: proc(world: ^World) { for y in 0 ..< MAP_H { for x in 0 ..< MAP_W { - c := tile_color(world_map.tiles[y][x]) + c := tile_color(world.world_map.tiles[y][x]) rl.DrawRectangle(i32(x * TILE_SIZE), i32(y * TILE_SIZE), TILE_SIZE, TILE_SIZE, c) } } diff --git a/game/overlay.odin b/game/overlay.odin index eba66db..c3bec01 100644 --- a/game/overlay.odin +++ b/game/overlay.odin @@ -12,11 +12,11 @@ render_overlay :: proc(world: ^World) { gap: i32 : 16 - gold := fmt.ctprintf("Gold: %d", world.economy.gold) + gold := fmt.ctprintf("Gold: %d", world.gold) rl.DrawText(gold, OVERLAY_PADDING, OVERLAY_PADDING, 18, OVERLAY_GOLD_COLOR) x += rl.MeasureText(gold, font_size) + gap - hp := fmt.ctprintf("Health: %d", world.player.base_health) + hp := fmt.ctprintf("Health: %d", world.base_health) rl.DrawText(hp, x, y, font_size, OVERLAY_TEXT_COLOR) x += rl.MeasureText(hp, font_size) + gap diff --git a/game/path.odin b/game/path.odin index 83e5ab1..0c83504 100644 --- a/game/path.odin +++ b/game/path.odin @@ -1,6 +1,6 @@ package game -build_path :: proc(world_map: ^World_Map, path: ^[dynamic]Vec2) { +build_path :: proc(world_map: ^Map, path: ^[dynamic]Vec2) { clear(path) for y in 0 ..< MAP_H { for x in 0 ..< MAP_W { diff --git a/game/projectile.odin b/game/projectile.odin index 68f8feb..1dd21a3 100644 --- a/game/projectile.odin +++ b/game/projectile.odin @@ -20,8 +20,8 @@ Projectile_Mode :: enum { acquire_projectile :: proc(world: ^World) -> ^Projectile { for i in 0 ..< MAX_PROJECTILES { - if !world.combat.projectiles[i].active { - return &world.combat.projectiles[i] + if !world.projectiles[i].active { + return &world.projectiles[i] } } @@ -30,10 +30,10 @@ acquire_projectile :: proc(world: ^World) -> ^Projectile { enemy_still_on_map :: proc(world: ^World, slot: int) -> (^Enemy, bool) { if slot < 0 || slot >= MAX_ENEMIES do return nil, false - e := &world.combat.enemies[slot] + e := &world.enemies[slot] if !e.active do return nil, false - if len(world.world_map.path) == 0 do return nil, false - if e.path_index >= len(world.world_map.path) do return nil, false + if len(world.path) == 0 do return nil, false + if e.path_index >= len(world.path) do return nil, false return e, true } @@ -68,7 +68,7 @@ spawn_ballistic :: proc(world: ^World, from, dir: Vec2, damage: int, splash: f32 hit_enemy_at :: proc(world: ^World, pos: Vec2, damage: int) -> bool { for i in 0 ..< MAX_ENEMIES { - e := &world.combat.enemies[i] + e := &world.enemies[i] if !e.active do continue if distance(pos, e.position) <= PROJECTILE_HIT_RADIUS { apply_tower_hit(world, e, damage) @@ -80,7 +80,7 @@ hit_enemy_at :: proc(world: ^World, pos: Vec2, damage: int) -> bool { update_projectiles :: proc(world: ^World, dt: f32) { for i in 0 ..< MAX_PROJECTILES { - p := &world.combat.projectiles[i] + p := &world.projectiles[i] if !p.active do continue switch p.mode { @@ -132,7 +132,7 @@ update_ballistic_projectile :: proc(world: ^World, p: ^Projectile, dt: f32) { render_projectiles :: proc(world: ^World) { for i in 0 ..< MAX_PROJECTILES { - p := world.combat.projectiles[i] + p := world.projectiles[i] if !p.active do continue color := rl.GOLD radius: f32 = 4 @@ -149,7 +149,7 @@ apply_splash_damage :: proc(world: ^World, center: Vec2, damage: int, radius: f3 hit_any := false for i in 0 ..< MAX_ENEMIES { - e := &world.combat.enemies[i] + e := &world.enemies[i] if !e.active do continue if distance(center, e.position) <= radius { apply_tower_hit(world, e, damage) diff --git a/game/render.odin b/game/render.odin index d6940f6..92c00e9 100644 --- a/game/render.odin +++ b/game/render.odin @@ -1,7 +1,7 @@ package game render_world :: proc(world: ^World) { - render_map(&world.world_map.current_map) + render_map(world) render_enemies(world) render_towers(world) render_placement_preview(world) diff --git a/game/tower.odin b/game/tower.odin index ef8c288..fea6c16 100644 --- a/game/tower.odin +++ b/game/tower.odin @@ -88,8 +88,8 @@ tower_stats_at_level :: proc(tower: Tower) -> (damage: int, range: f32) { can_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { if world.phase != .Build do return false arch := get_archetype(kind) - if world.economy.gold < arch.cost do return false - if !footprint_fits_map(&world.world_map.current_map, gx, gy, arch.footprint_w, arch.footprint_h) do return false + if world.gold < arch.cost do return false + if !footprint_fits_map(&world.world_map, gx, gy, arch.footprint_w, arch.footprint_h) do return false if footprint_blocked(world, gx, gy, arch.footprint_w, arch.footprint_h) do return false return true } @@ -113,7 +113,7 @@ footprint_center :: proc(gx, gy, footprint_w, footprint_h: int) -> Vec2 { } } -footprint_fits_map :: proc(world_map: ^World_Map, gx, gy, footprint_w, footprint_h: int) -> bool { +footprint_fits_map :: proc(world_map: ^Map, gx, gy, footprint_w, footprint_h: int) -> bool { if gx < 0 || gy < 0 do return false if gx + footprint_w > MAP_W do return false if gy + footprint_h > MAP_H do return false @@ -130,7 +130,7 @@ footprint_fits_map :: proc(world_map: ^World_Map, gx, gy, footprint_w, footprint } footprint_blocked :: proc(world: ^World, gx, gy, footprint_w, footprint_h: int) -> bool { - for placed_tower in world.combat.towers { + for placed_tower in world.towers { archetype := get_archetype(placed_tower.kind) if rects_overlap( gx, @@ -162,9 +162,9 @@ try_upgrade_tower :: proc(world: ^World, gx, gy: int) -> bool { if t.upgrade_level >= arch.max_upgrade do return false cost := arch.upgrade_cost + t.upgrade_level * 25 - if world.economy.gold < cost do return false + if world.gold < cost do return false - world.economy.gold -= cost + world.gold -= cost t.upgrade_level += 1 return true } @@ -173,9 +173,9 @@ try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { if !can_place_tower(world, gx, gy, kind) do return false arch := get_archetype(kind) - world.economy.gold -= arch.cost + world.gold -= arch.cost append( - &world.combat.towers, + &world.towers, Tower { position = footprint_center(gx, gy, arch.footprint_w, arch.footprint_h), anchor_gx = gx, @@ -188,8 +188,8 @@ try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { } tower_at_grid :: proc(world: ^World, gx, gy: int) -> ^Tower { - for i in 0 ..< len(world.combat.towers) { - t := &world.combat.towers[i] + for i in 0 ..< len(world.towers) { + t := &world.towers[i] arch := get_archetype(t.kind) if gx >= t.anchor_gx && gx < t.anchor_gx + arch.footprint_w && @@ -206,9 +206,9 @@ find_target_slot :: proc(tower: Tower, world: ^World) -> int { best_dist: f32 = 999999 for i in 0 ..< MAX_ENEMIES { - e := &world.combat.enemies[i] + e := &world.enemies[i] if !e.active do continue - if e.path_index >= len(world.world_map.path) do continue + if e.path_index >= len(world.path) do continue d := distance(tower.position, e.position) _, range := tower_stats_at_level(tower) @@ -221,8 +221,8 @@ find_target_slot :: proc(tower: Tower, world: ^World) -> int { } update_towers :: proc(world: ^World, dt: f32) { - for i in 0 ..< len(world.combat.towers) { - t := &world.combat.towers[i] + for i in 0 ..< len(world.towers) { + t := &world.towers[i] t.cooldown -= dt if t.cooldown > 0 do continue @@ -272,13 +272,13 @@ apply_ice_hit :: proc(world: ^World, target: ^Enemy, damage: int) { enemy_at_slot :: proc(world: ^World, slot: int) -> ^Enemy { if slot < 0 || slot >= MAX_ENEMIES do return nil - e := &world.combat.enemies[slot] + e := &world.enemies[slot] if !e.active do return nil return e } render_towers :: proc(world: ^World) { - for t in world.combat.towers { + for t in world.towers { archetype := get_archetype(t.kind) pixel_width := f32(archetype.footprint_w * TILE_SIZE) pixel_height := f32(archetype.footprint_h * TILE_SIZE) diff --git a/game/wave.odin b/game/wave.odin index a017695..eff29ef 100644 --- a/game/wave.odin +++ b/game/wave.odin @@ -17,13 +17,13 @@ Wave_Spawner :: struct { start_wave :: proc(world: ^World) { if world.phase != .Build do return - world.wave.current_wave += 1 + world.wave += 1 world.phase = .Combat - delete(world.wave.spawner.recipe) - world.wave.spawner = Wave_Spawner { + delete(world.spawner.recipe) + world.spawner = Wave_Spawner { spawn_timer = 0, spawn_interval = 0.55, - recipe = build_wave_recipe(world.wave.current_wave), + recipe = build_wave_recipe(world.wave), recipe_index = 0, entry_remaining = 0, } @@ -56,7 +56,7 @@ build_wave_recipe :: proc(wave: int) -> [dynamic]Wave_Entry { } load_next_recipe_entry :: proc(world: ^World) { - s := &world.wave.spawner + s := &world.spawner if s.recipe_index >= len(s.recipe) { s.entry_remaining = 0 return @@ -66,7 +66,7 @@ load_next_recipe_entry :: proc(world: ^World) { } clear_wave :: proc(world: ^World) -> bool { - s := world.wave.spawner + s := world.spawner if s.recipe_index < len(s.recipe) do return false if s.entry_remaining > 0 do return false return active_enemy_count(world) == 0 @@ -74,7 +74,7 @@ clear_wave :: proc(world: ^World) -> bool { update_wave :: proc(world: ^World, dt: f32) { if world.phase != .Combat do return - s := &world.wave.spawner + s := &world.spawner if s.entry_remaining <= 0 && s.recipe_index >= len(s.recipe) do return s.spawn_timer -= dt @@ -101,11 +101,11 @@ update_phase :: proc(world: ^World) { } check_end :: proc(world: ^World) { - if world.player.base_health <= 0 { + if world.base_health <= 0 { world.phase = .Game_Over } - if world.wave.current_wave >= MAX_WAVES && world.phase == .Build && clear_wave(world) { + if world.wave >= MAX_WAVES && world.phase == .Build && clear_wave(world) { world.phase = .Victory } } diff --git a/game/world.odin b/game/world.odin index c9670b8..fec5502 100644 --- a/game/world.odin +++ b/game/world.odin @@ -1,56 +1,29 @@ package game World :: struct { - economy: Economy, - combat: Combat, - player: Player, + gold: int, + base_health: int, world_map: Map, + path: [dynamic]Vec2, + enemies: [MAX_ENEMIES]Enemy, + towers: [dynamic]Tower, events: [dynamic]Event, phase: Game_Phase, - wave: Wave, + wave: int, + spawner: Wave_Spawner, + projectiles: [MAX_PROJECTILES]Projectile, selected_tower: Tower_Kind, } -Player :: struct { - base_health: int, -} - -Map :: struct { - current_map: World_Map, - path: [dynamic]Vec2, -} - -Economy :: struct { - gold: int, -} - -Combat :: struct { - enemies: [MAX_ENEMIES]Enemy, - towers: [dynamic]Tower, - projectiles: [MAX_PROJECTILES]Projectile, -} - -Wave :: struct { - current_wave: int, - spawner: Wave_Spawner, -} - init_world :: proc() -> World { - economy := Economy { - gold = 100, - } - - player := Player { - base_health = 20, - } world := World { - economy = economy, - player = player, + gold = 100, + base_health = 20, phase = .Build, selected_tower = .Archer, } - init_map(&world.world_map.current_map) - build_path(&world.world_map.current_map, &world.world_map.path) + init_map(&world.world_map) + build_path(&world.world_map, &world.path) return world }