From 9888e35072a72bb5a04f9492ff2d7192d25c5aa8 Mon Sep 17 00:00:00 2001 From: evilchili Date: Fri, 29 Sep 2023 17:23:11 -0700 Subject: [PATCH] adding lizardfolk --- npc/cli.py | 2 + npc/generator/base.py | 15 +++-- npc/generator/lizardfolk.py | 108 ++++++++++++++++++++++++++++++++++++ npc/languages/lizardfolk.py | 41 ++++++++++++++ pyproject.toml | 2 +- 5 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 npc/generator/lizardfolk.py create mode 100644 npc/languages/lizardfolk.py diff --git a/npc/cli.py b/npc/cli.py index 96b1c56..9098516 100644 --- a/npc/cli.py +++ b/npc/cli.py @@ -19,6 +19,7 @@ class Ancestry(str, Enum): highttiefling = 'hightiefling' human = 'human' tiefling = 'tiefling' + lizardfolk = 'lizardfolk' class Language(str, Enum): @@ -33,6 +34,7 @@ class Language(str, Enum): infernal = 'infernal' orcish = 'orcish' undercommon = 'undercommon' + lizardfolk = 'lizardfolk' app = typer.Typer() diff --git a/npc/generator/base.py b/npc/generator/base.py index d1b6c58..e17ab44 100644 --- a/npc/generator/base.py +++ b/npc/generator/base.py @@ -7,7 +7,7 @@ import dice import textwrap -_available_npc_types = {} +AVAILABLE_NPC_TYPES = {} def a_or_an(s): @@ -96,9 +96,9 @@ class BaseNPC: @property 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: - name = self.title.capitalize() + ' ' + name + name = self.title.title() + ' ' + name if self.nickname: name = f'{name} "{self.nickname}"' return name @@ -332,12 +332,12 @@ def available_npc_types(): """ 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')): module_name = os.path.basename(filename)[:-3] if module_name not in ['base', '__init__', 'traits']: - _available_npc_types[module_name] = import_module(f'npc.generator.{module_name}').NPC - return _available_npc_types + AVAILABLE_NPC_TYPES[module_name] = import_module(f'npc.generator.{module_name}').NPC + return AVAILABLE_NPC_TYPES 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. - 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. + 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. """ return npc_type(ancestry)( names=names, diff --git a/npc/generator/lizardfolk.py b/npc/generator/lizardfolk.py new file mode 100644 index 0000000..551b325 --- /dev/null +++ b/npc/generator/lizardfolk.py @@ -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} + +""" diff --git a/npc/languages/lizardfolk.py b/npc/languages/lizardfolk.py new file mode 100644 index 0000000..309c67b --- /dev/null +++ b/npc/languages/lizardfolk.py @@ -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), + ]), + ) diff --git a/pyproject.toml b/pyproject.toml index 386d55e..1671b0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dnd_npcs" -version = "0.2.0" +version = "0.3" description = "NPC tools for the telisar homebrew campaign setting" authors = ["evilchili "] license = "The Unlicense"