mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33:26 +02:00
Added basic support for Races and some raw text boxes.
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
# Emacs temp files
|
||||
*~
|
||||
|
||||
# Pytest
|
||||
.pytest_cache/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
+53
-19
@@ -1,10 +1,12 @@
|
||||
"""Tools for describing a player character."""
|
||||
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from .stats import Ability, Skill, findattr
|
||||
from .dice import read_dice_str
|
||||
from . import weapons
|
||||
from . import weapons, race
|
||||
from .weapons import Weapon
|
||||
|
||||
dice_re = re.compile('(\d+)d(\d+)')
|
||||
|
||||
@@ -20,9 +22,8 @@ class Character():
|
||||
background = ""
|
||||
level = 1
|
||||
alignment = "Neutral"
|
||||
race = "Human"
|
||||
race = None
|
||||
xp = 0
|
||||
speed = 30 # In feet
|
||||
# Hit points
|
||||
hp_max = 10
|
||||
hit_dice_faces = 2
|
||||
@@ -35,7 +36,7 @@ class Character():
|
||||
charisma = Ability()
|
||||
saving_throw_proficiencies = []
|
||||
skill_proficiencies = tuple()
|
||||
weapon_proficienies = tuple()
|
||||
weapon_proficiencies = tuple()
|
||||
proficiencies_extra = tuple()
|
||||
languages = ""
|
||||
# Skills
|
||||
@@ -58,10 +59,12 @@ class Character():
|
||||
stealth = Skill(ability='dexterity')
|
||||
survival = Skill(ability='wisdom')
|
||||
# Characteristics
|
||||
attacks_and_spellcasting = ""
|
||||
personality_traits = ""
|
||||
ideals = ""
|
||||
bonds = ""
|
||||
flaws = ""
|
||||
features_and_traits = ""
|
||||
# Inventory
|
||||
cp = 0
|
||||
sp = 0
|
||||
@@ -77,6 +80,16 @@ class Character():
|
||||
self.weapons = []
|
||||
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):
|
||||
"""Bulk setting of attributes. Useful for loading a character from a
|
||||
dictionary."""
|
||||
@@ -85,6 +98,9 @@ class Character():
|
||||
# Treat weapons specially
|
||||
for weap in val:
|
||||
self.wield_weapon(weap)
|
||||
elif attr == 'race':
|
||||
MyRace = findattr(race, val)
|
||||
self.race = MyRace()
|
||||
else:
|
||||
if not hasattr(self, attr):
|
||||
warnings.warn(f"Setting unknown character attribute {attr}",
|
||||
@@ -92,10 +108,29 @@ class Character():
|
||||
# Lookup general attributes
|
||||
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
|
||||
def proficiencies_text(self):
|
||||
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
|
||||
for txt in all_proficiencies:
|
||||
if not final_text:
|
||||
@@ -133,8 +168,7 @@ class Character():
|
||||
weapon_.attack_bonus += ability_mod
|
||||
weapon_.bonus_damage += ability_mod
|
||||
# Check for prifiency
|
||||
is_proficient = (weapon_.__class__ in self.weapon_proficienies)
|
||||
if is_proficient:
|
||||
if self.is_proficient(weapon_):
|
||||
weapon_.attack_bonus += self.proficiency_bonus
|
||||
# Save it to the array
|
||||
self.weapons.append(weapon_)
|
||||
@@ -172,7 +206,7 @@ class Barbarian(Character):
|
||||
saving_throw_proficiencies = ('strength', 'constitution')
|
||||
_proficiencies_text = ('light armor', 'medium armor', 'shields',
|
||||
'simple weapons', 'martial weapons')
|
||||
weapon_proficienies = (weapons.simple_weapons + weapons.martial_weapons)
|
||||
weapon_proficiencies = (weapons.simple_weapons + weapons.martial_weapons)
|
||||
|
||||
|
||||
class Bard(Character):
|
||||
@@ -182,7 +216,7 @@ class Bard(Character):
|
||||
_proficiencies_text = (
|
||||
'Light armor', 'simple weapons', 'hand crossbows', 'longswords',
|
||||
'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.simple_weapons)
|
||||
|
||||
@@ -193,7 +227,7 @@ class Cleric(Character):
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
_proficiencies_text = ('light armor', 'medium armor', 'shields',
|
||||
'all simple weapons')
|
||||
weapon_proficienies = weapons.simple_weapons
|
||||
weapon_proficiencies = weapons.simple_weapons
|
||||
|
||||
|
||||
class Druid(Character):
|
||||
@@ -205,7 +239,7 @@ class Druid(Character):
|
||||
'shields (druids will not wear armor or use shields made of metal)',
|
||||
'clubs', 'daggers', 'darts', 'javelins', 'maces', 'quarterstaffs',
|
||||
'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.Scimitar, weapons.Sickle, weapons.Sling, weapons.Spear)
|
||||
|
||||
@@ -215,7 +249,7 @@ class Fighter(Character):
|
||||
hit_dice_faces = 10
|
||||
saving_throw_proficiencies = ('strength', 'constitution')
|
||||
_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):
|
||||
@@ -225,7 +259,7 @@ class Monk(Character):
|
||||
_proficiencies_text = (
|
||||
'simple weapons', 'shortswords',
|
||||
"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):
|
||||
@@ -234,7 +268,7 @@ class Paladin(Character):
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
_proficiencies_text = ('All armor', 'shields', 'simple weapons',
|
||||
'martial weapons')
|
||||
weapon_proficienies = weapons.simple_weapons + weapons.martial_weapons
|
||||
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
|
||||
|
||||
|
||||
class Ranger(Character):
|
||||
@@ -243,7 +277,7 @@ class Ranger(Character):
|
||||
saving_throw_proficiencies = ('strength', 'dexterity')
|
||||
_proficiencies_text = ("light armor", "medium armor", "shields",
|
||||
"simple weapons", "martial weapons")
|
||||
weapon_proficienies = weapons.simple_weapons + weapons.martial_weapons
|
||||
weapon_proficiencies = weapons.simple_weapons + weapons.martial_weapons
|
||||
|
||||
|
||||
class Rogue(Character):
|
||||
@@ -253,7 +287,7 @@ class Rogue(Character):
|
||||
_proficiencies_text = (
|
||||
'light armor', 'simple weapons', 'hand crossbows', 'longswords',
|
||||
'rapiers', 'shortswords', "thieves' tools")
|
||||
weapon_proficienies = (weapons.HandCrossbow, weapons.Longsword,
|
||||
weapon_proficiencies = (weapons.HandCrossbow, weapons.Longsword,
|
||||
weapons.Rapier, weapons.Shortsword) + weapons.simple_weapons
|
||||
|
||||
|
||||
@@ -263,7 +297,7 @@ class Sorceror(Character):
|
||||
saving_throw_proficiencies = ('constitution', 'charisma')
|
||||
_proficiencies_text = ('daggers', 'darts', 'slings',
|
||||
'quarterstaffs', 'light crossbows')
|
||||
weapon_proficienies = (weapons.Dagger, weapons.Dart,
|
||||
weapon_proficiencies = (weapons.Dagger, weapons.Dart,
|
||||
weapons.Sling, weapons.Quarterstaff,
|
||||
weapons.LightCrossbow)
|
||||
|
||||
@@ -273,7 +307,7 @@ class Warlock(Character):
|
||||
hit_dice_faces = 8
|
||||
saving_throw_proficiencies = ('wisdom', 'charisma')
|
||||
_proficiencies_text = ("light Armor", "simple weapons")
|
||||
weapon_proficienies = weapons.simple_weapons
|
||||
weapon_proficiencies = weapons.simple_weapons
|
||||
|
||||
|
||||
class Wizard(Character):
|
||||
@@ -282,6 +316,6 @@ class Wizard(Character):
|
||||
saving_throw_proficiencies = ('intelligence', 'wisdom')
|
||||
_proficiencies_text = ('daggers', 'darts', 'slings',
|
||||
'quarterstaffs', 'light crossbows')
|
||||
weapon_proficienies = (weapons.Dagger, weapons.Dart,
|
||||
weapon_proficiencies = (weapons.Dagger, weapons.Dart,
|
||||
weapons.Sling, weapons.Quarterstaff,
|
||||
weapons.LightCrossbow)
|
||||
|
||||
@@ -61,7 +61,7 @@ def create_fdf(character, fdfname):
|
||||
('ClassLevel', class_level),
|
||||
('Background', character.background),
|
||||
('PlayerName', character.player_name),
|
||||
('Race ', character.race),
|
||||
('Race ', str(character.race)),
|
||||
('Alignment', character.alignment),
|
||||
('XP', character.xp),
|
||||
# Abilities
|
||||
@@ -111,11 +111,13 @@ def create_fdf(character, fdfname):
|
||||
# Hit points
|
||||
('HDTotal', character.hit_dice),
|
||||
('HPMax', character.hp_max),
|
||||
# Personality traits
|
||||
# Personality traits and other features
|
||||
('PersonalityTraits ', text_box(character.personality_traits)),
|
||||
('Ideals', text_box(character.ideals)),
|
||||
('Bonds', text_box(character.bonds)),
|
||||
('Flaws', text_box(character.flaws)),
|
||||
('AttacksSpellcasting', text_box(character.attacks_and_spellcasting)),
|
||||
('Features and Traits', text_box(character.features_and_traits)),
|
||||
# Inventory
|
||||
('CP', character.cp),
|
||||
('SP', character.sp),
|
||||
|
||||
@@ -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
|
||||
@@ -305,6 +305,16 @@ class Shortsword(Weapon):
|
||||
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):
|
||||
name = "Trident"
|
||||
cost = "5 gp"
|
||||
@@ -403,8 +413,10 @@ simple_ranged_weapons = (LightCrossbow, Dart, Shortbow, Sling)
|
||||
simple_weapons = simple_melee_weapons + simple_ranged_weapons
|
||||
|
||||
martial_melee_weapons = (Battleaxe, Flail, Glaive, Greataxe,
|
||||
Greatsword, Halberd, Lance, Longsword, Maul, Morningstar, Pike,
|
||||
Rapier, Scimitar, Shortsword, Trident, WarPick, Warhammer, Whip)
|
||||
Greatsword, Halberd, Lance, Longsword, Maul,
|
||||
Morningstar, Pike, Rapier, Scimitar,
|
||||
Shortsword, ThrowingHammer, Trident, WarPick,
|
||||
Warhammer, Whip)
|
||||
martial_ranged_weapons = (Blowgun, HandCrossbow, HeavyCrossbow,
|
||||
Longbow, Net)
|
||||
martial_weapons = martial_melee_weapons + martial_ranged_weapons
|
||||
|
||||
Binary file not shown.
+39
-1
@@ -7,7 +7,6 @@ level = 3
|
||||
alignment = "Neutral"
|
||||
xp = 1984
|
||||
hp_max = 19
|
||||
speed = 25
|
||||
|
||||
# Ability Scores
|
||||
strength = 8
|
||||
@@ -41,6 +40,45 @@ equipment = (
|
||||
tinderbox, waterskin, crowbar, set of dark common clothes
|
||||
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 don’t need advantage if another
|
||||
enemy of the target is within 5 feet of it and isn’t
|
||||
incapacitated. You can’t 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
|
||||
personality_traits = """I never have a plan, but I’m great at making things up as I go
|
||||
along. Also, the best way to get me to do something is to tell me I
|
||||
|
||||
+38
-3
@@ -2,7 +2,8 @@
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -27,11 +28,14 @@ class TestCharacter(TestCase):
|
||||
char.set_attrs(weapons=['shortsword'])
|
||||
self.assertEqual(len(char.weapons), 1)
|
||||
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):
|
||||
char = Character()
|
||||
char.strength = 14
|
||||
char.weapon_proficienies = [Shortsword]
|
||||
char.weapon_proficiencies = [Shortsword]
|
||||
# Add a weapon
|
||||
char.wield_weapon('shortsword')
|
||||
self.assertEqual(len(char.weapons), 1)
|
||||
@@ -46,7 +50,33 @@ class TestCharacter(TestCase):
|
||||
char.wield_weapon('shortsword')
|
||||
sword = char.weapons[0]
|
||||
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):
|
||||
char = Character()
|
||||
char._proficiencies_text = ('hello', 'world')
|
||||
@@ -54,6 +84,11 @@ class TestCharacter(TestCase):
|
||||
# Check for extra proficiencies
|
||||
char.proficiencies_extra = ("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):
|
||||
char = Character()
|
||||
|
||||
Reference in New Issue
Block a user