Source code for DnD_5e.character_classes.monk

from DnD_5e import features, armory
from DnD_5e.combatant.character import Character
from DnD_5e.utility_methods_dnd import ability_to_mod, proficiency_bonus_per_level, TYPE_DICE_TUPLE


[docs] class Monk(Character): """ Monk character class """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Some of the keyword arguments are overridden by this class. :param name: what *self* is called. A unique name is recommended but not required :type name: str :param vulnerabilities: all the damage types that *self* is vulnerable to :type vulnerabilities: set, list, or tuple of strings (will be converted to set of strings) :param resistances: all the damage types that *self* is resistant to :type resistances: set, list, or tuple of strings (will be converted to set of strings) :param immunities: all the damage types that *self* is immune to :type immunities: set, list, or tuple of strings (will be converted to set of strings) :param ac: *self's* armor class :type ac: positive integer :param temp_hp: temporary hit points :type temp_hp: non-negative integer :param conditions: all conditions currently affecting *self* :type conditions: list of strings :param strength: strength score. Will be converted to modifier and stored as such. :type strength: integer between 1 and 30 (inclusive) :param strength_mod: dexterity modifier :type strength_mod: int :param dexterity: dexterity score. Will be converted to modifier and stored as such. :type dexterity: integer between 1 and 30 (inclusive) :param dexterity_mod: dexterity modifier :type dexterity_mod: int :param constitution: constitution score. Will be converted to modifier and stored as such. :type constitution: integer between 1 and 30 (inclusive) :param constitution_mod: constitution modifier :type constitution_mod: int :param intelligence: intelligence score. Will be converted to modifier and stored as such. :type intelligence: integer between 1 and 30 (inclusive) :param intelligence_mod: intelligence modifier :type intelligence_mod: int :param wisdom: wisdom score. Will be converted to modifier and stored as such. :type wisdom: integer between 1 and 30 (inclusive) :param wisdom_mod: wisdom modifier :type wisdom_mod: int :param charisma: charisma score. Will be converted to modifier and stored as such. :type charisma: integer between 1 and 30 (inclusive) :param charisma_mod: charisma modifier :type charisma_mod: int :param death_saves: NOT IMPLEMENTED YET :param attacks: NOT IMPLEMENTED YET :param weapons: Weapons (see weapons module) that *self* has available to use :type weapons: list of Weapons :param size: size :type size: one of these strings: "tiny", "small", "medium", "large", "huge", "gargantuan" :param items: NOT IMPLEMENTED YET :param level: character level :type level: integer between 1 and 20 (inclusive) :raise: ValueError if input is invalid """ level = kwargs.get("level") if not level: raise ValueError("No level provided or level is 0") if not isinstance(level, int) or level < 1 or level > 20: raise ValueError("Level must be an integer between 1 and 20") hit_dice = (1 * level, 8) constitution = kwargs.get('constitution') if not constitution: constitution_mod = kwargs.get("constitution_mod") if constitution_mod is not None: if isinstance(constitution_mod, int): self._constitution = constitution_mod else: raise ValueError("Constitution mod must be an integer") else: raise ValueError("Must provide constitution score or modifier") else: # pragma: no cover constitution_mod = ability_to_mod(constitution) max_hp = kwargs.get("max_hp") if max_hp and (not isinstance(max_hp, int) or max_hp <= 0): raise ValueError("Must provide positive integer max hp") max_hp = 8 + constitution_mod if level > 1: max_hp += (5 + constitution_mod) * (level - 1) proficiencies = kwargs.get('proficiencies') if isinstance(proficiencies, (tuple, list, set)): proficiencies = set(proficiencies) elif proficiencies is None: # pragma: no cover proficiencies = set() else: # pragma: no cover raise ValueError("Proficiencies must be provided as a set, list, or tuple") proficiencies.add("monk weapons") proficiencies.add("strength") proficiencies.add("dexterity") proficiency_mod = proficiency_bonus_per_level(level) kwargs.update({"hit_dice": hit_dice, "max_hp": max_hp, "proficiencies": proficiencies, "proficiency_mod": proficiency_mod, "feature_classes": [features.UnarmoredDefenseMonk]}) super().__init__(**kwargs) if self.get_level() > 1: if self.get_level() < 6: self._unarmored_movement = 10 elif self.get_level() < 10: self._unarmored_movement = 15 elif self.get_level() < 14: self._unarmored_movement = 20 elif self.get_level() < 18: self._unarmored_movement = 25 else: self._unarmored_movement = 30 if self.get_level() < 5: self._martial_arts_dice = (1, 4) elif self.get_level() < 11: self._martial_arts_dice = (1, 6) elif self.get_level() < 17: self._martial_arts_dice = (1, 8) else: self._martial_arts_dice = (1, 10) self.add_feature("unarmored defense") if self.get_level() > 1: self._ki_points = self.get_level() self._ki_save_dc = 8 + self.get_proficiency_mod() + self.get_wisdom() self.add_feature("flurry of blows") self.add_feature("patient defense") self.add_feature("step of the wind") if self.get_level() > 2: self.add_feature("deflect missiles") if self.get_level() > 3: self.add_feature("slow fall") if self.get_level() > 4: self.add_feature("extra attack") self.add_feature("stunning strike") if self.get_level() > 5: self.add_feature("ki-empowered strikes") if self.get_level() > 6: self.add_feature("stillness of mind") self.add_feature("evasion") if self.get_level() > 9: self.add_feature("purity of body") if self.get_level() > 12: self.add_feature("tongue of the sun and moon") if self.get_level() > 13: self.add_feature("diamond soul") # proficiency in all Saving Throws self._proficiencies.add("constitution") self._proficiencies.add("intelligence") self._proficiencies.add("wisdom") self._proficiencies.add("charisma") self._saving_throws = {"strength": self.get_strength() + self.get_proficiency_mod(), "dexterity": self.get_dexterity() + self.get_proficiency_mod(), "constitution": self.get_constitution() + self.get_proficiency_mod(), "intelligence": self.get_intelligence() + self.get_proficiency_mod(), "wisdom": self.get_wisdom() + self.get_proficiency_mod(), "charisma": self.get_charisma() + self.get_proficiency_mod()} if self.get_level() > 17: self.add_feature("empty body") if self.get_level() == 20: self.add_feature("perfect soul") def __eq__(self, other) -> bool: """ Compare *self* and *other* to determine if they are equal based on the superclass method and these attributes: ki save dc :param other: the Monk to compare :type other: Monk :return: True if *self* equals *other*, False otherwise :rtype: bool """ return super().__eq__(other) \ and self.get_ki_save_dc() == other.get_ki_save_dc()
[docs] def current_eq(self, other) -> bool: """ Compare *self* and *other* to determine if they are identical based on the attributes checked in *equals* and also these attributes: ki points :param other: the Monk to compare :type other: Monk :return: True if *self* is identical to *other*, False otherwise :rtype: bool """ return super().current_eq(other) \ and self.get_ki_points() == other.get_ki_points()
[docs] def get_martial_arts_dice(self) -> TYPE_DICE_TUPLE: """ :return: martial arts dice :rtype: TYPE_DICE_TUPLE """ return self._martial_arts_dice
[docs] def get_ki_points(self) -> int: """ :return: ki points :rtype: non-negative integer """ try: return self._ki_points except AttributeError: return 0
[docs] def get_ki_save_dc(self) -> int: """ :return: ki save dc :rtype: positive integer """ try: return self._ki_save_dc except AttributeError: self.get_logger().error("Cannot use a ki saving throw attack", stack_info=True) raise ValueError("Cannot use a ki saving throw attack")
[docs] def spend_ki_points(self, num: int) -> int: """ Spend *num* number of ki points :param num: the number of ki points to spend :return: None :raise: ValueError if *self* doesn't have enough ki points or *num* is invalid """ if not isinstance(num, int) or num < 1: self.get_logger().error("Num of ki points must be a positive integer", stack_info=True) raise ValueError("Num of ki points must be a positive integer") if num > self.get_ki_points(): self.get_logger().error("You don't have enough ki points left to spend", stack_info=True) raise ValueError("You don't have enough ki points left to spend") self._ki_points -= num return num
[docs] def add_attack(self, attack): """ Add the given attack. If the attack is related to a monk weapon and dexterity mod is less than strength mod, change strength mod for attack and damage to dexterity mod. :param attack: the Attack to add :type attack: Attack :return: None """ if armory.is_monk_weapon(attack.get_weapon()) and self.get_dexterity() > self.get_strength(): # change from str to dex attack.set_attack_mod(attack.get_attack_mod() - self.get_strength() + self.get_dexterity()) attack.set_damage_mod(attack.get_damage_mod() - self.get_strength() + self.get_dexterity()) super().add_attack(attack)