628 lines
17 KiB
JavaScript
628 lines
17 KiB
JavaScript
// 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 };
|
|
}
|