diff --git a/game/commands.odin b/game/commands.odin index e906498..18431fc 100644 --- a/game/commands.odin +++ b/game/commands.odin @@ -6,6 +6,7 @@ Command_Kind :: enum { None, Place_Tower, Start_Wave, + Upgrade_Tower, } Command :: struct { @@ -25,6 +26,12 @@ gather_commands :: proc(world: ^World) -> Command { return Command{kind = .Start_Wave} } + if rl.IsMouseButtonPressed(.RIGHT) { + mouse := game_mouse() + gx, gy := screen_to_grid(mouse.x, mouse.y) + return Command{kind = .Upgrade_Tower, gx = gx, gy = gy} + } + if world.phase == .Build && rl.IsMouseButtonPressed(.LEFT) { mouse := game_mouse() gx, gy := screen_to_grid(mouse.x, mouse.y) @@ -41,6 +48,8 @@ execute_command :: proc(world: ^World, cmd: Command) { try_place_tower(world, cmd.gx, cmd.gy, world.selected_tower) case .Start_Wave: start_wave(world) + case .Upgrade_Tower: + try_upgrade_tower(world, cmd.gx, cmd.gy) } } diff --git a/game/tower.odin b/game/tower.odin index 5b86c2f..6576617 100644 --- a/game/tower.odin +++ b/game/tower.odin @@ -1,13 +1,15 @@ package game +import "core:fmt" import rl "vendor:raylib" Tower :: struct { - position: Vec2, - kind: Tower_Kind, - cooldown: f32, - anchor_gx: int, - anchor_gy: int, + upgrade_level: int, + position: Vec2, + kind: Tower_Kind, + cooldown: f32, + anchor_gx: int, + anchor_gy: int, } Tower_Kind :: enum { @@ -17,13 +19,17 @@ Tower_Kind :: enum { } Tower_Archetype :: struct { - kind: Tower_Kind, - range: f32, - damage: int, - fire_rate: f32, - cost: int, - footprint_w: int, // 1 column wide - footprint_h: int, // two rows tall + kind: Tower_Kind, + range: f32, + damage: int, + fire_rate: f32, + cost: int, + footprint_w: int, // 1 column wide + footprint_h: int, // two rows tall + max_upgrade: int, + upgrade_cost: int, + damage_per_level: int, + range_per_level: f32, } TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = { @@ -35,6 +41,10 @@ TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = { cost = TOWER_COST, footprint_w = 1, footprint_h = 2, + max_upgrade = 1, + upgrade_cost = 40, + damage_per_level = 3, + range_per_level = 8, }, .Cannon = { kind = .Cannon, @@ -44,6 +54,9 @@ TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = { cost = 80, footprint_w = 2, footprint_h = 2, + upgrade_cost = 40, + damage_per_level = 3, + range_per_level = 8, }, .Ice = { kind = .Ice, @@ -53,9 +66,19 @@ TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = { cost = 65, footprint_w = 1, footprint_h = 1, + upgrade_cost = 40, + damage_per_level = 3, + range_per_level = 8, }, } +tower_stats_at_level :: proc(tower: Tower) -> (damage: int, range: f32) { + arch := get_archetype(tower.kind) + damage = arch.damage + tower.upgrade_level * arch.damage_per_level + range = arch.range + f32(tower.upgrade_level) * arch.range_per_level + return +} + can_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { if world.phase != .Build do return false arch := get_archetype(kind) @@ -124,6 +147,22 @@ get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype { return TOWER_ARCHETYPES[kind] } +try_upgrade_tower :: proc(world: ^World, gx, gy: int) -> bool { + if world.phase != .Build do return false + t := tower_at_grid(world, gx, gy) + if t == nil do return false + + arch := get_archetype(t.kind) + if t.upgrade_level >= arch.max_upgrade do return false + + cost := arch.upgrade_cost + t.upgrade_level * 25 + if world.gold < cost do return false + + world.gold -= cost + t.upgrade_level += 1 + return true +} + try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { if !can_place_tower(world, gx, gy, kind) do return false @@ -142,8 +181,21 @@ try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { return true } +tower_at_grid :: proc(world: ^World, gx, gy: int) -> ^Tower { + 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 && + gy >= t.anchor_gy && + gy < t.anchor_gy + arch.footprint_h { + return t + } + } + return nil +} + find_target_slot :: proc(tower: Tower, world: ^World) -> int { - arch := get_archetype(tower.kind) best_slot: int = -1 best_dist: f32 = 999999 @@ -153,7 +205,8 @@ find_target_slot :: proc(tower: Tower, world: ^World) -> int { if e.path_index >= len(world.path) do continue d := distance(tower.position, e.position) - if d <= arch.range && d < best_dist { + _, range := tower_stats_at_level(tower) + if d <= range && d < best_dist { best_slot = i best_dist = d } @@ -173,16 +226,17 @@ update_towers :: proc(world: ^World, dt: f32) { arch := get_archetype(t.kind) + damage, _ := tower_stats_at_level(t^) switch t.kind { case .Archer: - spawn_projectile(world, t.position, slot, arch.damage) + spawn_projectile(world, t.position, slot, damage) case .Cannon: if target := enemy_at_slot(world, slot); target != nil { - apply_tower_hit(world, target, arch.damage) + apply_tower_hit(world, target, damage) } case .Ice: if target := enemy_at_slot(world, slot); target != nil { - apply_ice_hit(world, target, arch.damage) + apply_ice_hit(world, target, damage) } } t.cooldown = arch.fire_rate @@ -224,6 +278,10 @@ render_towers :: proc(world: ^World) { i32(pixel_height), color, ) + if t.upgrade_level > 0 { + label := fmt.ctprintf("Lv%d", t.upgrade_level + 1) // or just t.upgrade_level + rl.DrawText(label, i32(t.position.x), i32(t.position.y), 14, rl.WHITE) + } } }