Files
2026-06-15 01:23:12 -07:00

175 lines
3.7 KiB
Odin

package game
import rl "vendor:raylib"
Tower :: struct {
position: Vec2,
kind: Tower_Kind,
cooldown: f32,
anchor_gx: int,
anchor_gy: int,
}
Tower_Kind :: enum {
Archer,
}
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
}
TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = {
.Archer = {
kind = .Archer,
range = 50,
damage = 12,
fire_rate = 0.45,
cost = TOWER_COST,
footprint_w = 1,
footprint_h = 2,
},
}
rects_overlap :: proc(
left_a, top_a, width_a, height_a: int,
left_b, top_b, width_b, height_b: int,
) -> bool {
return(
left_a < left_b + width_b &&
left_a + width_a > left_b &&
top_a < top_b + height_b &&
top_a + height_a > top_b \
)
}
footprint_center :: proc(gx, gy, footprint_w, footprint_h: int) -> Vec2 {
return Vec2 {
f32(gx * TILE_SIZE + (footprint_w * TILE_SIZE) / 2),
f32(gy * TILE_SIZE + (footprint_h * TILE_SIZE) / 2),
}
}
footprint_fits_map :: proc(world_map: ^Map, gx, gy, footprint_w, footprint_h: int) -> bool {
if gx < 0 || gy < 0 do return false
if gx + footprint_w > MAP_W do return false
if gy + footprint_h > MAP_H do return false
for row in gy ..< gy + footprint_h {
for column in gx ..< gx + footprint_w {
if !can_build_at(world_map, column, row) do return false
}
}
return true
}
footprint_blocked :: proc(world: ^World, gx, gy, footprint_w, footprint_h: int) -> bool {
for placed_tower in world.towers {
archetype := get_archetype(placed_tower.kind)
if rects_overlap(
gx,
gy,
footprint_w,
footprint_h,
placed_tower.anchor_gx,
placed_tower.anchor_gy,
archetype.footprint_w,
archetype.footprint_h,
) {
return true
}
}
return false
}
get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype {
return TOWER_ARCHETYPES[kind]
}
try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool {
if world.phase != .Build do return false
arch := get_archetype(kind)
footprint_w := arch.footprint_w
footprint_h := arch.footprint_h
if !footprint_fits_map(&world.world_map, gx, gy, footprint_w, footprint_h) do return false
if footprint_blocked(world, gx, gy, footprint_w, footprint_h) do return false
if world.gold < arch.cost do return false
world.gold -= arch.cost
append(
&world.towers,
Tower {
position = footprint_center(gx, gy, footprint_w, footprint_h),
anchor_gx = gx,
anchor_gy = gy,
kind = kind,
cooldown = 0,
},
)
return true
}
find_target_slot :: proc(tower: Tower, world: ^World) -> int {
arch := get_archetype(tower.kind)
best_slot: int = -1
best_dist: f32 = 999999
for i in 0 ..< MAX_ENEMIES {
e := &world.enemies[i]
if !e.active do continue
if e.path_index >= len(world.path) do continue
d := distance(tower.position, e.position)
if d <= arch.range && d < best_dist {
best_slot = i
best_dist = d
}
}
return best_slot
}
update_towers :: proc(world: ^World, dt: f32) {
for i in 0 ..< len(world.towers) {
t := &world.towers[i]
t.cooldown -= dt
if t.cooldown > 0 do continue
slot := find_target_slot(t^, world)
if slot < 0 do continue
arch := get_archetype(t.kind)
switch t.kind {
case .Archer:
spawn_projectile(world, t.position, slot, arch.damage)
t.cooldown = arch.fire_rate
}
}
}
render_towers :: proc(world: ^World) {
for t in world.towers {
archetype := get_archetype(t.kind)
pixel_width := f32(archetype.footprint_w * TILE_SIZE)
pixel_height := f32(archetype.footprint_h * TILE_SIZE)
rl.DrawRectangle(
i32(t.position.x - pixel_width * 0.5),
i32(t.position.y - pixel_height * 0.5),
i32(pixel_width),
i32(pixel_height),
rl.Color{255, 203, 0, 255},
)
}
}