diff --git a/dnd_item/cli.py b/dnd_item/cli.py index ffbd9f2..e3f1978 100644 --- a/dnd_item/cli.py +++ b/dnd_item/cli.py @@ -1,15 +1,19 @@ import logging import os +from pathlib import Path + import typer from rich.logging import RichHandler from rich.console import Console +# from rich.table import Table -from dnd_item.types import Item - +from dnd_item.types import random_item +from dnd_item import five_e app = typer.Typer() +app_state = {} @app.callback() @@ -22,9 +26,20 @@ def main(): ) logging.getLogger('markdown_it').setLevel(logging.ERROR) + app_state['data'] = Path(__file__).parent / Path("sources") + @app.command() def item(count: int = typer.Option(1, help="The number of items to generate.")): - console = Console(width=80) - for _ in range(count): - console.print(Item()) + items = random_item(count) + console = Console() + for item in items: + console.print(f"{item['Name']} of {item['Enchantment Noun']} " + f"({item['Damage Dice']} {item['Damage Type']} + " + f"{item['Enchantment Damage']} {item['Enchantment Type']})") + + +@app.command() +def convert(): + src = five_e.weapons() + print(src.as_yaml) diff --git a/dnd_item/five_e.py b/dnd_item/five_e.py new file mode 100644 index 0000000..ff69eeb --- /dev/null +++ b/dnd_item/five_e.py @@ -0,0 +1,90 @@ +import json +import yaml + +from collections import defaultdict + +from rolltable.tables import DataSource + +from pathlib import Path + +sources = Path(__file__).parent / Path("sources") + +RARITY = { + 'unknown': 'common', + 'none': 'common', + '': '' +} + +TYPE = { + 'M': 'martial', + 'R': 'ranged', + '': '' +} + +DAMAGE = { + 'S': 'Slashing', + 'P': 'Piercing', + 'B': 'Bludgeoning', + '': '' +} + +PROPERTIES = { + 'F': 'finesse', + 'AF': 'firearm', + 'A': 'ammmunition', + 'T': 'thrown', + 'L': 'light', + '2H': 'two-handed', + 'V': 'versatile', + 'RLD': 'reload', + 'LD': 'loading', + 'S': 'special', + 'H': 'heavy', + 'R': 'reach', +} + + +class Weapons(DataSource): + """ + A rolltables data source backed by a 5e.tools json data file. used to + convert the 5e.tools data to the yaml format consumed by dnd-rolltables. + """ + def read_source(self) -> None: + src = json.load(self.source)['baseitem'] + self.data = defaultdict(list) + headers = ['Rarity', 'Name', 'Category', 'Type', 'Weight', 'Damage Type', + 'Damage Dice', 'Range', 'Reload', 'Value', 'Properties'] + + for item in src: + if not item.get('weapon', False): + continue + if item.get('age', False): + continue + rarity = RARITY.get(item['rarity'], 'Common').capitalize() + itype = TYPE.get(item['type'], '_unknown').capitalize() + properties = ', '.join([PROPERTIES[p] for p in item.get('property', [])]) + + self.data[rarity].append({ + item['name'].capitalize(): [ + item['weaponCategory'], + itype, + str(item.get('weight', 0)), + DAMAGE.get(item.get('dmgType', '')), + item.get('dmg1', None), + item.get('range', None), + str(item.get('reload', '')), + str(item.get('value', '')), + properties, + ] + }) + self.metadata = {'headers': headers} + + @property + def as_yaml(self) -> str: + return yaml.dump({'metadata': self.metadata}) + yaml.dump(dict(self.data)) + + +def weapons(source_path: str = 'items-base.json') -> dict: + with open(sources / Path(source_path)) as filehandle: + ds = Weapons(source=filehandle) + return ds diff --git a/dnd_item/sources/enchantments.yaml b/dnd_item/sources/enchantments.yaml new file mode 100644 index 0000000..cf3af43 --- /dev/null +++ b/dnd_item/sources/enchantments.yaml @@ -0,0 +1,13 @@ +metadata: + headers: + - Rarity + - Enchantment Type + - Enchantment Noun + - Enchantment Adjective +Common: + Fire: + - Flames + - Flaming + Ice: + - Ice + - Freezing diff --git a/dnd_item/sources/types.yaml b/dnd_item/sources/types.yaml new file mode 100644 index 0000000..8c6feae --- /dev/null +++ b/dnd_item/sources/types.yaml @@ -0,0 +1,435 @@ +metadata: + headers: + - Rarity + - Name + - Category + - Type + - Weight + - Damage Type + - Damage Dice + - Range + - Reload + - Value + - Properties +Common: +- Battleaxe: + - martial + - Martial + - '4' + - Slashing + - 1d8 + - null + - '' + - '1000' + - versatile +- Blowgun: + - martial + - Ranged + - '1' + - Piercing + - '1' + - 25/100 + - '' + - '1000' + - ammmunition, loading +- Club: + - simple + - Martial + - '2' + - Bludgeoning + - 1d4 + - null + - '' + - '10' + - light +- Dagger: + - simple + - Martial + - '1' + - Piercing + - 1d4 + - 20/60 + - '' + - '200' + - finesse, light, thrown +- Dart: + - simple + - Ranged + - '0.25' + - Piercing + - 1d4 + - 20/60 + - '' + - '5' + - finesse, thrown +- Double-bladed scimitar: + - martial + - Martial + - '6' + - Slashing + - 2d4 + - null + - '' + - '10000' + - special, two-handed +- Flail: + - martial + - Martial + - '2' + - Bludgeoning + - 1d8 + - null + - '' + - '1000' + - '' +- Glaive: + - martial + - Martial + - '6' + - Slashing + - 1d10 + - null + - '' + - '2000' + - heavy, reach, two-handed +- Greataxe: + - martial + - Martial + - '7' + - Slashing + - 1d12 + - null + - '' + - '3000' + - heavy, two-handed +- Greatclub: + - simple + - Martial + - '10' + - Bludgeoning + - 1d8 + - null + - '' + - '20' + - two-handed +- Greatsword: + - martial + - Martial + - '6' + - Slashing + - 2d6 + - null + - '' + - '5000' + - heavy, two-handed +- Halberd: + - martial + - Martial + - '6' + - Slashing + - 1d10 + - null + - '' + - '2000' + - heavy, reach, two-handed +- Hand crossbow: + - martial + - Ranged + - '3' + - Piercing + - 1d6 + - 30/120 + - '' + - '7500' + - ammmunition, light, loading +- Handaxe: + - simple + - Martial + - '2' + - Slashing + - 1d6 + - 20/60 + - '' + - '500' + - light, thrown +- Heavy crossbow: + - martial + - Ranged + - '18' + - Piercing + - 1d10 + - 100/400 + - '' + - '5000' + - ammmunition, heavy, loading, two-handed +- Hooked shortspear: + - martial + - Martial + - '2' + - Piercing + - 1d4 + - null + - '' + - '' + - light +- Hoopak: + - martial + - Martial + - '2' + - Piercing + - 1d6 + - 40/160 + - '' + - '10' + - ammmunition, finesse, special, two-handed +- Javelin: + - simple + - Martial + - '2' + - Piercing + - 1d6 + - 30/120 + - '' + - '50' + - thrown +- Lance: + - martial + - Martial + - '6' + - Piercing + - 1d12 + - null + - '' + - '1000' + - reach, special +- Light crossbow: + - simple + - Ranged + - '5' + - Piercing + - 1d8 + - 80/320 + - '' + - '2500' + - ammmunition, loading, two-handed +- Light hammer: + - simple + - Martial + - '2' + - Bludgeoning + - 1d4 + - 20/60 + - '' + - '200' + - light, thrown +- Light repeating crossbow: + - simple + - Ranged + - '5' + - Piercing + - 1d8 + - 40/160 + - '' + - '' + - ammmunition, two-handed +- Longbow: + - martial + - Ranged + - '2' + - Piercing + - 1d8 + - 150/600 + - '' + - '5000' + - ammmunition, heavy, two-handed +- Longsword: + - martial + - Martial + - '3' + - Slashing + - 1d8 + - null + - '' + - '1500' + - versatile +- Mace: + - simple + - Martial + - '4' + - Bludgeoning + - 1d6 + - null + - '' + - '500' + - '' +- Maul: + - martial + - Martial + - '10' + - Bludgeoning + - 2d6 + - null + - '' + - '1000' + - heavy, two-handed +- Morningstar: + - martial + - Martial + - '4' + - Piercing + - 1d8 + - null + - '' + - '1500' + - '' +- Net: + - martial + - Ranged + - '3' + - '' + - null + - 5/15 + - '' + - '100' + - special, thrown +- Pike: + - martial + - Martial + - '18' + - Piercing + - 1d10 + - null + - '' + - '500' + - heavy, reach, two-handed +- Quarterstaff: + - simple + - Martial + - '4' + - Bludgeoning + - 1d6 + - null + - '' + - '20' + - versatile +- Rapier: + - martial + - Martial + - '2' + - Piercing + - 1d8 + - null + - '' + - '2500' + - finesse +- Scimitar: + - martial + - Martial + - '3' + - Slashing + - 1d6 + - null + - '' + - '2500' + - finesse, light +- Shortbow: + - simple + - Ranged + - '2' + - Piercing + - 1d6 + - 80/320 + - '' + - '2500' + - ammmunition, two-handed +- Shortsword: + - martial + - Martial + - '2' + - Piercing + - 1d6 + - null + - '' + - '1000' + - finesse, light +- Sickle: + - simple + - Martial + - '2' + - Slashing + - 1d4 + - null + - '' + - '100' + - light +- Sling: + - simple + - Ranged + - '0' + - Bludgeoning + - 1d4 + - 30/120 + - '' + - '10' + - ammmunition +- Spear: + - simple + - Martial + - '3' + - Piercing + - 1d6 + - 20/60 + - '' + - '100' + - thrown, versatile +- Trident: + - martial + - Martial + - '4' + - Piercing + - 1d6 + - 20/60 + - '' + - '500' + - thrown, versatile +- War pick: + - martial + - Martial + - '2' + - Piercing + - 1d8 + - null + - '' + - '500' + - '' +- Warhammer: + - martial + - Martial + - '2' + - Bludgeoning + - 1d8 + - null + - '' + - '1500' + - versatile +- Whip: + - martial + - Martial + - '3' + - Slashing + - 1d4 + - null + - '' + - '200' + - finesse, reach +- Yklwa: + - simple + - Martial + - '3' + - Piercing + - 1d8 + - 10/30 + - '' + - '100' + - thrown + diff --git a/dnd_item/types.py b/dnd_item/types.py index ba0d977..6e60267 100644 --- a/dnd_item/types.py +++ b/dnd_item/types.py @@ -1,2 +1,21 @@ +from rolltable import tables +from pathlib import Path + +sources = Path(__file__).parent / Path("sources") + + class Item: pass + + +def random_item(count=1): + types = (sources / Path('types.yaml')).read_text() + enchantments = (sources / Path('enchantments.yaml')).read_text() + items = [] + for _ in range(count): + rt = tables.RollTable([types, enchantments], die=1, hide_rolls=True) + item = dict(zip(rt.rows[0], rt.rows[1])) + item['Enchantment Damage'] = '1d4' + items.append(item) + + return items diff --git a/pyproject.toml b/pyproject.toml index ab94310..44cefcc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ rich = "^13.7.0" typer = "^0.9.0" dice = "^4.0.0" +dnd-rolltable = { git = "https://github.com/evilchili/dnd-rolltable", branch='main' } + [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" black = "^23.3.0"