diff --git a/dungeonsheets/character.py b/dungeonsheets/character.py index 0925ff2..a076112 100644 --- a/dungeonsheets/character.py +++ b/dungeonsheets/character.py @@ -467,11 +467,11 @@ class Character(Creature): @property def spellcasting_classes_excluding_warlock(self): return [c for c in self.spellcasting_classes if not type(c) == classes.Warlock] - + @property def is_spellcaster(self): return len(self.spellcasting_classes) > 0 - + def spell_slots(self, spell_level): warlock_slots = 0 for c in self.spellcasting_classes: diff --git a/dungeonsheets/content.py b/dungeonsheets/content.py index 012054e..6f79943 100644 --- a/dungeonsheets/content.py +++ b/dungeonsheets/content.py @@ -105,7 +105,6 @@ class Content(ABC): attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"} Mechanic = type(class_name, (SuperClass,), attrs) return Mechanic - class Creature(Content): @@ -213,3 +212,7 @@ class Creature(Content): self.medicine, self.nature, self.perception, self.performance, self.persuasion, self.religion, self.sleight_of_hand, self.stealth, self.survival] + + @property + def is_spellcaster(self): + raise NotImplementedError diff --git a/dungeonsheets/forms.py b/dungeonsheets/forms.py index 8f89b43..35caa22 100644 --- a/dungeonsheets/forms.py +++ b/dungeonsheets/forms.py @@ -3,7 +3,7 @@ import re from jinja2 import Environment, PackageLoader -from dungeonsheets.stats import mod_str, ability_mod_str, stat_abbreviation +from dungeonsheets.stats import mod_str, ability_mod_str, stat_abbreviation, str_to_list from dungeonsheets.encounter import xp_thresholds from dungeonsheets.monsters import challenge_rating_to_xp @@ -37,4 +37,5 @@ def jinja_environment(): jinja_env.filters["stat_abbreviation"] = stat_abbreviation jinja_env.filters["challenge_rating_to_xp"] = challenge_rating_to_xp jinja_env.filters["xp_thresholds"] = xp_thresholds + jinja_env.filters["str_to_list"] = str_to_list return jinja_env diff --git a/dungeonsheets/forms/character_sheet_template.html b/dungeonsheets/forms/character_sheet_template.html index d327390..0ba51a3 100644 --- a/dungeonsheets/forms/character_sheet_template.html +++ b/dungeonsheets/forms/character_sheet_template.html @@ -49,7 +49,7 @@
Current Hit Points
[[ character.hp_current ]] 
Temporary Hit Points
-
[% if character.hp_temp > 0 %][[ character.hp_temp ]][% endif %] 
+
[[ character | selectattr('hp_temp', 0) ]] 
Hit Dice Total
[[ character.hit_dice ]]
@@ -126,12 +126,10 @@
  • [[ character.ep ]] EP
  • [[ character.gp ]] GP
  • [[ character.pp ]] PP
  • - [% set inventory_items = character.magic_items_text.split(',') %] - [% for item in inventory_items %] + [% for item in character | str_to_list('magic_items_text') %]
  • [[ item ]]
  • [% endfor %] - [% set inventory_items = character.equipment.split(',') %] - [% for item in inventory_items %] + [% for item in character | str_to_list('equipment') %]
  • [[ item ]]
  • [% endfor %] diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py index acd10b4..bc789dc 100644 --- a/dungeonsheets/make_sheets.py +++ b/dungeonsheets/make_sheets.py @@ -387,12 +387,12 @@ def make_character_content( content_suffix=content_format, use_dnd_decorations=fancy_decorations)) # Create a list of subcasses, features, spells, etc - if character.subclasses: + if len(getattr(character, 'subclasses', [])) > 0: content.append(create_subclasses_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) ) - if character.features: + if len(getattr(character, 'features', [])) > 0: content.append( create_features_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) ) diff --git a/dungeonsheets/monsters/monsters.py b/dungeonsheets/monsters/monsters.py index 084d6d2..339808c 100644 --- a/dungeonsheets/monsters/monsters.py +++ b/dungeonsheets/monsters/monsters.py @@ -106,3 +106,7 @@ class Monster(Creature, metaclass=SpellFactory): def has_feature(self, *args, **kwargs): return False + + @property + def is_spellcaster(self): + return len(self.spells) > 0 diff --git a/dungeonsheets/stats.py b/dungeonsheets/stats.py index 67fc999..d1e6087 100644 --- a/dungeonsheets/stats.py +++ b/dungeonsheets/stats.py @@ -32,7 +32,43 @@ log = logging.getLogger(__name__) def mod_str(modifier): """Converts a modifier to a string, eg 2 -> '+2'.""" - return "{:+d}".format(modifier) + try: + s = "{:+d}".format(modifier) + except TypeError: + s = "N/A" + return s + + +def str_to_list(obj, attr: str, sep: str = ","): + """Find the string *obj.attr* if it exists, and returns it as a + list. + + Parameters + ========== + obj + Any python object, presumably with an attribute *attr*. + attr + The name of the attribute to look up. + sep + The separator to use for splitting the string. + + Returns + ======= + items + A sequence of the items retrieved from *obj.attr* string. If + *obj.attr* does not exist, an empty list will be returned. If + *obj.attr* is not a string, then it will be presumed to be a + sequence already and returned as is. + + """ + string = getattr(obj, attr, []) + if hasattr(string, "split"): + # Convert the string to a list + lst = [s.strip() for s in string.split(sep)] + else: + # A non-string attribute was given, so just return it + lst = string + return lst def ability_mod_str(character, ability): diff --git a/examples/gm-campaign-notes.py b/examples/gm-campaign-notes.py index 9ef6470..0d5de0e 100644 --- a/examples/gm-campaign-notes.py +++ b/examples/gm-campaign-notes.py @@ -5,7 +5,7 @@ monsters, etc. """ -from dungeonsheets import mechanics, monsters +from dungeonsheets import mechanics, monsters as _monsters dungeonsheets_version = "0.14.0" @@ -18,6 +18,6 @@ haryk_omanie = mechanics.Character( name="Haryk Omanie", ) -party = ["rogue1.py", "paladin2.py", haryk_omanie, monsters.Veteran] +party = ["rogue1.py", "paladin2.py", haryk_omanie, _monsters.Veteran] random_tables = ["conjure animals"] diff --git a/tests/test_stats.py b/tests/test_stats.py index 84a0e17..e9541e6 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -8,6 +8,19 @@ class TestStats(TestCase): self.assertEqual(stats.mod_str(-3), "-3") self.assertEqual(stats.mod_str(0), "+0") self.assertEqual(stats.mod_str(2), "+2") + self.assertEqual(stats.mod_str(None), "N/A") + + def test_str_to_list(self): + char = character.Character(equipment="a, b, c") + # Regular string + self.assertEqual(stats.str_to_list(char, "equipment"), ["a", "b", "c"]) + # Alternate separator + char.equipment = "a; b; c" + self.assertEqual(stats.str_to_list(char, "equipment", sep=";"), ["a", "b", "c"]) + # No attribute + self.assertEqual(stats.str_to_list(char, "inventory"), []) + # Not a string + self.assertEqual(stats.str_to_list(char, "hp_max"), char.hp_max) def test_saving_throw(self): # Try it with an ST proficiency