class Monster(Combatant): cr = attrib(default=0) xp = attrib(default=0) size = attrib(default="medium") mtype = attrib(default="humanoid") alignment = attrib(default="unaligned") str = attrib(default=10) dex = attrib(default=10) con = attrib(default=10) int = attrib(default=10) wis = attrib(default=10) cha = attrib(default=10) def __getattr__(self, attr_name): if attr_name[3:] == "_mod" and attr_name[:3] in ( "str", "dex", "con", "int", "wis", "cha", ): return self.ability_modifier(getattr(self, attr_name[:3])) elif attr_name == "initiative_mod": return self.ability_modifier(getattr(self, "dex")) else: raise AttributeError( f"'Monster' object has no attribute '{attr_name}'") def ability_modifier(self, stat): return floor((stat - 10) / 2) armor = attrib(default="") speed = attrib(default=30) skills = attrib(default=attr_factory(dict)) resist = attrib(default=attr_factory(list)) immune = attrib(default=attr_factory(list)) vulnerable = attrib(default=attr_factory(list)) languages = attrib(default=attr_factory(list)) features = attrib(default=attr_factory(dict)) actions = attrib(default=attr_factory(dict)) lair_actions = attrib(default=attr_factory(dict)) legendary_actions = attrib(default=attr_factory(dict)) reactions = attrib(default=attr_factory(dict)) notes = attrib(default="") origin = attrib(default="origin unknown") visible_in_player_view = attrib(default=False) disposition = attrib(default="hostile")
class Monster(Combatant): cr = attrib(default=0) xp = attrib(default=0) size = attrib(default="medium") mtype = attrib(default="humanoid") alignment = attrib(default="unaligned") str = attrib(default=10) dex = attrib(default=10) con = attrib(default=10) int = attrib(default=10) wis = attrib(default=10) cha = attrib(default=10) def __getattr__(self, attr_name): if attr_name[3:] == '_mod' and \ attr_name[:3] in ('str', 'dex', 'con', 'int', 'wis', 'cha'): return self.ability_modifier(getattr(self, attr_name[:3])) elif attr_name == 'initiative_mod': return self.ability_modifier(getattr(self, 'dex')) else: raise AttributeError( f"'Monster' object has no attribute '{attr_name}'") def ability_modifier(self, stat): return floor((stat - 10) / 2) armor = attrib(default="") speed = attrib(default=30) skills = attrib(default=attr_factory(dict)) resist = attrib(default=attr_factory(list)) immune = attrib(default=attr_factory(list)) vulnerable = attrib(default=attr_factory(list)) languages = attrib(default=attr_factory(list)) features = attrib(default=attr_factory(dict)) actions = attrib(default=attr_factory(dict)) legendary_actions = attrib(default=attr_factory(dict)) reactions = attrib(default=attr_factory(dict)) notes = attrib(default="") origin = attrib(default="origin unknown")
class Combatant: name = attrib(default="") _alias = attrib(default="") @property def alias(self): return self._alias or self.name @alias.setter def alias(self, value): self._alias = value race = attrib(default="") ac = attrib(default=0) senses = attrib(default=attr_factory(dict)) conditions = attrib(default=attr_factory(dict)) _max_hp = attrib(default=10) _cur_hp = attrib(default=10) @property def max_hp(self): return self._max_hp @max_hp.setter def max_hp(self, value): try: self._max_hp = int(value) except ValueError: # we have an expression for max hp, so roll it self._max_hp = dice.roll_dice_expr(value) # setting max_hp for the first time? we should set cur_hp too if self.cur_hp is None: self.cur_hp = self._max_hp @property def cur_hp(self): return self._cur_hp @cur_hp.setter def cur_hp(self, value): if value > self.max_hp: value = self.max_hp if value < 0: if abs(value) >= self.max_hp: self.conditions = {'dead': inf} value = 0 self._cur_hp = value def set_condition(self, condition, duration=inf): self.conditions[condition] = duration def unset_condition(self, condition): try: self.conditions.pop(condition) except KeyError: # We can probably safely ignore failures here, # since it shouldn't be the end of the world # to remove a condition that isn't in effect. pass def decrement_condition_durations(self): conditions_removed = [] for condition in list(self.conditions): self.conditions[condition] -= 1 if self.conditions[condition] == 0: self.conditions.pop(condition) conditions_removed.append(condition) return conditions_removed @property def status(self): hp_percent = self.cur_hp / self.max_hp if hp_percent >= 0.9: return "healthy" elif hp_percent > 0.5: return "injured" elif hp_percent > 0.1: return "bloodied" elif hp_percent > 0: return "critical" else: return "down" @property def can_cast_spells(self): return hasattr(self, 'features') and \ 'spellcasting' in self.features @property def available_spell_slots(self): if not self.can_cast_spells: return [] slots = self.features['spellcasting']['slots'] slots_used = self.features['spellcasting']['slots_used'] return [str(i+1) for i in range(len(slots)) if slots_used[i] < slots[i]]
class Combatant: name = attrib(default="") race = attrib(default="") initiative_mod = attrib(default=0) ac = attrib(default=0) perception = attrib(default=10) darkvision = attrib(default=0) conditions = attrib(default=attr_factory(dict)) _max_hp = attrib(default=10) _cur_hp = attrib(default=10) @property def max_hp(self): return self._max_hp @max_hp.setter def max_hp(self, value): try: self._max_hp = int(value) except ValueError: # we have an expression for max hp, so roll it self._max_hp = dice.roll_dice_expr(value) # setting max_hp for the first time? we should set cur_hp too if self.cur_hp is None: self.cur_hp = self._max_hp @property def cur_hp(self): return self._cur_hp @cur_hp.setter def cur_hp(self, value): if value > self.max_hp: value = self.max_hp if value < 0: if abs(value) >= self.max_hp: self.conditions = {'dead': inf} value = 0 self._cur_hp = value def set_condition(self, condition, duration=inf): self.conditions[condition] = duration def unset_condition(self, condition): try: self.conditions.pop(condition) except KeyError: # We can probably safely ignore failures here, # since it shouldn't be the end of the world # to remove a condition that isn't in effect. pass def decrement_condition_durations(self): conditions_removed = [] for condition in list(self.conditions): self.conditions[condition] -= 1 if self.conditions[condition] == 0: self.conditions.pop(condition) conditions_removed.append(condition) return conditions_removed
class Combatant: name = attrib(default="") _alias = attrib(default="") @property def alias(self): return self._alias or self.name @alias.setter def alias(self, value): self._alias = value race = attrib(default="") pronouns = attrib(default="") ac = attrib(default=0) image_url = attrib(default="") senses = attrib(default=attr_factory(dict)) conditions = attrib(default=attr_factory(dict)) _max_hp = attrib(default=10) _cur_hp = attrib(default=10) max_hp_override = attrib(default=None) temp_hp = attrib(default=0) _exhaustion = attrib(default=0) @property def max_hp(self): if self.max_hp_override is not None: return self.max_hp_override return self._max_hp @max_hp.setter def max_hp(self, value): try: self._max_hp = int(value) except ValueError: # we have an expression for max hp, so roll it self._max_hp = dice.roll_dice_expr(value) # setting max_hp for the first time? we should set cur_hp too if self.cur_hp is None: self.cur_hp = self._max_hp @property def cur_hp(self): return self._cur_hp + self.temp_hp @cur_hp.setter def cur_hp(self, value): # If we're taking damage, do we have any temporary # hit points to absorb some of it? delta = value - self.cur_hp if delta < 0 and self.temp_hp: delta += self.temp_hp self.temp_hp = max(delta, 0) # Temp hit points absorbed all of the hit; # don't change the actual current hit points if delta > -1: return # Temp hit points blunted some but not all damage, # so figure out what the new current hp should be # set to value = self._cur_hp + delta # Can't set our new current hit points above our max if value > self.max_hp: value = self.max_hp # If we got knocked below 0 hit points... if value < 0: # If we took a lot of damage, it might be an # instant death! if abs(value) >= self.max_hp: self.conditions = {"dead": inf} # Regardless, we always "stop" at 0 hit points value = 0 self._cur_hp = value @property def exhaustion(self): return self._exhaustion @exhaustion.setter def exhaustion(self, value): if value < 0: value = 0 if value > 6: value = 6 self._exhaustion = value # 6 points of exhaustion => death! if value == 6: self.cur_hp = 0 self.conditions = {"dead": inf} def set_condition(self, condition, duration=inf): self.conditions[condition] = duration def unset_condition(self, condition): try: self.conditions.pop(condition) except KeyError: # We can probably safely ignore failures here, # since it shouldn't be the end of the world # to remove a condition that isn't in effect. pass def decrement_condition_durations(self): conditions_removed = [] for condition in list(self.conditions): self.conditions[condition] -= 1 if self.conditions[condition] == 0: self.conditions.pop(condition) conditions_removed.append(condition) return conditions_removed def increment_condition_durations(self): for condition in list(self.conditions): self.conditions[condition] += 1 @property def status(self): hp_percent = self.cur_hp / self.max_hp if hp_percent >= 0.9: return "healthy" elif hp_percent > 0.5: return "injured" elif hp_percent > 0.1: return "bloodied" elif hp_percent > 0: return "critical" else: return "down" @property def can_cast_spells(self): return hasattr(self, "features") and "spellcasting" in self.features @property def available_spell_slots(self): if not self.can_cast_spells: return [] slots = self.features["spellcasting"]["slots"] slots_used = self.features["spellcasting"]["slots_used"] return [str(i + 1) for i in range(len(slots)) if slots_used[i] < slots[i]]