mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33:26 +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 .dice import read_dice_str
|
||||
from . import (weapons, race, background, spells, armor, monsters,
|
||||
exceptions, classes, features)
|
||||
exceptions, classes, features, magic_items)
|
||||
from .weapons import Weapon
|
||||
from .armor import Armor, NoArmor, Shield, NoShield
|
||||
|
||||
@@ -345,7 +345,7 @@ class Character():
|
||||
fts |= set(self.race.features_by_level[lvl])
|
||||
if self.background is not None:
|
||||
fts |= set(getattr(self.background, 'features', ()))
|
||||
return tuple(fts)
|
||||
return sorted(tuple(fts), key=(lambda x: x.name))
|
||||
|
||||
@property
|
||||
def custom_features_text(self):
|
||||
@@ -375,7 +375,6 @@ class Character():
|
||||
return (len(self.spellcasting_classes) > 0)
|
||||
|
||||
def spell_slots(self, spell_level):
|
||||
# TODO: Update this for Multiclassing
|
||||
if len(self.spellcasting_classes) == 1:
|
||||
return self.spellcasting_classes[0].spell_slots(spell_level)
|
||||
else:
|
||||
@@ -407,7 +406,7 @@ class Character():
|
||||
spells |= set(c.spells_known) | set(c.spells_prepared)
|
||||
if self.race is not None:
|
||||
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
|
||||
def spells_prepared(self):
|
||||
@@ -418,7 +417,7 @@ class Character():
|
||||
spells |= set(c.spells_prepared)
|
||||
if self.race is not None:
|
||||
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):
|
||||
"""Bulk setting of attributes. Useful for loading a character from a
|
||||
@@ -436,7 +435,12 @@ class Character():
|
||||
if isinstance(val, str):
|
||||
val = [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':
|
||||
self.other_weapon_proficiencies = ()
|
||||
wps = set([findattr(weapons, w) for w in val])
|
||||
@@ -511,7 +515,6 @@ class Character():
|
||||
if self.has_feature(features.NaturalExplorerRevised):
|
||||
ini += '(A)'
|
||||
return ini
|
||||
|
||||
|
||||
def is_proficient(self, weapon: Weapon):
|
||||
"""Is the character proficient with this item?
|
||||
@@ -568,6 +571,14 @@ class Character():
|
||||
s += '\n\n=================\n\n'
|
||||
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):
|
||||
"""Accepts a string or Armor class and replaces the current armor.
|
||||
|
||||
|
||||
@@ -13,17 +13,21 @@
|
||||
|
||||
\maketitle
|
||||
|
||||
\section*{Subclasses}
|
||||
|
||||
[% 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 ]]
|
||||
|
||||
[% endfor %]
|
||||
|
||||
\section*{Features}
|
||||
|
||||
[% for feat in character.features %]
|
||||
|
||||
\section*{[[ feat.name ]]}
|
||||
\subsection*{[[ feat.name ]]}
|
||||
|
||||
\noindent
|
||||
\textbf{Source:} [[ feat.source ]] \\
|
||||
@@ -36,9 +40,11 @@
|
||||
|
||||
[% endfor %]
|
||||
|
||||
\section*{Magic Items}
|
||||
|
||||
[% for mitem in character.magic_items %]
|
||||
|
||||
\section*{[[ mitem.name ]]}
|
||||
\subsection*{[[ mitem.name ]]}
|
||||
|
||||
\noindent
|
||||
\textbf{Requires Attunement:} [[ mitem.requires_attunement ]] \\
|
||||
|
||||
@@ -9,6 +9,15 @@ class MagicItem():
|
||||
needs_implementation = False
|
||||
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):
|
||||
"""
|
||||
@@ -19,3 +28,123 @@ class RingOfProtection(MagicItem):
|
||||
ac_bonus = 1
|
||||
requires_attunement = True
|
||||
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,
|
||||
'GP': character.gp,
|
||||
'PP': character.pp,
|
||||
'Equipment': text_box(character.equipment),
|
||||
'Equipment': text_box(character.magic_items_text + character.equipment),
|
||||
}
|
||||
# Check boxes for proficiencies
|
||||
ST_boxes = {
|
||||
|
||||
@@ -509,7 +509,15 @@ class Musket(Firearm):
|
||||
weight = 10
|
||||
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
|
||||
simple_melee_weapons = (Club, Dagger, Greatclub, Handaxe, Javelin,
|
||||
LightHammer, Mace, Quarterstaff, Sickle, Spear)
|
||||
|
||||
Reference in New Issue
Block a user