Skip to main content
Glama
scenario-generation.ts8.78 kB
import * as fs from "fs/promises"; import * as path from "path"; import { createMap, addEvent, addEventCommand, addActor, addClass, addSkill, addItem } from "./game-creation-tools.js"; import { Validator, APIHelper, Logger, createErrorResponse, validateProjectPath, validateGeminiAPIKey } from "./error-handling.js"; export interface ScenarioGenerationRequest { projectPath: string; theme: string; style: string; length: "short" | "medium" | "long"; apiKey?: string; } export interface GeneratedScenario { title: string; synopsis: string; maps: Array<{ id: number; name: string; description: string; width: number; height: number; }>; characters: Array<{ id: number; name: string; classId: number; className: string; description: string; }>; events: Array<{ mapId: number; eventId: number; name: string; x: number; y: number; dialogues: string[]; }>; items: Array<{ id: number; name: string; description: string; type: string; }>; skills: Array<{ id: number; name: string; description: string; mpCost: number; }>; } export async function generateScenarioWithGemini(request: ScenarioGenerationRequest): Promise<{ success: boolean; scenario?: GeneratedScenario; error?: string }> { try { // Validate inputs await validateProjectPath(request.projectPath); Validator.requireString(request.theme, "theme"); Validator.requireString(request.style, "style"); Validator.requireEnum(request.length, "length", ["short", "medium", "long"] as const); const apiKey = validateGeminiAPIKey(request.apiKey); await Logger.info("Generating scenario with Gemini", { projectPath: request.projectPath, theme: request.theme, style: request.style, length: request.length }); const prompt = buildScenarioPrompt(request); const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`; const response = await APIHelper.fetchWithRetry( url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 1.0, topK: 40, topP: 0.95, maxOutputTokens: 8192, responseMimeType: "application/json" } }) }, 3 ); const data = await response.json(); const scenarioText = data.candidates?.[0]?.content?.parts?.[0]?.text; if (!scenarioText) { return { success: false, error: "No scenario generated" }; } const scenario = JSON.parse(scenarioText) as GeneratedScenario; await Logger.info("Scenario generated successfully", { mapsCount: scenario.maps?.length, charactersCount: scenario.characters?.length, eventsCount: scenario.events?.length }); return { success: true, scenario }; } catch (error) { await Logger.error("Failed to generate scenario", { request, error }); return createErrorResponse(error); } } function buildScenarioPrompt(request: ScenarioGenerationRequest): string { const lengthGuide = { short: "3-5 maps, 2-3 characters, 5-8 events", medium: "5-10 maps, 4-6 characters, 10-15 events", long: "10-20 maps, 6-10 characters, 20-30 events" }; return `Generate a complete RPG game scenario in JSON format for RPG Maker MZ. Theme: ${request.theme} Style: ${request.style} Length: ${request.length} (${lengthGuide[request.length]}) Return a JSON object with the following structure: { "title": "Game title", "synopsis": "Brief story synopsis", "maps": [ { "id": 1, "name": "Map name", "description": "Map description", "width": 17, "height": 13 } ], "characters": [ { "id": 1, "name": "Character name", "classId": 1, "className": "Class name", "description": "Character description" } ], "events": [ { "mapId": 1, "eventId": 1, "name": "Event name", "x": 10, "y": 10, "dialogues": ["Line 1", "Line 2"] } ], "items": [ { "id": 1, "name": "Item name", "description": "Item description", "type": "consumable" } ], "skills": [ { "id": 1, "name": "Skill name", "description": "Skill description", "mpCost": 10 } ] } Create a cohesive, engaging RPG scenario with: - A clear main quest and story arc - Interesting characters with unique personalities - Meaningful dialogues that advance the plot - Appropriate maps for different story beats - Items and skills that fit the theme - Logical progression and pacing IMPORTANT: Return ONLY valid JSON, no additional text.`; } export async function implementScenario(projectPath: string, scenario: GeneratedScenario): Promise<{ success: boolean; details?: any; error?: string }> { try { const results: any = { maps: [], characters: [], events: [], items: [], skills: [] }; // Create classes first const createdClasses = new Set<number>(); for (const char of scenario.characters) { if (!createdClasses.has(char.classId)) { await addClass(projectPath, char.classId, char.className); createdClasses.add(char.classId); } } // Create characters/actors for (const char of scenario.characters) { const result = await addActor(projectPath, char.id, char.name); results.characters.push(result); } // Create maps for (const map of scenario.maps) { const result = await createMap(projectPath, map.id, map.name, map.width, map.height); results.maps.push(result); } // Create items for (const item of scenario.items) { const result = await addItem(projectPath, item.id, item.name); results.items.push(result); } // Create skills for (const skill of scenario.skills) { const result = await addSkill(projectPath, skill.id, skill.name); results.skills.push(result); } // Create events with dialogues for (const event of scenario.events) { // Add event await addEvent(projectPath, event.mapId, event.eventId, event.name, event.x, event.y); // Add dialogue commands for (let i = 0; i < event.dialogues.length; i++) { const dialogue = event.dialogues[i]; if (i === 0) { // First dialogue uses Show Text command (101) await addEventCommand(projectPath, event.mapId, event.eventId, 0, { code: 101, indent: 0, parameters: ["", 0, 0, 2] }); } // Add dialogue text (401) await addEventCommand(projectPath, event.mapId, event.eventId, 0, { code: 401, indent: 0, parameters: [dialogue] }); } results.events.push({ eventId: event.eventId, mapId: event.mapId }); } return { success: true, details: results }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } export async function generateAndImplementScenario(request: ScenarioGenerationRequest): Promise<{ success: boolean; scenario?: GeneratedScenario; implementation?: any; error?: string }> { // Generate scenario const genResult = await generateScenarioWithGemini(request); if (!genResult.success || !genResult.scenario) { return { success: false, error: genResult.error }; } // Implement scenario const implResult = await implementScenario(request.projectPath, genResult.scenario); if (!implResult.success) { return { success: false, scenario: genResult.scenario, error: `Scenario generated but implementation failed: ${implResult.error}` }; } // Save scenario metadata try { const scenarioFile = path.join(request.projectPath, "scenario.json"); await fs.writeFile(scenarioFile, JSON.stringify(genResult.scenario, null, 2), "utf-8"); } catch { // Non-critical error } return { success: true, scenario: genResult.scenario, implementation: implResult.details }; } // Generate multiple scenario variations export async function generateScenarioVariations( request: ScenarioGenerationRequest, count: number ): Promise<Array<{ success: boolean; scenario?: GeneratedScenario; error?: string }>> { const results = []; for (let i = 0; i < count; i++) { const result = await generateScenarioWithGemini(request); results.push(result); } return results; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ShunsukeHayashi/rpgmaker-mz-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server