import type { Content } from '../content/schema'; import { addToAutomationQueue, clearAutomationQueue, isAutomationUnlocked } from './automation'; import type { GameState } from './game'; export const RECIPE_HEADER_V1 = 'idlegame-recipe/v1'; const ACTION_ID_RE = /^[a-z][a-z0-9_]*$/; export interface RecipeMeta { name?: string; } export function exportRecipe(state: GameState, content: Content, meta?: RecipeMeta): string { const lines = [RECIPE_HEADER_V1]; if (meta?.name) lines.push(`# name: ${meta.name}`); for (const actionId of state.automationQueue) { if (content.actionsById[actionId]) { lines.push(actionId); } } return lines.join('\n'); } export function parseRecipeLines(text: string): { name?: string; actionIds: string[] } { const trimmed = text.trim(); if (trimmed.startsWith(`${RECIPE_HEADER_V1}:`)) { const actionIds = trimmed .slice(RECIPE_HEADER_V1.length + 1) .split(',') .map((s) => s.trim()) .filter(Boolean); return { actionIds }; } const lines = trimmed.split(/\r?\n/); if (lines[0]?.trim() !== RECIPE_HEADER_V1) { throw new Error(`Invalid recipe header; expected "${RECIPE_HEADER_V1}"`); } let name: string | undefined; const actionIds: string[] = []; for (let i = 1; i < lines.length; i += 1) { const line = lines[i]?.trim() ?? ''; if (!line) continue; if (line.startsWith('# name:')) { name = line.slice('# name:'.length).trim(); continue; } if (line.startsWith('#')) continue; if (!ACTION_ID_RE.test(line)) { throw new Error(`Invalid action id "${line}"`); } actionIds.push(line); } return { name, actionIds }; } export function importRecipe(state: GameState, content: Content, text: string): { name?: string } { const { name, actionIds } = parseRecipeLines(text); const unknown = actionIds.filter((id) => !content.actionsById[id]); if (unknown.length > 0) { throw new Error(`Unknown action ids: ${unknown.join(', ')}`); } const locked = actionIds.filter((id) => !isAutomationUnlocked(state, content, id)); if (locked.length > 0) { throw new Error(`Automation locked for: ${locked.join(', ')}`); } clearAutomationQueue(state); for (const actionId of actionIds) { addToAutomationQueue(state, content, actionId); } return { name }; }