package game import "core:fmt" import "core:sys/posix" import rl "vendor:raylib" Enemy :: struct { position: Vec2, health: int, max_health: int, active: bool, path_index: int, base_speed: f32, slow_timer: f32, kind: Enemy_Kind, } Enemy_Kind :: enum { Grunt, Runner, Tank, } Enemy_Archetype :: struct { kind: Enemy_Kind, health: int, speed: f32, color: rl.Color, } get_enemy_archetype :: proc(kind: Enemy_Kind, wave: int) -> Enemy_Archetype { switch kind { case .Grunt: return { kind = .Grunt, health = 15 + wave * 5, speed = 55 + f32(wave) * 5, color = rl.MAROON, } case .Runner: return { kind = .Runner, health = 10 + wave * 2, speed = 95 + f32(wave) * 3, color = rl.ORANGE, } case .Tank: return { kind = .Tank, health = 50 + wave * 12, speed = 35 + f32(wave) * 2, color = rl.DARKGREEN, } } return {} } acquire_enemy :: proc(world: ^World) -> ^Enemy { // create an object pool with fixed slots to reuse instead of allocate/free every spawn for i in 0 ..< MAX_ENEMIES { if !world.combat.enemies[i].active { return &world.combat.enemies[i] } } return nil } spawn_enemy :: proc(world: ^World, kind: Enemy_Kind) { if len(world.world_map.path) == 0 do return // don't spawn enemies if no paths exist e := acquire_enemy(world) // grab a free slot if e == nil do return // if there are no slots, skil spawning arch := get_enemy_archetype(kind, world.wave.current_wave) e^ = Enemy { position = world.world_map.path[0], health = arch.health, max_health = arch.health, base_speed = arch.speed, slow_timer = 0, path_index = 1, kind = kind, active = true, } } update_enemies :: proc(world: ^World, dt: f32) { if len(world.world_map.path) < 2 do return for i in 0 ..< MAX_ENEMIES { e := &world.combat.enemies[i] if !e.active do continue if e.path_index >= len(world.world_map.path) { world.player.base_health -= 1 e.active = false continue } target := world.world_map.path[e.path_index] move_speed := e.base_speed if e.slow_timer > 0 { move_speed *= ICE_SLOW_FACTOR e.slow_timer -= dt } move_toward(&e.position, target, move_speed, dt) dx := target.x - e.position.x dy := target.y - e.position.y if dx * dx + dy * dy < 16.0 { e.path_index += 1 } } } active_enemy_count :: proc(world: ^World) -> int { count := 0 for i in 0 ..< MAX_ENEMIES { if world.combat.enemies[i].active do count += 1 } return count } render_enemies :: proc(world: ^World) { for i in 0 ..< MAX_ENEMIES { e := world.combat.enemies[i] if !e.active do continue color := rl.MAROON size := 11 switch e.kind { case .Grunt: color = rl.MAROON case .Runner: color = rl.ORANGE case .Tank: color = rl.DARKGREEN size = 14 } rl.DrawCircle(i32(e.position.x), i32(e.position.y), f32(size), color) if e.slow_timer > 0 { rl.DrawCircleLines(i32(e.position.x), i32(e.position.y), 14, rl.SKYBLUE) } rl.DrawText( fmt.ctprintf("%d", e.health), i32(e.position.x) - 4, i32(e.position.y) - 18, 12, rl.WHITE, ) } }