Added ability for a sheet to inherit from another parent sheet.

This commit is contained in:
Mark Wolfman
2021-06-12 13:15:11 -05:00
parent 2312f50d07
commit 4ce68d5642
6 changed files with 175 additions and 84 deletions
+1 -1
View File
@@ -1 +1 @@
0.14.0
0.15.0
+122
View File
@@ -0,0 +1,122 @@
===================
Advanced Features
===================
.. warning::
Character and GM files are python modules that are imported when
parsed. **NEVER parse a character file without inspecting it** to
verify that there are no unexpected consequences, especially a file
from someone you do not trust.
Homebrew
========
Dungeonsheets provides mechanisms for including items and abilities
outside of the standard rules ("homebrew"). This can be done in one of
two ways.
1. As subclasses (recommended)
2. As strings
Subclasses (Recommended)
------------------------
The best option is to define your homebrew item directly in the
character file as a subclass of one of the basic mechanics:
- :py:class:`dungeonsheets.spells.Spell`
- :py:class:`dungeonsheets.features.Feature`
- :py:class:`dungeonsheets.infusions.Infusion`
- :py:class:`dungeonsheets.weapons.Weapon`
- :py:class:`dungeonsheets.armor.Armor`
- :py:class:`dungeonsheets.armor.Shield`
- :py:class:`dungeonsheets.magic_items.MagicItem`
For convenience, these are all available in the
:py:mod:`dungeonsheets.mechanics` module. With this approach, a
homebrew weapon can be specified in the character file. See the
relevant super class for relevant attributes.
.. code:: python
from dungeonsheets import mechanics
class DullSword(mechanics.Weapon):
"""Bonk things with it."""
name = "Dullsword"
base_damage = "10d6"
weapons = ['shortsword', DullSword]
These homebrew definitions can also be stored in a separate file
(e.g. *my_homebrew.py*), then imported and used in multiple character
files:
.. code:: python
from dungeonsheets import import_homebrew
my_homebrew = import_homebrew("my_campaign.py")
weapons = ["shortsword", my_homebrew.DullSword]
See the :ref:`homebrew example` example for more examples.
Strings
-------
If a mechanic is listed in a character file, but not built into
dungeonsheets, it will still be listed on the character sheet with
generic attributes. This should be viewed as a fallback to the
recommended subclass method above, so that attributes and descriptions
can be given.
Roll20 (VTTES) and Foundry JSON Files
=====================================
Dungeonsheets has partial support for reading JSON files exported
either from roll20.net using the `VTTES browser extension`_, or
directly from `Foundry VTT`_ by choosing *export data* from the
actor's right-click menu. This allows character sheets to be exported
from roll20.net and foundry, and then rendered into full character
sheets.
.. _VTTES browser extension: https://wiki.5e.tools/index.php/R20es_Install_Guide
.. _Foundry VTT: https://foundryvtt.com/article/actors/
Cascading Sheets
================
Character and GM sheet files can **inherit from other character and GM
files**. This has two primary use cases:
1. A parent GM sheet can be made for a campaign, and then child sheets
can provide only the specific details needed for each session.
2. When importing JSON files from roll20 or Foundry VTT, missing
features (e.g. Druid wild shapes) can be added manually.
Sheet cascading is activated by using the ``parent_sheets`` attribute
in a python sheet file, which should be a list of paths to other
sheets (either ``.py`` or ``.json``):
.. code-block:: python
:caption: gm_session1_notes.py
dungeonsheets_version = "0.15.0"
monsters = ['giant eagle', 'wolf', 'goblin']
parent_sheets = ['gm_generic_notes.py']
.. code-block:: python
:caption: gm_generic_notes.py
dungeonsheets_version = "0.15.0"
party = ["rogue1.py", "paladin2.py", ...]
-80
View File
@@ -263,86 +263,6 @@ attribute of the character file:
infusions = ["enhanced_arcane_focus", "repulsion_shield"]
Homebrew
========
Dungeonsheets provides mechanisms for including items and abilities
outside of the standard rules ("homebrew"). This can be done in one of
two ways.
1. As subclasses (recommended)
2. As strings
Subclasses (Recommended)
------------------------
The best option is to define your homebrew item directly in the
character file as a subclass of one of the basic mechanics:
- :py:class:`dungeonsheets.spells.Spell`
- :py:class:`dungeonsheets.features.Feature`
- :py:class:`dungeonsheets.infusions.Infusion`
- :py:class:`dungeonsheets.weapons.Weapon`
- :py:class:`dungeonsheets.armor.Armor`
- :py:class:`dungeonsheets.armor.Shield`
- :py:class:`dungeonsheets.magic_items.MagicItem`
For convenience, these are all available in the
:py:mod:`dungeonsheets.mechanics` module. With this approach, a
homebrew weapon can be specified in the character file. See the
relevant super class for relevant attributes.
.. code:: python
from dungeonsheets import mechanics
class DullSword(mechanics.Weapon):
"""Bonk things with it."""
name = "Dullsword"
base_damage = "10d6"
weapons = ['shortsword', DullSword]
These homebrew definitions can also be stored in a separate file
(e.g. *my_homebrew.py*), then imported and used in multiple character
files:
.. code:: python
from dungeonsheets import import_homebrew
my_homebrew = import_homebrew("my_campaign.py")
weapons = ["shortsword", my_homebrew.DullSword]
See the :ref:`homebrew example` example for more examples.
Strings
-------
If a mechanic is listed in a character file, but not built into
dungeonsheets, it will still be listed on the character sheet with
generic attributes. This should be viewed as a fallback to the
recommended subclass method above, so that attributes and descriptions
can be given.
Roll20 (VTTES) and Foundry JSON Files
=====================================
Dungeonsheets has partial support for reading JSON files exported
either from roll20.net using the `VTTES browser extension`_, or
directly from `Foundry VTT`_ by choosing *export data* from the
actor's right-click menu. This allows character sheets to be exported
from roll20.net and foundry, and then rendered into full character
sheets.
.. _player's handbook: http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook
.. _issue: https://github.com/canismarko/dungeon-sheets/issues
.. _VTTES browser extension: https://wiki.5e.tools/index.php/R20es_Install_Guide
.. _Foundry VTT: https://foundryvtt.com/article/actors/
+1
View File
@@ -12,6 +12,7 @@ Welcome to Dungeonsheets's documentation!
character_files
gm_notes
advanced_features
examples
Indices and tables
+21 -3
View File
@@ -4,6 +4,7 @@ import json
import re
from functools import lru_cache
import logging
from typing import Union
from pathlib import Path
@@ -12,18 +13,26 @@ from dungeonsheets import exceptions
log = logging.getLogger(__file__)
def read_sheet_file(filename: str):
def read_sheet_file(filename: Union[str, Path]) -> dict:
"""Create a character object from the given definition file.
The definition file should be an importable python file or a JSON
file following one of the supported formats, filled with variables
describing the character.
This function also resolves any *parent_sheets* attributes in the
given sheet, loading parent sheets and updating those attributes.
Parameters
----------
filename
The path to the file that will be imported.
Returns
-------
char_props
Dictionary with the import character properties.
"""
filename = Path(filename)
# Parse the file name
@@ -33,8 +42,17 @@ def read_sheet_file(filename: str):
except KeyError:
raise ValueError(f"Character definition {filename} is not a known file type.")
else:
new_char = reader()
return new_char
these_props = reader()
# Resolve parent_sheets
char_props = {}
parent_sheets = these_props.pop('parent_sheets', [])
for parent_sheet in parent_sheets:
parent_sheet = Path(parent_sheet)
if parent_sheet != filename:
parent_props = read_sheet_file(parent_sheet)
char_props.update(parent_props)
char_props.update(these_props)
return char_props
class BaseCharacterReader:
+30
View File
@@ -2,6 +2,7 @@ import warnings
from pathlib import Path
import unittest
import types
from contextlib import contextmanager
from dungeonsheets import exceptions
from dungeonsheets.readers import read_sheet_file
@@ -15,6 +16,35 @@ SPELLCASTER_JSON_FILE = EG_DIR / "artificer2.json"
class PythonReaderTests(unittest.TestCase):
@contextmanager
def inherited_sheets(self):
"""Create some cascading sheets to be inherited."""
child = Path("child_sheet.py")
parent = Path("parent_sheet.py")
# Write inheritance files
with open(parent, mode="w") as fp:
fp.writelines("\n".join([
"dungeonsheets_version = '0.15.0'",
"background = 'entertainer'",
]) + "\n")
with open(child, mode="w") as fp:
fp.writelines("\n".join([
"dungeonsheets_version = '0.15.0'",
"name = 'Douglass Adams'",
f"parent_sheets = ['{str(parent)}']",
]) + "\n")
# Drop back into the calling code
yield child, parent
# Remove temporary files
child.unlink()
parent.unlink()
def test_cascading_sheets(self):
with self.inherited_sheets() as (child, parent):
char_props = read_sheet_file(child)
self.assertEqual(char_props["name"], "Douglass Adams")
self.assertEqual(char_props["background"], "entertainer")
def test_load_python_gm_sheet(self):
gmfile = GM_PYTHON_FILE
result = read_sheet_file(gmfile)