package main import "core:fmt" import math "core:math" import hm "core:container/handle_map" import rl "vendor:raylib" SQRT2_INV :: 0.7071067811865476 LOG_SIZE :: 8 CollisionPair :: struct { a: Handle, b: Handle, } GameState :: struct { player: Handle, camera: rl.Camera3D, entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, Handle), collisions: [dynamic]CollisionPair, log: [LOG_SIZE]string, log_count: i32, kills: i32, } get_entity :: proc(state: ^GameState, h: Handle) -> ^Entity { e, ok := hm.get(&state.entities, h) return e if ok else nil } spawn :: proc(state: ^GameState, e: Entity) -> Handle { return hm.add(&state.entities, e) } add_log :: proc(state: ^GameState, format: string, args: ..any) { msg := fmt.tprintf(format, ..args) if state.log_count >= LOG_SIZE { for i in 0 ..< LOG_SIZE - 1 { state.log[i] = state.log[i + 1] } state.log[LOG_SIZE - 1] = msg } else { state.log[state.log_count] = msg state.log_count += 1 } } entity_rect :: #force_inline proc(b: ^BaseEntity) -> rl.Rectangle { return rl.Rectangle{b.pos.x - b.col_size / 2, b.pos.y - b.col_size / 2, b.col_size, b.col_size} } check_col :: proc(a, b: ^BaseEntity) -> (overlap: rl.Rectangle, colliding: bool) { c := rl.GetCollisionRec(entity_rect(a), entity_rect(b)) if c.width <= 0 || c.height <= 0 do return rl.Rectangle{}, false return c, true } resolve_aabb :: proc(a, b: ^BaseEntity) -> bool { c, ok := check_col(a, b) if !ok do return false if c.width < c.height { sign: f32 = 1 if a.pos.x < b.pos.x else -1 a.pos.x -= sign * c.width * 0.5 b.pos.x += sign * c.width * 0.5 } else { sign: f32 = 1 if a.pos.y < b.pos.y else -1 a.pos.y -= sign * c.height * 0.5 b.pos.y += sign * c.height * 0.5 } return true } sys_update :: proc(state: ^GameState, dt: f32) { it := hm.iterator_make(&state.entities) for e, h in hm.iterate(&it) { #partial switch &v in e.v { case Player: player_update(state, &v, h, dt) case Enemy: enemy_update(state, &v, h, dt) case GroundItem: item_update(state, &v, h, dt) } } it2 := hm.iterator_make(&state.entities) for e, _ in hm.iterate(&it2) { b := get_base(e) b.pos += b.vel * dt } it3 := hm.iterator_make(&state.entities) for e, h in hm.iterate(&it3) { #partial switch &v in e.v { case FX: fx_update(state, &v, h, dt) case Attack: attack_update(state, &v, h, dt) } } resolve_collisions(state) apply_collision_effects(state) } sys_sweep :: proc(state: ^GameState) { it := hm.iterator_make(&state.entities) for e, h in hm.iterate(&it) { if get_base(e).marked { hm.remove(&state.entities, h) } } } entity_draw :: proc(state: ^GameState, e: ^Entity) { b := get_base(e) sp := rl.GetWorldToScreen({b.pos.x, 0, b.pos.y}, state.camera) scale := SCREEN_H / state.camera.fovy #partial switch v in b.gfx[0] { case GfxCircle: rl.DrawCircleV(sp, v.radius * scale, v.color) case: } } ground_effect_draw :: proc(state: ^GameState, e: ^Entity) { b := get_base(e) #partial switch v in e.v { case Attack: draw_arc_sector(b.pos, v.dir, v.radius, v.arc, v.color) case GroundItem: rl.DrawCylinder({b.pos.x, 0.02, b.pos.y}, 8, 8, 0.03, 24, {255, 255, 255, 30}) case: } } draw_arc_sector :: proc(pos: rl.Vector2, dir: rl.Vector2, radius: f32, arc: f32, color: rl.Color) { SEGMENTS :: 24 base := math.atan2_f32(dir.y, dir.x) start := base - arc * 0.5 for i in 0 ..< SEGMENTS { a1 := start + arc * f32(i) / f32(SEGMENTS) a2 := start + arc * f32(i + 1) / f32(SEGMENTS) p1 := pos + rl.Vector2{math.cos_f32(a1), math.sin_f32(a1)} * radius p2 := pos + rl.Vector2{math.cos_f32(a2), math.sin_f32(a2)} * radius rl.DrawTriangle3D( {pos.x, 0.03, pos.y}, {p1.x, 0.03, p1.y}, {p2.x, 0.03, p2.y}, color, ) } } sys_draw :: proc(state: ^GameState) { rl.BeginDrawing() rl.ClearBackground({20, 20, 30, 255}) rl.BeginMode3D(state.camera) draw_grid_3d() if ray := rl.GetScreenToWorldRay(rl.GetMousePosition(), state.camera); ray.direction.y != 0 { t := -ray.position.y / ray.direction.y hit := ray.position + ray.direction * t rl.DrawCylinder({hit.x, 0.02, hit.z}, 4, 4, 0.02, 16, {255, 255, 255, 90}) } it := hm.iterator_make(&state.entities) for e, _ in hm.iterate(&it) { ground_effect_draw(state, e) } rl.EndMode3D() scale := SCREEN_H / state.camera.fovy it2 := hm.iterator_make(&state.entities) for e, _ in hm.iterate(&it2) { entity_draw(state, e) if a := get_actor(e); a != nil { b := get_base(e) sp := rl.GetWorldToScreen({b.pos.x, 0, b.pos.y}, state.camera) hp_ratio := f32(a.hp) / f32(a.max_hp) bar_w := b.col_size * 3 * scale bar_pos := sp - rl.Vector2{bar_w / 2, b.col_size * 2 * scale + 12} rl.DrawRectangleV(bar_pos, {bar_w, 5}, {60, 60, 60, 255}) rl.DrawRectangleV(bar_pos, {bar_w * hp_ratio, 5}, {220, 40, 40, 255}) } } ui_draw(state) rl.EndDrawing() } resolve_collisions :: proc(state: ^GameState) { clear(&state.collisions) hs: [MAX_ENTITIES]Handle n := 0 it := hm.iterator_make(&state.entities) for _, h in hm.iterate(&it) { hs[n] = h n += 1 } for i in 0 ..< n { for j in i + 1 ..< n { a := get_entity(state, hs[i]) b := get_entity(state, hs[j]) if resolve_aabb(get_base(a), get_base(b)) { append(&state.collisions, CollisionPair{a = hs[i], b = hs[j]}) } } } } apply_collision_effects :: proc(state: ^GameState) { _, p := get_player(state) for c in state.collisions { other_h: Handle if c.a == state.player { other_h = c.b } else if c.b == state.player { other_h = c.a } else do continue e := get_entity(state, other_h) if e == nil do continue #partial switch &v in e.v { case Enemy: if p != nil && p.invuln_timer <= 0 { dmg := v.touch_dmg armor: i32 = 0 if p.equip.armor.kind != .None do armor = p.equip.armor.armor dmg = max(dmg - armor / 2, 1) p.hp -= dmg p.invuln_timer = 0.5 if p.hp <= 0 do p.hp = 0 } case GroundItem: if p != nil && !v.marked && p.inv_count < INV_SIZE { p.inventory[p.inv_count] = v.item p.inv_count += 1 v.marked = true add_log(state, "Picked up: %s", v.item.name) } } } }