package main import hm "core:container/handle_map" import rl "vendor:raylib" SQRT2_INV :: 0.7071067811865476 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, } 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) } 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 b.gfx[0] { case GfxRing: rl.DrawCylinder({b.pos.x, 0.02, b.pos.y}, v.outer_radius, v.outer_radius, 0.04, 48, v.color) case: } } sys_draw :: proc(state: ^GameState) { rl.BeginDrawing() rl.ClearBackground({20, 20, 30, 255}) rl.BeginMode3D(state.camera) draw_grid_3d() 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 { p.hp -= v.touch_dmg p.invuln_timer = 0.5 if p.hp <= 0 do p.hp = 0 } case GroundItem: if p != nil && !v.marked { p.hp = min(p.hp + v.heal, p.max_hp) v.marked = true } } } }