diff --git a/dnd_item/cli.py b/dnd_item/cli.py index 21f8733..85f4590 100644 --- a/dnd_item/cli.py +++ b/dnd_item/cli.py @@ -16,7 +16,9 @@ app_state = {} @app.callback() -def main(): +def main( + cr: int = typer.Option(default=None, help='The Challenge Rating to use when determining rarity.'), +): debug = os.getenv("FANITEM_DEBUG", None) logging.basicConfig( format="%(name)s %(message)s", @@ -25,20 +27,21 @@ def main(): ) logging.getLogger('markdown_it').setLevel(logging.ERROR) + app_state['cr'] = cr or 0 app_state['data'] = Path(__file__).parent / Path("sources") @app.command() def weapon(count: int = typer.Option(1, help="The number of weapons to generate.")): console = Console() - for weapon in WeaponGenerator().random(count): + for weapon in WeaponGenerator().random(count=count, challenge_rating=app_state['cr']): console.print(weapon.details) @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): + for weapon in MagicWeaponGenerator().random(count=count, challenge_rating=app_state['cr']): console.print(weapon.details) diff --git a/dnd_item/sources/magic_damage_types.yaml b/dnd_item/sources/magic_damage_types.yaml index c03b63d..bc04cc1 100644 --- a/dnd_item/sources/magic_damage_types.yaml +++ b/dnd_item/sources/magic_damage_types.yaml @@ -5,7 +5,6 @@ metadata: - adjective frequencies: default: - magic: 1.0 fire: 1.0 cold: 1.0 acid: 1.0 @@ -16,9 +15,6 @@ metadata: force: 1.0 necrotic: 1.0 radiant: 1.0 -magic: - - magic - - magical fire: - flames, fire - flaming, burning diff --git a/dnd_item/sources/rarity.yaml b/dnd_item/sources/rarity.yaml index 6a04f39..24165a8 100644 --- a/dnd_item/sources/rarity.yaml +++ b/dnd_item/sources/rarity.yaml @@ -1,15 +1,46 @@ +# 5e Iteam Rarity Distributions By Challenge Rating +# +# This distribution is based on this excellent analysis of the DMG loot tables, though +# I have smoothed and tweaked a little bit (the CR17+ frequencies are brokekn IMO): +# +# https://www.enworld.org/threads/analysis-of-typical-magic-item-distribution.395770/ +# metadata: headers: - - Rarity + - rarity frequencies: - default: - Common: 1.0 - Uncommon: 0.8 - Rare: 0.5 - Legendary: 0.1 - Unique: 0.05 -Common: -Uncommon: -Rare: -Legendary: -Unique: + 'default': + common: 1.0 + uncommon: 1.0 + rare: 1.0 + very rare: 1.0 + legendary: 1.0 + '1-4': + common: 0.75 + uncommon: 0.5 + rare: 0.25 + very rare: 0.0 + legendary: 0.0 + '5-10': + common: 0.5 + uncommon: 0.75 + rare: 0.25 + very rare: 0.05 + legendary: 0.0 + '11-16': + common: 0.25 + uncommon: 0.5 + rare: 0.75 + very rare: 0.5 + legendary: 0.1 + '17': + common: 0.05 + uncommon: 0.05 + rare: 0.5 + very rare: 0.5 + legendary: 0.3 +common: +uncommon: +rare: +very rare: +legendary: diff --git a/dnd_item/types.py b/dnd_item/types.py index 6f82184..7962db7 100644 --- a/dnd_item/types.py +++ b/dnd_item/types.py @@ -12,7 +12,7 @@ from random_sets.sets import WeightedSet, DataSourceSet 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')) +RARITY = DataSourceSet(sources / Path('rarity.yaml')) @dataclass @@ -143,9 +143,11 @@ class ItemGenerator: self, templates: WeightedSet, types: WeightedSet, + rarity: WeightedSet = RARITY, ): self.types = types self.templates = templates + self.rarity = rarity def random_properties(self) -> dict: """ @@ -167,14 +169,30 @@ class ItemGenerator: properties = { 'template': self.templates.random(), 'type': self.types.random(), + 'rarity': self.rarity.random(), } return properties - def random(self, count: int = 1) -> list: + def random(self, count: int = 1, challenge_rating: int = 0) -> list: """ Generate one or more random Item instances by selecting random values from the available types and template """ + + # select the appropriate frequency distributionnb ased on the specified + # challenge rating. By default, all rarities are weighted equally. + if challenge_rating in range(1, 5): + frequency = '1-4' + elif challenge_rating in range(5, 11): + frequency = '5-10' + elif challenge_rating in range(11, 17): + frequency = '11-16' + elif challenge_rating >= 17: + frequency = '17' + else: + frequency = 'default' + self.rarity.set_frequency(frequency) + items = [] for _ in range(count): items.append(self.item_class.from_dict(**self.random_properties())) @@ -192,10 +210,11 @@ class WeaponGenerator(ItemGenerator): self, templates: WeightedSet = None, types: WeightedSet = WEAPON_TYPES, + rarity: WeightedSet = RARITY, ): if not templates: templates = WeightedSet(('{type.name}', 1.0),) - super().__init__(types=types, templates=templates) + super().__init__(types=types, templates=templates, rarity=rarity) class MagicWeaponGenerator(WeaponGenerator): @@ -206,6 +225,7 @@ class MagicWeaponGenerator(WeaponGenerator): self, templates: WeightedSet = None, types: WeightedSet = WEAPON_TYPES, + rarity: WeightedSet = RARITY, magic: WeightedSet = MAGIC_DAMAGE, ): self.magic = magic @@ -216,7 +236,7 @@ class MagicWeaponGenerator(WeaponGenerator): # "Burning Lance" ('{magic.adjective} {type.name}', 1.0), ) - super().__init__(types=types, templates=templates) + super().__init__(types=types, templates=templates, rarity=rarity) def random_properties(self): """