const c = @import("c.zig"); pub const EffectParams = struct { duration: c_int, intensity: c_int, }; pub fn procForClass(dmg_class: c.DamageClass) c.StatusEffectType { return switch (dmg_class) { c.DMG_SLASH => c.EFFECT_BLEED, c.DMG_IMPACT => c.EFFECT_STUN, c.DMG_PIERCE => c.EFFECT_WEAKEN, c.DMG_FIRE => c.EFFECT_BURN, c.DMG_POISON => c.EFFECT_POISON, else => c.EFFECT_NONE, }; } pub fn name(effect: c.StatusEffectType) ?[*:0]const u8 { return switch (effect) { c.EFFECT_POISON => "Poisoned", c.EFFECT_BLEED => "Bleeding", c.EFFECT_STUN => "Stunned", c.EFFECT_BURN => "Burning", c.EFFECT_WEAKEN => "Weakened", else => null, }; } pub fn paramsFor(effect: c.StatusEffectType) EffectParams { return switch (effect) { c.EFFECT_BLEED => .{ .duration = 4, .intensity = c.BLEED_STACK_DAMAGE }, c.EFFECT_STUN => .{ .duration = 2, .intensity = 0 }, c.EFFECT_WEAKEN => .{ .duration = 3, .intensity = c.WEAKEN_ATTACK_REDUCTION }, c.EFFECT_BURN => .{ .duration = 2, .intensity = c.BURN_BASE_DAMAGE }, c.EFFECT_POISON => .{ .duration = 5, .intensity = c.POISON_BASE_DAMAGE }, else => .{ .duration = 0, .intensity = 0 }, }; } pub fn clampCount(count: c_int) usize { if (count < 0) return 0; if (count > c.MAX_EFFECTS) return @intCast(c.MAX_EFFECTS); return @intCast(count); } pub fn has(effects: [*c]const c.StatusEffect, count: c_int, effect_type: c.StatusEffectType) bool { const safe_count = clampCount(count); for (0..safe_count) |i| { if (effects[i].type == effect_type and effects[i].duration > 0) return true; } return false; } pub fn apply( effects: [*c]c.StatusEffect, count: [*c]c_int, effect_type: c.StatusEffectType, duration: c_int, intensity: c_int, ) void { const safe_count = clampCount(count[0]); for (0..safe_count) |i| { if (effects[i].type == effect_type and effects[i].duration > 0) { if (effect_type == c.EFFECT_BLEED) { effects[i].intensity += intensity; if (effects[i].duration < duration) effects[i].duration = duration; } else { effects[i].duration = duration; if (intensity > effects[i].intensity) effects[i].intensity = intensity; } return; } } if (safe_count < @as(usize, @intCast(c.MAX_EFFECTS))) { effects[safe_count] = .{ .type = effect_type, .duration = duration, .intensity = intensity, }; count[0] = @intCast(safe_count + 1); } } fn compact(effects: [*c]c.StatusEffect, count: [*c]c_int) void { var write: usize = 0; const safe_count = clampCount(count[0]); for (0..safe_count) |read| { if (effects[read].duration > 0) { if (write != read) effects[write] = effects[read]; write += 1; } } count[0] = @intCast(write); } fn tickOne(eff: *c.StatusEffect, hp: *c_int) c_int { if (eff.duration <= 0) return 0; var dmg: c_int = 0; switch (eff.type) { c.EFFECT_POISON, c.EFFECT_BLEED, c.EFFECT_BURN => { dmg = eff.intensity; hp.* -= dmg; }, else => {}, } eff.duration -= 1; return dmg; } pub fn tickPlayer(p: [*c]c.Player) c_int { if (p == null) return 0; var total: c_int = 0; const safe_count = clampCount(p[0].effect_count); for (0..safe_count) |i| { total += tickOne(&p[0].effects[i], &p[0].hp); } compact(&p[0].effects, &p[0].effect_count); return total; } pub fn tickEnemy(e: [*c]c.Enemy) c_int { if (e == null) return 0; if (e[0].alive == 0) return 0; var total: c_int = 0; const safe_count = clampCount(e[0].effect_count); for (0..safe_count) |i| { total += tickOne(&e[0].effects[i], &e[0].hp); } if (e[0].hp <= 0) { e[0].hp = 0; e[0].alive = 0; } compact(&e[0].effects, &e[0].effect_count); return total; }