diff --git a/game/commands.odin b/game/commands.odin index 7cd70fe..9869ce3 100644 --- a/game/commands.odin +++ b/game/commands.odin @@ -5,7 +5,7 @@ import rl "vendor:raylib" Command_Kind :: enum { None, Place_Tower, - Spawn_Test, + Start_Wave, } Command :: struct { @@ -15,14 +15,20 @@ Command :: struct { } gather_commands :: proc(world: ^World) -> Command { - if rl.IsMouseButtonPressed(.LEFT) { + if world.phase == .Build && rl.IsKeyPressed(.N) { + return Command{kind = .Start_Wave} + } + + if world.phase == .Build && rl.IsMouseButtonPressed(.LEFT) { mouse := rl.GetMousePosition() gx, gy := screen_to_grid(mouse.x, mouse.y) return Command{kind = .Place_Tower, gx = gx, gy = gy} } - if rl.IsKeyPressed(.SPACE) { - return Command{kind = .Spawn_Test} + if rl.IsMouseButtonPressed(.LEFT) { + mouse := rl.GetMousePosition() + gx, gy := screen_to_grid(mouse.x, mouse.y) + return Command{kind = .Place_Tower, gx = gx, gy = gy} } return Command{kind = .None} @@ -31,10 +37,10 @@ gather_commands :: proc(world: ^World) -> Command { execute_command :: proc(world: ^World, cmd: Command) { switch cmd.kind { case .None: - case .Spawn_Test: - spawn_group(world, 3) case .Place_Tower: try_place_tower(world, cmd.gx, cmd.gy, .Archer) + case .Start_Wave: + start_wave(world) } } diff --git a/game/constants.odin b/game/constants.odin index 2a85e20..73dfcf8 100644 --- a/game/constants.odin +++ b/game/constants.odin @@ -1,7 +1,7 @@ package game -MAP_W :: 16 // map width in tiles -MAP_H :: 12 // map height in tiles +MAP_W :: 30 // map width in tiles +MAP_H :: 20 // map height in tiles TILE_SIZE :: 32 // size of tiles in pixels SCREEN_W :: MAP_W * TILE_SIZE diff --git a/game/events.odin b/game/events.odin index c5f4481..b71b397 100644 --- a/game/events.odin +++ b/game/events.odin @@ -2,6 +2,7 @@ package game Event_Kind :: enum { Enemy_Killed, + Wave_Survived, } Event :: struct { @@ -18,6 +19,8 @@ process_events :: proc(world: ^World) { switch ev.kind { case .Enemy_Killed: world.gold += ev.gold_reward + case .Wave_Survived: + world.gold += ev.gold_reward } } clear(&world.events) diff --git a/game/map.odin b/game/map.odin index ed80429..969aba0 100644 --- a/game/map.odin +++ b/game/map.odin @@ -31,16 +31,16 @@ init_map :: proc(world_map: ^Map) { } } - for x in 1 ..< 13 { - world_map.tiles[6][x] = .Path + for x in 9 ..< 21 { + world_map.tiles[10][x] = .Path } - for y in 7 ..< 8 { - world_map.tiles[y][12] = .Path + for y in 11 ..< 14 { + world_map.tiles[y][20] = .Path } - for y in 4 ..< 9 { - for x in 3 ..< 12 { + for y in 7 ..< 15 { + for x in 6 ..< 24 { if world_map.tiles[y][x] != .Path { world_map.tiles[y][x] = .Build } diff --git a/game/render.odin b/game/render.odin index 0a11d2a..90d36c6 100644 --- a/game/render.odin +++ b/game/render.odin @@ -35,8 +35,17 @@ render_world :: proc(world: ^World) { if (world.base_health == 0) { rl.DrawText("**GAME OVER**", 20, 20, 20, rl.RED) } - rl.DrawText(fmt.ctprintf("Gold: %d", world.gold), 10, 10, 20, rl.BLACK) // Gold HUD - rl.DrawText(fmt.ctprintf("Base HP: %d", world.base_health), 10, 32, 20, rl.BLACK) // HP HUD - rl.DrawText(fmt.ctprintf("Enemies: %d", active_enemy_count(world)), 10, 54, 18, rl.DARKGRAY) + + rl.DrawText(fmt.ctprintf("Gold: %d", world.gold), 10, 10, 22, rl.BLACK) // Gold HUD + rl.DrawText(fmt.ctprintf("Base HP: %d", world.base_health), 10, 32, 22, rl.BLACK) // HP HUD + rl.DrawText(fmt.ctprintf("Enemies: %d", active_enemy_count(world)), 10, 54, 22, rl.BLACK) + rl.DrawText(fmt.ctprintf("Wave: %d / %d", world.wave, MAX_WAVES), 10, 76, 22, rl.BLACK) + + switch world.phase { + case .Build: + rl.DrawText("Build - click towers, N = start wave", 10, 98, 22, rl.BLACK) + case .Combat: + rl.DrawText("Combat!", 10, 74, 22, rl.MAROON) + } } diff --git a/game/tower.odin b/game/tower.odin index 02ecc1d..701ae47 100644 --- a/game/tower.odin +++ b/game/tower.odin @@ -19,7 +19,7 @@ Tower_Archetype :: struct { } TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = { - .Archer = {kind = .Archer, range = 95, damage = 12, fire_rate = 0.45, cost = TOWER_COST}, + .Archer = {kind = .Archer, range = 20, damage = 12, fire_rate = 0.45, cost = TOWER_COST}, } get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype { @@ -27,6 +27,7 @@ get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype { } try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { + if world.phase != .Build do return false arch := get_archetype(kind) if !can_build_at(&world.world_map, gx, gy) do return false if world.gold < TOWER_COST do return false diff --git a/game/wave.odin b/game/wave.odin new file mode 100644 index 0000000..a929bc3 --- /dev/null +++ b/game/wave.odin @@ -0,0 +1,51 @@ +package game + +Game_Phase :: enum { + Build, + Combat, +} + +Wave_Spawner :: struct { + enemies_to_spawn: int, + spawn_timer: f32, + spawn_interval: f32, +} + +start_wave :: proc(world: ^World) { + if world.phase != .Build do return + world.wave += 1 + world.phase = .Combat + world.spawner = Wave_Spawner { + enemies_to_spawn = 5 + world.wave * 2, + spawn_timer = 0, + spawn_interval = 0.55, + } +} + +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 + + world.spawner.spawn_timer -= dt + if world.spawner.spawn_timer <= 0 { + health := 15 + world.wave * 5 + speed := 55 + f32(world.wave) * 5 + spawn_enemy(world, health, speed) + world.spawner.enemies_to_spawn -= 1 + world.spawner.spawn_timer = world.spawner.spawn_interval + } +} + +wave_clear :: proc(world: ^World) -> bool { + if world.spawner.enemies_to_spawn > 0 do return false + return active_enemy_count(world) == 0 +} + +update_phase :: proc(world: ^World) { + if world.phase == .Combat && wave_clear(world) { + world.phase = .Build + push_event(world, .Wave_Survived, gold_reward = 25) + } +} + diff --git a/game/world.odin b/game/world.odin index 9d2b5e8..6339cac 100644 --- a/game/world.odin +++ b/game/world.odin @@ -10,12 +10,16 @@ World :: struct { enemies: [MAX_ENEMIES]Enemy, towers: [dynamic]Tower, events: [dynamic]Event, + phase: Game_Phase, + wave: int, + spawner: Wave_Spawner, } init_world :: proc() -> World { world := World { gold = 100, base_health = 20, + phase = .Build, } init_map(&world.world_map) build_path(&world.world_map, &world.path) @@ -27,8 +31,10 @@ update_world :: proc(world: ^World, dt: f32) { cmd := gather_commands(world) execute_command(world, cmd) + update_wave(world, dt) update_enemies(world, dt) update_towers(world, dt) + update_phase(world) process_events(world) }