diff --git a/apps/api/src/combat/combat-damage.spec.ts b/apps/api/src/combat/combat-damage.spec.ts new file mode 100644 index 0000000..a5a2c4e --- /dev/null +++ b/apps/api/src/combat/combat-damage.spec.ts @@ -0,0 +1,17 @@ +import { calculateDamage } from './combat-damage'; + +describe('calculateDamage', () => { + it('applies the established armor mitigation formula', () => { + // raw = 12 + 15 = 27; 27 * 60 / (60 + 20) = 20.25 -> rounds to 20 + expect(calculateDamage({ attack: 12, weaponDamage: 15 }, 20)).toBe(20); + }); + + it('never returns less than 1 damage, even against extreme armor', () => { + expect(calculateDamage({ attack: 1, weaponDamage: 0 }, 100_000)).toBe(1); + }); + + it('treats an attacker with no weaponDamage as having attack alone as its raw damage', () => { + // raw = 9; 9 * 60 / (60 + 5) = 8.307... -> rounds to 8 + expect(calculateDamage({ attack: 9 }, 5)).toBe(8); + }); +}); diff --git a/apps/api/src/combat/combat-damage.ts b/apps/api/src/combat/combat-damage.ts new file mode 100644 index 0000000..a8d8810 --- /dev/null +++ b/apps/api/src/combat/combat-damage.ts @@ -0,0 +1,13 @@ +export interface DamageAttacker { + attack: number; + weaponDamage?: number; +} + +const ARMOR_MITIGATION_CONSTANT = 60; + +export function calculateDamage(attacker: DamageAttacker, targetArmor: number): number { + const rawDamage = attacker.attack + (attacker.weaponDamage ?? 0); + const mitigatedDamage = + (rawDamage * ARMOR_MITIGATION_CONSTANT) / (ARMOR_MITIGATION_CONSTANT + targetArmor); + return Math.max(1, Math.round(mitigatedDamage)); +}