mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-18 20:23:27 +02:00
Merge branch 'master' into Companions-and-Weight
This commit is contained in:
@@ -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/
|
||||||
@@ -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
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.. include:: ../CONTRIBUTING.rst
|
||||||
|
|
||||||
@@ -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
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"] = []
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user