mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
Added ability for a sheet to inherit from another parent sheet.
This commit is contained in:
@@ -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", ...]
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -12,6 +12,7 @@ Welcome to Dungeonsheets's documentation!
|
||||
|
||||
character_files
|
||||
gm_notes
|
||||
advanced_features
|
||||
examples
|
||||
|
||||
Indices and tables
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user