arm5: sheet version 2.0 as git subtree merge

This commit is contained in:
Riernar
2023-06-20 21:02:41 +02:00
193 changed files with 46285 additions and 0 deletions
@@ -0,0 +1,33 @@
---
name: Bug report (display)
about: Report a bug about the display of the sheet
title: "[BUG] Display: TITLE HERE"
labels: bug, display
assignees: Riernar
---
### Describe the bug
A clear and concise description of what the bug is
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots
If applicable, add screenshots to help explain your problem.
### Desktop
*(please complete the following information):*
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Sheet version
### Additional context
Add any other context about the problem here.
@@ -0,0 +1,33 @@
---
name: Bug report (logic)
about: Report a bug about the logic of the sheet
title: "[BUG] Logic: TITLE HERE"
labels: bug, logic
assignees: Riernar
---
### Describe the bug
A clear and concise description of what the bug is
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots
If applicable, add screenshots to help explain your problem.
### Desktop
*(please complete the following information):*
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Sheet version
### Additional context
Add any other context about the problem here.
@@ -0,0 +1,17 @@
---
name: Development / Internals
about: Suggest an idea for the develoment of the sheet
title: "[DEV] TITLE HERE"
labels: development
assignees: Riernar
---
### Requested feature
A clear and concise description of what you want to happen.
### Alternative solutions
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context or screenshots about the feature request here.
+14
View File
@@ -0,0 +1,14 @@
---
name: Documentation
about: Report a problem in the documentation
title: "[DOC] TITLE HERE"
labels: documentation
assignees: Riernar
---
### Requested documentation
A clear and concise description of what you want to happen.
### Additional context
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEAT] TITLE HERE"
labels: enhancement
assignees: Riernar
---
### Requested feature
A clear and concise description of what you want to happen.
### Alternative solutions
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context or screenshots about the feature request here.
+25
View File
@@ -0,0 +1,25 @@
name: Ci/CD
on:
push:
branches: "**"
jobs:
compile:
name: "Compile sheet"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup npm
uses: actions/setup-node@v1
with:
node-version: 18
- name: Install npm packages
run: npm install
- name: Build test sheet
run: npm run build
+5
View File
@@ -0,0 +1,5 @@
.venv
local
node_modules
k-scaffold
**/__pycache__
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

+189
View File
@@ -0,0 +1,189 @@
# Ars Magica 5 sheet
This folder contains the source code for the Roll20 VTT sheet for the
Ars Magica 5th Edition, system.
The sheet was originally made by Coal Powered Puppet. It is currently maintained
by Medieve and Riernar.
## Getting help & providing feedback
If you have question, need help or would like to provide feedback, you can:
- Ask on the [unofficial Ars Magica 5th Discord](https://discord.gg/9M9fhwr6T9)
in the `#roll20-vtt` channel. Tag `@Riernar`. This is ideal for quick questions.
- Open an issue in [Roll20's sheet repository](https://github.com/Roll20/roll20-character-sheets/issues).
Include `[Ars Magica 5th]` in your title and tag `@Riernar`.
- Open an issue in [Riernar's own repository](https://github.com/Riernar/arm5-sheet)
where I develop the sheet. This is where I schedule up-coming features, so be sure
to check here if you have a feature request: maybe it's already in the work !
### Contributing
See Roll20's own guide on how to contribute to sheets _in general_. This section details
specific of this sheet.
If you have a feature request, please look in [Riernar's own repository](https://github.com/Riernar/arm5-sheet)
where feature request are recorded (so as not to bloat Roll20's repo with
issues).
When making a PR directly to the Roll20's repo, please tag @Riernar so that I can keep
the development repo above in sync.
### Tools
This repo uses the following tools:
- [PUG](https://pugjs.org/api/getting-started.html) is a template engine for HTML, which
makes building the sheet much easier, by having reusable code
- [SCSS](https://sass-lang.com/documentation/syntax) is similarly a way to write CSS much
more easily
- [k-scaffold](https://kurohyou-studios.github.io/k-scaffold/) is a set of
PUG mixins and SCSS tailored toward Roll20's character sheet, made by Scott Casey. It makes
building sheets much easier by provided standard element and logic used in sheets.
- [node.js](https://nodejs.org/en/) is a javascript runtime. We use it with `npm` to handle the
javascript package needed to build the sheet from the `.pug`, `.scss`, `.md` and `.js` source files.
In particular, the modules:
- `pug` is used to compile the source `.pug` files
- `sass` is used to compile the source `.scss` files
- `markdown-it` is used to render the markdown `.md` files inside the sheet
> **Note**
>
> We cannot use [prepros](https://prepros.io/), because we need to pass additional javascript values to
> pug during compilation, in particular `node.js`'s `require` function.
### Development environment
- You'll need an installation of `npm`. We recommend using [`nvm`](https://github.com/nvm-sh/nvm)
(Node _Version_ Manager) to manage `node.js` for you. It lets you have multiple versions of
node.js at the same time
- Once you have node and npm, run `npm install` in the directory to install the packages
described by the `package.json` file (this file is generated by npm when you use it).
### Building the sheet
You can use the
```bash
npm run start
```
command to tell `K-Scaffold` to monitor all the files in the `source/` repository and keep
the built sheet up-to-date. This works really well with Scott Casey's
[Roll20 API and Sheet Autouploader](https://chrome.google.com/webstore/detail/roll20-api-and-sheet-auto/hboggmcfmaakkifgifjbccnpfmnegick)
Chrome(ium) extension that will keep your sandbox up-to-date with the latest generated files.
Alternatively, yo generate the sheet once, run
```bash
npm run build
```
in a terminal.
## File organization and naming scheme
All the source files are in `source/`. The compiled files will be produced in the root folder.
All PUG and SCSS files _not_ starting with a leading underscore `_` will be compiled by K-Scaffold.
### Legacy code
This sheet is currently being ported from raw HTML/CSS with a python layer, to PUG/SCSS, k-scaffold
and javascript.
The last generated version of the sheet using the old tools is found in `source/legacy/`, splitted
and formatted appropriately so that it may be injected in PUG. This let us update parts of the sheet
in an incremental fashion.
The original source files for this generation are in `source/legacy/source` for reference only.
### New PUG code
All the source files are in `source/`. There are 4 kind of source files:
- `.pug` files are for generating HTML with PUG
- `.scss` files are for generating CSS with Sass
- `.js` files are included in pug to produce helpers, and also contain sheetworkers (
javascript that ends up as-is in the sheet and will be run inside the VTT).
- `.md` files are Markdown files wich are rendered to HTML by PUG. They are used to write
simple HTML, and also have files that can be viewed directly in github. Examples include
the sheet [documentation](documention.md) and the [changelog](changelog.md).
For javascript files, there are actually two javascript runtimes involved in that sheet:
- At **build** time, that is when the `node.js` package `pug` is rendering the `.pug` files to
HTML. PUG is written in javascript and allows javascript code inside the `.pug` files to
run to generate the sheet. This makes it very powerful.
Javascript files that are used at sheet generation time end in `.pug.js` to make it clear
when the javascript code is used.
- At **game** time, when the sheet is loaded and running inside Roll20. This is typically known
as _sheetworkers_: javascript that runs inside the VTT.
Javascritp files which are included in the sheet to be sheetworker are named `.sheet.js`
to make it clear when the javascript code is used.
## Advanced techniques used in the sheet
### Importing javascript into PUG runtime
When compiling PUG into HTML, we pass custom values into the PUG runtime with `generate.js`.
In particular, we pass node's `require` function, allowing us to load node packages or
our own `.js` files into the PUG runtime.
### Deferred attribute lookup
> **Note**
>
> The technique below pre-dates Custom Roll Parsing and could be replaced by CRP.
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 since 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.
File diff suppressed because one or more lines are too long
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
## Changelog
- **June 2023 - v2.0 - kScaffold update**
- Fix [#2](https://github.com/Riernar/arm5-sheet/issues/2):
Switch to PUG, SCSS and k-Scaffold framework to build the sheet
(PR [#1](https://github.com/Riernar/arm5-sheet/pull/1))
- Fix [#12](https://github.com/Riernar/arm5-sheet/issues/12):
Remove the sheet tab and use icon buttons
(PR [#13](https://github.com/Riernar/arm5-sheet/pull/13))
- Fix [#3](https://github.com/Riernar/arm5-sheet/issues/3):
Fix Lab Total computation when General Quality has a leading `+`
(PR [#15](https://github.com/Riernar/arm5-sheet/pull/15))
- Added an indicator that shows collapsed abilities's selected characteristic
- **February 2022 - v1.7.1 - Fix**
- Fixed accelerated abilities
- **January 2022 - v1.7 - Magic & Armory update**
- _General changes_:
- Added Welcome message to the "Sheet" tab, and made that tab the default tab
- Added tooltips to some elements of the sheet to provide info about them (e.g. XP fields)
- Repeated items (such as items, abilities, spells...) start expanded for easier completion.
- _Magic Update_:
- Added a toggle to hide elements of the sheet that are specific to Magi (see Sheet tab)
- Added deficiency toggles that halves casting totals
- Added a roll button for non-fatiguing spontaneous casting
- Separated the Artes Liberales and Philosophiae fields of ceremonial and ritual casting, to accomodate specialisation in either
- Added Soak computations with Form bonuses
- Added list for useful spontaneous spells in the "Spells" tab
- _Armory Update_:
- Added an armor list with togglable elements. Can store multiple armor (parts).
- Burden and Encumbrance are now computed automatically
- Added combat modifiers list that is applied to rolls
- **October 2021 v1.6.5**
- Fix to laboratory focus for characters with an affinity bonus to one of the arts used.
- **March 2021, v1.6.4**
- Added `lalign` and `ralign` to the `custom` rolltemplate
- **March 2021, v1.6.3**
- Added `roll-color` and `button-color` properties to the `custom` rolltemplate, with all CSSv3 colors
- **February 2021, v1.6.2**
- Fixed centering not working
- Fixed translation of spellcasting words options
- Added support for the `custom` rolltemplate from the forum, with all CSS colors. Can be useful for spell tables or similar chat menus
- **February 2021, v1.6.1**
- Fixed botch die botching on 1s instead of 10s
- Fixed critical dice displaying 10s as exploding, instead of 1s
- Fixed spell's arts value not being updated when the character's arts are modified
- Completely changed how arts are handled inside spells
- A script will be run to convert your data to the new format, but may fail for some spells
- Added a critical roll for initiative that sets the turn tracker initiative (not visible in the sheet, use the chat button)
- Added option to always ask which characteristic to use when rolling abilities
- Centered the sheet in its tab
- **February 2021, v1.6**
- Added inline labels to all rolls, hover rolls in chat to see details
- Fixed tabs not displaying properly _(tabs are again attached to an attribute)_
- Added simple/stress die handling. See the "Help" tab for explanation, there's a catch
- Support for focus in magic totals & rolls
- Support for additional fatigue levels
- Fixed ability rolltemplate being too wide in some cases
- Added documentation direclty to the sheet, see the "Help" tab
- **May 2020, v1.51**
- Small change to the Ability roll template for legibility
- **April 2020, v1.5**
- Incorporated new fonts throughout the sheet!
- Polished several roll templates and added new ones for Defending. Stylish!
- Added an extra button for rerolling stress explosions, can be found next to the botch button.
- Fixed several missing translation tags and side-effects of having translations added into the sheet. In particular, Abilities should now have access to the Accelerated Ability option.
- Updated the sheet space and button for Soak/Armor Protection to use the combat roller templates.
- Relinked images from imgur to github asset folders.
- **October 2019, v1.4**
- Initiatives now output to Roll20's Tracker while selecting a token.
- Character sheet tabs no longer attached to an attribute, so multiple players can view the same character sheet without wrestling over tab controls.
+193
View File
@@ -0,0 +1,193 @@
## Sheet Documentation
Welcome the Ars Magica 5th Edition character sheet. This page contains the documentation for the sheet.
### Experience
The fields for experience work as follows:
- the first number is your xp towards the next level. You can edit it.
- the second number is the xp needed to reach the next level. It is readonly.
- the third number is the total xp needed to reach your current level. It is readonly.
### Rolls and roll buttons
The sheet contains a number of button that roll dice, known as roll button. There are the three button right to the tabs: <span style="color:#169E9C">Roll</span>, <span style="color:#ee3f2f">Botch</span> and <span style="color:#DAA520">Critical</span> (in english, they might be translated to your language):
- <span style="color:#169E9C">Roll</span> rolls a 10-sided die, with no particular results (roll20's default formatting for 1s and 10s is disabled)
- <span style="color:#ee3f2f">Botch</span> handles rolling botch dice, see below
- <span style="color:#daa520">Critical</span> handles critical rolls, see below
There are also die-shaped roll buttons in the sheet near interesting totals (characteristics, abilities, spells etc.) for easy rolling. This sheet supports both simple dice and stress dice, for all of them.
#### Roll configuration
In the configuration section (above this documentation), you will find a setting for how you want to handle simple vs. stress rolls:
- on "both", you will have two roll buttons, the plain one rolling a simple die, the <span style="color:#ee3f2f">red</span> & <span style="color:#daa520">gold</span> one rolling a stress die
- on "toggle", a new button will appear left to the "Roll" button. It display the current type of roll, and you can click on it to toggle between simple and stress dice
#### Simple die
The icon for simple dice is a plain die, without particular colouring. When you roll a simple die from the sheet, the result in chat will be coloured <span style="color:#5e5e5e">grey</span> or <span style="color:white; text-shadow: 1px 0px 3px #666666">white</span> (note it is colorless).
A simple die has no special results, so no further formatting is needed.
#### Stress die
The icon for stress dice is a die with a <span style="color:#ee3f2f">red</span> side, and a <span style="color:#daa520">gold</span> side, reflecting the possibility of a botch or a critical roll. When you roll a stress die from the sheet, the result will be coloured:
- <span style="color:#8d4f23">brown</span> or <span style="color:#c2a973">khaki</span> if the result is nothing special
- <span style="color:#ee3f2f">red</span> on a botch
- <span style="color:#daa520">gold</span> on a critical (natural 1)
The formula for a stress die (you can see it by hovering over a result, see also "Labels" below) is quite complex, but does something simple:
- on a 0 (or 10 on the standard d10), the value will be 0. You then roll botch dice to see if you botch
- on a critical (natural 1), the value will also be 0
- Otherwise, the result is whatever was rolled
#### Botch
When you roll a 0, the formula for a stress die yields a value of 0 (though other modifiers will apply). A roll of 0 can be easily seen, as the result will be bright red. You must then roll botch dice. To roll botch dice, you have to equivalent options:
- click on <span style="color:#ee3f2f">Botch</span> right to the tabs on top of the sheet
- click the "botch!" button n direclty in the chat near the result
In the configuration section (above this documentation), you will find a setting for how you want to roll botch dice:
- on "separate", all botch die will be displayed individually in the chat, and you choose the number of botch dice with a dropdown menu (limited to 8 botch dice)
- on "grouped", only the total number of botch will be displayed (i.e. the number of 0s rolled). You enter the number of botch dice as a number. You can still see the individual rolls by hovering on the result in chat
When you do botch (i.e roll 10s), the output will be coloured bright red.
#### Critical
When you roll a natural 1 on a stress die, you rolled a critical. The result will be displayed in bright <span style="color:#daa520">gold</span> colour. In that case, the formula for a stress die has value 0, so that you can add the result of the dice to your modifiers and bonuses. To roll the critical dice, you have two nearly equivalent options:
- click on the <span style="color:#DAA520">Critical</span> button right to the tabs
- click the "critical!" golden text next to the result in chat
In both case, you will be asked for the previous total (just copy-paste the value), and the die special Ars Magica 5th doubling die will be rolled and added (the formula does now you already rolled at least a 1s, and so must multiply by at least 2). The displayed result is the sum, i.e. the final result.
> **Warning**
>
> When you crit on a _spontaneous casting roll_ or _initiative roll_, always use the "critical!" golden text next to the result in chat. Those button do not call the standard critical roll, but specific ones:
>
> - For spontaneous rolls, it takes into account the divide by 2
> - For initiative rolls, it will set your token's initiative in the turn tracker
As the exploding rolls of Ars Magica are quite hard to handle, there's a caveat in the formula if you use 3D dice in roll20: _the last result you see on screen is wrong_ as the last rolled die is rerolled. This is because the formula splits the roll in two parts:
- the first computes the number of 1s you rolled (for each 1, it will multiplie the final roll by 2, as in the Ars Magica rules). But in doing that, the last (non-1) roll is lost. This is a limitation of the roll20 dice engine
- the second part rerolls the last result as [[1d9+1]], a weird dice that takes values from 2 to 10 (because the last roll cannot be 1, otherwise it is not the last)
This might be a bit weird, but this yields exactly the same results (that is, you have the exact same chance to roll a given number) than what the Ars Magica 5 rules describe. The problem is only visual.
Other than that, the formula is correct: it does takes into account that you already rolled a 1 before clicking on the "Critical" button, and the previous total is also correct. No need to substract 1, or multiply by two, just copy-paste your previous result and read the final total in the chat.
#### Inline Labels
When you roll from the sheet, the rolltemplates displayed in the chat will show some values that went into the roll. You can hover on the final result with your mouse to see all the values that were used in the roll. The values are labelled so that you know where that +5 comes from.
### Roll buttons
The following roll buttons are defined in the sheet and may be called using ability command buttons:
- `reroll`
- `botch`
- `critical`
- `critical-spontaneous`
- `critical-init`
- `personality1_simple` / `personality1_stress` through `personality6_simple` / `personality6_stress`
- `reputation1_simple` / `reputation1_stress` through `reputation6_simple` / `reputation6_stress`
- `Intelligence_simple` / `Intelligence_stress`
- `Perception_simple` / `Perception_stress`
- `Strength_simple` / `Strength_stress`
- `Stamina_simple` / `Stamina_stress`
- `Presence_simple` / `Presence_stress`
- `Communication_simple` / `Communication_stress`
- `Dexterity_simple` / `Dexterity_stress`
- `Quickness_simple` / `Quickness_stress`
- `ability_simple` / `ability_stress` (inside a repeating section, needs special handling)
- `Formulaic_simple` / `Formulaic_stress`
- `Ritual_simple` / `Ritual_stress`
- `soak`
- `weapon_Init`
- `weapon_Atk`
- `weapon_Dfn`
- `spell_simple` / `spell_stress` (inside a repeating section, needs special handling)
### Roll Template
This is the list of available roll template and what value they support.
All rolltemplate support the following values:
- `stress`: If any value is supplied, indicate that the roll is a stress rolls. Changes the colors of the main roll, and displays the following buttons if appropriate
- `botch-button`: ability button to roll the botch dice if the roll is a botch. Usually `[@{botch_i18n}!](~@{character_name}|botch)`
- `crit-button`: ability button to roll the critical dice if the roll is a critical. Usually `[@{critical_i18n}!](~@{character_name}|critical)`, but not always (e.g. spontaneous and initiative rolls are special-cases)
Those are omitted in the following list of rolltemplate names and supported values:
- **`custom`**:
- This is the [improved default template](https://wiki.roll20.net/Building_Character_Sheets/Roll_Templates#Jakob.27s_Better_Default_Rolltemplate)
created by Jakob. All CSS v3 colors are supported by the `color` key (names must be all lowercase)
- In addition, the rolltemplate supports the `roll-color` and `button-color` to change the background color of inline rolls and ability buttons, respectively, within the template. Both values support all CSS v3 colors, as `color` does. Those also support the `transparent` color.
- The keys `left-align` and `right-align` can be used to control the text alignement of the left and right column, respectively. Values can be "left", "center" or "right".
- **`spell`**:
- `character`
- `duration`
- `effect`
- `Form`
- `Level`
- `mastery`
- `range`
- `roll`
- `sigil`
- `spell`
- `target`
- `Technique`
- **`botch`**:
- `roll`
- `type`
- **`reroll`**:
- `roll`
- **`critical`**:
- `roll`
- `type`
- **`attack`**:
- `attack`
- `damage`
- `name`
- **`defend`**:
- `defend`
- `name`
- **`soak`**:
- `armorsoak`
- `name`
- `rollsoak`
- `soakbonus`
- `formlabel`
- `formbonus`
- **`initiative`**:
- `name`
- `roll`
- `weapon`
- **`ability`**:
- `banner`
- `label0` through `label4`
- `name`
- `result0` through `result4`
- **`generic`**:
- `Banner`
- `Label`
- `Result`
- **`arcane`**:
- `label0` through `label6`
- `result0` through`result6`
- `textlabel`
- `textsublabel`
- `textfield`
+15
View File
@@ -0,0 +1,15 @@
//Require the K-scaffold that we installed via NPM
const k = require('@kurohyou/k-scaffold');
const fs = require("fs");
const kOpts = {
destination: '.',
testDestination: './__tests__',
source: './source',
pugOptions: { "require": require, "fs": fs },
};
if (process.argv[2] === '--watch') {
kOpts.watch = true;
}
k.all(kOpts);
+13
View File
@@ -0,0 +1,13 @@
## Getting help & providing feedback
If you have question, need help or would like to provide feedback, you can:
- Ask on the [unofficial Ars Magica 5th Discord](https://discord.gg/9M9fhwr6T9)
in the `#roll20-vtt` channel. Tag `@Riernar`. This is ideal for quick questions.
- Open an issue in [Roll20's sheet repository](https://github.com/Roll20/roll20-character-sheets/issues).
Include `[Ars Magica 5th]` in your title and tag `@Riernar`.
- Open an issue in [Riernar's own repository](https://github.com/Riernar/arm5-sheet)
where I develop the sheet. This is where I schedule up-coming features, so be sure
to check here if you have a feature request: maybe it's already in the work !
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<ellipse class="st0" cx="16" cy="8" rx="10" ry="5"/>
<path class="st0" d="M6,8v8c0,2.8,4.5,5,10,5c5.5,0,10-2.2,10-5V8"/>
<path class="st0" d="M6,16v8c0,2.8,4.5,5,10,5c5.5,0,10-2.2,10-5v-8"/>
</svg>

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<circle class="st0" cx="24" cy="23" r="7"/>
<line class="st0" x1="24" y1="20" x2="24" y2="26"/>
<line class="st0" x1="21" y1="23" x2="27" y2="23"/>
<path class="st0" d="M26.5,3c0.2,0,0.3,0,0.5,0c2.6,0.3,3.9,3.4,2.4,5.6L24.5,16"/>
<line class="st0" x1="26" y1="3" x2="9.5" y2="3"/>
<path class="st0" d="M11.3,10.3l-8.7,13C1.2,25.5,2.4,28.7,5,29c0.2,0,0.3,0,0.5,0h14.9"/>
<path class="st0" d="M9,3C6.8,3,5,4.8,5,7c0,0.5,0.2,1,0.4,1.4c0.6,1,1.8,1.6,3,1.6H24c-1,0-3.1-2.8-0.9-5.5C23.8,3.6,24.9,3,26,3
c2.2,0,4,1.8,4,4"/>
<line class="st0" x1="13" y1="15" x2="18" y2="15"/>
<line class="st0" x1="11" y1="18" x2="14" y2="18"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<circle class="st0" cx="24" cy="23" r="7"/>
<line class="st0" x1="21.9" y1="20.9" x2="26.1" y2="25.1"/>
<line class="st0" x1="21.9" y1="25.1" x2="26.1" y2="20.9"/>
<path class="st0" d="M26.5,3c0.2,0,0.3,0,0.5,0c2.6,0.3,3.9,3.4,2.4,5.6L24.5,16"/>
<line class="st0" x1="26" y1="3" x2="9.5" y2="3"/>
<path class="st0" d="M11.3,10.3l-8.7,13C1.2,25.5,2.4,28.7,5,29c0.2,0,0.3,0,0.5,0h14.9"/>
<path class="st0" d="M9,3C6.8,3,5,4.8,5,7c0,0.5,0.2,1,0.4,1.4c0.6,1,1.8,1.6,3,1.6H24c-1,0-3.1-2.8-0.9-5.5C23.8,3.6,24.9,3,26,3
c2.2,0,4,1.8,4,4"/>
<line class="st0" x1="13" y1="15" x2="18" y2="15"/>
<line class="st0" x1="11" y1="18" x2="14" y2="18"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

+1507
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"scripts": {
"build": "node generate.js",
"start": "node generate.js --watch"
},
"dependencies": {
"@kurohyou/k-scaffold": "^1.5.1",
"markdown-it": "^13.0.1",
"pug": "^3.0.2",
"sass": "^1.56.1"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"html": "Ars_Magica_5th.html",
"css": "Ars_Magica_5th.css",
"authors": "Medieve (Richard T.), original sheet by Coal Powered Puppet. Maintained by Riernar.",
"patreon": "https://www.patreon.com/sceneficant",
"roll20userid": "492849,23472,2080144",
"preview": "Ars_Magica_5th.png",
"instructions": "# Ars Magica 5th\n\n**For use with Ars Magica 5th edition** \n\n [Documentation](https://github.com/Roll20/roll20-character-sheets/blob/master/Ars_Magica_5th/documentation.md) and [Changelog](https://github.com/Roll20/roll20-character-sheets/blob/master/Ars_Magica_5th/changelog.md) for the sheet (Also found inside the sheet, in-game). \n\n If there's something you want added, or you'd like to report a bug, please contact the maintainer on github or [Discord](https://discord.gg/9M9fhwr6T9).",
"legacy": false
}
+54
View File
@@ -0,0 +1,54 @@
include k-scaffold
//- Object used to store the whole system, must be defined first
//- To clarify difference between sheet code and pug code, we use the
//- arm5.pug namespace for code that runs during sheet generation, and
//- arm5.sheet namespace for code that runs during sheet loading in the VTT
- const arm5 = {};
include helpers/_helpers.pug
include elements/_elements.pug
- k.version = "2.0";
//- We are now ready to create the sheet
include updates/_updates.pug
//- include legacy/_alerts.pug
//- +alert({name: "test-info"})
//- | This is a test alert with level info
//- +alert({name: "test-warning", level: "warning"})
//- | This is a test alert with level warning
//- include tabs/tabs.pug
.kscaffold-compatible.global-alerts
+alerts({name:"global-alerts", closeTrigger: {triggeredFuncs: ["onAlertRemoved"]}})
.arm5-sheet
include header/_header.pug
include legacy/_legacy.pug
<!-- Sheet Worker Scripts -->
+kscript
| k.debugMode = true;
| k.sheetName = "ArM5";
| k.version = "2.0";
|
| // k-scaffold fix ?
| const persistentTabs = [];
//- object used to store the whole system, must be defined first
//- To clarify difference between sheet code and pug code, we use the
//- arm5.pug namespace for code that runs during sheet generation, and
//- arm5.sheet namespace for code that runs during sheet loading in the VTT
|
| // Top-level object for the sheetworkers
| const arm5 = {sheet: {}};
|
//- Legacy sheetworkers
| // Legacy sheetworkers
|
include legacy/legacy.js
|
+26
View File
@@ -0,0 +1,26 @@
/*
@use 'k-scaffold/k.scss';
@use 'mixins/sheet.scss';
@use 'mixins/elements.scss';
@use 'tabs/tabs.scss';
*/
@use 'k-scaffold' as k;
@use 'legacy/_legacy.scss';
@use 'helpers/_helpers.scss';
@use 'elements/_elements.scss';
@use 'header/_header.scss';
@import url('https://fonts.googleapis.com/css?family=Delius|Bilbo+Swash+Caps|Kavivanar|Fredericka+the+Great|Macondo|Uncial+Antiqua|Material+Icons|Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap');
.ui-dialog .charsheet .kscaffold-compatible {
@include k.defaultStyles;
}
.ui-dialog .tab-content .charsheet {
.global-alerts {
background: white;
border-radius: 5px;
}
}
+33
View File
@@ -0,0 +1,33 @@
# `source/` folder
This folder contains the PUG, SCSS and javascript sources to build the sheet with
k-Scaffold.
## Layout
- `legacy/`
old sheet building code (raw HTML with a python layer) as well as
the last build of the sheet with this legacy build. The last build has been
reformatted and splitted so that it may be re-used in the new buid system for
elements that have not yet be entirely re-coded (i.e. almost everything).
- `helpers/`
various helpers for building the sheet (PUG mixins, javascript code, etc...) that
are not specific to Ars Magica 5th. Those could reasonably be pasted in another
sheet and re-used with minimal tweaks.
- `header/`
Sources for the header bar at the top of the sheet
- `updates/`
Sources for the update function and code, that keep the sheet up-to-date when an old
character is opened with a newer sheet.
- `shared/`
Some shared components that needs to be accessed from multiple files
The following folders are currently _not_ used in the sheet building and contains experimental
code, WIP improvements and re-work of the sheet.
- `elements/`
Basic elements to build the sheet from.
- `mixins/`
More complex building blocks in the form of PUG mixins.
- `nested_fieldset/`
An attempt at nesting repeating section. Highly experimental and absolutely non-functional.
@@ -0,0 +1,5 @@
.ui-dialog .tab-content .charsheet {
--arm5-color-accent-brown: #53332a;
}
@@ -0,0 +1,25 @@
mixin modalButton({name})
- name = actionButtonName(replaceProblems(name));
- attributes.class = attributes.class ? ` ${replaceProblems(attributes.class)}` : '';
- attributes.class = `kmodal kmodal--${name} kmodal__button kmodal--${name}__button${attributes.class}`;
- attributes.for = `kmodal-${name}`;
label&attributes(attributes)
block
mixin modalWindow({name})
- name = actionButtonName(replaceProblems(name))
- attributes.class = attributes.class ? ` ${replaceProblems(attributes.class)}` : '';
- attributes.class = `kmodal kmodal__outer kmodal--${name} kmodal--${name}__outer${attributes.class}`;
input(class=`kmodal kmodal--${name} kmodal__checkbox kmodal--${name}__checkbox` type="checkbox" id=`kmodal-${name}`)
div&attributes(attributes)
label(class=`kmodal kmodal__background kmodal--${name} kmodal--${name}__background` for=`kmodal-${name}`)
div(class=`kmodal kmodal__inner kmodal--${name} kmodal--${name}__inner`)
label(class=`kmodal kmodal__close kmodal--${name} kmodal--${name}__close` for=`kmodal-${name}`)
div(class=`kmodal kmodal__content kmodal--${name} kmodal--${name}__content`)
block
mixin modalContainer()
.kmodal.kmodal__container&attributes(attributes)
block
@@ -0,0 +1,142 @@
@forward 'colors.scss';
@use 'colors.scss';
.ui-dialog .tab-content .charsheet {
.flexrow {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 0;
padding: 0;
}
.flexcol {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
margin: 0;
padding: 0;
}
// modal window and button styling
// mke the top-level .charsheet div the default root of position: absolute; elements
position: relative;
top: 0;
bottom: 0;
left: 0;
right: 0;
--color-kmodal-background: rgba(0, 0, 0, .9);
.kmodal__container {
position: relative;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.kmodal__button {
all: initial;
display: flex;
margin: 0;
padding: 0;
cursor: pointer;
}
.kmodal__checkbox {
display: none;
}
.kmodal__outer {
opacity: 0;
visibility: hidden;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
text-align: left;
background: var(--color-kmodal-background);
transition: opacity .25s ease;
z-index: 999;
}
.kmodal__background {
all: initial;
margin: 0;
padding: 0;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
cursor: pointer;
}
.kmodal__inner {
position: absolute;
top: -20%;
right: 0;
bottom: 0;
left: 0;
width: 90%;
margin: auto;
overflow: auto;
background: #fff;
border-radius: 5px;
padding: 1em 2em;
height: 90%;
transition: opacity .25s ease;
}
.kmodal__close {
position: absolute;
right: 1em;
top: 1em;
width: 1.1em;
height: 1.1em;
cursor: pointer;
}
.kmodal__close:after,
.kmodal__close:before {
content: '';
position: absolute;
width: 2px;
height: 1.5em;
background: #ccc;
display: block;
transform: rotate(45deg);
left: 50%;
margin: -3px 0 0 -1px;
top: 0;
}
.kmodal__close:hover:after,
.kmodal__close:hover:before {
background: #aaa;
}
.kmodal__close:before {
transform: rotate(-45deg);
}
.kmodal__checkbox:not(:checked)+.kmodal__outer {
display: none;
}
.kmodal__checkbox:checked+.kmodal__outer {
opacity: 1;
visibility: visible;
}
.kmodal__checkbox:checked+.kmodal__outer .kmodal__inner {
top: 0;
}
}
+125
View File
@@ -0,0 +1,125 @@
include ../legacy/topbar.pug
mixin arm5_help()
.welcome
h1.heading_label Ars Magica 5th Edition Sheet (version 2.0)
br
div(data-i18n="welcome-title")
| Welcome to the Ars Magica 5th Edition Sheet !
+markdown({file: "./help_feedback.md"})
.welcome
h3.heading_label Forenote
br
div(data-i18n="welcome-0")
| Here are a few important notes you'll need to read before using the sheet. A more complete documentation can be found below.
br
div(data-i18n="welcome-1")
| First, this sheet features some configuration options, found below. Be sure to review those. Configure is your character is a Magi, how you want botches to be rolled and display in chat, and how you want to chose between simple and stress rolls (when applicable).
br
div(data-i18n="welcome-2")
| Second, you might want to read a bit the section on the documentation on how rolls works. The stress dice of Ars Magica 5th edition are quite complicated, and this sheet isn't <i>quite</i> able to handle them completely automatically. You'll need to copy the result before the critical roll, for instance.
br
div
span(data-i18n="welcome-3a") Third, some element of the sheet are
span.has_tooltip
span(data-i18n="welcome-3b") underlined
span(data-i18n="tooltip-welcome").sheet-tooltip Underlined texts feature a tooltip with additional informations
| :
span(data-i18n="welcome-3c") this means they have a tooltip that shows additional info about them.
+markdown({file: "./documentation.md"})
mixin arm5_settings()
h3.heading_label(data-i18n="config-configuration")
Configuration
.config
span(data-i18n="config-botch") Botch formula
+select({name: "botch_formula"}).select_1
+option({"value": "&{template:botch} {{roll= [[{[[?{@{botch_num_i18n}|1}]]d10cs0cf10}=10]] }} {{type=Grouped}}", "data-i18n": "config-botch-grouped", "selected": true})
| Grouped rolls
+option({"value": "&{template:botch} {{roll= ?{@{botch_num_i18n} | 1 Die,[[1d10cf10cs0]]|2 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]]|3 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]|4 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]|5 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]|6 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]|7 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]|8 Dice,[[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]] [[1d10cf10cs0]]} }} {{type=Grouped}}", "data-i18n": "config-botch-separate"})
| Separate rolls
span(data-i18n="config-dice") Dice type
// TODO: switch to snake_case
+select({name: "die-type-selection"}).select_1
+option({value: "both", "data-i18n": "config-dice-both", selected: true})
| Both
+option({value: "toggle", "data-i18n": "config-dice-toggle"})
| Toggle
span(data-i18n="config-magi") Magi character ?
// TODO: switch to snake_case
+select({name: "isMagi"})
+option({value:"yes", "data-i18n":"config-magi-yes", selected:true})
| Yes
+option({value:"no", "data-i18n": "config-magi-no"})
| No
mixin arm5_legacy()
h3(data-i18n="legacy-title").heading_label Legacy sheet elements
br
div(data-i18n="legacy-content").welcome
| This section contains part of the sheet that are no longer used, but cannot be removed because they contain character data.
br
// This is legacy code
<div style="margin-top: 15px;">
<h4 class="heading_label" data-i18n="armor" >Armor</h4>
<table class="table_2" style="width:800px;">
<tr>
<th colspan="2" data-i18n="armor-worn" >Armor Worn</th>
<th colspan="2" data-i18n="combat-modifiers" >Combat Modifiers</th>
</tr>
<tr>
<th colspan="2"><textarea class="textarea_2" name="attr_ArmorWorn"></textarea></th>
<th colspan="2"><textarea class="textarea_2" name="attr_ArmorModifiers"></textarea></th>
</tr>
</table>
</div>
.kscaffold-compatible
+modalWindow({name: "settings"})
+arm5_settings
+modalWindow({name: "help"})
+arm5_help
+modalWindow({name: "history"})
+arm5_legacy()
+markdown({file: "./changelog.md"})
.flexrow.top_bar.header
+legacy_header().flexrow.header__main
.flexcol.header__controls
+modalButton({name: "settings"})
span.material-icons(disabled) settings
+modalButton({name: "help"})
span.material-icons help
+modalButton({name: "history"})
span.material-icons history
+link({name:"Discord", href:"https://discord.gg/9M9fhwr6T9", color:"cornflowerblue"})
span.material-icons(disabled=true) forum
+30
View File
@@ -0,0 +1,30 @@
.ui-dialog .tab-content .charsheet {
.header {
justify-content: flex-end;
>.header__main {
flex-grow: 1;
}
> .header__controls {
flex-grow: 0;
gap: 2px;
align-self: stretch;
justify-content: flex-end;
margin-left: 15px;
margin-right: 5px;
margin-bottom: 5px;
.material-icons {
color: var(--arm5-color-accent-brown);
opacity: 0.85;
user-select: none;
&:hover {
opacity: 1;
}
}
}
}
}
@@ -0,0 +1,96 @@
//- Helpers
//- Load the PUG-side code of the helpers
//- The require() node.js function needs to be passed into the PUG when calling the PUG compiler
//- This is done in generate.js, called by `npm run start|build` (see package.json)
//- - arm5.helpers = require("./source/helpers/helpers.pug.js").helpers;
//- Allows to transfer data from the PUG runtime to the sheet runtime
mixin transfer_data_to_sheet(destination, data)
| // Data that was created during sheet building and persisted
| !{destination} = !{JSON.stringify(data, null, 2)};
//- Alert are call-outs/admonition
//- TODO: remove once kScaffold has it
//- @pugdoc
name: alerts
description: A mixin that creates a fieldset containing alerts (aka callouts, admonitions). The fieldset name is prepended by `alerts--`. All elements added by this mixin are in a containing div, to which any attributes passed to the mixin will be applied. The content of the mixin is ignored.
arguments:
- {string} name - The name of the repeating section. Will be prefixed with `repeating_alerts--` and spaces will be replaced with dashes (`-`).
- {object} [fieldsetTrigger] - A trigger object that is passed to the underlying `fieldset()`. `Optional`.
- {object} [closeTrigger] - A trigger object that is passed to the underlying `action()` mixin used to make the close button. `Optional`. If not provided, defaults to `{triggeredFuncs: ["kDeleteAlert"]}`. If you override this default, it is your responsability to call `kDeleteAlert`.
attributes:
example: |
include ../_k.pug
// A simple alert declaration.
+alerts({name:"global-alerts"})
// An alert declaration with a trigger and attributes
+fieldset({name:'global-alerts',trigger:{triggeredFuncs:['doWhenRemoved']}})(class="my-class")
mixin alerts({name, fieldsetTrigger, closeTrigger})
- name = actionButtonName(replaceProblems(name));
- attributes.class = attributes.class ? ` ${replaceProblems(attributes.class)}` : '';
- attributes.class = `kalerts kalerts--${name} kalerts__container kalerts--${name}__container${attributes.class}`;
- closeTrigger = closeTrigger || {triggeredFuncs: ["kDeleteAlert"]};
div&attributes(attributes)
+fieldset({name: `alerts--${name}`, trigger: fieldsetTrigger})
+hidden({name: "level", value: "info", class: `kalerts kalerts--${name} kalerts__level kalerts--${name}__level`})
div(class=`kalerts kalerts--${name} kalerts__content kalerts--${name}__content`)
span(class=`kalerts kalerts--${name} kalerts__title kalerts--${name}__title`)
+adaptiveInput({type: "text", name: "title", readonly: true, value: "title"})
+adaptiveTextarea({name: "text", readonly: true, class:`kalerts kalerts--${name} kalerts__text kalerts--${name}__text`})
+action({name: "close", class:`kalerts kalerts--${name} kalerts__close kalerts--${name}__close`, trigger: closeTrigger})
mixin link({name, href, color})
- let value = `/w @{character_name} &{template:custom} {{title=${name}}} {{desc=[${href}](${href})}}`;
-
if (color)
value = `${value} {{color=${color}}} {{button-color=${color}}}`;
//- .kscaffold-compatible
span.link
button(type="roll" value=value title=href)
block
//- In Roll20 sheet, <a href> tag can only link to the sheet itself
//- We replace <a href> tags in markdown rendering by a roll button similar to the
//- +link() mixin above
-
const md = require("markdown-it")({"html": true});
md.renderer.rules["link_open"] = function link_open(tokens, idx, options, env, self) {
const token = tokens[idx];
const href = Object.fromEntries(token.attrs)["href"];
if (href === undefined) { throw new Error(`No href found on token\n${token}`); }
const value = `/w @{character_name} &{template:custom} {{title=Link}} {{desc=[${href}](${href})}} {{color=mediumblue}} {{button-color=mediumblue}}`;
return `<span class="link"><button type="roll" value="${value}" title="${href}">`;
}
md.renderer.rules["link_close"] = function link_close(tokens, idx, options, env, self) {
return "</button></span>";
}
mixin markdown({file, string})
- let markdown = "";
if file
-
const fs = require("fs");
if (! fs.existsSync(file)) { throw new Error(`${file} doesn't exists`); }
markdown = md.render(fs.readFileSync(file, "utf8"));
if string
- markdown = md.render(string);
if markdown
div.markdown
!= markdown
//- The +module() mixin comes from k-scaffold: https://kurohyou-studios.github.io/k-scaffold/pug#module
//- It let us schedule PUG code to be run when injecting sheetworker source code into the sheet
+module
//- Include the sheet-side source code of our helpers
include helpers.sheet.js
+200
View File
@@ -0,0 +1,200 @@
@use 'k-scaffold' as k;
@use '../shared/heading_label.scss';
@import url('https://fonts.googleapis.com/css?family=Material+Icons|Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap');
.ui-dialog .tab-content .charsheet {
.hidden {
display: none;
}
// Alert system
.kalerts__container {
// Default, for unknown settings
// NOTE: since this is injected in rgba() calls, this MUST be in rgb
// --color-kalerts: fuschia;
--color-kalerts: 255, 0, 255;
div.repcontrol {
display: none;
}
.kalerts__content {
// Having a position property here breaks the action button afterwards ...
// position: relative;
top: 0;
left: 0;
margin: 1.5px;
padding: 3px;
background-color: rgba(var(--color-kalerts), 0.1);
border: 1px solid rgba(var(--color-kalerts), .8);
border-radius: 0.75em;
.kalerts__title {
color: rgb(var(--color-kalerts));
margin-bottom: 0px;
display: flex;
align-items: center;
width: 100%;
.adaptive {
flex-grow: 1;
}
&::before {
@include k.materialStyle;
font-family: "Material Symbols Outlined";
font-size: 16px;
content: "report";
}
& input {
font-size: 16px;
font-weight: 600;
flex-grow: 1;
}
}
.kalerts__text {
display: block;
width: 100%;
}
.kalerts__close {
position: absolute;
top: 8px;
right: 8px;
background: transparent;
border: none;
width: 1.1em;
height: 1.1em;
cursor: pointer;
}
.kalerts__close:after,
.kalerts__close:before {
content: '';
position: absolute;
width: 2px;
height: 1.5em;
display: block;
transform: rotate(45deg);
left: 50%;
margin: -3px 0 0 -1px;
top: 0;
background: rgba(var(--color-kalerts), .5);
}
.kalerts__close:before {
transform: rotate(-45deg);
}
.kalerts__close:hover:before,
.kalerts__close:hover:after {
background: rgba(var(--color-kalerts), 1);
;
}
}
.kalerts__level[value="info"]+.kalerts__content {
--color-kalerts: 100, 149, 237;
& .kalerts__title::before {
content: "info";
}
}
.kalerts__level[value="success"]+.kalerts__content {
--color-kalerts: 0, 128, 0;
& .kalerts__title::before {
content: "check_circle";
}
}
.kalerts__level[value="warning"]+.kalerts__content {
--color-kalerts: 255, 140, 0;
& .kalerts__title::before {
content: "warning";
}
}
.kalerts__level[value="error"]+.kalerts__content {
--color-kalerts: 139, 0, 0;
& .kalerts__title::before {
content: "error";
}
}
}
.link {
@include k.clear;
button {
cursor: pointer;
&::before {
content: '';
}
}
}
.markdown {
h1,
h2,
h3,
h4,
h5,
h6 {
@extend %heading-label-style;
margin-bottom: 5px;
margin-top: 15px;
}
p,
li {
font-family: 'Macondo';
color: #663333;
font-size: 14px;
}
li li {
font-size: 1em;
}
.codespan {
font-family: 'Delius';
padding-left: 2px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.05)
}
.link button[type=roll] {
font-family: 'Macondo';
font-size: 14px;
text-decoration-line: underline;
color: royalblue;
&:hover {
color: blue;
}
&::after {
@include k.materialStyle;
font-family: "Material Symbols Outlined";
font-size: 16px;
content: "arrow_outward";
margin-left: 1px;
}
}
}
}
@@ -0,0 +1,34 @@
- arm5.pug.sheet_data.alerts = [];
// Attribute used to store the display state of alerts
// Stored as a JSON string
+hidden({name:"arm5_sheet_alerts_states", value: "{}"})
mixin alert({name, level="info", container="div", closeTrigger="arm5.sheet.alerts.close"})
//- Check and sanitize arguments using k-scaffold functions
//- We use action button sanitization, because the name is later used as CSS classes
- name = actionButtonName(replaceProblems(name));
-
const levels = ["info", "warning"];
if (levels.indexOf(level) === -1 ) {
throw new Error(`Invalid alert level '${level}', expected one of ${levels}.`);
}
if (arm5.pug.sheet_data.alerts.indexOf(id) > -1 ) {
throw new Error(`Alert '${id}' is already registered.`);
}
div(class=`alert alert-level__${level} alert--${name} hidden`)
+action({name: `alert-close-${name}`, trigger:{triggeredFuncs:[closeTrigger]}})(data-alert-name=name, class=`alert--${name} alert__close alert--${name}__close`)
//- Inject the default classes into container
- attributes.class = attributes.class ? ` ${replaceProblems(attributes.class)}` : '';
- attributes.class = `alert__content alert--${name}${attributes.class}__content `;
#{container}&attributes(attributes)
block
- arm5.pug.sheet_data.alerts.push(name);
mixin sheetworker_helpers_alerts()
|
|
include alerts.sheetworker.js
@@ -0,0 +1,82 @@
.ui-dialog .tab-content .charsheet {
.hidden {
display: none;
}
.alert {
padding: 0px;
border-radius: 0.75em;
display: flex;
flex-flow: row-reverse nowrap;
align-items: flex-start;
}
.alert-level__info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
.alert__close:before,
.alert__close:after {
background: #519abe;
}
.alert__close:hover:before,
.alert__close:hover:after {
background: #31708f;
}
}
.alert-level__warning {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
.alert__close:before,
.alert__close:after {
background: #a94442;
}
.alert__close:hover:before,
.alert__close:hover:after {
background: darkred;
}
}
.alert__close {
position: relative;
background: transparent;
border: none;
padding: 0px;
margin: 0.5em;
flex-grow: 0;
margin-left: 1em;
width: 1.1em;
height: 1.1em;
cursor: pointer;
}
.alert__close:after,
.alert__close:before {
content: '';
position: absolute;
width: 2px;
height: 1.5em;
display: block;
transform: rotate(45deg);
left: 50%;
margin: -3px 0 0 -1px;
top: 0;
}
.alert__close:before {
transform: rotate(-45deg);
}
.alert__content {
flex-grow: 1;
margin: 1em;
}
}
@@ -0,0 +1,53 @@
arm5.sheet = arm5.sheet || {};
arm5.sheet.alerts = {
attributeName: "arm5_sheet_alerts_states",
showAlert({ name, attributes }) {
if (arm5.sheet.data.alerts.indexOf(name) === -1) {
k.log(`Cannot show alert '${name}': undefined alert`);
} else {
k.log(`Showing alert '${name}'...`);
$20(`.alert--${name}`).removeClass("hidden");
const state = JSON.parse(attributes[arm5.sheet.alerts.attributeName]);
state[name] = true;
attributes[arm5.sheet.alerts.attributeName] = JSON.stringify(state);
k.log(`Showing alert '${name}': done`);
}
},
hideAlert({ name, attributes }) {
if (arm5.sheet.data.alerts.indexOf(name) === -1) {
k.log(`Cannot show alert '${name}': undefined alert`);
} else {
k.log(`Hidding alert '${name}'...`);
$20(`.alert--${name}`).addClass("hidden");
const state = JSON.parse(attributes[arm5.sheet.alerts.attributeName]);
state[name] = false;
attributes[arm5.sheet.alerts.attributeName] = JSON.stringify(state);
k.log(`Hidding alert '${name}': done`);
}
},
close({ trigger, attributes }) {
const name = trigger.name.match(/alert-close-(.+)/)[1];
arm5.sheet.alerts.hideAlert({ name, attributes });
},
onOpen({ attributes }) {
const state = JSON.parse(attributes[arm5.sheet.alerts.attributeName]);
arm5.sheet.data.alerts.forEach(name => {
if (name in state && state[name]) {
arm5.sheet.alerts.showAlert({ name, attributes });
} else {
state[name] = false;
}
});
attributes[arm5.sheet.alerts.attributeName] = JSON.stringify(state);
}
};
k.registerFuncs({ "arm5.sheet.alerts.close": arm5.sheet.alerts.close });
k.registerFuncs({ "arm5.sheet.alerts.onOpen": arm5.sheet.alerts.onOpen }, { type: ["opener"] });
@@ -0,0 +1,12 @@
//- Object that will be persisted to the sheet
- arm5.pug.sheet_data = {};
mixin sheetworker_helpers_data_persistance()
//- arm5 object is created at the very top of the script tag
//- just after k-scaffold source code
|
|
| // Data that was created during sheet building and persisted
| arm5.sheet.data = !{JSON.stringify(arm5.pug.sheet_data, null, 2)};
@@ -0,0 +1,44 @@
mixin __dummy()
p
-
arm5.pug.js_loader = {
loaded_paths: [],
make_eval_context: (arm5_ref) => {
console.log("Building js_loader eval context...");
const arm5 = arm5_ref;
// Note: it is OK to use eval, this happens locally when we build
// the sheet. Running PUG is already running arbitrary javascript
// this is not worse.
return function(str) { eval(str) };
},
eval_context: null,
load_js: (path) => {
console.log(`Loading ${path}...`);
if (path.substring(0, 1) != "/") {
path = `${sourcedir}${path}`;
}
const loaded_paths = arm5.pug.js_loader.loaded_paths;
if (loaded_paths.indexOf(path) > -1) {
console.log(`${path} is already loaded, skipping.`);
} else {
const fs = require("fs");
if (! fs.existsSync(path)) {
throw new Error(`${path} doesn't exists`);
} else {
if (arm5.pug.js_loader.eval_context === null) {
arm5.pug.js_loader.eval_context = arm5.pug.js_loader.make_eval_context(arm5);
}
arm5.pug.js_loader.eval_context(fs.readFileSync(path, "utf8"));
loaded_paths.push(path);
}
}
}
}
@@ -0,0 +1,60 @@
mixin __dummy()
p
-
const js_loader2 = {
loaded_paths: {},
log: function(message) {
console.log(`[js_loader] ${message}`);
},
_load: function(path, namespace) {
const vm = require("node:vm");
const fs = require("fs");
js_loader2.log(`Reading ${path}...`);
const code = fs.readFileSync(path, "utf8");
js_loader2.log(`Reading ${path}: read\n${code}`);
js_loader2.log(`Reading ${path}: done`);
js_loader2.log(`Executing ${path}...`);
const context = {...namespace};
vm.createContext(context, {name: `Execution context for ${path}`});
vm.runInContext(code, context, {filename: path});
const that = vm.runInContext('this', context);
js_loader2.log(Object.keys(that));
js_loader2.log(`Executing ${path}: done`);
js_loader2.log(`Execution of ${path} produced:\n${JSON.stringify(context, null, 2)}`);
return context;
},
__load: function(path, namespace) {
}
load: function(path, namespace=null) {
js_loader2.log(`Loading ${path}...`);
if (path.substring(0, 1) != "/") {
path = `${sourcedir}${path}`;
}
if (namespace === null) {
namespace = {};
}
if (path in js_loader2.loaded_paths) {
js_loader2.log(`${path} is already loaded, skipping execution.`);
} else {
const fs = require("fs");
if (! fs.existsSync(path)) {
throw new Error(`${path} doesn't exists`);
}
js_loader2.loaded_paths[path] = js_loader2._load(path, namespace);
}
const result = Object.assign(namespace, js_loader2.loaded_paths[path]);
js_loader2.log(`Loading ${path}: done`);
return result;
}
};
@@ -0,0 +1 @@
@@ -0,0 +1,44 @@
@use '../../shared/heading_label.scss';
.ui-dialog .tab-content .charsheet {
.markdown {
h1,
h2,
h3,
h4 {
@extend %heading-label-style;
}
}
.documentation .markdown {
p,
li {
font-family: 'Macondo';
color: #663333;
font-size: 14px;
}
li li {
font-size: 1em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 5px;
margin-top: 15px;
}
.codespan {
font-family: 'Delius';
padding-left: 2px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.05)
}
}
}

Some files were not shown because too many files have changed in this diff Show More