mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-05 12:29:39 +02:00
Added magic items
This commit is contained in:
@@ -12,7 +12,7 @@ import subprocess
|
|||||||
from .stats import Ability, Skill, findattr, ArmorClass, Speed
|
from .stats import Ability, Skill, findattr, ArmorClass, Speed
|
||||||
from .dice import read_dice_str
|
from .dice import read_dice_str
|
||||||
from . import (weapons, race, background, spells, armor, monsters,
|
from . import (weapons, race, background, spells, armor, monsters,
|
||||||
exceptions, classes, features)
|
exceptions, classes, features, magic_items)
|
||||||
from .weapons import Weapon
|
from .weapons import Weapon
|
||||||
from .armor import Armor, NoArmor, Shield, NoShield
|
from .armor import Armor, NoArmor, Shield, NoShield
|
||||||
|
|
||||||
@@ -345,7 +345,7 @@ class Character():
|
|||||||
fts |= set(self.race.features_by_level[lvl])
|
fts |= set(self.race.features_by_level[lvl])
|
||||||
if self.background is not None:
|
if self.background is not None:
|
||||||
fts |= set(getattr(self.background, 'features', ()))
|
fts |= set(getattr(self.background, 'features', ()))
|
||||||
return tuple(fts)
|
return sorted(tuple(fts), key=(lambda x: x.name))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_features_text(self):
|
def custom_features_text(self):
|
||||||
@@ -375,7 +375,6 @@ class Character():
|
|||||||
return (len(self.spellcasting_classes) > 0)
|
return (len(self.spellcasting_classes) > 0)
|
||||||
|
|
||||||
def spell_slots(self, spell_level):
|
def spell_slots(self, spell_level):
|
||||||
# TODO: Update this for Multiclassing
|
|
||||||
if len(self.spellcasting_classes) == 1:
|
if len(self.spellcasting_classes) == 1:
|
||||||
return self.spellcasting_classes[0].spell_slots(spell_level)
|
return self.spellcasting_classes[0].spell_slots(spell_level)
|
||||||
else:
|
else:
|
||||||
@@ -407,7 +406,7 @@ class Character():
|
|||||||
spells |= set(c.spells_known) | set(c.spells_prepared)
|
spells |= set(c.spells_known) | set(c.spells_prepared)
|
||||||
if self.race is not None:
|
if self.race is not None:
|
||||||
spells |= set(self.race.spells_known) | set(self.race.spells_prepared)
|
spells |= set(self.race.spells_known) | set(self.race.spells_prepared)
|
||||||
return tuple(spells)
|
return sorted(tuple(spells), key=(lambda x: (x.level, x.name)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spells_prepared(self):
|
def spells_prepared(self):
|
||||||
@@ -418,7 +417,7 @@ class Character():
|
|||||||
spells |= set(c.spells_prepared)
|
spells |= set(c.spells_prepared)
|
||||||
if self.race is not None:
|
if self.race is not None:
|
||||||
spells |= set(self.race.spells_prepared)
|
spells |= set(self.race.spells_prepared)
|
||||||
return tuple(spells)
|
return sorted(tuple(spells), key=(lambda x: (x.level, x.name)))
|
||||||
|
|
||||||
def set_attrs(self, **attrs):
|
def set_attrs(self, **attrs):
|
||||||
"""Bulk setting of attributes. Useful for loading a character from a
|
"""Bulk setting of attributes. Useful for loading a character from a
|
||||||
@@ -436,7 +435,12 @@ class Character():
|
|||||||
if isinstance(val, str):
|
if isinstance(val, str):
|
||||||
val = [val]
|
val = [val]
|
||||||
for mitem in val:
|
for mitem in val:
|
||||||
self.magic_items.append(mitem(owner=self))
|
try:
|
||||||
|
self.magic_items.append(findattr(magic_items, mitem)(owner=self))
|
||||||
|
except (AttributeError):
|
||||||
|
msg = (f'Magic Item "{mitem}" not defined. '
|
||||||
|
f'Please add it to ``magic_items.py``')
|
||||||
|
warnings.warn(msg)
|
||||||
elif attr == 'weapon_proficiencies':
|
elif attr == 'weapon_proficiencies':
|
||||||
self.other_weapon_proficiencies = ()
|
self.other_weapon_proficiencies = ()
|
||||||
wps = set([findattr(weapons, w) for w in val])
|
wps = set([findattr(weapons, w) for w in val])
|
||||||
@@ -511,7 +515,6 @@ class Character():
|
|||||||
if self.has_feature(features.NaturalExplorerRevised):
|
if self.has_feature(features.NaturalExplorerRevised):
|
||||||
ini += '(A)'
|
ini += '(A)'
|
||||||
return ini
|
return ini
|
||||||
|
|
||||||
|
|
||||||
def is_proficient(self, weapon: Weapon):
|
def is_proficient(self, weapon: Weapon):
|
||||||
"""Is the character proficient with this item?
|
"""Is the character proficient with this item?
|
||||||
@@ -568,6 +571,14 @@ class Character():
|
|||||||
s += '\n\n=================\n\n'
|
s += '\n\n=================\n\n'
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@property
|
||||||
|
def magic_items_text(self):
|
||||||
|
s = ', '.join([f.name + ("**" if f.needs_implementation else "")
|
||||||
|
for f in sorted(self.magic_items, key=(lambda x: x.name))])
|
||||||
|
if s:
|
||||||
|
s += ', '
|
||||||
|
return s
|
||||||
|
|
||||||
def wear_armor(self, new_armor):
|
def wear_armor(self, new_armor):
|
||||||
"""Accepts a string or Armor class and replaces the current armor.
|
"""Accepts a string or Armor class and replaces the current armor.
|
||||||
|
|
||||||
|
|||||||
@@ -13,17 +13,21 @@
|
|||||||
|
|
||||||
\maketitle
|
\maketitle
|
||||||
|
|
||||||
|
\section*{Subclasses}
|
||||||
|
|
||||||
[% for sc in character.subclasses if sc not in ['', None, 'None', 'none']%]
|
[% for sc in character.subclasses if sc not in ['', None, 'None', 'none']%]
|
||||||
|
|
||||||
\section*{Subclass: [[ sc.name ]]}
|
\subsection*{Subclass: [[ sc.name ]]}
|
||||||
|
|
||||||
[[ sc.__doc__|rst_to_latex ]]
|
[[ sc.__doc__|rst_to_latex ]]
|
||||||
|
|
||||||
[% endfor %]
|
[% endfor %]
|
||||||
|
|
||||||
|
\section*{Features}
|
||||||
|
|
||||||
[% for feat in character.features %]
|
[% for feat in character.features %]
|
||||||
|
|
||||||
\section*{[[ feat.name ]]}
|
\subsection*{[[ feat.name ]]}
|
||||||
|
|
||||||
\noindent
|
\noindent
|
||||||
\textbf{Source:} [[ feat.source ]] \\
|
\textbf{Source:} [[ feat.source ]] \\
|
||||||
@@ -36,9 +40,11 @@
|
|||||||
|
|
||||||
[% endfor %]
|
[% endfor %]
|
||||||
|
|
||||||
|
\section*{Magic Items}
|
||||||
|
|
||||||
[% for mitem in character.magic_items %]
|
[% for mitem in character.magic_items %]
|
||||||
|
|
||||||
\section*{[[ mitem.name ]]}
|
\subsection*{[[ mitem.name ]]}
|
||||||
|
|
||||||
\noindent
|
\noindent
|
||||||
\textbf{Requires Attunement:} [[ mitem.requires_attunement ]] \\
|
\textbf{Requires Attunement:} [[ mitem.requires_attunement ]] \\
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ class MagicItem():
|
|||||||
needs_implementation = False
|
needs_implementation = False
|
||||||
rarity = ''
|
rarity = ''
|
||||||
|
|
||||||
|
def __init__(self, owner=None):
|
||||||
|
self.owner = owner
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '\"{:s}\"'.format(str(self))
|
||||||
|
|
||||||
|
|
||||||
class RingOfProtection(MagicItem):
|
class RingOfProtection(MagicItem):
|
||||||
"""
|
"""
|
||||||
@@ -19,3 +28,123 @@ class RingOfProtection(MagicItem):
|
|||||||
ac_bonus = 1
|
ac_bonus = 1
|
||||||
requires_attunement = True
|
requires_attunement = True
|
||||||
rarity = 'Rare'
|
rarity = 'Rare'
|
||||||
|
|
||||||
|
|
||||||
|
class DecanterOfEndlessWater(MagicItem):
|
||||||
|
"""This stoppered flask sloshes when shaken, as if it contains water. The
|
||||||
|
decanter weighs 2 pounds.
|
||||||
|
|
||||||
|
You can use an action to remove the stopper and speak one of three command
|
||||||
|
words, whereupon an amount of fresh water or salt water (your choice) pours
|
||||||
|
out of the flask. The water stops pouring out at the start of your next
|
||||||
|
turn. Choose from the following options:
|
||||||
|
|
||||||
|
--"Stream" produces 1 gallon of water.
|
||||||
|
|
||||||
|
--"Fountain" produces 5 gallons of water.
|
||||||
|
|
||||||
|
--"Geyser" produces 30 gallons of water that gushes forth in a geyser 30
|
||||||
|
feet long and 1 foot wide. As a bonus action while holding the decanter,
|
||||||
|
you can aim the geyser at a creature you can see within 30 feet of you. The
|
||||||
|
target must succeed on a DC 13 Strength saving throw or take 1d4
|
||||||
|
bludgeoning damage and fall prone. Instead of a creature, you can target an
|
||||||
|
object that isn't being worn or carried and that weighs no more than 200
|
||||||
|
pounds. The object is either knocked over or pushed up to 15 feet away from
|
||||||
|
you.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Decanter of Endless Water"
|
||||||
|
rarity = 'Uncommon'
|
||||||
|
|
||||||
|
|
||||||
|
class ToothOfAnimalFriendship(MagicItem):
|
||||||
|
"""While holding this wolf's tooth, you can expend it's one charge to cast
|
||||||
|
Animal Friendship (DC 13) or Speak With Animals.
|
||||||
|
|
||||||
|
The charge resets at the next Dawn.
|
||||||
|
"""
|
||||||
|
name = "Tooth of Animal Friendship"
|
||||||
|
rarity = 'Uncommon'
|
||||||
|
|
||||||
|
|
||||||
|
class CloakOfBillowing(MagicItem):
|
||||||
|
"""While wearing this cloak, you can use a bonus action to make it billow
|
||||||
|
dramatically.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Cloak of Billowing"
|
||||||
|
rarity = "Common"
|
||||||
|
|
||||||
|
|
||||||
|
class CapeOfTheMountebank(MagicItem):
|
||||||
|
"""This cape smells faintly of brimstone. While wearing it, you can use it to
|
||||||
|
cast the Dimension Door spell as an action. This property of the cape can't
|
||||||
|
be used again until the next dawn.
|
||||||
|
|
||||||
|
When you disappear, you leave behind a cloud of smoke, and you appear in a
|
||||||
|
similar cloud of smoke at your destination. The smoke lightly obscures the
|
||||||
|
space you left and the space you appear in, and it dissipates at the end of
|
||||||
|
your next turn. A light or stronger wind disperses the smoke.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Cape of the Mountebank"
|
||||||
|
rarity = "Rare"
|
||||||
|
|
||||||
|
|
||||||
|
class EyesOfCharming(MagicItem):
|
||||||
|
"""These Crystal lenses fit over the eyes. They have 3 Charges. While wearing
|
||||||
|
them, you can expend 1 charge as an action to cast the Charm Person spell
|
||||||
|
(save DC 13) on a humanoid within 30 feet of you, provided that you and the
|
||||||
|
target can see each other. The lenses regain all expended Charges daily at
|
||||||
|
dawn.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Eyes of Charming"
|
||||||
|
rarity = "Uncommon"
|
||||||
|
requires_attunement = True
|
||||||
|
|
||||||
|
|
||||||
|
class CharlattansDie(MagicItem):
|
||||||
|
"""Whenever you roll this six—sided die, you can control which number it
|
||||||
|
rolls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Charlattan's Die"
|
||||||
|
rarity = "Common"
|
||||||
|
|
||||||
|
|
||||||
|
class PipeOfSmokeMonsters(MagicItem):
|
||||||
|
"""While smoking this pipe, you can use an action to ex- hale a puff of smoke
|
||||||
|
that takes the form of a single crea— ture, such as a dragon, a flumph, or
|
||||||
|
a froghemoth. The form must be small enough to fit in a 1-foot cube and
|
||||||
|
loses its shape after a few seconds, becoming an ordi- nary puff of smoke.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'Pipe of Smoke Monsters'
|
||||||
|
rarity = "Common"
|
||||||
|
|
||||||
|
|
||||||
|
class CoinsOfCommunication(MagicItem):
|
||||||
|
"""This set of multiple coins are virtually indistinguishable from regular Gold
|
||||||
|
Pieces, but are connected by magic. Once per day, a holder of any of any
|
||||||
|
coin can whisper a single word into it, after which all coins will
|
||||||
|
immediately vibrate and the word will replace a word in the traditional
|
||||||
|
Kings Message imprinted on the coin. This ability cannot be used again by
|
||||||
|
the holder of any of the coins until the following dawn.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Coins of Communication"
|
||||||
|
rarity = "Uncommon"
|
||||||
|
|
||||||
|
|
||||||
|
class FlameTongue(MagicItem):
|
||||||
|
"""You can use a Bonus Action to speak this magic sword's Command Word, causing
|
||||||
|
flames to erupt from the blade. These flames shed bright light in a 40-foot
|
||||||
|
radius and dim light for an additional 40 feet. While the sword is ablaze,
|
||||||
|
it deals an extra 2d6 fire damage to any target it hits. The flames last
|
||||||
|
until you use a Bonus Action to speak the Command Word again or until you
|
||||||
|
drop or sheathe the sword
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "Flame Tongue"
|
||||||
|
rarity = "Rare"
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ def create_character_pdf(character, basename, flatten=False):
|
|||||||
'EP': character.ep,
|
'EP': character.ep,
|
||||||
'GP': character.gp,
|
'GP': character.gp,
|
||||||
'PP': character.pp,
|
'PP': character.pp,
|
||||||
'Equipment': text_box(character.equipment),
|
'Equipment': text_box(character.magic_items_text + character.equipment),
|
||||||
}
|
}
|
||||||
# Check boxes for proficiencies
|
# Check boxes for proficiencies
|
||||||
ST_boxes = {
|
ST_boxes = {
|
||||||
|
|||||||
@@ -509,7 +509,15 @@ class Musket(Firearm):
|
|||||||
weight = 10
|
weight = 10
|
||||||
properties = "Ammunition (range 120/480), Two-Handed, Reload 1, Misfire 2"
|
properties = "Ammunition (range 120/480), Two-Handed, Reload 1, Misfire 2"
|
||||||
|
|
||||||
|
|
||||||
|
# Magic Items
|
||||||
|
class FlameTongue(Greatsword):
|
||||||
|
name = "Flame Tongue +1"
|
||||||
|
magic_bonus = 1
|
||||||
|
base_damage = "4d6"
|
||||||
|
damage_type = 'f'
|
||||||
|
|
||||||
|
|
||||||
# Some lists of weapons for easy proficiency resolution
|
# Some lists of weapons for easy proficiency resolution
|
||||||
simple_melee_weapons = (Club, Dagger, Greatclub, Handaxe, Javelin,
|
simple_melee_weapons = (Club, Dagger, Greatclub, Handaxe, Javelin,
|
||||||
LightHammer, Mace, Quarterstaff, Sickle, Spear)
|
LightHammer, Mace, Quarterstaff, Sickle, Spear)
|
||||||
|
|||||||
Reference in New Issue
Block a user