from collections import defaultdict from ..features import Feature, FeatureSelector class CharClass(): """ A generic Character Class (not to be confused with builtin class) """ class_name = "" class_level = 1 hit_dice_faces = None weapon_proficiencies = () _proficiencies_text = () multiclass_weapon_proficiencies = () _multiclass_proficiencies_text = () languages = () class_skill_choices = () num_skill_choices = 2 spellcasting_ability = None spell_slots_by_level = None spells_known = () spells_prepared = () subclass = None subclasses_available = () features_by_level = defaultdict(list) def __init__(self, level, subclass=None, feature_choices=[], **params): self.class_level = level # Instantiate the features self.features_by_level = defaultdict(list) cls = type(self) for i in range(1, 21): fs = [] for f in cls.features_by_level[i]: if issubclass(f, FeatureSelector): fs.append(f(feature_choices=feature_choices)) elif issubclass(f, Feature): fs.append(f()) fs = [f() for f in cls.features_by_level[i]] self.features_by_level[i] = fs for k, v in params.items(): setattr(self, k, v) # Apply subclass self.subclass = self.select_subclass(subclass) if isinstance(self.subclass, SubClass): self.apply_subclass() def select_subclass(self, subclass_str): """ Return a SubClass object corresponding to given string. Intended to be replaced by classes so they can define their own methods of picking subclass by string. """ if subclass_str in ['', 'None', 'none', None]: return None for sc in self.subclasses_available: if subclass_str.lower() in sc.name.lower(): return sc(level=self.class_level) return None def apply_subclass(self): if self.subclass is None: return for i in range(1, 21): self.features_by_level[i] += ([f() for f in self.subclass.features_by_level[i]]) for attr in ('weapon_proficiencies', '_proficiencies_text', 'spells_known', 'spells_prepared'): new_list = getattr(self, attr, ()) + getattr(self.subclass, attr, ()) setattr(self, attr, new_list) # All subclass proficiencies transfer, regardless of if this is primary class self.multiclass_weapon_proficiencies += (self.subclass.weapon_proficiencies) self._multiclass_proficiencies_text += (self._proficiencies_text) self.spellcasting_ability = (self.spellcasting_ability or self.subclass.spellcasting_ability) self.spell_slots_by_level = (self.spell_slots_by_level or self.subclass.spell_slots_by_level) @property def features(self): features = () for lvl in range(1, self.class_level+1): features += tuple(self.features_by_level[lvl]) return features @property def is_spellcaster(self): result = (self.spellcasting_ability is not None) return result def spell_slots(self, spell_level): """How many spells slots are available for this spell level.""" if self.spell_slots_by_level is None: return 0 else: return self.spell_slots_by_level[self.class_level][spell_level] class SubClass(): """ A generic subclass object. Add more detail in the __doc__ attribute. """ name = '' features_by_level = defaultdict(list) weapon_proficiencies = () _proficiencies_text = () spellcasting_ability = None spell_slots_by_level = None spells_known = () spells_prepared = () def __init__(self, level): self.__doc__ = self.__doc__ or SubClass.__doc__ self.class_level = level def __str__(self): return self.name def __repr__(self): return "\"{:s}\"".format(self.name)