Merge branch 'master' into Companions-and-Weight

This commit is contained in:
Mark Wolfman
2022-07-08 10:58:10 -05:00
committed by GitHub
9 changed files with 170 additions and 12 deletions
+46
View File
@@ -0,0 +1,46 @@
name: Python package
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v2
- name: Set up required system dependencies
run: sudo apt-get -y install pdftk texlive-latex-base texlive-latex-extra texlive-fonts-recommended texlive-fonts-extra
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.0.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies and do a local pip install
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-tests.txt
pip install -e .
- name: Install DnD LaTeX styles
run: |
echo "$(kpsewhich -var-value TEXMFHOME)/tex/latex/"
mkdir -p "$(kpsewhich -var-value TEXMFHOME)/tex/latex/"
git clone https://github.com/rpgtex/DND-5e-LaTeX-Template.git "$(kpsewhich -var-value TEXMFHOME)/tex/latex/dnd"
- name: Run flake
run: flake8 dungeonsheets/ --exit-zero
- name: Run tests
run: >
cd examples/;
makesheets --debug;
makesheets --debug --fancy;
makesheets --debug --output-format=epub;
cd ../;
pytest --cov=dungeonsheets tests/
+67
View File
@@ -78,6 +78,73 @@ IP violations.
How to Contribute How to Contribute
================= =================
Running Tests
-------------
Dungeonsheets uses tests to verify the package works as
intended. Tests are found in the ``tests/`` folder. To run the tests
using *pytest*, run the following from a console:
.. code:: bash
pip install -r requirements.txt -r requirements-tests.txt
pytest
You can also run a sub-set of the tests, which can be convenient for
development. For example, to run just the tests related to dice
mechanics, use ``pytest tests/test_dice.py``. Dungeonsheets defines
tests using the *unittest* package in the standard library. **For
example**, to test a new function in the ``dungeonsheets/dice.py``
module, modify ``tests/test_dice.py``:
.. code-block:: python
:caption: dice.py
def roll(a, b=None):
"""roll(20) means roll 1d20, roll(2, 6) means roll 2d6"""
if b is None:
return random.randint(1, a)
else:
return sum([random.randint(1, b) for _ in range(a)])
.. code-block:: python
:caption: test_dice.py
from unittest import TestCase
from dungeonsheets.dice import roll
class TestDice(TestCase):
def test_simple_rolling(self):
num_tests = 100
# Do a bunch of rolls and make sure the numbers are within the requsted range
for _ in range(num_tests):
result = roll(6)
self.assertGreaterEqual(result, 1)
self.assertLessEqual(result, 6)
def test_multi_rolling(self):
num_tests = 100
for _ in range(num_tests):
result = roll(2, 4) # Roll 2d4
self.assertGreaterEqual(result, 2)
self.assertLessEqual(result, 8)
Building Documentation
----------------------
Dungeonsheets uses sphinx to build documentations. All files are in
reStructuredText and are kept in the ``docs/`` folder. To build the
HTML files, run:
.. code:: bash
pip install -r requirements.txt -r requirements-tests.txt
cd docs/
make html
The results can be found in the ``_build/html/`` foler.
Submitting Bugs Submitting Bugs
--------------- ---------------
+2
View File
@@ -0,0 +1,2 @@
.. include:: ../CONTRIBUTING.rst
+1
View File
@@ -14,6 +14,7 @@ Welcome to Dungeonsheets's documentation!
gm_notes gm_notes
advanced_features advanced_features
examples examples
contributing
Indices and tables Indices and tables
================== ==================
+4 -4
View File
@@ -15,7 +15,7 @@ class Race:
owner = None owner = None
languages = ("Common",) languages = ("Common",)
proficiencies_text = tuple() proficiencies_text = tuple()
weapon_proficiences = tuple() weapon_proficiencies = tuple()
skill_proficiencies = () skill_proficiencies = ()
skill_choices = () skill_choices = ()
num_skill_choices = 0 num_skill_choices = 0
@@ -61,7 +61,7 @@ class _Dwarf(Race):
languages = ("Common", "Dwarvish") languages = ("Common", "Dwarvish")
constitution_bonus = 2 constitution_bonus = 2
proficiencies_text = ("battleaxes", "handaxes", "throwing hammers", "warhammers") proficiencies_text = ("battleaxes", "handaxes", "throwing hammers", "warhammers")
weapon_proficiences = ( weapon_proficiencies = (
weapons.Battleaxe, weapons.Battleaxe,
weapons.Handaxe, weapons.Handaxe,
weapons.ThrowingHammer, weapons.ThrowingHammer,
@@ -388,7 +388,7 @@ class Tabaxi(Race):
speed = "30 (20 climb)" speed = "30 (20 climb)"
languages = ("Common", "[Choose One]") languages = ("Common", "[Choose One]")
weapon_proficiencies = (weapons.Claws,) weapon_proficiencies = (weapons.Claws,)
proficiences_text = ("Claws",) proficiencies_text = ("Claws",)
skill_proficiencies = ("perception", "stealth") skill_proficiencies = ("perception", "stealth")
features = ( features = (
feats.Darkvision, feats.Darkvision,
@@ -427,7 +427,7 @@ class Aarakocra(Race):
wisdom_bonus = 1 wisdom_bonus = 1
languages = ("Common", "Aarakocra", "Auran") languages = ("Common", "Aarakocra", "Auran")
weapon_proficiencies = (weapons.Talons,) weapon_proficiencies = (weapons.Talons,)
proficiences_text = ("Talons",) proficiencies_text = ("Talons",)
def __init__(self, owner=None): def __init__(self, owner=None):
super().__init__(owner=owner) super().__init__(owner=owner)
+31 -6
View File
@@ -9,6 +9,8 @@ from typing import Union
from pathlib import Path from pathlib import Path
from dungeonsheets import exceptions from dungeonsheets import exceptions
from dungeonsheets.magic_items import MagicItem
from dungeonsheets.content_registry import find_content
log = logging.getLogger(__file__) log = logging.getLogger(__file__)
@@ -431,6 +433,29 @@ class FoundryCharacterReader(JSONCharacterReader):
item_name += f"({quantity})" item_name += f"({quantity})"
yield item_name.lower() yield item_name.lower()
def magic_items(self):
"""Loads magic items. If not defined, try to figure out its
properties.
"""
item_types = ["weapon", "armor", "equipment"]
items = [item for item in self.json_data()['items']
if item['type'] in item_types]
from pprint import pprint
magic_items = [item for item in items if item['data']['rarity'] not in ["Common", ""]]
# Convert magic items into classes
def make_magic_item(data):
try:
item = find_content(data['name'], valid_classes=[MagicItem])
except exceptions.ContentNotFound:
# Make a generic version based on the JSON attributes
item_name = data['name'].replace(' ', '')
item = type(item_name, (MagicItem,), {})
return item
magic_items = [make_magic_item(item) for item in magic_items]
return magic_items
def class_levels(self): def class_levels(self):
for item in self.json_data()["items"]: for item in self.json_data()["items"]:
if item["type"] == "class": if item["type"] == "class":
@@ -475,14 +500,14 @@ class FoundryCharacterReader(JSONCharacterReader):
"cha": "charisma", "cha": "charisma",
} }
abilities = self.json_data()["data"]["abilities"] abilities = self.json_data()["data"]["abilities"]
save_proficiences = [] save_proficiencies = []
for abbr, attr in attribute_names.items(): for abbr, attr in attribute_names.items():
char_props[attr] = self.as_int(abilities[abbr]["value"]) char_props[attr] = self.as_int(abilities[abbr]["value"])
# Check proficiency # Check proficiency
is_proficient = bool(abilities[abbr]["proficient"]) is_proficient = bool(abilities[abbr]["proficient"])
if is_proficient: if is_proficient:
save_proficiences.append(attr) save_proficiencies.append(attr)
char_props["saving_throw_proficiencies"] = save_proficiences char_props["saving_throw_proficiencies"] = save_proficiencies
# Skill proficiencies # Skill proficiencies
skill_names = [ skill_names = [
"acrobatics", "acrobatics",
@@ -544,6 +569,8 @@ class FoundryCharacterReader(JSONCharacterReader):
char_props["equipment"] = ", ".join(self.equipment()) char_props["equipment"] = ", ".join(self.equipment())
char_props["armor"] = self.armor() char_props["armor"] = self.armor()
char_props["shield"] = self.shield() char_props["shield"] = self.shield()
# Magic items
char_props["magic_items"] = self.magic_items()
# Personality, etc # Personality, etc
char_props["personality_traits"] = details["trait"].strip() char_props["personality_traits"] = details["trait"].strip()
char_props["flaws"] = details["flaw"].strip() char_props["flaws"] = details["flaw"].strip()
@@ -555,12 +582,10 @@ class FoundryCharacterReader(JSONCharacterReader):
# Some unused values # Some unused values
warn_msg = ( warn_msg = (
"Importing the following traits from JSON is not yet supported: " "Importing the following traits from JSON is not yet supported: "
"magic_items, attacks_and_spellcasting, " "attacks_and_spellcasting, infusions, wild_shapes."
"infusions, wild_shapes."
) )
warnings.warn(warn_msg) warnings.warn(warn_msg)
log.warning(warn_msg) log.warning(warn_msg)
char_props["magic_items"] = ()
char_props["attacks_and_spellcasting"] = "" char_props["attacks_and_spellcasting"] = ""
char_props["infusions"] = [] char_props["infusions"] = []
char_props["wild_shapes"] = [] char_props["wild_shapes"] = []
+6 -1
View File
@@ -11,7 +11,7 @@ from dungeonsheets.character import (
Ranger Ranger
) )
from dungeonsheets.monsters import Panther from dungeonsheets.monsters import Panther
from dungeonsheets.weapons import Weapon, Shortsword from dungeonsheets.weapons import Weapon, Shortsword, Battleaxe
from dungeonsheets.magic_items import MagicItem from dungeonsheets.magic_items import MagicItem
from dungeonsheets.armor import Armor, LeatherArmor, Shield from dungeonsheets.armor import Armor, LeatherArmor, Shield
@@ -152,6 +152,11 @@ class TestCharacter(TestCase):
char.race = race.HighElf() char.race = race.HighElf()
self.assertTrue(char.is_proficient(sword)) self.assertTrue(char.is_proficient(sword))
def test_racial_is_proficient(self):
char = Character(classes=["Wizard"], race="Mountain Dwarf")
battleaxe = Battleaxe()
self.assertTrue(char.is_proficient(battleaxe))
def test_proficiencies_text(self): def test_proficiencies_text(self):
char = Character() char = Character()
char._proficiencies_text = ("hello", "world") char._proficiencies_text = ("hello", "world")
+1
View File
@@ -44,6 +44,7 @@ class TestDice(TestCase):
def test_simple_rolling(self): def test_simple_rolling(self):
num_tests = 100 num_tests = 100
# Do a bunch of rolls and make sure the numbers are within the requsted range
for _ in range(num_tests): for _ in range(num_tests):
result = roll(6) result = roll(6)
self.assertGreaterEqual(result, 1) self.assertGreaterEqual(result, 1)
+12 -1
View File
@@ -226,7 +226,7 @@ class FoundryReaderTests(unittest.TestCase):
gp=162, gp=162,
pp=2, pp=2,
weapons=["rapier"], weapons=["rapier"],
magic_items=(), # magic_items=(),
armor="padded armor", armor="padded armor",
shield="shield", shield="shield",
personality_traits="Loves a good lawyer joke.", personality_traits="Loves a good lawyer joke.",
@@ -284,3 +284,14 @@ class FoundryReaderTests(unittest.TestCase):
this_result = list(this_result) this_result = list(this_result)
self.assertEqual(this_result, val, key) self.assertEqual(this_result, val, key)
def test_load_homebrew_weapon(self):
"""Check that the properties of a homebrew magic weapon get read
properly.
"""
charfile = FOUNDRY_JSON_FILE
with warnings.catch_warnings(record=True):
result = read_sheet_file(charfile)
# Check that some magic items were set
self.assertGreater(len(result['magic_items']), 0,
"No magic items imported")