Added ability-specific saving throw bonuses to magic items.

This commit is contained in:
Mark Wolfman
2021-08-08 17:26:28 -05:00
parent 700541a35d
commit 7b5aac9247
6 changed files with 109 additions and 21 deletions
+2 -5
View File
@@ -73,8 +73,8 @@ multiclass_spellslots_by_level = {
class Character(Creature): class Character(Creature):
"""A generic player character.""" """A generic player character."""
# Character-specific # Character-specific
name = "Unknown Hero"
player_name = "" player_name = ""
xp = 0 xp = 0
# Extra hit points info, for characters only # Extra hit points info, for characters only
@@ -713,10 +713,7 @@ class Character(Creature):
@property @property
def magic_items_text(self): def magic_items_text(self):
s = ", ".join( s = ", ".join(
[ [f.name for f in sorted(self.magic_items, key=(lambda x: x.name))]
f.name + ("**" if f.needs_implementation else "")
for f in sorted(self.magic_items, key=(lambda x: x.name))
]
) )
if s: if s:
s += ", " s += ", "
+63 -14
View File
@@ -1,3 +1,5 @@
from typing import Optional
from dungeonsheets.content_registry import default_content_registry from dungeonsheets.content_registry import default_content_registry
@@ -5,18 +7,59 @@ default_content_registry.add_module(__name__)
class MagicItem: class MagicItem:
""" """Generic Magic Item. Add description here.
Generic Magic Item. Add description here.
Should be subclassed in order to create magic items.
Saving throw bonuses should be implemented using the various
*st_bonus_<ability>* attributes. *st_bonus_all* will be used if
the ST bonus for the ability in question is not specified on the
subclass.
Attributes
==========
name
Human-readable name for this magic item.
requires_attunement
If true, this magic item requires attunement in order to achieve
the benefits.
rarity
The rarity of this magic item, as a human-readable string.
item_type
The type of item: "armor", "weapon", etc.
ac_bonus
Provides an armor class bonus to any creature equipping this item.
st_bonus_all
A bonus to all savings throws to any creature equipping this item.
st_bonus_strength
A bonus to strength saving throws to any creature equipping this item.
st_bonus_dexterity
A bonus to dexterity saving throws to any creature equipping this item.
st_bonus_constitution
A bonus to constitution saving throws to any creature equipping this item.
st_bonus_intelligence
A bonus to intelligence saving throws to any creature equipping this item.
st_bonus_wisdom
A bonus to wisdom saving throws to any creature equipping this item.
st_bonus_charisma
A bonus to charisma saving throws to any creature equipping this item.
""" """
# Magic-item specific attributes
name = "" name: str = "Generic Magic Item"
ac_bonus = 0 requires_attunement: bool = False
st_bonus = 0 # needs_implementation: bool = False
requires_attunement = False rarity: str = ""
needs_implementation = False item_type: str = ""
rarity = "" # Bonuses
item_type = "" ac_bonus: int = 0
st_bonus_all: int = 0
st_bonus_strength: Optional[int] = None
st_bonus_dexterity: Optional[int] = None
st_bonus_constitution: Optional[int] = None
st_bonus_intelligence: Optional[int] = None
st_bonus_wisdom: Optional[int] = None
st_bonus_charisma: Optional[int] = None
def __init__(self, owner=None): def __init__(self, owner=None):
self.owner = owner self.owner = owner
@@ -25,17 +68,23 @@ class MagicItem:
return self.name return self.name
def __repr__(self): def __repr__(self):
return '"{:s}"'.format(str(self)) return '<MagicItem: "{:s}">'.format(str(self))
def st_bonus(self, ability: Optional[str] = "all"):
bonus = getattr(self, f"st_bonus_{ability}")
if bonus is None:
bonus = self.st_bonus_all
return bonus
class CloakOfProtection(MagicItem): class CloakOfProtection(MagicItem):
"""You gain a +1 bonus to AC and Saving Throws while wearing this """You gain a +1 bonus to AC and Saving Throws while wearing this
cloak. cloak.
""" """
name = "Cloak of Protection" name = "Cloak of Protection"
ac_bonus = 1 ac_bonus = 1
st_bonus = 1 st_bonus_all = 1
requires_attunement = True requires_attunement = True
rarity = "Uncommon" rarity = "Uncommon"
@@ -47,7 +96,7 @@ class RingOfProtection(MagicItem):
""" """
name = "Ring of Protection" name = "Ring of Protection"
ac_bonus = 1 ac_bonus = 1
st_bonus = 1 st_bonus_all = 1
requires_attunement = True requires_attunement = True
rarity = "Rare" rarity = "Rare"
item_type = "Ring" item_type = "Ring"
+2 -1
View File
@@ -78,7 +78,8 @@ class Ability:
saving_throw += actor.proficiency_bonus saving_throw += actor.proficiency_bonus
# Check for bonuses to saving throws from magic items # Check for bonuses to saving throws from magic items
for mitem in actor.magic_items: for mitem in actor.magic_items:
saving_throw += getattr(mitem, "st_bonus", 0) saving_throw += mitem.st_bonus(ability=self.ability_name)
# saving_throw += getattr(mitem, "st_bonus", 0)
# Create the named tuple # Create the named tuple
value = AbilityScore(modifier=modifier, value=score, saving_throw=saving_throw, name=self.ability_name) value = AbilityScore(modifier=modifier, value=score, saving_throw=saving_throw, name=self.ability_name)
return value return value
+40
View File
@@ -0,0 +1,40 @@
import unittest
from dungeonsheets.stats import Ability
from dungeonsheets import magic_items
from dungeonsheets.character import Character
class MyMagicItem(magic_items.MagicItem):
...
class MagicItemTests(unittest.TestCase):
def test_st_bonus_all(self):
char = Character()
my_item = MyMagicItem(owner=char)
char.magic_items = [my_item]
# Test an item that confers no saving throw bonus
bonus = my_item.st_bonus()
self.assertEqual(bonus, 0)
# Now test with positive ST bonus
my_item.st_bonus_all = 2
bonus = my_item.st_bonus()
self.assertEqual(bonus, 2)
def test_st_bonus_by_ability(self):
char = Character(strength=10)
my_item = MyMagicItem(owner=char)
char.magic_items = [my_item]
# Test an item with nonsense ability
with self.assertRaises(AttributeError):
my_item.st_bonus(ability="flight")
# Test that the st_bonus_all is used if the specific ability is not listed
my_item.st_bonus_all = 2
bonus = my_item.st_bonus(ability="strength")
self.assertEqual(bonus, 2)
# Test a specific st_bonus
my_item.st_bonus_strength = 3
bonus = my_item.st_bonus(ability="strength")
self.assertEqual(bonus, 3)
+1
View File
@@ -76,6 +76,7 @@ class EpubOutputTestCase(unittest.TestCase):
def test_character_html_content(self): def test_character_html_content(self):
my_char = self.new_character() my_char = self.new_character()
my_char.magic_items_text
html = make_sheets.make_character_content(character=my_char, html = make_sheets.make_character_content(character=my_char,
content_format="html") content_format="html")
html = "".join(html) html = "".join(html)
+1 -1
View File
@@ -19,7 +19,7 @@ class TestStats(TestCase):
my_class = MyClass() my_class = MyClass()
self.assertEqual(my_class.strength.saving_throw, 4) self.assertEqual(my_class.strength.saving_throw, 4)
# Try it with a magic item # Try it with a magic item
my_class.magic_items.append(magic_items.RingOfProtection) my_class.magic_items.append(magic_items.RingOfProtection())
self.assertEqual(my_class.strength.saving_throw, 5) self.assertEqual(my_class.strength.saving_throw, 5)
def test_modifier(self): def test_modifier(self):