import { z } from 'zod'; import type { Character } from '@/lib/schemas'; import type { CampaignContext, CreatureCandidate } from './context'; export const balanceSuggestionSchema = z.object({ reasoning: z.string(), add: z.array(z.object({ name: z.string(), count: z.number().int().min(1).max(8), ref: z.string().optional(), })), /** Monsters to take out when the fight is over-tuned (already at/above target). */ remove: z.array(z.object({ name: z.string(), count: z.number().int().min(1).max(8), })).optional(), targetDifficulty: z.string(), }); export type BalanceSuggestion = z.infer; function partyLine(ctx: CampaignContext): string { return ctx.party.map((p) => `${p.name} (level ${p.level} ${p.className})`).join(', ') || 'no PCs recorded'; } /** * Prompt for rebalancing the current encounter. The system message leads with the * grounding constraint and restricts choices to the provided candidate list. */ export function buildBalancePrompt( ctx: CampaignContext, current: { difficulty: string; targetDifficulty: string; candidates: CreatureCandidate[] }, ): { system: string; user: string } { const system = `${ctx.systemConstraint}\n\n` + `You are an encounter-balancing assistant for a ${ctx.systemLabel} game master. ` + `Recommend which creatures to ADD to the current encounter to bring it to roughly "${current.targetDifficulty}" difficulty. ` + `You MUST choose only from the provided candidate creatures (by exact name). Do not invent creatures or use any from another system. ` + `Prefer 1–3 distinct creatures with sensible counts. Keep the reasoning to one or two sentences.`; const candidateList = current.candidates .map((c) => `- ${c.name} (rating ${c.rating}${c.hp ? `, hp ${c.hp}` : ''}${c.ac ? `, ac ${c.ac}` : ''})`) .join('\n'); const user = `Party: ${partyLine(ctx)}.\n` + `Current encounter difficulty: ${current.difficulty}.\n` + `Target difficulty: ${current.targetDifficulty}.\n\n` + `Candidate creatures (choose only from these):\n${candidateList}\n\n` + `Respond with the creatures to add (name + count), a short reasoning, and the targetDifficulty.`; return { system, user }; } // ---------------- Level-up advisor ---------------- export const buildRoutesSchema = z.object({ routes: z.array(z.object({ title: z.string(), summary: z.string(), playstyle: z.string(), })).min(3).max(4), }); export type BuildRoute = z.infer['routes'][number]; export const buildStepsSchema = z.object({ steps: z.array(z.object({ label: z.string(), detail: z.string() })).min(1), }); export type BuildStep = z.infer['steps'][number]; function charLine(c: Character): string { const abilities = Object.entries(c.abilities).map(([k, v]) => `${k.toUpperCase()} ${v}`).join(', '); return `${c.name}, a level ${c.level} ${c.className || 'adventurer'} (${abilities})`; } export function buildLevelUpRoutesPrompt(ctx: CampaignContext, c: Character, nextLevel: number): { system: string; user: string } { const system = `${ctx.systemConstraint}\n\n` + `You are a character-building advisor for ${ctx.systemLabel}. Propose 4 distinct build routes for the next level. ` + `Each route needs a short title, a one-sentence summary, and a one-phrase playstyle. ` + `Use only ${ctx.systemLabel} mechanics, feats, and class options — never another system's.`; const user = `Character: ${charLine(c)}. Leveling up to ${nextLevel}. Suggest 4 build routes.`; return { system, user }; } export function buildLevelUpStepsPrompt(ctx: CampaignContext, c: Character, nextLevel: number, chosenRoute: string): { system: string; user: string } { const system = `${ctx.systemConstraint}\n\n` + `You are a character-building advisor for ${ctx.systemLabel}. Give concrete, ordered steps to pursue the chosen build route at this level. ` + `Each step has a short label and a one-sentence detail. Reference only ${ctx.systemLabel} options (feats, ability boosts, spells, skill increases as appropriate). Keep to 3–5 steps.`; const user = `Character: ${charLine(c)}. Leveling up to ${nextLevel}. Chosen route: "${chosenRoute}". How should they achieve it?`; return { system, user }; }