// Dragon Code V2.6 Decoder - Converts parsed data to human-readable text function decodeDragonCode(parsed) { const result = { species: [], physical: [], appearance: [], abilities: [], life: [], personality: [], other: [] }; // Decode species if (parsed.species) { const speciesItems = decodeSpecies(parsed.species); result.species.push(...speciesItems); } // Decode tags const tags = parsed.tags; // Physical characteristics if (tags.gender) { result.physical.push(decodeGender(tags.gender)); } if (tags.length) { result.physical.push(decodeLength(tags.length)); } if (tags.width) { result.physical.push(decodeSimpleTag('Width', tags.width, TAG_DESCRIPTIONS.width)); } if (tags.weight) { result.physical.push(decodeSimpleTag('Weight', tags.weight, TAG_DESCRIPTIONS.weight)); } if (tags.age) { result.physical.push(decodeSimpleTag('Age', tags.age, TAG_DESCRIPTIONS.age)); } // Appearance if (tags.appendages) { result.appearance.push(decodeAppendages(tags.appendages)); } if (tags.skinType) { result.appearance.push(decodeSkinType(tags.skinType)); } if (tags.color) { result.appearance.push(...decodeColor(tags.color)); } // Abilities if (tags.breath) { result.abilities.push(decodeBreath(tags.breath)); } if (tags.magic) { result.abilities.push(decodeSimpleTag('Magic', tags.magic, TAG_DESCRIPTIONS.magic)); } if (tags.psyPower) { result.abilities.push(decodeSimpleTag('Psy-Power', tags.psyPower, TAG_DESCRIPTIONS.psyPower)); } // Life & Relationships if (tags.nativeLand) { result.life.push(decodeSimpleTag('Native-Land', tags.nativeLand, TAG_DESCRIPTIONS.nativeLand)); } if (tags.mating) { result.life.push(decodeMating(tags.mating)); } if (tags.offspring) { result.life.push(decodeOffspring(tags.offspring)); } if (tags.hoard) { result.life.push(decodeSimpleTag('Hoard', tags.hoard, TAG_DESCRIPTIONS.hoard)); } if (tags.money) { result.life.push(decodeMoney(tags.money)); } if (tags.diet) { result.life.push(decodeDiet(tags.diet)); } // Personality & Behavior if (tags.reality) { result.personality.push(decodeSimpleTag('Reality', tags.reality, TAG_DESCRIPTIONS.reality)); } if (tags.activity) { result.personality.push(decodeSimpleTag('Activity', tags.activity, TAG_DESCRIPTIONS.activity)); } if (tags.humor) { result.personality.push(decodeSimpleTag('Humor', tags.humor, TAG_DESCRIPTIONS.humor)); } if (tags.social) { result.personality.push(decodeSimpleTag('Social', tags.social, TAG_DESCRIPTIONS.social)); } if (tags.irritability) { result.personality.push(decodeSimpleTag('Irritability', tags.irritability, TAG_DESCRIPTIONS.irritability)); } if (tags.emotion) { result.personality.push(decodeSimpleTag('Emotion', tags.emotion, TAG_DESCRIPTIONS.emotion)); } // Other traits if (tags.ubiquity) { result.other.push(decodeSimpleTag('Ubiquity', tags.ubiquity, TAG_DESCRIPTIONS.ubiquity)); } if (tags.technology) { result.other.push(decodeTechnology(tags.technology)); } if (tags.dragonFriend) { result.other.push(decodeSimpleTag('Dragon-Friend', tags.dragonFriend, TAG_DESCRIPTIONS.dragonFriend)); } return result; } // Decode species with all special cases function decodeSpecies(species) { const items = []; if (species.type === 'custom') { items.push({ label: 'Species', value: species.value, tag: species.raw }); return items; } if (species.type === 'shapechanger') { const forms = species.forms.map(form => { if (typeof form === 'object') { const speciesName = resolveSpeciesCode(form.species); if (form.modifier) { return `${speciesName} (${form.modifier})`; } return speciesName; } return resolveSpeciesCode(form); }); items.push({ label: 'Species', value: `I change between ${forms.join(' and ')} form`, tag: species.raw }); return items; } if (species.type === 'cross') { const speciesNames = species.species.map(s => resolveSpeciesCode(s) || s); items.push({ label: 'Species', value: `A cross between ${speciesNames.join(' and ')}`, tag: species.raw }); return items; } if (species.type === 'trapped') { const trueName = resolveSpeciesCode(species.trueForm); const trappedName = resolveSpeciesCode(species.trappedIn); items.push({ label: 'Species', value: `I am a ${trueName}, trapped in ${trappedName} form`, tag: species.raw, type: 'warning' }); return items; } if (species.type === 'shaped') { const trueName = resolveSpeciesCode(species.trueForm); const shapedName = resolveSpeciesCode(species.shapedAs); items.push({ label: 'Species', value: `I am a ${trueName}, currently shaped as ${shapedName}`, tag: species.raw }); return items; } // Simple species const speciesName = resolveSpeciesCode(species.value); if (speciesName) { items.push({ label: 'Species', value: speciesName, tag: species.raw }); } return items; } // Decode gender function decodeGender(gender) { if (gender.type === 'custom') { return { label: 'Gender', value: gender.value, tag: gender.raw }; } const genderValue = gender.value.toLowerCase(); const description = TAG_DESCRIPTIONS.gender[genderValue] || gender.value; let modifierText = ''; if (gender.modifiers.question) { modifierText = ' (not telling)'; } else if (gender.modifiers.tilde) { modifierText = ' (variable)'; } return { label: 'Gender', value: description + modifierText, tag: gender.raw }; } // Decode length function decodeLength(length) { let value = ''; if (length.value && length.unit) { // Quantitative length (L10m, L10m4t, etc.) const unitName = TAG_DESCRIPTIONS.length.units[length.unit] || length.unit; value = `${length.value} ${unitName}`; if (length.dimensions && length.dimensions.length > 0) { const modParts = length.dimensions.map(m => { const dimName = TAG_DESCRIPTIONS.length.dimensions[m]; // Extract number before modifier if present const numMatch = length.raw.match(new RegExp(`(\\d+)${m}`)); if (numMatch) { return `${numMatch[1]}${unitName} ${dimName}`; } return dimName; }); value += ` (with ${modParts.join(', ')})`; } } else if (length.modifiers) { // Qualitative length (L+++, L-, L, etc.) const modifierKey = getModifierString(length.modifiers); value = TAG_DESCRIPTIONS.length[modifierKey] || 'Normal (Draco-sized)'; } else { // Fallback value = 'Normal (Draco-sized)'; } return { label: 'Length', value: value, tag: length.raw }; } // Decode simple tags with +/- modifiers function decodeSimpleTag(label, tag, descriptions) { const modifierKey = getModifierString(tag.modifiers); let value = descriptions[modifierKey] || descriptions[''] || 'Normal'; const type = tag.modifiers.question ? 'warning' : 'normal'; return { label: label, value: value, tag: tag.raw, type: type }; } // Decode appendages function decodeAppendages(appendages) { const parts = []; appendages.parts.forEach(part => { const typeName = TAG_DESCRIPTIONS.appendages.types[part.type] || part.type; let description = typeName; // Handle count if (part.count) { description = `${part.count} ${typeName}`; } else if (part.modifiers.plus > 0) { description = `multiple ${typeName}`; } // Handle modifiers if (part.modifiers.caret) { description = `retractable ${description}`; } if (part.modifiers.minus > 0) { description = `vestigial ${description}`; } if (part.modifiers.exclaim) { description = `unusual ${description}`; } // Add pair for common paired appendages if (['a', 'l', 'w', 'v'].includes(part.type) && !part.count && part.modifiers.plus === 0) { description = `pair of ${typeName}`; } parts.push(description); }); return { label: 'Appendages', value: parts.join(', '), tag: appendages.raw }; } // Decode skin type function decodeSkinType(skinType) { let value = ''; // Main skin type if (skinType.mainType) { const mainTypeName = TAG_DESCRIPTIONS.skinType[skinType.mainType] || skinType.mainType; value = mainTypeName; } else { value = 'Unknown'; } // Body part modifiers (e.g., ",ak" = arms with skin, ",bm" = belly with metal) if (skinType.bodyPartTypes.length > 0) { const partDescriptions = skinType.bodyPartTypes.map(item => { const partName = TAG_DESCRIPTIONS.skinType.bodyParts[item.part] || item.part; const typeName = TAG_DESCRIPTIONS.skinType[item.type] || item.type; return `${typeName} on ${partName}`; }); value += `, with ${partDescriptions.join(', ')}`; } const hasQuestion = skinType.modifiers.question; return { label: 'Skin Type', value: value, tag: skinType.raw, type: hasQuestion ? 'warning' : 'normal' }; } // Decode color (complex) function decodeColor(color) { const items = []; // Build a comprehensive description let fullDescription = ''; const colorDescriptions = []; color.colors.forEach((colorPart, index) => { let description = ''; // Base color const baseName = TAG_DESCRIPTIONS.color.bases[colorPart.base] || colorPart.base; // Intensity modifiers const intensityParts = []; let darknessLevel = 0; let brightnessLevel = 0; colorPart.intensity.forEach(mod => { if (mod === '-') darknessLevel++; else if (mod === '+') brightnessLevel++; else { const modName = TAG_DESCRIPTIONS.color.intensity[mod]; if (modName) { intensityParts.push(modName); } } }); // Add darkness/brightness descriptions if (darknessLevel === 1) intensityParts.unshift('dark'); else if (darknessLevel === 2) intensityParts.unshift('very dark'); else if (darknessLevel >= 3) intensityParts.unshift('extremely dark'); if (brightnessLevel === 1) intensityParts.unshift('light'); else if (brightnessLevel === 2) intensityParts.unshift('very light'); else if (brightnessLevel >= 3) intensityParts.unshift('extremely light'); // Build color description if (intensityParts.length > 0) { description = `${intensityParts.join(', ')} ${baseName}`; } else { description = baseName; } // Pattern modifiers const patternParts = []; colorPart.patterns.forEach(pat => { const patternName = TAG_DESCRIPTIONS.color.patterns[pat]; if (patternName) { patternParts.push(patternName); } }); if (patternParts.length > 0) { description += ` (${patternParts.join(', ')})`; } // Body parts if (colorPart.bodyParts.length > 0) { const parts = colorPart.bodyParts.map(p => TAG_DESCRIPTIONS.skinType.bodyParts[p] || p ); description += ` on ${parts.join(', ')}`; } colorDescriptions.push(description); }); // Combine all colors if (colorDescriptions.length === 1) { fullDescription = colorDescriptions[0]; } else if (colorDescriptions.length === 2) { fullDescription = `${colorDescriptions[0]}, with ${colorDescriptions[1]}`; } else { const last = colorDescriptions.pop(); fullDescription = `${colorDescriptions.join(', ')}, with ${last}`; } items.push({ label: 'Color', value: fullDescription, tag: color.raw }); return items; } // Decode breath weapon function decodeBreath(breath) { if (breath.type === 'custom') { return { label: 'Breath Weapon', value: breath.value, tag: breath.raw }; } if (breath.simple) { const modifierKey = getModifierString(breath.simple); const value = TAG_DESCRIPTIONS.breath.simple[modifierKey] || 'Normal breath weapon'; return { label: 'Breath Weapon', value: value, tag: breath.raw }; } const types = breath.types.map(t => { const cleaned = t.replace(/[+\-!?~]/g, ''); return TAG_DESCRIPTIONS.breath.types[cleaned] || cleaned; }); let value = types.join(' / '); if (breath.modifiers.length > 0) { value += ` (${breath.modifiers.join(', ')})`; } return { label: 'Breath Weapon', value: value, tag: breath.raw }; } // Decode mating function decodeMating(mating) { const modifierKey = getModifierString(mating.modifiers); let value = TAG_DESCRIPTIONS.mating[modifierKey] || 'Not specified'; if (mating.count) { value += `, ${mating.count} mate${mating.count > 1 ? 's' : ''}`; } if (mating.separations) { value += `, ${mating.separations} separation${mating.separations > 1 ? 's' : ''}`; } return { label: 'Mating', value: value, tag: mating.raw }; } // Decode offspring function decodeOffspring(offspring) { const modifierKey = getModifierString(offspring.modifiers); let value = TAG_DESCRIPTIONS.offspring[modifierKey] || TAG_DESCRIPTIONS.offspring['']; if (offspring.count) { value = `${offspring.count} offspring`; } if (offspring.adopted) { value += ' (adopted)'; } return { label: 'Offspring', value: value, tag: offspring.raw }; } // Decode money function decodeMoney(money) { const dollarSigns = money.raw.match(/\$/g); const count = dollarSigns ? dollarSigns.length : 0; let key = ''; if (count === 3) key = '$$$'; else if (count === 2) key = '$$'; else if (count === 1) key = '$'; const minusSigns = money.raw.match(/-/g); const minusCount = minusSigns ? minusSigns.length : 0; if (minusCount === 3) key = '---'; else if (minusCount === 2) key = '--'; else if (minusCount === 1) key = '-'; const value = TAG_DESCRIPTIONS.money[key] || TAG_DESCRIPTIONS.money['']; return { label: 'Money', value: value, tag: money.raw }; } // Decode diet function decodeDiet(diet) { const typeNames = diet.types.map(t => TAG_DESCRIPTIONS.diet.types[t] || t ); let value = typeNames.join(', ') || 'omnivore'; const modifierKey = getModifierString(diet.modifiers); const modifierDesc = TAG_DESCRIPTIONS.diet.modifiers[modifierKey]; if (modifierDesc) { value = `${modifierDesc} ${value}`; } return { label: 'Diet', value: value, tag: diet.raw }; } // Decode technology function decodeTechnology(tech) { const modifierKey = getModifierString(tech.modifiers); let value = TAG_DESCRIPTIONS.technology[modifierKey] || TAG_DESCRIPTIONS.technology['']; if (tech.specialist) { value += `, specialist in ${tech.specialist}`; } return { label: 'Technology', value: value, tag: tech.raw }; } // Helper: Convert modifiers object to string key function getModifierString(modifiers) { let key = ''; // Handle exclamation special cases if (modifiers.exclaim) { if (modifiers.plus === 3) return '+++!'; if (modifiers.minus === 3) return '---!'; // Exclamation alone (e.g., S!) if (modifiers.plus === 0 && modifiers.minus === 0) return '!'; } // Build key from +/- counts if (modifiers.plus > 0) { key = '+'.repeat(modifiers.plus); } else if (modifiers.minus > 0) { key = '-'.repeat(modifiers.minus); } // Add special modifiers (but not if they're already the key) if (modifiers.question && key !== '!') { key += '?'; } if (modifiers.tilde && key === '') { key = '~'; } // Special case for slash if (modifiers.slash) { key = '/'; } return key; } // Export for Node.js (CommonJS) if (typeof module !== 'undefined' && module.exports) { module.exports = { decodeDragonCode }; }