.. _`usage`: ########################## Using the OpenD6 Tools ########################## In this section, we'll talk about the following two use cases for these tools: - Creating and validating elements of a world, campaign, or encounter. This includes the DSL's for spells, characters, creatures, etc. This the essential **Create-Compute-Consider** cycle. - Publishing documents for game masters and players. We'll start with using the tools to create things like Spells and Characters, and computing derived values like spell difficulty or the character dice budget. .. note:: The examples use "docstring" examples. The examples in this document use code prefaced with ``>>>``. This shows things as if you were typing them interactively to the Python interpreter. This is **not** practical, but it's the conventional approach to showing examples in Python documentation. It makes it easy for tools to find and validate the examples. Pragmatically, you'll be creating cells in a Jupyter Notebook or writing text in a ``.py`` module file with a programming text editor. Creation ========= These tools offer two languages specific to the **OpenD6** TTRPG domain. (We call them "DSL's", Domain-Specific Languages.) There are two sub-categories of languages with the **OpenD6** domain: Spells and Characters. They have similar Python syntax. Otherwise, there's little actual overlap. Spells and Items are focused on a difficulty budget. Characters and Creatures are focused on skills with a dice budget. In a larger game context, a character's skills determine how many dice they get to roll. The spell's details (and the situation in the game) determine the difficulty that must be exceeded by a roll of the dice to make that spell work. Spells, Invocations, and Items ------------------------------ The Spell DSL uses Python to do computations and transformations on a Spell definition. This also includes Invocations and magical Items -- they're all bundles of effects with additional aspects. Here's an example. >>> from opend6_tools.magic import * >>> example = Spell( ... name="Example", ... notes="Mage waves their hands and says the words", ... skill="Transformation", ... effect=SkillEffect("Acumen: testing", "+4D", "skill modifier"), ... duration=DurationAspect("1 sec"), ... range=RangeAspect("1m"), ... casting_time=CastingTimeAspect("5 sec"), ... speed=SpeedAspect.based_on("range", description="Instantaneous"), ... other_aspects = { ... "gestures": GesturesAspect("waves hands", "simple"), ... "incantations": IncantationsAspect("says the words", "short"), ... }, ... other_conditions = [GenericAspect(1, "Everything else is completed")], ... ) >>> example.difficulty 5 (Yes, there's a bit of redundancy here. It's helpful for pinpointing errors.) The **OpenD6 Rules** list 8 characteristics of a spell: Skill Used, Difficulty, Effect, Duration, Range, Speed, Casting Time, and "Other Aspects". This DSL is very close (but not identical) to the definition offered in the rules: - Skill Used is optional, and can sometimes be deduced from the effect definition. - Difficulty is not provided by the spell designer. This is computed from the details. The Python expression ``example.difficulty`` reveals the computed difficulty. - Three new characteristics are added to the spell definition: Name, Notes, and "Other Conditions". - The "Other Aspects" of a spell have a specific list of allowed aspects. The Effect of a Spell (or Invocation or Item) defines what happens. There are a number of distinct specialized subclasses of :py:class:`Effect`. See :ref:`magic_module.implementation` for the complete reference. Here's the short list of effects, with examples. .. csv-table:: "``SpecialAbilityEffect('Extra Sense: Bugs', 3)``","Grants one of the defined special abilities with the given rank." "``DisadvantageEffect('Hindrance: Initiative', 5, '-10 to all initiative totals')``","Saddles a character with one of the defined disadvantages with a given rank and additional notes." "``DamageEffect('Body damage', 4*D+1)``","Does the defined amount of damage." "``ProtectionEffect('Damage Resistance', 4*D+1, 'physical damage', 'ignore all armor')``","Provides the defied armor value, with additional notes." "``SkillEffect('Physique: lifting', 5*D)``","Grants a new skill to a character with the given number of dice." "``AttributeEffect('Physique', 5*D, 'attribute modifier')``","Modifies a character's attribute." The following are often part of a :py:class:`CompositeEffect`, and provides additional details on the scope of the effect: .. csv-table:: "``TimeEffect('Reduces duration', '10 min')``" "``DistanceEffect('Moves something', '1 km')``" "``MassEffect('Moves', '100 kilograms')``" "``VolumeEffect('Creates', '100 liters')``" In addition to the Effect of a spell, there are a large number of other Aspects. Here are the four required aspects for any Spell or Invocation: .. csv-table:: "``DurationAspect('1 min')``" "``RangeAspect('15 m')``" "``SpeedAspect.based_on('range', description='Instantaneous')``","This is the most common specification for speed. Rather than state the range twice (once as range, and once as speed), it's easier to use this ``based_on()`` construct to assure they match." "``CastingTimeAspect('1 r')``","'r' is short for 'round'" Here are the aspects that are often included in the "other aspects" details of a Spell or Invocation: .. csv-table:: "``AreaEffectAspect('2.5 m circle; 3m l 1m r cone')``","The area of effect includes a large number of shapes, including circle, sphere, hemisphere, divination sphere, cone, wall, cuboid, and blast." "``ChangeTargetAspect('2 targets')``" "``ChargesAspect(10)``" "``CommunityAspect('31 helpers', 'Simple actions')``" "``ComponentsAspect('something', 'uncommon; destroyed')``" "``ConcentrationAspect.based_on('casting_time')``" "``FeedbackAspect(3)``" "``FocusedAspect.based_on(('effect', 'duration'))``","Yes, the double ()'s are required to provide this kind of complex based-on computation." "``GesturesAspect('waves hands', 'simple; offensive')``" "``IncantationsAspect('Die, scum', 'phrase; loud; offensive')``" "``MultipleTargetAspect('3 targets')``" "``UnrealEffectAspect.based_on('effect', 'difficulty 9')``" "``VariableDurationAspect('on/off switch')``" "``VariableEffectAspect('Can increase', 10)``" "``VariableMovementAspect('bend around same size')``" "``ArcaneKnowledgeAspect('dimension, time')``","This is a zero-difficulty marker for additional skill details." "``GenericAspect(2, 'Not too hard')``","These are sometimes used as other conditions that don't fit any other category." The key benefit of this language is providing details in a way that's amenable to automatic computations. This handles the bookkeeping around the aspects which increase the difficulty, distinct from aspects which decrease difficulty. Characters ----------- The DSL is based on Python, it defines a set of computations and transformations on a Character definition. Creatures can also be defined. Here's an example Character. >>> from opend6_tools.character import * >>> henchman = Character( ... occupation="Henchman", ... race="Human", ... agility=Agility( ... 2*D, ... {"fighting": 4*D, "melee combat": 3*D, "stealth": 3*D}), ... coordination=Coordination( ... 2 * D, ... {"lockpicking": 3*D, "marksmanship": 4*D}), ... physique=Physique( ... 3 * D, {"running": 3*D+2}), ... intellect=Intellect( ... 2 * D, ... ), ... acumen=Acumen( ... 2 * D, ... {"hide": 3*D, "streetwise": 3*D, "tracking": 3*D}, ... ), ... charisma=Charisma(2 * D), ... move=10, ... fate_points=0, ... character_points=2, ... body=13, ... equipment="dagger (damage +1D), lockpicking tools (+1D to lockpicking rolls), soft leather armor (Armor Value +2)", ... ) >>> henchman.budget_check(CharacterBudget.NORMAL) {'Attributes': '13D out of 18D', 'Skills': '29D+2 out of 7D', 'Options': 'Nothing'} (Yes, there's a wee bit of redundancy here. It's actually helpful because it helps to pinpoint errors.) The ``budget_check()`` method recapitulates the character's budget for base attributes and specific skills. See :ref:`magic_module.implementation` for the complete reference. The basis for comparison is the starting budget for typical characters. There are only a few essential Attributes, defined in the :py:mod:`opend6_tools.character` package. .. csv-table:: :py:class:`opend6_tools.character.features.Acumen` :py:class:`opend6_tools.character.features.Charisma` :py:class:`opend6_tools.character.features.Intellect` :py:class:`opend6_tools.character.features.Agility` :py:class:`opend6_tools.character.features.Coordination` "Extranormal, either :py:class:`opend6_tools.character.features.Magic` or :py:class:`opend6_tools.character.features.Miracles`" Each of these uses the following pattern: :: Attribute(dice_code, {'skill': dice_code, ...}) For example: >>> physique=Physique(3*D, {'lifting': +2}) The ``dice_code`` values use a small shift in syntax from the ``2D+2`` used in most rulebooks to to ``2*D+2``. The ``*`` is part of using Python syntax for the DSL. The skill names are those defined in the rules using all lower-case letters and surrounded by apostrophes (``'``) or quotes (``"``). Consistency matters. Here are a large number of advantages, disadvantages, and special abilities. Here are the :py:class:`opend6_tools.character.features.Advantage` subclasses: .. csv-table:: :py:class:`opend6_tools.character.features.Authority` :py:class:`opend6_tools.character.features.Contacts` :py:class:`opend6_tools.character.features.Cultures` :py:class:`opend6_tools.character.features.Equipment` :py:class:`opend6_tools.character.features.Fame` :py:class:`opend6_tools.character.features.Patron` :py:class:`opend6_tools.character.features.Size` :py:class:`opend6_tools.character.features.TrademarkSpecialization` :py:class:`opend6_tools.character.features.Wealth` Each of these has a rank, and any additional notes. For example, ``Fame(2, "famous divorce settlement")``. Here are the :py:class:`opend6_tools.character.features.Disadvantage` subclasses: .. csv-table:: :py:class:`opend6_tools.character.features.AchillesHeel` :py:class:`opend6_tools.character.features.AdvantageFlaw` :py:class:`opend6_tools.character.features.MinorStigma` :py:class:`opend6_tools.character.features.Age` :py:class:`opend6_tools.character.features.BadLuck` :py:class:`opend6_tools.character.features.BurnOut` :py:class:`opend6_tools.character.features.CulturalUnfamiliarity` :py:class:`opend6_tools.character.features.Debt` :py:class:`opend6_tools.character.features.Devotion` :py:class:`opend6_tools.character.features.Employed` :py:class:`opend6_tools.character.features.Enemy` :py:class:`opend6_tools.character.features.Hindrance` :py:class:`opend6_tools.character.features.Infamy` :py:class:`opend6_tools.character.features.LanguageProblems` :py:class:`opend6_tools.character.features.LearningProblems` :py:class:`opend6_tools.character.features.Poverty` :py:class:`opend6_tools.character.features.Prejudice` :py:class:`opend6_tools.character.features.Price` :py:class:`opend6_tools.character.features.Quirk` :py:class:`opend6_tools.character.features.ReducedAttribute` Each of these has a rank, and any additional notes. For example, ``Quirk(2, "Exaggerated gestures")``. The Special Abilities each have a distinct cost for each rank. Here are the :py:class:`opend6_tools.character.features.SpecialAbility` subclasses: .. csv-table:: :header: SpecialAbility, Rank Cost :py:class:`opend6_tools.character.features.AcceleratedHealing`, 3 :py:class:`opend6_tools.character.features.Ambidextrous`, 2 :py:class:`opend6_tools.character.features.AnimalControl`, 3 :py:class:`opend6_tools.character.features.ArmorDefeatingAttack`, 2 :py:class:`opend6_tools.character.features.AtmosphericTolerance`, 2 :py:class:`opend6_tools.character.features.AttackResistance`, 2 :py:class:`opend6_tools.character.features.AttributeScramble`, 4 :py:class:`opend6_tools.character.features.Blur`, 3 :py:class:`opend6_tools.character.features.CombatSense`, 3 :py:class:`opend6_tools.character.features.Confusion`, 4 :py:class:`opend6_tools.character.features.Darkness`, 3 :py:class:`opend6_tools.character.features.Elasticity`, 1 :py:class:`opend6_tools.character.features.Endurance`, 1 :py:class:`opend6_tools.character.features.EnhancedSense`, 3 :py:class:`opend6_tools.character.features.EnvironmentalResistance`, 1 :py:class:`opend6_tools.character.features.ExtraBodyPart`, 0 :py:class:`opend6_tools.character.features.ExtraSense`, 1 :py:class:`opend6_tools.character.features.FastReactions`, 3 :py:class:`opend6_tools.character.features.Fear`, 2 :py:class:`opend6_tools.character.features.Flight`, 6 :py:class:`opend6_tools.character.features.GliderWings`, 3 :py:class:`opend6_tools.character.features.Hardiness`, 1 :py:class:`opend6_tools.character.features.Hypermovement`, 1 :py:class:`opend6_tools.character.features.Immortality`, 7 :py:class:`opend6_tools.character.features.Immunity`, 1 :py:class:`opend6_tools.character.features.IncreasedAttribute`, 2 :py:class:`opend6_tools.character.features.InfravisionUltravision`, 1 :py:class:`opend6_tools.character.features.Intangibility`, 5 :py:class:`opend6_tools.character.features.Invisibility`, 3 :py:class:`opend6_tools.character.features.IronWill`, 2 :py:class:`opend6_tools.character.features.LifeDrain`, 5 :py:class:`opend6_tools.character.features.Longevity`, 3 :py:class:`opend6_tools.character.features.LuckGood`, 2 :py:class:`opend6_tools.character.features.LuckGreat`, 3 :py:class:`opend6_tools.character.features.MasterOfDisguise`, 3 :py:class:`opend6_tools.character.features.MultipleAbilities`, 1 :py:class:`opend6_tools.character.features.NaturalArmor`, 3 :py:class:`opend6_tools.character.features.NaturalHandWeapon`, 2 :py:class:`opend6_tools.character.features.NaturalMagick`, 5 :py:class:`opend6_tools.character.features.NaturalRangedWeapon`, 3 :py:class:`opend6_tools.character.features.Omnivorous`, 2 :py:class:`opend6_tools.character.features.ParalyzingTouch`, 4 :py:class:`opend6_tools.character.features.PossessionLimited`, 8 :py:class:`opend6_tools.character.features.PossessionFull`, 10 :py:class:`opend6_tools.character.features.QuickStudy`, 3 :py:class:`opend6_tools.character.features.SenseOfDirection`, 2 :py:class:`opend6_tools.character.features.Shapeshifting`, 3 :py:class:`opend6_tools.character.features.Silence`, 3 :py:class:`opend6_tools.character.features.SkillBonus`, 1 :py:class:`opend6_tools.character.features.SkillMinimum`, 4 :py:class:`opend6_tools.character.features.Teleportation`, 3 :py:class:`opend6_tools.character.features.Transmutation`, 5 :py:class:`opend6_tools.character.features.UncannyAptitude`, 3 :py:class:`opend6_tools.character.features.Ventriloquism`, 3 :py:class:`opend6_tools.character.features.WaterBreathing`, 2 :py:class:`opend6_tools.character.features.YouthfulAppearance`, 1 When these are created, you must supply a numeric rank value and optional notes with details on the ability. The cost, however, is a product of the desired rank and the rank cost for the given special ability. We can use these Python class definitions as a DSL to write Spell, Invocation, Character, Creature, and Item descriptions. We can look at the dice budgets, make changes, and fine-tune the design. Because the DSL is tied to the rules, the resulting elements of a world, campaign, or encounter tend to be consistent. There are a few tools we have to support processing Spells and Characters. Supporting Functions --------------------- There are a few helpful functions that are part of these packages. For Spells (and Invocations and Items), there are two useful functions. - The :py:func:`opend6_tools.magic.workbook.display` function shows some details of the spell and the difficulty computation. The :py:func:`opend6_tools.character.workbook.display` function, similarly, shows details of a character. - The :py:func:`opend6_tools.magic.workbook.debug` function shows more in-depth details of the spell and the difficulty computation. The :py:func:`opend6_tools.character.workbook.debug` function, similarly, shows details of a character. - The :py:func:`opend6_tools.magic.output.detail` function transforms spell definitions into RST for publication. The :py:func:`opend6_tools.character.output.detail` function creates a character sheet in one of three forms: a short form for publication, and detailed form for publication, and a form that can be given to players to use. The overall workflow, then, is this: 1. Define a :py:class:`opend6_tools.magic.spells.Spell` or :py:class:`opend6_tools.character.features.Character` object. 2. Display it to be sure it has the right characteristics and difficulty. 3. Then, use the publication tool-chain to create the final documents to be shared with game masters and players. Publication =========== Publication works with Sphinx. (Alternative static content generating tools, like Hugo, also work. The core rule is the tool has to work with Restructured Text.) There is a multi-step transformation from idea to document. .. uml:: @startuml 'https://plantuml.com/activity-diagram-beta title Spell Design Activities start; :1. Idea; :2. Create and Change jupyter notebook "".ipynb""; partition "3. Publish" { :3a: Extract a module "".py"" from notebook "".ipynb""; :3b: Create ReStructuredText "".txt"" from module "".py""; :3c: Include into final "".rst"" document; :3d: Create "".html"" or "".tex"" for publication; } @enduml The notebook is a good place to start because it supports interactive computation. The **Change**-**Compute**-**Consider** cycle works out well using notebooks. An extract from the notebook becomes a Python module. The module (which is an application) will emit ReStructured Text, RST, that's used for publication. The conversion of RST to HTML or to a PDF is not something we want to look at in detail. This is something we're trusting other tools to handle for us. Examples include **Sphinx** and **docutils**. The goal of a world designer to to have source material for publication in RST notation. Here is a very detailed view of the various documents -- and extractions and conversions -- involved in publication. .. uml:: @startuml 'https://plantuml.com/component-diagram skinparam actorStyle awesome title Final Publication actor Designer component "Jupyter Lab" as jupyter <> component Make <> boundary Browser boundary Terminal package book_source { artifact "element.ipynb" as nb artifact "element.py" as mod <> artifact "element.txt" as doc artifact "document.rst" as book /' note "created by the designer" as note_1 note_1 ..> nb note_1 ..> book note "final output" as note_2 note_2 ..> web_page '/ } package book_build { artifact "document.html" as web_page } package opend6_tools { component "notebook_extract" as converter <> component magic component character } nb ..> magic mod ..> magic Designer --> Browser : "2. Create Notebook" Designer --> Terminal : "3. Publish" Browser --> jupyter jupyter --> nb Terminal --> [Make] [Make] --> converter : "3a. convert notebook" converter <-- nb : "reads" converter --> mod : "writes" [Make] --> mod : "3b. create RST" mod --> doc : "writes" component "sphinx-build" as sphinx <> [Make] --> sphinx : "3c. include in final document" sphinx <-- book : "reads" sphinx <-- doc : "reads" sphinx --> web_page : "3d. creates HTML" @enduml While this is tangled, it's a response to having two tasks with a profound conflict. (See the :ref:`tutorial` for more background on this.) - Computing difficulty requires an interactive tool, responsive to user input, focused on the technical needs of conformance with the rules. - Publication turns words, spells, items, and characters into a single, published document. This is focused on content, organization, and style considerations, separate from the technical conformance with the rules. The kind of tool that facilitates one task exacerbates the other. Specifically, the flexibility of a Jupyter Notebook doesn't (readily) support publication. Extracting to a Python module provides a file that can be tested, used by any tool, and preserved as part of the source of the publications. The RST files are merely the first step in the publication chain. The **Final Publication** diagram shows a number of transformations. - The **notebook_extract** app converts from ``.ipynb`` to ``.py``. The notebook, which has few rules or boundaries, is converted to an application module with a command-line interface usable by tools like **make**. For a "spells" conversion, only the ``Spell`` and ``Invocation`` cells are preserved. For a "characters" conversion, the ``Character`` and ``Creature`` cells are preserved. - The application modules extracted from notebooks will create RST-format files. These files generally have a suffix of ``.txt`` to distinguish it from the manually-created RST files. The module can also produce debugging output as well as the RST for publication. - The Sphinx tool converts the collection of ``.rst`` files to any of the final publication forms. These forms include HTML, PDF, and EPUB, among others. The diagram also shows two distinct interfaces to these tools: - An interactive Jupyter Lab session (in a browser) to create (and update) a notebook. - A terminal window to run the ``make`` command to create the final HTML web page (or PDF or EPUB). This can also be run via the browser from within the Jupyter Lab environment. There are other apps (including :py:mod:`opend6_tools.notebook_extract` and the module created from a notebook), but these are not often used directly by a designer. These are part of the publication processing stream controlled by the **make** tool. The notebook as a starting place is convenient, but not required. A skilled Python developer can comfortably build the Python module directly. Using the ``debug`` and ``display`` command-line options, they can edit and check their results from the terminal window. The module's CLI can display debugging information allowing a designer to change a module and compute the difficulties (or character budgets) entirely using command-line tools. Files and structure ------------------- .. another view: Here's a more detailed view of the document structures. .. uml:: @startuml 'https://plantuml.com/use-case-diagram title Structure of the Documents package notebooks { artifact some_spell.ipynb } package source { package spells { artifact some_spell.py artifact some_spell.rst } package characters artifact "index.rst" as index index --> some_spell.rst : "include" index --> characters : "include" } some_spell.ipynb .> some_spell.py some_spell.py -> some_spell.rst : "Writes" package build { package html { artifact "index.html" } package pdf package epub } artifact "Makefile" [Make] -- Makefile [Make] -- [sphinx-build] source --> [Make] : "Reads" [Make] ---> build : "Writes" Makefile .> some_spell.py Makefile ..> some_spell.rst @enduml The designer must be cognizant of the files they will create and the transformations that will happen. - The designer creates book content in RST-formatted files. This is used to produce the final book as HTML, PDF, or EPUB. This uses ``.. include::`` commands to include the ``.txt`` files created from the spell modules or character modules. - A ``.py`` module is the preferred format for the DSL. A small CLI application will emit RST-format files for use by Sphinx. - The designer can create an ``.ipynb`` notebook, which is slightly easier than working directly with a module. Often notebooks are kept separate from the Sphinx content. The relationships among these files leads to several individual ``Makefile`` instances to control publication. These rules are not complicated, and are helpful to optimize the workflow. Here's how a spell's RST might be included into a document. .. code-block:: rst Here's the **Quality Assurance** spell. .. include:: spells/qa.txt Maybe some more description of this spell. An **example** can be helpful. The details of how the file gets included are part of **Sphinx**. It's the job of the writer to make sure the reference is in place. For the include of ``qa.txt`` to work, we must be sure that the ``qa.py`` app was run and created the file. This is generally a question of processing and control. Processing and control ---------------------- Control of the publication pipeline is delegated to the **make** tool. The **make** tool understands file dependencies, and the recipe that will create new file when the sources on which it depends change. The essential command is this: .. code-block:: bash % make html The assumption is that the book's top-level directory has the book's ``conf.py`` and ``index.rst``, created by the ``sphinx-quickstart`` command. The ``make html`` will then process the entire rules document. The document is rarely written entirely in the ``index.rst``. Instead, the ``index.rst`` depends on many files, often one per chapter. One of these files is often ``spells.rst``. The ``spells.rst`` has an ``.. include::`` directive to gather content from the ``qa.txt`` file. These two dependencies -- document on a section and section on an included file -- are handled by the **Sphinx** tool. If the dependency changes, the spells section must change and the overall document must change. The ``qa.txt`` file depends on a ``qa.py`` module. The module may, in turn, depend on a ``qa.ipynb`` file in a separate ``notebooks`` directory. These dependencies are **not** visible to tools like **Sphinx**. These files are created by dependency and recipe rules in separate ``Makefile``\ s in the ``spells`` and ``characters`` directories. A project will often have three distinct Makefiles: - The overall document ``Makefile``. This is generated by the ``sphinx-quickstart`` application. The ``opend6_tools`` require two modifications to this file. .. code-block:: Makefile :emphasize-lines: 2-3 %: Makefile %(MAKE) -C spells %(MAKE) -C characters @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) We've highlighted the two new lines used to invoke the **make** tool to update the spells and characters prior to publication. The first new commands looks for a ``spells/Makefile`` and uses this to update the spells. The second new command looks for a ``characters/Makefile``. These commands needs to reflect the overall structure of the document. - A ``spells/Makefile`` will update the RST files for spells and invocations. It looks like this: .. code-block:: Makefile :emphasize-lines: 14 # Spells Makefile .phony: spells vpath %.ipynb ../../notebooks # Create a Python Spell module from a Jupyter Notebook with the same name. %.py : %.ipynb python -m opend6_tools.notebook_extract spells $< > $@ # Create an RST text file from a Python Spell module with the same name. %.txt : %.py python $< display > $@ spells : spell_1.txt spell_2.txt, etc. The core recipes are provided. What's requires is the definition of the ``spells`` target. This lists all files that must be converted for the final documentation. The source files are implied by the recipes. - A ``characters/Makefile`` will emit RST files for characters and creatures. It looks like this: .. code-block:: Makefile :emphasize-lines: 14 # Characters and Creatures Makefile .phony: characters vpath %.ipynb ../../notebooks # Create a Python Character or Creature module from a Jupyter Notebook with the same name. %.py : %.ipynb python -m opend6_tools.notebook_extract characters $< > $@ # Create an RST text file from a Python Character or Creature module with the same name. %.txt : %.py python $< display --format SHORT > $@ characters : characters.txt creatures.txt The core recipes are provided. What's requires is the definition of the ``characters`` target. This lists all files that must be converted for the final documentation. The source files are implied by the two recipes. Note that there are a number of character formats. The ``SHORT`` format is used for most descriptions of characters and creatures. There are however, some longer formats available. - ``LONG`` Very detailed. - ``LONG2`` Less detailed, often used for non-human races. - ``SHORT`` The kind of summary shown in the Adventure Tips chapter. - ``TABLE`` A full character sheet using HTML table constructs. - ``LITERAL`` A full character sheet using RST literal constructs. The need for some ``LONG2`` characters for the non-human races, and ``TABLE`` for the character templates will make the characters ``Makefile`` somewhat more complicated.