Files

158 lines
3.5 KiB
Odin

package game
import rl "vendor:raylib"
Projectile :: struct {
active: bool,
position: Vec2,
target_slot: int,
damage: int,
speed: f32,
mode: Projectile_Mode,
direction: Vec2,
splash_radius: f32,
}
Projectile_Mode :: enum {
Homing, // chases target
Ballistic, // flies in a fixed direction
}
acquire_projectile :: proc(world: ^World) -> ^Projectile {
for i in 0 ..< MAX_PROJECTILES {
if !world.projectiles[i].active {
return &world.projectiles[i]
}
}
return nil
}
enemy_still_on_map :: proc(world: ^World, slot: int) -> (^Enemy, bool) {
if slot < 0 || slot >= MAX_ENEMIES do return nil, false
e := &world.enemies[slot]
if !e.active 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
}
spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: int) {
p := acquire_projectile(world)
if p == nil do return
p.mode = .Homing
p^ = Projectile {
active = true,
position = from,
target_slot = target_slot,
damage = damage,
speed = PROJECTILE_SPEED,
splash_radius = 0,
}
}
spawn_ballistic :: proc(world: ^World, from, dir: Vec2, damage: int, splash: f32) {
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,
splash_radius = splash,
}
}
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
switch p.mode {
case .Homing:
update_homing_projectile(world, p, dt)
case .Ballistic:
update_ballistic_projectile(world, p, 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
}
move_toward(&p.position, target.position, p.speed, dt)
if distance(p.position, target.position) <= PROJECTILE_HIT_RADIUS {
p.active = false
apply_tower_hit(world, target, p.damage)
}
}
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 apply_splash_damage(world, p.position, p.damage, p.splash_radius) {
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
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)
}
}
apply_splash_damage :: proc(world: ^World, center: Vec2, damage: int, radius: f32) -> bool {
if radius <= 0 do return hit_enemy_at(world, center, damage)
hit_any := false
for i in 0 ..< MAX_ENEMIES {
e := &world.enemies[i]
if !e.active do continue
if distance(center, e.position) <= radius {
apply_tower_hit(world, e, damage)
hit_any = true
}
}
return hit_any
}