Files

127 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi } from 'vitest';
import {
absorbBlood,
endOfFightKrew,
gainKrewFromWeaponDamage,
greatSacrifice,
resolveLifeSteal,
sacrifice,
selfHarm,
startOfFightKrew,
useLifeSteal,
} from '../../src/logic/blood-magic';
import { krewFromWeaponHpLoss, runicArmorPointsDisabled, startingKrew } from '../../src/logic/rules';
function makeActor(opts: { slugs?: string[]; blood?: number; hp?: number; zeal?: number; used?: Record<string, number> } = {}) {
const flags: Record<string, unknown> = {};
const actor: any = {
system: {
attributes: {
blood: { value: opts.blood ?? 0, max: 20 },
health: { value: opts.hp ?? 20, max: 30 },
zeal: { value: opts.zeal ?? 3, max: 5 },
magic: { value: 3, actual: 3 },
bloodUsed: { selfHarm: 0, lifeSteal: 0, sacrifice: 0, absorb: 0, ...(opts.used ?? {}) },
greatSacrificeUsed: 0,
},
},
items: (opts.slugs ?? []).map((slug) => ({ type: 'talent', system: { slug } })),
update: vi.fn(async () => undefined),
setFlag: vi.fn(async (_s: string, k: string, v: unknown) => { flags[k] = v; }),
getFlag: vi.fn((_s: string, k: string) => flags[k]),
unsetFlag: vi.fn(async (_s: string, k: string) => { delete flags[k]; }),
};
return actor;
}
describe('Samookaleczenie', () => {
it('loses HP = Sukcesy and gains 1 Krew per HP, once per round', async () => {
const actor = makeActor();
const res = await selfHarm(actor, { roller: async () => 3 });
expect(res).toMatchObject({ ok: true, hpLost: 3, krewGained: 3 });
expect(actor.update).toHaveBeenCalledWith(expect.objectContaining({
'system.attributes.health.value': 17,
'system.attributes.blood.value': 3,
'system.attributes.bloodUsed.selfHarm': 1,
}));
const again = makeActor({ used: { selfHarm: 1 } });
expect((await selfHarm(again, { roller: async () => 3 })).reason).toBe('already-used');
});
it('Większe Samookaleczenie needs the talent and gives 2 Krew per HP', async () => {
expect((await selfHarm(makeActor(), { greater: true, roller: async () => 2 })).reason).toBe('missing-talent');
const res = await selfHarm(makeActor({ slugs: ['krwisty-pojedynkowicz'] }), { greater: true, roller: async () => 2 });
expect(res.krewGained).toBe(4);
});
});
describe('Kradzież Życia', () => {
it('costs 1 Zapał, arms the next attack and pays Krew for HP actually lost', async () => {
const actor = makeActor({ zeal: 2 });
expect(await useLifeSteal(actor)).toEqual({ ok: true, stepsEasier: 1 });
expect(actor.update).toHaveBeenCalledWith({ 'system.attributes.zeal.value': 1 });
expect(await resolveLifeSteal(actor, 4)).toBe(4);
});
it('greater version pays 2 Krew per HP and may be used twice per round', async () => {
const actor = makeActor({ slugs: ['wieksza-kradziez-zycia'], used: { lifeSteal: 1 } });
expect((await useLifeSteal(actor, { greater: true })).stepsEasier).toBe(2);
expect(await resolveLifeSteal(actor, 3)).toBe(6);
const spent = makeActor({ slugs: ['wieksza-kradziez-zycia'], used: { lifeSteal: 2 } });
expect((await useLifeSteal(spent, { greater: true })).reason).toBe('already-used');
});
});
describe('Złożenie w Ofierze / Wielka Ofiara', () => {
it('heals Sukcesy + Magia and gains Krew = Sukcesy', async () => {
const actor = makeActor({ hp: 10 });
expect(await sacrifice(actor, 2)).toMatchObject({ ok: true, hpGained: 5, krewGained: 2 });
});
it('Wielka Ofiara kills when Sukcesy exceed the highest Atrybut, else heals', async () => {
const actor = makeActor({ slugs: ['rytualista-krwi'], hp: 10 });
const kill = await greatSacrifice(actor, 6, { highestAttribute: 5, health: 12 });
expect(kill).toMatchObject({ ok: true, kills: true, targetHpLost: 12, krewGained: 12 });
const other = makeActor({ slugs: ['rytualista-krwi'], hp: 10 });
expect(await greatSacrifice(other, 4, { highestAttribute: 5, health: 12 })).toMatchObject({ kills: false, hpGained: 4, krewGained: 0 });
expect((await sacrifice(actor, 2)).reason).toBe('replaced-by-wielka-ofiara');
});
});
describe('Wchłanianie Krwi', () => {
it('needs at least 1 Krew; 2 HP per Krew once per round, 3 HP greater', async () => {
expect((await absorbBlood(makeActor({ blood: 5 }), 0)).reason).toBe('minimum-one-blood');
expect((await absorbBlood(makeActor({ blood: 5, hp: 10 }), 3))).toMatchObject({ ok: true, hpGained: 6 });
expect((await absorbBlood(makeActor({ blood: 5, used: { absorb: 1 } }), 1)).reason).toBe('already-used');
expect((await absorbBlood(makeActor({ blood: 5, hp: 10 }), 2, { greater: true }))).toMatchObject({ hpGained: 6 });
});
});
describe('passive Krew', () => {
it('Szacunek do Życia 1 per 4 HP, Krwisty Pojedynkowicz 1 per 2 HP', async () => {
expect(krewFromWeaponHpLoss(9, 'respect-for-life')).toBe(2);
expect(krewFromWeaponHpLoss(9, 'krwisty-pojedynkowicz')).toBe(4);
expect(krewFromWeaponHpLoss(9, 'rytualista-krwi')).toBe(0);
expect(await gainKrewFromWeaponDamage(makeActor({ slugs: ['respect-for-life'] }), 9)).toBe(2);
expect(await gainKrewFromWeaponDamage(makeActor(), 9)).toBe(0);
});
it('fight start/end: Rytualista starts with 4, leftover Krew heals 2 HP each', async () => {
expect(startingKrew('rytualista-krwi')).toBe(4);
const rit = makeActor({ slugs: ['rytualista-krwi'], blood: 0 });
await startOfFightKrew(rit);
expect(rit.update).toHaveBeenCalledWith({ 'system.attributes.blood.value': 4 });
const end = makeActor({ slugs: ['respect-for-life'], blood: 5, hp: 10 });
expect(await endOfFightKrew(end)).toBe(10);
});
});
describe('Runiczny Pancerz – single hit', () => {
it('> 5 disables 1 point; shield-breakers disable per 2 damage', () => {
expect(runicArmorPointsDisabled(5)).toBe(0);
expect(runicArmorPointsDisabled(6)).toBe(1);
expect(runicArmorPointsDisabled(40)).toBe(1);
expect(runicArmorPointsDisabled(7, 2)).toBe(3);
});
});