Files
roll20-character-sheets/Ars_Magica_5th

Ars Magica 5th

Usage

This sheet uses python to generate the sheet's HTML and CSS from templates. Repetitive parts of the sheet are generated by python which ease the sheet's making. Therefore, the files Ars_Magica_5th.html and Ars_Magica_5th.css are programmatically generated. Do not modify it direclty, as all changes will be overwritten. Instead, edit template.html and template.css, and possibly the python code found in the folder arm5_py_integration/ if you which to use the python integration (more details below).

The general workflow is the following: 0. Install python and required packages (see below Python Integration > Installation)

  1. Modify template.html and/or template.css (You can use python integration to make things easier, see below)
  2. Run make (On windows, run make.bat which mimics what make do on unix).
    • This will execute fileval.py with proper argumens
  3. Commit your changes

Warning

Always execute make while the current working directory is this directory

Currently, the files generated are:

  • Ars_Magica_5th.html from template.html, documentation.html and additional code in arm5_py_integration/
  • Arsm_Magica_5th.css from template.css and additional code in arm5_py_integration/

Python Integration

The python integration allows to use python code to generate some parts of the sheet. It useful for repetitive sections (characteristics, arts, arts option, simple/stress rolls etc.) and integrating the markdown documentation directly into the sheet.

Installation

You need Python version 3.8 or higher to run the script. You can install it from the Python Website, or using your system's package manager such as apt, brew or others such as conda/miniconda to name a few.

The integration requires some additional package to convert the markdown documentation into HTML. To install them, use the pip commands provided by python (with some package manager, pip might require an additional package). Navigates to this directory and execute:

pip3 -r requirements.txt

Note:

On windows, you might need to run

python -m pip requirements.txt

to access to pip if it was not added to your PATH

The pip command will read the requirements.txt file alongside this Readme and install all the listed packages.

Quick overview

The python integration is provided by the script fileval.py. Simply put, it reads an input file, looks for python expressions between specific delimiters and then write an output file where the python expressions are replaced by their value.

For this sheet, the delimiter used is $$, in both template.html and template.css. It must be present at the start and end of an integrated expression (e.g. <span value=$$expression$$></span>). The expression are evaluated using python's eval function. The value of the expression must be a string (python type str).

The expression can be used anywhere in the html/css. The script does not parse the html/css but simply replaces delimited expressions by their value. For the sheet to be valid, the result must obviously be valid css/html, but fileval.py will not ensure that.

Basic usage

The simplest way to the integration is to use very simple expressions made of a single variable name, that will be replaced by the variable's value (e.g. $$my_name$$). You then write some python code to generate the value in the arm5_py_integration/ folder, and makes your value available to the fileval.py script. Most integrations in both template.html and template.css work this way.

The Makefile is currently configured so that fileval.py loads the arm5_py_integration directory as a python package (this is, executes the arm5_py_integration/__init__.py file) then loads all values from the GLOBALS variable of that package (defined in arm5_py_integration/__init__.py). Any value defined in GLOBALS will be available, be it a python package, a function, a value etc.

Note

You can put most of your code direclty in arm5_py_integration/__init__.py as most code is likely to be short. If you need longer code, you can also create another file in arm5_py_integration/ and import it in __init__.py. Look at arm5_py_integration/translation.py for an example of this.

Note

Since most code is basically writing a template a bunch of times with one or two value varying, you will find useful helpers in the file arm5_py_integration/helpers.py. Refer to the function's docstring and the examples in arm5_py_integration/__init__.py for how to use them.

Advanced usage

The python integration allows for all kind of python expression to be integrated into the template file, not just variables. You can use arithmetics, function calls, packages, the walrus operator etc. This raises the question of which values are availabes in the expressions.

The way the eval() function works (and any expression evaluation in python, for that matter), there are two namespaces that maps identifiers to their values:

  • the global namespace contains functions, variable, etc... that are available outside of the current scope, but nevertheless readable. It cannot be modified.
  • the local namespace contains things that are available, and will also store variables, functions etc... defined by the expression (local definitions; e.g. with the walrus operator)

When looking for an identifier, python will first look into the local namespace, and then into the global namespace. This means you can mask a global identifier with a local one, so be careful with that.

When starting, the fileval.py script will build the local and global namespaces to use for all evaluations. They are created empty, but can be populated with the --global-namespaces and --local-namespaces arguments in the command-line.

Note

Note that the same two namespaces are used for all expression inside the processed file. This means you can modify the local namespace with the walrus operator and re-use the value latter in the same file.

Those argumentes take a list of import names (the name written after import in a python import statement, e.g. re for import re and typing.Any in import typing.Any) and populate their respective namespace with the content of the package. You can also add a final dot and a name at the end of the dotted chain, with the name of a value inside the package, to populate the namespace with the content of that value (it must be a mapping, e.g. a dict).

For instance, in the Makefile (and make.bat), the values in arm5_py_integration/__init__.py:GLOBALS are imported with --global-namespaces arm5_py_integration.GLOBALS.

For further details, refer to the help message of fileval.py.

Documentation

This sheet includes a documentation in the file documenation.md that details how it works and provides some inner details (e.g. the available rolltemplates). When you generate the final sheet, the documentation is converted to HTML and embedded into the sheet, so that players can easily access it in-game.

Translation Notes

Some of the translation tag name have suffixes to indicate where they are used. If a particular tag is used in several places, I haven't added any suffixes.

  • -rt : Used in a roll template. Example: "defend-rt"
  • -m : Used in a roll button macros or similar. Example: "weakness-m"

Deferred attribute lookup

The sheet uses deferred attribute lookup for spells and abilities. This is simply a clever way of writing roll20's rolls, so that you fetches the value of an attribute named according to another attribute value. That is, if your spell's art is Creo, you store the value 'creo' in the attribute linked to the spell (so that you can use the name, e.g. for inline labels), but you're able to lookup the Creo_Score attribute for the roll.

The Roll20 dice engine accepts up to 99-levels of nested attributes lookup (using the @{attr_name} syntax). If you use adjacent attributes lookup that, when resolve, yields a new attribute lookup, the next pass will resolve that attribute lookup normally. This is exactly like having an attribute lookup inside another, but the inner lookup is spread into several attributes. The important things is that all parts are resolved during the same pass.

Example

An example and a table makes things easier to understand. The "system" attributes used to build the query have the same name as they do in the sheet. We use "NAME" as a placeholder for the character's name.

Initial roll formula: @{sys_at}@{character_name}@{sys_pipe}@{spell_tech_name}_Score@{sys_rbk}

First Pass

Input: @{sys_at}@{character_name}@{sys_pipe}@{spell_tech_name}_Score@{sys_rbk}

@{sys_at} @{character_name} @{sys_pipe} @{spell_tech_name} _Score @{sys_rbk}
VALUE @{ NAME | Creo _Score }

Output: @{NAME|Creo_Score}

Second Pass

Input: @{NAME|Creo_Score}

Output: the character's score in Creo

This makes it possible to use inline labels that shows the name of the attribute that was looked up, e.g.

@{sys_at}@{character_name}@{sys_pipe}@{spell_tech_name}_Score@{sys_rbk} [@{spell_tech_name}]

Assuming you have a Creo score of 3, this yields 3 [Creo], which tells you where that +3 comes from. The sheet pushes this further, as the inline labels use the same deferred attribute lookup technique, to translate the labels to you local langages: a sheet worker creates attributes ending in _i18n (short for internationalization) -- such as Creo_i18n -- that contain the local translation of the word. It then uses deferred attribute lookup to translate the inline label.

While this is not very useful for Arts (ince many langage just use the Latin word, it is indeed useful for translating characteristics names in ability rolls, or words & gestures in spell rolls etc.