3d
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user