Source code for DnD_5e.tactics.attack_tactics

from copy import copy, deepcopy

from DnD_5e.tactics import Tactic, ThresholdTactic, ConditionTactic, MaxTactic

[docs] class AttackTactic(Tactic): """ Subclass of :py:class:`Tactic` used for selecting :py:class:`Attack` s """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] class DamageTypeTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has the given damage type """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Same as in superclasses, plus these: :param damage_type: the damage type that selected Attacks must have :type damage_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs) self._damage_type = kwargs.get("damage_type") if not isinstance(self._damage_type, str): raise ValueError("Damage type must be a string") def __eq__(self, other): """ :param other: :return: """ return super().__eq__(other) and self.get_damage_type() == other.get_damage_type()
[docs] def get_damage_type(self) -> str: """ :return: damage_type """ return self._damage_type
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.has_damage_type(self.get_damage_type()) except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class DamageVulnerabilityTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has a damage type that the target is vulnerable to """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: vulnerabilities = target.get_vulnerabilities() except AttributeError: raise ValueError("target must be a Combatant") try: for vulnerability in vulnerabilities: if item.has_damage_type(vulnerability): return True return False except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class DamageNoImmuneTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` such that none of that attack's damage is a type that the target is immune to """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the Attack to look at :type item: :py:class:`Attack` :param kwargs: :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: immunities = target.get_immunities() except AttributeError: raise ValueError("target must be a Combatant") try: for immunity in immunities: if item.has_damage_type(immunity): return False return True except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class DamageNoResistanceTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` such that none of that attack's damage is a type that the target is resistant to """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: resistances = target.get_resistances() except AttributeError: raise ValueError("target must be a Combatant") try: for resistance in resistances: if item.has_damage_type(resistance): return False return True except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class LowRangeTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has nonzero range lower than the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return 0 < item.get_range() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks") except TypeError: # if range is not a number, e.g., "self" return False
[docs] class HighRangeTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has range higher than the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_range() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks") except TypeError: # if range is not a number, e.g., "self" return False
[docs] class LowMeleeRangeTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has nonzero melee range lower than the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return 0 < item.get_melee_range() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks") except TypeError: # if range is not a number, e.g., "self" return False
[docs] class HighMeleeRangeTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has range higher than the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_melee_range() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks") except TypeError: # if range is not a number, e.g., "self" return False
[docs] class HasAdvTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has advantage """ def __init__(self, **kwargs): """ :param kwargs: """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_adv() > 0 except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HasDisAdvTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that has disadvantage. I don't know why you would do this, but now you can. """ def __init__(self, **kwargs): """ :param kwargs: """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_adv() < 0 except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HasNoDisadvTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` that does not have disadvantage """ def __init__(self, **kwargs): """ :param kwargs: """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_adv() > -1 except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighestMaxHitTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest maximum to-hit value (e.g., with an attack modifier of 10) """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ :param item: :return: """ try: return item.get_max_hit() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighMaxHitTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum to-hit value higher than the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_max_hit() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class MaxHitACTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum to-hit value greater than or equal to the target's AC """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_ac = target.get_ac() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_max_hit() >= target_ac except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighestAvgHitTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest average to-hit value """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ :param item: :return: """ try: return item.get_average_hit() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighAvgHitTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with an average to-hit value higher than threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_average_hit() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class AvgHitACHighTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with an average to-hit value greater than or equal to the target's AC """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_ac = target.get_ac() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_average_hit() >= target_ac except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighestMaxDamageTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest maximum damage """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ :param item: :return: """ try: return item.get_max_damage() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighMaxDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with maximum damage above the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_max_damage() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class MaxDamageHPHighTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum damage value greater than or equal to the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_max_damage() >= target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class LowMaxDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with maximum damage below the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_max_damage() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class MaxDamageHPLowTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum damage value lower than the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_max_damage() < target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighestMinDamageTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest minimum damage """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ :param item: :return: """ return item.get_min_damage()
[docs] class HighMinDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with minimum damage above the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_min_damage() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class MinDamageHPHighTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a minimum damage value greater than or equal to the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_min_damage() >= target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class LowMinDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with minimum damage below the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_min_damage() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class MinDamageHPLowTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum damage value lower than or the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_min_damage() < target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class HighestAvgDamageTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest average damage """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ :param item: :return: """ return item.get_average_damage()
[docs] class HighAvgDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with average damage above the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_average_damage() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class AvgDamageHPHighTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with a maximum damage value greater than or equal to the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_average_damage() >= target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class LowAvgDamageTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with average damage below the threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: :return: """ try: return item.get_average_damage() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class AvgDamageHPLowTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with an average damage value lower than the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs): """ :param item: the Attack to look at :type item: :py:class:`Attack` :param target: the Combatant the attack will be made against :type target: :py:class:`Combatant` :return: True if the condition is met, False otherwise """ target = kwargs.get("target") try: target_hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_average_damage() < target_hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class DprMaxTactic(MaxTactic, AttackTactic): """ Select the :py:class:`Attack` with the highest dpr for the target (dpr is probability to hit * average damage) """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ Get the dpr for this Attack for the given target :param item: the Attack we are looking at :type item: :py:class:`Attack` :param target: the Combatant we are trying to hit :type target: :py:class:`Combatant` :return: """ try: return item.get_dpr(target=kwargs.get("target")) except AttributeError: raise ValueError("target must be a Combatant and items in choices must be Attacks")
[docs] class DprHighTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with dpr (probability to hit the target * average damage) higher than threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def calculate_threshold(self, **kwargs) -> float: # pylint: disable=unused-argument """ Calculate threshold using keyword arguments. :param kwargs: passed on from constructor :return: threshold """ raise ValueError("Threshold must be a number")
[docs] def validate_threshold(self): """ :return: :raise: ValueError if threshold is invalid """ if not isinstance(self._threshold, (int, float)) or self._threshold < 0: raise ValueError("Must provide non-negative number for threshold")
[docs] def check_condition(self, item, **kwargs) -> bool: """ Check whether the given Attack has a dpr (probability to hit the target's ac * average damage) higher than threshold :param item: the Attack we are looking at :type item: :py:class:`Attack` :return: True if the condition is met, False otherwise """ try: return item.get_dpr(target=kwargs.get("target")) > self.get_threshold() except AttributeError: raise ValueError("target must be a Combatant and items in choices must be Attacks")
[docs] class DprHpHighTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with dpr (probability to hit the target * average damage) higher than the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ Check whether the given Attack has a dpr (probability to hit the target's ac * average damage) greater than or equal to the target's current hp :param item: the Attack we are looking at :type item: :py:class:`Attack` :return: True if the condition is met, False otherwise """ try: target = kwargs.get("target") hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_dpr(target=target) >= hp except AttributeError: raise ValueError("Items in choices must be Attacks")
[docs] class DprLowTactic(ThresholdTactic, ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with dpr (probability to hit the target * average damage) lower than threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def calculate_threshold(self, **kwargs) -> float: # pylint: disable=unused-argument """ Calculate threshold using keyword arguments. :param kwargs: passed on from constructor :return: threshold """ raise ValueError("Threshold must be a number")
[docs] def validate_threshold(self): """ :return: :raise: ValueError if threshold is invalid """ if not isinstance(self._threshold, (int, float)) or self._threshold < 0: raise ValueError("Must provide non-negative number for threshold")
[docs] def check_condition(self, item, **kwargs) -> bool: """ Check whether the given Attack has a dpr (probability to hit the target's ac * average damage) higher than threshold :param item: the Attack we are looking at :type item: :py:class:`Attack` :return: True if the condition is met, False otherwise """ try: return item.get_dpr(target=kwargs.get("target")) < self.get_threshold() except AttributeError: raise ValueError("target must be a Combatant and items in choices must be Attacks")
[docs] class DprHpLowTactic(ConditionTactic, AttackTactic): """ Select the :py:class:`Attack` with dpr (probability to hit the target * average damage) lower than the target's current hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ Check whether the given Attack has a dpr (probability to hit the target's ac * average damage) lower than the target's current hp :param item: the Attack we are looking at :type item: :py:class:`Attack` :return: True if the condition is met, False otherwise """ try: target = kwargs.get("target") hp = target.get_current_hp() except AttributeError: raise ValueError("target must be a Combatant") try: return item.get_dpr(target=target) < hp except AttributeError: raise ValueError("Items in choices must be Attacks")