import { findRoomTemplateById, findRoomTemplateForLookup, findTableByCode, } from "@/data/contentHelpers"; import type { ContentPack, ExitType, RoomClass } from "@/types/content"; import type { DungeonLevelState, RoomExitState, RoomState } from "@/types/state"; import { lookupTable, type TableLookupResult } from "./tables"; import type { DiceRoller } from "./dice"; export type RoomGenerationOptions = { content: ContentPack; roomId: string; level: number; roomTemplateId?: string; roomTableCode?: string; roomClass?: RoomClass; position?: { x: number; y: number; }; roller?: DiceRoller; }; export type RoomGenerationResult = { room: RoomState; lookup?: TableLookupResult; templateSource: "direct-template" | "table-lookup"; }; const DEFAULT_ROOM_DIMENSIONS: Record = { start: { width: 4, height: 4 }, normal: { width: 4, height: 4 }, small: { width: 2, height: 3 }, large: { width: 6, height: 6 }, special: { width: 4, height: 4 }, stairs: { width: 4, height: 4 }, }; const DEFAULT_DIRECTIONS = ["north", "east", "south", "west"] as const; function inferExitType(exitHint?: string): ExitType { const normalized = exitHint?.toLowerCase() ?? ""; if (normalized.includes("reinforced")) { return "locked"; } if (normalized.includes("curtain")) { return "open"; } if (normalized.includes("archway")) { return "open"; } if (normalized.includes("wooden")) { return "door"; } return "door"; } function inferExitCount(roomClass: RoomClass, exitHint?: string) { const normalized = exitHint?.toLowerCase() ?? ""; if (normalized.includes("random")) { return roomClass === "large" ? 3 : 2; } if (normalized.includes("archway")) { return roomClass === "large" ? 3 : 2; } if (roomClass === "small") { return 1; } if (roomClass === "large") { return 3; } return 2; } function createExits( roomId: string, roomClass: RoomClass, exitHint?: string, ): RoomExitState[] { const exitCount = inferExitCount(roomClass, exitHint); const exitType = inferExitType(exitHint); return DEFAULT_DIRECTIONS.slice(0, exitCount).map((direction, index) => ({ id: `${roomId}.exit.${index + 1}`, direction, exitType, discovered: roomClass === "start", traversable: true, })); } export function createRoomStateFromTemplate( content: ContentPack, roomId: string, level: number, roomTemplateId: string, position = { x: 0, y: 0 }, ): RoomState { const template = findRoomTemplateById(content, roomTemplateId); const dimensions = template.dimensions ?? DEFAULT_ROOM_DIMENSIONS[template.roomClass]; const exits = template.exits?.map((exit, index) => ({ id: `${roomId}.exit.${index + 1}`, direction: exit.direction ?? DEFAULT_DIRECTIONS[index] ?? "north", exitType: exit.exitType, discovered: template.roomClass === "start", traversable: exit.exitType !== "locked", destinationLevel: exit.destinationLevel, })) ?? createExits(roomId, template.roomClass, template.exitHint); return { id: roomId, level, templateId: template.id, position, dimensions, roomClass: template.roomClass, exits, discovery: { generated: true, entered: template.roomClass === "start", cleared: false, searched: false, }, encounter: undefined, objects: [], notes: [template.text ?? template.title].filter(Boolean), flags: [ `table:${template.tableCode}`, `entry:${template.tableEntryKey}`, ...(template.unique ? ["unique"] : []), ...template.tags, ], }; } export function generateRoomFromTable( options: RoomGenerationOptions, ): RoomGenerationResult { if (!options.roomTableCode) { throw new Error("Room table code is required for table-based room generation."); } const table = findTableByCode(options.content, options.roomTableCode); const lookup = lookupTable(table, { roller: options.roller }); const template = findRoomTemplateForLookup(options.content, lookup); const room = createRoomStateFromTemplate( options.content, options.roomId, options.level, template.id, options.position, ); return { room, lookup, templateSource: "table-lookup", }; } export function generateLevel1StartRoom( content: ContentPack, roomId = "room.level1.start", ): RoomGenerationResult { const room = createRoomStateFromTemplate(content, roomId, 1, "room.level1.entry", { x: 0, y: 0, }); return { room, templateSource: "direct-template", }; } export function createLevelShell(level: number): DungeonLevelState { return { level, themeName: level === 1 ? "The Entry" : undefined, rooms: {}, discoveredRoomOrder: [], secretDoorUsed: false, exhaustedExitSearch: false, }; }