Initial commit

This commit is contained in:
Octoturge
2026-06-09 22:06:56 +02:00
commit fb42c6e8cc
121 changed files with 14976 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest';
import { getZealRegen } from '../../src/logic/combat';
describe('getZealRegen', () => {
it('returns base 1 when no flag set', () => {
const actor = { getFlag: () => undefined };
expect(getZealRegen(actor)).toBe(1);
});
it('adds bonus from flag', () => {
const actor = { getFlag: (_ns: string, key: string) => (key === 'zealRegenBonus' ? 2 : undefined) };
expect(getZealRegen(actor)).toBe(3);
});
it('floors at 0', () => {
const actor = { getFlag: () => -5 };
expect(getZealRegen(actor)).toBe(0);
});
it('handles missing getFlag gracefully', () => {
expect(getZealRegen({})).toBe(1);
});
});
+88
View File
@@ -0,0 +1,88 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { applyDamage } from '../../src/logic/damage';
function makeActor(overrides: Partial<any> = {}) {
const updates: Record<string, unknown>[] = [];
const actor: any = {
name: 'Test',
type: 'character',
system: {
attributes: {
body: { value: 3 },
mind: { value: 3 },
soul: { value: 3 },
magicalArmor: { value: 0, runicCounter: 0 },
magicalShield: { value: 0 },
physicalArmor: { value: 0 },
health: { value: 30, max: 30 },
...overrides,
},
},
items: [],
update: vi.fn(async (u: Record<string, unknown>) => { updates.push(u); }),
toggleStatusEffect: vi.fn(async () => undefined),
};
return { actor, updates };
}
describe('applyDamage', () => {
beforeEach(() => {
(globalThis as any).ChatMessage = { create: vi.fn(async () => undefined) };
});
it('applies plain HP damage when no armor', async () => {
const { actor } = makeActor();
const r = await applyDamage(actor, { amount: 7, postChat: false });
expect(r.hpDamage).toBe(7);
expect(actor.system.attributes.health.value).toBe(30); // pre-update; check call
expect(actor.update).toHaveBeenCalledWith(expect.objectContaining({
'system.attributes.health.value': 23,
}));
});
it('absorbs through magical armor + shield + physical armor', async () => {
const { actor } = makeActor({
magicalArmor: { value: 3, runicCounter: 0 },
magicalShield: { value: 4 },
physicalArmor: { value: 2 },
});
const r = await applyDamage(actor, { amount: 12, postChat: false });
expect(r.absorbed.magicalArmor).toBe(3);
expect(r.absorbed.magicalShield).toBe(4);
expect(r.absorbed.physicalArmor).toBe(2);
expect(r.hpDamage).toBe(3);
});
it('ignoreMagicalArmor skips layer 1', async () => {
const { actor } = makeActor({
magicalArmor: { value: 5, runicCounter: 0 },
});
const r = await applyDamage(actor, { amount: 4, ignoreMagicalArmor: true, postChat: false });
expect(r.absorbed.magicalArmor).toBe(0);
expect(r.hpDamage).toBe(4);
});
it('ignoreMagicalShield skips layer 2', async () => {
const { actor } = makeActor({
magicalShield: { value: 10 },
});
const r = await applyDamage(actor, { amount: 5, ignoreMagicalShield: true, postChat: false });
expect(r.absorbed.magicalShield).toBe(0);
expect(r.hpDamage).toBe(5);
});
it('ignorePhysicalArmor skips layer 3', async () => {
const { actor } = makeActor({
physicalArmor: { value: 4 },
});
const r = await applyDamage(actor, { amount: 6, ignorePhysicalArmor: true, postChat: false });
expect(r.absorbed.physicalArmor).toBe(0);
expect(r.hpDamage).toBe(6);
});
it('marks shield as dropped when reduced to 0', async () => {
const { actor } = makeActor({ magicalShield: { value: 3 } });
const r = await applyDamage(actor, { amount: 5, postChat: false });
expect(r.shieldDropped).toBe(true);
});
});
+107
View File
@@ -0,0 +1,107 @@
import { describe, it, expect, vi } from 'vitest';
import '../setup';
import { rest } from '../../src/logic/rest';
function makeActor(overrides: Partial<any> = {}) {
const updates: Record<string, unknown> = {};
const deletedEffects: string[] = [];
const actor: any = {
type: 'character',
items: [],
effects: overrides.effects ?? [],
system: {
attributes: {
body: { value: 3 },
health: { value: 10, max: 30 },
mana: { value: 1, max: 20 },
zeal: { value: 0, max: 5 },
blood: { value: 0, max: 6 },
elixirTolerance: 2,
magicalArmor: { value: 4, runicCounter: 3 },
...overrides.attributes,
},
skills: overrides.skills ?? {},
},
update: vi.fn(async (u: Record<string, unknown>) => { Object.assign(updates, u); }),
deleteEmbeddedDocuments: vi.fn(async (_t: string, ids: string[]) => { deletedEffects.push(...ids); }),
unsetFlag: vi.fn(async () => undefined),
};
return { actor, updates, deletedEffects };
}
describe('rest', () => {
it('take a breather restores all mana and 1d6 + endurance HP, leaves zeal/blood alone', async () => {
const { actor, updates } = makeActor({
attributes: {
health: { value: 10, max: 30 },
mana: { value: 1, max: 20 },
},
skills: {
endurance: { value: 2 },
},
});
const r = await rest(actor, 'breather');
expect(r.kind).toBe('breather');
expect(r.hpRestored).toBe(6); // 4 (stub roll default) + 2 (endurance) = 6
expect(r.manaRestored).toBe(19);
expect(updates['system.attributes.health.value']).toBe(16);
expect(updates['system.attributes.mana.value']).toBe(20);
expect(updates['system.attributes.zeal.value']).toBeUndefined();
});
it('short rest restores all mana, 1/3 HP, and removes unconscious state', async () => {
const effects = [
{ id: 'nieprzytomny', statuses: new Set(['nieprzytomny']) },
{ id: 'przewrocony', statuses: new Set(['przewrocony']) },
];
const { actor, updates, deletedEffects } = makeActor({
effects,
attributes: {
health: { value: 10, max: 30 },
mana: { value: 1, max: 20 },
},
});
const r = await rest(actor, 'short');
expect(r.kind).toBe('short');
expect(r.hpRestored).toBe(10); // 30 / 3 = 10
expect(r.manaRestored).toBe(19);
expect(updates['system.attributes.health.value']).toBe(20);
expect(updates['system.attributes.mana.value']).toBe(20);
expect(deletedEffects).toContain('nieprzytomny');
expect(deletedEffects).not.toContain('przewrocony');
expect(r.effectsCleared).toBe(1);
});
it('long rest fully restores HP/mana/zeal/blood, resets runic counter, and clears all conditions and temporary effects', async () => {
const effects = [
{ id: 'nieprzytomny', statuses: new Set(['nieprzytomny']) },
{ id: 'przewrocony', statuses: new Set(['przewrocony']) },
{ id: 'eff-1', origin: null, duration: { rounds: 3 } },
{ id: 'eff-2', origin: 'Item.abc', duration: { rounds: 3 } },
];
const { actor, updates, deletedEffects } = makeActor({
effects,
attributes: {
health: { value: 10, max: 30 },
mana: { value: 1, max: 20 },
zeal: { value: 0, max: 5 },
blood: { value: 0, max: 6 },
magicalArmor: { value: 4, runicCounter: 3 },
},
});
const r = await rest(actor, 'long');
expect(r.kind).toBe('long');
expect(updates['system.attributes.health.value']).toBe(30);
expect(updates['system.attributes.mana.value']).toBe(20);
expect(updates['system.attributes.zeal.value']).toBe(5);
expect(updates['system.attributes.blood.value']).toBe(6);
expect(updates['system.attributes.magicalArmor.runicCounter']).toBe(0);
expect(r.toleranceRecovered).toBe(1);
expect(deletedEffects).toContain('nieprzytomny');
expect(deletedEffects).toContain('przewrocony');
expect(deletedEffects).toContain('eff-1');
expect(deletedEffects).not.toContain('eff-2');
expect(r.effectsCleared).toBe(3);
});
});