Files
scrollsmith/src/dungeonBuild.js
2026-02-21 22:19:21 -05:00

107 lines
4.7 KiB
JavaScript

export function deduplicateRoomsByName(rooms) {
if (!rooms || rooms.length === 0) return [];
const seenNames = new Set();
return rooms.filter(room => {
if (!room || !room.name) return false;
const nameLower = room.name.toLowerCase().trim();
if (seenNames.has(nameLower)) {
console.warn(`Duplicate room name detected: "${room.name}", skipping duplicate`);
return false;
}
seenNames.add(nameLower);
return true;
});
}
export function padNpcsToMinimum(parsedNpcs, coreConcepts, minCount = 4) {
const factionName = coreConcepts?.match(/Primary Faction[:\s]+([^.]+)/i)?.[1]?.trim() || 'the primary faction';
if (!parsedNpcs || parsedNpcs.length >= minCount || parsedNpcs.length === 0) return parsedNpcs || [];
const list = [...parsedNpcs];
while (list.length < minCount) {
list.push({
name: `NPC ${list.length + 1}`,
trait: `A member of ${factionName.toLowerCase()} with unknown motives.`
});
}
return list;
}
export function buildEncountersList(parsedEncounters, rooms, coreConcepts) {
const dynamicElement = coreConcepts?.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences';
const conflict = coreConcepts?.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a threat';
const fallbackNames = (roomName) => [
`${roomName} Guardian`,
`${roomName} Threat`,
`${roomName} Challenge`,
`${dynamicElement.split(' ')[0]} Manifestation`,
`${conflict.split(' ')[0]} Encounter`,
`${roomName} Hazard`
];
if (parsedEncounters.length > 0 && parsedEncounters.length < 6) {
return [
...parsedEncounters,
...Array.from({ length: 6 - parsedEncounters.length }, (_, i) => {
const roomIndex = (parsedEncounters.length + i) % rooms.length;
const roomName = rooms[roomIndex]?.name || 'Unknown Location';
return {
name: fallbackNames(roomName)[(parsedEncounters.length + i) % 6],
details: `An encounter related to ${dynamicElement.toLowerCase()} occurs here.`
};
})
];
}
if (parsedEncounters.length === 0) {
return Array.from({ length: 6 }, (_, i) => {
const roomName = rooms[i % rooms.length]?.name || 'Unknown Location';
return { name: `${roomName} Encounter`, details: `An encounter related to ${dynamicElement.toLowerCase()} occurs here.` };
});
}
return parsedEncounters;
}
export function mergeRandomEventsWithFallbacks(parsedEvents, coreConcepts, maxCount = 6) {
const dynamicElement = coreConcepts?.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences';
const conflict = (coreConcepts?.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a mysterious threat').toLowerCase();
const fallbackEvents = [
{ name: 'Environmental Shift', description: `The ${dynamicElement.toLowerCase()} causes unexpected changes in the environment.` },
{ name: 'Conflict Manifestation', description: `A sign of ${conflict} appears, requiring immediate attention.` },
{ name: 'Dungeon Shift', description: `The dungeon shifts, revealing a previously hidden passage or danger.` },
{ name: 'Faction Messenger', description: `An NPC from the primary faction appears with urgent information.` },
{ name: 'Power Fluctuation', description: `The power source fluctuates, creating temporary hazards or opportunities.` },
{ name: 'Echoes of the Past', description: `Echoes of past events manifest, providing clues or complications.` }
];
const truncated = (parsedEvents || []).slice(0, maxCount);
if (truncated.length > 0 && truncated.length < maxCount) {
return [
...truncated,
...Array.from({ length: maxCount - truncated.length }, (_, i) =>
fallbackEvents[(truncated.length + i) % fallbackEvents.length])
];
}
return truncated;
}
export function limitIntermediateRooms(rooms, maxCount = 3) {
if (rooms.length > maxCount) {
console.warn(`Expected exactly ${maxCount} intermediate locations but got ${rooms.length}, limiting to first ${maxCount}`);
}
return rooms.slice(0, maxCount);
}
export function fixRoomPlaceholderName(room) {
if (!room) return room;
if (room.name && (room.name.toLowerCase().includes('room name') || room.name.toLowerCase() === 'room name')) {
const desc = room.description || '';
const nameMatch = desc.match(/^([^:]+?)(?:\s+Description|\s*:)/i) || desc.match(/^([A-Z][^.!?]{5,40}?)(?:\s+is\s|\.)/);
if (nameMatch) {
room.name = nameMatch[1].trim().replace(/^(The|A|An)\s+/i, '').trim();
room.description = desc.replace(new RegExp(`^${nameMatch[1]}\\s*(Description|:)?\\s*`, 'i'), '').trim();
} else {
const words = desc.split(/\s+/).slice(0, 4).join(' ');
room.name = words.replace(/^(The|A|An)\s+/i, '').trim();
}
}
return room;
}