first working commit of random items

This commit is contained in:
evilchili 2023-12-27 00:42:31 -08:00
parent 84c955fb62
commit 6d45ba9c4b
9 changed files with 246 additions and 213 deletions

View File

@ -11,7 +11,7 @@ from rich.logging import RichHandler
from rich.console import Console from rich.console import Console
from rich.table import Table from rich.table import Table
from dnd_item.types import WeaponGenerator, MagicWeaponGenerator, RollTable from dnd_item.types import WeaponGenerator, RollTable
from dnd_item import five_e from dnd_item import five_e
app = typer.Typer() app = typer.Typer()
@ -24,7 +24,6 @@ class OUTPUT_FORMATS(Enum):
markdown = 'markdown' markdown = 'markdown'
@app.callback() @app.callback()
def main( def main(
cr: int = typer.Option(default=None, help='The Challenge Rating to use when determining rarity.'), cr: int = typer.Option(default=None, help='The Challenge Rating to use when determining rarity.'),
@ -48,12 +47,6 @@ def weapon(count: int = typer.Option(1, help="The number of weapons to generate.
console.print(weapon.details) 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=count, challenge_rating=app_state['cr']):
console.print(weapon.details)
@app.command("roll-table") @app.command("roll-table")
def table( def table(
die: int = typer.Option( die: int = typer.Option(
@ -78,7 +71,7 @@ def table(
CLI for creating roll tables of randomly-generated items. CLI for creating roll tables of randomly-generated items.
""" """
rt = RollTable( rt = RollTable(
sources=[MagicWeaponGenerator], sources=[WeaponGenerator],
die=die, die=die,
hide_rolls=hide_rolls, hide_rolls=hide_rolls,
challenge_rating=app_state['cr'], challenge_rating=app_state['cr'],
@ -96,7 +89,6 @@ def table(
print(table) print(table)
@app.command() @app.command()
def convert(): def convert():
src = five_e.weapons() src = five_e.weapons()

View File

@ -1,24 +0,0 @@
metadata:
headers:
- rarity
- name
- nouns
- adjectives
- description
- type
common:
uncommon:
rare:
'+2':
- magic
- '+2'
- This magical weapon grants +2 to attack and damage rolls.
- weapon
very rare:
'+3':
- magic
- '+3'
- This magical weapon grants +3 to attack and damage rolls.
- weapon
legendary:

View File

@ -8,5 +8,5 @@ metadata:
light: light:
- 'light' - 'light'
- 'light' - 'light'
- 'Weapons with the light property...' - 'A light weapon is small and easy to handle, making it ideal for use when fighting with two weapons.'
- weapon - weapon

View File

@ -4,17 +4,23 @@ metadata:
- nouns - nouns
- adjectives - adjectives
- description - description
- damage_modifier - damage_type
- damage
- to_hit
- type - type
element: enchanted:
- '{element.nouns}' - '{enchanted.nouns}'
- '{element.adjectives}' - '{enchanted.adjectives}'
- 'Attacks made with the {name} do an extra {this.damage_modifier} {element.damage_type} damage.' - 'Attacks made with this magical weapon do an extra {this.damage} {this.damage_type} damage.'
- d6 - '{enchanted.damage_type}'
- 1d6
- 0
- weapon - weapon
'+2': magical:
- magic - magic
- '+2' - '+2'
- This magical weapon grants +2 to attack and damage rolls. - This magical weapon grants +2 to attack and damage rolls.
- '{base.damage_type}'
- 2
- 2 - 2
- weapon - weapon

View File

@ -4,17 +4,23 @@ metadata:
- nouns - nouns
- adjectives - adjectives
- description - description
- damage_modifier - damage_type
- damage
- to_hit
- type - type
element: enchanted:
- '{element.nouns}' - '{enchanted.nouns}'
- '{element.adjectives}' - '{enchanted.adjectives}'
- 'Attacks made with the {name} do an extra {this.damage_modifier} {element.damage_type} damage.' - 'Attacks made with this magical weapon do an extra {this.damage} {this.damage_type} damage.'
- d4 - '{enchanted.damage_type}'
- 1d4
- 0
- weapon - weapon
'+1': magical:
- magic - magic
- '+1' - '+1'
- This magical weapon grants +1 to attack and damage rolls. - This magical weapon grants +1 to attack and damage rolls.
- '{base.damage_type}'
- 1
- 1 - 1
- weapon - weapon

View File

@ -4,17 +4,23 @@ metadata:
- nouns - nouns
- adjectives - adjectives
- description - description
- damage modifier - damage_type
- damage
- to_hit
- type - type
element: enchanted:
- '{element.nouns}' - '{enchanted.nouns}'
- '{element.adjectives}' - '{enchanted.adjectives}'
- 'Attacks made with the {name} do an extra {this.damage_modifier} {element.damage_type} damage.' - 'Attacks made with this magical weapon do an extra {this.damage} {this.damage_type} damage.'
- d8 - '{enchanted.damage_type}'
- 1d8
- 0
- weapon - weapon
'+3': magical:
- magic - magic
- '+3' - '+3'
- This magical weapon grants +3 to attack and damage rolls. - This magical weapon grants +3 to attack and damage rolls.
- '{base.damage_type}'
- 3
- 3 - 3
- weapon - weapon

View File

@ -8,6 +8,7 @@
metadata: metadata:
headers: headers:
- rarity - rarity
- sort_order
frequencies: frequencies:
'default': 'default':
common: 1.0 common: 1.0
@ -40,7 +41,12 @@ metadata:
very rare: 0.5 very rare: 0.5
legendary: 0.3 legendary: 0.3
common: common:
- 0
uncommon: uncommon:
- 1
rare: rare:
- 2
very rare: very rare:
- 3
legendary: legendary:
- 4

View File

@ -4,8 +4,8 @@ metadata:
- category - category
- type - type
- weight - weight
- damage_type
- damage - damage
- die
- range - range
- reload - reload
- value - value
@ -16,7 +16,7 @@ Battleaxe:
- '4' - '4'
- Slashing - Slashing
- 1d8 - 1d8
- null - 5
- '' - ''
- '1000' - '1000'
- versatile - versatile
@ -36,7 +36,7 @@ Club:
- '2' - '2'
- Bludgeoning - Bludgeoning
- 1d4 - 1d4
- null - 5
- '' - ''
- '10' - '10'
- light - light
@ -66,7 +66,7 @@ Double-bladed scimitar:
- '6' - '6'
- Slashing - Slashing
- 2d4 - 2d4
- null - 5
- '' - ''
- '10000' - '10000'
- special, two-handed - special, two-handed
@ -76,7 +76,7 @@ Flail:
- '2' - '2'
- Bludgeoning - Bludgeoning
- 1d8 - 1d8
- null - 5
- '' - ''
- '1000' - '1000'
- '' - ''
@ -86,7 +86,7 @@ Glaive:
- '6' - '6'
- Slashing - Slashing
- 1d10 - 1d10
- null - 5
- '' - ''
- '2000' - '2000'
- heavy, reach, two-handed - heavy, reach, two-handed
@ -96,7 +96,7 @@ Greataxe:
- '7' - '7'
- Slashing - Slashing
- 1d12 - 1d12
- null - 5
- '' - ''
- '3000' - '3000'
- heavy, two-handed - heavy, two-handed
@ -106,7 +106,7 @@ Greatclub:
- '10' - '10'
- Bludgeoning - Bludgeoning
- 1d8 - 1d8
- null - 5
- '' - ''
- '20' - '20'
- two-handed - two-handed
@ -116,7 +116,7 @@ Greatsword:
- '6' - '6'
- Slashing - Slashing
- 2d6 - 2d6
- null - 5
- '' - ''
- '5000' - '5000'
- heavy, two-handed - heavy, two-handed
@ -126,7 +126,7 @@ Halberd:
- '6' - '6'
- Slashing - Slashing
- 1d10 - 1d10
- null - 5
- '' - ''
- '2000' - '2000'
- heavy, reach, two-handed - heavy, reach, two-handed
@ -166,7 +166,7 @@ Hooked shortspear:
- '2' - '2'
- Piercing - Piercing
- 1d4 - 1d4
- null - 5
- '' - ''
- '' - ''
- light - light
@ -196,7 +196,7 @@ Lance:
- '6' - '6'
- Piercing - Piercing
- 1d12 - 1d12
- null - 5
- '' - ''
- '1000' - '1000'
- reach, special - reach, special
@ -246,7 +246,7 @@ Longsword:
- '3' - '3'
- Slashing - Slashing
- 1d8 - 1d8
- null - 5
- '' - ''
- '1500' - '1500'
- versatile - versatile
@ -256,7 +256,7 @@ Mace:
- '4' - '4'
- Bludgeoning - Bludgeoning
- 1d6 - 1d6
- null - 5
- '' - ''
- '500' - '500'
- '' - ''
@ -266,7 +266,7 @@ Maul:
- '10' - '10'
- Bludgeoning - Bludgeoning
- 2d6 - 2d6
- null - 5
- '' - ''
- '1000' - '1000'
- heavy, two-handed - heavy, two-handed
@ -276,7 +276,7 @@ Morningstar:
- '4' - '4'
- Piercing - Piercing
- 1d8 - 1d8
- null - 5
- '' - ''
- '1500' - '1500'
- '' - ''
@ -285,7 +285,7 @@ Net:
- Ranged - Ranged
- '3' - '3'
- '' - ''
- null - 5
- 5/15 - 5/15
- '' - ''
- '100' - '100'
@ -296,7 +296,7 @@ Pike:
- '18' - '18'
- Piercing - Piercing
- 1d10 - 1d10
- null - 5
- '' - ''
- '500' - '500'
- heavy, reach, two-handed - heavy, reach, two-handed
@ -306,7 +306,7 @@ Quarterstaff:
- '4' - '4'
- Bludgeoning - Bludgeoning
- 1d6 - 1d6
- null - 5
- '' - ''
- '20' - '20'
- versatile - versatile
@ -316,7 +316,7 @@ Rapier:
- '2' - '2'
- Piercing - Piercing
- 1d8 - 1d8
- null - 5
- '' - ''
- '2500' - '2500'
- finesse - finesse
@ -326,7 +326,7 @@ Scimitar:
- '3' - '3'
- Slashing - Slashing
- 1d6 - 1d6
- null - 5
- '' - ''
- '2500' - '2500'
- finesse, light - finesse, light
@ -346,7 +346,7 @@ Shortsword:
- '2' - '2'
- Piercing - Piercing
- 1d6 - 1d6
- null - 5
- '' - ''
- '1000' - '1000'
- finesse, light - finesse, light
@ -356,7 +356,7 @@ Sickle:
- '2' - '2'
- Slashing - Slashing
- 1d4 - 1d4
- null - 5
- '' - ''
- '100' - '100'
- light - light
@ -396,7 +396,7 @@ War pick:
- '2' - '2'
- Piercing - Piercing
- 1d8 - 1d8
- null - 5
- '' - ''
- '500' - '500'
- '' - ''
@ -406,7 +406,7 @@ Warhammer:
- '2' - '2'
- Bludgeoning - Bludgeoning
- 1d8 - 1d8
- null - 5
- '' - ''
- '1500' - '1500'
- versatile - versatile
@ -416,7 +416,7 @@ Whip:
- '3' - '3'
- Slashing - Slashing
- 1d4 - 1d4
- null - 5
- '' - ''
- '200' - '200'
- finesse, reach - finesse, reach

View File

@ -11,7 +11,7 @@ import rolltable.types
# objects generated from yaml data files. These are used to supply default # objects generated from yaml data files. These are used to supply default
# values to item generators; see below. # 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')) ENCHANTMENT = DataSourceSet(sources / Path('magic_damage_types.yaml'))
WEAPON_TYPES = DataSourceSet(sources / Path('weapons.yaml')) WEAPON_TYPES = DataSourceSet(sources / Path('weapons.yaml'))
RARITY = DataSourceSet(sources / Path('rarity.yaml')) RARITY = DataSourceSet(sources / Path('rarity.yaml'))
PROPERTIES = { PROPERTIES = {
@ -24,79 +24,9 @@ PROPERTIES = {
@dataclass @dataclass
class Item: class AttributeDict:
"""
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 _attrs: field(default_factory=dict) = None
@property
def name(self):
return self._template.format(name=self._name, **self._attrs).format(**self._attrs).title()
@property
def summary(self):
txt = []
for k, v in self._attrs['properties']._attrs.items():
txt.append(v.description.format(
this=v,
name=self.name,
**self._attrs,
))
return "\n\n".join(txt)
@property
def details(self):
"""
Format the item attributes as nested bullet lists.
"""
return f"{self.name} ({self.rarity.rarity})\n{self.summary}\n--\n{self.properties}"
@property
def properties(self):
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(["Properties:"] + sorted(list(attrs_to_lines(self))))
def __getattr__(self, attr): def __getattr__(self, attr):
""" """
Look up attributes in the _attrs dict first, then fall back to the default. Look up attributes in the _attrs dict first, then fall back to the default.
@ -105,6 +35,88 @@ class Item:
return self._attrs[attr] return self._attrs[attr]
return self.__getattribute__(attr) return self.__getattribute__(attr)
def __str__(self):
return self._flatten(self)
def __repr__(self):
return str(self)
def _flatten(self, obj, prefix=None):
if prefix == '':
prefix = f"{self.__class__.__name__}."
else:
prefix = ''
lines = []
for (k, v) in obj._attrs.items():
if type(v) is AttributeDict:
lines.append(self._flatten(v, prefix=f"{prefix}{k}"))
else:
lines.append(f"{prefix}{k} = {v}")
return "\n".join(lines)
@classmethod
def from_dict(cls, **kwargs: dict):
"""
Create a new AttributeDict object using keyword arguments. Dicts are
recursively converted to AttributeDict objects; everything else is
passed as-is.
"""
attrs = {}
for k, v in sorted(kwargs.items()):
attrs[k] = AttributeDict.from_dict(**v)if type(v) is dict else v
return cls(_attrs=attrs)
@dataclass
class Item(AttributeDict):
"""
"""
_name: str = None
rarity: str = None
_template: str = None
@property
def name(self):
return self._template.format(name=self._name, **self._attrs).format(**self._attrs).title()
@property
def properties(self):
return self._attrs['properties']._attrs
@property
def description(self):
txt = []
for k, v in self.properties.items():
txt.append(k.title() + ". " + v.description.format(
this=v,
name=self.name,
**self._attrs,
).format(name=self.name, **self._attrs))
return "\n".join(txt)
@property
def details(self):
"""
Format the item attributes as nested bullet lists.
"""
return "\n".join([
f"{self.name} ({self.rarity['rarity']}):",
self.description,
"",
self._flatten(self.base, prefix=None)
])
@property
def _properties_as_text(self):
def attrs_to_lines(item, prefix: str = ''):
for (k, v) in item._attrs.items():
if type(v) is AttributeDict:
yield from attrs_to_lines(v, prefix=f"{k}.")
continue
yield f" * {prefix}{k}: {v}"
return "\n".join(["Properties:"] + sorted(list(attrs_to_lines(self))))
@classmethod @classmethod
def from_dict(cls, **kwargs: dict): def from_dict(cls, **kwargs: dict):
""" """
@ -117,18 +129,70 @@ class Item:
accessed directly through dotted attribute notation. accessed directly through dotted attribute notation.
""" """
name = kwargs.pop('name') if 'name' in kwargs else '(unnamed)' name = kwargs.pop('name') if 'name' in kwargs else '(unnamed)'
rarity = kwargs.pop('rarity') if 'rarity' in kwargs else 'common'
template = kwargs.pop('template') if 'template' in kwargs else '{name}' template = kwargs.pop('template') if 'template' in kwargs else '{name}'
attrs = {} attrs = {}
for k, v in kwargs.items(): for k, v in kwargs.items():
attrs[k] = Item.from_dict(**v)if type(v) is dict else v attrs[k] = AttributeDict.from_dict(**v)if type(v) is dict else v
return cls(_name=name, _template=template, _attrs=attrs) return cls(_name=name, rarity=rarity, _template=template, _attrs=attrs)
def __repr__(self):
return str(self)
class Weapon(Item): class Weapon(Item):
""" """
An Item class representing a weapon. An Item class representing a weapon with the following attributes:
""" """
@property
def to_hit(self):
bonus_val = 0
bonus_dice = ''
for prop in self.properties.values():
mod = getattr(prop, 'to_hit', None)
if not mod:
continue
if type(mod) is int:
bonus_val += mod
elif type(mod) is str:
bonus_dice += f"+{mod}"
return f"+{bonus_val}{bonus_dice}"
@property
def damage_dice(self):
dmg = dict()
dmg[self.base.damage_type] = self.base.damage or ''
for prop in self.properties.values():
mod = getattr(prop, 'damage', None)
if not mod:
continue
key = str(prop.damage_type).format(**self._attrs).title()
if key not in dmg:
dmg[key] = str(mod)
else:
dmg[key] += f"+{mod}"
return ' + '.join([f"{v} {k}" for k, v in dmg.items()])
@property
def summary(self):
return f"{self.to_hit} to hit, {self.base.range} ft., {self.base.targets} targets. {self.damage_dice}"
@property
def details(self):
"""
Format the item attributes as nested bullet lists.
"""
return "\n".join([
f"{self.name}",
f" * {self.rarity['rarity']} {self.base.category} weapon ({self.base.properties})",
f" * {self.summary}",
f"\n{self.description}\n" if self.description else "",
"----",
self._flatten(self.base, prefix=None)
])
class ItemGenerator: class ItemGenerator:
""" """
@ -165,11 +229,11 @@ class ItemGenerator:
def __init__( def __init__(
self, self,
templates: WeightedSet, templates: WeightedSet,
types: WeightedSet, bases: WeightedSet,
rarity: WeightedSet = RARITY, rarity: WeightedSet = RARITY,
properties: WeightedSet = PROPERTIES, properties: WeightedSet = PROPERTIES,
): ):
self.types = types self.bases = bases
self.templates = templates self.templates = templates
self.rarity = rarity self.rarity = rarity
self.properties = properties self.properties = properties
@ -191,7 +255,7 @@ class ItemGenerator:
""" """
return { return {
'template': self.templates.random(), 'template': self.templates.random(),
'type': self.types.random(), 'base': self.bases.random(),
'rarity': self.rarity.random(), 'rarity': self.rarity.random(),
'properties': self.properties.random(), 'properties': self.properties.random(),
} }
@ -223,37 +287,18 @@ class ItemGenerator:
class WeaponGenerator(ItemGenerator): class WeaponGenerator(ItemGenerator):
"""
An ItemGenerator that generates basic (non-magical) weapons.
"""
item_class = Weapon item_class = Weapon
def __init__( def __init__(
self, self,
templates: WeightedSet = None, templates: WeightedSet = WeightedSet(('{base.name}', 1.0),),
types: WeightedSet = WEAPON_TYPES, bases: WeightedSet = WEAPON_TYPES,
rarity: WeightedSet = RARITY, rarity: WeightedSet = RARITY,
enchanted: WeightedSet = ENCHANTMENT,
properties: WeightedSet = PROPERTIES, properties: WeightedSet = PROPERTIES,
): ):
if not templates: super().__init__(bases=bases, templates=None, rarity=rarity, properties=properties)
templates = WeightedSet(('{type.name}', 1.0),) self.enchanted = enchanted
super().__init__(types=types, templates=templates, rarity=rarity, properties=properties)
class MagicWeaponGenerator(WeaponGenerator):
"""
An ItemGenerator that generates weapons imbued with magical effects.
"""
def __init__(
self,
types: WeightedSet = WEAPON_TYPES,
rarity: WeightedSet = RARITY,
element: WeightedSet = MAGIC_DAMAGE,
properties: WeightedSet = PROPERTIES,
):
super().__init__(types=types, templates=None, rarity=rarity, properties=properties)
self.element = element
self.property_count_by_rarity = { self.property_count_by_rarity = {
'common': WeightedSet((0, 1.0)), 'common': WeightedSet((0, 1.0)),
'uncommon': WeightedSet((1, 1.0)), 'uncommon': WeightedSet((1, 1.0)),
@ -264,19 +309,19 @@ class MagicWeaponGenerator(WeaponGenerator):
def get_template(self, attrs) -> WeightedSet: def get_template(self, attrs) -> WeightedSet:
if not attrs['properties']: if not attrs['properties']:
return '{type.name}' return '{base.name}'
options = [] options = []
if attrs['nouns']: if attrs['nouns']:
options.append(('{type.name} of {nouns}', 1.0)) options.append(('{base.name} of {nouns}', 1.0))
if attrs['adjectives']: if attrs['adjectives']:
options.append(('{adjectives} {type.name}', 1.0)) options.append(('{adjectives} {base.name}', 1.0))
if attrs['nouns'] and attrs['adjectives']: if attrs['nouns'] and attrs['adjectives']:
numprops = len(attrs['properties'].keys()) numprops = len(attrs['properties'].keys())
if numprops == 1: if numprops == 1:
options.append(('{adjectives} {type.name} of {nouns}', 1.0)) options.append(('{adjectives} {base.name} of {nouns}', 0.1))
elif len(attrs['properties'].items()) > 1: elif len(attrs['properties'].items()) > 1:
options.append(('{adjectives} {type.name} of {nouns}', 1.0)) options.append(('{adjectives} {base.name} of {nouns}', 1.0))
options.append(('{type.name} of {adjectives} {nouns}', 1.0)) options.append(('{base.name} of {adjectives} {nouns}', 1.0))
return WeightedSet(*options).random() return WeightedSet(*options).random()
def random_attributes(self) -> dict: def random_attributes(self) -> dict:
@ -288,10 +333,16 @@ class MagicWeaponGenerator(WeaponGenerator):
# currently selectedon the rarity data source, which in turn will be # currently selectedon the rarity data source, which in turn will be
# set by self.random(), controllable by the caller. # set by self.random(), controllable by the caller.
attrs = dict( attrs = dict(
type=self.types.random(), base=self.bases.random(),
rarity=self.rarity.random(), rarity=self.rarity.random(),
properties=dict(), properties=dict(),
) )
attrs['base']['targets'] = 1
if attrs['base']['category'] == 'Martial':
if not attrs['base']['range']:
attrs['base']['range'] = '5'
rarity = attrs['rarity']['rarity'] rarity = attrs['rarity']['rarity']
numprops = min( numprops = min(
@ -311,10 +362,10 @@ class MagicWeaponGenerator(WeaponGenerator):
for prop_name, prop in attrs['properties'].items(): for prop_name, prop in attrs['properties'].items():
attrs['adjectives'].append(prop['adjectives']) attrs['adjectives'].append(prop['adjectives'])
attrs['nouns'].append(prop['nouns']) attrs['nouns'].append(prop['nouns'])
if prop['name'] == 'element': if prop['name'] == 'enchanted':
attrs['element'] = self.element.random() attrs['enchanted'] = self.enchanted.random()
attrs['element']['adjectives'] = random.choice(attrs['element']['adjectives'].split(',')).strip() attrs['enchanted']['adjectives'] = random.choice(attrs['enchanted']['adjectives'].split(',')).strip()
attrs['element']['nouns'] = random.choice(attrs['element']['nouns'].split(',')).strip() attrs['enchanted']['nouns'] = random.choice(attrs['enchanted']['nouns'].split(',')).strip()
attrs['template'] = self.get_template(attrs) attrs['template'] = self.get_template(attrs)
attrs['adjectives'] = ' '.join(attrs['adjectives']) attrs['adjectives'] = ' '.join(attrs['adjectives'])
@ -328,10 +379,11 @@ class GeneratorSource:
cr: int cr: int
def random_values(self, count: int = 1) -> list: def random_values(self, count: int = 1) -> list:
return [ vals = sorted(
[item.name, item.rarity.rarity, item.summary] (item.rarity['sort_order'], [item.name, item.rarity['rarity'], item.summary, item.base.properties])
for item in self.generator.random(count=count, challenge_rating=self.cr) for item in self.generator.random(count=count, challenge_rating=self.cr)
] )
return [v[1] for v in vals]
class RollTable(rolltable.types.RollTable): class RollTable(rolltable.types.RollTable):
@ -340,14 +392,14 @@ class RollTable(rolltable.types.RollTable):
sources: list, sources: list,
die: int = 20, die: int = 20,
hide_rolls: bool = False, hide_rolls: bool = False,
challenge_rating: int = 0 challenge_rating: int = 0,
): ):
self._cr = challenge_rating self._cr = challenge_rating
super().__init__( super().__init__(
sources=sources, sources=sources,
frequency='default', frequency='default',
die=die, die=die,
hide_rolls=hide_rolls hide_rolls=hide_rolls,
) )
def _config(self): def _config(self):
@ -358,18 +410,7 @@ class RollTable(rolltable.types.RollTable):
self._headers = [ self._headers = [
'Name', 'Name',
'Rarity', 'Rarity',
'Description', 'Summary',
'Properties'
] ]
self._header_excludes = [] self._header_excludes = []
@property
def _values(self) -> list:
if not self._generated_values:
ds_values = [t.random_values(self.die) for t in self._data]
self._generated_values = []
for face in range(self._die):
value = []
for index, ds in enumerate(ds_values):
value += ds_values[index][face]
self._generated_values.append(value)
return self._generated_values