initial commit of types library
This commit is contained in:
parent
e073647e5d
commit
f379d01f75
51
README.md
51
README.md
|
@ -1,2 +1,49 @@
|
||||||
# dnd-item-generator
|
# D&D Weapon, Item, and Loot Generator
|
||||||
Generate random weapons, items, and loot for Dungeons & Dragons 5th ed.
|
|
||||||
|
**WIP!**
|
||||||
|
|
||||||
|
This package includes a library and CLI for generating randomized weapons, items, and loot for
|
||||||
|
Dungeons & Dragons, 5th edition.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The `dnd-item` command-line utility supports several comments:
|
||||||
|
|
||||||
|
* **item**: Generate a random item (the default)
|
||||||
|
* **weapon**: Generate a basic, non-magical weapon
|
||||||
|
* **magical-weapon**: Generate a weapon with an added magical damage type
|
||||||
|
|
||||||
|
### Examples:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
% dnd-item weapon
|
||||||
|
Pike
|
||||||
|
* type.category: martial
|
||||||
|
* type.damage: Piercing
|
||||||
|
* type.die: 1d10
|
||||||
|
* type.properties: heavy, reach, two-handed
|
||||||
|
* type.range: None
|
||||||
|
* type.reload:
|
||||||
|
* type.type: Martial
|
||||||
|
* type.value: 500
|
||||||
|
* type.weight: 18
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
% dnd-item magic-weapon
|
||||||
|
Shortsword Of Thunder
|
||||||
|
* magic.adjective: booming
|
||||||
|
* magic.die: 1d4
|
||||||
|
* magic.noun: thunder
|
||||||
|
* type.category: martial
|
||||||
|
* type.damage: Piercing
|
||||||
|
* type.die: 1d6
|
||||||
|
* type.properties: finesse, light
|
||||||
|
* type.range: None
|
||||||
|
* type.reload:
|
||||||
|
* type.type: Martial
|
||||||
|
* type.value: 1000
|
||||||
|
* type.weight: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,8 @@ import typer
|
||||||
|
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
# from rich.table import Table
|
|
||||||
|
|
||||||
from dnd_item.types import random_item
|
from dnd_item.types import WeaponGenerator, MagicWeaponGenerator
|
||||||
from dnd_item import five_e
|
from dnd_item import five_e
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
@ -30,13 +29,17 @@ def main():
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def item(count: int = typer.Option(1, help="The number of items to generate.")):
|
def weapon(count: int = typer.Option(1, help="The number of weapons to generate.")):
|
||||||
items = random_item(count)
|
|
||||||
console = Console()
|
console = Console()
|
||||||
for item in items:
|
for weapon in WeaponGenerator().random(count):
|
||||||
console.print(f"{item['Name']} of {item['Enchantment Noun']} "
|
console.print(weapon.details)
|
||||||
f"({item['Damage Dice']} {item['Damage Type']} + "
|
|
||||||
f"{item['Enchantment Damage']} {item['Enchantment Type']})")
|
|
||||||
|
@app.command()
|
||||||
|
def magic_weapon(count: int = typer.Option(1, help="The number of weapons to generate.")):
|
||||||
|
console = Console()
|
||||||
|
for weapon in MagicWeaponGenerator().random(count):
|
||||||
|
console.print(weapon.details)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
|
@ -3,7 +3,7 @@ import yaml
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from rolltable.tables import DataSource
|
from random_sets.datasources import DataSource
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
metadata:
|
|
||||||
headers:
|
|
||||||
- Rarity
|
|
||||||
- Enchantment Type
|
|
||||||
- Enchantment Noun
|
|
||||||
- Enchantment Adjective
|
|
||||||
Common:
|
|
||||||
Fire:
|
|
||||||
- Flames
|
|
||||||
- Flaming
|
|
||||||
Ice:
|
|
||||||
- Ice
|
|
||||||
- Freezing
|
|
50
dnd_item/sources/magic_damage_types.yaml
Normal file
50
dnd_item/sources/magic_damage_types.yaml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
metadata:
|
||||||
|
headers:
|
||||||
|
- damage_type
|
||||||
|
- noun
|
||||||
|
- adjectives
|
||||||
|
- effect
|
||||||
|
frequencies:
|
||||||
|
default:
|
||||||
|
fire: 1.0
|
||||||
|
cold: 1.0
|
||||||
|
acid: 1.0
|
||||||
|
thunder: 1.0
|
||||||
|
psychic: 1.0
|
||||||
|
poison: 1.0
|
||||||
|
lightning: 1.0
|
||||||
|
force: 1.0
|
||||||
|
necrotic: 1.0
|
||||||
|
radiant: 1.0
|
||||||
|
fire:
|
||||||
|
flames:
|
||||||
|
- flaming, burning
|
||||||
|
- burning
|
||||||
|
cold:
|
||||||
|
ice:
|
||||||
|
- freezing, frosty
|
||||||
|
acid:
|
||||||
|
acid:
|
||||||
|
- acidic, caustic
|
||||||
|
thunder:
|
||||||
|
thunder:
|
||||||
|
- thundering,booming
|
||||||
|
psychic:
|
||||||
|
psychic:
|
||||||
|
- psychic
|
||||||
|
- psychic
|
||||||
|
poison:
|
||||||
|
poison:
|
||||||
|
- poisonous,toxic
|
||||||
|
lightning:
|
||||||
|
lightning:
|
||||||
|
- lightning,shocking
|
||||||
|
force:
|
||||||
|
force:
|
||||||
|
- force
|
||||||
|
necrotic:
|
||||||
|
necrosis:
|
||||||
|
- necrotic, darkness, unholy
|
||||||
|
radiant:
|
||||||
|
radiance:
|
||||||
|
- radiant, holy
|
15
dnd_item/sources/rarity.yaml
Normal file
15
dnd_item/sources/rarity.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
metadata:
|
||||||
|
headers:
|
||||||
|
- Rarity
|
||||||
|
frequencies:
|
||||||
|
default:
|
||||||
|
Common: 1.0
|
||||||
|
Uncommon: 0.8
|
||||||
|
Rare: 0.5
|
||||||
|
Legendary: 0.1
|
||||||
|
Unique: 0.05
|
||||||
|
Common:
|
||||||
|
Uncommon:
|
||||||
|
Rare:
|
||||||
|
Legendary:
|
||||||
|
Unique:
|
|
@ -1,18 +1,16 @@
|
||||||
metadata:
|
metadata:
|
||||||
headers:
|
headers:
|
||||||
- Rarity
|
- name
|
||||||
- Name
|
- category
|
||||||
- Category
|
- type
|
||||||
- Type
|
- weight
|
||||||
- Weight
|
- damage
|
||||||
- Damage Type
|
- die
|
||||||
- Damage Dice
|
- range
|
||||||
- Range
|
- reload
|
||||||
- Reload
|
- value
|
||||||
- Value
|
- properties
|
||||||
- Properties
|
Battleaxe:
|
||||||
Common:
|
|
||||||
- Battleaxe:
|
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '4'
|
- '4'
|
||||||
|
@ -22,7 +20,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- versatile
|
- versatile
|
||||||
- Blowgun:
|
Blowgun:
|
||||||
- martial
|
- martial
|
||||||
- Ranged
|
- Ranged
|
||||||
- '1'
|
- '1'
|
||||||
|
@ -32,7 +30,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- ammmunition, loading
|
- ammmunition, loading
|
||||||
- Club:
|
Club:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -42,7 +40,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '10'
|
- '10'
|
||||||
- light
|
- light
|
||||||
- Dagger:
|
Dagger:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '1'
|
- '1'
|
||||||
|
@ -52,7 +50,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '200'
|
- '200'
|
||||||
- finesse, light, thrown
|
- finesse, light, thrown
|
||||||
- Dart:
|
Dart:
|
||||||
- simple
|
- simple
|
||||||
- Ranged
|
- Ranged
|
||||||
- '0.25'
|
- '0.25'
|
||||||
|
@ -62,7 +60,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '5'
|
- '5'
|
||||||
- finesse, thrown
|
- finesse, thrown
|
||||||
- Double-bladed scimitar:
|
Double-bladed scimitar:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '6'
|
- '6'
|
||||||
|
@ -72,7 +70,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '10000'
|
- '10000'
|
||||||
- special, two-handed
|
- special, two-handed
|
||||||
- Flail:
|
Flail:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -82,7 +80,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- ''
|
- ''
|
||||||
- Glaive:
|
Glaive:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '6'
|
- '6'
|
||||||
|
@ -92,7 +90,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2000'
|
- '2000'
|
||||||
- heavy, reach, two-handed
|
- heavy, reach, two-handed
|
||||||
- Greataxe:
|
Greataxe:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '7'
|
- '7'
|
||||||
|
@ -102,7 +100,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '3000'
|
- '3000'
|
||||||
- heavy, two-handed
|
- heavy, two-handed
|
||||||
- Greatclub:
|
Greatclub:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '10'
|
- '10'
|
||||||
|
@ -112,7 +110,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '20'
|
- '20'
|
||||||
- two-handed
|
- two-handed
|
||||||
- Greatsword:
|
Greatsword:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '6'
|
- '6'
|
||||||
|
@ -122,7 +120,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '5000'
|
- '5000'
|
||||||
- heavy, two-handed
|
- heavy, two-handed
|
||||||
- Halberd:
|
Halberd:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '6'
|
- '6'
|
||||||
|
@ -132,7 +130,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2000'
|
- '2000'
|
||||||
- heavy, reach, two-handed
|
- heavy, reach, two-handed
|
||||||
- Hand crossbow:
|
Hand crossbow:
|
||||||
- martial
|
- martial
|
||||||
- Ranged
|
- Ranged
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -142,7 +140,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '7500'
|
- '7500'
|
||||||
- ammmunition, light, loading
|
- ammmunition, light, loading
|
||||||
- Handaxe:
|
Handaxe:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -152,7 +150,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '500'
|
- '500'
|
||||||
- light, thrown
|
- light, thrown
|
||||||
- Heavy crossbow:
|
Heavy crossbow:
|
||||||
- martial
|
- martial
|
||||||
- Ranged
|
- Ranged
|
||||||
- '18'
|
- '18'
|
||||||
|
@ -162,7 +160,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '5000'
|
- '5000'
|
||||||
- ammmunition, heavy, loading, two-handed
|
- ammmunition, heavy, loading, two-handed
|
||||||
- Hooked shortspear:
|
Hooked shortspear:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -172,7 +170,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- ''
|
- ''
|
||||||
- light
|
- light
|
||||||
- Hoopak:
|
Hoopak:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -182,7 +180,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '10'
|
- '10'
|
||||||
- ammmunition, finesse, special, two-handed
|
- ammmunition, finesse, special, two-handed
|
||||||
- Javelin:
|
Javelin:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -192,7 +190,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '50'
|
- '50'
|
||||||
- thrown
|
- thrown
|
||||||
- Lance:
|
Lance:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '6'
|
- '6'
|
||||||
|
@ -202,7 +200,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- reach, special
|
- reach, special
|
||||||
- Light crossbow:
|
Light crossbow:
|
||||||
- simple
|
- simple
|
||||||
- Ranged
|
- Ranged
|
||||||
- '5'
|
- '5'
|
||||||
|
@ -212,7 +210,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2500'
|
- '2500'
|
||||||
- ammmunition, loading, two-handed
|
- ammmunition, loading, two-handed
|
||||||
- Light hammer:
|
Light hammer:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -222,7 +220,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '200'
|
- '200'
|
||||||
- light, thrown
|
- light, thrown
|
||||||
- Light repeating crossbow:
|
Light repeating crossbow:
|
||||||
- simple
|
- simple
|
||||||
- Ranged
|
- Ranged
|
||||||
- '5'
|
- '5'
|
||||||
|
@ -232,7 +230,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- ''
|
- ''
|
||||||
- ammmunition, two-handed
|
- ammmunition, two-handed
|
||||||
- Longbow:
|
Longbow:
|
||||||
- martial
|
- martial
|
||||||
- Ranged
|
- Ranged
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -242,7 +240,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '5000'
|
- '5000'
|
||||||
- ammmunition, heavy, two-handed
|
- ammmunition, heavy, two-handed
|
||||||
- Longsword:
|
Longsword:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -252,7 +250,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1500'
|
- '1500'
|
||||||
- versatile
|
- versatile
|
||||||
- Mace:
|
Mace:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '4'
|
- '4'
|
||||||
|
@ -262,7 +260,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '500'
|
- '500'
|
||||||
- ''
|
- ''
|
||||||
- Maul:
|
Maul:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '10'
|
- '10'
|
||||||
|
@ -272,7 +270,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- heavy, two-handed
|
- heavy, two-handed
|
||||||
- Morningstar:
|
Morningstar:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '4'
|
- '4'
|
||||||
|
@ -282,7 +280,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1500'
|
- '1500'
|
||||||
- ''
|
- ''
|
||||||
- Net:
|
Net:
|
||||||
- martial
|
- martial
|
||||||
- Ranged
|
- Ranged
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -292,7 +290,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '100'
|
- '100'
|
||||||
- special, thrown
|
- special, thrown
|
||||||
- Pike:
|
Pike:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '18'
|
- '18'
|
||||||
|
@ -302,7 +300,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '500'
|
- '500'
|
||||||
- heavy, reach, two-handed
|
- heavy, reach, two-handed
|
||||||
- Quarterstaff:
|
Quarterstaff:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '4'
|
- '4'
|
||||||
|
@ -312,7 +310,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '20'
|
- '20'
|
||||||
- versatile
|
- versatile
|
||||||
- Rapier:
|
Rapier:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -322,7 +320,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2500'
|
- '2500'
|
||||||
- finesse
|
- finesse
|
||||||
- Scimitar:
|
Scimitar:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -332,7 +330,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2500'
|
- '2500'
|
||||||
- finesse, light
|
- finesse, light
|
||||||
- Shortbow:
|
Shortbow:
|
||||||
- simple
|
- simple
|
||||||
- Ranged
|
- Ranged
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -342,7 +340,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '2500'
|
- '2500'
|
||||||
- ammmunition, two-handed
|
- ammmunition, two-handed
|
||||||
- Shortsword:
|
Shortsword:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -352,7 +350,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1000'
|
- '1000'
|
||||||
- finesse, light
|
- finesse, light
|
||||||
- Sickle:
|
Sickle:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -362,7 +360,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '100'
|
- '100'
|
||||||
- light
|
- light
|
||||||
- Sling:
|
Sling:
|
||||||
- simple
|
- simple
|
||||||
- Ranged
|
- Ranged
|
||||||
- '0'
|
- '0'
|
||||||
|
@ -372,7 +370,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '10'
|
- '10'
|
||||||
- ammmunition
|
- ammmunition
|
||||||
- Spear:
|
Spear:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -382,7 +380,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '100'
|
- '100'
|
||||||
- thrown, versatile
|
- thrown, versatile
|
||||||
- Trident:
|
Trident:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '4'
|
- '4'
|
||||||
|
@ -392,7 +390,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '500'
|
- '500'
|
||||||
- thrown, versatile
|
- thrown, versatile
|
||||||
- War pick:
|
War pick:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -402,7 +400,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '500'
|
- '500'
|
||||||
- ''
|
- ''
|
||||||
- Warhammer:
|
Warhammer:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '2'
|
- '2'
|
||||||
|
@ -412,7 +410,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '1500'
|
- '1500'
|
||||||
- versatile
|
- versatile
|
||||||
- Whip:
|
Whip:
|
||||||
- martial
|
- martial
|
||||||
- Martial
|
- Martial
|
||||||
- '3'
|
- '3'
|
||||||
|
@ -422,7 +420,7 @@ Common:
|
||||||
- ''
|
- ''
|
||||||
- '200'
|
- '200'
|
||||||
- finesse, reach
|
- finesse, reach
|
||||||
- Yklwa:
|
Yklwa:
|
||||||
- simple
|
- simple
|
||||||
- Martial
|
- Martial
|
||||||
- '3'
|
- '3'
|
|
@ -1,21 +1,232 @@
|
||||||
from rolltable import tables
|
import random
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from random_sets.sets import WeightedSet, DataSourceSet
|
||||||
|
|
||||||
|
|
||||||
|
# Create DataSourceSets, which are WeightedSets populated with DataSource
|
||||||
|
# objects generated from yaml data files. These are used to supply default
|
||||||
|
# values to item generators; see below.
|
||||||
sources = Path(__file__).parent / Path("sources")
|
sources = Path(__file__).parent / Path("sources")
|
||||||
|
MAGIC_DAMAGE = DataSourceSet(sources / Path('magic_damage_types.yaml'))
|
||||||
|
WEAPON_TYPES = DataSourceSet(sources / Path('weapons.yaml'))
|
||||||
|
# RARITY = DataSourceSet(sources / Path('rarity.yaml'))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Item:
|
class Item:
|
||||||
pass
|
"""
|
||||||
|
Item is a data class that constructs its attributes from keyword arguments
|
||||||
|
passed to the Item.from_dict() method. Any args that are dicts are
|
||||||
|
recursively converted into Item objects, allowing for access to nested
|
||||||
|
attribtues using dotted notation. Example:
|
||||||
|
|
||||||
|
>>> orb = Item.from_dict(rarity='rare', name='Orb of Example',
|
||||||
|
extra={'cost_in_gp': 1000})
|
||||||
|
>>> orb.rarity
|
||||||
|
rare
|
||||||
|
>>> orb.name
|
||||||
|
Orb of Example
|
||||||
|
>>> orb.extra.cost_in_gp
|
||||||
|
1000
|
||||||
|
|
||||||
|
String Formatting:
|
||||||
|
|
||||||
|
>>> orb.details
|
||||||
|
Orb of Example
|
||||||
|
* extra.cost_in_gp: 1000
|
||||||
|
* rarity: rare
|
||||||
|
|
||||||
|
Name Templates:
|
||||||
|
|
||||||
|
Item names can be built by overriding the default template, using
|
||||||
|
any available attribute:
|
||||||
|
|
||||||
|
>>> orb = Item.from_dict(
|
||||||
|
? name="orb",
|
||||||
|
? rarity="rare",
|
||||||
|
? extra={"cost_in_gp": 1000, "color": "green"},
|
||||||
|
? template="{rarity} {extra.color} {name} of Example",
|
||||||
|
? )
|
||||||
|
>>> orb.name
|
||||||
|
Rare Green Orb of Example
|
||||||
|
"""
|
||||||
|
_name: str = None
|
||||||
|
_template: str = None
|
||||||
|
_attrs: field(default_factory=dict) = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._template.format(name=self._name, **self._attrs).title()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def details(self):
|
||||||
|
"""
|
||||||
|
Format the item attributes as nested bullet lists.
|
||||||
|
"""
|
||||||
|
def attrs_to_lines(item, prefix: str = ''):
|
||||||
|
for (k, v) in item._attrs.items():
|
||||||
|
if type(v) is Item:
|
||||||
|
yield from attrs_to_lines(v, prefix=f"{k}.")
|
||||||
|
continue
|
||||||
|
yield f" * {prefix}{k}: {v}"
|
||||||
|
return "\n".join([self.name] + sorted(list(attrs_to_lines(self))))
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
"""
|
||||||
|
Look up attributes in the _attrs dict first, then fall back to the default.
|
||||||
|
"""
|
||||||
|
if attr in self._attrs:
|
||||||
|
return self._attrs[attr]
|
||||||
|
return self.__getattribute__(attr)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, **kwargs: dict):
|
||||||
|
"""
|
||||||
|
Create a new Item object using keyword arguments. Dicts are recursively
|
||||||
|
converted to Item objects; everything else is passed as-is.
|
||||||
|
|
||||||
|
The "name" and "template" arguments, if supplied, are removed from the
|
||||||
|
keyword arguments and used to populate those attributes directly; all
|
||||||
|
other attributes will be added to the _attrs dict so they can be
|
||||||
|
accessed directly through dotted attribute notation.
|
||||||
|
"""
|
||||||
|
name = kwargs.pop('name') if 'name' in kwargs else '(unnamed)'
|
||||||
|
template = kwargs.pop('template') if 'template' in kwargs else '{name}'
|
||||||
|
attrs = {}
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
attrs[k] = Item.from_dict(**v)if type(v) is dict else v
|
||||||
|
return cls(_name=name, _template=template, _attrs=attrs)
|
||||||
|
|
||||||
|
|
||||||
def random_item(count=1):
|
class Weapon(Item):
|
||||||
types = (sources / Path('types.yaml')).read_text()
|
"""
|
||||||
enchantments = (sources / Path('enchantments.yaml')).read_text()
|
An Item class representing a weapon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ItemGenerator:
|
||||||
|
"""
|
||||||
|
Generate randomized instances of Item objects.
|
||||||
|
|
||||||
|
The main interfaces is the random() method, which will generate one or
|
||||||
|
more random Item instances by selecting random values from the supplied
|
||||||
|
WeightedSets. This allows for fully-controllable frequency distributions.
|
||||||
|
|
||||||
|
You probably want to subclass this class, in order to provide sensible
|
||||||
|
defaults, and control what attributes are available; refer to the
|
||||||
|
subclasses elsewhere in this module.
|
||||||
|
|
||||||
|
The class requires two arguments to instantiate:
|
||||||
|
* templates - a WeightedSet of format strings for item names; and
|
||||||
|
* types - a WeightedSet of item types to be selected from at random.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
>>> ig = ItemGenerator(
|
||||||
|
? templates=WeightedSet("{type.name}", 1.0),
|
||||||
|
? types=WeightedSet(
|
||||||
|
? ({'name': 'ring'}, 1.0),
|
||||||
|
? ({'name': 'hat'}, 1.0),
|
||||||
|
? ),
|
||||||
|
? )
|
||||||
|
>>> ig.random(3).name
|
||||||
|
['hat', 'hat', 'ring']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create instances of this class. Subclasses may wish to override this.
|
||||||
|
item_class = Item
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
templates: WeightedSet,
|
||||||
|
types: WeightedSet,
|
||||||
|
):
|
||||||
|
self.types = types
|
||||||
|
self.templates = templates
|
||||||
|
|
||||||
|
def random_properties(self) -> dict:
|
||||||
|
"""
|
||||||
|
Select random values from the available attributes. These values will
|
||||||
|
be passed as arguments to the Item constructor.
|
||||||
|
|
||||||
|
If you subclass this class and override this method, be sure that
|
||||||
|
whatever attributes are referenced in your template strings are
|
||||||
|
available as properties here. For example, if you have a subclass with
|
||||||
|
the template:
|
||||||
|
|
||||||
|
WeightedSet("{this.color} {that.thing}", 1.0)
|
||||||
|
|
||||||
|
This method must return a dict that includes both this and that, and
|
||||||
|
each of them must be either Item instances or dictionaries.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Select one random template string and one item type.
|
||||||
|
properties = {
|
||||||
|
'template': self.templates.random(),
|
||||||
|
'type': self.types.random(),
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
|
||||||
|
def random(self, count: int = 1) -> list:
|
||||||
|
"""
|
||||||
|
Generate one or more random Item instances by selecting random values
|
||||||
|
from the available types and template
|
||||||
|
"""
|
||||||
items = []
|
items = []
|
||||||
for _ in range(count):
|
for _ in range(count):
|
||||||
rt = tables.RollTable([types, enchantments], die=1, hide_rolls=True)
|
items.append(self.item_class.from_dict(**self.random_properties()))
|
||||||
item = dict(zip(rt.rows[0], rt.rows[1]))
|
|
||||||
item['Enchantment Damage'] = '1d4'
|
|
||||||
items.append(item)
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponGenerator(ItemGenerator):
|
||||||
|
"""
|
||||||
|
An ItemGenerator that generates basic (non-magical) weapons.
|
||||||
|
"""
|
||||||
|
|
||||||
|
item_class = Weapon
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
templates: WeightedSet = None,
|
||||||
|
types: WeightedSet = WEAPON_TYPES,
|
||||||
|
):
|
||||||
|
if not templates:
|
||||||
|
templates = WeightedSet(('{type.name}', 1.0),)
|
||||||
|
super().__init__(types=types, templates=templates)
|
||||||
|
|
||||||
|
|
||||||
|
class MagicWeaponGenerator(WeaponGenerator):
|
||||||
|
"""
|
||||||
|
An ItemGenerator that generates weapons imbued with magical effects.
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
templates: WeightedSet = None,
|
||||||
|
types: WeightedSet = WEAPON_TYPES,
|
||||||
|
magic: WeightedSet = MAGIC_DAMAGE,
|
||||||
|
):
|
||||||
|
self.magic = magic
|
||||||
|
if not templates:
|
||||||
|
templates = WeightedSet(
|
||||||
|
# "Shortsword of Flames"
|
||||||
|
('{type.name} of {magic.noun}', 1.0),
|
||||||
|
# "Burning Lance"
|
||||||
|
('{magic.adjective} {type.name}', 1.0),
|
||||||
|
)
|
||||||
|
super().__init__(types=types, templates=templates)
|
||||||
|
|
||||||
|
def random_properties(self):
|
||||||
|
"""
|
||||||
|
Select a random magical damage type and add it to our properties.
|
||||||
|
"""
|
||||||
|
properties = super().random_properties()
|
||||||
|
magic = self.magic.random()
|
||||||
|
properties['magic'] = {
|
||||||
|
'adjective': random.choice(magic['adjectives'].split(',')).strip(),
|
||||||
|
'noun': random.choice(magic['noun'].split(',')).strip(),
|
||||||
|
'die': '1d4'
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
|
|
@ -14,7 +14,8 @@ rich = "^13.7.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
dice = "^4.0.0"
|
dice = "^4.0.0"
|
||||||
|
|
||||||
dnd-rolltable = { git = "https://github.com/evilchili/dnd-rolltable", branch='main' }
|
dnd-name-generator = { git = "https://github.com/evilchili/dnd-name-generator", branch='main' }
|
||||||
|
random-sets = { git = "https://github.com/evilchili/random-sets", branch='main' }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = "^7.4.3"
|
pytest = "^7.4.3"
|
||||||
|
@ -45,5 +46,5 @@ remove-duplicate-keys = true # remove all duplicate keys in objects
|
||||||
remove-unused-variables = true # remove unused variables
|
remove-unused-variables = true # remove unused variables
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
fanitem = "dnd_item.cli:app"
|
dnd-item = "dnd_item.cli:app"
|
||||||
|
|
||||||
|
|
11
tests/test_types.py
Normal file
11
tests/test_types.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from dnd_item import types
|
||||||
|
|
||||||
|
|
||||||
|
def test_item_attributes():
|
||||||
|
item = types.Item.from_dict(
|
||||||
|
foo='bar',
|
||||||
|
baz={
|
||||||
|
'qaz': True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert item.baz.qaz is True
|
Loading…
Reference in New Issue
Block a user