import { z } from "zod"; import { logEntrySchema, rollResultSchema, ruleEffectSchema } from "./rules"; export const statusInstanceSchema = z.object({ id: z.string().min(1), source: z.string().optional(), duration: z.enum(["round", "combat", "room", "run", "permanent"]).optional(), value: z.number().optional(), notes: z.string().optional(), }); export const inventoryEntrySchema = z.object({ definitionId: z.string().min(1), quantity: z.number().int().nonnegative(), identified: z.boolean().optional(), charges: z.number().int().optional(), notes: z.string().optional(), }); export const inventoryStateSchema = z.object({ carried: z.array(inventoryEntrySchema), equipped: z.array(inventoryEntrySchema), stored: z.array(inventoryEntrySchema), currency: z.object({ gold: z.number().int().nonnegative(), }), rationCount: z.number().int().nonnegative(), lightSources: z.array(inventoryEntrySchema), }); export const adventurerStateSchema = z.object({ id: z.string().min(1), name: z.string().min(1), level: z.number().int().positive(), xp: z.number().int().nonnegative(), hp: z.object({ current: z.number().int(), max: z.number().int().positive(), base: z.number().int().positive(), }), stats: z.object({ shift: z.number().int(), discipline: z.number().int(), precision: z.number().int(), }), weaponId: z.string().min(1), manoeuvreIds: z.array(z.string()), armourId: z.string().optional(), favour: z.record(z.string(), z.number().int()), statuses: z.array(statusInstanceSchema), inventory: inventoryStateSchema, progressionFlags: z.array(z.string()), }); export const townStateSchema = z.object({ visits: z.number().int().nonnegative(), knownServices: z.array(z.string()), stash: z.array(inventoryEntrySchema), pendingSales: z.array(inventoryEntrySchema), serviceFlags: z.array(z.string()), }); export const questStateSchema = z.object({ id: z.string().min(1), title: z.string().min(1), status: z.enum(["available", "active", "completed", "failed"]), progressFlags: z.array(z.string()), rewardText: z.string().optional(), }); export const runSummarySchema = z.object({ runId: z.string().min(1), startedAt: z.string().min(1), endedAt: z.string().optional(), deepestLevel: z.number().int().positive(), roomsVisited: z.number().int().nonnegative(), creaturesDefeated: z.array(z.string()), xpGained: z.number().int().nonnegative(), treasureValue: z.number().int().nonnegative(), outcome: z.enum(["escaped", "defeated", "saved-in-progress"]), }); export const campaignStateSchema = z.object({ id: z.string().min(1), createdAt: z.string().min(1), updatedAt: z.string().min(1), rulesVersion: z.string().min(1), contentVersion: z.string().min(1), adventurer: adventurerStateSchema, unlockedLevels: z.array(z.number().int().positive()), completedLevels: z.array(z.number().int().positive()), townState: townStateSchema, questState: z.array(questStateSchema), campaignFlags: z.array(z.string()), runHistory: z.array(runSummarySchema), }); export const roomExitStateSchema = z.object({ id: z.string().min(1), direction: z.enum(["north", "east", "south", "west"]), exitType: z.enum(["open", "door", "locked", "secret", "shaft", "stairs"]), discovered: z.boolean(), traversable: z.boolean(), leadsToRoomId: z.string().optional(), destinationLevel: z.number().int().optional(), }); export const encounterStateSchema = z.object({ id: z.string().min(1), sourceTableCode: z.string().optional(), creatureIds: z.array(z.string()), resolved: z.boolean(), surprise: z.boolean().optional(), rewardPending: z.boolean().optional(), }); export const roomObjectStateSchema = z.object({ id: z.string().min(1), objectType: z.enum(["container", "altar", "corpse", "hazard", "feature", "quest"]), sourceTableCode: z.string().optional(), interacted: z.boolean(), hidden: z.boolean().optional(), effects: z.array(ruleEffectSchema).optional(), notes: z.string().optional(), }); export const roomStateSchema = z.object({ id: z.string().min(1), level: z.number().int().positive(), templateId: z.string().optional(), position: z.object({ x: z.number().int(), y: z.number().int(), }), dimensions: z.object({ width: z.number().int().positive(), height: z.number().int().positive(), }), roomClass: z.enum(["normal", "small", "large", "special", "start", "stairs"]), exits: z.array(roomExitStateSchema), discovery: z.object({ generated: z.boolean(), entered: z.boolean(), cleared: z.boolean(), searched: z.boolean(), }), encounter: encounterStateSchema.optional(), objects: z.array(roomObjectStateSchema), notes: z.array(z.string()), flags: z.array(z.string()), }); export const dungeonLevelStateSchema = z.object({ level: z.number().int().positive(), themeName: z.string().optional(), rooms: z.record(z.string(), roomStateSchema), discoveredRoomOrder: z.array(z.string()), stairsUpRoomId: z.string().optional(), stairsDownRoomId: z.string().optional(), secretDoorUsed: z.boolean().optional(), exhaustedExitSearch: z.boolean().optional(), }); export const dungeonStateSchema = z.object({ levels: z.record(z.string(), dungeonLevelStateSchema), revealedPercentByLevel: z.record(z.string(), z.number()), globalFlags: z.array(z.string()), }); export const combatantStateSchema = z.object({ id: z.string().min(1), name: z.string().min(1), sourceDefinitionId: z.string().optional(), hpCurrent: z.number().int(), hpMax: z.number().int().positive(), shift: z.number().int(), discipline: z.number().int(), precision: z.number().int(), armourValue: z.number().int().optional(), statuses: z.array(statusInstanceSchema), traits: z.array(z.string()), }); export const interruptStateSchema = z.object({ source: z.enum(["player", "enemy"]), trigger: z.string().min(1), effectText: z.string().min(1), resolved: z.boolean(), }); export const combatStateSchema = z.object({ id: z.string().min(1), round: z.number().int().positive(), actingSide: z.enum(["player", "enemy"]), fatigueDie: z.number().int().optional(), player: combatantStateSchema, enemies: z.array(combatantStateSchema), selectedManoeuvreId: z.string().optional(), lastRoll: rollResultSchema.optional(), pendingInterrupt: interruptStateSchema.optional(), combatLog: z.array(logEntrySchema), }); export const runStateSchema = z.object({ id: z.string().min(1), campaignId: z.string().min(1), status: z.enum(["active", "paused", "completed", "failed"]), startedAt: z.string().min(1), currentLevel: z.number().int().positive(), currentRoomId: z.string().optional(), dungeon: dungeonStateSchema, adventurerSnapshot: adventurerStateSchema, activeCombat: combatStateSchema.optional(), log: z.array(logEntrySchema), pendingEffects: z.array(ruleEffectSchema), });