3e5bdc06e2
- Condition durations + auto-expiry: timed conditions tick down on the affected combatant's turn and drop at 0 (engine tickConditions; UI rounds field + (Nr) chip) - Initiative: Roll-all (d20 + per-combatant bonus, re-sort anchored) + group/quantity add - Combat log: per-round event feed (turns, round changes, damage/heal, expiries, removals) + in-memory Undo of the last change - Encounter difficulty budget: 5e XP thresholds + PF2e level budget (pure lib + tests); live difficulty/XP-award readout from monster combatants vs the party - Combatants carry initBonus + cr/level; compendium add-to-combat passes them 18 new unit tests (durations, applyInitiatives, budget), new combat-depth e2e. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { z } from 'zod';
|
|
import { conditionSchema, hpSchema, int } from './common';
|
|
|
|
export const combatantKindSchema = z.enum(['pc', 'npc', 'monster']);
|
|
|
|
export const combatantSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string().min(1).max(120),
|
|
kind: combatantKindSchema,
|
|
/** link to a Character, if this combatant is a tracked PC/NPC */
|
|
characterId: z.string().optional(),
|
|
/** ref into the bestiary, if spawned from a monster */
|
|
monsterRef: z.string().optional(),
|
|
|
|
initiative: int,
|
|
/** modifier added when (re)rolling initiative */
|
|
initBonus: int.default(0),
|
|
ac: int,
|
|
hp: hpSchema,
|
|
conditions: z.array(conditionSchema).default([]),
|
|
notes: z.string().max(2000).default(''),
|
|
/** 5e challenge rating (for difficulty budget), if known */
|
|
cr: z.number().finite().nonnegative().optional(),
|
|
/** PF2e creature level (for difficulty budget), if known */
|
|
level: int.optional(),
|
|
});
|
|
|
|
export type Combatant = z.infer<typeof combatantSchema>;
|
|
|
|
export const logEntrySchema = z.object({
|
|
round: int.nonnegative(),
|
|
text: z.string(),
|
|
});
|
|
export type LogEntry = z.infer<typeof logEntrySchema>;
|
|
|
|
export const encounterStatusSchema = z.enum(['planning', 'active', 'ended']);
|
|
|
|
export const encounterSchema = z.object({
|
|
id: z.string(),
|
|
campaignId: z.string(),
|
|
name: z.string().min(1).max(120),
|
|
status: encounterStatusSchema.default('planning'),
|
|
round: int.nonnegative().default(0),
|
|
/** index into combatants of whose turn it is */
|
|
turnIndex: int.nonnegative().default(0),
|
|
combatants: z.array(combatantSchema).default([]),
|
|
/** newest-last event feed for the fight */
|
|
log: z.array(logEntrySchema).default([]),
|
|
createdAt: z.string(),
|
|
updatedAt: z.string(),
|
|
});
|
|
|
|
export type Encounter = z.infer<typeof encounterSchema>;
|