const c = @import("c.zig"); const event = @import("event.zig"); const effects = @import("effects.zig"); fn rng(min: c_int, max: c_int) c_int { return c.rng_int(min, max); } fn applyResistance(damage: c_int, resistance: c_int) c_int { var r = resistance; if (r > 75) r = 75; const factor = 100 - r; var result = @divTrunc(damage * factor, 100); if (result < 1) result = 1; return result; } fn rollProc( target_effects: [*c]c.StatusEffect, target_count: [*c]c_int, dmg_class: c.DamageClass, status_chance: c_int, ) c.StatusEffectType { if (status_chance <= 0 or rng(0, 99) >= status_chance) return c.EFFECT_NONE; const eff_type = effects.procForClass(dmg_class); if (eff_type == c.EFFECT_NONE) return c.EFFECT_NONE; const params = effects.paramsFor(eff_type); effects.apply(target_effects, target_count, eff_type, params.duration, params.intensity); return eff_type; } pub fn playerAttack(p: [*c]c.Player, e: [*c]c.Enemy) void { if (p == null or e == null) return; if (e[0].alive == 0) return; event.reset(); if (e[0].dodge > 0 and rng(0, 99) < e[0].dodge) { event.last.is_player_damage = 0; event.last.was_dodged = 1; event.last.message = "Enemy dodged!"; return; } // Read weapon stats or unarmed defaults var dmg_class: c.DamageClass = c.DMG_IMPACT; var crit_chance: c_int = c.UNARMED_CRIT_CHANCE; var crit_mult: c_int = c.UNARMED_CRIT_MULT; var status_chance: c_int = c.UNARMED_STATUS_CHANCE; if (p[0].has_weapon != 0) { dmg_class = p[0].equipped_weapon.dmg_class; crit_chance = p[0].equipped_weapon.crit_chance; crit_mult = p[0].equipped_weapon.crit_multiplier; status_chance = p[0].equipped_weapon.status_chance; } var base_attack = p[0].attack; if (effects.has(&p[0].effects, p[0].effect_count, c.EFFECT_WEAKEN)) base_attack -= c.WEAKEN_ATTACK_REDUCTION; if (base_attack < 1) base_attack = 1; var damage = base_attack; if (rng(0, 99) < crit_chance) { damage = @divTrunc(damage * crit_mult, 100); event.last.is_critical = 1; } const variance = rng(80, 120); damage = @divTrunc(damage * variance, 100); if (damage < 1) damage = 1; const res_index: usize = @intCast(dmg_class); if (res_index < c.NUM_DMG_CLASSES) { damage = applyResistance(damage, e[0].resistance[res_index]); } if (damage == 0) { event.last.damage = 0; event.last.is_player_damage = 0; event.last.message = "No effect!"; return; } if (e[0].block > 0 and rng(0, 99) < 30) { var blocked = e[0].block; if (blocked > damage) blocked = damage; damage -= blocked; if (damage < 1) damage = 1; event.last.was_blocked = 1; event.last.block_amount = blocked; } e[0].hp -= damage; event.last.damage = damage; event.last.is_player_damage = 0; const applied = rollProc(&e[0].effects, &e[0].effect_count, dmg_class, status_chance); event.last.applied_effect = applied; if (e[0].hp <= 0) { e[0].hp = 0; e[0].alive = 0; event.last.message = "Enemy killed!"; } else if (applied != c.EFFECT_NONE) { event.last.message = switch (applied) { c.EFFECT_BLEED => "Hit! Bleeding!", c.EFFECT_STUN => "Hit! Stunned!", c.EFFECT_WEAKEN => "Hit! Weakened!", c.EFFECT_BURN => "Hit! Burning!", c.EFFECT_POISON => "Hit! Poisoned!", else => "You hit", }; } else if (event.last.is_critical != 0) { event.last.message = "Critical hit!"; } else { event.last.message = "You hit"; } } pub fn enemyAttack(e: [*c]c.Enemy, p: [*c]c.Player) void { if (e == null or p == null) return; if (e[0].alive == 0) return; event.reset(); event.last.is_player_damage = 1; if (p[0].dodge > 0 and rng(0, 99) < p[0].dodge) { event.last.was_dodged = 1; event.last.message = "You dodged!"; return; } var base_damage = e[0].attack; if (effects.has(&e[0].effects, e[0].effect_count, c.EFFECT_WEAKEN)) base_damage -= c.WEAKEN_ATTACK_REDUCTION; base_damage -= p[0].defense; if (base_damage < 1) base_damage = 1; var damage = base_damage; const e_crit_chance = if (e[0].crit_chance > 0) e[0].crit_chance else c.ENEMY_CRIT_CHANCE; const e_crit_mult = if (e[0].crit_mult > 0) e[0].crit_mult else c.ENEMY_CRIT_MULT; if (rng(0, 99) < e_crit_chance) { damage = @divTrunc(damage * e_crit_mult, 100); event.last.is_critical = 1; } const variance = rng(80, 120); damage = @divTrunc(damage * variance, 100); if (damage < 1) damage = 1; if (p[0].block > 0 and rng(0, 99) < 30) { var blocked = p[0].block; if (blocked > damage) blocked = damage; damage -= blocked; if (damage < 1) damage = 1; event.last.was_blocked = 1; event.last.block_amount = blocked; } p[0].hp -= damage; event.last.damage = damage; const applied = rollProc(&p[0].effects, &p[0].effect_count, e[0].dmg_class, e[0].status_chance); event.last.applied_effect = applied; if (p[0].hp <= 0) { p[0].hp = 0; event.last.message = "You died!"; } else if (applied != c.EFFECT_NONE) { event.last.message = switch (applied) { c.EFFECT_POISON => "Hit! Poisoned!", c.EFFECT_BLEED => "Hit! Bleeding!", c.EFFECT_STUN => "Hit! Stunned!", c.EFFECT_BURN => "Hit! Burning!", c.EFFECT_WEAKEN => "Hit! Weakened!", else => "Hit", }; } else if (event.last.was_blocked != 0) { event.last.message = "Blocked some damage"; } else if (event.last.is_critical != 0) { event.last.message = "Critical!"; } else { event.last.message = "Hit"; } }