Added basic support for Races and some raw text boxes.

This commit is contained in:
Mark Wolfman
2018-03-28 19:52:59 -05:00
parent e44bb9203b
commit cafde3465d
8 changed files with 278 additions and 27 deletions
+6
View File
@@ -1,3 +1,9 @@
# Emacs temp files
*~
# Pytest
.pytest_cache/
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
+53 -19
View File
@@ -1,10 +1,12 @@
"""Tools for describing a player character.""" """Tools for describing a player character."""
import re import re
import warnings
from .stats import Ability, Skill, findattr from .stats import Ability, Skill, findattr
from .dice import read_dice_str from .dice import read_dice_str
from . import weapons from . import weapons, race
from .weapons import Weapon
dice_re = re.compile('(\d+)d(\d+)') dice_re = re.compile('(\d+)d(\d+)')
@@ -20,9 +22,8 @@ class Character():
background = "" background = ""
level = 1 level = 1
alignment = "Neutral" alignment = "Neutral"
race = "Human" race = None
xp = 0 xp = 0
speed = 30 # In feet
# Hit points # Hit points
hp_max = 10 hp_max = 10
hit_dice_faces = 2 hit_dice_faces = 2
@@ -35,7 +36,7 @@ class Character():
charisma = Ability() charisma = Ability()
saving_throw_proficiencies = [] saving_throw_proficiencies = []
skill_proficiencies = tuple() skill_proficiencies = tuple()
weapon_proficienies = tuple() weapon_proficiencies = tuple()
proficiencies_extra = tuple() proficiencies_extra = tuple()
languages = "" languages = ""
# Skills # Skills
@@ -58,10 +59,12 @@ class Character():
stealth = Skill(ability='dexterity') stealth = Skill(ability='dexterity')
survival = Skill(ability='wisdom') survival = Skill(ability='wisdom')
# Characteristics # Characteristics
attacks_and_spellcasting = ""
personality_traits = "" personality_traits = ""
ideals = "" ideals = ""
bonds = "" bonds = ""
flaws = "" flaws = ""
features_and_traits = ""
# Inventory # Inventory
cp = 0 cp = 0
sp = 0 sp = 0
@@ -77,6 +80,16 @@ class Character():
self.weapons = [] self.weapons = []
self.set_attrs(**attrs) self.set_attrs(**attrs)
def __str__(self):
return self.name
def __repr__(self):
return f"<{self.class_name}: {self.name}>"
@property
def speed(self):
return self.race.speed
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
dictionary.""" dictionary."""
@@ -85,6 +98,9 @@ class Character():
# Treat weapons specially # Treat weapons specially
for weap in val: for weap in val:
self.wield_weapon(weap) self.wield_weapon(weap)
elif attr == 'race':
MyRace = findattr(race, val)
self.race = MyRace()
else: else:
if not hasattr(self, attr): if not hasattr(self, attr):
warnings.warn(f"Setting unknown character attribute {attr}", warnings.warn(f"Setting unknown character attribute {attr}",
@@ -92,10 +108,29 @@ class Character():
# Lookup general attributes # Lookup general attributes
setattr(self, attr, val) setattr(self, attr, val)
def is_proficient(self, weapon: Weapon):
"""Is the character proficient with this item?
Considers class proficiencies and race proficiencies.
Parameters
----------
weapon
The weapon to be tested for proficiency.
"""
all_proficiencies = tuple(self.weapon_proficiencies)
all_proficiencies += tuple(getattr(self.race, 'weapon_proficiencies', tuple()))
is_proficient = any((isinstance(weapon, W) for W in all_proficiencies))
return is_proficient
@property @property
def proficiencies_text(self): def proficiencies_text(self):
final_text = "" final_text = ""
all_proficiencies = (self._proficiencies_text + self.proficiencies_extra) all_proficiencies = self._proficiencies_text
if self.race is not None:
all_proficiencies += self.race.proficiencies_text
all_proficiencies += self.proficiencies_extra
# Create a single string out of all the proficiencies # Create a single string out of all the proficiencies
for txt in all_proficiencies: for txt in all_proficiencies:
if not final_text: if not final_text:
@@ -133,8 +168,7 @@ class Character():
weapon_.attack_bonus += ability_mod weapon_.attack_bonus += ability_mod
weapon_.bonus_damage += ability_mod weapon_.bonus_damage += ability_mod
# Check for prifiency # Check for prifiency
is_proficient = (weapon_.__class__ in self.weapon_proficienies) if self.is_proficient(weapon_):
if is_proficient:
weapon_.attack_bonus += self.proficiency_bonus weapon_.attack_bonus += self.proficiency_bonus
# Save it to the array # Save it to the array
self.weapons.append(weapon_) self.weapons.append(weapon_)
@@ -172,7 +206,7 @@ class Barbarian(Character):
saving_throw_proficiencies = ('strength', 'constitution') saving_throw_proficiencies = ('strength', 'constitution')
_proficiencies_text = ('light armor', 'medium armor', 'shields', _proficiencies_text = ('light armor', 'medium armor', 'shields',
'simple weapons', 'martial weapons') 'simple weapons', 'martial weapons')
weapon_proficienies = (weapons.simple_weapons + weapons.martial_weapons) weapon_proficiencies = (weapons.simple_weapons + weapons.martial_weapons)
class Bard(Character): class Bard(Character):
@@ -182,7 +216,7 @@ class Bard(Character):
_proficiencies_text = ( _proficiencies_text = (
'Light armor', 'simple weapons', 'hand crossbows', 'longswords', 'Light armor', 'simple weapons', 'hand crossbows', 'longswords',
'rapiers', 'shortswords', 'three musical instruments of your choice') 'rapiers', 'shortswords', 'three musical instruments of your choice')
weapon_proficienies = ((weapons.HandCrossbow, weapons.Longsword, weapon_proficiencies = ((weapons.HandCrossbow, weapons.Longsword,
weapons.Rapier, weapons.Shortsword) + weapons.Rapier, weapons.Shortsword) +
weapons.simple_weapons) weapons.simple_weapons)
@@ -193,7 +227,7 @@ class Cleric(Character):
saving_throw_proficiencies = ('wisdom', 'charisma') saving_throw_proficiencies = ('wisdom', 'charisma')
_proficiencies_text = ('light armor', 'medium armor', 'shields', _proficiencies_text = ('light armor', 'medium armor', 'shields',
'all simple weapons') 'all simple weapons')
weapon_proficienies = weapons.simple_weapons weapon_proficiencies = weapons.simple_weapons
class Druid(Character): class Druid(Character):
@@ -205,7 +239,7 @@ class Druid(Character):
'shields (druids will not wear armor or use shields made of metal)', 'shields (druids will not wear armor or use shields made of metal)',
'clubs', 'daggers', 'darts', 'javelins', 'maces', 'quarterstaffs', 'clubs', 'daggers', 'darts', 'javelins', 'maces', 'quarterstaffs',
'scimitars', 'sickles', 'slings', 'spears') 'scimitars', 'sickles', 'slings', 'spears')
weapon_proficienies = (weapons.Club, weapons.Dagger, weapons.Dart, weapon_proficiencies = (weapons.Club, weapons.Dagger, weapons.Dart,
weapons.Javelin, weapons.Mace, weapons.Quarterstaff, weapons.Javelin, weapons.Mace, weapons.Quarterstaff,
weapons.Scimitar, weapons.Sickle, weapons.Sling, weapons.Spear) weapons.Scimitar, weapons.Sickle, weapons.Sling, weapons.Spear)
@@ -215,7 +249,7 @@ class Fighter(Character):
hit_dice_faces = 10 hit_dice_faces = 10
saving_throw_proficiencies = ('strength', 'constitution') saving_throw_proficiencies = ('strength', 'constitution')
_proficiencies_text = ('All armar', 'shields', 'simple weapons', 'martial weapons') _proficiencies_text = ('All armar', 'shields', 'simple weapons', 'martial weapons')
weapon_proficienies = weapons.simple_weapons + weapons.martial_weapons weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
class Monk(Character): class Monk(Character):
@@ -225,7 +259,7 @@ class Monk(Character):
_proficiencies_text = ( _proficiencies_text = (
'simple weapons', 'shortswords', 'simple weapons', 'shortswords',
"one type of artisan's tools or one musical instrument") "one type of artisan's tools or one musical instrument")
weapon_proficienies = (weapons.Shortsword,) + weapons.simple_weapons weapon_proficiencies = (weapons.Shortsword,) + weapons.simple_weapons
class Paladin(Character): class Paladin(Character):
@@ -234,7 +268,7 @@ class Paladin(Character):
saving_throw_proficiencies = ('wisdom', 'charisma') saving_throw_proficiencies = ('wisdom', 'charisma')
_proficiencies_text = ('All armor', 'shields', 'simple weapons', _proficiencies_text = ('All armor', 'shields', 'simple weapons',
'martial weapons') 'martial weapons')
weapon_proficienies = weapons.simple_weapons + weapons.martial_weapons weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
class Ranger(Character): class Ranger(Character):
@@ -243,7 +277,7 @@ class Ranger(Character):
saving_throw_proficiencies = ('strength', 'dexterity') saving_throw_proficiencies = ('strength', 'dexterity')
_proficiencies_text = ("light armor", "medium armor", "shields", _proficiencies_text = ("light armor", "medium armor", "shields",
"simple weapons", "martial weapons") "simple weapons", "martial weapons")
weapon_proficienies = weapons.simple_weapons + weapons.martial_weapons weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
class Rogue(Character): class Rogue(Character):
@@ -253,7 +287,7 @@ class Rogue(Character):
_proficiencies_text = ( _proficiencies_text = (
'light armor', 'simple weapons', 'hand crossbows', 'longswords', 'light armor', 'simple weapons', 'hand crossbows', 'longswords',
'rapiers', 'shortswords', "thieves' tools") 'rapiers', 'shortswords', "thieves' tools")
weapon_proficienies = (weapons.HandCrossbow, weapons.Longsword, weapon_proficiencies = (weapons.HandCrossbow, weapons.Longsword,
weapons.Rapier, weapons.Shortsword) + weapons.simple_weapons weapons.Rapier, weapons.Shortsword) + weapons.simple_weapons
@@ -263,7 +297,7 @@ class Sorceror(Character):
saving_throw_proficiencies = ('constitution', 'charisma') saving_throw_proficiencies = ('constitution', 'charisma')
_proficiencies_text = ('daggers', 'darts', 'slings', _proficiencies_text = ('daggers', 'darts', 'slings',
'quarterstaffs', 'light crossbows') 'quarterstaffs', 'light crossbows')
weapon_proficienies = (weapons.Dagger, weapons.Dart, weapon_proficiencies = (weapons.Dagger, weapons.Dart,
weapons.Sling, weapons.Quarterstaff, weapons.Sling, weapons.Quarterstaff,
weapons.LightCrossbow) weapons.LightCrossbow)
@@ -273,7 +307,7 @@ class Warlock(Character):
hit_dice_faces = 8 hit_dice_faces = 8
saving_throw_proficiencies = ('wisdom', 'charisma') saving_throw_proficiencies = ('wisdom', 'charisma')
_proficiencies_text = ("light Armor", "simple weapons") _proficiencies_text = ("light Armor", "simple weapons")
weapon_proficienies = weapons.simple_weapons weapon_proficiencies = weapons.simple_weapons
class Wizard(Character): class Wizard(Character):
@@ -282,6 +316,6 @@ class Wizard(Character):
saving_throw_proficiencies = ('intelligence', 'wisdom') saving_throw_proficiencies = ('intelligence', 'wisdom')
_proficiencies_text = ('daggers', 'darts', 'slings', _proficiencies_text = ('daggers', 'darts', 'slings',
'quarterstaffs', 'light crossbows') 'quarterstaffs', 'light crossbows')
weapon_proficienies = (weapons.Dagger, weapons.Dart, weapon_proficiencies = (weapons.Dagger, weapons.Dart,
weapons.Sling, weapons.Quarterstaff, weapons.Sling, weapons.Quarterstaff,
weapons.LightCrossbow) weapons.LightCrossbow)
+4 -2
View File
@@ -61,7 +61,7 @@ def create_fdf(character, fdfname):
('ClassLevel', class_level), ('ClassLevel', class_level),
('Background', character.background), ('Background', character.background),
('PlayerName', character.player_name), ('PlayerName', character.player_name),
('Race ', character.race), ('Race ', str(character.race)),
('Alignment', character.alignment), ('Alignment', character.alignment),
('XP', character.xp), ('XP', character.xp),
# Abilities # Abilities
@@ -111,11 +111,13 @@ def create_fdf(character, fdfname):
# Hit points # Hit points
('HDTotal', character.hit_dice), ('HDTotal', character.hit_dice),
('HPMax', character.hp_max), ('HPMax', character.hp_max),
# Personality traits # Personality traits and other features
('PersonalityTraits ', text_box(character.personality_traits)), ('PersonalityTraits ', text_box(character.personality_traits)),
('Ideals', text_box(character.ideals)), ('Ideals', text_box(character.ideals)),
('Bonds', text_box(character.bonds)), ('Bonds', text_box(character.bonds)),
('Flaws', text_box(character.flaws)), ('Flaws', text_box(character.flaws)),
('AttacksSpellcasting', text_box(character.attacks_and_spellcasting)),
('Features and Traits', text_box(character.features_and_traits)),
# Inventory # Inventory
('CP', character.cp), ('CP', character.cp),
('SP', character.sp), ('SP', character.sp),
+124
View File
@@ -0,0 +1,124 @@
from . import weapons
class Race():
name = "Unknown"
size = "medium"
speed = 30
proficiencies_text = tuple()
weapon_proficiences = tuple()
def __str__(self):
return self.name
def __repr__(self):
return f"<self.name>"
# Dwarves
class Dwarf(Race):
name = "Dwarf"
size = "medium"
speed = 25
proficiencies_text = ('battleaxes', 'handaxes', 'throwing hammers', 'warhammers')
weapon_proficiences = (weapons.Battleaxe, weapons.Handaxe,
weapons.ThrowingHammer, weapons.Warhammer)
class HillDwarf(Dwarf):
name = "Hill Dwarf"
class MountainDwarf(Dwarf):
name = "Mountain Dwarf"
# Elves
class Elf(Race):
name = "Elf"
size = "medium"
speed = 30
class HighElf(Elf):
name = "High Elf"
weapon_proficiencies = (weapons.Longsword, weapons.Shortsword,
weapons.Shortbow, weapons.Longbow)
proficiencies_text = ('longswords', 'shortswords', 'shortbows', 'longbows')
class WoodElf(Elf):
name = "Wood Elf"
weapon_proficiencies = (weapons.Longsword, weapons.Shortsword,
weapons.Shortbow, weapons.Longbow)
proficiencies_text = ('longswords', 'shortswords', 'shortbows', 'longbows')
class DarkElf(Elf):
name = "Dark Elf"
weapon_proficiencies = (weapons.Rapier, weapons.Shortsword, weapons.HandCrossbow)
proficiencies_text = ('repiers', 'shortswords', 'hand crossbows')
# Halflings
class Halfling(Race):
name = "Halfling"
size = "small"
speed = 25
class LightfootHalfling(Halfling):
name = "Lightfoot Halfling"
class StoutHalfling(Halfling):
name = "Stout Halfling"
# Humans
class Human(Race):
name = "Human"
size = "medium"
speed = 30
# Dragonborn
class Dragonborn(Race):
name = "Dragonborn"
size = "medium"
speed = 30
# Gnomes
class Gnome(Race):
name = "Gnome"
size = "small"
speed = 25
class ForestGnome(Gnome):
name = "Forest Gnome"
class RockGnome(Gnome):
name = "Rock Gnome"
# Half-elves
class HalfElf(Race):
name = "Half-Elf"
size = "medium"
speed = 30
# Half-Orcs
class HalfOrc(Race):
name = "Half-Orc"
size = "medium"
speed = 30
# Tielflings
class Tiefling(Race):
name = "Tiefling"
size = "medium"
speed = 30
+14 -2
View File
@@ -305,6 +305,16 @@ class Shortsword(Weapon):
ability = 'strength' ability = 'strength'
class ThrowingHammer(Weapon):
name = "Throwing Hammer"
cost = "15 gp"
base_damage = '1d6'
damage_type = "bludgeoning"
weight = 4
properties = "Thrown (range 60/120)"
ability = "strength"
class Trident(Weapon): class Trident(Weapon):
name = "Trident" name = "Trident"
cost = "5 gp" cost = "5 gp"
@@ -403,8 +413,10 @@ simple_ranged_weapons = (LightCrossbow, Dart, Shortbow, Sling)
simple_weapons = simple_melee_weapons + simple_ranged_weapons simple_weapons = simple_melee_weapons + simple_ranged_weapons
martial_melee_weapons = (Battleaxe, Flail, Glaive, Greataxe, martial_melee_weapons = (Battleaxe, Flail, Glaive, Greataxe,
Greatsword, Halberd, Lance, Longsword, Maul, Morningstar, Pike, Greatsword, Halberd, Lance, Longsword, Maul,
Rapier, Scimitar, Shortsword, Trident, WarPick, Warhammer, Whip) Morningstar, Pike, Rapier, Scimitar,
Shortsword, ThrowingHammer, Trident, WarPick,
Warhammer, Whip)
martial_ranged_weapons = (Blowgun, HandCrossbow, HeavyCrossbow, martial_ranged_weapons = (Blowgun, HandCrossbow, HeavyCrossbow,
Longbow, Net) Longbow, Net)
martial_weapons = martial_melee_weapons + martial_ranged_weapons martial_weapons = martial_melee_weapons + martial_ranged_weapons
Binary file not shown.
+39 -1
View File
@@ -7,7 +7,6 @@ level = 3
alignment = "Neutral" alignment = "Neutral"
xp = 1984 xp = 1984
hp_max = 19 hp_max = 19
speed = 25
# Ability Scores # Ability Scores
strength = 8 strength = 8
@@ -41,6 +40,45 @@ equipment = (
tinderbox, waterskin, crowbar, set of dark common clothes tinderbox, waterskin, crowbar, set of dark common clothes
including a hood, pouch.""") including a hood, pouch.""")
attacks_and_spellcasting = (
"""Sneak Attack: Once per turn, when you hit a creature with a
Dexterity-based attack (such as with your shortsword or shortbow)
and you have advantage on the attack roll, you can deal an extra
1d6 damage to your target. You dont need advantage if another
enemy of the target is within 5 feet of it and isnt
incapacitated. You cant deal the extra damage, however, if you
have disadvantage on the attack roll.""")
features_and_traits = (
"""Thieves' Cant: You know thieves cant, a secret mix of dialect,
jargon, and code that allows you to hide messages in seemingly
normal conversation. You also understand a set of secret signs and
symbols used to convey short, simple messages, such as whether an
area is dangerous, whether loot is nearby, or whether the people
in an area are easy marks or will provide a safe house for thieves
on the run.
Lucky: When you roll a natural 1 on an attack roll, ability check,
or saving throw, you can reroll the die and must use the new roll.
Brave: You have advantage on saving throws against being
frightened.
Halfling Nimbleness: You can move through the space of any
creature that is of a size larger than yours.
Naturally Stealthy: You can attempt to hide when you are obscured
by a creature that is at least one size larger than you.
Criminal Contact: You have a contact who acts as your liaison to a
network of other criminals. You know how to get messages to and
from your contact, even over great distances; you know the local
messengers, corrupt caravan masters, and seedy sailors who can
carry messages for you. You can move secret information or stolen
goods through your contact in exchange for money or other
information you seek.""")
# Backstory # Backstory
personality_traits = """I never have a plan, but Im great at making things up as I go personality_traits = """I never have a plan, but Im great at making things up as I go
along. Also, the best way to get me to do something is to tell me I along. Also, the best way to get me to do something is to tell me I
+37 -2
View File
@@ -2,7 +2,8 @@
from unittest import TestCase from unittest import TestCase
from dungeonsheets.character import Character from dungeonsheets import race
from dungeonsheets.character import Character, Wizard
from dungeonsheets.weapons import Weapon, Shortsword from dungeonsheets.weapons import Weapon, Shortsword
@@ -27,11 +28,14 @@ class TestCharacter(TestCase):
char.set_attrs(weapons=['shortsword']) char.set_attrs(weapons=['shortsword'])
self.assertEqual(len(char.weapons), 1) self.assertEqual(len(char.weapons), 1)
self.assertTrue(isinstance(char.weapons[0], Shortsword)) self.assertTrue(isinstance(char.weapons[0], Shortsword))
# Check that race gets set to an object
char.set_attrs(race='high elf')
self.assertIsInstance(char.race, race.HighElf)
def test_wield_weapon(self): def test_wield_weapon(self):
char = Character() char = Character()
char.strength = 14 char.strength = 14
char.weapon_proficienies = [Shortsword] char.weapon_proficiencies = [Shortsword]
# Add a weapon # Add a weapon
char.wield_weapon('shortsword') char.wield_weapon('shortsword')
self.assertEqual(len(char.weapons), 1) self.assertEqual(len(char.weapons), 1)
@@ -46,6 +50,32 @@ class TestCharacter(TestCase):
char.wield_weapon('shortsword') char.wield_weapon('shortsword')
sword = char.weapons[0] sword = char.weapons[0]
self.assertEqual(sword.attack_bonus, 5) # dex + prof self.assertEqual(sword.attack_bonus, 5) # dex + prof
# Check if race weapon proficiencies are considered
char.weapons = []
char.weapon_proficiencies = []
char.race = race.HighElf()
char.wield_weapon('shortsword')
sword = char.weapons[0]
self.assertEqual(sword.attack_bonus, 5)
def test_str(self):
char = Wizard(name="Inara")
self.assertEqual(str(char), 'Inara')
self.assertEqual(repr(char), '<Wizard: Inara>')
def test_is_proficient(self):
char = Character()
char.weapon_proficiencies
sword = Shortsword()
# Check for not-proficient weapon
self.assertFalse(char.is_proficient(sword))
# Check if we're proficient in the weapon
char.weapon_proficiencies = [Shortsword]
self.assertTrue(char.is_proficient(sword))
# Now try it with a racial proficiency
char.weapon_proficiencies = tuple()
char.race = race.HighElf()
self.assertTrue(char.is_proficient(sword))
def test_proficiencies_text(self): def test_proficiencies_text(self):
char = Character() char = Character()
@@ -54,6 +84,11 @@ class TestCharacter(TestCase):
# Check for extra proficiencies # Check for extra proficiencies
char.proficiencies_extra = ("it's", "me") char.proficiencies_extra = ("it's", "me")
self.assertEqual(char.proficiencies_text, "Hello, world, it's, me.") self.assertEqual(char.proficiencies_text, "Hello, world, it's, me.")
# Check that race proficienceis are included
elf = race.HighElf()
char.race = elf
expected = "Hello, world, longswords, shortswords, shortbows, longbows, it's, me."
self.assertEqual(char.proficiencies_text, expected)
def test_proficiency_bonus(self): def test_proficiency_bonus(self):
char = Character() char = Character()