Add ink integration UI and media playback

This commit is contained in:
2026-05-15 21:23:46 +02:00
parent 44dc64f830
commit f2e786d5bc
89 changed files with 6561 additions and 556 deletions
+120
View File
@@ -0,0 +1,120 @@
import path from 'path';
import { existsSync, mkdirSync, readFileSync } from 'fs';
export type EngineName = 'yaml' | 'ink' | 'zork' | string;
export interface GameMetadata {
title: string;
author?: string;
subtitle?: string;
version?: string;
copyright?: string;
}
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 === 'zork'
? '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: '',
},
};
}
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 ?? {}),
},
};
}
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/',
},
};
}