Files
ai.interactive.fiction/src/config/game-config.ts
T

126 lines
3.3 KiB
TypeScript

import path from 'path';
import { existsSync, mkdirSync, readFileSync } from 'fs';
export type EngineName = 'yaml' | 'ink' | 'zcode' | string;
export interface GameMetadata {
title: string;
author?: string;
subtitle?: string;
version?: string;
copyright?: string;
language?: string;
bookPageCount?: number;
pageReserve?: number;
}
export interface GamePaths {
mainGameFile: string;
inkSource?: string;
inkCompiled?: string;
promptDir?: string;
music?: string;
sfx?: string;
images?: string;
[key: string]: string | undefined;
}
export interface GameEngineConfig {
engine: EngineName;
locale: 'en_US' | 'de_DE' | string;
paths: GamePaths;
metadata: GameMetadata;
}
const PROJECT_ROOT = path.resolve(__dirname, '../..');
function fallbackConfig(engine: EngineName): GameEngineConfig {
return {
engine,
locale: 'en_US',
paths: {
mainGameFile:
engine === 'ink'
? 'data/ink/story.ink.json'
: engine === 'zcode'
? 'data/z-code/zork1.bin'
: 'data/worlds/example_world.yml',
music: 'public/music',
sfx: 'public/sounds',
images: 'public/images',
},
metadata: {
title: 'AI Interactive Fiction',
author: 'Generative AI',
subtitle: 'An open-world text adventure',
version: '1.0.0',
copyright: '',
language: 'en_US',
},
};
}
export function projectPath(relativeOrAbsolutePath: string): string {
return path.isAbsolute(relativeOrAbsolutePath)
? relativeOrAbsolutePath
: path.resolve(PROJECT_ROOT, relativeOrAbsolutePath);
}
export function loadGameConfig(configPath: string, engine: EngineName): GameEngineConfig {
const absolutePath = projectPath(configPath);
if (!existsSync(absolutePath)) {
console.warn(`[config] Missing ${absolutePath}; using ${engine} defaults.`);
return fallbackConfig(engine);
}
const parsed = JSON.parse(readFileSync(absolutePath, 'utf8')) as Partial<GameEngineConfig>;
const fallback = fallbackConfig(engine);
return {
engine: parsed.engine ?? fallback.engine,
locale: parsed.locale ?? fallback.locale,
paths: {
...fallback.paths,
...(parsed.paths ?? {}),
},
metadata: {
...fallback.metadata,
...(parsed.metadata ?? {}),
language: parsed.metadata?.language ?? parsed.locale ?? fallback.metadata.language,
},
};
}
export function ensureConfiguredAssetDirectories(config: GameEngineConfig): void {
const directories = [
config.paths.music,
config.paths.sfx,
config.paths.images,
config.paths.inkSource ? path.dirname(config.paths.inkSource) : undefined,
config.paths.inkCompiled ? path.dirname(config.paths.inkCompiled) : undefined,
config.paths.mainGameFile ? path.dirname(config.paths.mainGameFile) : undefined,
config.paths.promptDir,
];
for (const directory of directories) {
if (!directory) continue;
const absolutePath = projectPath(directory);
if (!existsSync(absolutePath)) {
mkdirSync(absolutePath, { recursive: true });
}
}
}
export function clientGameConfig(config: GameEngineConfig) {
return {
engine: config.engine,
locale: config.locale,
metadata: config.metadata,
assets: {
music: '/music/',
sfx: '/sounds/',
sounds: '/sounds/',
images: '/images/',
},
};
}