import { describe, expect, it } from 'vitest'; import { WEAPONS } from '../../app/static/js/config.js'; import { degToRad } from '../../app/static/js/logic/math.js'; import { createRng } from '../../app/static/js/logic/rng.js'; import { pelletOffsets, WeaponState } from '../../app/static/js/logic/weapon.js'; const def = { id: 'test', name: 'Test', magazineSize: 3, reserveStart: 4, reserveMax: 10, fireIntervalMs: 100, reloadMs: 500, damage: 1, pellets: 1, spreadDeg: 0, bulletSpeed: 1, rangePx: 1, automatic: true, }; const rng = createRng(1); describe('WeaponState', () => { it('startet mit vollem Magazin und Startreserve', () => { const w = new WeaponState(def); expect(w.magazine).toBe(3); expect(w.reserve).toBe(4); expect(w.isReloading).toBe(false); }); it('respektiert die Feuerrate', () => { const w = new WeaponState(def); expect(w.fire(0, rng)).toHaveLength(1); expect(w.fire(50, rng)).toBeNull(); expect(w.fire(100, rng)).toHaveLength(1); expect(w.magazine).toBe(1); }); it('lädt bei leerem Magazin automatisch nach und kann währenddessen nicht feuern', () => { const w = new WeaponState(def); w.fire(0, rng); w.fire(100, rng); w.fire(200, rng); expect(w.magazine).toBe(0); expect(w.isReloading).toBe(true); expect(w.fire(300, rng)).toBeNull(); expect(w.update(699)).toBe(false); expect(w.update(700)).toBe(true); expect(w.magazine).toBe(3); expect(w.reserve).toBe(1); }); it('lädt bei knapper Reserve nur teilweise nach und ist danach leer', () => { const w = new WeaponState({ ...def, reserveStart: 1 }); w.fire(0, rng); w.fire(100, rng); w.fire(200, rng); w.update(10_000); expect(w.magazine).toBe(1); expect(w.reserve).toBe(0); w.fire(10_100, rng); expect(w.isEmpty).toBe(true); expect(w.startReload(10_200)).toBe(false); }); it('lädt nicht nach, wenn das Magazin voll ist', () => { const w = new WeaponState(def); expect(w.startReload(0)).toBe(false); }); it('bricht das Nachladen beim Waffenwechsel ohne Verlust ab', () => { const w = new WeaponState(def); w.fire(0, rng); expect(w.startReload(100)).toBe(true); w.cancelReload(); w.update(10_000); expect(w.magazine).toBe(2); expect(w.reserve).toBe(4); }); it('zeigt den Nachladefortschritt', () => { const w = new WeaponState(def); w.fire(0, rng); w.startReload(1000); expect(w.reloadProgress(1250)).toBeCloseTo(0.5); w.update(1500); expect(w.reloadProgress(1600)).toBe(1); }); it('füllt die Reserve per Nachschub auf', () => { const w = new WeaponState(def); w.refill(); expect(w.reserve).toBe(10); }); it('hat bei der Dienstpistole unbegrenzte Reserve', () => { const pistol = new WeaponState(WEAPONS.pistol); expect(pistol.hasInfiniteReserve).toBe(true); let t = 0; for (let i = 0; i < 100; i += 1) { t += 10_000; pistol.update(t); pistol.fire(t, rng); } expect(pistol.isEmpty).toBe(false); pistol.refill(); expect(pistol.reserve).toBe(Infinity); }); }); describe('pelletOffsets', () => { it('streut ein Einzelprojektil innerhalb des Fächers', () => { for (let i = 0; i < 50; i += 1) { const [offset] = pelletOffsets(1, 10, rng); expect(Math.abs(offset)).toBeLessThanOrEqual(degToRad(5)); } }); it('verteilt Schrot über den ganzen Fächer', () => { const offsets = pelletOffsets(8, 24, rng); expect(offsets).toHaveLength(8); const spread = degToRad(24); expect(Math.min(...offsets)).toBeLessThan(-spread / 3); expect(Math.max(...offsets)).toBeGreaterThan(spread / 3); for (const o of offsets) expect(Math.abs(o)).toBeLessThanOrEqual(spread / 2 + spread / 7); }); });