Source code for DnD_5e.tactics.combatant_tactics

from copy import copy, deepcopy

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

[docs] class CombatantTactic(Tactic): """ Subclass of :py:class:`Tactic` used for selecting :py:class:`Combatant` s """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] class LowestAcTactic(MinTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with the lowest AC. """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ Get the value for whatever detail of *item* we are concerned about :param item: the option we are looking at :return: the numerical value of whatever detail we are concerned about """ return item.get_ac()
[docs] class HighestAcTactic(MaxTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with the highest AC """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ Get the value for whatever detail of *item* we are concerned about :param item: the option we are looking at :return: the numerical value of whatever detail we are concerned about """ return item.get_ac()
[docs] class LowAcTactic(ConditionTactic, ThresholdTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with AC below a threshold determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Same as superclasses, plus these: :param attack: the attack to get the threshold from :type attack: :py:class:`Attack` :param use_max: indicates whether to use max hit instead of average hit :type use_max: bool :raise: ValueError if input is invalid """ super().__init__(**kwargs) if kwargs.get("copy"): return
[docs] def calculate_threshold(self, **kwargs) -> int: """ Calculate threshold using keyword arguments. :param kwargs: passed on from constructor :param attack: the attack to get the threshold from :type attack: :py:class:`Attack` :param use_max: indicates whether to use max hit instead of average hit :type use_max: bool :return: threshold """ attack = kwargs.get("attack") use_max = kwargs.get("use_max") try: if use_max: return attack.get_max_hit() + 1 return int(attack.get_average_hit()) + 1 except AttributeError: raise ValueError("attack must be an Attack")
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has AC lower than :py:attr:`_threshold`, False otherwise """ try: return item.get_ac() < self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HighAcTactic(ConditionTactic, ThresholdTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with AC above a threshold determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Same as in superclasses. :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has AC higher than :py:attr:`_threshold`, False otherwise """ try: return item.get_ac() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class BloodiedTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that is bloodied """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is bloodied, False otherwise """ return item.is_bloodied()
[docs] class LowestHpTactic(MinTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with the lowest AC. """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ Get the value for whatever detail of *item* we are concerned about :param item: the option we are looking at :return: the numerical value of whatever detail we are concerned about """ return item.get_current_hp()
[docs] class HighestHpTactic(MaxTactic, CombatantTactic): """ Selects the :py:class:`Combatant` with the highest AC """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def get_value(self, item, **kwargs): """ Get the value for whatever detail of *item* we are concerned about :param item: the option we are looking at :return: the numerical value of whatever detail we are concerned about """ return item.get_current_hp()
[docs] class LowHpTactic(ConditionTactic, ThresholdTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that has hp lower than a given threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def calculate_threshold(self, **kwargs): """ Calculate threshold using keyword arguments. :param kwargs: passed on from constructor :param attack: the attack to get the threshold from :type attack: :py:class:`Attack` :param use_max: indicates whether to use max hit instead of average hit :type use_max: bool :return: threshold """ attack = kwargs.get("attack") use_max = kwargs.get("use_max") try: if use_max: return attack.get_max_damage() + 1 return int(attack.get_average_damage()) + 1 except AttributeError: raise ValueError("attack must be an Attack")
[docs] def check_condition(self, item, **kwargs): """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has hp lower than threshold, False otherwise """ return item.get_current_hp() < self.get_threshold()
[docs] class HighHpTactic(ConditionTactic, ThresholdTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that has hp lower than a given threshold """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def calculate_threshold(self, **kwargs): """ Calculate threshold using keyword arguments. :param kwargs: passed on from constructor :param attack: the attack to get the threshold from :type attack: :py:class:`Attack` :param use_max: indicates whether to use max hit instead of average hit :type use_max: bool :return: threshold """ attack = kwargs.get("attack") use_max = kwargs.get("use_max") try: if use_max: return attack.get_max_damage() + 1 return int(attack.get_average_damage()) + 1 except AttributeError: raise ValueError("attack must be an Attack")
[docs] def check_condition(self, item, **kwargs): """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has hp lower than threshold, False otherwise """ return item.get_current_hp() > self.get_threshold()
[docs] class MaxHpTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that is at max hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is at max hp, False otherwise """ try: return item.is_hp_max() except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HighHpToMaxTactic(ConditionTactic, ThresholdTactic, CombatantTactic): """ Selects the :py:class:`Combatant` for whom the difference between max hp and current hp is greater than a threshold determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Same as superclass, plus these: :param healing: a HealingSpell to determine the threshold - Combatants that have an hp to max greater than or equal to the healing from this spell will be selected :type healing: HealingSpell :param use_max: if this argument evaluates to True, the threshold will be set so that Combatants that will not be healed beyond their max using the max healing of this spell will be selected (as opposed to all Combatants that the will not be healed beyond their max using the average healing of this spell) :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def calculate_threshold(self, **kwargs): healing = kwargs.get("healing") use_max = kwargs.get("use_max", True) try: if use_max: return healing.get_max_damage() - 1 return int(healing.get_average_damage()) - 1 except AttributeError: raise ValueError("Healing must be an Attack (recommended to be a HealingSpell)")
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if the difference between item's max hp and current hp is greater than :py:attr:`_threshold`, False otherwise """ try: return item.get_hp_to_max() > self.get_threshold() except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class IsConsciousTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that is conscious (i.e., not unconscious or dead) """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is unconscious, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.is_conscious() except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class IsUnconsciousTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that is unconscious """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is unconscious, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.has_condition("unconscious") except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HasTempHpTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that has temporary hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has temporary hp, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.get_temp_hp() > 0 except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class NoTempHpTactic(ConditionTactic, CombatantTactic): """ Selects the :py:class:`Combatant` that has no temporary hp """ def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item has no temporary hp, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.get_temp_hp() < 1 except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class VulnerabilityTactic(CombatantTactic): def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param vulnerability_type: the damage type that selected Combatants must be vulnerable to :type vulnerability_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs) self._vulnerability_type = kwargs.get("vulnerability_type") if not isinstance(self._vulnerability_type, str): raise ValueError("Vulnerability type must be a string") def __eq__(self, other) -> bool: """ :return: """ return super().__eq__(other) and self.get_vulnerability_type() == other.get_vulnerability_type()
[docs] def get_vulnerability_type(self) -> str: """ :return: vulnerability_type """ return self._vulnerability_type
[docs] class HasVulnerabilityTactic(VulnerabilityTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that has a vulnerability determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param vulnerability_type: the damage type that selected Combatants must be vulnerable to :type vulnerability_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is vulnerable to :py:attr:`vulnerability_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.is_vulnerable(self.get_vulnerability_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HasNoVulnerabilityTactic(VulnerabilityTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that does not have a vulnerability determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param vulnerability_type: the damage type that selected Combatants must not be vulnerable to :type vulnerability_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is not vulnerable to :py:attr:`vulnerability_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return not item.is_vulnerable(self.get_vulnerability_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class ResistanceTactic(CombatantTactic): def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param resistance_type: the damage type that selected Combatants must be resistant to :type resistance_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs) self._resistance_type = kwargs.get("resistance_type") if not isinstance(self._resistance_type, str): raise ValueError("Resistance type must be a string") def __eq__(self, other) -> bool: """ :param other: :return: """ return super().__eq__(other) and self.get_resistance_type() == other.get_resistance_type()
[docs] def get_resistance_type(self) -> str: """ :return: resistance_type """ return self._resistance_type
[docs] class HasResistanceTactic(ResistanceTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that has a resistance determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param resistance_type: the damage type that selected Combatants must be resistant to :type resistance_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is resistant to :py:attr:`resistance_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.is_resistant(self.get_resistance_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HasNoResistanceTactic(ResistanceTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that does not have a resistance determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param resistance_type: the damage type that selected Combatants must not be resistant to :type resistance_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is not resistant to :py:attr:`_resistance_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return not item.is_resistant(self.get_resistance_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class ImmunityTactic(CombatantTactic): def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param immunity_type: the damage type or condition that selected Combatants must be immune to. :type immunity_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs) self._immunity_type = kwargs.get("immunity_type") if not isinstance(self._immunity_type, str): raise ValueError("Immunity type must be a string") def __eq__(self, other) -> bool: """ :param other: :return: """ return super().__eq__(other) and self.get_immunity_type() == other.get_immunity_type()
[docs] def get_immunity_type(self) -> str: """ :return: immunity_type """ return self._immunity_type
[docs] class HasImmunityTactic(ImmunityTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that has an immunity determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param immunity_type: the damage type or condition that selected Combatants must be immune to. :type immunity_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is immune to :py:attr:`immunity_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return item.is_immune(self.get_immunity_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")
[docs] class HasNoImmunityTactic(ImmunityTactic, ConditionTactic): """ Selects the :py:class:`Combatant` that does not have an immunity determined at initialization """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param tiebreakers: the Tactics to be applied in case of a tie, in the order in which they will be applied :type tiebreakers: list of :py:class:`Tactic` s :param verbose: if this argument is provided with anything that evaluates to True, *self._verbose* is set to True. This means that output about what *self* is doing (and what is happening to *self*) will be printed to the console :param immunity_type: the damage type that selected Combatants must not be immune to. :type immunity_type: str :raise: ValueError if input is invalid """ super().__init__(**kwargs)
[docs] def check_condition(self, item, **kwargs) -> bool: """ :param item: the :py:class:`Combatant` to check :type item: :py:class:`Combatant` :return: True if item is not immune to :py:attr:`immunity_type`, False otherwise :raise: ValueError if item is not a Combatant """ try: return not item.is_immune(self.get_immunity_type()) except AttributeError: raise ValueError("Items in choices must be Combatants")