adding lizardfolk
This commit is contained in:
parent
99f479910c
commit
9888e35072
|
@ -19,6 +19,7 @@ class Ancestry(str, Enum):
|
||||||
highttiefling = 'hightiefling'
|
highttiefling = 'hightiefling'
|
||||||
human = 'human'
|
human = 'human'
|
||||||
tiefling = 'tiefling'
|
tiefling = 'tiefling'
|
||||||
|
lizardfolk = 'lizardfolk'
|
||||||
|
|
||||||
|
|
||||||
class Language(str, Enum):
|
class Language(str, Enum):
|
||||||
|
@ -33,6 +34,7 @@ class Language(str, Enum):
|
||||||
infernal = 'infernal'
|
infernal = 'infernal'
|
||||||
orcish = 'orcish'
|
orcish = 'orcish'
|
||||||
undercommon = 'undercommon'
|
undercommon = 'undercommon'
|
||||||
|
lizardfolk = 'lizardfolk'
|
||||||
|
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import dice
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
_available_npc_types = {}
|
AVAILABLE_NPC_TYPES = {}
|
||||||
|
|
||||||
|
|
||||||
def a_or_an(s):
|
def a_or_an(s):
|
||||||
|
@ -96,9 +96,9 @@ class BaseNPC:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
name = ' '.join([n.capitalize() for n in self.names])
|
name = ' '.join([n.title() for n in self.names])
|
||||||
if self.title:
|
if self.title:
|
||||||
name = self.title.capitalize() + ' ' + name
|
name = self.title.title() + ' ' + name
|
||||||
if self.nickname:
|
if self.nickname:
|
||||||
name = f'{name} "{self.nickname}"'
|
name = f'{name} "{self.nickname}"'
|
||||||
return name
|
return name
|
||||||
|
@ -332,12 +332,12 @@ def available_npc_types():
|
||||||
"""
|
"""
|
||||||
Load all available NPC submodules and return a dictionary keyed by module name.
|
Load all available NPC submodules and return a dictionary keyed by module name.
|
||||||
"""
|
"""
|
||||||
if not _available_npc_types:
|
if not AVAILABLE_NPC_TYPES:
|
||||||
for filename in glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '*.py')):
|
for filename in glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '*.py')):
|
||||||
module_name = os.path.basename(filename)[:-3]
|
module_name = os.path.basename(filename)[:-3]
|
||||||
if module_name not in ['base', '__init__', 'traits']:
|
if module_name not in ['base', '__init__', 'traits']:
|
||||||
_available_npc_types[module_name] = import_module(f'npc.generator.{module_name}').NPC
|
AVAILABLE_NPC_TYPES[module_name] = import_module(f'npc.generator.{module_name}').NPC
|
||||||
return _available_npc_types
|
return AVAILABLE_NPC_TYPES
|
||||||
|
|
||||||
|
|
||||||
def npc_type(ancestry=None):
|
def npc_type(ancestry=None):
|
||||||
|
@ -358,8 +358,7 @@ def generate_npc(ancestry=None, names=[], pronouns=None, title=None, nickname=No
|
||||||
"""
|
"""
|
||||||
Return a randomized NPC. Any supplied keyword parameters will override the generated values.
|
Return a randomized NPC. Any supplied keyword parameters will override the generated values.
|
||||||
|
|
||||||
By default, NPC stats are all 10 (+0). If randomize is True, the NPC will be given random stats from the standard
|
By default, NPC stats are all 10 (+0). If randomize is True, the NPC will be given random stats from the standard distribution, but overrides will still take precedence.
|
||||||
distribution, but overrides will still take precedence.
|
|
||||||
"""
|
"""
|
||||||
return npc_type(ancestry)(
|
return npc_type(ancestry)(
|
||||||
names=names,
|
names=names,
|
||||||
|
|
108
npc/generator/lizardfolk.py
Normal file
108
npc/generator/lizardfolk.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
from npc.languages import lizardfolk
|
||||||
|
from npc.generator.base import BaseNPC, a_or_an
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class NPC(BaseNPC):
|
||||||
|
|
||||||
|
ancestry = 'Lizardfolk'
|
||||||
|
language = lizardfolk.Lizardfolk()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._tail = None
|
||||||
|
self._horns = None
|
||||||
|
self._fangs = None
|
||||||
|
self._frills = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def age(self):
|
||||||
|
if not self._age:
|
||||||
|
self._age = random.choice([
|
||||||
|
'hatchling',
|
||||||
|
'juvenile',
|
||||||
|
'adult',
|
||||||
|
'ancient',
|
||||||
|
])
|
||||||
|
return self._age
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tail(self):
|
||||||
|
if not self._tail:
|
||||||
|
if random.random() <= -0.6:
|
||||||
|
self._tail = super(self)
|
||||||
|
else:
|
||||||
|
self._tail = 'no'
|
||||||
|
return self._tail
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frills(self):
|
||||||
|
if not self._frills:
|
||||||
|
if self.age in ('adult', 'ancient'):
|
||||||
|
self._frills = random.choice([
|
||||||
|
'orange',
|
||||||
|
'red',
|
||||||
|
'yellow',
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'silvery',
|
||||||
|
])
|
||||||
|
return self._frills
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skin_color(self):
|
||||||
|
if not self._skin_color:
|
||||||
|
self._skin_color = random.choice([
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'grey',
|
||||||
|
'brown',
|
||||||
|
'tan',
|
||||||
|
'sandy',
|
||||||
|
'gold',
|
||||||
|
])
|
||||||
|
return self._skin_color
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
trait = random.choice([
|
||||||
|
f'{self.eyes} eyes',
|
||||||
|
f'{self.tail} tail',
|
||||||
|
f'{self.teeth} teeth',
|
||||||
|
f'{self.frills} frills',
|
||||||
|
self.facial_structure,
|
||||||
|
])
|
||||||
|
return (
|
||||||
|
f"{self.full_name} ({self.pronouns}) is {a_or_an(self.age)} {self.age}, {self.skin_color}-scaled "
|
||||||
|
f"{self.ancestry.lower()} with {a_or_an(self.nose)} {self.nose} snout, {self.body} body and {trait}."
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def character_sheet(self):
|
||||||
|
desc = '\n'.join(textwrap.wrap(self.description, width=120))
|
||||||
|
return f"""\
|
||||||
|
|
||||||
|
{desc}
|
||||||
|
|
||||||
|
Physical Traits:
|
||||||
|
|
||||||
|
Face: {self.face}, {self.nose} snout, {self.teeth} teeth
|
||||||
|
Eyes: {self.eyes}
|
||||||
|
Skin: {self.skin_tone}
|
||||||
|
Scales: {self.skin_color}
|
||||||
|
Body: {self.body}
|
||||||
|
Tail: {self.tail}
|
||||||
|
Voice: {self.voice}
|
||||||
|
|
||||||
|
Details:
|
||||||
|
|
||||||
|
Personality: {self.personality}
|
||||||
|
Flaw: {self.flaw}
|
||||||
|
Goal: {self.goal}
|
||||||
|
|
||||||
|
Whereabouts: {self.whereabouts}
|
||||||
|
|
||||||
|
"""
|
41
npc/languages/lizardfolk.py
Normal file
41
npc/languages/lizardfolk.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from npc.languages.base import BaseLanguage
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class Lizardfolk(BaseLanguage):
|
||||||
|
|
||||||
|
vowels = []
|
||||||
|
consonants = []
|
||||||
|
affixes = []
|
||||||
|
|
||||||
|
syllable_template = ()
|
||||||
|
syllable_weights = []
|
||||||
|
|
||||||
|
family = [
|
||||||
|
'sweet', 'floral', 'fruity', 'sour', 'fermented', 'green', 'vegetal', 'old',
|
||||||
|
'roasted', 'spiced', 'nutty', 'cocoa', 'pepper', 'pungent', 'burnt', 'carmelized',
|
||||||
|
'raw', 'rotting', 'dead', 'young',
|
||||||
|
]
|
||||||
|
|
||||||
|
scents = [
|
||||||
|
'honey', 'caramel', 'maple syrup', 'molasses', 'dark chocolate', 'chocolate', 'almond',
|
||||||
|
'hazelnut', 'peanut', 'clove', 'cinnamon', 'nutmeg', 'anise', 'malt', 'grain', 'roast',
|
||||||
|
'smoke', 'ash', 'acrid', 'rubber', 'skunk', 'petroleum', 'medicine', 'salt', 'bitter',
|
||||||
|
'phrenolic', 'meat', 'broth', 'animal', 'musty', 'earth', 'mould', 'damp', 'wood', 'paper',
|
||||||
|
'cardboard', 'stale', 'herb', 'hay', 'grass', 'peapod', 'whisky', 'wine', 'malic',
|
||||||
|
'citric', 'isovaleric', 'butyric', 'acetic', 'lime', 'lemon',
|
||||||
|
'orange', 'grapefruit', 'pear', 'peach', 'apple', 'grape', 'pineapple', 'pomegranate',
|
||||||
|
'cherry', 'coconut', 'prune', 'raisin', 'strawberry', 'blueberry', 'raspberry',
|
||||||
|
'blackberry', 'jasmine', 'rose', 'camomile', 'tobacco',
|
||||||
|
]
|
||||||
|
|
||||||
|
nicknames = []
|
||||||
|
|
||||||
|
def person(self):
|
||||||
|
return(
|
||||||
|
random.choice(self.family),
|
||||||
|
'-'.join([
|
||||||
|
random.choice(self.scents),
|
||||||
|
random.choice(self.scents),
|
||||||
|
]),
|
||||||
|
)
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "dnd_npcs"
|
name = "dnd_npcs"
|
||||||
version = "0.2.0"
|
version = "0.3"
|
||||||
description = "NPC tools for the telisar homebrew campaign setting"
|
description = "NPC tools for the telisar homebrew campaign setting"
|
||||||
authors = ["evilchili <evilchili@gmail.com>"]
|
authors = ["evilchili <evilchili@gmail.com>"]
|
||||||
license = "The Unlicense"
|
license = "The Unlicense"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user