80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
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 };
|
|
}
|