From cafde3465d9e3117487a31b23969060abb199610 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Wed, 28 Mar 2018 19:52:59 -0500 Subject: [PATCH] Added basic support for Races and some raw text boxes. --- .gitignore | 6 ++ dungeonsheets/character.py | 72 ++++++++++++++------ dungeonsheets/make_sheets.py | 6 +- dungeonsheets/race.py | 124 +++++++++++++++++++++++++++++++++++ dungeonsheets/weapons.py | 16 ++++- examples/rogue.pdf | Bin 256440 -> 261464 bytes examples/rogue.py | 40 ++++++++++- tests/test_character.py | 41 +++++++++++- 8 files changed, 278 insertions(+), 27 deletions(-) create mode 100644 dungeonsheets/race.py diff --git a/.gitignore b/.gitignore index 7bbc71c..9e270f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# Emacs temp files +*~ + +# Pytest +.pytest_cache/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/dungeonsheets/character.py b/dungeonsheets/character.py index dff7520..b2bda59 100644 --- a/dungeonsheets/character.py +++ b/dungeonsheets/character.py @@ -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) diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py index 747a63e..dbddd8e 100644 --- a/dungeonsheets/make_sheets.py +++ b/dungeonsheets/make_sheets.py @@ -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), diff --git a/dungeonsheets/race.py b/dungeonsheets/race.py new file mode 100644 index 0000000..f10c58e --- /dev/null +++ b/dungeonsheets/race.py @@ -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"" + + +# 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 diff --git a/dungeonsheets/weapons.py b/dungeonsheets/weapons.py index d17e93e..30d53f5 100644 --- a/dungeonsheets/weapons.py +++ b/dungeonsheets/weapons.py @@ -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 diff --git a/examples/rogue.pdf b/examples/rogue.pdf index a6b4b5249a9193aae33b762f3ca7b636fcd7cf07..c5127984ef09c51fb7bedfa43719088770fdec3f 100644 GIT binary patch delta 40891 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 delta 36876 zcmb__d6*T&)%R1kWtlt7KEpCIGIs`s9p?7Ey|V9!?1%m*GQh z{^#e}#sB*A?I&!Wv&`EzdVA6P>6>1jwwW!Q{`1mDJC>}Tv}Eq?!y_*H=Gl>VY<1W4 z?{pO{>s+;dM`~Obx_I={bo;0^^zy3pw9lBewEfuU=q1Ojp(QhW)7y`|p6(pGhCY4L z^>q69?zG>yHS0S~XdtwCyadjh)Q$F^xR!3NUPb>kxd&Y_X${@bc_nQq)%`M(DQw+!_4{9W$pJ-8}Qw z^=(fcOlYUs#dQCw0-8Fdfi}f%C*H~{T=6ZH^?m3ZYAkR|L{P8VR|OrdQmOyaM45}|GMI$ zEPek%n|^wcy?#=2x)m|kr!H=4)rI%NdrS@-m~0xCnqhjfOVe{i*QV#vyMH&Ro9FSL z$)?G3m(E*s?zE-z=A}%}m#`5F=Pp||VeXQY$%c+MD(R{fMeDm=^-*hDcX1Kzw4$7t ztazN!p_QPS9XQsgJ$n9W%P^Lokz@FLUQ<)1FED1&Ll4)`wJRQ=ovtb>oOs^ZKuzPN zP3K-$Pq$oG)Jeij(*iUg({%B-OYgm|k)E)k*yEiCZ+w?`J}cRDT&ii<(sSpZx^Nz* z0qVs>lY<8*=Pf!-umDZi!Er_f?Y6SCv+2tj<2`RVdhyb;MKj;z-O-`9w`V_$Gum<7 zYSWjT&~fSHm33I;YPxgfN|L5$uF51FpRQeXG=4n3s;hxf=zFUk!6KcxDhn8YW8zIW z`q-)>T6gXv$_^q|MxEQdy?iHt`}(d6-) zOM#xEAKZLwj*{_czF~DIdNs!1z>3#DwEC_Dy>v~A&i|vBO2W?2M^;zS@--P+e+#Fv zgdWB#pb*Xlo1VR<2m|({x8ccoW%I#2+V)o7amR#{M*Hscx3@kc<~Me24c)u8m@W;T zx329krm}yn|3X%y_Fylx$%Vg6+ zh%*@>AgN3y_-zW=NM#_QT*xzNDfAgLXlDlf*};1!c)Dr2`0j2=C+NoZ=^c0f--4`E zl}tA1#THVU%GklEX7D=`Jk0hhbdyn^!El-X^$hy`AMXn@nQ%H;TKDHVI`Pk`6N3q7 z{y#m(N)&wXYC3$);haE?vxRRVw59^oMm96bGF4WFPeGzf7X9{N*^>bWc^gRK^SV z3wJT}p5C1VS7GkXU3%WB!p2Wsy!c#LN{jyPo@pe*KCdTr^oM&o^%R{Zn~s^c@cfkM zBH47(+>xiGGSZ?ao2Jd5x)=>JwEw+5U}8?aw-?E>K4%$q^nrWFlPo=S?;c{&!+-5d zTmQ9`{&;T(+V`(p>5lvQ(Vq90(yi-?=;-@rV}i{Om_`ORa?3*v>wmg`GC|XA4|k|ZW*6mu1<4xXz*B1ZhA<1;Ym2^-`M{wnlA1jJ2slgl$ zhTivRJzez}7s3FC9(c5p4tlhdmOs`WSUn#bN$-8MoHjpJT3{*t70`b?RzauitOnjc zAFE>zwj-SKiy!Co-t%}Jee*Fs(1FKBqWkRQ^#y6AgIeOz$|o9u)BlN)_`CUu`T|G6 zDWESrQBKS8|DQZj1o)*TzdS|e43X$%~`SG=>|G$LjiG_Sx%~H>u0Ly-Oq3)KJ`p} z;S9@@cEj>$=S_SGzI&z%7H;lyb=2QbNn39$p(kxjhK-kRsG#FElo=KRya1(|+pUO@ zn{JNpVnE>9^miNj&>NnwDC068!L>YAb~NF<^EcMhKW!XC_dFZ&4Lm?o_~vKp=*(w3 z(dExo&}*K}K!~1rwo6_(1p>v!x9^@5KuG*rqZ%Ycrpp zXk-d9M5CiNmltGmU4{yI{)^>wJl;elvYPi~%c(?`Z-#9KlL-O_JMeEdf@~hP*rrW~ z{kvZ)F($kA7^8;X|5BkOrQDXW3lQ*Yg=xmzKeYv(;bP=UcWqkpy zd%2v>+fo*1y-SbLgLso{Hw&ZKdwgTC=WH@A7*@�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 MHa3o$G&=eJ06igg*Z=?k diff --git a/examples/rogue.py b/examples/rogue.py index df35a87..b3997fd 100644 --- a/examples/rogue.py +++ b/examples/rogue.py @@ -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 diff --git a/tests/test_character.py b/tests/test_character.py index c888c2d..952d5a6 100644 --- a/tests/test_character.py +++ b/tests/test_character.py @@ -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), '') + + 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()