adding docs to dnd_item.weapons
This commit is contained in:
parent
4548623831
commit
6220e04553
|
@ -9,15 +9,25 @@ from dnd_item import types
|
||||||
|
|
||||||
|
|
||||||
def random_from_csv(csv: str) -> str:
|
def random_from_csv(csv: str) -> str:
|
||||||
|
"""
|
||||||
|
Split a comma-separated value string into a list and return a random value.
|
||||||
|
"""
|
||||||
return random.choice(csv.split(",")).strip()
|
return random.choice(csv.split(",")).strip()
|
||||||
|
|
||||||
|
|
||||||
class Weapon(types.Item):
|
class Weapon(types.Item):
|
||||||
""" """
|
"""
|
||||||
|
An Item subclass representing weapons, both magical and mundane.
|
||||||
|
|
||||||
|
Much of this subclass is devoted to generating descriptive and entertaining
|
||||||
|
weapon names. It also implements a number of handy properties for presentation.
|
||||||
|
"""
|
||||||
|
|
||||||
def _descriptors(self) -> tuple:
|
def _descriptors(self) -> tuple:
|
||||||
"""
|
"""
|
||||||
Collect the nouns and adjectives from the properties of this item.
|
Collect the 'nouns' and 'adjectives' properties from the Item's
|
||||||
|
'properties' attribute. This is used by _random_descriptors to choose a
|
||||||
|
set of random nouns and adjectives to apply to a weapon name.
|
||||||
"""
|
"""
|
||||||
nouns = dict()
|
nouns = dict()
|
||||||
adjectives = dict()
|
adjectives = dict()
|
||||||
|
@ -31,6 +41,20 @@ class Weapon(types.Item):
|
||||||
return (nouns, adjectives)
|
return (nouns, adjectives)
|
||||||
|
|
||||||
def _name_template(self, with_adjectives: bool, with_nouns: bool) -> str:
|
def _name_template(self, with_adjectives: bool, with_nouns: bool) -> str:
|
||||||
|
"""
|
||||||
|
Generate a WeightedSet of potential name template strings and pick one.
|
||||||
|
|
||||||
|
The possible templates are determined by whether we have selected
|
||||||
|
nouns, adjectives, or both to describe the item; we can't use a
|
||||||
|
template that includes {adjectives} if no adjectives are defined in the
|
||||||
|
Item's properties, for example.
|
||||||
|
|
||||||
|
The weighted distribution is tuned so that we mostly get names of the
|
||||||
|
typical form ("Venomous Shortsowrd", "Dagger of Shocks"). Occasionally
|
||||||
|
we will select templates resulting in very long names ("Frosty +3 Mace
|
||||||
|
of Mighty Striking") or repeitive descriptions ("Flaming Spear of
|
||||||
|
Flames"), but not so often as to make all generated items too silly.
|
||||||
|
"""
|
||||||
num_properties = len(self.properties)
|
num_properties = len(self.properties)
|
||||||
options = []
|
options = []
|
||||||
if with_nouns and not with_adjectives:
|
if with_nouns and not with_adjectives:
|
||||||
|
@ -51,7 +75,15 @@ class Weapon(types.Item):
|
||||||
|
|
||||||
def _random_descriptors(self):
|
def _random_descriptors(self):
|
||||||
"""
|
"""
|
||||||
Select random nouns and adjectives from the object
|
Select random nouns and adjectives from the available descriptors in
|
||||||
|
the properties attribute.
|
||||||
|
|
||||||
|
This method ensures that if a property adding fire damage to the wepaon
|
||||||
|
is chosen, 'Flames' or 'Flaming' or 'Fire' (or whatever is defined on
|
||||||
|
that enchantement) is used when naming the Item.
|
||||||
|
|
||||||
|
The randomly-selected set of nouns and/or adjectives will then govern
|
||||||
|
the naming template selected; see _random_template() above.
|
||||||
"""
|
"""
|
||||||
random_nouns = []
|
random_nouns = []
|
||||||
random_adjectives = []
|
random_adjectives = []
|
||||||
|
@ -65,8 +97,12 @@ class Weapon(types.Item):
|
||||||
|
|
||||||
seen_nouns = dict()
|
seen_nouns = dict()
|
||||||
for prop_name in set(list(nouns.keys()) + list(adjectives.keys())):
|
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 in nouns and prop_name not in adjectives:
|
||||||
if prop_name not in seen_nouns:
|
if prop_name not in seen_nouns:
|
||||||
|
# Ensure we only add one noun for each property so taht we
|
||||||
|
# don't end up with Items named like "Mace of Venomous
|
||||||
|
# Venom Poison".
|
||||||
add_word(prop_name, random_nouns, nouns)
|
add_word(prop_name, random_nouns, nouns)
|
||||||
seen_nouns[prop_name] = True
|
seen_nouns[prop_name] = True
|
||||||
|
|
||||||
|
@ -89,12 +125,25 @@ class Weapon(types.Item):
|
||||||
add_word(prop_name, random_nouns, nouns)
|
add_word(prop_name, random_nouns, nouns)
|
||||||
add_word(prop_name, random_adjectives, adjectives)
|
add_word(prop_name, random_adjectives, adjectives)
|
||||||
|
|
||||||
|
# Join multiple nouns together, so that instead of "Staff of Strikes
|
||||||
|
# Cold" we get "Staff of Strikes and Cold." Adjectives we just join
|
||||||
|
# together ("Staff of Frosty Striking")
|
||||||
random_nouns = " and ".join(random_nouns)
|
random_nouns = " and ".join(random_nouns)
|
||||||
random_adjectives = " ".join(random_adjectives)
|
random_adjectives = " ".join(random_adjectives)
|
||||||
return (random_nouns, random_adjectives)
|
return (random_nouns, random_adjectives)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
|
"""
|
||||||
|
Generate and cache a random name for the Weapon based on its properties.
|
||||||
|
|
||||||
|
This method selects a subset of random nouns and adjectives from the
|
||||||
|
Weapon's properties, a random name string template compatible with
|
||||||
|
those descriptors, and returns a formatted title string. The return
|
||||||
|
value is cached because there are multiple possisble names for a Weapon
|
||||||
|
with the same set of properties, and we don't want multiple references
|
||||||
|
to self.name to return different values on the same instance.
|
||||||
|
"""
|
||||||
base_name = super().name
|
base_name = super().name
|
||||||
(nouns, adjectives) = self._random_descriptors()
|
(nouns, adjectives) = self._random_descriptors()
|
||||||
if not (nouns or adjectives):
|
if not (nouns or adjectives):
|
||||||
|
@ -108,6 +157,10 @@ class Weapon(types.Item):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def to_hit(self):
|
def to_hit(self):
|
||||||
|
"""
|
||||||
|
Return a string summarizing the total bonus to hit from this weapon and its properties. This
|
||||||
|
could be either a single number (+2), a die roll (1d6), or a combination of both (1d6+2).
|
||||||
|
"""
|
||||||
bonus_val = 0
|
bonus_val = 0
|
||||||
bonus_dice = ""
|
bonus_dice = ""
|
||||||
if not hasattr(self, "properties"):
|
if not hasattr(self, "properties"):
|
||||||
|
@ -124,6 +177,15 @@ class Weapon(types.Item):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def damage_dice(self):
|
def damage_dice(self):
|
||||||
|
"""
|
||||||
|
Return a string summarizing the damage done by a hit with this weapon
|
||||||
|
by combining all the damage types and dice from the Weapon's
|
||||||
|
properties. Examples:
|
||||||
|
- 5 Bludgeoning
|
||||||
|
- 1d6 Piercing
|
||||||
|
- 1d8+1 Thunder
|
||||||
|
- 1d6+1 Slashing + 1d4 Thunder + 3 Poison
|
||||||
|
"""
|
||||||
if not hasattr(self, "properties"):
|
if not hasattr(self, "properties"):
|
||||||
return ""
|
return ""
|
||||||
dmg = {self.damage_type: str(self.damage) or ""}
|
dmg = {self.damage_type: str(self.damage) or ""}
|
||||||
|
@ -143,12 +205,17 @@ class Weapon(types.Item):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self):
|
def summary(self):
|
||||||
|
"""
|
||||||
|
Return a one-line summary of the Weapon's attack. For example:
|
||||||
|
|
||||||
|
+2 to hit, 5 ft., 1 tgts. 1d6+2 Piercing + 1d6 Fire
|
||||||
|
"""
|
||||||
return f"{self.to_hit} to hit, {self.range} ft., {self.targets} tgts. {self.damage_dice}"
|
return f"{self.to_hit} to hit, {self.range} ft., {self.targets} tgts. {self.damage_dice}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def details(self):
|
def details(self):
|
||||||
"""
|
"""
|
||||||
Format the item properties as nested bullet lists.
|
Return details of the Weapon as a multi-line string.
|
||||||
"""
|
"""
|
||||||
props = ", ".join(self.get("properties", dict()).keys())
|
props = ", ".join(self.get("properties", dict()).keys())
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
|
@ -160,8 +227,18 @@ class Weapon(types.Item):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def id(self):
|
def id(self):
|
||||||
|
"""
|
||||||
|
Generate a unique ID for this weapon. Unlike self.name, which may have
|
||||||
|
any of several possisble generated values based on the Weapon's
|
||||||
|
properties, this property will generate the same ID every for every
|
||||||
|
instance with the same properties.
|
||||||
|
|
||||||
|
This is useful for asserting equality between instances, which may
|
||||||
|
be helpful when (for example) generating weapon look-up tables,
|
||||||
|
web pages, item cards, and so on.
|
||||||
|
"""
|
||||||
sha1bytes = hashlib.sha1(
|
sha1bytes = hashlib.sha1(
|
||||||
"".join(
|
"".join(
|
||||||
[
|
[
|
||||||
|
@ -171,10 +248,19 @@ class Weapon(types.Item):
|
||||||
]
|
]
|
||||||
).encode()
|
).encode()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Only use the first ten characteres of the encoded value. This
|
||||||
|
# increases the likelihood of hash collisions, but 10 characters is far
|
||||||
|
# more than is necessary to encode all possible weapons generated by
|
||||||
|
# this package, and 10 is friendlier for user-facing use casees, such
|
||||||
|
# as stub URLs.
|
||||||
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
||||||
|
|
||||||
|
|
||||||
class WeaponGenerator(types.ItemGenerator):
|
class WeaponGenerator(types.ItemGenerator):
|
||||||
|
"""
|
||||||
|
A subclass of ItemGenerator that generates Weapon instances.
|
||||||
|
"""
|
||||||
item_class = Weapon
|
item_class = Weapon
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -186,7 +272,8 @@ class WeaponGenerator(types.ItemGenerator):
|
||||||
super().__init__(bases=bases, rarity=rarity, properties_by_rarity=properties_by_rarity)
|
super().__init__(bases=bases, rarity=rarity, properties_by_rarity=properties_by_rarity)
|
||||||
|
|
||||||
def random_properties(self) -> dict:
|
def random_properties(self) -> dict:
|
||||||
# add missing base weapon defaults (TODO: update the sources)
|
# add missing base weapon defaults
|
||||||
|
# TODO: update the sources then delete this method
|
||||||
item = super().random_properties()
|
item = super().random_properties()
|
||||||
item["targets"] = 1
|
item["targets"] = 1
|
||||||
if item["category"] == "Martial":
|
if item["category"] == "Martial":
|
||||||
|
@ -194,9 +281,12 @@ class WeaponGenerator(types.ItemGenerator):
|
||||||
item["range"] = ""
|
item["range"] = ""
|
||||||
return item
|
return item
|
||||||
|
|
||||||
# handlers for extra properties
|
|
||||||
|
|
||||||
def get_enchantment(self, **attrs) -> dict:
|
def get_enchantment(self, **attrs) -> dict:
|
||||||
|
"""
|
||||||
|
PROPERTIES_BY_RARITY includes references to enchamentments, so make
|
||||||
|
sure we know how to generate a random enchantment when it is referenced
|
||||||
|
by a template string.
|
||||||
|
"""
|
||||||
prop = types.ENCHANTMENT.random()
|
prop = types.ENCHANTMENT.random()
|
||||||
prop["adjectives"] = random_from_csv(prop["adjectives"])
|
prop["adjectives"] = random_from_csv(prop["adjectives"])
|
||||||
prop["nouns"] = random_from_csv(prop["nouns"])
|
prop["nouns"] = random_from_csv(prop["nouns"])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user