156 lines
2.9 KiB
Odin
156 lines
2.9 KiB
Odin
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.enemies[i].active {
|
|
return &world.enemies[i]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
spawn_enemy :: proc(world: ^World, kind: Enemy_Kind) {
|
|
if len(world.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)
|
|
e^ = Enemy {
|
|
position = world.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.path) < 2 do return
|
|
|
|
for i in 0 ..< MAX_ENEMIES {
|
|
e := &world.enemies[i]
|
|
if !e.active do continue
|
|
|
|
if e.path_index >= len(world.path) {
|
|
world.base_health -= 1
|
|
e.active = false
|
|
continue
|
|
}
|
|
|
|
target := world.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.enemies[i].active do count += 1
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
render_enemies :: proc(world: ^World) {
|
|
for i in 0 ..< MAX_ENEMIES {
|
|
e := world.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,
|
|
)
|
|
}
|
|
}
|
|
|