107 lines
4.7 KiB
JavaScript
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;
|
|
}
|