initial import of legacy npc codebase
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
from npc.languages import abyssal
|
||||
from npc.languages import celestial
|
||||
from npc.languages import common
|
||||
from npc.languages import draconic
|
||||
from npc.languages import dwarvish
|
||||
from npc.languages import elven
|
||||
from npc.languages import gnomish
|
||||
from npc.languages import halfling
|
||||
from npc.languages import infernal
|
||||
from npc.languages import orcish
|
||||
from npc.languages import undercommon
|
||||
|
||||
|
||||
__ALL__ = [
|
||||
'abyssal',
|
||||
'base',
|
||||
'celestial',
|
||||
'common',
|
||||
'draconic',
|
||||
'dwarvish',
|
||||
'elven',
|
||||
'gnomish',
|
||||
'halfling',
|
||||
'infernal',
|
||||
'orcish',
|
||||
'undercommon',
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
from npc.languages.base import BaseLanguage
|
||||
import re
|
||||
|
||||
|
||||
class Abyssal(BaseLanguage):
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u', 'î', 'ê', 'â', 'û', 'ô', 'ä', 'ö', 'ü', 'äu', 'ȧ', 'ė', 'ị', 'ȯ', 'u̇']
|
||||
|
||||
consonants = ['c', 'g', 'j', 'k', 'p', 'ss', 't']
|
||||
|
||||
syllable_template = ('V', 'v', 'c', 'v')
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{5}|' +
|
||||
r'[' + ''.join(consonants) + ']{3}'
|
||||
)
|
||||
|
||||
syllable_weights = [3, 2]
|
||||
|
||||
minimum_length = 2
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
t = ''.join(sequence)
|
||||
|
||||
if self._invalid_sequences.match(t):
|
||||
self._logger.debug(f"Invalid sequence: {t}")
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,194 @@
|
||||
import random
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
grapheme = namedtuple('Grapheme', ['char', 'weight'])
|
||||
|
||||
|
||||
class LanguageException(Exception):
|
||||
"""
|
||||
Thrown when language validators fail.
|
||||
"""
|
||||
|
||||
|
||||
class SyllableFactory:
|
||||
|
||||
def __init__(self, template, weights, prefixes, vowels, consonants, suffixes, affixes):
|
||||
self.template = template
|
||||
self.weights = weights
|
||||
self.grapheme = {
|
||||
'chars': {
|
||||
'p': [x.char for x in prefixes],
|
||||
'c': [x.char for x in consonants],
|
||||
'v': [x.char for x in vowels],
|
||||
's': [x.char for x in suffixes],
|
||||
'a': [x.char for x in affixes]
|
||||
},
|
||||
'weights': {
|
||||
'p': [x.weight for x in prefixes],
|
||||
'c': [x.weight for x in consonants],
|
||||
'v': [x.weight for x in vowels],
|
||||
's': [x.weight for x in suffixes],
|
||||
'a': [x.weight for x in affixes]
|
||||
}
|
||||
}
|
||||
|
||||
def _filtered_graphemes(self, key):
|
||||
return [(k, v) for (k, v) in self.grapheme['chars'].items() if k in key]
|
||||
|
||||
def graphemes(self, key='apcvs'):
|
||||
for _, chars in self._filtered_graphemes(key):
|
||||
for char in chars:
|
||||
yield char
|
||||
|
||||
def is_valid(self, chars, key='apcvs'):
|
||||
for grapheme_type, _ in self._filtered_graphemes(key):
|
||||
if chars in self.grapheme['chars'][grapheme_type]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Generate a single syllable
|
||||
"""
|
||||
syllable = ''
|
||||
for t in self.template:
|
||||
if t.islower() and random.random() < 0.5:
|
||||
continue
|
||||
if '|' in t:
|
||||
t = random.choice(t.split('|'))
|
||||
t = t.lower()
|
||||
syllable = syllable + random.choices(self.grapheme['chars'][t], self.grapheme['weights'][t])[0]
|
||||
return syllable
|
||||
|
||||
def __str__(self):
|
||||
return self.get()
|
||||
|
||||
|
||||
class WordFactory:
|
||||
|
||||
def __init__(self, language):
|
||||
self.language = language
|
||||
|
||||
def random_syllable_count(self):
|
||||
return 1 + random.choices(range(len(self.language.syllable.weights)), self.language.syllable.weights)[0]
|
||||
|
||||
def get(self):
|
||||
|
||||
total_syllables = self.random_syllable_count()
|
||||
seq = []
|
||||
while not self.language.validate_sequence(seq, total_syllables):
|
||||
seq = [self.language.syllable.get()]
|
||||
while len(seq) < total_syllables - 2:
|
||||
seq.append(self.language.syllable.get())
|
||||
if len(seq) < total_syllables:
|
||||
seq.append(self.language.syllable.get())
|
||||
return ''.join(seq)
|
||||
|
||||
def __str__(self):
|
||||
return self.get()
|
||||
|
||||
|
||||
class BaseLanguage:
|
||||
"""
|
||||
Words are created by combining syllables selected from random phonemes according to templates, each containing one
|
||||
or more of the following grapheme indicators:
|
||||
|
||||
c - an optional consonant
|
||||
C - a required consonant
|
||||
v - an optional vowel
|
||||
V - a required consonant
|
||||
|
||||
The simplest possible syllable consists of a single grapheme, and the simplest possible word a single syllable.
|
||||
|
||||
Words can also be generated from affixes; these are specified by the special template specifiers 'a'/'A'.
|
||||
|
||||
Examples:
|
||||
|
||||
('c', 'V') - a syllable consisting of exactly one vowel, possibly preceeded by a single consonant
|
||||
('C', 'c', 'V', 'v') - a syllable consisting of one or two consonants followed by one or two vowels
|
||||
('a', 'C', 'V') - a syllable consisting of an optional affix, a consonant and a vowel.
|
||||
|
||||
Word length is determined by the number of syllables, which is chosen at random using relative weights:
|
||||
|
||||
[2, 2, 1] - Names may contain one, two or three syllables, but are half as likely to contain three.
|
||||
[0, 1] - Names must have exactly two syllables
|
||||
"""
|
||||
|
||||
affixes = []
|
||||
vowels = []
|
||||
consonants = []
|
||||
|
||||
prefixes = vowels + consonants
|
||||
suffixes = vowels + consonants
|
||||
|
||||
syllable_template = ('C', 'V')
|
||||
syllable_weights = [1, 1]
|
||||
|
||||
minimum_length = 3
|
||||
|
||||
def __init__(self):
|
||||
self._logger = logging.getLogger()
|
||||
|
||||
self.syllable = SyllableFactory(
|
||||
template=self.syllable_template,
|
||||
weights=self.syllable_weights,
|
||||
prefixes=[grapheme(char=c, weight=1) for c in self.__class__.prefixes],
|
||||
suffixes=[grapheme(char=c, weight=1) for c in self.__class__.suffixes],
|
||||
vowels=[grapheme(char=c, weight=1) for c in self.__class__.vowels],
|
||||
consonants=[grapheme(char=c, weight=1) for c in self.__class__.consonants],
|
||||
affixes=[grapheme(char=c, weight=1) for c in self.__class__.affixes]
|
||||
)
|
||||
|
||||
|
||||
def _valid_syllable(self, syllable, text, key='apcvs', reverse=False):
|
||||
length = 0
|
||||
for seq in reverse(sorted(syllable.graphemes(key=key), key=len)):
|
||||
length = len(seq)
|
||||
substr = text[-1 * length:] if reverse else text[0:length]
|
||||
if substr == seq:
|
||||
return length
|
||||
return False
|
||||
|
||||
def is_valid(self, text):
|
||||
|
||||
for part in text.lower().split(' '):
|
||||
|
||||
if part in self.affixes:
|
||||
continue
|
||||
|
||||
if len(part) < self.minimum_length:
|
||||
self._logger.debug(f"'{part}' too short; must be {self.minimum_length} characters.")
|
||||
return False
|
||||
|
||||
first_offset = self._valid_syllable(self.syllable, text=part, key='p')
|
||||
if first_offset is False:
|
||||
self._logger.debug(f"'{part}' is not a valid syllable.")
|
||||
return False
|
||||
|
||||
last_offset = self._valid_syllable(self.last_syllable, text=part, key='s', reverse=True)
|
||||
if last_offset is False:
|
||||
self._logger.debug(f"'{part}' is not a valid syllable.")
|
||||
return False
|
||||
last_offset = len(part) - last_offset
|
||||
|
||||
while first_offset < last_offset:
|
||||
middle = part[first_offset:last_offset]
|
||||
new_offset = self._valid_syllable(self.syllable, text=middle, key='cv')
|
||||
if new_offset is False:
|
||||
self._logger.debug(f"'{middle}' is not a valid middle sequence.")
|
||||
return False
|
||||
first_offset = first_offset + new_offset
|
||||
return True
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
return len(''.join(sequence)) > self.minimum_length
|
||||
|
||||
def word(self):
|
||||
return WordFactory(language=self)
|
||||
|
||||
def place(self):
|
||||
return self.word()
|
||||
|
||||
def person(self):
|
||||
return (self.word(), self.word())
|
||||
@@ -0,0 +1,32 @@
|
||||
from npc.languages.base import BaseLanguage
|
||||
import re
|
||||
|
||||
|
||||
class Celestial(BaseLanguage):
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u', 'î', 'ê', 'â', 'û', 'ô', 'ä', 'ö', 'ü', 'äu', 'ȧ', 'ė', 'ị', 'ȯ', 'u̇']
|
||||
|
||||
consonants = ['b', 'sc', 'f', 'h', 'l', 'm', 'n', 'r', 's', 'v']
|
||||
|
||||
syllable_template = ('V', 'v', 'c', 'c', 'v', 'v')
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{5}|' +
|
||||
r'[' + ''.join(consonants) + ']{3}'
|
||||
)
|
||||
|
||||
syllable_weights = [3, 2]
|
||||
|
||||
minimum_length = 5
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
t = ''.join(sequence)
|
||||
|
||||
if self._invalid_sequences.match(t):
|
||||
self._logger.debug(f"Invalid sequence: {t}")
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,94 @@
|
||||
import re
|
||||
import random
|
||||
from npc.languages.base import BaseLanguage, WordFactory
|
||||
|
||||
|
||||
class Common(BaseLanguage):
|
||||
vowels = ['a', 'e', 'i', 'o', 'u']
|
||||
|
||||
consonants = [
|
||||
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't',
|
||||
'v', 'w', 'x', 'y', 'z'
|
||||
]
|
||||
|
||||
_middle_clusters = re.compile(
|
||||
r'bs|ct|ch|ck|dd|ff|gh|gs|ms|ns|ps|qu|rb|rd|rf|rk|rl|rm|rn|rp|rs|rt|ry' +
|
||||
r'|sh|sk|ss|st|sy|th|tk|ts|tt|ty|ws|yd|yk|yl|ym|yn|yp|yr|ys|yt|yz|mcd' +
|
||||
r'|[' + ''.join(vowels) + '][' + ''.join(consonants) + ']' +
|
||||
r'|[' + ''.join(consonants) + '][' + ''.join(vowels) + ']' +
|
||||
r'|[' + ''.join(vowels) + ']{1,2}'
|
||||
)
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{3}|' +
|
||||
r'[' + ''.join(consonants) + ']{4}'
|
||||
)
|
||||
|
||||
suffixes = [
|
||||
'ad', 'ed', 'id', 'od', 'ud',
|
||||
'af', 'ef', 'if', 'of', 'uf',
|
||||
'ah', 'eh', 'ih', 'oh', 'uh',
|
||||
'al', 'el', 'il', 'ol', 'ul',
|
||||
'am', 'em', 'im', 'om', 'um',
|
||||
'an', 'en', 'in', 'on', 'un',
|
||||
'ar', 'er', 'ir', 'or', 'ur',
|
||||
'as', 'es', 'is', 'os', 'us',
|
||||
'at', 'et', 'it', 'ot', 'ut',
|
||||
'ax', 'ex', 'ix', 'ox', 'ux',
|
||||
'ay', 'ey', 'iy', 'oy', 'uy',
|
||||
'az', 'ez', 'iz', 'oz', 'uz',
|
||||
]
|
||||
|
||||
prefixes = [s[::-1] for s in suffixes]
|
||||
|
||||
affixes = []
|
||||
|
||||
syllable_template = ('p', 'c|v', 's')
|
||||
|
||||
minimum_length = 1
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
t = ''.join(sequence)
|
||||
|
||||
if self._invalid_sequences.match(t):
|
||||
self._logger.debug(f"Invalid sequence: {t}")
|
||||
return False
|
||||
|
||||
for pos in range(len(t)):
|
||||
seq = t[pos:pos+2]
|
||||
if len(seq) != 2:
|
||||
return True
|
||||
if not self._middle_clusters.match(seq):
|
||||
self._logger.debug(f"Invalid sequence: {seq}")
|
||||
return False
|
||||
|
||||
|
||||
class CommonSurname(Common):
|
||||
|
||||
syllable_template = ('P|C', 'v', 'S')
|
||||
syllable_weights = [1]
|
||||
|
||||
word_suffixes = [
|
||||
'berg', 'borg', 'borough', 'bury', 'berry', 'by', 'ford', 'gard', 'grave', 'grove', 'gren', 'hardt', 'hart',
|
||||
'heim', 'holm', 'land', 'leigh', 'ley', 'ly', 'lof', 'love', 'lund', 'man', 'mark', 'ness', 'olf', 'olph',
|
||||
'quist', 'rop', 'rup', 'stad', 'stead', 'stein', 'strom', 'thal', 'thorpe', 'ton', 'vall', 'wich', 'win',
|
||||
'some', 'smith', 'bridge', 'cope', 'town', 'er', 'don', 'den', 'dell', 'son',
|
||||
]
|
||||
|
||||
def word(self):
|
||||
return str(WordFactory(self)) + random.choice(self.word_suffixes)
|
||||
|
||||
|
||||
class CommonPerson(Common):
|
||||
|
||||
syllable_template = ('p', 'C', 'V', 's')
|
||||
syllable_weights = [3, 1]
|
||||
|
||||
minimum_length = 2
|
||||
|
||||
def person(self):
|
||||
return (WordFactory(language=self), CommonSurname().word())
|
||||
@@ -0,0 +1,67 @@
|
||||
from npc.languages.base import BaseLanguage, WordFactory
|
||||
import random
|
||||
import re
|
||||
|
||||
|
||||
class Draconic(BaseLanguage):
|
||||
|
||||
vowels = ["a'", "aa", "ah", "e'", "ee", "ei", "ey", "i'", "ii", "ir", "o'", "u'", "uu"]
|
||||
|
||||
consonants = [
|
||||
'd', 'f', 'g', 'h', 'j', 'k', 'l',
|
||||
'n', 'r', 's', 't', 'v', 'x', 'y', 'z',
|
||||
]
|
||||
|
||||
syllable_template = ('C', 'V')
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{3}|' +
|
||||
r'[' + ''.join(consonants) + ']{4}'
|
||||
)
|
||||
|
||||
syllable_weights = [0, 0, 1, 2, 2, 1]
|
||||
|
||||
minimum_length = 3
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
t = ''.join(sequence)
|
||||
|
||||
if self._invalid_sequences.match(t):
|
||||
self._logger.debug(f"Invalid sequence: {t}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class Dragon(Draconic):
|
||||
|
||||
syllable_template = ('v', 'C', 'V')
|
||||
syllable_weights = [0, 1, 2]
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u']
|
||||
last_vowels = vowels
|
||||
last_consonants = ['th', 'x', 'ss', 'z']
|
||||
|
||||
minimum_length = 2
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(last_vowels) + ']{2}|' +
|
||||
r'[' + ''.join(Draconic.consonants) + ']{2}'
|
||||
)
|
||||
|
||||
def names(self):
|
||||
prefix = str(WordFactory(self))
|
||||
suffix = ''
|
||||
while not self.validate_sequence(suffix, 1):
|
||||
suffix = ''.join([
|
||||
random.choice(self.last_vowels),
|
||||
random.choice(self.last_consonants),
|
||||
random.choice(['us', 'ux', 'as', 'ax', 'is', 'ix', 'es', 'ex'])
|
||||
|
||||
])
|
||||
return [prefix + suffix]
|
||||
|
||||
person = names
|
||||
@@ -0,0 +1,42 @@
|
||||
import random
|
||||
|
||||
from npc.languages.base import BaseLanguage
|
||||
|
||||
|
||||
class Dwarvish(BaseLanguage):
|
||||
|
||||
consonants = [
|
||||
'b', 'p', 'ph', 'd', 't', 'th', 'j', 'c', 'ch', 'g', 'k', 'kh', 'v', 'f', 'z', 's', 'zh', 'sh', 'hy', 'h', 'r',
|
||||
'l', 'y', 'w', 'm', 'n'
|
||||
]
|
||||
|
||||
vowels = [
|
||||
'a', 'e', 'i', 'o', 'u', 'î', 'ê', 'â', 'û', 'ô'
|
||||
]
|
||||
|
||||
affixes = []
|
||||
|
||||
first_consonants = consonants
|
||||
first_vowels = vowels
|
||||
first_affixes = affixes
|
||||
|
||||
last_vowels = vowels
|
||||
last_consonants = consonants
|
||||
last_affixes = affixes
|
||||
|
||||
syllable_template = ('C', 'V', 'c')
|
||||
syllable_weights = [4, 1]
|
||||
|
||||
name_suffixes = ['son', 'sson', 'zhon', 'dottir', 'dothir', 'dottyr']
|
||||
|
||||
def person(self):
|
||||
words = super().person()
|
||||
suffix = random.choice(Dwarvish.name_suffixes)
|
||||
return (str(words[0]), f"{words[1]}{suffix}")
|
||||
|
||||
def is_valid(self, text):
|
||||
for suffix in self.name_suffixes:
|
||||
if text.endswith(suffix):
|
||||
text = text[0:len(suffix)]
|
||||
break
|
||||
return super().is_valid(text)
|
||||
@@ -0,0 +1,166 @@
|
||||
import random
|
||||
import re
|
||||
|
||||
from npc.languages.base import BaseLanguage, WordFactory
|
||||
|
||||
|
||||
class Elven(BaseLanguage):
|
||||
"""
|
||||
Phonetics for the Elven language in Telisar. Inspired by Tolkein's Quenya language, but with naming conventions
|
||||
following Twirrim's conventions in-game.
|
||||
"""
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u']
|
||||
consonants = ['b', 'c', 'd', 'f', 'g', 'h', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'y', 'z']
|
||||
affixes = []
|
||||
|
||||
first_vowels = ['a', 'e', 'i', 'o', 'u', 'y']
|
||||
first_consonants = ['c', 'g', 'l', 'm', 'n', 'r', 's', 't', 'v', 'z']
|
||||
first_affixes = []
|
||||
|
||||
last_vowels = ['a', 'i', 'e']
|
||||
last_consonants = ['t', 's', 'm', 'n', 'l', 'r', 'd', 'a', 'th']
|
||||
last_affixes = []
|
||||
|
||||
syllable_template = ('c', 'v', 'c', 'V', 'C', 'v')
|
||||
minimum_length = 4
|
||||
|
||||
_valid_consonant_sequences = [
|
||||
'cc', 'ht', 'kd', 'kl', 'km', 'kp', 'kt', 'kv', 'kw', 'ky', 'lc', 'ld',
|
||||
'lf', 'll', 'lm', 'lp', 'lt', 'lv', 'lw', 'ly', 'mb', 'mm', 'mp', 'my',
|
||||
'nc', 'nd', 'ng', 'nn', 'nt', 'nw', 'ny', 'ps', 'pt', 'rc', 'rd', 'rm',
|
||||
'rn', 'rp', 'rr', 'rs', 'rt', 'rw', 'ry', 'sc', 'ss', 'ts', 'tt', 'th',
|
||||
'tw', 'ty'
|
||||
]
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{3}|' +
|
||||
r'[' + ''.join(consonants) + ']{4}'
|
||||
)
|
||||
|
||||
def validate_sequence(self, sequence, *args, **kwargs):
|
||||
"""
|
||||
Ensure the specified sequence of syllables results in valid letter combinations.
|
||||
"""
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
# the whole string must be checked against the invalid sequences pattern
|
||||
chars = ''.join(sequence)
|
||||
if self._invalid_sequences.match(chars):
|
||||
self._logger.debug(f"Invalid sequence: {chars}")
|
||||
return False
|
||||
|
||||
# Now step through the sequence, two letters at a time, and verify that
|
||||
# all pairs of consonants are valid.
|
||||
for offset in range(0, len(chars), 2):
|
||||
seq = chars[offset:2]
|
||||
if not seq:
|
||||
break
|
||||
if seq[0] in self.consonants and seq[1] in self.consonants:
|
||||
if seq not in self._valid_consonant_sequences:
|
||||
self._logger.debug(f"Invalid sequence: {seq}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ElvenPlaceName(Elven):
|
||||
"""
|
||||
Place names are a restricted subset of Elven; the initial syllables are constructed as normal, but place names
|
||||
end in a sequence consisting of exactly one vowel and one consonant.
|
||||
"""
|
||||
syllable_template = ('v', 'C', 'v')
|
||||
syllable_weights = [2, 1]
|
||||
first_consonants = Elven.first_consonants + ['q']
|
||||
|
||||
minimum_length = 2
|
||||
|
||||
affixes = ['el']
|
||||
|
||||
def word(self):
|
||||
prefix = str(WordFactory(self))
|
||||
suffix = []
|
||||
while not self.validate_sequence(suffix):
|
||||
suffix = [
|
||||
random.choice(self.last_vowels),
|
||||
random.choice(self.last_consonants + ['ss']),
|
||||
]
|
||||
return prefix + ''.join(suffix)
|
||||
|
||||
def full_name(self):
|
||||
return 'el '.join(self.names)
|
||||
|
||||
|
||||
class HighElvenSurname(Elven):
|
||||
"""
|
||||
High Elven names follow the same naming conventions as more modern names, but ancient place names were longer, and
|
||||
suffixes always followed a pattern of vowel, consonant, two vowels, and a final consonant, but the rules for
|
||||
each are much more restrictive. In practice just a few suffixes are permitted: ieth, ies, ier, ien, iath, ias, iar,
|
||||
ian, ioth, ios, ior, and ion.
|
||||
"""
|
||||
|
||||
syllable_template = ('v', 'C', 'v')
|
||||
syllable_weights = [1, 2, 2]
|
||||
minimum_length = 2
|
||||
|
||||
def word(self):
|
||||
prefix = str(WordFactory(self))
|
||||
suffix = ''
|
||||
while not self.validate_sequence(suffix):
|
||||
suffix = ''.join([
|
||||
random.choice(self.last_vowels),
|
||||
random.choice(self.last_consonants + ['ss']),
|
||||
random.choice([
|
||||
'ie',
|
||||
'ia',
|
||||
'io',
|
||||
]),
|
||||
random.choice(['th', 's', 'r', 'n'])
|
||||
])
|
||||
return prefix + suffix
|
||||
|
||||
|
||||
class ElvenPerson(Elven):
|
||||
"""
|
||||
A modern Elven name. Surnames follow the same convention as High Elven in including place names, though over time
|
||||
the social function of denoting where renown was earned has been lost. An elf who names himself "am Uman", for
|
||||
example, would be seen as either foolish or obnoxious, or both. Like "Johnny New York."
|
||||
"""
|
||||
|
||||
syllable_template = ('c', 'V', 'C', 'v')
|
||||
syllable_weights = [1, 2]
|
||||
|
||||
last_affixes = ['am', 'an', 'al', 'um']
|
||||
|
||||
def place(self):
|
||||
return ElvenPlaceName().word()
|
||||
|
||||
def word(self):
|
||||
return (
|
||||
super().word(),
|
||||
random.choice(self.last_affixes),
|
||||
self.place()
|
||||
)
|
||||
|
||||
person = word
|
||||
|
||||
|
||||
class HighElvenPerson(ElvenPerson):
|
||||
"""
|
||||
Given names in High Elven and modern Elven follow the same conventions, but a High Elven surname is generally
|
||||
chosen by the individual, to indicate "the place where renown is earned." So the High-Elven Elstuviar am
|
||||
Vakaralithien implies a place or organization named Vakarlithien where the elf Elstuviar was first recognized by
|
||||
their peers for worthy accompliments.
|
||||
"""
|
||||
syllable_weights = [2, 2, 2]
|
||||
|
||||
def word(self):
|
||||
return (
|
||||
super(Elven, self).word(),
|
||||
random.choice(self.last_affixes),
|
||||
HighElvenSurname().word()
|
||||
)
|
||||
|
||||
person = word
|
||||
@@ -0,0 +1,24 @@
|
||||
from npc.languages.base import BaseLanguage
|
||||
|
||||
|
||||
class Gnomish(BaseLanguage):
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u', 'y']
|
||||
consonants = ['b', 'd', 'f', 'g', 'h', 'j', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'z']
|
||||
affixes = []
|
||||
|
||||
first_vowels = vowels
|
||||
first_consonants = consonants
|
||||
first_affixes = affixes
|
||||
|
||||
last_vowels = ['a', 'e', 'i', 'o', 'y']
|
||||
last_consonants = consonants
|
||||
last_affixes = affixes
|
||||
|
||||
syllable_template = ('C', 'V', 'v')
|
||||
syllable_weights = [3, 1]
|
||||
|
||||
minimum_length = 1
|
||||
|
||||
def person(self):
|
||||
return (self.word(), self.word())
|
||||
@@ -0,0 +1,53 @@
|
||||
from npc.languages.base import BaseLanguage
|
||||
|
||||
|
||||
class Halfling(BaseLanguage):
|
||||
|
||||
vowels = ["a'", "e'", "i'" "o'", 'a', 'e', 'i', 'o', 'y']
|
||||
consonants = ['b', 'd', 'f', 'g', 'h', 'j', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'z']
|
||||
affixes = []
|
||||
|
||||
first_vowels = vowels
|
||||
first_consonants = consonants
|
||||
first_affixes = affixes
|
||||
|
||||
last_vowels = ['a', 'e', 'i', 'o', 'y']
|
||||
last_consonants = consonants
|
||||
last_affixes = affixes
|
||||
|
||||
syllable_template = ('c', 'V')
|
||||
syllable_weights = [0, 1, 2, 3, 2, 1]
|
||||
|
||||
nicknames = [
|
||||
'able', 'clean', 'enthusiastic', 'heartening', 'meek', 'reasonable', 'talented',
|
||||
'accommodating', 'clever', 'ethical', 'helpful', 'meritorious', 'refined', 'temperate',
|
||||
'accomplished', 'commendable', 'excellent', 'moral', 'reliable', 'terrific',
|
||||
'adept', 'compassionate', 'exceptional', 'honest', 'neat', 'remarkable', 'tidy',
|
||||
'admirable', 'composed', 'exemplary', 'honorable', 'noble', 'resilient', 'quality',
|
||||
'agreeable', 'considerate', 'exquisite', 'hopeful', 'obliging', 'respectable', 'tremendous',
|
||||
'amazing', 'consummate', 'extraordinary', 'humble', 'observant', 'respectful', 'trustworthy',
|
||||
'appealing', 'cooperative', 'fabulous', 'important', 'optimistic', 'resplendent', 'trusty',
|
||||
'astute', 'correct', 'faithful', 'impressive', 'organized', 'responsible', 'truthful',
|
||||
'attractive', 'courageous', 'fantastic', 'incisive', 'outstanding', 'robust', 'unbeatable',
|
||||
'awesome', 'courteous', 'fascinating', 'incredible', 'peaceful', 'selfless', 'understanding',
|
||||
'beautiful', 'dazzling', 'fine', 'innocent', 'perceptive', 'sensational', 'unequaled',
|
||||
'benevolent', 'decent', 'classy', 'insightful', 'perfect', 'sensible', 'unparalleled',
|
||||
'brave', 'delightful', 'fortitudinous', 'inspiring', 'pleasant', 'serene', 'upbeat',
|
||||
'breathtaking', 'dependable', 'gallant', 'intelligent', 'pleasing', 'sharp', 'valiant',
|
||||
'bright', 'devoted', 'generous', 'joyful', 'polite', 'shining', 'valuable',
|
||||
'brilliant', 'diplomatic', 'gentle', 'judicious', 'positive', 'shrewd', 'vigilant',
|
||||
'bubbly', 'discerning', 'gifted', 'just', 'praiseworthy', 'smart', 'vigorous',
|
||||
'buoyant', 'disciplined', 'giving', 'kindly', 'precious', 'sparkling', 'virtuous',
|
||||
'calm', 'elegant', 'gleaming', 'laudable', 'priceless', 'spectacular', 'well mannered',
|
||||
'capable', 'elevating', 'glowing', 'likable', 'principled', 'splendid', 'wholesome',
|
||||
'charitable', 'enchanting', 'good', 'lovable', 'prompt', 'steadfast', 'wise',
|
||||
'charming', 'encouraging', 'gorgeous', 'lovely', 'prudent', 'stunning', 'witty',
|
||||
'chaste', 'endearing', 'graceful', 'loyal', 'punctual', 'super', 'wonderful',
|
||||
'cheerful', 'energetic', 'gracious', 'luminous', 'pure', 'superb', 'worthy',
|
||||
'chivalrous', 'engaging', 'great', 'magnanimous', 'quick', 'superior', 'zesty',
|
||||
'gallant', 'enhanced', 'happy', 'magnificent', 'radiant', 'supportive',
|
||||
'civil', 'enjoyable', 'hardy', 'marvelous', 'rational', 'supreme'
|
||||
]
|
||||
|
||||
def person(self):
|
||||
return (self.word(), self.word(), self.word())
|
||||
@@ -0,0 +1,108 @@
|
||||
from npc.languages.base import BaseLanguage
|
||||
import random
|
||||
import re
|
||||
|
||||
|
||||
class Infernal(BaseLanguage):
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u']
|
||||
|
||||
consonants = [
|
||||
'b', 'c', 'd', 'f', 'g', 'j', 'k', 'l', 'm',
|
||||
'n', 'p', 'r', 's', 't', 'v', 'x', 'y', 'z',
|
||||
"t'h", "t'j", "t'z", "x't", "x'z", "x'j"
|
||||
]
|
||||
|
||||
syllable_template = ('C', 'V')
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{3}|' +
|
||||
r'[' + ''.join(consonants) + ']{4}'
|
||||
)
|
||||
|
||||
syllable_weights = [3, 2]
|
||||
|
||||
minimum_length = 1
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
t = ''.join(sequence)
|
||||
|
||||
if self._invalid_sequences.match(t):
|
||||
self._logger.debug(f"Invalid sequence: {t}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class Tiefling(Infernal):
|
||||
"""
|
||||
Tiefling names are formed using an infernal root and a few common suffixes.
|
||||
"""
|
||||
|
||||
nicknames = [
|
||||
'eternal',
|
||||
'wondrous',
|
||||
'luminous',
|
||||
'perfect',
|
||||
'essential',
|
||||
'golden',
|
||||
'unfailing',
|
||||
'perpetual',
|
||||
'infinite',
|
||||
'exquisite',
|
||||
'sinless',
|
||||
'ultimate',
|
||||
'flawless',
|
||||
'timeless',
|
||||
'glorious',
|
||||
'absolute',
|
||||
'boundless',
|
||||
'true',
|
||||
'incredible',
|
||||
'virtuous',
|
||||
'supreme',
|
||||
'enchanted',
|
||||
'magnificent',
|
||||
'superior',
|
||||
'spectacular',
|
||||
'divine',
|
||||
] + ['' for _ in range(50)]
|
||||
|
||||
def person(self):
|
||||
suffix = random.choice([
|
||||
'us',
|
||||
'ius'
|
||||
'to',
|
||||
'tro'
|
||||
'eus',
|
||||
'a',
|
||||
'an',
|
||||
'is',
|
||||
])
|
||||
return [str(self.word()) + suffix]
|
||||
|
||||
|
||||
class HighTiefling(Tiefling):
|
||||
"""
|
||||
"High" Tieflings revere their bloodlines and take their lineage as part of their name.
|
||||
"""
|
||||
|
||||
nicknames = []
|
||||
|
||||
def person(self):
|
||||
bloodline = random.choice([
|
||||
'Asmodeus',
|
||||
'Baalzebul',
|
||||
'Rimmon',
|
||||
'Dispater',
|
||||
'Fierna',
|
||||
'Glasya',
|
||||
'Levistus',
|
||||
'Mammon',
|
||||
'Mephistopheles',
|
||||
'Zariel',
|
||||
])
|
||||
return [bloodline] + super().person()
|
||||
@@ -0,0 +1,57 @@
|
||||
import re
|
||||
|
||||
from npc.languages.base import BaseLanguage
|
||||
|
||||
|
||||
class Orcish(BaseLanguage):
|
||||
|
||||
vowels = ['a', 'e', 'i', 'o', 'u']
|
||||
consonants = ['b', 'c', 'ch', 'd', 'f', 'h', 'k', 'm', 'n', 'p', 'r', 's', 'sh', 't', 'z']
|
||||
affixes = []
|
||||
|
||||
first_vowels = vowels
|
||||
first_consonants = consonants
|
||||
first_affixes = affixes
|
||||
|
||||
last_vowels = vowels
|
||||
last_consonants = consonants
|
||||
last_affixes = affixes
|
||||
|
||||
syllable_template = ('C', 'c', 'V')
|
||||
syllable_weights = [2, 4, 0.5]
|
||||
|
||||
_middle_clusters = re.compile(
|
||||
r'\S?[' +
|
||||
r'bd|bk|br|bs|' +
|
||||
r'ch|ck|cp|cr|cs|ct|' +
|
||||
r'db|dk|ds|' +
|
||||
r'fr|ft|' +
|
||||
r'kr|ks|kz|' +
|
||||
r'ms|' +
|
||||
r'ns|nt|nz|' +
|
||||
r'ps|pt|' +
|
||||
r'rk|rt|rz|' +
|
||||
r'sc|sh|sk|sr|st|' +
|
||||
r'tc|th|tr|ts|tz' +
|
||||
r']\S?'
|
||||
)
|
||||
|
||||
def validate_sequence(self, sequence, total_syllables):
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
seq = ''.join(sequence[-2:])
|
||||
if not self._middle_clusters.match(seq):
|
||||
self._logger.debug(f"Invalid sequence: {sequence[-2:]}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class OrcishPerson(Orcish):
|
||||
pass
|
||||
|
||||
|
||||
class HalfOrcPerson(Orcish):
|
||||
syllable_template = ('C', 'V', 'c')
|
||||
first_consonants = ['b', 'c', 'd', 'k', 'p', 't', 'z']
|
||||
last_consonants = Orcish.consonants + ['sht', 'cht', 'zt', 'zch']
|
||||
@@ -0,0 +1,122 @@
|
||||
import random
|
||||
import re
|
||||
|
||||
from npc.languages.base import BaseLanguage, WordFactory
|
||||
|
||||
|
||||
class Undercommon(BaseLanguage):
|
||||
vowels = ['a', 'e', 'i', 'o', 'u', 'a', 'e', 'i', 'o', 'u', 'ä', 'ö', 'ü', 'äu']
|
||||
consonants = ['b', 'c', 'd', 'f', 'g', 'h', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'y', 'z']
|
||||
affixes = []
|
||||
|
||||
first_vowels = ['a', 'e', 'i', 'o', 'u', 'y']
|
||||
first_consonants = ['c', 'g', 'l', 'm', 'n', 'r', 's', 't', 'v', 'z']
|
||||
first_affixes = []
|
||||
|
||||
last_vowels = ['a', 'i', 'e']
|
||||
last_consonants = ['t', 's', 'm', 'n', 'l', 'r', 'd', 'a', 'th']
|
||||
last_affixes = []
|
||||
|
||||
syllable_template = ('c', 'v', 'c', 'V', 'C', 'v')
|
||||
minimum_length = 4
|
||||
|
||||
_valid_consonant_sequences = [
|
||||
'cc', 'ht', 'kd', 'kl', 'km', 'kp', 'kt', 'kv', 'kw', 'ky', 'lc', 'ld',
|
||||
'lf', 'll', 'lm', 'lp', 'lt', 'lv', 'lw', 'ly', 'mb', 'mm', 'mp', 'my',
|
||||
'nc', 'nd', 'ng', 'nn', 'nt', 'nw', 'ny', 'ps', 'pt', 'rc', 'rd', 'rm',
|
||||
'rn', 'rp', 'rr', 'rs', 'rt', 'rw', 'ry', 'sc', 'ss', 'ts', 'tt', 'th',
|
||||
'tw', 'ty'
|
||||
]
|
||||
|
||||
_invalid_sequences = re.compile(
|
||||
r'[' + ''.join(vowels) + ']{3}|' +
|
||||
r'[' + ''.join(consonants) + ']{4}'
|
||||
)
|
||||
|
||||
def validate_sequence(self, sequence, *args, **kwargs):
|
||||
"""
|
||||
Ensure the specified sequence of syllables results in valid letter combinations.
|
||||
"""
|
||||
too_short = len(''.join(sequence)) < self.minimum_length
|
||||
if too_short:
|
||||
return False
|
||||
|
||||
# the whole string must be checked against the invalid sequences pattern
|
||||
chars = ''.join(sequence)
|
||||
if self._invalid_sequences.match(chars):
|
||||
self._logger.debug(f"Invalid sequence: {chars}")
|
||||
return False
|
||||
|
||||
# Now step through the sequence, two letters at a time, and verify that
|
||||
# all pairs of consonants are valid.
|
||||
for offset in range(0, len(chars), 2):
|
||||
seq = chars[offset:2]
|
||||
if not seq:
|
||||
break
|
||||
if seq[0] in self.consonants and seq[1] in self.consonants:
|
||||
if seq not in self._valid_consonant_sequences:
|
||||
self._logger.debug(f"Invalid sequence: {seq}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DrowPlaceName(Undercommon):
|
||||
syllable_template = ('v', 'C', 'v')
|
||||
syllable_weights = [2, 1]
|
||||
first_consonants = Undercommon.first_consonants + ['q']
|
||||
|
||||
minimum_length = 2
|
||||
|
||||
affixes = ['el']
|
||||
|
||||
def word(self):
|
||||
prefix = str(WordFactory(self))
|
||||
suffix = []
|
||||
while not self.validate_sequence(suffix):
|
||||
suffix = [
|
||||
random.choice(self.last_vowels),
|
||||
random.choice(self.last_consonants + ['ss']),
|
||||
]
|
||||
return prefix + ''.join(suffix)
|
||||
|
||||
def full_name(self):
|
||||
return 'el '.join(self.names)
|
||||
|
||||
|
||||
class DrowSurname(Undercommon):
|
||||
syllable_template = ('v', 'C', 'v')
|
||||
syllable_weights = [1, 2, 2]
|
||||
minimum_length = 2
|
||||
|
||||
def word(self):
|
||||
prefix = str(WordFactory(self))
|
||||
suffix = ''
|
||||
while not self.validate_sequence(suffix):
|
||||
suffix = ''.join([
|
||||
random.choice(self.last_vowels),
|
||||
random.choice(self.last_consonants + ['ss']),
|
||||
random.choice([
|
||||
'ie',
|
||||
'ia',
|
||||
'io',
|
||||
]),
|
||||
random.choice(['th', 's', 'r', 'n'])
|
||||
])
|
||||
return prefix + suffix
|
||||
|
||||
|
||||
class DrowPerson(Undercommon):
|
||||
syllable_template = ('c', 'V', 'C', 'v')
|
||||
syllable_weights = [1, 2]
|
||||
|
||||
def place(self):
|
||||
return DrowPlaceName().word()
|
||||
|
||||
def word(self):
|
||||
return (
|
||||
super().word(),
|
||||
DrowSurname().word(),
|
||||
)
|
||||
|
||||
person = word
|
||||
Reference in New Issue
Block a user