mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-05 12:29:39 +02:00
Added ability-specific saving throw bonuses to magic items.
This commit is contained in:
@@ -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 += ", "
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user