import { describe, expect, it } from "vitest"; import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog"; import { ROM_COA_HOLY_POWER_EXPLOSION_ID, ROM_COA_HOLY_SEAL_NO_DOT_ABILITY_IDS, ROM_COA_BLOCKING_STANCE_ID, ROM_COA_RESIST_ID, ROM_LOCAL_ACTIVE_BLOCKING_STANCE_AURA_ID, romCurrentCoaBlockingStanceModifiers, romCurrentCoaHolyPowerExplosionThreatMultiplier, romCurrentCoaResistModifiers, romCurrentCoaThreatFactors, withoutCurrentCoaHolySealPeriodicDamage, } from "./romWarriorCurrentCoaMechanics"; const damage: AbilityEffect = { kind: "damage", coefficient: 1, school: "physical" }; const dot: AbilityEffect = { kind: "dot", coefficient: 0.25, ticks: 5, intervalMs: 2_000 }; const combo: AbilityEffect = { kind: "combo", amount: 1 }; const knightAction = { source: "runewaker", classId: "rom-knight" } as Pick< AbilityDefinition, "source" | "classId" >; describe("current CoA Warrior/Knight mechanics", () => { it.each(ROM_COA_HOLY_SEAL_NO_DOT_ABILITY_IDS)( "removes the obsolete Holy Seal periodic damage from %i while preserving hit and combo effects", (sourceSkillId) => { const effects: AbilityEffect[] = [damage, dot, combo]; expect(withoutCurrentCoaHolySealPeriodicDamage(sourceSkillId, effects)).toEqual([damage, combo]); }, ); it("leaves unrelated ability effects and non-Holy-Seal periodic effects unchanged", () => { const effects: AbilityEffect[] = [damage, dot, combo]; expect(withoutCurrentCoaHolySealPeriodicDamage(490152, effects)).toBe(effects); }); it("applies Holy Power Explosion's 2.98 threat multiplier to Knight actions only when learned", () => { expect(romCurrentCoaHolyPowerExplosionThreatMultiplier(knightAction, [ROM_COA_HOLY_POWER_EXPLOSION_ID])) .toBe(2.98); expect(romCurrentCoaHolyPowerExplosionThreatMultiplier(knightAction, [])).toBe(1); expect(romCurrentCoaHolyPowerExplosionThreatMultiplier( { source: "runewaker", classId: "rom-warrior" }, [ROM_COA_HOLY_POWER_EXPLOSION_ID], )).toBe(1); expect(romCurrentCoaHolyPowerExplosionThreatMultiplier( { source: "core", classId: "rom-knight" }, [ROM_COA_HOLY_POWER_EXPLOSION_ID], )).toBe(1); }); it("keeps each CoA aggro factor separate and leaves their overlap unmodeled", () => { const ability = { source: "runewaker" as const, classId: "rom-knight" as const }; const holyPower = romCurrentCoaThreatFactors({ ability, classId: "rom-knight", learnedPassiveSpellIds: [490172], blockingStanceAuraActive: false, }); expect(holyPower).toMatchObject({ holyPowerExplosionMultiplier: 2.98, resolvedMultiplier: 2.98, overlapUnresolved: false }); const blocking = romCurrentCoaThreatFactors({ ability: { source: "runewaker", classId: "rom-warrior" }, classId: "rom-warrior", secondaryClassId: "rom-knight", learnedPassiveSpellIds: [493287], blockingStanceAuraActive: true, }); expect(blocking).toMatchObject({ blockingStanceMultiplier: 1.754, resolvedMultiplier: 1.754, overlapUnresolved: false }); const resist = romCurrentCoaThreatFactors({ ability: { source: "runewaker", classId: "rom-warrior" }, classId: "rom-warrior", secondaryClassId: "rom-knight", learnedPassiveSpellIds: [493287, 493251], blockingStanceAuraActive: true, }); expect(resist).toMatchObject({ resistMultiplier: 1.8, resolvedMultiplier: 1, overlapUnresolved: true }); const overlapping = romCurrentCoaThreatFactors({ ability, classId: "rom-warrior", secondaryClassId: "rom-knight", learnedPassiveSpellIds: [490172, 493287, 493251], blockingStanceAuraActive: true, }); expect(overlapping).toMatchObject({ holyPowerExplosionMultiplier: 2.98, blockingStanceMultiplier: 1.754, resistMultiplier: 1.8, resolvedMultiplier: 1, overlapUnresolved: true, }); }); it("enables Resist's +80% aggro and 20% physical reduction only for learned Warrior/Knight under CoA stance", () => { expect(romCurrentCoaResistModifiers({ classId: "rom-warrior", secondaryClassId: "rom-knight", learnedPassiveSpellIds: [ROM_COA_RESIST_ID, ROM_COA_BLOCKING_STANCE_ID], blockingStanceAuraActive: true, })).toEqual({ threatMultiplier: 1.8, physicalDamageTakenMultiplier: 0.8 }); }); it("applies CoA Blocking Stance's sourced static modifiers only while its passive and active aura are both present", () => { const stance = { classId: "rom-warrior" as const, secondaryClassId: "rom-knight" as const, learnedPassiveSpellIds: [ROM_COA_BLOCKING_STANCE_ID], blockingStanceAuraActive: true, }; expect(romCurrentCoaBlockingStanceModifiers(stance)).toEqual({ parryRateBonusPercent: 8, receivedDamageReductionPercent: 18.9, aggroBonusPercent: 75.4, }); expect(romCurrentCoaBlockingStanceModifiers({ ...stance, learnedPassiveSpellIds: [], })).toEqual({ parryRateBonusPercent: 0, receivedDamageReductionPercent: 0, aggroBonusPercent: 0, }); expect(romCurrentCoaBlockingStanceModifiers({ ...stance, blockingStanceAuraActive: false, })).toEqual({ parryRateBonusPercent: 0, receivedDamageReductionPercent: 0, aggroBonusPercent: 0, }); }); it("exposes the stable local 491480 aura ID without treating it as CoA passive 493287", () => { expect(ROM_LOCAL_ACTIVE_BLOCKING_STANCE_AURA_ID).toBe("rom-aura-491480-502007-0-parry"); expect(romCurrentCoaBlockingStanceModifiers({ classId: "rom-warrior", secondaryClassId: "rom-knight", learnedPassiveSpellIds: [], blockingStanceAuraActive: true, })).toEqual({ parryRateBonusPercent: 0, receivedDamageReductionPercent: 0, aggroBonusPercent: 0, }); }); it("requires learned Resist, learned CoA Blocking Stance, the active stance aura, and Warrior/Knight", () => { const baseline = { threatMultiplier: 1, physicalDamageTakenMultiplier: 1 }; const state = { classId: "rom-warrior" as const, secondaryClassId: "rom-knight" as const, learnedPassiveSpellIds: [ROM_COA_RESIST_ID, ROM_COA_BLOCKING_STANCE_ID], blockingStanceAuraActive: true, }; expect(romCurrentCoaResistModifiers({ ...state, learnedPassiveSpellIds: [] })).toEqual(baseline); expect(romCurrentCoaResistModifiers({ ...state, learnedPassiveSpellIds: [ROM_COA_RESIST_ID], })).toEqual(baseline); expect(romCurrentCoaResistModifiers({ ...state, blockingStanceAuraActive: false, })).toEqual(baseline); expect(romCurrentCoaResistModifiers({ ...state, classId: "rom-knight", secondaryClassId: "rom-warrior", })).toEqual(baseline); }); });