feat: temp HP, character isNpc, initiative box
Temp HP: - setTempHp() in shared/turn.js (replaces, no stacking) - Damage absorbs temp HP first (both 5e + generic rulesets) - Inline temp HP input on participant card (cyan when active) - Temp HP field in edit modal - makeParticipant tempHp field (default 0) - 8 shared tests Character isNpc: - isNpc field on character roster model - buildCharacterParticipant: type 'npc' when isNpc - Add form checkbox + edit form checkbox - NPC badge on character list row - Writeback preserves isNpc Initiative box: - Amber-bordered box around initiative input - Gold text (amber-300), disabled stays gold - Stone-950 bg matches page - Bumped maxHp (stone-200) + tempHp (stone-300/cyan) visibility
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// Temp HP: damage hits temp first, then regular.
|
||||
// setTempHp replaces (no stacking). Healing regular HP ignores temp.
|
||||
const shared = require('@ttrpg/shared');
|
||||
const { makeParticipant, applyHpChange, setTempHp, addParticipant } = shared;
|
||||
const { mockCtx } = require('./_helpers');
|
||||
|
||||
function setupEnc() {
|
||||
const { ctx } = mockCtx();
|
||||
return {
|
||||
enc: { id: 'enc1', name: 'TempEnc', participants: [], isStarted: false, isPaused: false, round: 0 },
|
||||
ctx,
|
||||
};
|
||||
}
|
||||
|
||||
describe('temp HP', () => {
|
||||
test('setTempHp sets tempHp on participant', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(5);
|
||||
});
|
||||
|
||||
test('setTempHp replaces existing temp (no stacking)', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
e = await setTempHp(e, p.id, 3, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(3);
|
||||
});
|
||||
|
||||
test('setTempHp to 0 clears temp', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
e = await setTempHp(e, p.id, 0, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(0);
|
||||
});
|
||||
|
||||
test('damage: temp absorbs before regular HP', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
e = await applyHpChange(e, p.id, 'damage', 8, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(0);
|
||||
expect(e.participants[0].currentHp).toBe(17);
|
||||
});
|
||||
|
||||
test('damage less than temp: only temp reduced', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 10, ctx);
|
||||
e = await applyHpChange(e, p.id, 'damage', 4, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(6);
|
||||
expect(e.participants[0].currentHp).toBe(20);
|
||||
});
|
||||
|
||||
test('heal: does not affect temp HP', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 10,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
e = await applyHpChange(e, p.id, 'heal', 8, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(5);
|
||||
expect(e.participants[0].currentHp).toBe(18);
|
||||
});
|
||||
|
||||
test('damage exactly equals temp: temp gone, regular intact', async () => {
|
||||
const { enc, ctx } = setupEnc();
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
let e = await addParticipant(enc, p, ctx);
|
||||
e = await setTempHp(e, p.id, 5, ctx);
|
||||
e = await applyHpChange(e, p.id, 'damage', 5, ctx);
|
||||
expect(e.participants[0].tempHp).toBe(0);
|
||||
expect(e.participants[0].currentHp).toBe(20);
|
||||
});
|
||||
|
||||
test('makeParticipant: tempHp defaults to 0', async () => {
|
||||
const p = makeParticipant({
|
||||
name: 'Hero', type: 'character', initiative: 10, maxHp: 20, currentHp: 20,
|
||||
});
|
||||
expect(p.tempHp).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user