diff --git a/dnd_item/cli.py b/dnd_item/cli.py index c121255..e456a65 100644 --- a/dnd_item/cli.py +++ b/dnd_item/cli.py @@ -61,7 +61,7 @@ def table( True, help='If True, collapse multiple die values with the same option.'), width: int = typer.Option( - 120, + 180, help='Width of the table.'), output: OUTPUT_FORMATS = typer.Option( 'text', diff --git a/dnd_item/sources/properties_base.yaml b/dnd_item/sources/properties_base.yaml index 759516e..13d84bd 100644 --- a/dnd_item/sources/properties_base.yaml +++ b/dnd_item/sources/properties_base.yaml @@ -3,22 +3,24 @@ metadata: - name - description light: - - 'A light weapon is small and easy to handle, making it ideal for use when fighting with two weapons.' + - "A light weapon is small and easy to handle, making it ideal for use when fighting with two weapons." thrown: - - '' + - "If a weapon has the thrown property, you can throw the weapon to make a ranged attack. If the weapon is a melee weapon, you use the same ability modifier for that attack roll and damage roll that you would use for a melee attack with the weapon." heavy: - - '' -special: - - '' + - "Small creatures have disadvantage on attack rolls with heavy weapons. A heavy weapon’s size and bulk make it too large for a Small creature to use effectively." two-handed: - - '' + - "This weapon requires two hands when you attack with it." versatile: - - '' + - "This weapon can be used with one or two hands. A damage value in parentheses appears with the property—the damage when the weapon is used with two hands to make a melee attack." finesse: - - '' + - "When making an attack with a finesse weapon, you use your choice of your Strength or Dexterity modifier for the attack and damage rolls. You must use the same modifier for both rolls." ammunition: - - '' + - "You can use a weapon that has the ammunition property to make a ranged attack only if you have ammunition to fire from the weapon. Each time you attack with the weapon, you expend one piece of ammunition. Drawing the ammunition from a quiver, case, or other container is part of the attack (you need a free hand to load a one-handed weapon)." reach: - - '' + - "This weapon adds 5 feet to your reach when you attack with it, as well as when determining your reach for opportunity attacks with it." loading: - - '' + - "Because of the time required to load this weapon, you can fire only one piece of ammunition from it when you use an action, bonus action, or reaction to fire it, regardless of the number of attacks you can normally make." +ranged: + - "A weapon that can be used to make a ranged attack has a range in parentheses after the ammunition or thrown property. The range lists two numbers. The first is the weapon’s normal range in feet, and the second indicates the weapon’s long range. When attacking a target beyond normal range, you have disadvantage on the attack roll. You can’t attack a target beyond the weapon’s long range." +special: + - "Refer to Player's Handbook." diff --git a/dnd_item/sources/properties_legendary.yaml b/dnd_item/sources/properties_legendary.yaml index 6c6d1ca..cb3d9d0 100644 --- a/dnd_item/sources/properties_legendary.yaml +++ b/dnd_item/sources/properties_legendary.yaml @@ -17,7 +17,7 @@ enchanted: - 0 - weapon magical: - - 'striking, smacking' + - 'legend, legendary strking, legendary smacking, legendary strikes' - '+3' - This magical weapon grants +3 to attack and damage rolls. - '{damage_type}' diff --git a/dnd_item/sources/properties_rare.yaml b/dnd_item/sources/properties_rare.yaml index 5653481..a93a224 100644 --- a/dnd_item/sources/properties_rare.yaml +++ b/dnd_item/sources/properties_rare.yaml @@ -17,7 +17,7 @@ enchanted: - 0 - weapon magical: - - 'striking, smacking' + - 'precision, precise striking, precise smacking, precise strikes' - '+2' - This magical weapon grants +2 to attack and damage rolls. - '{damage_type}' diff --git a/dnd_item/sources/properties_uncommon.yaml b/dnd_item/sources/properties_uncommon.yaml index 07896e8..ca3d06b 100644 --- a/dnd_item/sources/properties_uncommon.yaml +++ b/dnd_item/sources/properties_uncommon.yaml @@ -32,7 +32,7 @@ enchanted: - null - weapon magical: - - 'striking, smacking' + - 'honed striking, honed smacking, honed strikes' - '+1' - This magical weapon grants +1 to attack and damage rolls. - '{damage_type}' diff --git a/dnd_item/sources/properties_very_rare.yaml b/dnd_item/sources/properties_very_rare.yaml index 4de5844..9ac25f7 100644 --- a/dnd_item/sources/properties_very_rare.yaml +++ b/dnd_item/sources/properties_very_rare.yaml @@ -17,7 +17,7 @@ enchanted: - 0 - weapon magical: - - 'striking, smacking' + - 'might, mighty strking, mighty smacking, mighty strikes' - '+3' - This magical weapon grants +3 to attack and damage rolls. - '{damage_type}' diff --git a/dnd_item/types.py b/dnd_item/types.py index 0360e89..d03d638 100644 --- a/dnd_item/types.py +++ b/dnd_item/types.py @@ -252,8 +252,11 @@ class GeneratorSource: ( item.rarity['sort_order'], [ - item.name, item.rarity['rarity'], - item.summary, ', '.join(item.get('properties', [])) + item.name, + item.rarity['rarity'], + item.summary, + ', '.join(item.get('properties', [])), + item.id ] ) for item in self.generator.random(count=count, challenge_rating=self.cr) @@ -286,6 +289,7 @@ class RollTable(rolltable.types.RollTable): 'Name', 'Rarity', 'Summary', - 'Properties' + 'Properties', + 'ID', ] self._header_excludes = [] diff --git a/dnd_item/weapons.py b/dnd_item/weapons.py index 70dd552..3507310 100644 --- a/dnd_item/weapons.py +++ b/dnd_item/weapons.py @@ -1,4 +1,6 @@ import random +import base64 +import hashlib from functools import cached_property @@ -27,14 +29,14 @@ class Weapon(types.Item): nouns[prop_name] = equal_weights(prop.nouns.split(','), blank=False) if hasattr(prop, 'adjectives'): adjectives[prop_name] = equal_weights(prop.adjectives.split(','), blank=False) - return (nouns, adjectives) + return (nouns, adjectives) def _name_template(self, with_adjectives: bool, with_nouns: bool) -> str: num_properties = len(self.properties) options = [] - if with_nouns: + if with_nouns and not with_adjectives: options.append(('{name} of {nouns}', 0.5)) - if with_adjectives: + if with_adjectives and not with_nouns: options.append(('{adjectives} {name}', 0.5)) if with_nouns and with_adjectives: if num_properties == 1: @@ -60,22 +62,34 @@ class Weapon(types.Item): def add_word(key, obj, source): obj.append(source[key].random().strip()) + seen_nouns = dict() for prop_name in set(list(nouns.keys()) + list(adjectives.keys())): - if prop_name in nouns and prop_name in adjectives: - val = random.random() - if val <= 0.4: + + if prop_name in nouns and prop_name not in adjectives: + if prop_name not in seen_nouns: add_word(prop_name, random_nouns, nouns) + seen_nouns[prop_name] = True + + elif prop_name in adjectives and prop_name not in nouns: + add_word(prop_name, random_adjectives, adjectives) + + if prop_name in nouns and prop_name in adjectives: + # if the property has both nouns and adjectives, select one + # or the other or both, for the weapon name. Both leads to + # spurious names like 'thundering dagger of thunder', so we + # we reduce the likelihood of this eventuality so it is an + # occasionl bit of silliness, not consistently silly. + val = random.random() + if val <= 0.4 and prop_name not in seen_nouns: + add_word(prop_name, random_nouns, nouns) + seen_nouns[prop_name] = True elif val <= 0.8: add_word(prop_name, random_adjectives, adjectives) else: add_word(prop_name, random_nouns, nouns) add_word(prop_name, random_adjectives, adjectives) - elif prop_name in nouns: - add_word(prop_name, random_nouns, nouns) - elif prop_name in adjectives: - add_word(prop_name, random_adjectives, adjectives) - random_nouns = ' '.join(random_nouns) + random_nouns = ' and '.join(random_nouns) random_adjectives = ' '.join(random_adjectives) return (random_nouns, random_adjectives) @@ -131,7 +145,7 @@ class Weapon(types.Item): @property def summary(self): - return f"{self.to_hit} to hit, {self.range} ft., {self.targets} targets. {self.damage_dice}" + return f"{self.to_hit} to hit, {self.range} ft., {self.targets} tgts. {self.damage_dice}" @property def details(self): @@ -146,6 +160,13 @@ class Weapon(types.Item): f"\n{self.description}\n" ]) + @property + def id(self): + sha1bytes = hashlib.sha1(''.join([ + self._name, self.to_hit, self.damage_dice, + ]).encode()) + return base64.urlsafe_b64encode(sha1bytes.digest()).decode('ascii')[:10] + class WeaponGenerator(types.ItemGenerator): item_class = Weapon