mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-18 20:23:27 +02:00
239 lines
7.9 KiB
Python
239 lines
7.9 KiB
Python
#!/usr/bin/env python
|
|
|
|
from unittest import TestCase, skip
|
|
|
|
from dungeonsheets.armor import ChainShirt
|
|
from dungeonsheets.character import Character
|
|
from dungeonsheets.encounter import Encounter
|
|
from dungeonsheets.encounter.actions import Attack
|
|
from dungeonsheets.encounter.events import AttackEvent
|
|
from dungeonsheets.monsters import Monster, GiantRat
|
|
from dungeonsheets.stats import Ability
|
|
from dungeonsheets.dice import roll
|
|
|
|
|
|
class TestEncounter(TestCase):
|
|
"""Tests for features and feature-related activities."""
|
|
|
|
def test_simple_rat_fight(self):
|
|
"""A fight against a giant rat"""
|
|
char = SimpleRanger()
|
|
|
|
class StravajiaxenAttack(Attack):
|
|
# TODO: Get default player attacks as a result of
|
|
# creating a player character and classes
|
|
|
|
def __init__(self, subj, obj):
|
|
self.subj = subj
|
|
self.obj = obj
|
|
|
|
def execute(self):
|
|
result = roll(20) + self.subj.dexterity.modifier + self.subj.proficiency_bonus
|
|
damage = roll(1, 8) + self.subj.dexterity.modifier
|
|
is_hit = result >= self.obj.armor_class
|
|
|
|
if is_hit:
|
|
self.obj.current_hp -= damage
|
|
|
|
return AttackEvent(self, result, damage, is_hit)
|
|
|
|
char.default_actions.append(StravajiaxenAttack)
|
|
|
|
class GiantRatAttack(Attack):
|
|
|
|
def __init__(self, subj, obj):
|
|
self.subj = subj
|
|
self.obj = obj
|
|
|
|
def execute(self):
|
|
result = roll(20) + 4
|
|
damage = roll(1, 4) + 2
|
|
is_hit = result >= self.obj.armor_class
|
|
|
|
if is_hit:
|
|
self.obj.current_hp -= damage
|
|
|
|
return AttackEvent(self, result, damage, is_hit)
|
|
|
|
enemy = GiantRat()
|
|
enemy.name = "Nameless Rat" # I don't want things to be personal...
|
|
enemy.default_actions.append(GiantRatAttack)
|
|
|
|
battle = Encounter([char], [enemy])
|
|
battle.reset()
|
|
results = battle.simulate()
|
|
for event in results:
|
|
print(str(event))
|
|
print(results)
|
|
|
|
@skip('NotImplementedError')
|
|
def test_langdedrosa_fight(self):
|
|
"""Can I run an encounter against Langdedrosa Cyanwrath?"""
|
|
char = Character()
|
|
char.set_attrs(name="Stravajiaxen")
|
|
char.set_attrs(weapons=["greataxe"])
|
|
char.set_attrs(armor="split mail")
|
|
|
|
# Check that race gets set to an object
|
|
char.set_attrs(race="half orc")
|
|
char.set_attrs(inspiration=False)
|
|
|
|
class LangdedrosaCyanwrath(Monster):
|
|
"""
|
|
**Action Surge (Recharges on a Short or Long Rest).** On his turn, Langdedrosa
|
|
can take one additional action.
|
|
|
|
**Improved Critical.** Langdedrosa's weapon attacks score a critical hit on a
|
|
roll of 19 or 20.
|
|
|
|
**Multiattack:** Schlangdedrosa attacks twice, either with his greatsword or spear.
|
|
|
|
**Greatsword.** Melee Weapon Attack: +6 to hit, reach 5 ft., one target.
|
|
Hit: 11 (2d6 + 4) slashing damage.
|
|
|
|
**Spear.** Melee or Ranged Weapon Attack: +6 to hit, reach 5 ft. or
|
|
ranged 20/60 ft., one target. Hit: 7 (1d6 + 4) piercing damage.
|
|
|
|
**Lightning Breath (Recharge 5-6)**. Schlangdedrosa breathes lightning in a
|
|
30-foot line that is 5 feet wide. Each creature in the line must make a DC 13
|
|
Dexterity saving throw, taking 22 (4d10) lightning damage on a failed save, or
|
|
half as much damage on a successful one.
|
|
|
|
**Climbing speed:** 30 ft.
|
|
"""
|
|
|
|
name = "Langdedrosa Cyanwrath"
|
|
description = "Medium humanoid (half-dragon), lawful evil"
|
|
challenge_rating = 4
|
|
armor_class = 17
|
|
skills = "Athletics +6, Intimidation +3, Perception +4"
|
|
senses = "blindsight 10 ft., darkvision 60ft., passive Perception 14"
|
|
strength = Ability(19)
|
|
dexterity = Ability(13)
|
|
constitution = Ability(16)
|
|
intelligence = Ability(10)
|
|
wisdom = Ability(14)
|
|
charisma = Ability(12)
|
|
speed = 30
|
|
swim_speed = 0
|
|
fly_speed = 0
|
|
hp_max = 57
|
|
hit_dice = "6d12+18"
|
|
|
|
lang = LangdedrosaCyanwrath()
|
|
|
|
battle = Encounter([char], [lang])
|
|
results = battle.simulate()
|
|
|
|
|
|
class SimpleRanger(Character): # Taken from ranger2.py
|
|
"""This file describes the heroic adventurer Ranger2.
|
|
|
|
It's used primarily for saving characters from create-character,
|
|
where there will be many missing sections.
|
|
|
|
Modify this file as you level up and then re-generate the character
|
|
sheet by running ``makesheets`` from the command line.
|
|
|
|
"""
|
|
|
|
dungeonsheets_version = "0.9.4"
|
|
|
|
name = "Stravajiaxen"
|
|
player_name = "Ben"
|
|
|
|
# Be sure to list Primary class first
|
|
classes = ['Ranger'] # ex: ['Wizard'] or ['Rogue', 'Fighter']
|
|
levels = [3] # ex: [10] or [3, 2]
|
|
subclasses = ["Horizon Walker"] # ex: ['Necromacy'] or ['Thief', None]
|
|
background = "Uthgardt Tribe Member"
|
|
race = "Lizardfolk"
|
|
alignment = "Neutral good"
|
|
|
|
xp = 0
|
|
hp_max = 24
|
|
inspiration = 0 # integer inspiration value
|
|
|
|
# Ability Scores
|
|
strength = Ability(13)
|
|
dexterity = Ability(15)
|
|
constitution = Ability(12)
|
|
intelligence = Ability(8)
|
|
wisdom = Ability(15)
|
|
charisma = Ability(12)
|
|
|
|
# Select what skills you're proficient with
|
|
# ex: skill_proficiencies = ('athletics', 'acrobatics', 'arcana')
|
|
skill_proficiencies = ('athletics', 'insight', 'investigation')
|
|
|
|
# Any skills you have "expertise" (Bard/Rogue) in
|
|
skill_expertise = ()
|
|
|
|
# Named features / feats that aren't part of your classes, race, or background.
|
|
# Also include Eldritch Invocations and features you make multiple selection of
|
|
# (like Maneuvers for Fighter, Metamagic for Sorcerors, Trick Shots for
|
|
# Gunslinger, etc.)
|
|
# Example:
|
|
# features = ('Tavern Brawler',) # take the optional Feat from PHB
|
|
features = ()
|
|
|
|
# If selecting among multiple feature options: ex Fighting Style
|
|
# Example (Fighting Style):
|
|
# feature_choices = ('Archery',)
|
|
feature_choices = ('dueling',)
|
|
|
|
# Weapons/other proficiencies not given by class/race/background
|
|
weapon_proficiencies = () # ex: ('shortsword', 'quarterstaff')
|
|
_proficiencies_text = () # ex: ("thieves' tools",)
|
|
|
|
# Proficiencies and languages
|
|
languages = """Dwarvish, Common, Draconic"""
|
|
|
|
# Inventory
|
|
# TODO: Get yourself some money
|
|
cp = 0
|
|
sp = 0
|
|
ep = 0
|
|
gp = 0
|
|
pp = 0
|
|
|
|
# TODO: Put your equipped weapons and armor here
|
|
weapons = ('rapier', 'hand crossbow') # Example: ('shortsword', 'longsword')
|
|
magic_items = () # Example: ('ring of protection',)
|
|
armor = ChainShirt # Eg "leather armor"
|
|
shield = "" # Eg "shield"
|
|
|
|
equipment = """TODO: list the equipment and magic items your character carries"""
|
|
|
|
attacks_and_spellcasting = """TODO: Describe how your character usually attacks
|
|
or uses spells."""
|
|
|
|
# List of known spells
|
|
# Example: spells_prepared = ('magic missile', 'mage armor')
|
|
spells_prepared = () # Todo: Learn some spells
|
|
|
|
# Which spells have not been prepared
|
|
__spells_unprepared = ()
|
|
|
|
# all spells known
|
|
spells = spells_prepared + __spells_unprepared
|
|
|
|
# Wild shapes for Druid
|
|
wild_shapes = () # Ex: ('ape', 'wolf', 'ankylosaurus')
|
|
|
|
# Backstory
|
|
# Describe your backstory here
|
|
personality_traits = """TODO: How does your character behave? See the PHB for
|
|
examples of all the sections below"""
|
|
|
|
ideals = """TODO: What does your character believe in?"""
|
|
|
|
bonds = """TODO: Describe what debts your character has to pay,
|
|
and other commitments or ongoing quests they have."""
|
|
|
|
flaws = """TODO: Describe your characters interesting flaws.
|
|
"""
|
|
|
|
features_and_traits = """TODO: Describe other features and abilities your
|
|
character has."""
|