From d6ee5abb01b87912d96af30beb8be7a9815ec478 Mon Sep 17 00:00:00 2001 From: codegirl-007 Date: Wed, 24 Jun 2026 20:28:02 -0700 Subject: [PATCH] wave recipes + tanks --- game/enemy.odin | 15 +++++++- game/wave.odin | 94 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/game/enemy.odin b/game/enemy.odin index 5a7a77d..b3ce025 100644 --- a/game/enemy.odin +++ b/game/enemy.odin @@ -18,6 +18,7 @@ Enemy :: struct { Enemy_Kind :: enum { Grunt, Runner, + Tank, } Enemy_Archetype :: struct { @@ -43,10 +44,18 @@ get_enemy_archetype :: proc(kind: Enemy_Kind, wave: int) -> Enemy_Archetype { speed = 95 + f32(wave) * 3, color = rl.ORANGE, } + case .Tank: + return { + kind = .Tank, + health = 50 + wave * 12, + speed = 35 + f32(wave) * 2, + color = rl.DARKGREEN, + } } return {} } + 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 { @@ -120,13 +129,17 @@ render_enemies :: proc(world: ^World) { e := world.enemies[i] if !e.active do continue color := rl.MAROON + size := 11 switch e.kind { case .Grunt: color = rl.MAROON case .Runner: color = rl.ORANGE + case .Tank: + color = rl.DARKGREEN + size = 14 } - rl.DrawCircle(i32(e.position.x), i32(e.position.y), 11, color) + rl.DrawCircle(i32(e.position.x), i32(e.position.y), f32(size), color) if e.slow_timer > 0 { rl.DrawCircleLines(i32(e.position.x), i32(e.position.y), 14, rl.SKYBLUE) } diff --git a/game/wave.odin b/game/wave.odin index 1536534..3eed1dd 100644 --- a/game/wave.odin +++ b/game/wave.odin @@ -8,10 +8,11 @@ Game_Phase :: enum { } Wave_Spawner :: struct { - enemies_to_spawn: int, - spawn_timer: f32, - spawn_interval: f32, - spawn_count: int, + spawn_timer: f32, + spawn_interval: f32, + recipe: [dynamic]Wave_Entry, + recipe_index: int, + entry_remaining: int, } start_wave :: proc(world: ^World) { @@ -19,37 +20,80 @@ start_wave :: proc(world: ^World) { world.wave += 1 world.phase = .Combat world.spawner = Wave_Spawner { - enemies_to_spawn = 5 + world.wave * 2, - spawn_timer = 0, - spawn_interval = 0.55, + spawn_timer = 0, + spawn_interval = 0.55, + recipe = build_wave_recipe(world.wave), + recipe_index = 0, + entry_remaining = 0, } + load_next_recipe_entry(world) +} + +Wave_Entry :: struct { + kind: Enemy_Kind, + count: int, +} + +build_wave_recipe :: proc(wave: int) -> [dynamic]Wave_Entry { + recipe: [dynamic]Wave_Entry + switch wave { + case 1: + append(&recipe, Wave_Entry{.Grunt, 6}) + case 2: + append(&recipe, Wave_Entry{.Grunt, 4}) + append(&recipe, Wave_Entry{.Runner, 2}) + case: + grunt := 3 + wave + runners := wave / 2 + tanks := wave / 3 + append(&recipe, Wave_Entry{.Grunt, grunt}) + if runners > 0 do append(&recipe, Wave_Entry{.Runner, runners}) + if tanks > 0 do append(&recipe, Wave_Entry{.Tank, tanks}) + } + + return recipe +} + +load_next_recipe_entry :: proc(world: ^World) { + s := &world.spawner + if s.recipe_index >= len(s.recipe) { + s.entry_remaining = 0 + return + } + entry := s.recipe[s.recipe_index] + s.entry_remaining = entry.count +} + +clear_wave :: proc(world: ^World) -> bool { + 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 } update_wave :: proc(world: ^World, dt: f32) { if world.phase != .Combat do return - if world.spawner.enemies_to_spawn <= 0 do return - if len(world.path) < 2 do return + s := &world.spawner + if s.entry_remaining <= 0 && s.recipe_index >= len(s.recipe) do return - world.spawner.spawn_timer -= dt - kind := Enemy_Kind.Grunt - if world.spawner.spawn_count % 3 == 2 do kind = .Runner - if world.spawner.spawn_timer <= 0 { - health := 15 + world.wave * 5 - speed := 55 + f32(world.wave) * 5 - spawn_enemy(world, kind) - world.spawner.spawn_count += 1 - world.spawner.enemies_to_spawn -= 1 - world.spawner.spawn_timer = world.spawner.spawn_interval + s.spawn_timer -= dt + if s.spawn_timer > 0 do return + + if s.entry_remaining <= 0 { + s.recipe_index += 1 + load_next_recipe_entry(world) + if s.entry_remaining <= 0 do return } -} -wave_clear :: proc(world: ^World) -> bool { - if world.spawner.enemies_to_spawn > 0 do return false - return active_enemy_count(world) == 0 + kind := s.recipe[s.recipe_index].kind + spawn_enemy(world, kind) + s.entry_remaining -= 1 + s.spawn_timer = s.spawn_interval + } update_phase :: proc(world: ^World) { - if world.phase == .Combat && wave_clear(world) { + if world.phase == .Combat && clear_wave(world) { world.phase = .Build push_event(world, .Wave_Survived, gold_reward = 25) } @@ -60,7 +104,7 @@ check_end :: proc(world: ^World) { world.phase = .Game_Over } - if world.wave >= MAX_WAVES && world.phase == .Build && wave_clear(world) { + if world.wave >= MAX_WAVES && world.phase == .Build && clear_wave(world) { world.phase = .Victory } }