def damage(self, dice_str, crit=False, d=None, c=None, critdice=0, overheal=False): """ Does damage to a combatant, and returns the rolled result and total, accounting for resistances. :param str dice_str: The damage to do (e.g. ``"1d6[acid]"``). :param bool crit: Whether or not the damage should be rolled as a crit. :param str d: Any additional damage to add (equivalent of -d). :param str c: Any additional damage to add to crits (equivalent of -c). :param int critdice: How many extra weapon dice to roll on a crit (in addition to normal dice). :param bool overheal: Whether or not to allow this damage to exceed a target's HP max. :return: Dictionary representing the results of the Damage Automation. :rtype: dict """ from cogs5e.models.automation import AutomationContext, AutomationTarget, \ Damage # this has to be here to avoid circular imports class _SimpleAutomationContext(AutomationContext): def __init__(self, caster, target, args, combat, crit=False): super(_SimpleAutomationContext, self).__init__(None, None, caster, [target], args, combat) self.in_crit = crit self.target = AutomationTarget(target) args = ParsedArguments.from_dict({ 'critdice': [critdice], 'resist': self._combatant.resistances['resist'], 'immune': self._combatant.resistances['immune'], 'vuln': self._combatant.resistances['vuln'] }) if d: args['d'] = d if c: args['c'] = c damage = Damage(dice_str, overheal=overheal) autoctx = _SimpleAutomationContext(StatBlock("generic"), self._combatant, args, self._combatant.combat, crit) result = damage.run(autoctx) roll_for = "Damage" if not result.in_crit else "Damage (CRIT!)" return { 'damage': f"**{roll_for}**: {result.damage_roll.result}", 'total': result.damage, 'roll': SimpleRollResult(result.damage_roll) }
def __init__( self, name: str, size: str, race: str, alignment: str, ac: int, armortype: str, hp: int, hitdice: str, speed: str, ability_scores: BaseStats, saves: Saves, skills: Skills, senses: str, display_resists: Resistances, condition_immune: list, languages: list, cr: str, xp: int, # optional traits: list = None, actions: list = None, reactions: list = None, legactions: list = None, la_per_round=3, passiveperc: int = None, # augmented resistances: Resistances = None, attacks: AttackList = None, proper: bool = False, image_url: str = None, spellcasting=None, # sourcing homebrew=False, **kwargs): if traits is None: traits = [] if actions is None: actions = [] if reactions is None: reactions = [] if legactions is None: legactions = [] if attacks is None: attacks = AttackList() if spellcasting is None: spellcasting = MonsterSpellbook() if passiveperc is None: passiveperc = 10 + skills.perception.value # old/new resist handling if resistances is None: # fall back to old-style resistances (deprecated) vuln = kwargs.get('vuln', []) resist = kwargs.get('resist', []) immune = kwargs.get('immune', []) resistances = Resistances.from_dict( dict(vuln=vuln, resist=resist, immune=immune)) try: levels = Levels({"Monster": spellcasting.caster_level or int(cr)}) except ValueError: levels = None Sourced.__init__(self, 'monster', homebrew, source=kwargs['source'], entity_id=kwargs.get('entity_id'), page=kwargs.get('page'), url=kwargs.get('url'), is_free=kwargs.get('is_free')) StatBlock.__init__(self, name=name, stats=ability_scores, attacks=attacks, skills=skills, saves=saves, resistances=resistances, spellbook=spellcasting, ac=ac, max_hp=hp, levels=levels) self.size = size self.race = race self.alignment = alignment self.armortype = armortype self.hitdice = hitdice self.speed = speed self.cr = cr self.xp = xp self.passive = passiveperc self.senses = senses self.condition_immune = condition_immune self.languages = languages self.traits = traits self.actions = actions self.reactions = reactions self.legactions = legactions self.la_per_round = la_per_round self.proper = proper self.image_url = image_url # resistances including notes, e.g. "Bludgeoning from nonmagical weapons" self._displayed_resistances = display_resists or resistances
import logging import textwrap import pytest from aliasing.evaluators import AutomationEvaluator from cogs5e.models import automation from cogs5e.models.sheet.statblock import StatBlock from gamedata.compendium import compendium from tests.conftest import end_init, start_init from tests.utils import active_character, active_combat, requires_data log = logging.getLogger(__name__) pytestmark = pytest.mark.asyncio DEFAULT_CASTER = StatBlock("Bob") DEFAULT_EVALUATOR = AutomationEvaluator.with_caster(DEFAULT_CASTER) @pytest.mark.parametrize("attack_bonus", [ # valid inputs "3", "dexterityMod", "{dexterityMod}", "dexterityMod + proficiencyBonus", "{dexterityMod + proficiencyBonus}", # inputs that should be NaN "foobar", "{}", "dexterymod", "RogueLevel" ]) async def test_attack_strs(attack_bonus): attack = automation.Attack(hit=[], miss=[], attackBonus=attack_bonus) result = attack.build_str(DEFAULT_CASTER, DEFAULT_EVALUATOR) log.info(f"Attack str: ({attack_bonus=!r}) -> {result}") assert result