90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { computeRangedTarget } from '$lib/engine/ranged';
|
|
import { fkBasis } from '$lib/engine/derived';
|
|
import { newCharacter } from '$lib/characters/default';
|
|
import type { ExtraModifierId } from '$lib/rules/modifiers-ranged';
|
|
|
|
const fixedId = '00000000-0000-4000-8000-000000000099';
|
|
|
|
function charWithBogen(taw: number) {
|
|
const c = newCharacter({ name: 'Archer', id: fixedId });
|
|
const kt = c.kampftalente.find((k) => k.id === 'bogen');
|
|
if (kt) kt.taw = taw;
|
|
return c;
|
|
}
|
|
|
|
const baseSel = {
|
|
range: 'near' as const,
|
|
/** Zielgröße „groß“ hat Modifikator 0 in den Tabellen */
|
|
targetSize: 'large' as const,
|
|
visibility: 'clear' as const,
|
|
movement: 'slow' as const,
|
|
extras: [] as ExtraModifierId[],
|
|
aimTurns: 0,
|
|
targetBuild: 'biped' as const
|
|
};
|
|
|
|
describe('ranged', () => {
|
|
it('computes FK target with zero modifiers', () => {
|
|
const c = charWithBogen(4);
|
|
const fkBase = fkBasis(c);
|
|
const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: undefined });
|
|
expect(r.fkBase).toBe(fkBase);
|
|
expect(r.baseTarget).toBe(fkBase + 4);
|
|
expect(r.finalTarget).toBe(fkBase + 4);
|
|
});
|
|
|
|
it('applies distance modifier (mittel = +4 EW)', () => {
|
|
const c = charWithBogen(0);
|
|
const r = computeRangedTarget(
|
|
c,
|
|
'shortbow',
|
|
{ ...baseSel, range: 'medium', reloadState: 'regular_shot' }
|
|
);
|
|
// +4 Entfernung, +1 normaler Schuss, +0 Zielgröße „groß“
|
|
expect(r.totalModifier).toBe(5);
|
|
expect(r.finalTarget).toBe(r.baseTarget - 5);
|
|
});
|
|
|
|
it('halves TaW for aimed shot without expertise', () => {
|
|
const c = charWithBogen(10);
|
|
const r = computeRangedTarget(
|
|
c,
|
|
'shortbow',
|
|
{ ...baseSel, reloadState: 'aimed_shot' }
|
|
);
|
|
expect(r.effectiveTaW).toBe(5);
|
|
expect(r.baseTarget).toBe(r.fkBase + 5);
|
|
});
|
|
|
|
it('applies matching weapon specialization (+2 FK)', () => {
|
|
const c = charWithBogen(0);
|
|
c.abilities = [{ id: 'sf1', defId: 'specialize_shortbow' }];
|
|
const base = computeRangedTarget(c, 'shortbow', baseSel);
|
|
const plain = computeRangedTarget(charWithBogen(0), 'shortbow', baseSel);
|
|
expect(base.finalTarget).toBe(plain.finalTarget + 2);
|
|
});
|
|
|
|
it('ignores specialization for a different weapon', () => {
|
|
const c = charWithBogen(0);
|
|
c.abilities = [{ id: 'sf1', defId: 'specialize_longbow' }];
|
|
const r = computeRangedTarget(c, 'shortbow', baseSel);
|
|
const plain = computeRangedTarget(charWithBogen(0), 'shortbow', baseSel);
|
|
expect(r.finalTarget).toBe(plain.finalTarget);
|
|
});
|
|
|
|
it('derives expert from Scharfschütze ability (TaW/2 -2 for aimed shot)', () => {
|
|
const c = charWithBogen(10);
|
|
c.abilities = [{ id: 'sf1', defId: 'sniper' }];
|
|
const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: 'aimed_shot' });
|
|
expect(r.effectiveTaW).toBe(3);
|
|
});
|
|
|
|
it('derives master from Meisterschütze ability (full TaW for aimed shot)', () => {
|
|
const c = charWithBogen(10);
|
|
c.abilities = [{ id: 'sf1', defId: 'marksman' }];
|
|
const r = computeRangedTarget(c, 'shortbow', { ...baseSel, reloadState: 'aimed_shot' });
|
|
expect(r.effectiveTaW).toBe(10);
|
|
});
|
|
});
|