automatic formatting
This commit is contained in:
parent
51ecf83357
commit
5b4c26ecf2
|
@ -1,33 +1,31 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
from rich import print
|
||||
from rich.logging import RichHandler
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from rich.table import Table
|
||||
|
||||
from dnd_item import five_e
|
||||
from dnd_item.types import RollTable
|
||||
from dnd_item.weapons import WeaponGenerator
|
||||
from dnd_item import five_e
|
||||
|
||||
app = typer.Typer()
|
||||
app_state = {}
|
||||
|
||||
|
||||
class OUTPUT_FORMATS(Enum):
|
||||
text = 'text'
|
||||
yaml = 'yaml'
|
||||
markdown = 'markdown'
|
||||
text = "text"
|
||||
yaml = "yaml"
|
||||
markdown = "markdown"
|
||||
|
||||
|
||||
@app.callback()
|
||||
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."),
|
||||
):
|
||||
debug = os.getenv("FANITEM_DEBUG", None)
|
||||
logging.basicConfig(
|
||||
|
@ -35,38 +33,32 @@ def main(
|
|||
level=logging.DEBUG if debug else logging.INFO,
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[typer])],
|
||||
)
|
||||
logging.getLogger('markdown_it').setLevel(logging.ERROR)
|
||||
logging.getLogger("markdown_it").setLevel(logging.ERROR)
|
||||
|
||||
app_state['cr'] = cr or 0
|
||||
app_state['data'] = Path(__file__).parent / Path("sources")
|
||||
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=count, challenge_rating=app_state['cr']):
|
||||
for weapon in WeaponGenerator().random(count=count, challenge_rating=app_state["cr"]):
|
||||
console.print(weapon.details)
|
||||
|
||||
|
||||
@app.command("roll-table")
|
||||
def table(
|
||||
die: int = typer.Option(
|
||||
20,
|
||||
help='The size of the die for which to create a table'),
|
||||
die: int = typer.Option(20, help="The size of the die for which to create a table"),
|
||||
hide_rolls: bool = typer.Option(
|
||||
False,
|
||||
help='If True, do not show the Roll column.',
|
||||
help="If True, do not show the Roll column.",
|
||||
),
|
||||
collapsed: bool = typer.Option(
|
||||
True,
|
||||
help='If True, collapse multiple die values with the same option.'),
|
||||
width: int = typer.Option(
|
||||
180,
|
||||
help='Width of the table.'),
|
||||
collapsed: bool = typer.Option(True, help="If True, collapse multiple die values with the same option."),
|
||||
width: int = typer.Option(180, help="Width of the table."),
|
||||
output: OUTPUT_FORMATS = typer.Option(
|
||||
'text',
|
||||
help='The output format to use.',
|
||||
)
|
||||
"text",
|
||||
help="The output format to use.",
|
||||
),
|
||||
):
|
||||
"""
|
||||
CLI for creating roll tables of randomly-generated items.
|
||||
|
@ -75,7 +67,7 @@ def table(
|
|||
sources=[WeaponGenerator],
|
||||
die=die,
|
||||
hide_rolls=hide_rolls,
|
||||
challenge_rating=app_state['cr'],
|
||||
challenge_rating=app_state["cr"],
|
||||
)
|
||||
|
||||
if output == OUTPUT_FORMATS.yaml:
|
||||
|
|
|
@ -1,46 +1,31 @@
|
|||
import json
|
||||
import yaml
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from random_sets.datasources import DataSource
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from random_sets.datasources import DataSource
|
||||
|
||||
sources = Path(__file__).parent / Path("sources")
|
||||
|
||||
RARITY = {
|
||||
'unknown': 'common',
|
||||
'none': 'common',
|
||||
'': ''
|
||||
}
|
||||
RARITY = {"unknown": "common", "none": "common", "": ""}
|
||||
|
||||
TYPE = {
|
||||
'M': 'martial',
|
||||
'R': 'ranged',
|
||||
'': ''
|
||||
}
|
||||
TYPE = {"M": "martial", "R": "ranged", "": ""}
|
||||
|
||||
DAMAGE = {
|
||||
'S': 'Slashing',
|
||||
'P': 'Piercing',
|
||||
'B': 'Bludgeoning',
|
||||
'': ''
|
||||
}
|
||||
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',
|
||||
"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",
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,42 +34,56 @@ 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']
|
||||
src = json.load(self.source)["baseitem"]
|
||||
self.data = defaultdict(list)
|
||||
headers = ['Rarity', 'Name', 'Category', 'Type', 'Weight', 'Damage Type',
|
||||
'Damage Dice', 'Range', 'Reload', 'Value', 'Properties']
|
||||
headers = [
|
||||
"Rarity",
|
||||
"Name",
|
||||
"Category",
|
||||
"Type",
|
||||
"Weight",
|
||||
"Damage Type",
|
||||
"Damage Dice",
|
||||
"Range",
|
||||
"Reload",
|
||||
"Value",
|
||||
"Properties",
|
||||
]
|
||||
|
||||
for item in src:
|
||||
if not item.get('weapon', False):
|
||||
if not item.get("weapon", False):
|
||||
continue
|
||||
if item.get('age', False):
|
||||
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', [])])
|
||||
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}
|
||||
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))
|
||||
return yaml.dump({"metadata": self.metadata}) + yaml.dump(dict(self.data))
|
||||
|
||||
|
||||
def weapons(source_path: str = 'items-base.json') -> dict:
|
||||
def weapons(source_path: str = "items-base.json") -> dict:
|
||||
with open(sources / Path(source_path)) as filehandle:
|
||||
ds = Weapons(source=filehandle)
|
||||
return ds
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
import re
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from random_sets.sets import WeightedSet, DataSourceSet
|
||||
from pathlib import Path
|
||||
|
||||
import rolltable.types
|
||||
|
||||
from random_sets.sets import DataSourceSet, WeightedSet
|
||||
|
||||
# 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")
|
||||
ENCHANTMENT = DataSourceSet(sources / Path('magic_damage_types.yaml'))
|
||||
WEAPON_TYPES = DataSourceSet(sources / Path('weapons.yaml'))
|
||||
RARITY = DataSourceSet(sources / Path('rarity.yaml'))
|
||||
ENCHANTMENT = DataSourceSet(sources / Path("magic_damage_types.yaml"))
|
||||
WEAPON_TYPES = DataSourceSet(sources / Path("weapons.yaml"))
|
||||
RARITY = DataSourceSet(sources / Path("rarity.yaml"))
|
||||
PROPERTIES_BY_RARITY = {
|
||||
'base': DataSourceSet(sources / Path('properties_base.yaml')),
|
||||
'common': DataSourceSet(sources / Path('properties_common.yaml')),
|
||||
'uncommon': DataSourceSet(sources / Path('properties_uncommon.yaml')),
|
||||
'rare': DataSourceSet(sources / Path('properties_rare.yaml')),
|
||||
'very rare': DataSourceSet(sources / Path('properties_very_rare.yaml')),
|
||||
'legendary': DataSourceSet(sources / Path('properties_legendary.yaml')),
|
||||
"base": DataSourceSet(sources / Path("properties_base.yaml")),
|
||||
"common": DataSourceSet(sources / Path("properties_common.yaml")),
|
||||
"uncommon": DataSourceSet(sources / Path("properties_uncommon.yaml")),
|
||||
"rare": DataSourceSet(sources / Path("properties_rare.yaml")),
|
||||
"very rare": DataSourceSet(sources / Path("properties_very_rare.yaml")),
|
||||
"legendary": DataSourceSet(sources / Path("properties_legendary.yaml")),
|
||||
}
|
||||
|
||||
|
||||
|
@ -57,14 +54,14 @@ class AttributeDict(Mapping):
|
|||
"""
|
||||
attrs = {}
|
||||
for k, v in sorted(kwargs.items()):
|
||||
attrs[k] = AttributeDict.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(attributes=attrs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item(AttributeDict):
|
||||
"""
|
||||
"""
|
||||
""" """
|
||||
|
||||
_name: str = None
|
||||
|
||||
@property
|
||||
|
@ -76,7 +73,7 @@ class Item(AttributeDict):
|
|||
|
||||
@property
|
||||
def description(self):
|
||||
desc = "\n".join([k.title() + ". " + v.description for k, v in self.get('properties', {}).items()])
|
||||
desc = "\n".join([k.title() + ". " + v.description for k, v in self.get("properties", {}).items()])
|
||||
return desc.format(**self)
|
||||
|
||||
@classmethod
|
||||
|
@ -88,13 +85,12 @@ class Item(AttributeDict):
|
|||
|
||||
# delay processing the 'properties' attribute until after the other
|
||||
# attributes, because they may contain references to those attributes.
|
||||
properties = attrs.pop('properties', None)
|
||||
properties = attrs.pop("properties", None)
|
||||
|
||||
attributes = dict()
|
||||
|
||||
# recursively locate and populate template strings
|
||||
def _format(obj, this=None):
|
||||
|
||||
# enables use of the 'this' keyword to refer to the current context
|
||||
# in a template. Refer to the enchantment sources for an example.
|
||||
if this:
|
||||
|
@ -102,9 +98,7 @@ class Item(AttributeDict):
|
|||
|
||||
# dicts and lists are descended into
|
||||
if type(obj) is dict:
|
||||
return AttributeDict.from_dict(dict(
|
||||
(key, _format(val, this=obj)) for key, val in obj.items()
|
||||
))
|
||||
return AttributeDict.from_dict(dict((key, _format(val, this=obj)) for key, val in obj.items()))
|
||||
if type(obj) is list:
|
||||
return [_format(o, this=this) for o in obj]
|
||||
|
||||
|
@ -124,32 +118,29 @@ class Item(AttributeDict):
|
|||
else:
|
||||
attributes[k] = _format(v)
|
||||
if properties:
|
||||
attributes['properties'] = AttributeDict.from_dict(_format(properties))
|
||||
for prop in attributes['properties'].values():
|
||||
overrides = [k for k in prop.attributes.keys() if k.startswith('override_')]
|
||||
attributes["properties"] = AttributeDict.from_dict(_format(properties))
|
||||
for prop in attributes["properties"].values():
|
||||
overrides = [k for k in prop.attributes.keys() if k.startswith("override_")]
|
||||
for o in overrides:
|
||||
if prop.attributes[o]:
|
||||
attributes[o.replace('override_', '')] = prop.attributes[o]
|
||||
attributes[o.replace("override_", "")] = prop.attributes[o]
|
||||
|
||||
# store the item name as the _name attribute; it is accessable directly, or
|
||||
# via the name property. This makes overriding the name convenient for subclassers,
|
||||
# which may require naming semantics that cannot be resolved at instantiation time.
|
||||
_name = attributes['name']
|
||||
del attributes['name']
|
||||
_name = attributes["name"]
|
||||
del attributes["name"]
|
||||
|
||||
# At this point, attributes is a dictionary with members of multiple
|
||||
# types, but every dict member has been converted to an AttributeDict,
|
||||
# and all template strings in the object have been formatted. Return an
|
||||
# instance of the Item class using these formatted attributes.
|
||||
return cls(
|
||||
_name=_name,
|
||||
attributes=attributes
|
||||
)
|
||||
return cls(_name=_name, attributes=attributes)
|
||||
|
||||
|
||||
class ItemGenerator:
|
||||
"""
|
||||
"""
|
||||
""" """
|
||||
|
||||
item_class = Item
|
||||
|
||||
def __init__(self, bases: WeightedSet, rarity: WeightedSet, properties_by_rarity: dict):
|
||||
|
@ -159,19 +150,16 @@ class ItemGenerator:
|
|||
|
||||
def _property_count_by_rarity(self, rarity: str) -> int:
|
||||
property_count_by_rarity = {
|
||||
'common': WeightedSet((1, 0.1), (0, 1.0)),
|
||||
'uncommon': WeightedSet((1, 1.0)),
|
||||
'rare': WeightedSet((1, 1.0), (2, 0.5)),
|
||||
'very rare': WeightedSet((1, 0.5), (2, 1.0)),
|
||||
'legendary': WeightedSet((2, 1.0), (3, 1.0)),
|
||||
"common": WeightedSet((1, 0.1), (0, 1.0)),
|
||||
"uncommon": WeightedSet((1, 1.0)),
|
||||
"rare": WeightedSet((1, 1.0), (2, 0.5)),
|
||||
"very rare": WeightedSet((1, 0.5), (2, 1.0)),
|
||||
"legendary": WeightedSet((2, 1.0), (3, 1.0)),
|
||||
}
|
||||
return min(
|
||||
property_count_by_rarity[rarity].random(),
|
||||
len(self.properties_by_rarity[rarity].members)
|
||||
)
|
||||
return min(property_count_by_rarity[rarity].random(), len(self.properties_by_rarity[rarity].members))
|
||||
|
||||
def get_requirements(self, item) -> set:
|
||||
pat = re.compile(r'{([^\.\}]+)')
|
||||
pat = re.compile(r"{([^\.\}]+)")
|
||||
|
||||
def getreqs(obj):
|
||||
if type(obj) is dict:
|
||||
|
@ -188,28 +176,28 @@ class ItemGenerator:
|
|||
|
||||
def random_properties(self) -> dict:
|
||||
item = self.bases.random()
|
||||
item['rarity'] = self.rarity.random()
|
||||
item["rarity"] = self.rarity.random()
|
||||
|
||||
properties = {}
|
||||
num_properties = self._property_count_by_rarity(item['rarity']['rarity'])
|
||||
num_properties = self._property_count_by_rarity(item["rarity"]["rarity"])
|
||||
while len(properties) != num_properties:
|
||||
thisprop = self.properties_by_rarity[item['rarity']['rarity']].random()
|
||||
properties[thisprop['name']] = thisprop
|
||||
thisprop = self.properties_by_rarity[item["rarity"]["rarity"]].random()
|
||||
properties[thisprop["name"]] = thisprop
|
||||
|
||||
# add properties from the base item (versatile, thrown, artifact..)
|
||||
for name in item.pop('properties', '').split(','):
|
||||
for name in item.pop("properties", "").split(","):
|
||||
name = name.strip()
|
||||
if name:
|
||||
properties[name] = self.properties_by_rarity['base'].source.as_dict()[name]
|
||||
properties[name] = self.properties_by_rarity["base"].source.as_dict()[name]
|
||||
|
||||
item['properties'] = properties
|
||||
item["properties"] = properties
|
||||
|
||||
# look for template strings that reference item attributes which do not yet exist.
|
||||
# Add anything that is missing via a callback.
|
||||
predefined = list(item.keys()) + ['this', '_name']
|
||||
predefined = list(item.keys()) + ["this", "_name"]
|
||||
for requirement in [r for r in self.get_requirements(item) if r not in predefined]:
|
||||
try:
|
||||
item[requirement] = getattr(self, f'get_{requirement}')(**item)
|
||||
item[requirement] = getattr(self, f"get_{requirement}")(**item)
|
||||
except AttributeError:
|
||||
logging.error("{item['name']} requires {self.__class__.__name__} to have a get_{requirement}() method.")
|
||||
raise
|
||||
|
@ -225,15 +213,15 @@ class ItemGenerator:
|
|||
# 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'
|
||||
frequency = "1-4"
|
||||
elif challenge_rating in range(5, 11):
|
||||
frequency = '5-10'
|
||||
frequency = "5-10"
|
||||
elif challenge_rating in range(11, 17):
|
||||
frequency = '11-16'
|
||||
frequency = "11-16"
|
||||
elif challenge_rating >= 17:
|
||||
frequency = '17'
|
||||
frequency = "17"
|
||||
else:
|
||||
frequency = 'default'
|
||||
frequency = "default"
|
||||
self.rarity.set_frequency(frequency)
|
||||
|
||||
items = []
|
||||
|
@ -250,14 +238,8 @@ class GeneratorSource:
|
|||
def random_values(self, count: int = 1) -> list:
|
||||
vals = sorted(
|
||||
(
|
||||
item.rarity['sort_order'],
|
||||
[
|
||||
item.name,
|
||||
item.rarity['rarity'],
|
||||
item.summary,
|
||||
', '.join(item.get('properties', [])),
|
||||
item.id
|
||||
]
|
||||
item.rarity["sort_order"],
|
||||
[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)
|
||||
)
|
||||
|
@ -275,7 +257,7 @@ class RollTable(rolltable.types.RollTable):
|
|||
self._cr = challenge_rating
|
||||
super().__init__(
|
||||
sources=sources,
|
||||
frequency='default',
|
||||
frequency="default",
|
||||
die=die,
|
||||
hide_rolls=hide_rolls,
|
||||
)
|
||||
|
@ -286,10 +268,10 @@ class RollTable(rolltable.types.RollTable):
|
|||
self._data.append(GeneratorSource(generator=src(), cr=self._cr))
|
||||
|
||||
self._headers = [
|
||||
'Name',
|
||||
'Rarity',
|
||||
'Summary',
|
||||
'Properties',
|
||||
'ID',
|
||||
"Name",
|
||||
"Rarity",
|
||||
"Summary",
|
||||
"Properties",
|
||||
"ID",
|
||||
]
|
||||
self._header_excludes = []
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import random
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
import random
|
||||
from functools import cached_property
|
||||
|
||||
from dnd_item import types
|
||||
from random_sets.sets import WeightedSet, equal_weights
|
||||
|
||||
from dnd_item import types
|
||||
|
||||
|
||||
def random_from_csv(csv: str) -> str:
|
||||
return random.choice(csv.split(',')).strip()
|
||||
return random.choice(csv.split(",")).strip()
|
||||
|
||||
|
||||
class Weapon(types.Item):
|
||||
"""
|
||||
"""
|
||||
""" """
|
||||
|
||||
def _descriptors(self) -> tuple:
|
||||
"""
|
||||
|
@ -22,30 +21,32 @@ class Weapon(types.Item):
|
|||
"""
|
||||
nouns = dict()
|
||||
adjectives = dict()
|
||||
if not hasattr(self, 'properties'):
|
||||
if not hasattr(self, "properties"):
|
||||
return (nouns, adjectives)
|
||||
for prop_name, prop in self.properties.items():
|
||||
if hasattr(prop, 'nouns'):
|
||||
nouns[prop_name] = equal_weights(prop.nouns.split(','), blank=False)
|
||||
if hasattr(prop, 'adjectives'):
|
||||
adjectives[prop_name] = equal_weights(prop.adjectives.split(','), blank=False)
|
||||
if hasattr(prop, "nouns"):
|
||||
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)
|
||||
|
||||
def _name_template(self, with_adjectives: bool, with_nouns: bool) -> str:
|
||||
num_properties = len(self.properties)
|
||||
options = []
|
||||
if with_nouns and not with_adjectives:
|
||||
options.append(('{name} of {nouns}', 0.5))
|
||||
options.append(("{name} of {nouns}", 0.5))
|
||||
if with_adjectives and not with_nouns:
|
||||
options.append(('{adjectives} {name}', 0.5))
|
||||
options.append(("{adjectives} {name}", 0.5))
|
||||
if with_nouns and with_adjectives:
|
||||
if num_properties == 1:
|
||||
options.append(('{adjectives} {name} of {nouns}', 1.0))
|
||||
options.append(("{adjectives} {name} of {nouns}", 1.0))
|
||||
elif num_properties > 1:
|
||||
options.extend([
|
||||
('{adjectives} {name} of {nouns}', 1.0),
|
||||
('{name} of {adjectives} {nouns}', 0.5),
|
||||
])
|
||||
options.extend(
|
||||
[
|
||||
("{adjectives} {name} of {nouns}", 1.0),
|
||||
("{name} of {adjectives} {nouns}", 0.5),
|
||||
]
|
||||
)
|
||||
return WeightedSet(*options).random()
|
||||
|
||||
def _random_descriptors(self):
|
||||
|
@ -64,7 +65,6 @@ class Weapon(types.Item):
|
|||
|
||||
seen_nouns = dict()
|
||||
for prop_name in set(list(nouns.keys()) + list(adjectives.keys())):
|
||||
|
||||
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)
|
||||
|
@ -89,8 +89,8 @@ class Weapon(types.Item):
|
|||
add_word(prop_name, random_nouns, nouns)
|
||||
add_word(prop_name, random_adjectives, adjectives)
|
||||
|
||||
random_nouns = ' and '.join(random_nouns)
|
||||
random_adjectives = ' '.join(random_adjectives)
|
||||
random_nouns = " and ".join(random_nouns)
|
||||
random_adjectives = " ".join(random_adjectives)
|
||||
return (random_nouns, random_adjectives)
|
||||
|
||||
@cached_property
|
||||
|
@ -109,11 +109,11 @@ class Weapon(types.Item):
|
|||
@property
|
||||
def to_hit(self):
|
||||
bonus_val = 0
|
||||
bonus_dice = ''
|
||||
if not hasattr(self, 'properties'):
|
||||
return ''
|
||||
bonus_dice = ""
|
||||
if not hasattr(self, "properties"):
|
||||
return ""
|
||||
for prop in self.properties.values():
|
||||
mod = getattr(prop, 'to_hit', None)
|
||||
mod = getattr(prop, "to_hit", None)
|
||||
if not mod:
|
||||
continue
|
||||
if type(mod) is int:
|
||||
|
@ -124,24 +124,22 @@ class Weapon(types.Item):
|
|||
|
||||
@property
|
||||
def damage_dice(self):
|
||||
if not hasattr(self, 'properties'):
|
||||
return ''
|
||||
dmg = {
|
||||
self.damage_type: str(self.damage) or ''
|
||||
}
|
||||
if not hasattr(self, "properties"):
|
||||
return ""
|
||||
dmg = {self.damage_type: str(self.damage) or ""}
|
||||
|
||||
for prop in self.properties.values():
|
||||
mod = getattr(prop, 'damage', None)
|
||||
mod = getattr(prop, "damage", None)
|
||||
if not mod:
|
||||
continue
|
||||
key = str(prop.damage_type)
|
||||
this_damage = dmg.get(key, '')
|
||||
this_damage = dmg.get(key, "")
|
||||
if this_damage:
|
||||
dmg[key] = f"{this_damage}+{mod}"
|
||||
else:
|
||||
dmg[key] = mod
|
||||
|
||||
return ' + '.join([f"{v} {k}" for k, v in dmg.items()])
|
||||
return " + ".join([f"{v} {k}" for k, v in dmg.items()])
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
|
@ -152,20 +150,28 @@ class Weapon(types.Item):
|
|||
"""
|
||||
Format the item properties as nested bullet lists.
|
||||
"""
|
||||
props = ', '.join(self.get('properties', dict()).keys())
|
||||
return "\n".join([
|
||||
f"{self.name}",
|
||||
f" * {self.rarity.rarity} {self.category} weapon ({props})",
|
||||
f" * {self.summary}",
|
||||
f"\n{self.description}\n"
|
||||
])
|
||||
props = ", ".join(self.get("properties", dict()).keys())
|
||||
return "\n".join(
|
||||
[
|
||||
f"{self.name}",
|
||||
f" * {self.rarity.rarity} {self.category} weapon ({props})",
|
||||
f" * {self.summary}",
|
||||
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]
|
||||
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):
|
||||
|
@ -182,16 +188,16 @@ class WeaponGenerator(types.ItemGenerator):
|
|||
def random_properties(self) -> dict:
|
||||
# add missing base weapon defaults (TODO: update the sources)
|
||||
item = super().random_properties()
|
||||
item['targets'] = 1
|
||||
if item['category'] == 'Martial':
|
||||
if not item['range']:
|
||||
item['range'] = ''
|
||||
item["targets"] = 1
|
||||
if item["category"] == "Martial":
|
||||
if not item["range"]:
|
||||
item["range"] = ""
|
||||
return item
|
||||
|
||||
# handlers for extra properties
|
||||
|
||||
def get_enchantment(self, **attrs) -> dict:
|
||||
prop = types.ENCHANTMENT.random()
|
||||
prop['adjectives'] = random_from_csv(prop['adjectives'])
|
||||
prop['nouns'] = random_from_csv(prop['nouns'])
|
||||
prop["adjectives"] = random_from_csv(prop["adjectives"])
|
||||
prop["nouns"] = random_from_csv(prop["nouns"])
|
||||
return prop
|
||||
|
|
Loading…
Reference in New Issue
Block a user