package main import rl "vendor:raylib" SKILL_COUNT :: 3 SkillKind :: enum { Dash, Spin, Heal, } Skill :: struct { kind: SkillKind, cd: f32, timer: f32, } Equipment :: struct { weapon: Item, armor: Item, amulet: Item, } Player :: struct { using actor: Actor, level: i32, xp: i32, xp_next: i32, base_dmg: i32, base_crit: f32, base_atk_cd: f32, base_max_hp: i32, equip: Equipment, inventory: [INV_SIZE]Item, inv_count: i32, selected_slot: i32, skills: [SKILL_COUNT]Skill, aim: rl.Vector2, attack_range: f32, attack_timer: f32, invuln_timer: f32, dash_timer: f32, } make_player :: proc() -> Entity { p := Player { level = 1, xp = 0, xp_next = 50, base_dmg = 10, base_crit = 0.1, base_atk_cd = 0.35, base_max_hp = 100, attack_range = 90, selected_slot = -1, skills = { 0 = Skill{kind = .Dash, cd = 2.0}, 1 = Skill{kind = .Spin, cd = 4.0}, 2 = Skill{kind = .Heal, cd = 6.0}, }, } p.base.pos = {0, 0} p.base.col_size = 16 p.base.gfx = {0 = GfxCircle{pos = {}, radius = 14, color = {80, 220, 100, 255}}} p.hp = p.base_max_hp p.max_hp = p.base_max_hp p.speed = 220 return Entity{v = p} } // ─── Итоговые статы из экипировки ─── player_total_dmg :: proc(p: ^Player) -> i32 { dmg := p.base_dmg if p.equip.weapon.kind != .None do dmg += p.equip.weapon.dmg return dmg } player_crit :: proc(p: ^Player) -> f32 { crit := p.base_crit if p.equip.weapon.kind != .None do crit += p.equip.weapon.crit if p.equip.amulet.kind != .None do crit += p.equip.amulet.crit return min(crit, 0.8) } player_atk_cd :: proc(p: ^Player) -> f32 { bonus: f32 = 0 if p.equip.weapon.kind != .None do bonus += p.equip.weapon.atk_spd if p.equip.amulet.kind != .None do bonus += p.equip.amulet.atk_spd return max(p.base_atk_cd * (1 - bonus), 0.1) } player_max_hp :: proc(p: ^Player) -> i32 { hp := p.base_max_hp if p.equip.armor.kind != .None do hp += p.equip.armor.hp_bonus if p.equip.amulet.kind != .None do hp += p.equip.amulet.hp_bonus return hp } recalc_stats :: proc(p: ^Player) { p.max_hp = player_max_hp(p) if p.hp > p.max_hp do p.hp = p.max_hp } // ─── Экипировка / использование ─── equip_item :: proc(state: ^GameState, p: ^Player, item: Item) { switch item.kind { case .Weapon: if p.equip.weapon.kind != .None { add_log(state, "Dropped %s", p.equip.weapon.name) } p.equip.weapon = item case .Armor: if p.equip.armor.kind != .None { add_log(state, "Dropped %s", p.equip.armor.name) } p.equip.armor = item case .Amulet: if p.equip.amulet.kind != .None { add_log(state, "Dropped %s", p.equip.amulet.name) } p.equip.amulet = item case .Potion: p.hp = min(p.hp + item.heal, player_max_hp(p)) add_log(state, "Drank potion +%d HP", item.heal) return case .None: return } recalc_stats(p) add_log(state, "Equipped: %s", item.name) } // ─── Уровни ─── check_level_up :: proc(state: ^GameState, p: ^Player) { for p.xp >= p.xp_next { p.xp -= p.xp_next p.level += 1 p.xp_next = i32(f32(p.xp_next) * 1.4) p.base_dmg += 2 p.base_max_hp += 12 p.hp = min(p.hp + 20, player_max_hp(p)) recalc_stats(p) add_log(state, "LEVEL %d! +dmg +HP", p.level) } } // ─── Ввод ─── sys_input :: proc(state: ^GameState, dt: f32) { _, p := get_player(state) if p == nil do return // направление к мыши (по земле) ray := rl.GetScreenToWorldRay(rl.GetMousePosition(), state.camera) if ray.direction.y != 0 { t := -ray.position.y / ray.direction.y hit := ray.position + ray.direction * t p.aim = rl.Vector2Normalize({hit.x - p.pos.x, hit.z - p.pos.y}) } // движение (45° поворот под изометрию) move: rl.Vector2 if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do move += {-1, -1} if rl.IsKeyDown(.S) || rl.IsKeyDown(.DOWN) do move += {1, 1} if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do move += {-1, 1} if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) do move += {1, -1} if p.dash_timer > 0 { p.dash_timer -= dt } else { p.vel = rl.Vector2Normalize(move) * p.speed } // инвентарь: клик по слоту (не атакуем при клике по UI) if rl.IsMouseButtonPressed(.LEFT) { if slot := ui_inventory_slot_at(rl.GetMousePosition()); slot >= 0 { if slot < int(p.inv_count) { item := p.inventory[slot] equip_item(state, p, item) for i in slot ..< int(p.inv_count) - 1 { p.inventory[i] = p.inventory[i + 1] } p.inventory[int(p.inv_count) - 1] = Item{} p.inv_count -= 1 } return } } // атака ЛКМ — сектор к мыши if p.attack_timer > 0 do p.attack_timer -= dt if rl.IsMouseButtonPressed(.LEFT) && p.attack_timer <= 0 { p.attack_timer = player_atk_cd(p) init_attack(state, Attack{ parent = state.player, dir = p.aim, dmg = player_total_dmg(p), crit = player_crit(p), radius = p.attack_range, arc = 1.3, lifetime = player_atk_cd(p), color = {255, 255, 255, 60}, }) } // навыки if rl.IsKeyPressed(.Q) do cast_skill(state, p, .Dash) if rl.IsKeyPressed(.W) do cast_skill(state, p, .Spin) if rl.IsKeyPressed(.E) do cast_skill(state, p, .Heal) } // ─── Тик ─── player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) { if p.invuln_timer > 0 do p.invuln_timer -= dt for &s in p.skills { if s.timer > 0 do s.timer -= dt } }