Exemple #1
0
    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)
        }
Exemple #2
0
    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
Exemple #3
0
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