Initial version
This commit is contained in:
180
js/app.js
Normal file
180
js/app.js
Normal file
@@ -0,0 +1,180 @@
|
||||
// Main application logic
|
||||
|
||||
let debounceTimer = null;
|
||||
const EXAMPLE_CODE = 'DC2.Dw Gm L W T+ Phvwalt Sk? Cbk--^\\bl+lum B- A+ N? M+++!1 O/ H+++ $ F+o R++ Ac+++! J++ S U- I-- V- Q--- Tc+++[SE] E++';
|
||||
|
||||
// DOM elements
|
||||
const input = document.getElementById('dragon-code-input');
|
||||
const welcomeMessage = document.getElementById('welcome-message');
|
||||
const decodedOutput = document.getElementById('decoded-output');
|
||||
const loadExampleBtn = document.getElementById('load-example');
|
||||
const clearInputBtn = document.getElementById('clear-input');
|
||||
|
||||
// Category sections
|
||||
const sections = {
|
||||
species: document.getElementById('species-content'),
|
||||
physical: document.getElementById('physical-content'),
|
||||
appearance: document.getElementById('appearance-content'),
|
||||
abilities: document.getElementById('abilities-content'),
|
||||
life: document.getElementById('life-content'),
|
||||
personality: document.getElementById('personality-content'),
|
||||
other: document.getElementById('other-content'),
|
||||
errors: document.getElementById('errors-content')
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
input.addEventListener('input', handleInput);
|
||||
loadExampleBtn.addEventListener('click', loadExample);
|
||||
clearInputBtn.addEventListener('click', clearInput);
|
||||
|
||||
function handleInput() {
|
||||
// Debounce input to avoid excessive processing
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
const code = input.value.trim();
|
||||
if (code) {
|
||||
processCode(code);
|
||||
} else {
|
||||
showWelcome();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function loadExample() {
|
||||
input.value = EXAMPLE_CODE;
|
||||
processCode(EXAMPLE_CODE);
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
input.value = '';
|
||||
showWelcome();
|
||||
}
|
||||
|
||||
function showWelcome() {
|
||||
welcomeMessage.classList.remove('hidden');
|
||||
decodedOutput.classList.add('hidden');
|
||||
}
|
||||
|
||||
function processCode(code) {
|
||||
try {
|
||||
// Parse the Dragon Code
|
||||
const parsed = parseDragonCode(code);
|
||||
|
||||
// Decode to human-readable format
|
||||
const decoded = decodeDragonCode(parsed);
|
||||
|
||||
// Render the results
|
||||
renderResults(decoded, parsed.errors);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing code:', error);
|
||||
showError('An unexpected error occurred while processing the code.');
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults(decoded, errors) {
|
||||
// Clear all sections
|
||||
Object.values(sections).forEach(section => {
|
||||
section.innerHTML = '';
|
||||
});
|
||||
|
||||
// Hide welcome, show output
|
||||
welcomeMessage.classList.add('hidden');
|
||||
decodedOutput.classList.remove('hidden');
|
||||
|
||||
// Render each category
|
||||
renderCategory('species', decoded.species);
|
||||
renderCategory('physical', decoded.physical);
|
||||
renderCategory('appearance', decoded.appearance);
|
||||
renderCategory('abilities', decoded.abilities);
|
||||
renderCategory('life', decoded.life);
|
||||
renderCategory('personality', decoded.personality);
|
||||
renderCategory('other', decoded.other);
|
||||
|
||||
// Render errors if any
|
||||
if (errors && errors.length > 0) {
|
||||
document.getElementById('errors-section').classList.remove('hidden');
|
||||
errors.forEach(error => {
|
||||
const errorElement = createTraitItem('Warning', error, '', 'warning');
|
||||
sections.errors.appendChild(errorElement);
|
||||
});
|
||||
} else {
|
||||
document.getElementById('errors-section').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Hide empty sections
|
||||
hideEmptySections();
|
||||
}
|
||||
|
||||
function renderCategory(categoryName, items) {
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
const section = sections[categoryName];
|
||||
items.forEach(item => {
|
||||
const element = createTraitItem(item.label, item.value, item.tag, item.type || 'normal');
|
||||
section.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
function createTraitItem(label, value, tag, type = 'normal') {
|
||||
const div = document.createElement('div');
|
||||
div.className = `trait-item ${type}`;
|
||||
|
||||
const labelDiv = document.createElement('div');
|
||||
labelDiv.className = 'trait-label';
|
||||
labelDiv.textContent = label;
|
||||
|
||||
const valueDiv = document.createElement('div');
|
||||
valueDiv.className = 'trait-value';
|
||||
valueDiv.textContent = value;
|
||||
|
||||
div.appendChild(labelDiv);
|
||||
div.appendChild(valueDiv);
|
||||
|
||||
if (tag) {
|
||||
const tagSpan = document.createElement('span');
|
||||
tagSpan.className = 'trait-tag';
|
||||
tagSpan.textContent = tag;
|
||||
div.appendChild(tagSpan);
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function hideEmptySections() {
|
||||
const sectionElements = {
|
||||
species: document.getElementById('species-section'),
|
||||
physical: document.getElementById('physical-section'),
|
||||
appearance: document.getElementById('appearance-section'),
|
||||
abilities: document.getElementById('abilities-section'),
|
||||
life: document.getElementById('life-section'),
|
||||
personality: document.getElementById('personality-section'),
|
||||
other: document.getElementById('other-section')
|
||||
};
|
||||
|
||||
Object.keys(sectionElements).forEach(key => {
|
||||
const content = sections[key];
|
||||
const section = sectionElements[key];
|
||||
|
||||
if (content.children.length === 0) {
|
||||
section.classList.add('hidden');
|
||||
} else {
|
||||
section.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
welcomeMessage.classList.add('hidden');
|
||||
decodedOutput.classList.remove('hidden');
|
||||
|
||||
Object.values(sections).forEach(section => {
|
||||
section.innerHTML = '';
|
||||
});
|
||||
|
||||
document.getElementById('errors-section').classList.remove('hidden');
|
||||
const errorElement = createTraitItem('Error', message, '', 'error');
|
||||
sections.errors.appendChild(errorElement);
|
||||
|
||||
hideEmptySections();
|
||||
}
|
||||
622
js/decoder.js
Normal file
622
js/decoder.js
Normal file
@@ -0,0 +1,622 @@
|
||||
// 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;
|
||||
}
|
||||
738
js/parser.js
Normal file
738
js/parser.js
Normal file
@@ -0,0 +1,738 @@
|
||||
// Dragon Code V2.6 Parser
|
||||
|
||||
function parseDragonCode(input) {
|
||||
const result = {
|
||||
species: null,
|
||||
tags: {},
|
||||
errors: []
|
||||
};
|
||||
|
||||
try {
|
||||
// Tokenize the input
|
||||
const tokens = tokenize(input);
|
||||
|
||||
// Process each token
|
||||
tokens.forEach((token, index) => {
|
||||
try {
|
||||
const tagType = identifyTagType(token, index);
|
||||
|
||||
if (tagType === 'species') {
|
||||
result.species = parseSpecies(token);
|
||||
} else if (tagType) {
|
||||
const parsed = parseTag(token, tagType);
|
||||
if (parsed) {
|
||||
result.tags[tagType] = parsed;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
result.errors.push(`Error parsing token "${token}": ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
result.errors.push(`Error tokenizing input: ${error.message}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tokenizer: splits Dragon Code into individual tokens
|
||||
function tokenize(input) {
|
||||
// Remove DC2. prefix if present
|
||||
let code = input.trim();
|
||||
if (code.startsWith('DC2.')) {
|
||||
code = code.substring(4);
|
||||
} else if (code.startsWith('DC.')) {
|
||||
code = code.substring(3);
|
||||
}
|
||||
|
||||
const tokens = [];
|
||||
let current = '';
|
||||
let inQuotes = false;
|
||||
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
const char = code[i];
|
||||
|
||||
if (char === '"') {
|
||||
inQuotes = !inQuotes;
|
||||
current += char;
|
||||
} else if (char === ' ' && !inQuotes) {
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Identifies the type of tag
|
||||
function identifyTagType(token, index) {
|
||||
// IMPORTANT: Check two-letter tags before single-letter tags to avoid conflicts
|
||||
// (e.g., Tc before T, Ac before A, Sk before S, Df before D)
|
||||
|
||||
// Two-letter tags first
|
||||
if (token.startsWith('Tc')) return 'technology';
|
||||
if (token.startsWith('Ac')) return 'activity';
|
||||
if (token.startsWith('Sk')) return 'skinType';
|
||||
if (token.startsWith('Df')) return 'dragonFriend';
|
||||
if (token.startsWith('Ph') || token.startsWith('Pa') || token.startsWith('Pl') ||
|
||||
token.startsWith('Pw') || token.startsWith('Pt') || token.startsWith('Pv') ||
|
||||
token.startsWith('Pk') || token.startsWith('Pf') || token.startsWith('Pp')) return 'appendages';
|
||||
|
||||
// Species tag is typically first, or contains species indicators
|
||||
if (index === 0 || token.match(/^[~^]?[A-Z][a-z]*[\[+{^~]?/) || token.includes('/') || token.includes('[') || token.includes('^')) {
|
||||
// At index 0, check if it's a single-letter or multi-letter species code first
|
||||
// Species codes can be: D, Dw, H, A, Ag, etc.
|
||||
if (index === 0) {
|
||||
// At index 0, check in this order:
|
||||
// 1. Two-letter tag prefixes (Tc, Ac, Sk, Df, Ph, etc.)
|
||||
// 2. Single-letter tags (G, L, W, T, etc.)
|
||||
// 3. Species indicators (~, ^, [, +) - clear species markers
|
||||
// 4. Multi-letter species codes (Dw, De, etc.)
|
||||
// 5. Fallback to species for remaining patterns
|
||||
|
||||
// Two-letter tags (must check BEFORE single-letter checks)
|
||||
if (token.startsWith('Tc')) return 'technology';
|
||||
if (token.startsWith('Ac')) return 'activity';
|
||||
if (token.startsWith('Sk')) return 'skinType';
|
||||
if (token.startsWith('Df')) return 'dragonFriend';
|
||||
if (token.startsWith('Ph') || token.startsWith('Pa') || token.startsWith('Pl') ||
|
||||
token.startsWith('Pw') || token.startsWith('Pt') || token.startsWith('Pv') ||
|
||||
token.startsWith('Pk') || token.startsWith('Pf') || token.startsWith('Pp')) return 'appendages';
|
||||
|
||||
// Special case: bare "H" at index 0 is ambiguous
|
||||
// "H" alone = Human (species), "H+++" = Hoard (tag)
|
||||
// Check if it's exactly "H" with no modifiers
|
||||
if (token === 'H') {
|
||||
return 'species'; // Treat bare "H" as Human species
|
||||
}
|
||||
|
||||
// Single-letter tags (with or without modifiers)
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
if (token.startsWith('L')) return 'length';
|
||||
if (token.startsWith('W')) return 'width';
|
||||
if (token.startsWith('T')) return 'weight';
|
||||
if (token.startsWith('C') && token[1] && token[1].match(/[a-z]/)) return 'color';
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
if (token.startsWith('A')) return 'age';
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
if (token.startsWith('$')) return 'money';
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
if (token.startsWith('S')) return 'social';
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
// Check for species-specific indicators (after tag checks)
|
||||
// ~ at start (shapeshifter), or [ ] (trapped), or + (cross)
|
||||
if (token.startsWith('~') || token.includes('[') || token.includes('+')) {
|
||||
return 'species';
|
||||
}
|
||||
// ^ for shaped (H^Dw), but not if it's a tag with ^ modifier
|
||||
if (token.includes('^') && token.match(/[A-Z][a-z]*\^[A-Z]/)) {
|
||||
return 'species';
|
||||
}
|
||||
// / for multiple species in shapeshifter (must have species codes on both sides)
|
||||
// E.g., "Dw/H" or within "~Dw/H", but NOT "O/" or "M/"
|
||||
if (token.includes('/') && token.match(/[A-Z][a-z]*\/[A-Z]/)) {
|
||||
return 'species';
|
||||
}
|
||||
|
||||
// Multi-letter species codes (Dw, De, Ag, etc.)
|
||||
if (token.match(/^[A-Z][a-z]+/)) {
|
||||
return 'species';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's actually a non-species tag
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
if (token.startsWith('L')) return 'length';
|
||||
if (token.startsWith('W')) return 'width';
|
||||
if (token.startsWith('T') && !token.startsWith('Tc')) return 'weight';
|
||||
if (token.startsWith('C') && (token.length > 1 && token[1].match(/[a-z]/))) return 'color';
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
if (token.startsWith('A') && !token.startsWith('Ac')) return 'age';
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
if (token.startsWith('$')) return 'money';
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
if (token.startsWith('S') && !token.startsWith('Sk')) return 'social';
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
// If none of the above, likely species
|
||||
if (index === 0) return 'species';
|
||||
}
|
||||
|
||||
// Single-letter tags (checked after two-letter tags)
|
||||
// Gender
|
||||
if (token.startsWith('G')) return 'gender';
|
||||
|
||||
// Length (with or without numbers)
|
||||
if (token.startsWith('L')) return 'length';
|
||||
|
||||
// Width (with any modifiers)
|
||||
if (token.startsWith('W')) return 'width';
|
||||
|
||||
// Weight (Tonnage) - already checked it's not Technology
|
||||
if (token.startsWith('T')) return 'weight';
|
||||
|
||||
// Color
|
||||
if (token.startsWith('C')) return 'color';
|
||||
|
||||
// Breath Weapon
|
||||
if (token.startsWith('B')) return 'breath';
|
||||
|
||||
// Age - already checked it's not Activity
|
||||
if (token.startsWith('A')) return 'age';
|
||||
|
||||
// Native Land
|
||||
if (token.startsWith('N')) return 'nativeLand';
|
||||
|
||||
// Mating
|
||||
if (token.startsWith('M')) return 'mating';
|
||||
|
||||
// Offspring
|
||||
if (token.startsWith('O')) return 'offspring';
|
||||
|
||||
// Hoard
|
||||
if (token.startsWith('H')) return 'hoard';
|
||||
|
||||
// Money
|
||||
if (token.startsWith('$')) return 'money';
|
||||
|
||||
// Diet
|
||||
if (token.startsWith('F')) return 'diet';
|
||||
|
||||
// Reality
|
||||
if (token.startsWith('R')) return 'reality';
|
||||
|
||||
// Humor
|
||||
if (token.startsWith('J')) return 'humor';
|
||||
|
||||
// Social - already checked it's not Skin Type
|
||||
if (token.startsWith('S')) return 'social';
|
||||
|
||||
// Ubiquity
|
||||
if (token.startsWith('U')) return 'ubiquity';
|
||||
|
||||
// Irritability
|
||||
if (token.startsWith('I')) return 'irritability';
|
||||
|
||||
// Magic
|
||||
if (token.startsWith('V')) return 'magic';
|
||||
|
||||
// Psy Power
|
||||
if (token.startsWith('Q')) return 'psyPower';
|
||||
|
||||
// Emotion
|
||||
if (token.startsWith('E')) return 'emotion';
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse species (handles ~, ^, [], +, {})
|
||||
function parseSpecies(token) {
|
||||
const result = {
|
||||
type: 'simple',
|
||||
value: null,
|
||||
modifiers: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Handle quoted custom species
|
||||
if (token.startsWith('"') && token.endsWith('"')) {
|
||||
result.type = 'custom';
|
||||
result.value = token.substring(1, token.length - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shapechanger: ~species1/species2
|
||||
if (token.includes('~') && token.includes('/')) {
|
||||
result.type = 'shapechanger';
|
||||
const parts = token.split('/');
|
||||
result.forms = parts.map(p => p.replace('~', '').trim());
|
||||
|
||||
// Check for {} modifiers
|
||||
result.forms = result.forms.map(form => {
|
||||
if (form.includes('{')) {
|
||||
const match = form.match(/([^{]+)\{([^}]+)\}/);
|
||||
if (match) {
|
||||
return { species: match[1], modifier: match[2] };
|
||||
}
|
||||
}
|
||||
return { species: form, modifier: null };
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cross-breed: species1+species2
|
||||
if (token.includes('+') && !token.match(/^[+\-]/)) {
|
||||
result.type = 'cross';
|
||||
result.species = token.split('+').map(s => s.trim());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Trapped form: species1[species2]
|
||||
if (token.includes('[') && token.includes(']')) {
|
||||
result.type = 'trapped';
|
||||
const match = token.match(/([^\[]+)\[([^\]]+)\]/);
|
||||
if (match) {
|
||||
result.trueForm = match[1];
|
||||
result.trappedIn = match[2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shaped: species1^species2
|
||||
if (token.includes('^')) {
|
||||
result.type = 'shaped';
|
||||
const parts = token.split('^');
|
||||
result.trueForm = parts[0];
|
||||
result.shapedAs = parts[1];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simple species code
|
||||
result.value = token;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse individual tag based on type
|
||||
function parseTag(token, type) {
|
||||
const parsers = {
|
||||
gender: parseGender,
|
||||
length: parseLength,
|
||||
width: parseSimpleModifier,
|
||||
weight: parseSimpleModifier,
|
||||
appendages: parseAppendages,
|
||||
skinType: parseSkinType,
|
||||
color: parseColor,
|
||||
breath: parseBreath,
|
||||
age: parseSimpleModifier,
|
||||
nativeLand: parseSimpleModifier,
|
||||
mating: parseMating,
|
||||
offspring: parseOffspring,
|
||||
hoard: parseSimpleModifier,
|
||||
money: parseSimpleModifier,
|
||||
diet: parseDiet,
|
||||
reality: parseSimpleModifier,
|
||||
activity: parseSimpleModifier,
|
||||
humor: parseSimpleModifier,
|
||||
social: parseSimpleModifier,
|
||||
ubiquity: parseSimpleModifier,
|
||||
irritability: parseSimpleModifier,
|
||||
magic: parseSimpleModifier,
|
||||
psyPower: parseSimpleModifier,
|
||||
technology: parseTechnology,
|
||||
emotion: parseSimpleModifier,
|
||||
dragonFriend: parseSimpleModifier
|
||||
};
|
||||
|
||||
const parser = parsers[type];
|
||||
if (parser) {
|
||||
return parser(token);
|
||||
}
|
||||
|
||||
return { raw: token };
|
||||
}
|
||||
|
||||
// Parse gender
|
||||
function parseGender(token) {
|
||||
// Handle quoted custom gender
|
||||
if (token.includes('"')) {
|
||||
const match = token.match(/"([^"]+)"/);
|
||||
return {
|
||||
type: 'custom',
|
||||
value: match ? match[1] : token,
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
value: token.substring(1), // Remove 'G' prefix
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
// Parse length (quantitative with units)
|
||||
function parseLength(token) {
|
||||
const result = {
|
||||
raw: token,
|
||||
value: null,
|
||||
unit: null,
|
||||
dimensions: [], // For quantitative modifiers like 4t (4m tail)
|
||||
modifiers: null // For qualitative modifiers like +++, -, etc.
|
||||
};
|
||||
|
||||
// Check if it has a numeric value (quantitative)
|
||||
const match = token.match(/L(\d+)([a-z]+)?/i);
|
||||
if (match) {
|
||||
result.value = parseInt(match[1]);
|
||||
result.unit = match[2] || null;
|
||||
|
||||
// Extract dimension modifiers (a, l, n, t, w, h)
|
||||
const dimMatch = token.match(/\d+[a-z]*(\d*[alntwh])/);
|
||||
if (dimMatch) {
|
||||
result.dimensions = dimMatch[1].split('').map(m => {
|
||||
if (m.match(/\d/)) return null;
|
||||
return m;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
} else {
|
||||
// Qualitative length (L+++, L-, L, etc.)
|
||||
// Remove 'L' prefix and extract modifiers
|
||||
const modifierPart = token.substring(1);
|
||||
result.modifiers = extractModifiers(modifierPart);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse simple modifier tags (+, -, !, ?, ~, etc.)
|
||||
function parseSimpleModifier(token) {
|
||||
// Remove tag prefix to get modifiers
|
||||
let modifierPart = token.replace(/^[A-Z][a-z]?/, '');
|
||||
|
||||
return {
|
||||
modifiers: extractModifiers(modifierPart),
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
// Extract modifiers from a string
|
||||
function extractModifiers(str) {
|
||||
const modifiers = {
|
||||
plus: 0,
|
||||
minus: 0,
|
||||
exclaim: false,
|
||||
question: false,
|
||||
tilde: false,
|
||||
caret: false,
|
||||
slash: false,
|
||||
real: false,
|
||||
virtual: false
|
||||
};
|
||||
|
||||
for (let char of str) {
|
||||
if (char === '+') modifiers.plus++;
|
||||
else if (char === '-') modifiers.minus++;
|
||||
else if (char === '!') modifiers.exclaim = true;
|
||||
else if (char === '?') modifiers.question = true;
|
||||
else if (char === '~') modifiers.tilde = true;
|
||||
else if (char === '^') modifiers.caret = true;
|
||||
else if (char === '/') modifiers.slash = true;
|
||||
}
|
||||
|
||||
// Check for r/v (real/virtual)
|
||||
if (str.includes('r')) modifiers.real = true;
|
||||
if (str.includes('v')) modifiers.virtual = true;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
// Parse appendages (complex sequence)
|
||||
function parseAppendages(token) {
|
||||
const result = {
|
||||
parts: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Parse the appendage code
|
||||
let current = '';
|
||||
for (let i = 0; i < token.length; i++) {
|
||||
const char = token[i];
|
||||
|
||||
if (char === 'P') continue; // Skip prefix
|
||||
|
||||
if (char.match(/[halvwtkfp']/)) {
|
||||
if (current) {
|
||||
result.parts.push(parseAppendagePart(current));
|
||||
}
|
||||
current = char;
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
result.parts.push(parseAppendagePart(current));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAppendagePart(part) {
|
||||
const type = part[0];
|
||||
const modifiers = part.substring(1);
|
||||
|
||||
return {
|
||||
type: type,
|
||||
modifiers: extractModifiers(modifiers),
|
||||
count: modifiers.match(/\d+/) ? parseInt(modifiers.match(/\d+/)[0]) : null
|
||||
};
|
||||
}
|
||||
|
||||
// Parse skin type
|
||||
function parseSkinType(token) {
|
||||
const result = {
|
||||
mainType: null,
|
||||
bodyPartTypes: [], // Array of {part, type} objects
|
||||
modifiers: {},
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove Sk prefix
|
||||
let content = token.substring(2);
|
||||
|
||||
// Check for ? modifier
|
||||
if (content.includes('?')) {
|
||||
result.modifiers.question = true;
|
||||
content = content.replace('?', '');
|
||||
}
|
||||
|
||||
// Split by comma to separate main type from body part modifiers
|
||||
const parts = content.split(',');
|
||||
|
||||
// First part is the main skin type (single letter)
|
||||
if (parts[0]) {
|
||||
result.mainType = parts[0][0]; // First character is the main type
|
||||
}
|
||||
|
||||
// Remaining parts are body part modifiers (format: <part><type>)
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part.length >= 2) {
|
||||
// First character is body part, rest is skin type
|
||||
const bodyPart = part[0];
|
||||
const skinType = part.substring(1);
|
||||
result.bodyPartTypes.push({
|
||||
part: bodyPart,
|
||||
type: skinType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse color (MOST COMPLEX)
|
||||
function parseColor(token) {
|
||||
const result = {
|
||||
colors: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove C prefix
|
||||
let content = token.substring(1);
|
||||
|
||||
// Split by / for multiple colors, and handle \ as additional color/effect
|
||||
const colorParts = content.split(/[\/\\]/);
|
||||
|
||||
colorParts.forEach(colorPart => {
|
||||
if (colorPart) {
|
||||
result.colors.push(parseColorPart(colorPart));
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseColorPart(part) {
|
||||
const color = {
|
||||
base: null,
|
||||
intensity: [],
|
||||
patterns: [],
|
||||
bodyParts: [],
|
||||
modifiers: []
|
||||
};
|
||||
|
||||
// Extract base color (2-3 letter code)
|
||||
const baseMatch = part.match(/^([a-z]{2,3})/);
|
||||
if (baseMatch) {
|
||||
color.base = baseMatch[1];
|
||||
part = part.substring(baseMatch[1].length);
|
||||
}
|
||||
|
||||
// Check for 'lum' (luminescent) special modifier
|
||||
if (part.includes('lum')) {
|
||||
color.intensity.push('lum');
|
||||
part = part.replace('lum', '');
|
||||
}
|
||||
|
||||
// Extract intensity modifiers (+, -, ^, _, ', %, !)
|
||||
for (let char of part) {
|
||||
if (['+', '-', '^', '_', "'", '%', '!'].includes(char)) {
|
||||
color.intensity.push(char);
|
||||
} else if (['|', '=', ':', '*', '@', '#', '&', '>'].includes(char)) {
|
||||
color.patterns.push(char);
|
||||
} else if (char === ',') {
|
||||
// Body part modifier follows
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract body part modifiers (,a, ,b, etc.)
|
||||
const bodyPartMatches = part.match(/,([a-z])/g);
|
||||
if (bodyPartMatches) {
|
||||
color.bodyParts = bodyPartMatches.map(m => m.substring(1));
|
||||
}
|
||||
|
||||
// Check for special patterns like &1, &2
|
||||
const specialPattern = part.match(/&(\d+)/);
|
||||
if (specialPattern) {
|
||||
color.patterns.push('&' + specialPattern[1]);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
// Parse breath weapon
|
||||
function parseBreath(token) {
|
||||
// Handle quoted custom breath
|
||||
if (token.includes('"')) {
|
||||
const match = token.match(/"([^"]+)"/);
|
||||
return {
|
||||
type: 'custom',
|
||||
value: match ? match[1] : token,
|
||||
raw: token
|
||||
};
|
||||
}
|
||||
|
||||
const result = {
|
||||
types: [],
|
||||
modifiers: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Remove B prefix
|
||||
let content = token.substring(1);
|
||||
|
||||
// Check for modifiers
|
||||
if (content.includes('|')) result.modifiers.push('beam');
|
||||
if (content.includes('#')) result.modifiers.push('cloud');
|
||||
|
||||
// Handle simple +/- modifiers (no specific type)
|
||||
if (content.match(/^[+\-!?~]+$/)) {
|
||||
result.simple = extractModifiers(content);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract breath types (fl, ac, ic, etc.)
|
||||
if (content.length > 0) {
|
||||
const types = content.split('/').map(t => t.replace(/[|#]/g, '').trim()).filter(Boolean);
|
||||
result.types = types;
|
||||
} else {
|
||||
// "B" with no content - normal breath
|
||||
result.simple = extractModifiers('');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse mating
|
||||
function parseMating(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
count: null,
|
||||
separations: null,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract count (number at end)
|
||||
const countMatch = token.match(/(\d+)$/);
|
||||
if (countMatch) {
|
||||
result.count = parseInt(countMatch[1]);
|
||||
}
|
||||
|
||||
// Extract separations (^number)
|
||||
const sepMatch = token.match(/\^(\d+)/);
|
||||
if (sepMatch) {
|
||||
result.separations = parseInt(sepMatch[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse offspring
|
||||
function parseOffspring(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
count: null,
|
||||
adopted: false,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Check for adopted (a modifier)
|
||||
if (token.includes('a')) {
|
||||
result.adopted = true;
|
||||
}
|
||||
|
||||
// Extract count
|
||||
const countMatch = token.match(/(\d+)/);
|
||||
if (countMatch) {
|
||||
result.count = parseInt(countMatch[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse diet
|
||||
function parseDiet(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(1)),
|
||||
types: [],
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract diet type letters (c, h, o, v)
|
||||
const content = token.substring(1);
|
||||
for (let char of content) {
|
||||
if (['c', 'h', 'o', 'v'].includes(char)) {
|
||||
result.types.push(char);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse technology
|
||||
function parseTechnology(token) {
|
||||
const result = {
|
||||
modifiers: extractModifiers(token.substring(2)),
|
||||
specialist: null,
|
||||
raw: token
|
||||
};
|
||||
|
||||
// Extract specialist field in []
|
||||
const specialistMatch = token.match(/\[([^\]]+)\]/);
|
||||
if (specialistMatch) {
|
||||
result.specialist = specialistMatch[1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
249
js/species-data.js
Normal file
249
js/species-data.js
Normal file
@@ -0,0 +1,249 @@
|
||||
// Hierarchical species tree based on Dragon Code V2.6 specification
|
||||
const SPECIES_DATA = {
|
||||
// Dragons (D)
|
||||
D: {
|
||||
name: "Dragon",
|
||||
subtypes: {
|
||||
w: "Western Dragon",
|
||||
e: "Eastern Dragon",
|
||||
p: "Pernese Dragon",
|
||||
f: "Faerie Dragon",
|
||||
r: "Reformed Dragon",
|
||||
c: "Chromatic Dragon",
|
||||
m: "Metallic Dragon",
|
||||
g: "Gem Dragon"
|
||||
}
|
||||
},
|
||||
|
||||
// Humanoid (H)
|
||||
H: {
|
||||
name: "Human",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Avian (A)
|
||||
A: {
|
||||
name: "Avian",
|
||||
subtypes: {
|
||||
c: "Cockatrice",
|
||||
g: "Griffin",
|
||||
h: "Hippogriff",
|
||||
p: "Phoenix",
|
||||
r: "Roc"
|
||||
}
|
||||
},
|
||||
|
||||
// Bovine (B)
|
||||
B: {
|
||||
name: "Bovine",
|
||||
subtypes: {
|
||||
c: "Cattle",
|
||||
m: "Minotaur"
|
||||
}
|
||||
},
|
||||
|
||||
// Canine (C)
|
||||
C: {
|
||||
name: "Canine",
|
||||
subtypes: {
|
||||
c: "Coyote",
|
||||
d: "Dog",
|
||||
f: "Fox",
|
||||
j: "Jackal",
|
||||
w: "Wolf"
|
||||
}
|
||||
},
|
||||
|
||||
// Serpent (S)
|
||||
S: {
|
||||
name: "Serpent",
|
||||
subtypes: {
|
||||
b: "Basilisk",
|
||||
h: "Hydra",
|
||||
n: "Naga",
|
||||
s: "Sea Serpent"
|
||||
}
|
||||
},
|
||||
|
||||
// Equine (E)
|
||||
E: {
|
||||
name: "Equine",
|
||||
subtypes: {
|
||||
c: "Centaur",
|
||||
h: "Horse",
|
||||
p: "Pegasus",
|
||||
u: "Unicorn",
|
||||
z: "Zebra"
|
||||
}
|
||||
},
|
||||
|
||||
// Feline (F)
|
||||
F: {
|
||||
name: "Feline",
|
||||
subtypes: {
|
||||
c: "Cat",
|
||||
h: "Cheetah",
|
||||
j: "Jaguar",
|
||||
l: "Lion",
|
||||
o: "Ocelot",
|
||||
p: "Panther",
|
||||
t: "Tiger",
|
||||
x: "Lynx"
|
||||
}
|
||||
},
|
||||
|
||||
// Insectoid (I)
|
||||
I: {
|
||||
name: "Insectoid",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Leporine (L)
|
||||
L: {
|
||||
name: "Leporine",
|
||||
subtypes: {
|
||||
h: "Hare",
|
||||
r: "Rabbit"
|
||||
}
|
||||
},
|
||||
|
||||
// Mammal (M)
|
||||
M: {
|
||||
name: "Mammal",
|
||||
subtypes: {
|
||||
a: {
|
||||
name: "Aquatic",
|
||||
subtypes: {
|
||||
d: "Dolphin",
|
||||
o: "Orca",
|
||||
p: "Porpoise",
|
||||
s: "Seal",
|
||||
w: "Walrus",
|
||||
h: "Whale"
|
||||
}
|
||||
},
|
||||
b: "Badger",
|
||||
e: "Elephant",
|
||||
f: {
|
||||
name: "Feral",
|
||||
subtypes: {
|
||||
p: {
|
||||
name: "Pantherine",
|
||||
subtypes: {
|
||||
c: "Cougar",
|
||||
j: "Jaguar",
|
||||
l: "Leopard",
|
||||
n: "Panther",
|
||||
s: "Snow Leopard",
|
||||
t: "Tiger"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
k: "Skunk",
|
||||
m: "Mink",
|
||||
o: "Otter",
|
||||
r: "Rodent",
|
||||
t: "Taur",
|
||||
w: "Weasel"
|
||||
}
|
||||
},
|
||||
|
||||
// Otter (O)
|
||||
O: {
|
||||
name: "Otter",
|
||||
subtypes: {}
|
||||
},
|
||||
|
||||
// Mythological (Y)
|
||||
Y: {
|
||||
name: "Mythological",
|
||||
subtypes: {
|
||||
c: "Chimera",
|
||||
g: "Gargoyle",
|
||||
m: "Manticore",
|
||||
s: "Sphinx"
|
||||
}
|
||||
},
|
||||
|
||||
// Porcine (P)
|
||||
P: {
|
||||
name: "Porcine",
|
||||
subtypes: {
|
||||
b: "Boar",
|
||||
p: "Pig"
|
||||
}
|
||||
},
|
||||
|
||||
// Reptile (R)
|
||||
R: {
|
||||
name: "Reptile",
|
||||
subtypes: {
|
||||
a: "Alligator",
|
||||
c: "Crocodile",
|
||||
d: "Dinosaur",
|
||||
l: "Lizard",
|
||||
t: "Turtle"
|
||||
}
|
||||
},
|
||||
|
||||
// Aquatic (Q)
|
||||
Q: {
|
||||
name: "Aquatic",
|
||||
subtypes: {
|
||||
d: "Dolphin",
|
||||
f: "Fish",
|
||||
m: "Mermaid/Merman",
|
||||
o: "Orca",
|
||||
s: "Shark"
|
||||
}
|
||||
},
|
||||
|
||||
// Ursine (U)
|
||||
U: {
|
||||
name: "Ursine",
|
||||
subtypes: {
|
||||
b: "Bear",
|
||||
g: "Grizzly",
|
||||
p: "Panda",
|
||||
l: "Polar Bear"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to resolve a species code to its full name
|
||||
function resolveSpeciesCode(code) {
|
||||
if (!code || code.length === 0) return null;
|
||||
|
||||
const parts = code.split('');
|
||||
let current = SPECIES_DATA;
|
||||
let path = [];
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const char = parts[i];
|
||||
|
||||
if (!current[char]) {
|
||||
// If we can't find the next level, return what we have
|
||||
break;
|
||||
}
|
||||
|
||||
current = current[char];
|
||||
|
||||
if (typeof current === 'string') {
|
||||
// We've reached a leaf node (species name)
|
||||
return current;
|
||||
} else if (current.name) {
|
||||
// We're at a node with a name, but there might be subtypes
|
||||
path.push(current.name);
|
||||
if (current.subtypes) {
|
||||
current = current.subtypes;
|
||||
} else {
|
||||
// No more subtypes, return the name
|
||||
return current.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the accumulated path or the name if we have it
|
||||
return path.length > 0 ? path[path.length - 1] : null;
|
||||
}
|
||||
408
js/tags-data.js
Normal file
408
js/tags-data.js
Normal file
@@ -0,0 +1,408 @@
|
||||
// Tag descriptions and meanings for Dragon Code V2.6
|
||||
|
||||
const TAG_DESCRIPTIONS = {
|
||||
gender: {
|
||||
m: "Male",
|
||||
f: "Female",
|
||||
h: "Hermaphrodite",
|
||||
n: "Neuter",
|
||||
'?': "Not telling",
|
||||
'~': "Variable"
|
||||
},
|
||||
|
||||
length: {
|
||||
'+++!': "Celestial",
|
||||
'+++': "Mistaken for mountain ranges",
|
||||
'++': "Lair fits a regiment",
|
||||
'+': "Bigger than most dragons",
|
||||
'': "Normal (Draco-sized)",
|
||||
'-': "Smaller than most dragons",
|
||||
'--': "Fits on your shoulder",
|
||||
'---': "Fits in your pocket",
|
||||
'---!': "Microscopic",
|
||||
units: {
|
||||
'i': 'inches',
|
||||
'f': 'feet',
|
||||
'y': 'yards',
|
||||
'c': 'centimeters',
|
||||
'm': 'meters',
|
||||
'k': 'kilometers',
|
||||
'mi': 'miles'
|
||||
},
|
||||
dimensions: {
|
||||
'a': 'arm length',
|
||||
'l': 'leg length',
|
||||
'n': 'neck length',
|
||||
't': 'tail length',
|
||||
'w': 'wingspan',
|
||||
'h': 'height'
|
||||
}
|
||||
},
|
||||
|
||||
width: {
|
||||
'+++!': "I am Athelind! My belly is now several galaxies wide ... while I'm only a few hundred feet long!",
|
||||
'+++': "Planets have been known to crack in half with my arrival!",
|
||||
'++': "My digestion of food has been known to cause earthquakes",
|
||||
'+': "I move by rolling. Flying has always been an effort for me",
|
||||
'': "What can I say ... I'm normal, except for a few feasts here or there",
|
||||
'~': "Variable - 'My girth depends on what I've just eaten!'",
|
||||
'-': "I'm slightly on the slim side!",
|
||||
'--': "Ever heard of serpentine?",
|
||||
'---': "Whoah! Whaddaya mean I look like a long string with wings?",
|
||||
'---!': "I'm one-dimensional - all length and no width or depth. Just one long super-string!"
|
||||
},
|
||||
|
||||
weight: {
|
||||
'+++': "Obese",
|
||||
'++': "Fat",
|
||||
'+': "Over-weight",
|
||||
'': "Normal weight",
|
||||
'-': "Under-weight",
|
||||
'--': "Skeleton with scales",
|
||||
'---': "Anorexic"
|
||||
},
|
||||
|
||||
appendages: {
|
||||
types: {
|
||||
'h': 'head',
|
||||
'a': 'arms',
|
||||
'l': 'legs',
|
||||
'v': 'horns',
|
||||
'w': 'wings',
|
||||
't': 'tail',
|
||||
'k': 'spikes/ridge',
|
||||
'f': 'fins',
|
||||
'p': 'pouch',
|
||||
"'": 'feathers'
|
||||
},
|
||||
modifiers: {
|
||||
'^': 'retractable',
|
||||
'+': 'extra/multiple',
|
||||
'-': 'missing/vestigial',
|
||||
'!': 'unusual',
|
||||
'~': 'variable'
|
||||
}
|
||||
},
|
||||
|
||||
skinType: {
|
||||
's': 'scales',
|
||||
'h': 'hide',
|
||||
'u': 'fur',
|
||||
'k': 'skin',
|
||||
'l': 'leather',
|
||||
'm': 'metal',
|
||||
'r': 'rock',
|
||||
'f': 'feathers',
|
||||
'e': 'exoskeleton',
|
||||
'b': 'bark',
|
||||
'c': 'cellulose',
|
||||
'x': 'crystals',
|
||||
'': 'none (just bones)',
|
||||
bodyParts: {
|
||||
'a': 'arms',
|
||||
'b': 'belly',
|
||||
'h': 'head',
|
||||
'l': 'legs',
|
||||
'n': 'neck',
|
||||
't': 'tail',
|
||||
'w': 'wings'
|
||||
}
|
||||
},
|
||||
|
||||
color: {
|
||||
bases: {
|
||||
'bk': 'black',
|
||||
'bl': 'blue',
|
||||
'br': 'brown',
|
||||
'au': 'gold',
|
||||
'ag': 'silver',
|
||||
'gr': 'green',
|
||||
're': 'red',
|
||||
'wh': 'white',
|
||||
'ye': 'yellow',
|
||||
'or': 'orange',
|
||||
'pu': 'purple',
|
||||
'pi': 'pink',
|
||||
'gy': 'grey',
|
||||
'cy': 'cyan',
|
||||
'mg': 'magenta',
|
||||
'tn': 'tan',
|
||||
'be': 'beige',
|
||||
'iv': 'ivory',
|
||||
'cr': 'crimson',
|
||||
'sc': 'scarlet',
|
||||
'bz': 'bronze',
|
||||
'cp': 'copper',
|
||||
'bs': 'brass',
|
||||
'lm': 'lime',
|
||||
'tq': 'turquoise',
|
||||
'aq': 'aqua',
|
||||
'nv': 'navy',
|
||||
'in': 'indigo',
|
||||
'vi': 'violet',
|
||||
'la': 'lavender',
|
||||
'ms': 'mauve',
|
||||
'ch': 'chartreuse',
|
||||
'ol': 'olive',
|
||||
'ma': 'maroon',
|
||||
'rs': 'rust',
|
||||
'sa': 'salmon',
|
||||
'pc': 'peach',
|
||||
'ap': 'apricot',
|
||||
'co': 'coral'
|
||||
},
|
||||
intensity: {
|
||||
'+': 'lighter/brighter',
|
||||
'-': 'darker/duller',
|
||||
'^': 'metallic/shiny',
|
||||
'_': 'matte/flat',
|
||||
"'": 'iridescent',
|
||||
'%': 'translucent',
|
||||
'!': 'glowing',
|
||||
'lum': 'luminescent'
|
||||
},
|
||||
patterns: {
|
||||
'|': 'striped',
|
||||
'=': 'banded',
|
||||
':': 'spotted',
|
||||
'*': 'starred',
|
||||
'@': 'swirled',
|
||||
'\\': 'scaled pattern',
|
||||
'/': 'mixed with',
|
||||
'#': 'checkered',
|
||||
'&': 'gradiated',
|
||||
'&1': 'gradient type 1',
|
||||
'&2': 'gradient type 2',
|
||||
'>': 'highlighted'
|
||||
}
|
||||
},
|
||||
|
||||
breath: {
|
||||
types: {
|
||||
'fl': 'flame',
|
||||
'fi': 'fire',
|
||||
'ac': 'acid',
|
||||
'co': 'corrosive',
|
||||
'ic': 'ice',
|
||||
'fr': 'frost',
|
||||
'li': 'lightning',
|
||||
'el': 'electricity',
|
||||
'ga': 'gas',
|
||||
'po': 'poison',
|
||||
'st': 'steam',
|
||||
'wa': 'water',
|
||||
'wi': 'wind',
|
||||
'so': 'sonic',
|
||||
'ch': 'chlorine',
|
||||
'di': 'disintegration',
|
||||
'ma': 'magic'
|
||||
},
|
||||
simple: {
|
||||
'+++': 'Legendary breath weapon',
|
||||
'++': 'Very powerful breath',
|
||||
'+': 'Above average breath',
|
||||
'': 'Normal breath weapon',
|
||||
'-': 'Weak breath',
|
||||
'--': 'Very weak breath',
|
||||
'---': 'No breath weapon'
|
||||
}
|
||||
},
|
||||
|
||||
age: {
|
||||
'+++!': 'Eternal',
|
||||
'+++': 'Ancient beyond measure',
|
||||
'++': 'Ancient',
|
||||
'+': "You've been around",
|
||||
'': 'Adult',
|
||||
'-': 'Young adult',
|
||||
'--': 'Adolescent',
|
||||
'---': 'Hatchling',
|
||||
'?': 'Unknown age'
|
||||
},
|
||||
|
||||
nativeLand: {
|
||||
'+++': 'Known across the realm',
|
||||
'++': 'Well-known region',
|
||||
'+': 'Specific location',
|
||||
'': 'General area',
|
||||
'-': 'Wanderer',
|
||||
'--': 'Nomad',
|
||||
'?': 'Unknown/uncertain'
|
||||
},
|
||||
|
||||
mating: {
|
||||
'+++!': 'Mated in RL to online mate\'s RL self',
|
||||
'+++': 'Mated to RL person I met online',
|
||||
'++': 'Mated online, not met in RL',
|
||||
'+': 'Mated/married',
|
||||
'': 'Dating',
|
||||
'-': 'Looking',
|
||||
'--': 'Not interested',
|
||||
'---': 'Sworn celibate',
|
||||
'!': 'notation for RL connection'
|
||||
},
|
||||
|
||||
offspring: {
|
||||
'+++': 'Large family',
|
||||
'++': 'Several offspring',
|
||||
'+': 'Have offspring',
|
||||
'': 'Interested in having',
|
||||
'-': 'Not interested',
|
||||
'--': "Don't even think about it",
|
||||
'---': 'Absolutely not',
|
||||
'/': 'If one was my fault, I\'d faint',
|
||||
'a': 'adopted'
|
||||
},
|
||||
|
||||
hoard: {
|
||||
'+++': "Really can't tell when someone steals",
|
||||
'++': 'Large hoard',
|
||||
'+': 'Growing collection',
|
||||
'': 'Small hoard',
|
||||
'-': 'Starting out',
|
||||
'--': 'No hoard yet',
|
||||
'---': 'No interest in hoarding'
|
||||
},
|
||||
|
||||
money: {
|
||||
'$$$': 'Richest dragon alive',
|
||||
'$$': 'Wealthy',
|
||||
'$': "Don't touch my hoard",
|
||||
'': 'Comfortable',
|
||||
'-': 'Getting by',
|
||||
'--': 'Poor',
|
||||
'---': 'Broke'
|
||||
},
|
||||
|
||||
diet: {
|
||||
types: {
|
||||
'c': 'carnivore',
|
||||
'h': 'herbivore',
|
||||
'o': 'omnivore',
|
||||
'v': 'vegetarian'
|
||||
},
|
||||
modifiers: {
|
||||
'+++': 'Glutton',
|
||||
'++': 'Overindulgent',
|
||||
'+': 'Healthy appetite',
|
||||
'': 'Normal',
|
||||
'-': 'Light eater',
|
||||
'--': 'Picky eater',
|
||||
'---': 'Barely eats'
|
||||
}
|
||||
},
|
||||
|
||||
reality: {
|
||||
'+++': 'I AM a dragon',
|
||||
'++': 'Strongly identify with on-line form',
|
||||
'+': 'Identify with character',
|
||||
'': 'Just playing',
|
||||
'-': "It's only a game",
|
||||
'--': "Don't confuse RL with online",
|
||||
'---': 'Completely separate'
|
||||
},
|
||||
|
||||
activity: {
|
||||
'+++!': 'T1 connection, never off the net',
|
||||
'+++': 'Online most of the time',
|
||||
'++': 'Online frequently',
|
||||
'+': 'Online regularly',
|
||||
'': 'Moderate activity',
|
||||
'-': 'Occasional visits',
|
||||
'--': 'Rare appearances',
|
||||
'---': 'Almost never online'
|
||||
},
|
||||
|
||||
humor: {
|
||||
'+++': 'Constantly joking',
|
||||
'++': 'Laughing is good for you',
|
||||
'+': 'Good sense of humor',
|
||||
'': 'Normal humor',
|
||||
'-': 'Serious',
|
||||
'--': 'Very serious',
|
||||
'---': 'No sense of humor'
|
||||
},
|
||||
|
||||
social: {
|
||||
'+++': 'Everyone knows',
|
||||
'++': 'Most people know',
|
||||
'+': 'Several people know',
|
||||
'': 'A few friends know',
|
||||
'-': 'Very select few know',
|
||||
'--': 'One or two people know',
|
||||
'---': 'Complete secret',
|
||||
'!': 'Out and proud'
|
||||
},
|
||||
|
||||
ubiquity: {
|
||||
'+++': 'Legendary status',
|
||||
'++': 'Well known',
|
||||
'+': 'Known in community',
|
||||
'': 'Regular member',
|
||||
'-': 'Pretty sure did something important',
|
||||
'--': 'Mostly unnoticed',
|
||||
'---': 'Unknown'
|
||||
},
|
||||
|
||||
irritability: {
|
||||
'+++': 'Constantly angry',
|
||||
'++': 'Short temper',
|
||||
'+': 'Get annoyed easily',
|
||||
'': 'Normal temperament',
|
||||
'-': 'Patient',
|
||||
'--': 'Take everything in stride',
|
||||
'---': 'Nothing bothers me'
|
||||
},
|
||||
|
||||
magic: {
|
||||
'+++': 'Archmage level',
|
||||
'++': 'Powerful magic',
|
||||
'+': 'Competent magic user',
|
||||
'': 'Some magical ability',
|
||||
'-': 'Minor magic',
|
||||
'--': 'Magicians worry when near',
|
||||
'---': 'Magic has no effect'
|
||||
},
|
||||
|
||||
psyPower: {
|
||||
'+++': 'Master psychic',
|
||||
'++': 'Strong psychic',
|
||||
'+': 'Psychic abilities',
|
||||
'': 'Minor psychic talent',
|
||||
'-': 'Resistant to psionics',
|
||||
'--': 'Immune to psionics',
|
||||
'---': 'Psionics have no effect'
|
||||
},
|
||||
|
||||
technology: {
|
||||
'+++': 'Program in assembly',
|
||||
'++': 'Expert programmer',
|
||||
'+': 'Competent with tech',
|
||||
'': 'Normal tech skills',
|
||||
'-': 'Basic computer use',
|
||||
'--': 'Technology challenged',
|
||||
'---': 'What\'s a computer?'
|
||||
},
|
||||
|
||||
emotion: {
|
||||
'+++': 'Extremely affectionate',
|
||||
'++': 'Fairly free with hugs',
|
||||
'+': 'Affectionate',
|
||||
'': 'Normal emotional expression',
|
||||
'-': 'Reserved',
|
||||
'--': 'Emotionally distant',
|
||||
'---': 'Cold/unemotional'
|
||||
},
|
||||
|
||||
dragonFriend: {
|
||||
'+++!': "Have you noticed? You've started growing scales!",
|
||||
'+++': 'Popular with dragons - you exchange overnight visits',
|
||||
'++': 'Reasonably popular - you exchange social visits',
|
||||
'+': 'Polite acquaintance - you exchange social pleasantries',
|
||||
'': "Tolerance - they don't eat you, you don't try to slice them!",
|
||||
'-': 'Irritant - they think about having you over for lunch!',
|
||||
'--': 'Maddening - they think about a quick snack ... now!',
|
||||
'---': "Infuriating - you're not good enough for a snack",
|
||||
'---!': "Cold fury - they're going to hunt you, and find you, and..."
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user