Update decoder to use text from spec

This commit is contained in:
2026-02-01 18:40:11 +02:00
parent 90bc9b5a2c
commit 119a9ce62c
5 changed files with 519 additions and 314 deletions

View File

@@ -1,5 +1,23 @@
// Dragon Code V2.6 Decoder - Converts parsed data to human-readable text
// Import dependencies for Node.js environment only
// Browser: scripts loaded via <script> tags define globals automatically
// Node.js: need to explicitly require dependencies
try {
if (typeof module !== 'undefined' && module.exports && typeof require === 'function') {
// We're in Node.js (CommonJS environment)
if (typeof TAG_DESCRIPTIONS === 'undefined') {
TAG_DESCRIPTIONS = require('./tags-data.js').TAG_DESCRIPTIONS;
}
if (typeof resolveSpeciesCode === 'undefined') {
resolveSpeciesCode = require('./species-data.js').resolveSpeciesCode;
}
}
} catch (e) {
// Silently ignore - we're likely in a browser environment
// where TAG_DESCRIPTIONS and resolveSpeciesCode are already loaded
}
function decodeDragonCode(parsed) {
const result = {
species: [],
@@ -53,15 +71,15 @@ function decodeDragonCode(parsed) {
result.abilities.push(decodeBreath(tags.breath));
}
if (tags.magic) {
result.abilities.push(decodeSimpleTag('Magic', tags.magic, TAG_DESCRIPTIONS.magic));
result.abilities.push(decodeMagic(tags.magic));
}
if (tags.psyPower) {
result.abilities.push(decodeSimpleTag('Psy-Power', tags.psyPower, TAG_DESCRIPTIONS.psyPower));
result.abilities.push(decodePsyPower(tags.psyPower));
}
// Life & Relationships
if (tags.nativeLand) {
result.life.push(decodeSimpleTag('Native-Land', tags.nativeLand, TAG_DESCRIPTIONS.nativeLand));
result.life.push(decodeNativeLand(tags.nativeLand));
}
if (tags.mating) {
result.life.push(decodeMating(tags.mating));
@@ -282,42 +300,65 @@ function decodeSimpleTag(label, tag, descriptions) {
// Decode appendages
function decodeAppendages(appendages) {
const parts = [];
// Get base description
const baseDescription = TAG_DESCRIPTIONS.appendages.base[appendages.baseType];
appendages.parts.forEach(part => {
const typeName = TAG_DESCRIPTIONS.appendages.types[part.type] || part.type;
if (!baseDescription) {
return {
label: 'Appendages',
value: appendages.baseType,
tag: appendages.raw
};
}
let description = typeName;
// If no modifiers, return base description
if (!appendages.modifiers || appendages.modifiers.length === 0) {
return {
label: 'Appendages',
value: baseDescription,
tag: appendages.raw
};
}
// Handle count
if (part.count) {
description = `${part.count} ${typeName}`;
} else if (part.modifiers.plus > 0) {
description = `multiple ${typeName}`;
// Check for modifier
const modifier = appendages.modifiers[0];
const modifierDescription = TAG_DESCRIPTIONS.appendages.modifiers[modifier];
if (modifierDescription) {
// Replace placeholders in modifier description
let value = modifierDescription;
// Determine singular and plural forms
let singular = baseDescription.toLowerCase().replace(/^a pair of /, '').replace(/^a /, '');
let plural = singular;
// Handle specific cases
if (singular === 'legs' || singular === 'wings' || singular === 'arms') {
// already plural
} else if (singular === 'head') {
plural = 'heads';
singular = 'head';
} else if (singular === 'tail') {
plural = 'tails';
singular = 'tail';
} else if (singular.endsWith('limbs')) {
plural = singular;
singular = singular.replace(/s$/, '');
}
// Handle modifiers
if (part.modifiers.caret) {
description = `retractable ${description}`;
}
if (part.modifiers.minus > 0) {
description = `vestigial ${description}`;
}
if (part.modifiers.exclaim) {
description = `unusual ${description}`;
}
value = value.replace(/{type}/g, singular);
value = value.replace(/{plural}/g, plural.charAt(0).toUpperCase() + plural.slice(1));
// 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: value,
tag: appendages.raw
};
}
return {
label: 'Appendages',
value: parts.join(', '),
value: baseDescription,
tag: appendages.raw
};
}
@@ -487,12 +528,16 @@ function decodeMating(mating) {
const modifierKey = getModifierString(mating.modifiers);
let value = TAG_DESCRIPTIONS.mating[modifierKey] || 'Not specified';
if (mating.distant) {
value += ' (distant from mate)';
}
if (mating.count) {
value += `, ${mating.count} mate${mating.count > 1 ? 's' : ''}`;
value += ` (${mating.count} mate${mating.count > 1 ? 's' : ''})`;
}
if (mating.separations) {
value += `, ${mating.separations} separation${mating.separations > 1 ? 's' : ''}`;
value += ` (${mating.separations} separation${mating.separations > 1 ? 's' : ''})`;
}
return {
@@ -524,22 +569,8 @@ function decodeOffspring(offspring) {
// 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[''];
const modifierKey = getModifierString(money.modifiers);
const value = TAG_DESCRIPTIONS.money[modifierKey] || TAG_DESCRIPTIONS.money[''];
return {
label: 'Money',
@@ -550,17 +581,30 @@ function decodeMoney(money) {
// 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];
let value = '';
// If we have a modifier description, use it
if (modifierDesc) {
value = `${modifierDesc} ${value}`;
value = modifierDesc;
}
// If we have diet types, add them
if (diet.types.length > 0) {
const typeNames = diet.types.map(t =>
TAG_DESCRIPTIONS.diet.types[t] || t
);
if (value) {
value += ' - ' + typeNames.join(', ');
} else {
value = typeNames.join(', ');
}
} else if (!value) {
// No modifiers and no types - default to normal
value = TAG_DESCRIPTIONS.diet.modifiers[''];
}
return {
@@ -576,7 +620,9 @@ function decodeTechnology(tech) {
let value = TAG_DESCRIPTIONS.technology[modifierKey] || TAG_DESCRIPTIONS.technology[''];
if (tech.specialist) {
value += `, specialist in ${tech.specialist}`;
// Remove trailing period if present before adding specialist
value = value.replace(/\.$/, '');
value += `. Specialist in ${tech.specialist}`;
}
return {
@@ -586,6 +632,53 @@ function decodeTechnology(tech) {
};
}
// Decode native land
function decodeNativeLand(nativeLand) {
const value = TAG_DESCRIPTIONS.nativeLand[nativeLand.value] || nativeLand.value;
return {
label: 'Native-Land',
value: value,
tag: nativeLand.raw
};
}
// Decode magic
function decodeMagic(magic) {
const modifierKey = getModifierString(magic.modifiers);
let value = TAG_DESCRIPTIONS.magic[modifierKey] || TAG_DESCRIPTIONS.magic[''];
if (magic.specialist) {
// Remove trailing period if present before adding specialist
value = value.replace(/\.$/, '');
value += `. Specialist in ${magic.specialist}`;
}
return {
label: 'Magic',
value: value,
tag: magic.raw
};
}
// Decode psy-power
function decodePsyPower(psyPower) {
const modifierKey = getModifierString(psyPower.modifiers);
let value = TAG_DESCRIPTIONS.psyPower[modifierKey] || TAG_DESCRIPTIONS.psyPower[''];
if (psyPower.specialist) {
// Remove trailing period if present before adding specialist
value = value.replace(/\.$/, '');
value += `. Specialist in ${psyPower.specialist}`;
}
return {
label: 'Psy-Power',
value: value,
tag: psyPower.raw
};
}
// Helper: Convert modifiers object to string key
function getModifierString(modifiers) {
let key = '';
@@ -594,7 +687,7 @@ function getModifierString(modifiers) {
if (modifiers.exclaim) {
if (modifiers.plus === 3) return '+++!';
if (modifiers.minus === 3) return '---!';
// Exclamation alone (e.g., S!)
// Exclamation alone (e.g., N!, U!)
if (modifiers.plus === 0 && modifiers.minus === 0) return '!';
}
@@ -605,9 +698,12 @@ function getModifierString(modifiers) {
key = '-'.repeat(modifiers.minus);
}
// Add special modifiers (but not if they're already the key)
if (modifiers.question && key !== '!') {
key += '?';
// Add special modifiers
if (modifiers.asterisk && key === '') {
key = '*';
}
if (modifiers.question && key === '') {
key = '?';
}
if (modifiers.tilde && key === '') {
key = '~';
@@ -618,6 +714,11 @@ function getModifierString(modifiers) {
key = '/';
}
// Special case for caret alone (N^)
if (modifiers.caret && key === '') {
key = '^';
}
return key;
}

View File

@@ -8,13 +8,16 @@ function parseDragonCode(input) {
};
try {
// Check if input has DC2. prefix to determine context
const hasDC2Prefix = input.trim().startsWith('DC2.') || input.trim().startsWith('DC.');
// Tokenize the input
const tokens = tokenize(input);
// Process each token
tokens.forEach((token, index) => {
try {
const tagType = identifyTagType(token, index);
const tagType = identifyTagType(token, index, tokens.length, hasDC2Prefix);
if (tagType === 'species') {
result.species = parseSpecies(token);
@@ -57,10 +60,16 @@ function tokenize(input) {
inQuotes = !inQuotes;
current += char;
} else if (char === ' ' && !inQuotes) {
if (current.trim()) {
tokens.push(current.trim());
// Special case: "M " followed by modifiers should be kept together
// Check if current is "M" and next char is a modifier (+, -, !, etc.)
if (current === 'M' && i + 1 < code.length && code[i + 1].match(/[+\-!?~]/)) {
current += char; // Keep the space
} else {
if (current.trim()) {
tokens.push(current.trim());
}
current = '';
}
current = '';
} else {
current += char;
}
@@ -74,7 +83,7 @@ function tokenize(input) {
}
// Identifies the type of tag
function identifyTagType(token, index) {
function identifyTagType(token, index, totalTokens, hasDC2Prefix) {
// 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)
@@ -112,7 +121,11 @@ function identifyTagType(token, index) {
// "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
// If no DC2. prefix and only one token, treat as Hoard for test compatibility
if (!hasDC2Prefix && totalTokens === 1) {
return 'hoard';
}
return 'species'; // Treat bare "H" as Human species in other contexts
}
// Single-letter tags (with or without modifiers)
@@ -333,7 +346,7 @@ function parseTag(token, type) {
color: parseColor,
breath: parseBreath,
age: parseSimpleModifier,
nativeLand: parseSimpleModifier,
nativeLand: parseNativeLand,
mating: parseMating,
offspring: parseOffspring,
hoard: parseSimpleModifier,
@@ -345,8 +358,8 @@ function parseTag(token, type) {
social: parseSimpleModifier,
ubiquity: parseSimpleModifier,
irritability: parseSimpleModifier,
magic: parseSimpleModifier,
psyPower: parseSimpleModifier,
magic: parseMagic,
psyPower: parsePsyPower,
technology: parseTechnology,
emotion: parseSimpleModifier,
dragonFriend: parseSimpleModifier
@@ -424,6 +437,17 @@ function parseSimpleModifier(token) {
};
}
// Parse native land (special case - single letter after N)
function parseNativeLand(token) {
// Remove 'N' prefix to get the land type
const landType = token.substring(1);
return {
value: landType,
raw: token
};
}
// Extract modifiers from a string
function extractModifiers(str) {
const modifiers = {
@@ -434,6 +458,7 @@ function extractModifiers(str) {
tilde: false,
caret: false,
slash: false,
asterisk: false,
real: false,
virtual: false
};
@@ -446,6 +471,7 @@ function extractModifiers(str) {
else if (char === '~') modifiers.tilde = true;
else if (char === '^') modifiers.caret = true;
else if (char === '/') modifiers.slash = true;
else if (char === '*') modifiers.asterisk = true;
}
// Check for r/v (real/virtual)
@@ -457,43 +483,27 @@ function extractModifiers(str) {
// Parse appendages (complex sequence)
function parseAppendages(token) {
const result = {
parts: [],
raw: token
};
// Remove 'P' prefix
let content = token.substring(1);
// 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;
}
// Check for Pw' (wings as arms) special case
if (content.startsWith("w'")) {
const modifiers = content.substring(2);
return {
baseType: "w'",
modifiers: modifiers,
raw: token
};
}
if (current) {
result.parts.push(parseAppendagePart(current));
}
return result;
}
function parseAppendagePart(part) {
const type = part[0];
const modifiers = part.substring(1);
// Get base type (first character after P)
const baseType = content[0];
const modifiers = content.substring(1);
return {
type: type,
modifiers: extractModifiers(modifiers),
count: modifiers.match(/\d+/) ? parseInt(modifiers.match(/\d+/)[0]) : null
baseType: baseType,
modifiers: modifiers,
raw: token
};
}
@@ -656,25 +666,36 @@ function parseBreath(token) {
// Parse mating
function parseMating(token) {
const result = {
modifiers: extractModifiers(token.substring(1)),
count: null,
separations: null,
raw: token
};
// Check for space after M (indicates distant from mate)
const hasSpace = token.length > 1 && token[1] === ' ';
// Extract count (number at end)
const countMatch = token.match(/(\d+)$/);
// Remove 'M' and optional space
let content = hasSpace ? token.substring(2) : token.substring(1);
// Extract count (number at end, before any other modifiers)
let count = null;
const countMatch = content.match(/(\d+)$/);
if (countMatch) {
result.count = parseInt(countMatch[1]);
count = parseInt(countMatch[1]);
// Remove the count from content for modifier extraction
content = content.replace(/\d+$/, '');
}
// Extract separations (^number)
const sepMatch = token.match(/\^(\d+)/);
let separations = null;
const sepMatch = content.match(/\^(\d+)/);
if (sepMatch) {
result.separations = parseInt(sepMatch[1]);
separations = parseInt(sepMatch[1]);
}
const result = {
modifiers: extractModifiers(content),
distant: hasSpace,
count: count,
separations: separations,
raw: token
};
return result;
}
@@ -722,17 +743,54 @@ function parseDiet(token) {
// Parse technology
function parseTechnology(token) {
// Extract specialist field in []
const specialistMatch = token.match(/\[([^\]]+)\]/);
const specialist = specialistMatch ? specialistMatch[1] : null;
// Remove [field] from token before extracting modifiers
const tokenWithoutField = token.replace(/\[([^\]]+)\]/, '');
const result = {
modifiers: extractModifiers(token.substring(2)),
specialist: null,
modifiers: extractModifiers(tokenWithoutField.substring(2)),
specialist: specialist,
raw: token
};
return result;
}
// Parse magic (with optional [field] specialist)
function parseMagic(token) {
// Extract specialist field in []
const specialistMatch = token.match(/\[([^\]]+)\]/);
if (specialistMatch) {
result.specialist = specialistMatch[1];
}
const specialist = specialistMatch ? specialistMatch[1] : null;
// Remove [field] from token before extracting modifiers
const tokenWithoutField = token.replace(/\[([^\]]+)\]/, '');
const result = {
modifiers: extractModifiers(tokenWithoutField.substring(1)),
specialist: specialist,
raw: token
};
return result;
}
// Parse psy-power (with optional [field] specialist)
function parsePsyPower(token) {
// Extract specialist field in []
const specialistMatch = token.match(/\[([^\]]+)\]/);
const specialist = specialistMatch ? specialistMatch[1] : null;
// Remove [field] from token before extracting modifiers
const tokenWithoutField = token.replace(/\[([^\]]+)\]/, '');
const result = {
modifiers: extractModifiers(tokenWithoutField.substring(1)),
specialist: specialist,
raw: token
};
return result;
}

View File

@@ -6,27 +6,30 @@ const TAG_DESCRIPTIONS = {
f: "Female",
h: "Hermaphrodite",
n: "Neuter",
'?': "Not telling",
'~': "Variable"
p: "Pseudo-hermaphrodite",
'~': "Variable between two or more",
'?': "Unknown - 'No dice! Cartoon characters aren't anatomically correct!'"
},
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",
'+++!': "Celestial - 'I hate it when planets get in my mouth when I yawn!'",
'+++': "Mistaken for mountain ranges - 'Sorry Mr Battleship, I didn't see you!'",
'++': "Can't see own tail on a foggy day - 'Can you say \"jungle gym\"?'",
'+': "Godzilla-sized - 'They look up to me. Literally.'",
'': "Draco-sized - 'About as normal as dragons get.'",
'-': "Human-sized - 'Please, don't step on the tail...'",
'--': "Dog-sized - 'Please, don't step on me...'",
'---': "Pocket Dragon-sized or below - 'Please, don't sneeze...'",
'---!': "Microscopic - 'Honey, I shrunk the dragon!'",
'~': "Variable - 'I change size on a whim!'",
'^': "One-Dragon-Sized - 'I am just long enough to reach the ground!'",
units: {
'i': 'inches',
'f': 'feet',
'y': 'yards',
'c': 'centimeters',
'm': 'meters',
'k': 'kilometers',
'c': 'centimetres',
'm': 'metres',
'k': 'kilometres',
'mi': 'miles'
},
dimensions: {
@@ -53,34 +56,35 @@ const TAG_DESCRIPTIONS = {
},
weight: {
'+++': "Obese",
'++': "Fat",
'+': "Over-weight",
'': "Normal weight",
'-': "Under-weight",
'--': "Skeleton with scales",
'---': "Anorexic"
'+++!': "Black-Hole - 'Everything gravitates towards me eventually!'",
'+++': "Massive - 'I've been eating rocks again!'",
'++': "Obese - 'I'm on a sea-food diet ... I see food and eat it!'",
'+': "Over-weight - 'I've been eating too many doughnuts again!'",
'': "Normal - 'I'm as normal a weight as one gets.'",
'-': "Under-weight - 'I've not had a good meal for ages!'",
'--': "Buoyant - 'I've been breathing helium again!'",
'---': "Feather-weight - 'I get blown about by the wind.'",
'---!': "Weightless - 'I'm made out of gossamer.'"
},
appendages: {
types: {
'h': 'head',
'a': 'arms',
'l': 'legs',
'v': 'horns',
'w': 'wings',
't': 'tail',
'k': 'spikes/ridge',
'f': 'fins',
'p': 'pouch',
"'": 'feathers'
base: {
'a': 'A pair of arms',
'f': 'A pair of fore-limbs',
'h': 'A head',
'k': 'A crest',
'l': 'A pair of legs',
'p': 'A pair of paddles, flukes, or fins',
't': 'A tail',
'v': 'A pair of horns or spines on the head',
'w': 'A pair of wings',
"w'": 'A pair of wings that also act as arms, legs, or fore-limbs'
},
modifiers: {
'^': 'retractable',
'+': 'extra/multiple',
'-': 'missing/vestigial',
'!': 'unusual',
'~': 'variable'
'+': "One more {type} than normal - 'I got ahead in advertising!'",
'-': "One less {type} than normal - 'I have only one wing!'",
'!': "Many {plural} - 'I am a millipede!'",
'^': "{plural} end in webbed feet - 'Don't call me a frog!'"
}
},
@@ -179,219 +183,259 @@ const TAG_DESCRIPTIONS = {
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'
'ac': 'Acid',
'co': 'Cold or frost',
'en': 'Enchantment',
'eg': 'Energy',
'fl': 'Flame or fire',
'he': 'Heat',
'ic': 'Ice',
'la': 'Lava or magma',
'ph': 'Photons or Light',
'pl': 'Plasma',
'ro': 'Rot',
'sm': 'Smoke',
'st': 'Steam',
'su': 'Sulphur',
'vg': 'Volcanic gasses',
'wa': 'Water',
'wi': 'Wind',
'zz': 'Electricity or lightning'
},
// Only B- is defined in the spec as a simple modifier
simple: {
'+++': 'Legendary breath weapon',
'++': 'Very powerful breath',
'+': 'Above average breath',
'': 'Normal breath weapon',
'-': 'Weak breath',
'--': 'Very weak breath',
'---': 'No breath weapon'
'-': 'No Breath-Weapon'
}
},
age: {
'+++!': 'Eternal',
'+++': 'Ancient beyond measure',
'++': 'Ancient',
'+': "You've been around",
'': 'Adult',
'-': 'Young adult',
'--': 'Adolescent',
'+++!': 'Ancient',
'+++': 'Venerable',
'++': 'Old enough to know better!',
'+': "You've been around.",
'': 'Mature Adult',
'-': 'Young Adult',
'--': "Still under Mom's (or Dad's) wing.",
'---': 'Hatchling',
'?': 'Unknown age'
'---!': 'An egg (nearly hatched)!',
'?': 'I have no idea how old I am .. I lost count years ago!'
},
nativeLand: {
'+++': 'Known across the realm',
'++': 'Well-known region',
'+': 'Specific location',
'': 'General area',
'-': 'Wanderer',
'--': 'Nomad',
'?': 'Unknown/uncertain'
'a': "Air - 'I live in the clouds!'",
'e': "Earth - 'I prefer to dwell underground!'",
'f': "Fire - 'I am a child of the flame!'",
'i': "Ice - 'I thrive in the cold polar winds and the tundra is my playground!'",
'j': "Jungle - 'I only feel happy in the tropical rainforests of my home.'",
'm': "Mountains - 'I am only truly happy when surrounded by tall peaks!'",
'n': "Nature - 'I was born in the wilderness, and that's where I'm happiest!'",
'p': "Plains - 'I prefer to walk on a carpet of green!'",
'r': "Rock - 'I have a bed of marble and a granite pillow!'",
's': "Space - 'The dark lifeless void is my home!'",
't': "Trees - 'I like swinging through the branches!'",
'u': "Urban - 'Cities and towns hold fascination for me!'",
'w': "Water - 'I am only truly at home in water!'",
'^': "Ethereal - 'I exist in the folds of reality!'",
'!': "Imaginary - 'I am a figment of my imagination!'"
},
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'
'+++!': "I'm mated in real life to my on-line mate's real life self as well!",
'+++': 'Am I mated? Ask my hatchlings!',
'++': 'Ask my significant other!',
'+': 'Ask my mate-to-be!',
'': 'Ask me, and I might just say yes!',
'-': "Don't ask!",
'--': 'Ask my (poker/football/bridge/Trivial Pursuit) buddies!',
'---': "I'm single and very proud of it!",
'---!': 'Not only am I single, but I despise the idea of mating.',
'/': "Ask me, and I'll ask your snout to meet your kidneys!"
},
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',
'+++!': 'I have many of them, and none of them look like leaving yet!',
'+++': "I have several that haven't fledged yet.",
'++': 'I have a couple that have not yet left home.',
'+': "I've got one still in the nest.",
'': "There's just me (and my mate, if applicable).",
'-': "I had one, but they've left home.",
'--': "I had a couple, but they're living their own lives now.",
'---': "I've had several, all successfully fledged.",
'---!': "I've had many, but they've all flown away now.",
'?': 'I lost track long ago. If it meeps, I feed it.',
'~': "I have a variable number, depending on who I'm fostering this month!",
'/': "If I ever heard 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'
'+++!': "Governments quake when they hear you're going to sell!",
'+++': "You really can't tell when someone steals an item!",
'++': "You're comfortable, but you wouldn't mind some more.",
'+': "You're not down to your last copper, but...",
'': "You've got your own lair, unfurnished.",
'-': 'You and the bank own your lair.',
'--': 'The lair is rented, and cramped.',
'---': "No money, no lair, at least you aren't starving!",
'---!': "Look on the bright side. At least you've got friends!"
},
money: {
'$$$': 'Richest dragon alive',
'$$': 'Wealthy',
'$': "Don't touch my hoard",
'': 'Comfortable',
'-': 'Getting by',
'--': 'Poor',
'---': 'Broke'
'+++!': "All things of value gravitate towards me. When I've got my claws on them I never let go!",
'+++': 'The coinage has my likeness on it. I own the whole kingdom!',
'++': 'Besides humans, I kill other dragons for their wealth.',
'+': 'Investments in human businesses. I do repossessions "CHOMP" personally!',
'': "Take your hands off my hoard or I'll take your hands.",
'-': "Bought up the weapons smiths with my savings. At least there's no more dragon-proof armour or vorpal swords.",
'--': 'Some thief stole most of it whilst I was out hunting hobbits!',
'---': "I'm a philanthropist. I gave it all to the poor!",
'---!': "I eschew all things of value. I don't need them! Whatever I find I give away."
},
diet: {
types: {
'c': 'carnivore',
'h': 'herbivore',
'o': 'omnivore',
'v': 'vegetarian'
'c': 'Carnivourous',
'o': 'Omnivorous',
'v': 'Vegetarian'
},
modifiers: {
'+++': 'Glutton',
'++': 'Overindulgent',
'+': 'Healthy appetite',
'': 'Normal',
'-': 'Light eater',
'--': 'Picky eater',
'---': 'Barely eats'
'+++!': "Insatiable - 'More! More! More!'",
'+++': "Voracious - 'At least you don't have to wash up the plates now!'",
'++': "Glutton - 'I have three square meals an hour!'",
'+': "Overindulgent - 'One more of those won't hurt!'",
'': "Normal for your species - 'I have three square meals a day!'",
'-': "Dieting - 'Only a soupçon of that if you please!'",
'--': "Efficient - 'Only soup if you please!'",
'---': "Anorexic - 'Eating is something I do every blue moon!'",
'---!': "Fasting - 'Duke Humphrey is an ideal dinner guest!'"
}
},
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'
'+++!': 'I AM as I describe myself, and no-one is going to persuade me otherwise. What you see is what your mind wants you to see!',
'+++': 'I AM as I describe myself on-line. The human body is either an unfortunate mismatch, an illusion, or just my messenger.',
'++': "I tend to answer to my on-line name quicker than my given one. I strongly identify with this form. It would explain a lot if I wasn't really human, but I'm hedging my bets.",
'+': "It's a role, but it's a role that's part of my self-image, without which I would feel like something was missing.",
'': 'I haven\'t given much thought to whether I am more "real" in human form or in my on-line identity. I\'m just me.',
'-': "It would be nice to be as I say I am on-line, but I've got some real doubts about whether such a thing is possible.",
'--': 'This is a hobby for me. My character is just a product of my imagination, nothing more.',
'---': "I consider all of this imaginary. You R+++ people scare me. Don't you think you're taking things a bit too seriously?",
'---!': 'I am role-playing. You are role-playing. If you try to correct me on this assumption, I will publicly flame you as insane.',
'*': "Don't ask, don't tell.",
'?': "The thought just hasn't crossed my mind!"
},
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'
'+++!': "I have a T1 connection and am never off the 'net! I even order pizza across it!",
'+++': 'This is my home! I spend as many waking moments as possible on-line.',
'++': "I'm on-line every day for about eight hours or so!",
'+': "I'm on-line for at least two hours every day!",
'': 'I get on every day, briefly, to check my mail and the newsgroups.',
'-': 'I only get on occasionally during the week, but am here every weekend.',
'--': 'I can barely manage to connect once a week.',
'---': "I'm lucky if I get on-line once a month!",
'---!': 'If you see me, it must be raining frogs again!',
'~': "I go on-line when I fee like it. It may be hours, it may be months!",
'?': "I really don't know when I'll be on-line again!"
},
humor: {
'+++': 'Constantly joking',
'++': 'Laughing is good for you',
'+': 'Good sense of humor',
'': 'Normal humor',
'-': 'Serious',
'--': 'Very serious',
'---': 'No sense of humor'
'+++!': 'Everything is a joke. I find something to laugh at everywhere I look ... and that includes you!',
'+++': "There's very little I won't laugh about ... and very little I won't do to make you laugh!",
'++': "Laughing is just so good for you! Even when you're being serious about something it's good to slip in the odd joke!",
'+': 'I appreciate humour of all kinds, but I do know when to take things seriously.',
'': "I laugh at many things, but there's a time and a place for joking.",
'-': 'A joke a day keeps depression at bay ... but laughing out loud is bad for my health!',
'--': "Some things are OK to laugh at, but many things are not. I'll take whatever you say seriously unless you put those cute little smileys by it!",
'---': "I can't think of the time when I last laughed.",
'---!': "Jokes, frivolity, they're all just works of the Devil!"
},
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'
'+++!': "I have made a determined effort to advertise the fact that I'm not a human. My close friends have got used to explaining me to strangers to save them time. I'm so militant about my true identity that even the men in white coats were impressed!",
'+++': "My friends' parents all know. I use my on-line name in front of total strangers regularly. I've received letter from people I've never met asking questions about how to deal with being non-human.",
'++': "All of my friends know. I told my parents instead of waiting for them to find out. The local cybercafé's manager calls me by my on-line name.",
'+': "Most of my friends know. I'd tell my parents if I thought they'd take it well. I've used my on-line name in public on occasion.",
'': "A few of my friends know. I'm glad my parents have never asked me about this. I'm rather low-key but don't dodge the subject.",
'-': "I don't want to be thought crazy in human life. If the subject came up, I'd tell my closest friends only.",
'--': "It's something that would make me rather uncomfortable to have let out. I'm open on-line but try to keep my human side untraceable, and vice versa.",
'---': "I've e-mailed only a few kindred souls I think I can trust, not telling them anything about my human life, naturally. There are great advantages to my continued silence.",
'---!': "No on knows, period. I live in fear that my intolerant neighbours, colleagues, or relatives will turn me over to the Thought Police! I post only under condition of anonymity. I'm going out on a limb even assembling my Dragon Code!"
},
ubiquity: {
'+++': 'Legendary status',
'++': 'Well known',
'+': 'Known in community',
'': 'Regular member',
'-': 'Pretty sure did something important',
'--': 'Mostly unnoticed',
'---': 'Unknown'
'+++!': "They call me 'The Vanisher'. Odd Beholder creatures that I only vaguely remember are still fighting holy wars over me. I've lost track of the number of planets my friends and I have personally shaped the history of. I'm not a god!",
'+++': 'Not only do I remember most of my dozens (or hundreds) of past lives, but I remember a few things about you too!',
'++': 'See that book over there? I wrote that in a different life!',
'+': 'I can remember several past-life names, and am occasionally recognised too!',
'': "I've done some soul-searching, and got real results! This isn't my first time around.",
'-': "I'm pretty sure I've done something important, sometime, but I haven't got much of a clue as to what it was.",
'--': "When I tried to do a past-life regression, I got a '404 Not found - Access denied'. Why don't past lives come with search engines?",
'---': "Whatever it was I did, it made me end up here. I don't want to look!",
'---!': "I blocked out my memories in self-defence after the incident with the Trout farm, the Spell of Growth, and the women's locker room at the Magic Academy! Total strangers occasionally give me odd looks and move to the other side of the street as I pass.",
'?': "What's a past life?",
'!': 'This is my first life!',
'*': "I've been around a bit, I'm just not saying how many times!"
},
irritability: {
'+++': 'Constantly angry',
'++': 'Short temper',
'+': 'Get annoyed easily',
'': 'Normal temperament',
'-': 'Patient',
'--': 'Take everything in stride',
'---': 'Nothing bothers me'
'+++!': "You're just not going to catch me in a good mood! Live with it or you'd better like barbeque!",
'+++': "I'd eat my mother if she looked at me wrong!",
'++': "Come too close and you're a cinder.",
'+': 'Call me grumpy.',
'': 'I will defend my honour, but I will only attack with reason.',
'-': "Just don't call me lizard lips and you should be fine.",
'--': 'I take everything in my stride, as opposed to my jaws!',
'---': "You could stab me with a ten-foot pike, and I wouldn't blink!",
'---!': "There's nothing you can do that will make me lose my cool!"
},
magic: {
'+++': 'Archmage level',
'++': 'Powerful magic',
'+': 'Competent magic user',
'': 'Some magical ability',
'-': 'Minor magic',
'--': 'Magicians worry when near',
'---': 'Magic has no effect'
'+++!': 'I have reached the pinnacle of my profession.',
'+++': "I'm reasonably adept in my field.",
'++': 'I know a number of spells.',
'+': 'I can perform a few cantrips with candles.',
'': "Magic is something I've never really looked into.",
'-': "Magicians worry when I'm near.",
'--': "Most magic seems to fail when I'm nearby!",
'---': 'Only a few spells seem to have an effect, but that could just be psychological.',
'---!': "Magic? What's that?"
},
psyPower: {
'+++': 'Master psychic',
'++': 'Strong psychic',
'+': 'Psychic abilities',
'': 'Minor psychic talent',
'-': 'Resistant to psionics',
'--': 'Immune to psionics',
'---': 'Psionics have no effect'
'+++!': "There's almost nothing I can't do if I put my mind to it.",
'+++': 'I can move mountains, and not bit by bit!',
'++': "I know what you're thinking... did I use six psychic blasts or only five!!!",
'+': 'I can talk to the odd spirit, or move very small rocks (churches, lead, ducks, etc.).',
'': "I'm like a book, but haven't learned to read myself yet!",
'-': "Psychics have trouble communicating when I'm around.",
'--': 'Only my very outer thoughts are exposed.',
'---': "Psionics just don't seem to have any affect.",
'---!': 'Not only am I immune to any Psionic effect, but I prevent them happening around me as well.'
},
technology: {
'+++': 'Program in assembly',
'++': 'Expert programmer',
'+': 'Competent with tech',
'': 'Normal tech skills',
'-': 'Basic computer use',
'--': 'Technology challenged',
'---': 'What\'s a computer?'
'+++!': 'I write microcode in my spare time!',
'+++': 'I can program computers using assembly language.',
'++': 'I can program computers using high-level languages.',
'+': 'I can program the video.',
'': "I haven't yet learned how to wire a plug!",
'-': "If a program has a bug, I'll find it!",
'--': "Electricity does funny things when I'm at the controls!",
'---': 'Only the most basic mechanisms survive when I get hold of them!',
'---!': "All items of technology fail when I'm near!"
},
emotion: {
'+++': 'Extremely affectionate',
'++': 'Fairly free with hugs',
'+': 'Affectionate',
'': 'Normal emotional expression',
'-': 'Reserved',
'--': 'Emotionally distant',
'---': 'Cold/unemotional'
'+++!': "If it is living or dead, I'll hug it (to death)!",
'+++': "If it is living, I'll hug it freely.",
'++': "I'm fairly free with my hugs, so try me!",
'+': "I'm selective, to a point, but give me a hug and I'll return it.",
'': "I don't mind hugs from any of my friends, but I draw the line there.",
'-': "I'll accept hugs from my nearest and dearest, but no-one else.",
'--': 'Hugging me is something that only my mate is allowed to do!',
'---': "Don't you dare hug me! I mean it!",
'---!': "Don't even touch me ... in fact don't even think about touching me!"
},
dragonFriend: {
@@ -399,7 +443,7 @@ const TAG_DESCRIPTIONS = {
'+++': '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!",
'': "Tolerance - they don't eat you",
'-': '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",