mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
Included companions and weight management
This commit is contained in:
@@ -25,6 +25,7 @@ from dungeonsheets.content_registry import find_content
|
|||||||
from dungeonsheets.weapons import Weapon
|
from dungeonsheets.weapons import Weapon
|
||||||
from dungeonsheets.content import Creature
|
from dungeonsheets.content import Creature
|
||||||
from dungeonsheets.dice import combine_dice
|
from dungeonsheets.dice import combine_dice
|
||||||
|
from dungeonsheets.equipment_reader import equipment_weight_parser
|
||||||
|
|
||||||
|
|
||||||
dice_re = re.compile(r"(\d+)d(\d+)")
|
dice_re = re.compile(r"(\d+)d(\d+)")
|
||||||
@@ -86,6 +87,10 @@ class Character(Creature):
|
|||||||
attacks_and_spellcasting = ""
|
attacks_and_spellcasting = ""
|
||||||
class_list = list()
|
class_list = list()
|
||||||
_background = None
|
_background = None
|
||||||
|
_companions = []
|
||||||
|
_carrying_capacity = 0
|
||||||
|
_carrying_weight = 0
|
||||||
|
equipment_weight_dict = {}
|
||||||
|
|
||||||
# Characteristics
|
# Characteristics
|
||||||
personality_traits = (
|
personality_traits = (
|
||||||
@@ -748,16 +753,37 @@ class Character(Creature):
|
|||||||
featS += info_list
|
featS += info_list
|
||||||
return "\n\n".join(featS)
|
return "\n\n".join(featS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def carrying_capacity(self):
|
||||||
|
_ccModD = {"tiny":0.5, "small":1, "medium":1,
|
||||||
|
"large":2, "huge":4, "gargantum":8}
|
||||||
|
cc_mod = _ccModD[self.race.size.lower()]
|
||||||
|
return 15*self.strength.value*cc_mod
|
||||||
|
|
||||||
|
@property
|
||||||
|
def carrying_weight(self):
|
||||||
|
weight = equipment_weight_parser(self.equipment,
|
||||||
|
self.equipment_weight_dict)
|
||||||
|
weight += sum([w.weight for w in self.weapons])
|
||||||
|
weight += self.armor.weight
|
||||||
|
if self.shield:
|
||||||
|
weight += 6
|
||||||
|
weight += sum([self.cp, self.sp, self.ep, self.gp, self.pp])/50
|
||||||
|
return round(weight, 2)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def equipment_text(self):
|
def equipment_text(self):
|
||||||
eq_list = []
|
eq_list = []
|
||||||
if hasattr(self, "magic_items"):
|
if hasattr(self, "magic_items") and len(self.magic_items) > 0:
|
||||||
eq_list += ["**Magic Items**"]
|
eq_list += ["**Magic Items**"]
|
||||||
eq_list += [item.name for item in self.magic_items]
|
eq_list += [item.name for item in self.magic_items]
|
||||||
if hasattr(self, "equipment"):
|
if hasattr(self, "equipment") and len(self.equipment.strip()) > 0:
|
||||||
eq_list += ["**Other Equipment**"]
|
eq_list += ["**Other Equipment**"]
|
||||||
eq_list += [text.strip() for text in self.equipment.split("\n")
|
eq_list += [text.strip() for text in self.equipment.split("\n")
|
||||||
if not(text.isspace())]
|
if not(text.isspace())]
|
||||||
|
cw, cc = self.carrying_weight, self.carrying_capacity
|
||||||
|
eq_list += [f"**Weight:** {cw} lb\n\n**Capacity:** {cc} lb"]
|
||||||
|
|
||||||
return "\n\n".join(eq_list)
|
return "\n\n".join(eq_list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -961,6 +987,42 @@ class Character(Creature):
|
|||||||
if hasattr(self, "Druid"):
|
if hasattr(self, "Druid"):
|
||||||
self.Druid.wild_shapes = new_shapes
|
self.Druid.wild_shapes = new_shapes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ranger_beast(self):
|
||||||
|
if hasattr(self, "Ranger"):
|
||||||
|
return self.Ranger.ranger_beast
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@ranger_beast.setter
|
||||||
|
def ranger_beast(self, beast):
|
||||||
|
msg = (
|
||||||
|
f"Companion '{beast}' not found. Please add it to"
|
||||||
|
" ``monsters.py``" )
|
||||||
|
beast = self._resolve_mechanic(beast, monsters.Monster, msg)
|
||||||
|
self.Ranger.ranger_beast = (beast, self.proficiency_bonus)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def companions(self):
|
||||||
|
"""Return the list of companions and summonables"""
|
||||||
|
companions = [compa for compa in self._companions]
|
||||||
|
if self.ranger_beast:
|
||||||
|
companions.append(self.ranger_beast)
|
||||||
|
return companions
|
||||||
|
|
||||||
|
@companions.setter
|
||||||
|
def companions(self, compas):
|
||||||
|
companions_list = []
|
||||||
|
# Retrieve the actual monster classes if possible
|
||||||
|
for compa in compas:
|
||||||
|
msg = (
|
||||||
|
f"Companion '{compa}' not found. Please add it to"
|
||||||
|
" ``monsters.py``" )
|
||||||
|
new_compa = self._resolve_mechanic(compa, monsters.Monster, msg)
|
||||||
|
companions_list.append(new_compa)
|
||||||
|
# Save the updated list for later
|
||||||
|
self._companions = companions_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def infusions_text(self):
|
def infusions_text(self):
|
||||||
if hasattr(self, "Artificer"):
|
if hasattr(self, "Artificer"):
|
||||||
|
|||||||
+28
-7
@@ -5,27 +5,38 @@ from itertools import groupby
|
|||||||
|
|
||||||
from dungeonsheets.exceptions import DiceError
|
from dungeonsheets.exceptions import DiceError
|
||||||
|
|
||||||
dice_re = re.compile(r"(\d+)d(\d+)", flags=re.I)
|
dice_re = re.compile(r"(\d+)d(\d+)([+-]\d+)*", flags=re.I)
|
||||||
dice_part_re = re.compile(r"[0-9d]+", flags=re.I)
|
dice_part_re = re.compile(r"[0-9d]+", flags=re.I)
|
||||||
Dice = namedtuple("Dice", ("num", "faces"))
|
Dice = namedtuple("Dice", ("num", "faces", "modifier"))
|
||||||
|
|
||||||
|
|
||||||
def read_dice_str(dice_str):
|
def read_dice_str(dice_str):
|
||||||
"""Interpret a D&D dice string, eg. 3d10.
|
"""Interpret a D&D dice string, eg. 3d10+2.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
dice : tuple
|
dice : tuple
|
||||||
A named tuple with the scheme (num, faces), so '3d10' return
|
A named tuple with the scheme (num, faces), so '3d10-2' return
|
||||||
(num=3, faces=10)
|
(num=3, faces=10, modifier=-2)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
dice_str = dice_str.replace(" ", "").replace("\n", "")
|
||||||
match = dice_re.match(dice_str)
|
match = dice_re.match(dice_str)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise DiceError(f"Cannot interpret dice string {dice_str}")
|
raise DiceError(f"Cannot interpret dice string {dice_str}")
|
||||||
dice = Dice(num=int(match.group(1)), faces=int(match.group(2)))
|
num, faces = int(match.group(1)), int(match.group(2))
|
||||||
|
if match.group(3) is None:
|
||||||
|
modifier = 0
|
||||||
|
else:
|
||||||
|
modifier = int(match.group(3))
|
||||||
|
dice = Dice(num, faces, modifier)
|
||||||
return dice
|
return dice
|
||||||
|
|
||||||
|
def _dice_mean(dice, force_min=True):
|
||||||
|
"""Support function for calculating dice string mean."""
|
||||||
|
dmg_min = dice.num + dice.modifier
|
||||||
|
dmg_max = dice.num*dice.faces + dice.modifier
|
||||||
|
return (dmg_max - dmg_min)/2.0 + dmg_min
|
||||||
|
|
||||||
def combine_dice(dice_str):
|
def combine_dice(dice_str):
|
||||||
"""Condense a dice string into its simplest representation.
|
"""Condense a dice string into its simplest representation.
|
||||||
@@ -56,10 +67,20 @@ def combine_dice(dice_str):
|
|||||||
new_dice_str = " + ".join(new_parts)
|
new_dice_str = " + ".join(new_parts)
|
||||||
return new_dice_str
|
return new_dice_str
|
||||||
|
|
||||||
|
|
||||||
def roll(a, b=None):
|
def roll(a, b=None):
|
||||||
"""roll(20) means roll 1d20, roll(2, 6) means roll 2d6"""
|
"""roll(20) means roll 1d20, roll(2, 6) means roll 2d6"""
|
||||||
if b is None:
|
if b is None:
|
||||||
return random.randint(1, a)
|
return random.randint(1, a)
|
||||||
else:
|
else:
|
||||||
return sum([random.randint(1, b) for _ in range(a)])
|
return sum([random.randint(1, b) for _ in range(a)])
|
||||||
|
|
||||||
|
def dice_roll_mean(dice_text):
|
||||||
|
"""Takes a dice string like '3d6 +3' and returns its average roll."""
|
||||||
|
dice = read_dice_str(dice_text)
|
||||||
|
return round(_dice_mean(dice))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ds = "10d12+10"
|
||||||
|
v = read_dice_str(ds)
|
||||||
|
print(v)
|
||||||
|
print(_dice_mean(v))
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Tue Feb 15 22:41:46 2022
|
||||||
|
|
||||||
|
@author: mauricio
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
import re
|
||||||
|
from dungeonsheets.weapons import simple_weapons, martial_weapons, firearms
|
||||||
|
from dungeonsheets.armor import all_armors
|
||||||
|
|
||||||
|
all_weapons = simple_weapons + martial_weapons + firearms
|
||||||
|
item_reader = re.compile(r"(\d*)(\s*)(.+)")
|
||||||
|
gear_weight = {"abacus":2,
|
||||||
|
"vial of acid":1,
|
||||||
|
"flask of alchemist's fire":1,
|
||||||
|
"arrows":1/20,
|
||||||
|
"arrow":1/20,
|
||||||
|
"bowgun needles": 1/50,
|
||||||
|
"crosbow bolts":1.5/20,
|
||||||
|
"sling bullets":1.5/20,
|
||||||
|
"antitoxin":0,
|
||||||
|
"crystal":1,
|
||||||
|
"orb":3,
|
||||||
|
"rod":2,
|
||||||
|
"staff":4,
|
||||||
|
"wand":1,
|
||||||
|
"backpack":5,
|
||||||
|
"ball bearings":2/1000,
|
||||||
|
"barrel":70,
|
||||||
|
"basked":2,
|
||||||
|
"bedroll":7,
|
||||||
|
"bell":0,
|
||||||
|
"blanket":3,
|
||||||
|
"block and tackle":5,
|
||||||
|
"book":5,
|
||||||
|
"glass bottle":2,
|
||||||
|
"bucket":2,
|
||||||
|
"caltrops":2/20,
|
||||||
|
"candle":0,
|
||||||
|
"crosbow bolt case":1,
|
||||||
|
"scroll case":1,
|
||||||
|
"map case":1,
|
||||||
|
"feet of chain":1,
|
||||||
|
"feet chain":1,
|
||||||
|
"chalk":0,
|
||||||
|
"chest":25,
|
||||||
|
"climber's kit":12,
|
||||||
|
"common clothes":3,
|
||||||
|
"costume":4,
|
||||||
|
"fine clothes":6,
|
||||||
|
"traveler's clothes":4,
|
||||||
|
"component pouch":2,
|
||||||
|
"crowbar": 5,
|
||||||
|
"sprig of mistletoe":0,
|
||||||
|
"totem":0,
|
||||||
|
"wooden staff":4,
|
||||||
|
"yew wand":1,
|
||||||
|
"fishing tackle":4,
|
||||||
|
"flask":1,
|
||||||
|
"tankard":1,
|
||||||
|
"grappling hook":4,
|
||||||
|
"hammer":3,
|
||||||
|
"knife":1,
|
||||||
|
"sacrificial knife":1,
|
||||||
|
"sledge hammer":10,
|
||||||
|
"healer's kit":3,
|
||||||
|
"amulet":1,
|
||||||
|
"emblem":0,
|
||||||
|
"reliquary":2,
|
||||||
|
"flask of holy water":1,
|
||||||
|
"hourglass":1,
|
||||||
|
"hunting trap":25,
|
||||||
|
"bottle of ink":0,
|
||||||
|
"ink pen":0,
|
||||||
|
"jug":4,
|
||||||
|
"pitcher":4,
|
||||||
|
"ladder":25,
|
||||||
|
"lamp":1,
|
||||||
|
"bullseye lantern":2,
|
||||||
|
"hooded lantern":2,
|
||||||
|
"lock":1,
|
||||||
|
"magnifying glass":0,
|
||||||
|
"manacles":6,
|
||||||
|
"mess kit":1,
|
||||||
|
"steel mirror":0.5,
|
||||||
|
"flask of oil":1,
|
||||||
|
"paper sheet":0,
|
||||||
|
"parchment":0,
|
||||||
|
"vial of perfume":0,
|
||||||
|
"miner's pick":10,
|
||||||
|
"piton":0.25,
|
||||||
|
"pitons":0.25,
|
||||||
|
"vial of poison":0,
|
||||||
|
"feet pole":7,
|
||||||
|
"iron pot":10,
|
||||||
|
"potion of healing":0.5,
|
||||||
|
"pouch":1,
|
||||||
|
"quiver":1,
|
||||||
|
"portable ram":35,
|
||||||
|
"days of rations":2,
|
||||||
|
"day of ration":2,
|
||||||
|
"robes":4,
|
||||||
|
"feet of hempen rope":10/50,
|
||||||
|
"feet hempen rope":10/50,
|
||||||
|
"feet of silk rope":5/50,
|
||||||
|
"feet silk rope":5/50,
|
||||||
|
"sack":0.5,
|
||||||
|
"merchant's scale":3,
|
||||||
|
"sealing wax":0,
|
||||||
|
"shovel":5,
|
||||||
|
"signal whistle":0,
|
||||||
|
"signet ring":0,
|
||||||
|
"soap":0,
|
||||||
|
"spell book":3,
|
||||||
|
"iron spikes":5/10,
|
||||||
|
"spyglass":1,
|
||||||
|
"two-person tent":20,
|
||||||
|
"tinderbox":1,
|
||||||
|
"torch":1,
|
||||||
|
"torches":1,
|
||||||
|
"vial":0,
|
||||||
|
"waterskin":5,
|
||||||
|
"wheatstone":1}
|
||||||
|
|
||||||
|
tools_weight = {"alchemist's supplies":8,
|
||||||
|
"brewer's supplies":9,
|
||||||
|
"calligrapher's supplies":5,
|
||||||
|
"capenter's tools":6,
|
||||||
|
"cartographer's tools":6,
|
||||||
|
"cobbler's tools":5,
|
||||||
|
"cook's utensils":8,
|
||||||
|
"glassblower's tools":5,
|
||||||
|
"jeweler's tools":2,
|
||||||
|
"leatherworker's tools":5,
|
||||||
|
"mason's tools":8,
|
||||||
|
"painter's supplies":5,
|
||||||
|
"potter's tools":3,
|
||||||
|
"smith's tools":8,
|
||||||
|
"tinker's tools":10,
|
||||||
|
"weaver's tools":5,
|
||||||
|
"woodcarver's tools":5,
|
||||||
|
"disguise kit":3,
|
||||||
|
"forgery kit":5,
|
||||||
|
"dice set":0,
|
||||||
|
"set of bone dice":0,
|
||||||
|
"dragonchess set":0.5,
|
||||||
|
"playing card set":0,
|
||||||
|
"three-dragon ante set":0,
|
||||||
|
"herbalism kit":3,
|
||||||
|
"bagpipes":6,
|
||||||
|
"drum":3,
|
||||||
|
"dulcimer":10,
|
||||||
|
"flute":1,
|
||||||
|
"lute":2,
|
||||||
|
"lyre":2,
|
||||||
|
"horn":2,
|
||||||
|
"pan flute":2,
|
||||||
|
"shawm":1,
|
||||||
|
"viol":1,
|
||||||
|
"navigator's tools":2,
|
||||||
|
"poisoner's kit":2,
|
||||||
|
"thieves' tools":1}
|
||||||
|
gear_weight.update(tools_weight)
|
||||||
|
gear_weight.update({armor.name.lower():armor.weight for armor in all_armors})
|
||||||
|
gear_weight.update({w.name.lower():w.weight for w in all_weapons})
|
||||||
|
|
||||||
|
def equipment_weight_parser(equipment, gear_dict={}):
|
||||||
|
if not equipment.strip():
|
||||||
|
return 0
|
||||||
|
gear_w = gear_weight.copy()
|
||||||
|
gear_w.update(gear_dict)
|
||||||
|
weight = 0
|
||||||
|
for gear in equipment.split(','):
|
||||||
|
gear = gear.lower().strip().strip(".")
|
||||||
|
q, _, item = item_reader.match(gear).groups()
|
||||||
|
if q:
|
||||||
|
q = int(q)
|
||||||
|
else:
|
||||||
|
q = 1
|
||||||
|
if not(item in gear_w.keys()):
|
||||||
|
msg = f'{item} not found in gear_weight dictionary, please add.'
|
||||||
|
warnings.warn(msg)
|
||||||
|
continue
|
||||||
|
weight = weight + q*gear_w[item]
|
||||||
|
return weight
|
||||||
|
|
||||||
@@ -86,14 +86,20 @@ def create_monsters_content(
|
|||||||
monsters: Sequence[Union[monsters.Monster, str]],
|
monsters: Sequence[Union[monsters.Monster, str]],
|
||||||
suffix: str,
|
suffix: str,
|
||||||
use_dnd_decorations: bool = False,
|
use_dnd_decorations: bool = False,
|
||||||
|
base_template: str = "monsters_template"
|
||||||
) -> str:
|
) -> str:
|
||||||
# Convert strings to Monster objects
|
# Convert strings to Monster objects
|
||||||
template = jinja_env.get_template(f"monsters_template.{suffix}")
|
template = jinja_env.get_template(base_template+f".{suffix}")
|
||||||
spell_list = [Spell() for monster in monsters for Spell in monster.spells]
|
spell_list = [Spell() for monster in monsters for Spell in monster.spells]
|
||||||
return template.render(monsters=monsters,
|
return template.render(monsters=monsters,
|
||||||
use_dnd_decorations=use_dnd_decorations, spell_list=spell_list)
|
use_dnd_decorations=use_dnd_decorations, spell_list=spell_list)
|
||||||
|
|
||||||
|
|
||||||
|
def create_gm_spellbook(spell_list, suffix):
|
||||||
|
template = jinja_env.get_template(f"gm_spellbook_template.{suffix}")
|
||||||
|
return template.render(spells=spell_list)
|
||||||
|
|
||||||
|
|
||||||
def create_party_summary_content(
|
def create_party_summary_content(
|
||||||
party: Sequence[Creature],
|
party: Sequence[Creature],
|
||||||
summary_rst: str,
|
summary_rst: str,
|
||||||
@@ -282,6 +288,12 @@ def make_gm_sheet(
|
|||||||
monsters_, suffix=content_suffix, use_dnd_decorations=fancy_decorations
|
monsters_, suffix=content_suffix, use_dnd_decorations=fancy_decorations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add the GM Spellbook
|
||||||
|
spells = [Spell() for monster in monsters_ for Spell in monster.spells]
|
||||||
|
spells = set(spells)
|
||||||
|
content.append(create_gm_spellbook(spells, content_suffix))
|
||||||
|
|
||||||
# Add the random tables
|
# Add the random tables
|
||||||
tables = [
|
tables = [
|
||||||
find_content(s, valid_classes=[random_tables.RandomTable])
|
find_content(s, valid_classes=[random_tables.RandomTable])
|
||||||
@@ -414,6 +426,13 @@ def make_character_content(
|
|||||||
content.append(
|
content.append(
|
||||||
create_druid_shapes_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations)
|
create_druid_shapes_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create a list of companions
|
||||||
|
if len(getattr(character, "companions", [])) > 0:
|
||||||
|
content.append(
|
||||||
|
create_monsters_content(character.companions, suffix=content_format,
|
||||||
|
use_dnd_decorations=fancy_decorations, base_template="companions_template")
|
||||||
|
)
|
||||||
# Postamble, empty for HTML
|
# Postamble, empty for HTML
|
||||||
content.append(
|
content.append(
|
||||||
jinja_env.get_template(f"postamble.{content_format}").render(
|
jinja_env.get_template(f"postamble.{content_format}").render(
|
||||||
@@ -479,8 +498,8 @@ def make_character_sheet(
|
|||||||
character_props = readers.read_sheet_file(char_file)
|
character_props = readers.read_sheet_file(char_file)
|
||||||
character = _char.Character.load(character_props)
|
character = _char.Character.load(character_props)
|
||||||
# Load image file if present
|
# Load image file if present
|
||||||
portrait_file=""
|
portrait_file = character.portrait
|
||||||
if character.portrait:
|
if portrait_file is True:
|
||||||
portrait_file=char_file.stem + ".jpeg"
|
portrait_file=char_file.stem + ".jpeg"
|
||||||
# Set the fields in the FDF
|
# Set the fields in the FDF
|
||||||
basename = char_file.stem
|
basename = char_file.stem
|
||||||
|
|||||||
+59
-1
@@ -1,9 +1,11 @@
|
|||||||
|
import re
|
||||||
import math
|
import math
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dungeonsheets.armor import HeavyArmor, NoArmor, NoShield
|
from dungeonsheets.armor import HeavyArmor, NoArmor, NoShield
|
||||||
|
from dungeonsheets.dice import dice_roll_mean
|
||||||
from dungeonsheets.features import (
|
from dungeonsheets.features import (
|
||||||
AmbushMaster,
|
AmbushMaster,
|
||||||
Defense,
|
Defense,
|
||||||
@@ -28,7 +30,14 @@ from dungeonsheets.features import (
|
|||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
skill_text_locator = re.compile(r"\S+ [+-]\d+")
|
||||||
|
attack_text_locator = re.compile(r"attack:.*?damage", re.IGNORECASE|re.DOTALL)
|
||||||
|
attack = re.compile(r"attack:.*?to hit", re.IGNORECASE|re.DOTALL)
|
||||||
|
damage = re.compile(r"hit:.*?(\d+)d(\d+).*?damage", re.IGNORECASE|re.DOTALL)
|
||||||
|
damage_avg = re.compile(r"hit:.*?(\d+)", re.IGNORECASE|re.DOTALL)
|
||||||
|
damage_nodice = re.compile(r"hit:.*?damage", re.IGNORECASE|re.DOTALL)
|
||||||
|
modifier = re.compile(r"[+-].*?(\d+)", re.IGNORECASE|re.DOTALL)
|
||||||
|
single_damage = re.compile(r"(\d+)")
|
||||||
|
|
||||||
def mod_str(modifier):
|
def mod_str(modifier):
|
||||||
"""Converts a modifier to a string, eg 2 -> '+2'."""
|
"""Converts a modifier to a string, eg 2 -> '+2'."""
|
||||||
@@ -325,3 +334,52 @@ class Initiative(NumericalInitiative):
|
|||||||
if has_advantage:
|
if has_advantage:
|
||||||
ini += "(A)"
|
ini += "(A)"
|
||||||
return ini
|
return ini
|
||||||
|
|
||||||
|
def _add_modifier(att_text, prof):
|
||||||
|
"""Auxiliary function to add proficiency bonus prof
|
||||||
|
to att_text."""
|
||||||
|
_att_bonus_re = modifier.search(att_text)
|
||||||
|
att_bonus_text = _att_bonus_re.group()
|
||||||
|
att_bonus = int(att_bonus_text.replace(" ", "").replace("\n", "")) + prof
|
||||||
|
return re.sub(modifier, "{:+d}".format(att_bonus), att_text)
|
||||||
|
|
||||||
|
def skill_modifier(skills_text, prof):
|
||||||
|
"""Modifies the skill text string adding the proficiency
|
||||||
|
bonus to its values."""
|
||||||
|
skills_updated = []
|
||||||
|
skill_list = re.findall(skill_text_locator, skills_text)
|
||||||
|
if not skill_list:
|
||||||
|
return ""
|
||||||
|
for sk in skill_list:
|
||||||
|
increased_skill = _add_modifier(sk, prof)
|
||||||
|
skills_updated.append(increased_skill)
|
||||||
|
return ", ".join(skills_updated)
|
||||||
|
|
||||||
|
def att_dmg_modifier(text, prof):
|
||||||
|
"""Modify the attack and damage rolls for a strip
|
||||||
|
of attack text description."""
|
||||||
|
_att_re = attack.search(text)
|
||||||
|
if not _att_re:
|
||||||
|
raise ValueError("No attack info detected.")
|
||||||
|
att_text = _att_re.group()
|
||||||
|
new_att_text = _add_modifier(att_text, prof)
|
||||||
|
text = re.sub(attack, new_att_text, text)
|
||||||
|
_dmg_re = damage.search(text)
|
||||||
|
if _dmg_re:
|
||||||
|
dmg_text = _dmg_re.group()
|
||||||
|
new_dmg_text = _add_modifier(dmg_text, prof)
|
||||||
|
dmg_avg_value = dice_roll_mean(new_dmg_text)
|
||||||
|
_dmg_avg_re = damage_avg.search(new_dmg_text)
|
||||||
|
dmg_avg_text = _dmg_avg_re.group()
|
||||||
|
new_dmg_avg_text = re.sub("(\d+)", "{:d}".format(dmg_avg_value),
|
||||||
|
dmg_avg_text, 1)
|
||||||
|
new_dmg_text = re.sub(damage_avg, new_dmg_avg_text, new_dmg_text)
|
||||||
|
text = re.sub(damage, new_dmg_text, text)
|
||||||
|
else:
|
||||||
|
_dmg_re = damage_nodice.search(text)
|
||||||
|
dmg_text = _dmg_re.group()
|
||||||
|
_sdamage_re = single_damage.search(dmg_text)
|
||||||
|
sdamage = int(_sdamage_re.group()) + prof
|
||||||
|
new_dmg_text = re.sub(single_damage, "{:d}".format(sdamage), dmg_text)
|
||||||
|
text = re.sub(single_damage, new_dmg_text, text)
|
||||||
|
return text
|
||||||
|
|||||||
Reference in New Issue
Block a user