dd694477b2
Crash fix: - restoreBackup (file + cloud pull) now validates each row through its Zod schema, backfilling new fields — an older backup no longer lands characters missing feats/concentration/etc and crashing the sheet. + test. pf2e parity (closing feature gaps vs 5e): - +9 missing classes (Animist, Exemplar, Gunslinger, Inventor, Kineticist, Magus, Psychic, Summoner, Thaumaturge) with data + class tips. - the wizard now enriches pf2e classes with their caster ability, so pf2e spellcasters get the Spells step + "Caster" tag like 5e. - editable Perception rank and spellcasting proficiency on the pf2e sheet (fixes wrong initiative / spell DC at higher levels); rank-10 spell slots. - pf2e monster resistances/weaknesses/immunities now apply in combat (flat amounts: resistance subtracts, weakness adds, 'all' matches any type). + tests. Glyph purge (finishing the emoji→Lucide migration): replaced ~40 leftover text-glyphs (✕ − + ✦ 🤫 ⬆ ⬇ ☑ ☐ ▶ ◀ ‹ › ★ ↻ ▸ ↑ ●) with Lucide icons across Modal (every dialog), the sheet sections, dice, settings, player panels, world pages, map editor, roll tray, and session UI. Gate: 293 unit + e2e green; tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
3.0 KiB
TypeScript
77 lines
3.0 KiB
TypeScript
import { z } from 'zod';
|
|
import { conditionSchema, hpSchema, int } from './common';
|
|
|
|
export const combatantKindSchema = z.enum(['pc', 'npc', 'monster']);
|
|
|
|
/** Damage/condition defenses snapshotted from the bestiary at add time, so the
|
|
* combat engine can apply resistances without an async compendium load. */
|
|
const flatDefenseSchema = z.object({ type: z.string(), amount: int.nonnegative() });
|
|
export const damageDefensesSchema = z.object({
|
|
// 5e: halve / double / negate by type
|
|
resist: z.array(z.string()).default([]),
|
|
immune: z.array(z.string()).default([]),
|
|
vulnerable: z.array(z.string()).default([]),
|
|
conditionImmune: z.array(z.string()).default([]),
|
|
notes: z.array(z.string()).default([]),
|
|
// pf2e: flat resistance subtracts, weakness adds (type 'all' applies to any)
|
|
resistFlat: z.array(flatDefenseSchema).default([]),
|
|
weakness: z.array(flatDefenseSchema).default([]),
|
|
});
|
|
export type CombatantDamageDefenses = z.infer<typeof damageDefensesSchema>;
|
|
|
|
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(''),
|
|
/** damage resistances/immunities snapshotted from the bestiary, if known */
|
|
damageDefenses: damageDefensesSchema.optional(),
|
|
/** 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([]),
|
|
/** PC levels captured when combat started — lets the assistant rate this
|
|
* encounter's difficulty against the party as it was, not as it is now. */
|
|
partyLevelsSnapshot: z.array(int.positive()).optional(),
|
|
/** how the fight resolved, if recorded */
|
|
outcome: z.enum(['tpk', 'party-victory', 'fled', 'unknown']).optional(),
|
|
createdAt: z.string(),
|
|
updatedAt: z.string(),
|
|
});
|
|
|
|
export type Encounter = z.infer<typeof encounterSchema>;
|