diff --git a/game/constants.odin b/game/constants.odin index 2cdaa03..72e378e 100644 --- a/game/constants.odin +++ b/game/constants.odin @@ -21,6 +21,7 @@ TOWER_COST :: 50 MAX_PROJECTILES :: 64 PROJECTILE_SPEED :: 280 // pixels per second PROJECTILE_HIT_RADIUS :: 10 +CANNON_PROJECTILES_SPEED :: 320.0 OVERLAY_BAR_H :: 28 OVERLAY_PADDING :: 6 diff --git a/game/projectile.odin b/game/projectile.odin index c1617ea..dbe134e 100644 --- a/game/projectile.odin +++ b/game/projectile.odin @@ -8,6 +8,13 @@ Projectile :: struct { target_slot: int, damage: int, speed: f32, + mode: Projectile_Mode, + direction: Vec2, +} + +Projectile_Mode :: enum { + Homing, // chases target + Ballistic, // flies in a fixed direction } acquire_projectile :: proc(world: ^World) -> ^Projectile { @@ -33,6 +40,7 @@ spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: in p := acquire_projectile(world) if p == nil do return + p.mode = .Homing p^ = Projectile { active = true, position = from, @@ -42,36 +50,94 @@ spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: in } } +spawn_ballistic :: proc(world: ^World, from, dir: Vec2, damage: int) { + p := acquire_projectile(world) + if p == nil do return + p^ = Projectile { + active = true, + position = from, + direction = dir, + mode = .Ballistic, + damage = damage, + speed = CANNON_PROJECTILES_SPEED, + } +} + +hit_enemy_at :: proc(world: ^World, pos: Vec2, damage: int) -> bool { + for i in 0 ..< MAX_ENEMIES { + e := &world.enemies[i] + if !e.active do continue + if distance(pos, e.position) <= PROJECTILE_HIT_RADIUS { + apply_tower_hit(world, e, damage) + return true + } + } + return false +} + update_projectiles :: proc(world: ^World, dt: f32) { for i in 0 ..< MAX_PROJECTILES { p := &world.projectiles[i] if !p.active do continue - target, ok := enemy_still_on_map(world, p.target_slot) - if !ok { - p.active = false - continue + switch p.mode { + case .Homing: + update_homing_projectile(world, p, dt) + case .Ballistic: + update_ballistic_projectile(world, p, dt) } + } +} - move_toward(&p.position, target.position, p.speed, dt) +update_homing_projectile :: proc(world: ^World, p: ^Projectile, dt: f32) { + target, ok := enemy_still_on_map(world, p.target_slot) + if !ok { + p.active = false + return + } - if distance(p.position, target.position) <= PROJECTILE_HIT_RADIUS { - target.health -= p.damage - p.active = false + move_toward(&p.position, target.position, p.speed, dt) - if target.health <= 0 { - target.active = false - push_event(world, .Enemy_Killed, gold_reward = 5) - } + if distance(p.position, target.position) <= PROJECTILE_HIT_RADIUS { + target.health -= p.damage + p.active = false + + if target.health <= 0 { + target.active = false + push_event(world, .Enemy_Killed, gold_reward = 5) } } } +update_ballistic_projectile :: proc(world: ^World, p: ^Projectile, dt: f32) { + step := p.speed * dt + p.position.x += p.direction.x * step + p.position.y += p.direction.y * step + + if hit_enemy_at(world, p.position, p.damage) { + p.active = false + return + } + + if p.position.x < 0 || + p.position.y < 0 || + p.position.x > MAP_W * TILE_SIZE || + p.position.y > MAP_H * TILE_SIZE { + p.active = false + } +} + render_projectiles :: proc(world: ^World) { for i in 0 ..< MAX_PROJECTILES { p := world.projectiles[i] if !p.active do continue - rl.DrawCircle(i32(p.position.x), i32(p.position.y), 4, rl.GOLD) + color := rl.GOLD + radius: f32 = 4 + if p.mode == .Ballistic { + color = rl.GRAY + radius = 6 + } + rl.DrawCircle(i32(p.position.x), i32(p.position.y), radius, color) } } diff --git a/game/tower.odin b/game/tower.odin index 6576617..e7fdcde 100644 --- a/game/tower.odin +++ b/game/tower.odin @@ -232,7 +232,14 @@ update_towers :: proc(world: ^World, dt: f32) { spawn_projectile(world, t.position, slot, damage) case .Cannon: if target := enemy_at_slot(world, slot); target != nil { - apply_tower_hit(world, target, damage) + len := distance(t.position, target.position) + if (len > 0.001) { + dir := Vec2 { + (target.position.x - t.position.x) / len, + (target.position.y - t.position.y) / len, + } + spawn_ballistic(world, t.position, dir, damage) + } } case .Ice: if target := enemy_at_slot(world, slot); target != nil {