├── .gitattributes ├── .npmignore ├── styles ├── soutane-webfont.eot ├── soutane-webfont.ttf ├── soutane-webfont.woff ├── soutanebold-webfont.eot ├── soutanebold-webfont.ttf ├── soutaneblack-webfont.eot ├── soutaneblack-webfont.ttf ├── soutaneblack-webfont.woff ├── soutanebold-webfont.woff ├── soutaneitalic-webfont.eot ├── soutaneitalic-webfont.ttf ├── soutaneitalic-webfont.woff ├── soutanebolditalic-webfont.eot ├── soutanebolditalic-webfont.ttf ├── soutanebolditalic-webfont.woff ├── texgyreadventor-bold-webfont.eot ├── texgyreadventor-bold-webfont.ttf ├── texgyreadventor-bold-webfont.woff ├── texgyreadventor-italic-webfont.eot ├── texgyreadventor-italic-webfont.ttf ├── texgyreadventor-italic-webfont.woff ├── texgyreadventor-regular-webfont.eot ├── texgyreadventor-regular-webfont.ttf ├── texgyreadventor-bolditalic-webfont.eot ├── texgyreadventor-bolditalic-webfont.ttf ├── texgyreadventor-regular-webfont.woff ├── texgyreadventor-bolditalic-webfont.woff ├── melee.svg ├── damage.svg ├── ranged.svg └── basicfantasyrpg.css ├── .gitignore ├── module ├── helpers │ ├── templates.mjs │ ├── chat.mjs │ ├── config.mjs │ └── effects.mjs ├── sheets │ ├── item-sheet.mjs │ └── actor-sheet.mjs ├── documents │ ├── item.mjs │ └── actor.mjs └── basicfantasyrpg.mjs ├── templates ├── actor │ ├── actor-sheet.html │ ├── parts │ │ ├── actor-description.html │ │ ├── actor-features.html │ │ ├── actor-effects.html │ │ ├── actor-spells.html │ │ ├── actor-floors.html │ │ ├── actor-items.html │ │ └── actor-combat.html │ ├── actor-stronghold-sheet.html │ ├── actor-siegeEngine-sheet.html │ ├── actor-character-sheet.html │ ├── actor-monster-sheet.html │ └── actor-vehicle-sheet.html └── item │ ├── item-sheet.html │ ├── item-item-sheet.html │ ├── item-armor-sheet.html │ ├── item-feature-sheet.html │ ├── item-spell-sheet.html │ ├── item-weapon-sheet.html │ ├── item-wall-sheet.html │ └── item-floor-sheet.html ├── LICENSE.txt ├── system.json ├── README.md ├── lang ├── en.json └── fr.json └── template.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea/ 3 | .vs/ 4 | 5 | # Node Modules 6 | node_modules/ 7 | npm-debug.log -------------------------------------------------------------------------------- /styles/soutane-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutane-webfont.eot -------------------------------------------------------------------------------- /styles/soutane-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutane-webfont.ttf -------------------------------------------------------------------------------- /styles/soutane-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutane-webfont.woff -------------------------------------------------------------------------------- /styles/soutanebold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebold-webfont.eot -------------------------------------------------------------------------------- /styles/soutanebold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebold-webfont.ttf -------------------------------------------------------------------------------- /styles/soutaneblack-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneblack-webfont.eot -------------------------------------------------------------------------------- /styles/soutaneblack-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneblack-webfont.ttf -------------------------------------------------------------------------------- /styles/soutaneblack-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneblack-webfont.woff -------------------------------------------------------------------------------- /styles/soutanebold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebold-webfont.woff -------------------------------------------------------------------------------- /styles/soutaneitalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneitalic-webfont.eot -------------------------------------------------------------------------------- /styles/soutaneitalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneitalic-webfont.ttf -------------------------------------------------------------------------------- /styles/soutaneitalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutaneitalic-webfont.woff -------------------------------------------------------------------------------- /styles/soutanebolditalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebolditalic-webfont.eot -------------------------------------------------------------------------------- /styles/soutanebolditalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebolditalic-webfont.ttf -------------------------------------------------------------------------------- /styles/soutanebolditalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/soutanebolditalic-webfont.woff -------------------------------------------------------------------------------- /styles/texgyreadventor-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bold-webfont.eot -------------------------------------------------------------------------------- /styles/texgyreadventor-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bold-webfont.ttf -------------------------------------------------------------------------------- /styles/texgyreadventor-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bold-webfont.woff -------------------------------------------------------------------------------- /styles/texgyreadventor-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-italic-webfont.eot -------------------------------------------------------------------------------- /styles/texgyreadventor-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-italic-webfont.ttf -------------------------------------------------------------------------------- /styles/texgyreadventor-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-italic-webfont.woff -------------------------------------------------------------------------------- /styles/texgyreadventor-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-regular-webfont.eot -------------------------------------------------------------------------------- /styles/texgyreadventor-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-regular-webfont.ttf -------------------------------------------------------------------------------- /styles/texgyreadventor-bolditalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bolditalic-webfont.eot -------------------------------------------------------------------------------- /styles/texgyreadventor-bolditalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bolditalic-webfont.ttf -------------------------------------------------------------------------------- /styles/texgyreadventor-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-regular-webfont.woff -------------------------------------------------------------------------------- /styles/texgyreadventor-bolditalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/styles/texgyreadventor-bolditalic-webfont.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea/ 3 | .vs/ 4 | 5 | # Node Modules 6 | node_modules/ 7 | npm-debug.log 8 | 9 | # Foundry 10 | *.lock 11 | jsconfig.json 12 | foundry -------------------------------------------------------------------------------- /styles/melee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /module/helpers/templates.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Define a set of template paths to pre-load 3 | * Pre-loaded templates are compiled and cached for fast access when rendering 4 | * @return {Promise} 5 | */ 6 | export const preloadHandlebarsTemplates = async function() { 7 | return loadTemplates([ 8 | 9 | // Actor partials. 10 | 'systems/basicfantasyrpg/templates/actor/parts/actor-combat.html', 11 | 'systems/basicfantasyrpg/templates/actor/parts/actor-description.html', 12 | 'systems/basicfantasyrpg/templates/actor/parts/actor-items.html', 13 | 'systems/basicfantasyrpg/templates/actor/parts/actor-spells.html', 14 | 'systems/basicfantasyrpg/templates/actor/parts/actor-features.html', 15 | 'systems/basicfantasyrpg/templates/actor/parts/actor-floors.html', 16 | ]); 17 | }; 18 | -------------------------------------------------------------------------------- /templates/actor/actor-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | {{!-- Header stuff goes here --}} 6 | 7 |
8 |

9 |
{{!-- resources here --}}
10 |
{{!-- abilities here --}}
11 |
12 |
13 | 14 | {{!-- Sheet Tab Navigation --}} 15 | 19 | 20 | {{!-- Sheet Body --}} 21 |
22 | {{!-- Tab content goes here --}} 23 |
24 |
25 | -------------------------------------------------------------------------------- /styles/damage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-description.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 | {{editor enrichedBiography target="system.biography" button=true editable=editable}} -------------------------------------------------------------------------------- /templates/item/item-sheet.html: -------------------------------------------------------------------------------- 1 | {{!-- This template is a fallback for when items don't have more specific templates. --}} 2 | {{!-- Generally, you'll want to make more specific templates when possible. --}} 3 |
4 |
5 | 6 |
7 |

8 |
9 |
10 | 11 | {{!-- Sheet Tab Navigation --}} 12 | 16 | 17 | {{!-- Sheet Body --}} 18 |
19 | 20 | {{!-- Description Tab --}} 21 |
22 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 23 |
24 | 25 | {{!-- Attributes Tab --}} 26 |
27 | {{!-- As you add new fields, add them in here! --}} 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Asacolips Projects / Foundry Mods 4 | Copyright (c) 2022 Steve Simenic 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | This license does not apply to the compendium content listed in this software's 17 | "packs" directory. See the README for licensing information for the compendium 18 | packs. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /system.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "basicfantasyrpg", 3 | "title": "Basic Fantasy RPG", 4 | "description": "The Basic Fantasy RPG system for FoundryVTT!", 5 | "version": "r15", 6 | "compatibility": { 7 | "minimum": "11", 8 | "verified": "13" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Orffen", 13 | "email": "orffen@orffenspace.com", 14 | "discord": "Orffen" 15 | }, 16 | { 17 | "name": "JugglinDan", 18 | "email": "jugglindan@gmail.com", 19 | "discord": "JugglinDan" 20 | } 21 | ], 22 | "esmodules": ["module/basicfantasyrpg.mjs"], 23 | "styles": ["styles/basicfantasyrpg.css"], 24 | "scripts": [], 25 | "packs": [], 26 | "languages": [ 27 | { 28 | "lang": "en", 29 | "name": "English", 30 | "path": "lang/en.json" 31 | }, 32 | { 33 | "lang": "fr", 34 | "name": "Français", 35 | "path": "lang/fr.json" 36 | } 37 | ], 38 | "grid": { 39 | "distance": 5, 40 | "units": "ft" 41 | }, 42 | "gridDistance": 5, 43 | "gridUnits": "ft", 44 | "primaryTokenAttribute": "hitPoints", 45 | "secondaryTokenAttribute": null, 46 | "url": "https://github.com/orffen/basicfantasyrpg", 47 | "manifest": "https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/system.json", 48 | "download": "https://github.com/orffen/basicfantasyrpg/archive/refs/tags/r15.zip", 49 | "license": "LICENSE.txt" 50 | } 51 | -------------------------------------------------------------------------------- /module/helpers/chat.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if a roll was successful and format a success/failure message. 3 | * 4 | * @param {Number} result The result of the roll 5 | * @param {Number} targetNumber The target number to beat for the roll to be successful 6 | * @param {Boolean} rollUnder Whether to check for rolling under the target number (defaults to false) 7 | * 8 | * @return {String} Will return a blank message if it cannot parse result or targetNumber 9 | */ 10 | export function successChatMessage(result, targetNumber, rollUnder=false) { 11 | let msg = ''; 12 | let success = false; 13 | if (result && !isNaN(result) && targetNumber && !isNaN(targetNumber)) { 14 | if (rollUnder) { 15 | success = (Number(result) <= Number(targetNumber)); 16 | } else { 17 | success = (Number(result) === 20 || (Number(result) > 1 && Number(result) >= Number(targetNumber))); 18 | } 19 | msg += ``; 20 | if (success) { 21 | msg += `✅ ${game.i18n.localize('BASICFANTASYRPG.Success')}`; 22 | } else { 23 | msg += `⛔ ${game.i18n.localize('BASICFANTASYRPG.Failure')}`; 24 | } 25 | msg += ` ${game.i18n.localize('BASICFANTASYRPG.VersusAbbr')} ${game.i18n.localize('BASICFANTASYRPG.TargetNumber').toLowerCase()} ${targetNumber}`; 26 | } 27 | return msg; 28 | } 29 | -------------------------------------------------------------------------------- /module/helpers/config.mjs: -------------------------------------------------------------------------------- 1 | export const BASICFANTASYRPG = {}; 2 | 3 | /** 4 | * The set of Ability Scores used within the sytem. 5 | * @type {Object} 6 | */ 7 | BASICFANTASYRPG.abilities = { 8 | 'str': 'BASICFANTASYRPG.AbilityStr', 9 | 'dex': 'BASICFANTASYRPG.AbilityDex', 10 | 'con': 'BASICFANTASYRPG.AbilityCon', 11 | 'int': 'BASICFANTASYRPG.AbilityInt', 12 | 'wis': 'BASICFANTASYRPG.AbilityWis', 13 | 'cha': 'BASICFANTASYRPG.AbilityCha' 14 | }; 15 | 16 | BASICFANTASYRPG.abilityAbbreviations = { 17 | 'str': 'BASICFANTASYRPG.AbilityStrAbbr', 18 | 'dex': 'BASICFANTASYRPG.AbilityDexAbbr', 19 | 'con': 'BASICFANTASYRPG.AbilityConAbbr', 20 | 'int': 'BASICFANTASYRPG.AbilityIntAbbr', 21 | 'wis': 'BASICFANTASYRPG.AbilityWisAbbr', 22 | 'cha': 'BASICFANTASYRPG.AbilityChaAbbr' 23 | }; 24 | 25 | /** 26 | * The set of Saving Throws used within the sytem. 27 | * @type {Object} 28 | */ 29 | BASICFANTASYRPG.saves = { 30 | 'death': 'BASICFANTASYRPG.SaveDeath', 31 | 'wands': 'BASICFANTASYRPG.SaveWands', 32 | 'paralysis': 'BASICFANTASYRPG.SaveParalysis', 33 | 'breath': 'BASICFANTASYRPG.SaveBreath', 34 | 'spells': 'BASICFANTASYRPG.SaveSpells' 35 | }; 36 | 37 | /** 38 | * Money used within the sytem. 39 | * @type {Object} 40 | */ 41 | BASICFANTASYRPG.money = { 42 | 'pp': 'BASICFANTASYRPG.Platinum', 43 | 'gp': 'BASICFANTASYRPG.Gold', 44 | 'ep': 'BASICFANTASYRPG.Electrum', 45 | 'sp': 'BASICFANTASYRPG.Silver', 46 | 'cp': 'BASICFANTASYRPG.Copper' 47 | }; 48 | -------------------------------------------------------------------------------- /templates/item/item-item-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | {{!-- Sheet Body --}} 24 |
25 | 26 | {{!-- Description --}} 27 |
28 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /templates/item/item-armor-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | {{!-- Sheet Body --}} 24 |
25 | 26 | {{!-- Description Tab --}} 27 |
28 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 29 |
30 | 31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-features.html: -------------------------------------------------------------------------------- 1 |
    2 |
  1. 3 |
    {{localize 'ITEM.TypeFeature'}}
    4 |
    {{localize 'BASICFANTASYRPG.Formula'}}
    5 | 8 |
  2. 9 | {{#each features as |item id|}} 10 |
  3. 11 |
    12 |
    13 | 14 |
    15 |

    {{item.name}}

    16 |
    17 |
    {{item.system.formula.value}}{{#if item.system.targetNumber.value}} ({{#if item.system.rollUnder.value}}≤{{else}}≥{{/if}} {{item.system.targetNumber.value}}){{/if}}
    18 |
    19 | 20 | 21 |
    22 |
  4. 23 | {{/each}} 24 |
-------------------------------------------------------------------------------- /templates/item/item-feature-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | {{!-- Sheet Body --}} 24 |
25 | 26 | {{!-- Description Tab --}} 27 |
28 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 29 |
30 | 31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /styles/ranged.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-effects.html: -------------------------------------------------------------------------------- 1 |
    2 | {{#each effects as |section sid|}} 3 |
  1. 4 |

    {{localize section.label}}

    5 |
    Source
    6 |
    Duration
    7 | 12 |
  2. 13 | 14 |
      15 | {{#each section.effects as |effect|}} 16 |
    1. 17 |
      18 | 19 |

      {{effect.data.label}}

      20 |
      21 |
      {{effect.sourceName}}
      22 |
      {{effect.duration.label}}
      23 | 34 |
    2. 35 | {{/each}} 36 |
    37 | {{/each}} 38 |
39 | -------------------------------------------------------------------------------- /templates/item/item-spell-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | 31 | {{!-- Sheet Body --}} 32 |
33 | 34 | {{!-- Description Tab --}} 35 |
36 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 37 |
38 | 39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /module/sheets/item-sheet.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend the basic ItemSheet with some very simple modifications 3 | * @extends {ItemSheet} 4 | */ 5 | export class BasicFantasyRPGItemSheet extends ItemSheet { 6 | 7 | /** @override */ 8 | static get defaultOptions() { 9 | return foundry.utils.mergeObject(super.defaultOptions, { 10 | classes: ['basicfantasyrpg', 'sheet', 'item'], 11 | width: 520, 12 | height: 480 13 | }); 14 | } 15 | 16 | /** @override */ 17 | get template() { 18 | const path = 'systems/basicfantasyrpg/templates/item'; 19 | // Return a single sheet for all item types. 20 | // return `${path}/item-sheet.html`; 21 | 22 | // Alternatively, you could use the following return statement to do a 23 | // unique item sheet by type, like `weapon-sheet.html`. 24 | return `${path}/item-${this.item.type}-sheet.html`; 25 | } 26 | 27 | /* -------------------------------------------- */ 28 | 29 | /** @override */ 30 | async getData() { 31 | // Retrieve base data structure. 32 | const context = super.getData(); 33 | 34 | // enrichedDescription - enriches system.description for editor 35 | context.enrichedDescription = await TextEditor.enrichHTML(this.object.system.description, {async: true}); 36 | 37 | // Use a safe clone of the item data for further operations. 38 | const itemData = context.item; 39 | 40 | // Retrieve the roll data for TinyMCE editors. 41 | context.rollData = {}; 42 | let actor = this.object?.parent ?? null; 43 | if (actor) { 44 | context.rollData = actor.getRollData(); 45 | } 46 | 47 | // Add the actor's data to context.data for easier access, as well as flags. 48 | context.data = itemData.system; 49 | context.flags = itemData.flags; 50 | 51 | return context; 52 | } 53 | 54 | /* -------------------------------------------- */ 55 | 56 | /** @override */ 57 | activateListeners(html) { 58 | super.activateListeners(html); 59 | 60 | // Everything below here is only needed if the sheet is editable 61 | if (!this.isEditable) return; 62 | 63 | // Roll handlers, click handlers, etc. would go here. 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-spells.html: -------------------------------------------------------------------------------- 1 |
2 | {{#each data.spellsPerLevel.value as |spellsPerLevel level|}} 3 |
4 | 5 |
6 | 7 |
8 |
9 | {{/each}} 10 | 11 |
12 | 13 |
    14 | {{#each spells as |spells spellLevel|}} 15 |
  1. 16 |
    {{localize 'BASICFANTASYRPG.SpellLevel'}} {{spellLevel}} {{localize 'BASICFANTASYRPG.Spells'}}
    17 |
    {{localize 'BASICFANTASYRPG.Prepared'}}
    18 | 21 |
  2. 22 | {{#each spells as |item id|}} 23 |
  3. 24 |
    25 |
    26 | 27 |
    28 |

    {{item.name}}

    29 |
    30 |
    31 |
    32 |
    {{item.system.prepared.value}}
    33 |
    34 |
    35 |
    36 | 37 | 38 |
    39 |
  4. 40 | {{/each}} 41 | {{/each}} 42 |
-------------------------------------------------------------------------------- /module/helpers/effects.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Manage Active Effect instances through the Actor Sheet via effect control buttons. 3 | * @param {MouseEvent} event The left-click event on the effect control 4 | * @param {Actor|Item} owner The owning document which manages this effect 5 | */ 6 | export function onManageActiveEffect(event, owner) { 7 | event.preventDefault(); 8 | const a = event.currentTarget; 9 | const li = a.closest('li'); 10 | const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null; 11 | switch ( a.dataset.action ) { 12 | case 'create': 13 | return owner.createEmbeddedDocuments('ActiveEffect', [{ 14 | label: 'New Effect', 15 | icon: 'icons/svg/aura.svg', 16 | origin: owner.uuid, 17 | 'duration.rounds': li.dataset.effectType === 'temporary' ? 1 : undefined, 18 | disabled: li.dataset.effectType === 'inactive' 19 | }]); 20 | case 'edit': 21 | return effect.sheet.render(true); 22 | case 'delete': 23 | return effect.delete(); 24 | case 'toggle': 25 | return effect.update({disabled: !effect.data.disabled}); 26 | } 27 | } 28 | 29 | /** 30 | * Prepare the data structure for Active Effects which are currently applied to an Actor or Item. 31 | * @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for 32 | * @return {object} Data for rendering 33 | */ 34 | export function prepareActiveEffectCategories(effects) { 35 | 36 | // Define effect header categories 37 | const categories = { 38 | temporary: { 39 | type: 'temporary', 40 | label: 'Temporary Effects', 41 | effects: [] 42 | }, 43 | passive: { 44 | type: 'passive', 45 | label: 'Passive Effects', 46 | effects: [] 47 | }, 48 | inactive: { 49 | type: 'inactive', 50 | label: 'Inactive Effects', 51 | effects: [] 52 | } 53 | }; 54 | 55 | // Iterate over active effects, classifying them into categories 56 | for ( let e of effects ) { 57 | e._getSourceName(); // Trigger a lookup for the source name 58 | if ( e.data.disabled ) categories.inactive.effects.push(e); 59 | else if ( e.isTemporary ) categories.temporary.effects.push(e); 60 | else categories.passive.effects.push(e); 61 | } 62 | return categories; 63 | } -------------------------------------------------------------------------------- /templates/item/item-weapon-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 | {{!-- Sheet Body --}} 36 |
37 | 38 | {{!-- Description Tab --}} 39 |
40 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 41 |
42 | 43 |
44 | 45 |
46 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-floors.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
7 |
8 | 9 | 10 |
    11 | {{#each floors as |floor floorLevel|}} 12 |
  1. 13 |
    {{floorLevel}}: {{floor.name}} ({{localizeFloorMaterial floor.system.material.value}})
    14 |
    {{localize 'BASICFANTASYRPG.Height'}}: {{floor.system.height.value}} {{localize 'BASICFANTASYRPG.FeetAbbr'}}
    15 |
    {{localize 'BASICFANTASYRPG.Area'}}: {{floor.system.area.value}} {{localize 'BASICFANTASYRPG.FeetAbbr'}}2
    16 |
    17 | {{localize 'TYPES.Item.wall'}} 18 | 19 | 20 |
    21 | {{#each (lookup ../walls @floorLevel) as |wall id|}} 22 |
  2. 23 |
    24 |

    {{wall.name}} - {{wall.system.thickness.value}}{{localize 'BASICFANTASYRPG.FeetAbbr'}} {{localizeLowerCase 'BASICFANTASYRPG.Thick'}} {{localizeWallMaterial wall.system.material.value}}

    25 |
    26 |
    27 |
    28 |
    {{wall.system.quantity.value}}
    29 |
    30 |
    31 |
    32 | 33 | 34 |
    35 |
  3. 36 | {{/each}} 37 | 38 | {{/each}} 39 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic Fantasy RPG for FoundryVTT 2 | 3 | This is the [Basic Fantasy RPG](https://www.basicfantasy.org/) system for FoundryVTT. Please also see the [companion compendium module](https://github.com/Stew-rt/basicfantasyrpg-corerules-en), which contains items, spells, monsters etc. for easy use with the system. 4 | 5 | ## Installation 6 | 7 | This system is available within FoundryVTT, or you can manually install it by using the manifest link below: 8 | 9 | https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/system.json 10 | 11 | ## Usage 12 | 13 | ### Initiative 14 | 15 | Initiative automatically includes the character's Dexterity modifier. The character sheet has a field for Initiative Bonus which can be used to add the Halfling's initiative bonus, or any bonuses from magic items. 16 | 17 | The system does not yet automatically reset initiative at the end of each combat round, but this can be manually done in FoundryVTT's initiative tracker. 18 | 19 | ### Roll Formulas 20 | 21 | To add ability bonuses/penalties to roll formulas (damage or special ability), you can use `@str.bonus`. Replace `str` with `int`, `wis`, `dex`, `con` or `cha`. You can use the full ability value with `@str.value`, for example if you wanted to do something like `d20<=@str.value`. `@lvl` is available as short-hand for the character level. 22 | 23 | ### Character Special Abilities 24 | 25 | Special Abilities are a flexible item type with just a description and a roll formula and an optional target number. They can be used for thief abilities (formula: d100), open doors checks (d6), or even just as text (formula left blank). Clicking the icon in the list will either roll the formula if present, or output the description to the chat window. 26 | 27 | ### Monster Special Abilities 28 | 29 | The monster sheet has a "special abilities" field. This field should be 0, 1, or 2, depending on how many asterisks appear after the monster's hit dice value. XP values and attack bonus for monsters are automatically calculated. 30 | 31 | ## License 32 | 33 | All software components are licensed under the MIT license - see [LICENSE.txt](https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/LICENSE.txt) for details. 34 | 35 | Basic Fantasy Role-Playing Game content is distributed under the terms of the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). 36 | 37 | ### Copyright Notices 38 | 39 | - Basic Fantasy Role-Playing Game Copyright © 2006-2023 Chris Gonnerman. 40 | - Boilerplate System Copyright © 2020 Asacolips Projects / Foundry Mods. 41 | - Basic Fantasy RPG for FoundryVTT © 2022 Steve Simenic. 42 | -------------------------------------------------------------------------------- /templates/item/item-wall-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 |
35 | 36 | {{!-- Sheet Body --}} 37 |
38 | 39 | {{!-- Description --}} 40 |
41 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 42 |
43 | 44 |
45 |
46 | -------------------------------------------------------------------------------- /templates/item/item-floor-sheet.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 | 9 |
10 | 11 |  {{localize 'BASICFANTASYRPG.FeetAbbr'}} 12 |
13 |
14 |
15 | 16 |
17 | 18 |  {{localize 'BASICFANTASYRPG.FeetAbbr'}}2 19 |
20 |
21 |
22 | 23 | 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 |
37 | 38 | {{!-- Sheet Body --}} 39 |
40 | 41 | {{!-- Description --}} 42 |
43 | {{editor enrichedDescription target="system.description" button=true editable=editable}} 44 |
45 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-items.html: -------------------------------------------------------------------------------- 1 | {{#if (eq actor.type 'character')}} 2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 | {{/if}} 41 | 42 |
    43 |
  1. 44 |
    {{localizeItemNameForActor actor.type}}
    45 |
    {{localize 'BASICFANTASYRPG.CarriedWeight'}}: {{carriedWeight}} {{localize 'BASICFANTASYRPG.PoundsAbbr'}}
    46 | 49 |
  2. 50 | {{#each gear as |item id|}} 51 |
  3. 52 |
    53 |
    54 | 55 |
    56 |

    {{item.system.quantity.value}} {{item.name}}

    57 |
    58 |
    59 |
    {{item.system.weight.value}} {{localize 'BASICFANTASYRPG.PoundsAbbr'}}
    60 |
    {{item.system.price.value}}
    61 |
    62 |
    63 | 64 | 65 |
    66 |
  4. 67 | {{/each}} 68 |
-------------------------------------------------------------------------------- /module/documents/item.mjs: -------------------------------------------------------------------------------- 1 | import {successChatMessage} from '../helpers/chat.mjs'; 2 | 3 | /** 4 | * Extend the basic Item with some very simple modifications. 5 | * @extends {Item} 6 | */ 7 | export class BasicFantasyRPGItem extends Item { 8 | /** 9 | * Augment the basic Item data model with additional dynamic data. 10 | */ 11 | prepareData() { 12 | // As with the actor class, items are documents that can have their data 13 | // preparation methods overridden (such as prepareBaseData()). 14 | super.prepareData(); 15 | } 16 | 17 | /** @override */ 18 | prepareBaseData() { 19 | const itemData = this; 20 | 21 | // Handle items which are missing system.rollUnder.value -- this will be handled in the system data model when it's implemented 22 | if (itemData.type === 'feature' && !itemData.system.rollUnder) { 23 | itemData.system.rollUnder.value = true; 24 | itemData.system.rollUnder.label = 'BASICFANTASYRPG.RollUnder'; 25 | } 26 | } 27 | 28 | /** 29 | * Prepare a data object which is passed to any Roll formulas which are created related to this Item 30 | * @private 31 | */ 32 | getRollData() { 33 | // If present, return the actor's roll data. 34 | if ( !this.actor ) return null; 35 | const data = this.actor.getRollData(); 36 | data.item = foundry.utils.deepClone(this.system); 37 | 38 | return data; 39 | } 40 | 41 | /** 42 | * Handle clickable rolls. 43 | * @param {Event} event The originating click event 44 | * @private 45 | */ 46 | async roll() { 47 | const item = this; 48 | 49 | // Initialize chat data. 50 | const speaker = ChatMessage.getSpeaker({ actor: item.actor }); 51 | const rollMode = game.settings.get('core', 'rollMode'); 52 | 53 | // If there's no roll data, or the formula is empty, just send a chat message. 54 | if (!item.system.formula || !item.system.formula.value) { 55 | ChatMessage.create({ 56 | speaker: speaker, 57 | rollMode: rollMode, 58 | flavor: `${game.i18n.localize('ITEM.Type' + item.type.capitalize())} - ${item.name}`, 59 | content: item.system.description ? `${item.system.description}` : '' 60 | }); 61 | } else { // Otherwise, create a roll and send a chat message from it. 62 | let label = `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${game.i18n.localize('ITEM.Type' + item.type.capitalize())} - ${item.name}`; 63 | if (item.type === 'feature' && item.system.description) { 64 | label += `${item.system.description}`; 65 | } 66 | 67 | // Retrieve roll data and invoke the roll 68 | const rollData = item.getRollData(); 69 | const roll = new Roll(rollData.item.formula.value, rollData); 70 | await roll.roll(); 71 | 72 | let targetParsed = rollData.item.targetNumber.value; 73 | // targetNumber may be a formula - use a Roll object to parse it if it's not a number already 74 | if (targetParsed && isNaN(targetParsed) && typeof targetParsed === 'string') { 75 | try { 76 | const rollTN = new Roll(targetParsed, rollData); 77 | await rollTN.roll(); 78 | targetParsed = rollTN.total; 79 | } catch { 80 | ui.notifications.warn(`${game.i18n.localize('ERROR.InvalidTargetNumber')} ${game.i18n.localize('TYPES.Item.' + item.type)} - ${item.name}: ${targetParsed}`, {localize: false, permanent: true}); 81 | targetParsed = ''; 82 | } 83 | } 84 | label += successChatMessage(roll.total, targetParsed, rollData.item.rollUnder.value); 85 | roll.toMessage({ 86 | speaker: speaker, 87 | rollMode: rollMode, 88 | flavor: label 89 | }); 90 | return roll; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /templates/actor/parts/actor-combat.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
    34 |
  1. 35 |
    {{localize 'ITEM.TypeWeapon'}}
    36 |
    {{localize 'BASICFANTASYRPG.Attack'}} / {{localize 'BASICFANTASYRPG.DamageAbbr'}}
    37 | 40 |
  2. 41 | {{#each weapons as |weapon id|}} 42 |
  3. 43 |
    44 |
    45 | 46 |
    47 |

    {{weapon.name}}

    48 |
    49 | 55 |
    56 | 57 | 58 |
    59 |
  4. 60 | {{/each}} 61 |
62 | 63 |
    64 |
  1. 65 |
    {{localize 'ITEM.TypeArmor'}}
    66 |
    {{localize 'BASICFANTASYRPG.ArmorClass'}}
    67 | 70 |
  2. 71 | {{#each armors as |armor id|}} 72 |
  3. 73 |
    74 |
    75 | 76 |
    77 |

    {{armor.name}}

    78 |
    79 |
    {{localize armor.system.armorClass.abbr}} {{armor.system.armorClass.value}}
    80 |
    81 | 82 | 83 |
    84 |
  4. 85 | {{/each}} 86 |
-------------------------------------------------------------------------------- /templates/actor/actor-stronghold-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | 6 |
7 |

8 |
9 | 10 |
11 | 12 |
13 | 14 |  {{localize 'BASICFANTASYRPG.FeetAbbr'}} 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 | 25 |
26 | 27 |
28 | 29 |  {{localizeLowerCase 'BASICFANTASYRPG.Gold'}} 30 |
31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |  {{localizeLowerCase 'BASICFANTASYRPG.WorkerDays'}} 49 |
50 |
51 | 52 |
53 | 54 |
55 | 56 |
57 |
58 | 59 |
60 | 61 |
62 |
63 | 64 | {{!-- Sheet Tab Navigation --}} 65 | 70 | 71 | {{!-- Sheet Body --}} 72 |
73 | 74 | {{!-- Default actor tab is 'combat' so we'll retain that name internally --}} 75 |
76 | 77 |
78 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-floors.html"}} 79 |
80 | 81 |
82 | 83 | {{!-- Description Tab --}} 84 |
85 | {{editor enrichedBiography target="system.biography" button=true editable=editable}} 86 |
87 | 88 |
89 |
90 | -------------------------------------------------------------------------------- /templates/actor/actor-siegeEngine-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | 6 |
7 |

8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 | 35 | {{!-- Sheet Body --}} 36 |
37 | 38 | 70 | 71 | {{!-- Description --}} 72 |
73 |
Description
74 |
75 | {{editor enrichedBiography target="system.biography" button=true editable=editable}} 76 |
77 | 78 |
79 | 80 | -------------------------------------------------------------------------------- /templates/actor/actor-character-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | 6 |
7 |

8 |
9 | 10 |
11 | 12 |
13 | 14 |  /  15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |   24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |  /  33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 | 41 | {{!-- Sheet Tab Navigation --}} 42 | 50 | 51 | {{!-- Sheet Body --}} 52 |
53 | 54 | {{!-- Combat Tab --}} 55 |
56 |
57 | 78 | 79 | {{!-- For the main combat list, span the right two columns --}} 80 |
81 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-combat.html"}} 82 |
83 | 84 |
85 |
86 | 87 | {{!-- Description Tab --}} 88 |
89 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-description.html"}} 90 |
91 | 92 | {{!-- Owned Items Tab --}} 93 |
94 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-items.html"}} 95 |
96 | 97 | {{!-- Owned Spells Tab --}} 98 |
99 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-spells.html"}} 100 |
101 | 102 | {{!-- Features Tab --}} 103 |
104 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-features.html"}} 105 |
106 | 107 |
108 |
109 | 110 | -------------------------------------------------------------------------------- /templates/actor/actor-monster-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | 6 |
7 |

8 |
9 | 10 |
11 | 12 |
13 | 14 |  /  15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 | 24 | + 25 | 26 |  /  27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 |
48 | 49 |
50 | 51 |
52 | 53 |
54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 |
66 | 67 | {{!-- Sheet Tab Navigation --}} 68 | 73 | 74 | {{!-- Sheet Body --}} 75 |
76 | 77 | {{!-- Combat Tab --}} 78 |
79 |
80 | 91 | 92 | {{!-- For the main combat list, span the right two columns --}} 93 |
94 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-combat.html"}} 95 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-features.html"}} 96 |
97 | 98 |
99 |
100 | 101 | {{!-- Description Tab --}} 102 |
103 | {{editor enrichedBiography target="system.biography" button=true editable=editable}} 104 |
105 | 106 |
107 |
108 | -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "ACTOR.TypeCharacter": "Character", 3 | "ACTOR.TypeMonster": "Monster", 4 | "ACTOR.TypeSiegeEngine": "Siege Engine", 5 | "ACTOR.TypeStronghold": "Stronghold", 6 | "ACTOR.TypeVehicle": "Vehicle", 7 | 8 | "ERROR.InvalidTargetNumber": "Couldn't parse target number for", 9 | 10 | "ITEM.TypeItem": "Equipment", 11 | "ITEM.TypeWeapon": "Weapon", 12 | "ITEM.TypeArmor": "Armor", 13 | "ITEM.TypeSpell": "Spell", 14 | "ITEM.TypeFeature": "Special Ability", 15 | "ITEM.TypeFloor": "Floor", 16 | "ITEM.TypeWall": "Wall", 17 | 18 | "TYPES.Actor.character": "Character", 19 | "TYPES.Actor.monster": "Monster", 20 | "TYPES.Actor.siegeEngine": "Siege Engine", 21 | "TYPES.Actor.stronghold": "Stronghold", 22 | "TYPES.Actor.vehicle": "Vehicle", 23 | 24 | "TYPES.Item.item": "Equipment", 25 | "TYPES.Item.weapon": "Weapon", 26 | "TYPES.Item.armor": "Armor", 27 | "TYPES.Item.spell": "Spell", 28 | "TYPES.Item.feature": "Special Ability", 29 | "TYPES.Item.floor": "Floor", 30 | "TYPES.Item.wall": "Wall", 31 | 32 | "BASICFANTASYRPG.TabCargo": "Cargo", 33 | "BASICFANTASYRPG.TabCombat": "Combat", 34 | "BASICFANTASYRPG.TabDescription": "Description", 35 | "BASICFANTASYRPG.TabFeatures": "Special Abilities", 36 | "BASICFANTASYRPG.TabFloors": "Floors & Walls", 37 | "BASICFANTASYRPG.TabItems": "Equipment", 38 | "BASICFANTASYRPG.TabSpells": "Spells", 39 | 40 | "BASICFANTASYRPG.AbilityStr": "Strength", 41 | "BASICFANTASYRPG.AbilityCon": "Constitution", 42 | "BASICFANTASYRPG.AbilityDex": "Dexterity", 43 | "BASICFANTASYRPG.AbilityInt": "Intelligence", 44 | "BASICFANTASYRPG.AbilityWis": "Wisdom", 45 | "BASICFANTASYRPG.AbilityCha": "Charisma", 46 | "BASICFANTASYRPG.AbilityStrAbbr": "str", 47 | "BASICFANTASYRPG.AbilityConAbbr": "con", 48 | "BASICFANTASYRPG.AbilityDexAbbr": "dex", 49 | "BASICFANTASYRPG.AbilityIntAbbr": "int", 50 | "BASICFANTASYRPG.AbilityWisAbbr": "wis", 51 | "BASICFANTASYRPG.AbilityChaAbbr": "cha", 52 | 53 | "BASICFANTASYRPG.SaveDeath": "Death Ray or Poison", 54 | "BASICFANTASYRPG.SaveWands": "Magic Wands", 55 | "BASICFANTASYRPG.SaveParalysis": "Paralysis or Petrify", 56 | "BASICFANTASYRPG.SaveBreath": "Dragon Breath", 57 | "BASICFANTASYRPG.SaveSpells": "Rods, Staves, and Spells", 58 | 59 | "BASICFANTASYRPG.Platinum": "Platinum", 60 | "BASICFANTASYRPG.Gold": "Gold", 61 | "BASICFANTASYRPG.Electrum": "Electrum", 62 | "BASICFANTASYRPG.Silver": "Silver", 63 | "BASICFANTASYRPG.Copper": "Copper", 64 | 65 | "BASICFANTASYRPG.AbilityCheck": "Ability Check", 66 | "BASICFANTASYRPG.Add": "Add", 67 | "BASICFANTASYRPG.Age": "Age", 68 | "BASICFANTASYRPG.Area": "Area", 69 | "BASICFANTASYRPG.ArmorClass": "Armor Class", 70 | "BASICFANTASYRPG.ArmorClassAbbr": "AC", 71 | "BASICFANTASYRPG.Attack": "Attack", 72 | "BASICFANTASYRPG.AttackBonus": "Attack Bonus", 73 | "BASICFANTASYRPG.AttackBonusAbbr": "AB", 74 | "BASICFANTASYRPG.AttackPenalty": "Attack Penalty", 75 | "BASICFANTASYRPG.BonusAttackBonus": "Bonus AB", 76 | "BASICFANTASYRPG.BuildTime": "Time to Build", 77 | "BASICFANTASYRPG.Cargo": "Cargo", 78 | "BASICFANTASYRPG.CarriedWeight": "Load", 79 | "BASICFANTASYRPG.Class": "Class", 80 | "BASICFANTASYRPG.Cost": "Cost", 81 | "BASICFANTASYRPG.CostMultiplier": "Cost Multiplier", 82 | "BASICFANTASYRPG.Damage": "Damage", 83 | "BASICFANTASYRPG.DamageAbbr": "Dmg", 84 | "BASICFANTASYRPG.Days": "Days", 85 | "BASICFANTASYRPG.Duration": "Duration", 86 | "BASICFANTASYRPG.ExperiencePoints": "Experience Points", 87 | "BASICFANTASYRPG.ExperiencePointsAbbr": "XP", 88 | "BASICFANTASYRPG.Failure": "Failure", 89 | "BASICFANTASYRPG.FeetAbbr": "ft.", 90 | "BASICFANTASYRPG.Floors": "Floors", 91 | "BASICFANTASYRPG.Followers": "Followers", 92 | "BASICFANTASYRPG.Formula": "Roll Formula", 93 | "BASICFANTASYRPG.Hardness": "Hardness", 94 | "BASICFANTASYRPG.Height": "Height", 95 | "BASICFANTASYRPG.HitDice": "Hit Dice", 96 | "BASICFANTASYRPG.HitDiceAbbr": "HD", 97 | "BASICFANTASYRPG.HitPoints": "Hit Points", 98 | "BASICFANTASYRPG.HitPointsAbbr": "HP", 99 | "BASICFANTASYRPG.Immobilized": "Immobilized", 100 | "BASICFANTASYRPG.InitiativeBonus": "Init. Bonus", 101 | "BASICFANTASYRPG.Length": "Length", 102 | "BASICFANTASYRPG.Level": "Level", 103 | "BASICFANTASYRPG.Maneuverability": "Maneuverability", 104 | "BASICFANTASYRPG.Material": "Material", 105 | "BASICFANTASYRPG.MaterialBrick": "Brick", 106 | "BASICFANTASYRPG.MaterialStoneHard": "Hard Stone", 107 | "BASICFANTASYRPG.MaterialStoneSoft": "Soft Stone", 108 | "BASICFANTASYRPG.MaterialWood": "Wood", 109 | "BASICFANTASYRPG.Melee": "Melee", 110 | "BASICFANTASYRPG.Morale": "Morale", 111 | "BASICFANTASYRPG.Movement": "Movement", 112 | "BASICFANTASYRPG.Name": "Name", 113 | "BASICFANTASYRPG.NextLevel": "Next Level", 114 | "BASICFANTASYRPG.NumberAppearing": "No. Appearing", 115 | "BASICFANTASYRPG.PoundsAbbr": "lbs.", 116 | "BASICFANTASYRPG.Prepared": "Prepared", 117 | "BASICFANTASYRPG.Price": "Price", 118 | "BASICFANTASYRPG.Quantity": "Quantity", 119 | "BASICFANTASYRPG.Race": "Race", 120 | "BASICFANTASYRPG.Range": "Range", 121 | "BASICFANTASYRPG.RangeBonus": "Range Bonus", 122 | "BASICFANTASYRPG.Ranged": "Ranged", 123 | "BASICFANTASYRPG.RangeLong": "Long (-2)", 124 | "BASICFANTASYRPG.RangeMedium": "Medium (+0)", 125 | "BASICFANTASYRPG.RangeShort": "Short (+1)", 126 | "BASICFANTASYRPG.RateOfFire": "Rate of Fire", 127 | "BASICFANTASYRPG.Roll": "Roll", 128 | "BASICFANTASYRPG.RollUnder": "Roll Under", 129 | "BASICFANTASYRPG.RoofSlate": "Slate-shingled Roof", 130 | "BASICFANTASYRPG.RoofThatched": "Thatched Roof", 131 | "BASICFANTASYRPG.RoofWood": "Wood-shingled Roof", 132 | "BASICFANTASYRPG.SavingThrow": "Saving Throw", 133 | "BASICFANTASYRPG.Sex": "Gender", 134 | "BASICFANTASYRPG.SideAft": "Aft", 135 | "BASICFANTASYRPG.SideForward": "Forward", 136 | "BASICFANTASYRPG.SidePort": "Port", 137 | "BASICFANTASYRPG.SideStarboard": "Starboard", 138 | "BASICFANTASYRPG.Size": "Size", 139 | "BASICFANTASYRPG.SpecialAbility": "Special Ability", 140 | "BASICFANTASYRPG.SpecialAbilityXPBonus": "Special Ability XP Bonus", 141 | "BASICFANTASYRPG.SpellLevel": "Level", 142 | "BASICFANTASYRPG.Spells": "Spells", 143 | "BASICFANTASYRPG.SpellsPerLevel": "Spells", 144 | "BASICFANTASYRPG.Success": "Success", 145 | "BASICFANTASYRPG.Sunk": "Sunk", 146 | "BASICFANTASYRPG.TargetAC": "Target AC", 147 | "BASICFANTASYRPG.TargetNumber": "Target Number", 148 | "BASICFANTASYRPG.Thick": "Thick", 149 | "BASICFANTASYRPG.Thickness": "Thickness", 150 | "BASICFANTASYRPG.TreasureType": "Treasure Type", 151 | "BASICFANTASYRPG.Versus": "Versus", 152 | "BASICFANTASYRPG.VersusAbbr": "vs.", 153 | "BASICFANTASYRPG.Weight": "Weight", 154 | "BASICFANTASYRPG.Width": "Width", 155 | "BASICFANTASYRPG.WorkerDays": "Worker-days", 156 | "BASICFANTASYRPG.Workers": "Workers", 157 | 158 | "BASICFANTASYRPG.EffectCreate": "Create Effect", 159 | "BASICFANTASYRPG.EffectToggle": "Toggle Effect", 160 | "BASICFANTASYRPG.EffectEdit": "Edit Effect", 161 | "BASICFANTASYRPG.EffectDelete": "Delete Effect" 162 | } -------------------------------------------------------------------------------- /lang/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "ACTOR.TypeCharacter": "Personage", 3 | "ACTOR.TypeMonster": "Monstre", 4 | "ACTOR.TypeSiegeEngine": "Engins de Siège", 5 | "ACTOR.TypeStronghold": "Bastion", 6 | "ACTOR.TypeVehicle": "Vehicule", 7 | 8 | "ERROR.InvalidTargetNumber": "Impossible d'analyser le numéro cible", 9 | 10 | "ITEM.TypeItem": "Equipment", 11 | "ITEM.TypeWeapon": "Arme", 12 | "ITEM.TypeArmor": "Armure", 13 | "ITEM.TypeSpell": "Sortilège", 14 | "ITEM.TypeFeature": "Capacité spéciale", 15 | "ITEM.TypeFloor": "Sol", 16 | "ITEM.TypeWall": "Mur", 17 | 18 | "TYPES.Actor.character": "Personage", 19 | "TYPES.Actor.monster": "Monstre", 20 | "TYPES.Actor.siegeEngine": "Engins de Siège", 21 | "TYPES.Actor.stronghold": "Bastion", 22 | "TYPES.Actor.vehicle": "Vehicule", 23 | 24 | "TYPES.Item.item": "Equipment", 25 | "TYPES.Item.weapon": "Arme", 26 | "TYPES.Item.armor": "Armure", 27 | "TYPES.Item.spell": "Sortilège", 28 | "TYPES.Item.feature": "Capacité spéciale", 29 | "TYPES.Item.floor": "Sol", 30 | "TYPES.Item.wall": "Mur", 31 | 32 | "BASICFANTASYRPG.TabCargo": "Cargo", 33 | "BASICFANTASYRPG.TabCombat": "Combat", 34 | "BASICFANTASYRPG.TabDescription": "Description", 35 | "BASICFANTASYRPG.TabFeatures": "Capacité spéciale", 36 | "BASICFANTASYRPG.TabFloors": "Sols & Murs", 37 | "BASICFANTASYRPG.TabItems": "Équipement", 38 | "BASICFANTASYRPG.TabSpells": "Sortilèges", 39 | 40 | "BASICFANTASYRPG.AbilityStr": "Force", 41 | "BASICFANTASYRPG.AbilityCon": "Constitution", 42 | "BASICFANTASYRPG.AbilityDex": "Dextérité", 43 | "BASICFANTASYRPG.AbilityInt": "Intelligence", 44 | "BASICFANTASYRPG.AbilityWis": "Sagesse", 45 | "BASICFANTASYRPG.AbilityCha": "Charisme", 46 | "BASICFANTASYRPG.AbilityStrAbbr": "for", 47 | "BASICFANTASYRPG.AbilityConAbbr": "con", 48 | "BASICFANTASYRPG.AbilityDexAbbr": "dex", 49 | "BASICFANTASYRPG.AbilityIntAbbr": "int", 50 | "BASICFANTASYRPG.AbilityWisAbbr": "sag", 51 | "BASICFANTASYRPG.AbilityChaAbbr": "cha", 52 | 53 | "BASICFANTASYRPG.SaveDeath": "Rayon de la mort ou poison", 54 | "BASICFANTASYRPG.SaveWands": "Baguettes Magiques", 55 | "BASICFANTASYRPG.SaveParalysis": "Paralysie ou Pétrification", 56 | "BASICFANTASYRPG.SaveBreath": "Souffle de dragon", 57 | "BASICFANTASYRPG.SaveSpells": "Baguettes, Bâtons, et Sortilèges", 58 | 59 | "BASICFANTASYRPG.Platinum": "Platine", 60 | "BASICFANTASYRPG.Gold": "Or", 61 | "BASICFANTASYRPG.Electrum": "Électrum", 62 | "BASICFANTASYRPG.Silver": "Argent", 63 | "BASICFANTASYRPG.Copper": "Cuivre", 64 | 65 | "BASICFANTASYRPG.AbilityCheck": "Test de capacité", 66 | "BASICFANTASYRPG.Add": "Ajouter", 67 | "BASICFANTASYRPG.Age": "Âge", 68 | "BASICFANTASYRPG.Area": "Zone", 69 | "BASICFANTASYRPG.ArmorClass": "Classe d'Armure", 70 | "BASICFANTASYRPG.ArmorClassAbbr": "CA", 71 | "BASICFANTASYRPG.Attack": "Attaque", 72 | "BASICFANTASYRPG.AttackBonus": "Attaque Bonus", 73 | "BASICFANTASYRPG.AttackBonusAbbr": "AB", 74 | "BASICFANTASYRPG.AttackPenalty": "Pénalité d'Attaque", 75 | "BASICFANTASYRPG.BonusAttackBonus": "Bonus AB", 76 | "BASICFANTASYRPG.BuildTime": "Temps d'Incantation", 77 | "BASICFANTASYRPG.Cargo": "Cargo", 78 | "BASICFANTASYRPG.CarriedWeight": "Charge", 79 | "BASICFANTASYRPG.Class": "Classe", 80 | "BASICFANTASYRPG.Cost": "Coût", 81 | "BASICFANTASYRPG.CostMultiplier": "Multiplicateur de Coût", 82 | "BASICFANTASYRPG.Damage": "Dommages", 83 | "BASICFANTASYRPG.DamageAbbr": "Dmg", 84 | "BASICFANTASYRPG.Days": "Jours", 85 | "BASICFANTASYRPG.Duration": "Durée", 86 | "BASICFANTASYRPG.ExperiencePoints": "Points Expérience", 87 | "BASICFANTASYRPG.ExperiencePointsAbbr": "XP", 88 | "BASICFANTASYRPG.Failure": "Échec", 89 | "BASICFANTASYRPG.FeetAbbr": "m", 90 | "BASICFANTASYRPG.Floors": "Sol", 91 | "BASICFANTASYRPG.Followers": "Compagnion", 92 | "BASICFANTASYRPG.Formula": "Jet de formule", 93 | "BASICFANTASYRPG.Hardness": "Dureté", 94 | "BASICFANTASYRPG.Height": "Hauteur", 95 | "BASICFANTASYRPG.HitDice": "Dés de Dégâts", 96 | "BASICFANTASYRPG.HitDiceAbbr": "DD", 97 | "BASICFANTASYRPG.HitPoints": "Points de Dégâts", 98 | "BASICFANTASYRPG.HitPointsAbbr": "PD", 99 | "BASICFANTASYRPG.Immobilized": "Immobilisé", 100 | "BASICFANTASYRPG.InitiativeBonus": "Bonus d'Init.", 101 | "BASICFANTASYRPG.Length": "Longueur", 102 | "BASICFANTASYRPG.Level": "Niveau", 103 | "BASICFANTASYRPG.Maneuverability": "Manœuvrabilité", 104 | "BASICFANTASYRPG.Material": "Materiaux", 105 | "BASICFANTASYRPG.MaterialBrick": "Brique", 106 | "BASICFANTASYRPG.MaterialStoneHard": "Roche dure", 107 | "BASICFANTASYRPG.MaterialStoneSoft": "Roche tendre", 108 | "BASICFANTASYRPG.MaterialWood": "Bois", 109 | "BASICFANTASYRPG.Melee": "Mêlée", 110 | "BASICFANTASYRPG.Morale": "Moral", 111 | "BASICFANTASYRPG.Movement": "Mouvement", 112 | "BASICFANTASYRPG.Name": "Nom", 113 | "BASICFANTASYRPG.NextLevel": "Niveau suivant", 114 | "BASICFANTASYRPG.NumberAppearing": "Nmbr. d'apparitions", 115 | "BASICFANTASYRPG.PoundsAbbr": "Kg", 116 | "BASICFANTASYRPG.Prepared": "Préparé", 117 | "BASICFANTASYRPG.Price": "Prix", 118 | "BASICFANTASYRPG.Quantity": "Quantité", 119 | "BASICFANTASYRPG.Race": "Race", 120 | "BASICFANTASYRPG.Range": "Porté", 121 | "BASICFANTASYRPG.RangeBonus": "Bonus de Porté", 122 | "BASICFANTASYRPG.Ranged": "mêlée", 123 | "BASICFANTASYRPG.RangeLong": "Long (-2)", 124 | "BASICFANTASYRPG.RangeMedium": "Moyen (+0)", 125 | "BASICFANTASYRPG.RangeShort": "Court (+1)", 126 | "BASICFANTASYRPG.RateOfFire": "Cadence de tir", 127 | "BASICFANTASYRPG.Roll": "Jet", 128 | "BASICFANTASYRPG.RollUnder": "Jet inferieur a", 129 | "BASICFANTASYRPG.RoofSlate": "Toiture en ardoises", 130 | "BASICFANTASYRPG.RoofThatched": "Toiture en chaume", 131 | "BASICFANTASYRPG.RoofWood": "Toiture en bardeaux de bois", 132 | "BASICFANTASYRPG.SavingThrow": "Jet de sauvegarde", 133 | "BASICFANTASYRPG.Sex": "Sexe", 134 | "BASICFANTASYRPG.SideAft": "Arrière", 135 | "BASICFANTASYRPG.SideForward": "Avant", 136 | "BASICFANTASYRPG.SidePort": "Latéral", 137 | "BASICFANTASYRPG.SideStarboard": "tribord", 138 | "BASICFANTASYRPG.Size": "Taille", 139 | "BASICFANTASYRPG.SpecialAbility": "Capacité spéciale", 140 | "BASICFANTASYRPG.SpecialAbilityXPBonus": "Bonus d'XP pour les capacités spéciales", 141 | "BASICFANTASYRPG.SpellLevel": "Niveau", 142 | "BASICFANTASYRPG.Spells": "Sortilèges", 143 | "BASICFANTASYRPG.SpellsPerLevel": "Sortilèges", 144 | "BASICFANTASYRPG.Success": "Succès", 145 | "BASICFANTASYRPG.Sunk": "Échoué", 146 | "BASICFANTASYRPG.TargetAC": "CA de la cible", 147 | "BASICFANTASYRPG.TargetNumber": "Numéro Cible", 148 | "BASICFANTASYRPG.Thick": "Épais", 149 | "BASICFANTASYRPG.Thickness": "Épaisseur", 150 | "BASICFANTASYRPG.TreasureType": "Type de trésor", 151 | "BASICFANTASYRPG.Versus": "Versus", 152 | "BASICFANTASYRPG.VersusAbbr": "vs.", 153 | "BASICFANTASYRPG.Weight": "Poids", 154 | "BASICFANTASYRPG.Width": "Largeur", 155 | "BASICFANTASYRPG.WorkerDays": "Jours Travailé", 156 | "BASICFANTASYRPG.Workers": "Travailleurs", 157 | 158 | "BASICFANTASYRPG.EffectCreate": "Créer un Effet", 159 | "BASICFANTASYRPG.EffectToggle": "Effet On/Off", 160 | "BASICFANTASYRPG.EffectEdit": "Modifier l'Effet", 161 | "BASICFANTASYRPG.EffectDelete": "Supprimer l'Effet" 162 | } 163 | -------------------------------------------------------------------------------- /templates/actor/actor-vehicle-sheet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{!-- Sheet Header --}} 4 |
5 | 6 |
7 |

8 |
9 | 10 |
11 | 12 |
13 | 14 |  /  15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |  /  24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |  {{localize 'BASICFANTASYRPG.FeetAbbr'}} 44 |
45 |
46 | 47 |
48 | 49 |
50 | 51 |  {{localize 'BASICFANTASYRPG.FeetAbbr'}} 52 |
53 |
54 | 55 |
56 | 57 |
58 | 59 |
60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 | {{!-- Sheet Tab Navigation --}} 69 | 74 | 75 | {{!-- Sheet Body --}} 76 |
77 | 78 | {{!-- Combat Tab --}} 79 |
80 |
81 | 141 | 142 |
143 | {{> "systems/basicfantasyrpg/templates/actor/parts/actor-items.html"}} 144 |
145 | 146 |
147 |
148 | 149 | {{!-- Description Tab --}} 150 |
151 | {{editor enrichedBiography target="system.biography" button=true editable=editable}} 152 |
153 | 154 |
155 |
156 | -------------------------------------------------------------------------------- /module/basicfantasyrpg.mjs: -------------------------------------------------------------------------------- 1 | // Import document classes. 2 | import { BasicFantasyRPGActor } from './documents/actor.mjs'; 3 | import { BasicFantasyRPGItem } from './documents/item.mjs'; 4 | // Import sheet classes. 5 | import { BasicFantasyRPGActorSheet } from './sheets/actor-sheet.mjs'; 6 | import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs'; 7 | // Import helper/utility classes and constants. 8 | import { preloadHandlebarsTemplates } from './helpers/templates.mjs'; 9 | import { BASICFANTASYRPG } from './helpers/config.mjs'; 10 | 11 | /* -------------------------------------------- */ 12 | /* Init Hook */ 13 | /* -------------------------------------------- */ 14 | 15 | Hooks.once('init', async function() { 16 | 17 | // Add utility classes to the global game object so that they're more easily 18 | // accessible in global contexts. 19 | game.basicfantasyrpg = { 20 | BasicFantasyRPGActor, 21 | BasicFantasyRPGItem, 22 | rollItemMacro 23 | }; 24 | 25 | // Add custom constants for configuration. 26 | CONFIG.BASICFANTASYRPG = BASICFANTASYRPG; 27 | 28 | /** 29 | * Set an initiative formula for the system 30 | * @type {String} 31 | */ 32 | CONFIG.Combat.initiative = { 33 | formula: 'max(1, 1d6 + @abilities.dex.bonus + @initBonus.value)', 34 | decimals: 0 35 | }; 36 | 37 | // Define custom Document classes 38 | CONFIG.Actor.documentClass = BasicFantasyRPGActor; 39 | CONFIG.Item.documentClass = BasicFantasyRPGItem; 40 | 41 | // Register sheet application classes 42 | Actors.unregisterSheet('core', ActorSheet); 43 | Actors.registerSheet('basicfantasyrpg', BasicFantasyRPGActorSheet, { makeDefault: true }); 44 | Items.unregisterSheet('core', ItemSheet); 45 | Items.registerSheet('basicfantasyrpg', BasicFantasyRPGItemSheet, { makeDefault: true }); 46 | 47 | // Preload Handlebars templates. 48 | return preloadHandlebarsTemplates(); 49 | }); 50 | 51 | /* -------------------------------------------- */ 52 | /* Handlebars Helpers & Partials */ 53 | /* -------------------------------------------- */ 54 | 55 | Handlebars.registerHelper('calculateAbilityTargetNumber', function(lvl) { 56 | return Math.floor(17 - (lvl / 2 - lvl % 2)); 57 | }); 58 | 59 | Handlebars.registerHelper('localizeFloorMaterial', function(type) { 60 | switch (type) { 61 | case 'roofThatch': return game.i18n.localize('BASICFANTASYRPG.RoofThatched'); 62 | case 'roofSlate' : return game.i18n.localize('BASICFANTASYRPG.RoofSlate'); 63 | case 'roofWood' : return game.i18n.localize('BASICFANTASYRPG.RoofWood'); 64 | default : return game.i18n.localize('ITEM.TypeFloor'); 65 | } 66 | }); 67 | 68 | Handlebars.registerHelper('localizeWallMaterial', function(type) { 69 | switch (type) { 70 | case 'stoneHard' : return game.i18n.localize('BASICFANTASYRPG.MaterialStoneHard'); 71 | case 'stoneSoft' : return game.i18n.localize('BASICFANTASYRPG.MaterialStoneSoft'); 72 | case 'brick' : return game.i18n.localize('BASICFANTASYRPG.MaterialBrick'); 73 | case 'wood' : return game.i18n.localize('BASICFANTASYRPG.MaterialWood'); 74 | default : return game.i18n.localize('ITEM.TypeFloor'); 75 | } 76 | }); 77 | 78 | Handlebars.registerHelper('localizeItemNameForActor', function(type) { 79 | if (type === 'stronghold') { 80 | return game.i18n.localize('ITEM.TypeFloor'); 81 | } else if (type === 'vehicle') { 82 | return game.i18n.localize('BASICFANTASYRPG.Cargo'); 83 | } else { 84 | return game.i18n.localize('ITEM.TypeItem'); 85 | } 86 | }); 87 | 88 | Handlebars.registerHelper('localizeLowerCase', function(str) { 89 | return game.i18n.localize(str).toLowerCase(); 90 | }); 91 | 92 | Handlebars.registerHelper('toLowerCase', function(str) { 93 | return str.toLowerCase(); 94 | }); 95 | 96 | Handlebars.registerHelper('selected', function(value) { 97 | return Boolean(value) ? "selected" : ""; 98 | }); 99 | 100 | Handlebars.registerPartial('iconDamage', ``); 101 | //`` 102 | 103 | Handlebars.registerPartial('iconMelee', ``); 104 | //`` 105 | 106 | Handlebars.registerPartial('iconRanged', ``); 107 | //`` 108 | 109 | /* -------------------------------------------- */ 110 | /* Ready Hook & Others */ 111 | /* -------------------------------------------- */ 112 | 113 | Hooks.once('ready', async function() { 114 | // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to 115 | Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot)); 116 | }); 117 | 118 | // Hide certain types from being created through the UI 119 | Hooks.on("renderDialog", (dialog, html) => { 120 | let hiddenTypes = ["floor", "wall"]; 121 | Array.from(html.find("#document-create option")).forEach(i => {if (hiddenTypes.includes(i.value)) i.remove()}); 122 | }); 123 | 124 | /* -------------------------------------------- */ 125 | /* Character Creation Hooks */ 126 | /* -------------------------------------------- */ 127 | 128 | Hooks.on('createActor', async function(actor) { 129 | if (actor.type === 'character') { 130 | actor.updateSource({ 131 | prototypeToken: { 132 | actorLink: true, 133 | disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY 134 | } 135 | }); 136 | } else if (actor.type === 'monster') { 137 | actor.updateSource({ 138 | prototypeToken: { 139 | appendNumber: true, 140 | displayName: CONST.TOKEN_DISPLAY_MODES.OWNER 141 | } 142 | }); 143 | } else if (actor.type === 'stronghold') { 144 | const floor = { 145 | name: `New ${game.i18n.localize('ITEM.TypeFloor')}`, 146 | type: 'floor' 147 | }; 148 | if (!actor.items.size) await actor.createEmbeddedDocuments('Item', [floor]); 149 | } 150 | }); 151 | 152 | /* -------------------------------------------- */ 153 | /* Token Creation Hooks */ 154 | /* -------------------------------------------- */ 155 | 156 | Hooks.on('createToken', async function(token, options, id) { 157 | if (token.actor.type === 'monster') { 158 | let newHitPoints = new Roll(`${token.actor.system.hitDice.number}${token.actor.system.hitDice.size}+${token.actor.system.hitDice.mod}`); 159 | await newHitPoints.evaluate({ async: true }); 160 | token.actor.system.hitPoints.value = Math.max(1, newHitPoints.total); 161 | token.actor.system.hitPoints.max = Math.max(1, newHitPoints.total); 162 | } 163 | }); 164 | 165 | /* -------------------------------------------- */ 166 | /* Hotbar Macros */ 167 | /* -------------------------------------------- */ 168 | 169 | /** 170 | * Create a Macro from an Item drop. 171 | * Get an existing item macro if one exists, otherwise create a new one. 172 | * @param {Object} data The dropped data 173 | * @param {number} slot The hotbar slot to use 174 | * @returns {Promise} 175 | */ 176 | async function createItemMacro(data, slot) { 177 | if (data.type !== 'Item') return; 178 | if (!('data' in data)) return ui.notifications.warn('You can only create macro buttons for owned Items'); 179 | const item = data.data; 180 | 181 | // Create the macro command 182 | const command = `game.basicfantasyrpg.rollItemMacro('${item.name}');`; 183 | let macro = game.macros.find(m => (m.name === item.name) && (m.command === command)); 184 | if (!macro) { 185 | macro = await Macro.create({ 186 | name: item.name, 187 | type: 'script', 188 | img: item.img, 189 | command: command, 190 | flags: { 'basicfantasyrpg.itemMacro': true } 191 | }); 192 | } 193 | game.user.assignHotbarMacro(macro, slot); 194 | return false; 195 | } 196 | 197 | /** 198 | * Create a Macro from an Item drop. 199 | * Get an existing item macro if one exists, otherwise create a new one. 200 | * @param {string} itemName 201 | * @return {Promise} 202 | */ 203 | function rollItemMacro(itemName) { 204 | const speaker = ChatMessage.getSpeaker(); 205 | let actor; 206 | if (speaker.token) actor = game.actors.tokens[speaker.token]; 207 | if (!actor) actor = game.actors.get(speaker.actor); 208 | const item = actor ? actor.items.find(i => i.name === itemName) : null; 209 | if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`); 210 | 211 | // Trigger the item roll 212 | return item.roll(); 213 | } -------------------------------------------------------------------------------- /template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actor": { 3 | "types": ["character", "monster", "siegeEngine", "stronghold", "vehicle"], 4 | "templates": { 5 | "base": { 6 | "armorClass": { 7 | "value": 11, 8 | "label": "BASICFANTASYRPG.ArmorClass", 9 | "abbr": "BASICFANTASYRPG.ArmorClassAbbr" 10 | }, 11 | "attackBonus": { 12 | "value": 1, 13 | "label": "BASICFANTASYRPG.AttackBonus", 14 | "abbr": "BASICFANTASYRPG.AttackBonusAbbr" 15 | }, 16 | "biography": "", 17 | "hitPoints": { 18 | "value": 10, 19 | "max": 10, 20 | "label": "BASICFANTASYRPG.HitPoints", 21 | "abbr": "BASICFANTASYRPG.HitPointsAbbr" 22 | }, 23 | "initBonus": { 24 | "value": 0, 25 | "label": "BASICFANTASYRPG.InitiativeBonus" 26 | }, 27 | "move": { 28 | "value": 30, 29 | "label": "BASICFANTASYRPG.Movement" 30 | }, 31 | "saves": { 32 | "death": { 33 | "value": 13 34 | }, 35 | "wands": { 36 | "value": 14 37 | }, 38 | "paralysis": { 39 | "value": 15 40 | }, 41 | "breath": { 42 | "value": 16 43 | }, 44 | "spells": { 45 | "value": 18 46 | } 47 | } 48 | } 49 | }, 50 | "character": { 51 | "templates": ["base"], 52 | "abilities": { 53 | "str": { 54 | "value": 10 55 | }, 56 | "int": { 57 | "value": 10 58 | }, 59 | "wis": { 60 | "value": 10 61 | }, 62 | "dex": { 63 | "value": 10 64 | }, 65 | "con": { 66 | "value": 10 67 | }, 68 | "cha": { 69 | "value": 10 70 | } 71 | }, 72 | "age": { 73 | "value": "", 74 | "label": "BASICFANTASYRPG.Age" 75 | }, 76 | "class": { 77 | "value": "", 78 | "label": "BASICFANTASYRPG.Class" 79 | }, 80 | "race": { 81 | "value": "", 82 | "label": "BASICFANTASYRPG.Race" 83 | }, 84 | "sex": { 85 | "value": "", 86 | "label": "BASICFANTASYRPG.Sex" 87 | }, 88 | "level": { 89 | "value": 1, 90 | "label": "BASICFANTASYRPG.Level" 91 | }, 92 | "money": { 93 | "pp": { 94 | "value": 0 95 | }, 96 | "gp": { 97 | "value": 0 98 | }, 99 | "ep": { 100 | "value": 0 101 | }, 102 | "sp": { 103 | "value": 0 104 | }, 105 | "cp": { 106 | "value": 0 107 | } 108 | }, 109 | "spellsPerLevel": { 110 | "value": { 111 | "1": 0, 112 | "2": 0, 113 | "3": 0, 114 | "4": 0, 115 | "5": 0, 116 | "6": 0 117 | }, 118 | "label": "BASICFANTASYRPG.SpellsPerLevel" 119 | }, 120 | "xp": { 121 | "value": 0, 122 | "next": 2000, 123 | "label": "BASICFANTASYRPG.ExperiencePoints", 124 | "abbr": "BASICFANTASYRPG.ExperiencePointsAbbr" 125 | } 126 | }, 127 | "monster": { 128 | "templates": ["base"], 129 | "hitDice": { 130 | "size": "d8", 131 | "number": 1, 132 | "mod": 0, 133 | "label": "BASICFANTASYRPG.HitDice", 134 | "abbr": "BASICFANTASYRPG.HitDiceAbbr" 135 | }, 136 | "morale": { 137 | "value": 7, 138 | "label": "BASICFANTASYRPG.Morale" 139 | }, 140 | "numberAppearing": { 141 | "value": "1d4", 142 | "label": "BASICFANTASYRPG.NumberAppearing" 143 | }, 144 | "specialAbility": { 145 | "value": 0, 146 | "label": "BASICFANTASYRPG.SpecialAbilityXPBonus" 147 | }, 148 | "treasureType": { 149 | "value": "None", 150 | "label": "BASICFANTASYRPG.TreasureType" 151 | }, 152 | "xp": { 153 | "value": 0, 154 | "label": "BASICFANTASYRPG.ExperiencePoints", 155 | "abbr": "BASICFANTASYRPG.ExperiencePointsAbbr" 156 | } 157 | }, 158 | "siegeEngine": { 159 | "templates": [], 160 | "attackBonus": { 161 | "value": 0, 162 | "label": "BASICFANTASYRPG.AttackBonus", 163 | "abbr": "BASICFANTASYRPG.AttackBonusAbbr" 164 | }, 165 | "biography": "", 166 | "cost": { 167 | "value": "", 168 | "label": "BASICFANTASYRPG.Cost" 169 | }, 170 | "damage": { 171 | "value": 0, 172 | "label": "BASICFANTASYRPG.Damage" 173 | }, 174 | "rangeBonus": { 175 | "value": 0, 176 | "label": "BASICFANTASYRPG.RangeBonus" 177 | }, 178 | "rangeShort": { 179 | "value": "", 180 | "label": "BASICFANTASYRPG.RangeShort" 181 | }, 182 | "rangeMedium": { 183 | "value": "", 184 | "label": "BASICFANTASYRPG.RangeMedium" 185 | }, 186 | "rangeLong": { 187 | "value": "", 188 | "label": "BASICFANTASYRPG.RangeLong" 189 | }, 190 | "rateOfFire": { 191 | "value": "", 192 | "label": "BASICFANTASYRPG.RateOfFire" 193 | }, 194 | "targetAC": { 195 | "value": 20, 196 | "label": "BASICFANTASYRPG.TargetAC" 197 | } 198 | }, 199 | "stronghold": { 200 | "templates": [], 201 | "biography": "", 202 | "costMultiplier": { 203 | "value": 1, 204 | "label": "BASICFANTASYRPG.CostMultiplier" 205 | }, 206 | "floors": [], 207 | "followers": { 208 | "value": 0, 209 | "label": "BASICFANTASYRPG.Followers" 210 | }, 211 | "workers": { 212 | "value": 1, 213 | "label": "BASICFANTASYRPG.Workers" 214 | } 215 | }, 216 | "vehicle": { 217 | "templates": [], 218 | "armorClass": { 219 | "value": 11, 220 | "label": "BASICFANTASYRPG.ArmorClass", 221 | "abbr": "BASICFANTASYRPG.ArmorClassAbbr" 222 | }, 223 | "biography": "", 224 | "cargo": { 225 | "value": 0, 226 | "label": "BASICFANTASYRPG.Cargo" 227 | }, 228 | "hardness": { 229 | "value": 6, 230 | "label": "BASICFANTASYRPG.Hardness" 231 | }, 232 | "hitPoints": { 233 | "aft": { 234 | "value": 16, 235 | "max": 16, 236 | "label": "BASICFANTASYRPG.SideAft" 237 | }, 238 | "forward": { 239 | "value": 16, 240 | "max": 16, 241 | "label": "BASICFANTASYRPG.SideForward" 242 | }, 243 | "port": { 244 | "value": 16, 245 | "max": 16, 246 | "label": "BASICFANTASYRPG.SidePort" 247 | }, 248 | "starboard": { 249 | "value": 16, 250 | "max": 16, 251 | "label": "BASICFANTASYRPG.SideStarboard" 252 | }, 253 | "label": "BASICFANTASYRPG.HitPoints", 254 | "abbr": "BASICFANTASYRPG.HitPointsAbbr" 255 | }, 256 | "length": { 257 | "value": 35, 258 | "label": "BASICFANTASYRPG.Length" 259 | }, 260 | "maneuverability": { 261 | "value": "15'", 262 | "label": "BASICFANTASYRPG.Maneuverability" 263 | }, 264 | "move": { 265 | "value": 20, 266 | "label": "BASICFANTASYRPG.Movement" 267 | }, 268 | "width": { 269 | "value": 8, 270 | "label": "BASICFANTASYRPG.Width" 271 | } 272 | } 273 | }, 274 | "Item": { 275 | "types": ["item", "weapon", "armor", "spell", "feature", "floor", "wall"], 276 | "templates": { 277 | "base": { 278 | "description": "" 279 | }, 280 | "valuable": { 281 | "price": { 282 | "value": "", 283 | "label": "BASICFANTASYRPG.Price" 284 | }, 285 | "weight": { 286 | "value": 1, 287 | "label": "BASICFANTASYRPG.Weight" 288 | } 289 | } 290 | }, 291 | "item": { 292 | "templates": ["base", "valuable"], 293 | "quantity": { 294 | "value": 1, 295 | "label": "BASICFANTASYRPG.Quantity" 296 | } 297 | }, 298 | "weapon": { 299 | "templates": ["base", "valuable"], 300 | "bonusAb": { 301 | "value": 0, 302 | "label": "BASICFANTASYRPG.BonusAttackBonus" 303 | }, 304 | "damage": { 305 | "value": "1d6", 306 | "label": "BASICFANTASYRPG.Damage", 307 | "abbr": "BASICFANTASYRPG.DamageAbbr" 308 | }, 309 | "range":{ 310 | "value": "Melee", 311 | "label": "BASICFANTASYRPG.Range" 312 | }, 313 | "size": { 314 | "value": "M", 315 | "label": "BASICFANTASYRPG.Size" 316 | } 317 | }, 318 | "armor": { 319 | "templates": ["base", "valuable"], 320 | "armorClass": { 321 | "value": 11, 322 | "label": "BASICFANTASYRPG.ArmorClass", 323 | "abbr": "BASICFANTASYRPG.ArmorClassAbbr" 324 | } 325 | }, 326 | "spell": { 327 | "templates": ["base"], 328 | "class": { 329 | "value": "", 330 | "label": "BASICFANTASYRPG.Class" 331 | }, 332 | "duration": { 333 | "value": "", 334 | "label": "BASICFANTASYRPG.Duration" 335 | }, 336 | "prepared": { 337 | "value": 0, 338 | "label": "BASICFANTASYRPG.Prepared" 339 | }, 340 | "range": { 341 | "value": "", 342 | "label": "BASICFANTASYRPG.Range" 343 | }, 344 | "spellLevel": { 345 | "value": 1, 346 | "label": "BASICFANTASYRPG.SpellLevel" 347 | } 348 | }, 349 | "feature": { 350 | "templates": ["base"], 351 | "formula": { 352 | "value": "d100", 353 | "label": "BASICFANTASYRPG.Formula" 354 | }, 355 | "rollUnder": { 356 | "value": true, 357 | "label": "BASICFANTASYRPG.RollUnder" 358 | }, 359 | "targetNumber": { 360 | "value": "", 361 | "label": "BASICFANTASYRPG.TargetNumber" 362 | } 363 | }, 364 | "floor": { 365 | "templates": ["base", "valuable"], 366 | "area": { 367 | "value": 0, 368 | "label": "BASICFANTASYRPG.Area" 369 | }, 370 | "height": { 371 | "value": 10, 372 | "label": "BASICFANTASYRPG.Height" 373 | }, 374 | "material": { 375 | "value": "floor", 376 | "label": "BASICFANTASYRPG.Material" 377 | } 378 | }, 379 | "wall": { 380 | "templates": ["base", "valuable"], 381 | "floor": { 382 | "value": 0, 383 | "label": "TYPES.Item.floor" 384 | }, 385 | "hardness": { 386 | "value": 6, 387 | "label": "BASICFANTASYRPG.Hardness" 388 | }, 389 | "material": { 390 | "value": "wood", 391 | "label": "BASICFANTASYRPG.Material" 392 | }, 393 | "quantity": { 394 | "value": 1, 395 | "label": "BASICFANTASYRPG.Quantity" 396 | }, 397 | "thickness": { 398 | "value": 1, 399 | "label": "BASICFANTASYRPG.Thickness" 400 | } 401 | } 402 | } 403 | } -------------------------------------------------------------------------------- /module/documents/actor.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system. 3 | * @extends {Actor} 4 | */ 5 | export class BasicFantasyRPGActor extends Actor { 6 | 7 | /** @override */ 8 | prepareData() { 9 | // Prepare data for the actor. Calling the super version of this executes 10 | // the following, in order: data reset (to clear active effects), 11 | // prepareBaseData(), prepareEmbeddedDocuments() (including active effects), 12 | // prepareDerivedData(). 13 | super.prepareData(); 14 | } 15 | 16 | /** @override */ 17 | prepareBaseData() { 18 | // Data modifications in this step occur before processing embedded 19 | // documents or derived data. 20 | const actorData = this; 21 | 22 | // Make separate methods for each Actor type to keep things organized. 23 | this._prepareCharacterData(actorData); 24 | this._prepareMonsterData(actorData); 25 | this._prepareSiegeEngineData(actorData); 26 | this._prepareStrongholdData(actorData); 27 | this._prepareVehicleData(actorData); 28 | } 29 | 30 | /** 31 | * @override 32 | * Augment the basic actor data with additional dynamic data. Typically, 33 | * you'll want to handle most of your calculated/derived data in this step. 34 | * Data calculated in this step should generally not exist in template.json 35 | * (such as ability modifiers rather than ability scores) and should be 36 | * available both inside and outside of character sheets (such as if an actor 37 | * is queried and has a roll executed directly from it). 38 | */ 39 | prepareDerivedData() { 40 | const actorData = this; 41 | 42 | // Make separate methods for each Actor type to keep things organized. 43 | this._prepareCharacterDerivedData(actorData); 44 | this._prepareMonsterDerivedData(actorData); 45 | this._prepareSiegeEngineDerivedData(actorData); 46 | this._prepareStrongholdDerivedData(actorData); 47 | this._prepareVehicleDerivedData(actorData); 48 | } 49 | 50 | 51 | /** 52 | * Prepare Character type template data 53 | */ 54 | _prepareCharacterData(actorData) { 55 | if (actorData.type !== 'character') return; 56 | } 57 | 58 | /** 59 | * Prepare Character type derived data 60 | */ 61 | _prepareCharacterDerivedData(actorData) { 62 | if (actorData.type !== 'character') return; 63 | 64 | // Make modifications to data here. For example: 65 | const data = actorData.system; 66 | 67 | // Loop through ability scores, and add their modifiers to our sheet output. 68 | for (let [key, ability] of Object.entries(data.abilities)) { 69 | // Calculate the ability bonus 70 | ability.bonus = this._calculateAbilityBonus(ability.value); 71 | } 72 | } 73 | 74 | /** 75 | * Determine ability score modifiers 76 | */ 77 | _calculateAbilityBonus(abilityScore) { 78 | switch (abilityScore) { 79 | case 3: return -3; 80 | case 4: 81 | case 5: return -2; 82 | case 6: 83 | case 7: 84 | case 8: return -1; 85 | case 13: 86 | case 14: 87 | case 15: return 1; 88 | case 16: 89 | case 17: return 2; 90 | case 18: return 3; 91 | default: return 0; 92 | } 93 | } 94 | 95 | 96 | /** 97 | * Prepare Monster type template data. 98 | */ 99 | _prepareMonsterData(actorData) { 100 | if (actorData.type !== 'monster') return; 101 | 102 | const data = actorData.system; 103 | 104 | // Handle changed label for monster special ability XP bonus -- this will be handled in the system data model when it's implemented 105 | data.specialAbility.label = 'BASICFANTASYRPG.SpecialAbilityXPBonus'; 106 | 107 | data.xp.value = this._calculateMonsterXPValue(); 108 | data.attackBonus.value = this._calculateMonsterAttackBonus(); 109 | } 110 | 111 | /** 112 | * Calculate monster attack bonus 113 | */ 114 | _calculateMonsterAttackBonus() { 115 | const hitDiceNumber = this.system.hitDice.number; 116 | if (hitDiceNumber < 1) { 117 | return 0; 118 | } else if (hitDiceNumber > 31) { 119 | return 16; 120 | } 121 | switch (hitDiceNumber) { 122 | case 9: return 8; 123 | case 10: 124 | case 11: return 9 125 | case 12: 126 | case 13: return 10; 127 | case 14: 128 | case 15: return 11; 129 | case 16: 130 | case 17: 131 | case 18: 132 | case 19: return 12; 133 | case 20: 134 | case 21: 135 | case 22: 136 | case 23: return 13; 137 | case 24: 138 | case 25: 139 | case 26: 140 | case 27: return 14; 141 | case 28: 142 | case 29: 143 | case 30: 144 | case 31: return 15; 145 | default: return hitDiceNumber; // this handles 1-9 146 | } 147 | } 148 | 149 | /** 150 | * Calculate Monster XP value 151 | */ 152 | _calculateMonsterXPValue() { 153 | const hitDice = this.system.hitDice; 154 | const specialAbility = this.system.specialAbility.value; 155 | let xpLookup = [10, 25, 75, 145, 240, 360, 500, 670, 875, 1075, 1300, 1575, 1875, 2175, 2500, 2850, 3250, 3600, 4000, 4500, 5250, 6000, 6750, 7500, 8250, 9000]; 156 | let specialAbilityLookup = [3, 12, 25, 30, 40, 45, 55, 65, 70, 75, 90, 95, 100, 110, 115, 125, 135, 145, 160, 175, 200, 225, 250, 275, 300, 325]; 157 | let xpValue = 0; 158 | let xpSpecialAbilityBonus = 0; 159 | if (hitDice.number < 1 || (hitDice.number === 1 && hitDice.mod < 0) || hitDice.size < 'd8') { 160 | xpValue = xpLookup[0]; 161 | xpSpecialAbilityBonus = specialAbilityLookup[0] * specialAbility; 162 | } else if (hitDice.number > 25) { 163 | xpValue = 9000 + (hitDice.number - 25) * 750; 164 | xpSpecialAbilityBonus = (325 + (hitDice.number - 25) * 25) * specialAbility; 165 | } else { 166 | xpValue = xpLookup[hitDice.number]; 167 | xpSpecialAbilityBonus = specialAbilityLookup[hitDice.number] * specialAbility; 168 | } 169 | return xpValue + Math.max(0, xpSpecialAbilityBonus); // never return a negative special ability bonus 170 | } 171 | 172 | /** 173 | * Prepare Monster type derived data. 174 | */ 175 | _prepareMonsterDerivedData(actorData) { 176 | if (actorData.type !== 'monster') return; 177 | } 178 | 179 | 180 | /** 181 | * Prepare Siege Engine type template data 182 | */ 183 | _prepareSiegeEngineData(actorData) { 184 | if (actorData.type !== 'siegeEngine') return; 185 | } 186 | 187 | /** 188 | * Prepare Siege Engine type derived data 189 | */ 190 | _prepareSiegeEngineDerivedData(actorData) { 191 | if (actorData.type !== 'siegeEngine') return; 192 | } 193 | 194 | /** 195 | * Prepare Stronghold type template data 196 | */ 197 | _prepareStrongholdData(actorData) { 198 | if (actorData.type !== 'stronghold') return; 199 | 200 | const data = actorData.system; 201 | const floors = actorData.itemTypes.floor; 202 | const walls = actorData.itemTypes.wall; 203 | 204 | floors.forEach(floor => { 205 | switch (floor.system.material.value) { 206 | case 'roofSlate': floor.system.price.value = floor.system.area.value / 10 * 4; break; 207 | case 'roofWood': floor.system.price.value = floor.system.area.value / 10 * 2; break; 208 | case 'floor': 209 | case 'roofThatch': 210 | default: floor.system.price.value = floor.system.area.value / 10; break; 211 | } 212 | }); 213 | 214 | walls.forEach(wall => { 215 | switch (wall.system.material.value) { 216 | case 'stoneHard': 217 | wall.system.hardness.value = 16; 218 | switch (wall.system.thickness.value) { 219 | case 15: wall.system.price.value = 350; break; 220 | case 10: wall.system.price.value = 260; break; 221 | case 5: wall.system.price.value = 90; break; 222 | default: wall.system.price.value = 40; break; 223 | } 224 | break; 225 | case 'stoneSoft': 226 | wall.system.hardness.value = 12; 227 | switch (wall.system.thickness.value) { 228 | case 10: wall.system.price.value = 200; break; 229 | case 5: wall.system.price.value = 70; break; 230 | default: wall.system.price.value = 30; break; 231 | } 232 | break; 233 | case 'brick': 234 | wall.system.hardness.value = 8; 235 | switch (wall.system.thickness.value) { 236 | case 5: wall.system.price.value = 50; break; 237 | default: wall.system.price.value = 20; break; 238 | } 239 | break; 240 | case 'wood': 241 | default: 242 | wall.system.hardness.value = 6; 243 | wall.system.thickness.value = 1; 244 | wall.system.price.value = 10; 245 | break; 246 | } 247 | wall.system.price.value *= wall.system.quantity.value; 248 | }); 249 | } 250 | 251 | /** 252 | * Prepare Stronghold type derived data 253 | */ 254 | _prepareStrongholdDerivedData(actorData) { 255 | if (actorData.type !== 'stronghold') return; 256 | 257 | const data = actorData.system; 258 | const floors = actorData.itemTypes.floor; 259 | const walls = actorData.itemTypes.wall; 260 | 261 | let totalCost = 0; 262 | let totalHeight = 0; 263 | floors.forEach(floor => { 264 | totalHeight += floor.system.height.value; 265 | totalCost += floor.system.price.value; 266 | }); 267 | walls.forEach(wall => { 268 | totalCost += wall.system.price.value; 269 | }); 270 | 271 | data.height = { 272 | "value": totalHeight, 273 | "label": 'BASICFANTASYRPG.Height' 274 | }; 275 | 276 | data.cost = { 277 | "value": (totalCost + (totalCost * (totalHeight / 100))) * data.costMultiplier.value, // each 10' of height adds 10% to the costs in both time and money 278 | "label": 'BASICFANTASYRPG.Cost' 279 | }; 280 | 281 | data.buildTime = { 282 | "value": Math.ceil(Math.max(data.cost.value / data.workers.value, Math.sqrt(data.cost.value))), 283 | "label": 'BASICFANTASYRPG.BuildTime' 284 | }; 285 | } 286 | 287 | 288 | /** 289 | * Prepare Vehicle type template data 290 | */ 291 | _prepareVehicleData(actorData) { 292 | if (actorData.type !== 'vehicle') return; 293 | 294 | const data = actorData.system; 295 | data.hitPoints.value = 0; 296 | data.hitPoints.max = 0; 297 | 298 | // Calculate totals for HP value and HP max 299 | for (let [key, side] of Object.entries(data.hitPoints)) { 300 | if (['forward', 'aft', 'port', 'starboard'].includes(key)) { 301 | data.hitPoints.value += side.value; 302 | data.hitPoints.max += side.max; 303 | } 304 | } 305 | } 306 | 307 | /** 308 | * Prepare Vehicle type derived data 309 | */ 310 | _prepareVehicleDerivedData(actorData) { 311 | if (actorData.type !== 'vehicle') return; 312 | 313 | const data = actorData.system; 314 | 315 | // Check if any 1 or 2 sides are reduced to 0 HP 316 | let sidesAtZeroHP = 0; 317 | for (let [key, side] of Object.entries(data.hitPoints)) { 318 | if (['forward', 'aft', 'port', 'starboard'].includes(key) && side.value === 0 && side.max !== 0) { 319 | ++sidesAtZeroHP; 320 | } 321 | } 322 | if (sidesAtZeroHP === 1) { 323 | data.move.current = Math.floor(data.move.value / 2); 324 | } else if (sidesAtZeroHP > 1) { 325 | data.move.current = 0; 326 | } else { 327 | data.move.current = data.move.value; 328 | } 329 | } 330 | 331 | 332 | /** 333 | * Override getRollData() that's supplied to rolls. 334 | */ 335 | getRollData() { 336 | const data = super.getRollData(); 337 | 338 | // Prepare character roll data. 339 | this._getCharacterRollData(data); 340 | this._getMonsterRollData(data); 341 | this._getSiegeEngineRollData(data); 342 | this._getStrongholdRollData(data); 343 | this._getVehicleRollData(data); 344 | this._getActorRollData(data); 345 | 346 | return data; 347 | } 348 | 349 | /** 350 | * Prepare character roll data. 351 | */ 352 | _getCharacterRollData(data) { 353 | if (this.type !== 'character') return; 354 | 355 | // Copy the ability scores to the top level, so that rolls can use 356 | // formulas like `@str.bonus + 4`. 357 | if (data.abilities) { 358 | for (let [k, v] of Object.entries(data.abilities)) { 359 | data[k] = foundry.utils.deepClone(v); 360 | } 361 | } 362 | 363 | // Add level for easier access, or fall back to 0. 364 | if (data.level) { 365 | data.lvl = data.level.value ?? 0; 366 | } 367 | } 368 | 369 | /** 370 | * Prepare NPC roll data. 371 | */ 372 | _getMonsterRollData(data) { 373 | if (this.type !== 'monster') return; 374 | 375 | // Process additional NPC data here. 376 | 377 | } 378 | 379 | /** 380 | * Prepare Siege Engine roll data. 381 | */ 382 | _getSiegeEngineRollData(data) { 383 | if (this.type !== 'siegeEngine') return; 384 | 385 | // Process additional Siege Engine data here. 386 | 387 | } 388 | 389 | /** 390 | * Prepare Stronghold roll data. 391 | */ 392 | _getStrongholdRollData(data) { 393 | if (this.type !== 'stronghold') return; 394 | 395 | // Process additional Stronghold data here. 396 | 397 | } 398 | 399 | /** 400 | * Prepare Vehicle roll data. 401 | */ 402 | _getVehicleRollData(data) { 403 | if (this.type !== 'vehicle') return; 404 | 405 | // Process additional Vehicle data here. 406 | 407 | } 408 | 409 | /** 410 | * Prepare shared Actor roll data. 411 | */ 412 | _getActorRollData(data) { 413 | // Add attack bonus for easier access, or fall back to 0. 414 | if (data.attackBonus) { 415 | data.ab = data.attackBonus.value ?? 0; 416 | } 417 | } 418 | } -------------------------------------------------------------------------------- /module/sheets/actor-sheet.mjs: -------------------------------------------------------------------------------- 1 | import {successChatMessage} from '../helpers/chat.mjs'; 2 | import {onManageActiveEffect, prepareActiveEffectCategories} from '../helpers/effects.mjs'; 3 | 4 | /** 5 | * Extend the basic ActorSheet with some very simple modifications 6 | * @extends {ActorSheet} 7 | */ 8 | export class BasicFantasyRPGActorSheet extends ActorSheet { 9 | 10 | /** @override */ 11 | static get defaultOptions() { 12 | return foundry.utils.mergeObject(super.defaultOptions, { 13 | classes: ['basicfantasyrpg', 'sheet', 'actor'], 14 | template: 'systems/basicfantasyrpg/templates/actor/actor-sheet.html', 15 | width: 600, 16 | height: 600, 17 | tabs: [{ navSelector: '.sheet-tabs', contentSelector: '.sheet-body', initial: 'combat' }] 18 | }); 19 | } 20 | 21 | /** @override */ 22 | get template() { 23 | return `systems/basicfantasyrpg/templates/actor/actor-${this.actor.type}-sheet.html`; 24 | } 25 | 26 | /* -------------------------------------------- */ 27 | 28 | /** @override */ 29 | async getData() { 30 | // Retrieve the data structure from the base sheet. You can inspect or log 31 | // the context variable to see the structure, but some key properties for 32 | // sheets are the actor object, the data object, whether or not it's 33 | // editable, the items array, and the effects array. 34 | const context = super.getData(); 35 | 36 | //enrichedBiography -- enriches system.biography for editor 37 | context.enrichedBiography = await TextEditor.enrichHTML(this.object.system.biography, {async: true}); 38 | 39 | // Use a safe clone of the actor data for further operations. 40 | const actorData = this.actor.toObject(false); 41 | 42 | // Add the actor's data to context.data for easier access, as well as flags. 43 | context.data = actorData.system; 44 | context.flags = actorData.flags; 45 | 46 | // Prepare character data and items. 47 | if (actorData.type === 'character') { 48 | this._prepareItems(context); 49 | this._prepareCharacterData(context); 50 | this._prepareActorData(context); 51 | } 52 | 53 | // Prepare NPC data and items. 54 | if (actorData.type === 'monster') { 55 | this._prepareItems(context); 56 | this._prepareActorData(context); 57 | } 58 | 59 | // Prepare Stronghold data and items 60 | if (actorData.type === 'stronghold') { 61 | this._prepareItems(context); 62 | } 63 | 64 | // Prepare Vehicle data and items 65 | if (actorData.type === 'vehicle') { 66 | this._prepareItems(context); 67 | } 68 | 69 | // Add roll data for TinyMCE editors. 70 | context.rollData = context.actor.getRollData(); 71 | 72 | // Prepare active effects 73 | context.effects = prepareActiveEffectCategories(this.actor.effects); 74 | 75 | return context; 76 | } 77 | 78 | /** 79 | * Organize and classify Items for Actor sheets. 80 | * 81 | * @param {Object} actorData The actor to prepare. 82 | * 83 | * @return {undefined} 84 | */ 85 | _prepareActorData(context) { 86 | // Handle saves. 87 | for (let [k, v] of Object.entries(context.data.saves)) { 88 | v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.saves[k]) ?? k; 89 | } 90 | } 91 | 92 | /** 93 | * Organize and classify Items for Character sheets. 94 | * 95 | * @param {Object} actorData The actor to prepare. 96 | * 97 | * @return {undefined} 98 | */ 99 | _prepareCharacterData(context) { 100 | // Handle ability scores. 101 | for (let [k, v] of Object.entries(context.data.abilities)) { 102 | v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.abilities[k]) ?? k; 103 | } 104 | // Handle money. 105 | for (let [k, v] of Object.entries(context.data.money)) { 106 | v.label = game.i18n.localize(CONFIG.BASICFANTASYRPG.money[k]) ?? k; 107 | } 108 | } 109 | 110 | /** 111 | * Organize and classify Items for Actor sheets. 112 | * 113 | * @param {Object} actorData The actor to prepare. 114 | * 115 | * @return {undefined} 116 | */ 117 | _prepareItems(context) { 118 | // Initialize containers. 119 | const gear = []; 120 | const weapons = []; 121 | const armors = []; 122 | const spells = { 123 | 1: [], 124 | 2: [], 125 | 3: [], 126 | 4: [], 127 | 5: [], 128 | 6: [] 129 | }; 130 | const features = []; 131 | const floors = []; 132 | const walls = []; 133 | 134 | // Define an object to store carried weight. 135 | let carriedWeight = { 136 | 'value': 0, 137 | _addWeight (moreWeight, quantity) { 138 | if (!quantity || quantity === '' || Number.isNaN(quantity) || quantity < 0) { 139 | return; // check we have a valid quantity, and do nothing if we do not 140 | } 141 | let q = Math.floor(quantity / 20); 142 | if (!Number.isNaN(parseFloat(moreWeight))) { 143 | this.value += parseFloat(moreWeight) * quantity; 144 | } else if (moreWeight === '*' && q > 0) { // '*' is gold pieces 145 | this.value += q; 146 | } 147 | } 148 | }; 149 | 150 | // Iterate through items, allocating to containers 151 | for (let i of context.items) { 152 | i.img = i.img || DEFAULT_TOKEN; 153 | // Append to gear. 154 | if (i.type === 'item') { 155 | gear.push(i); 156 | carriedWeight._addWeight(i.system.weight.value, i.system.quantity.value); 157 | } else if (i.type === 'weapon') { // Append to weapons. 158 | weapons.push(i); 159 | carriedWeight._addWeight(i.system.weight.value, 1); // Weapons are always quantity 1 160 | } else if (i.type === 'armor') { // Append to armors. 161 | armors.push(i); 162 | carriedWeight._addWeight(i.system.weight.value, 1); // Armor is always quantity 1 163 | } else if (i.type === 'spell') { // Append to spells. 164 | if (i.system.spellLevel.value !== undefined) { 165 | spells[i.system.spellLevel.value].push(i); 166 | } 167 | } else if (i.type === 'feature') { // Append to features. 168 | features.push(i); 169 | } else if (i.type === 'floor') { // Append to floors for strongholds. 170 | floors.push(i); 171 | } else if (i.type === 'wall') { // Append to walls for strongholds. 172 | if (i.system.floor.value !== undefined) { 173 | if (!walls[i.system.floor.value]) walls[i.system.floor.value] = []; 174 | walls[i.system.floor.value].push(i); 175 | } 176 | } 177 | } 178 | 179 | // Iterate through money, add to carried weight 180 | if (context.data.money) { 181 | let gp = Number(context.data.money.gp.value); 182 | gp += context.data.money.pp.value; 183 | gp += context.data.money.ep.value; 184 | gp += context.data.money.sp.value; 185 | gp += context.data.money.cp.value; 186 | carriedWeight._addWeight('*', gp); // '*' will calculate GP weight 187 | } 188 | 189 | // Assign and return 190 | context.gear = gear; 191 | context.weapons = weapons; 192 | context.armors = armors; 193 | context.spells = spells; 194 | context.features = features; 195 | context.floors = floors; 196 | context.walls = walls; 197 | context.carriedWeight = Math.floor(carriedWeight.value); // we discard fractions of weight when we update the sheet 198 | } 199 | 200 | /* -------------------------------------------- */ 201 | 202 | /** @override */ 203 | activateListeners(html) { 204 | super.activateListeners(html); 205 | 206 | // Render the item sheet for viewing/editing prior to the editable check. 207 | html.find('.item-edit').click(ev => { 208 | const li = $(ev.currentTarget).parents('.item'); 209 | const item = this.actor.items.get(li.data('itemId')); 210 | item.sheet.render(true); 211 | }); 212 | 213 | // ------------------------------------------------------------- 214 | // Everything below here is only needed if the sheet is editable 215 | if (!this.isEditable) return; 216 | 217 | // Add Inventory Item 218 | html.find('.item-create').click(this._onItemCreate.bind(this)); 219 | 220 | // Delete Inventory Item 221 | html.find('.item-delete').click(ev => { 222 | const li = $(ev.currentTarget).parents('.item'); 223 | const item = this.actor.items.get(li.data('itemId')); 224 | item.delete(); 225 | li.slideUp(200, () => this.render(false)); 226 | }); 227 | 228 | // Prepare Spells 229 | html.find('.spell-prepare').click(ev => { 230 | const change = ev.currentTarget.dataset.change; 231 | if (parseInt(change)) { 232 | const li = $(ev.currentTarget).parents('.item'); 233 | const item = this.actor.items.get(li.data('itemId')); 234 | let newValue = item.system.prepared.value + parseInt(change); 235 | item.update({'system.prepared.value': newValue}); 236 | } 237 | }); 238 | 239 | // Quantity 240 | html.find('.quantity').click(ev => { 241 | const change = ev.currentTarget.dataset.change; 242 | if (parseInt(change)) { 243 | const li = $(ev.currentTarget).parents('.item'); 244 | const item = this.actor.items.get(li.data('itemId')); 245 | let newValue = item.system.quantity.value + parseInt(change); 246 | item.update({'system.quantity.value': newValue}); 247 | } 248 | }); 249 | 250 | // Active Effect management 251 | html.find('.effect-control').click(ev => onManageActiveEffect(ev, this.actor)); 252 | 253 | // Rollable abilities. 254 | html.find('.rollable').click(this._onRoll.bind(this)); 255 | 256 | // Siege Engine range bonuses 257 | if (this.actor.type === 'siegeEngine') { 258 | html.find('input[name="rangeBonus"]').click(ev => this.actor.update({'system.rangeBonus.value': Number(ev.currentTarget.value)})); 259 | } 260 | 261 | // Drag events for macros. 262 | if (this.actor.isOwner) { 263 | let handler = ev => this._onDragStart(ev); 264 | html.find('li.item').each((i, li) => { 265 | if (li.classList.contains('inventory-header')) return; 266 | li.setAttribute('draggable', true); 267 | li.addEventListener('dragstart', handler, false); 268 | }); 269 | } 270 | } 271 | 272 | /** 273 | * Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset 274 | * @param {Event} event The originating click event 275 | * @private 276 | */ 277 | async _onItemCreate(event) { 278 | event.preventDefault(); 279 | const header = event.currentTarget; 280 | // Get the type of item to create. 281 | const type = header.dataset.type; 282 | // Grab any data associated with this control. 283 | const data = foundry.utils.duplicate(header.dataset); 284 | if (type === 'spell') { 285 | // Move dataset spellLevelValue into spellLevel.value 286 | data.spellLevel = { 287 | 'value': data.spellLevelValue 288 | }; 289 | delete data.spellLevelValue; 290 | } else if (type === 'wall') { 291 | data.floor = { 292 | "value": data.floorNumber 293 | }; 294 | delete data.floorNumber; 295 | } 296 | // Initialize a default name. 297 | const name = `New ${type.capitalize()}`; 298 | // Prepare the item object. 299 | const itemData = { 300 | name: name, 301 | type: type, 302 | data: data 303 | }; 304 | // Remove the type from the dataset since it's in the itemData.type prop. 305 | delete itemData.data['type']; 306 | 307 | // Finally, create the item! 308 | return await Item.create(itemData, {parent: this.actor}); 309 | } 310 | 311 | /** 312 | * Handle clickable rolls. 313 | * @param {Event} event The originating click event 314 | * @private 315 | */ 316 | async _onRoll(event) { 317 | event.preventDefault(); 318 | const element = event.currentTarget; 319 | const dataset = element.dataset; 320 | 321 | if (dataset.rollType) { 322 | // Handle weapon rolls. 323 | if (dataset.rollType === 'weapon') { 324 | const itemId = element.closest('.item').dataset.itemId; 325 | const item = this.actor.items.get(itemId); 326 | let label = dataset.label ? `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.attack.capitalize()} attack with ${item.name}`; 327 | let rollFormula = 'd20+@ab'; 328 | if (this.actor.type === 'character') { 329 | if (dataset.attack === 'melee') { 330 | rollFormula += '+@str.bonus'; 331 | } else if (dataset.attack === 'ranged') { 332 | rollFormula += '+@dex.bonus'; 333 | } 334 | } 335 | rollFormula += '+' + item.system.bonusAb.value; 336 | let roll = new Roll(rollFormula, this.actor.getRollData()); 337 | roll.toMessage({ 338 | speaker: ChatMessage.getSpeaker({ actor: this.actor }), 339 | flavor: label, 340 | rollMode: game.settings.get('core', 'rollMode'), 341 | }); 342 | return roll; 343 | } 344 | 345 | // Handle item rolls. 346 | if (dataset.rollType === 'item') { 347 | const itemId = element.closest('.item').dataset.itemId; 348 | const item = this.actor.items.get(itemId); 349 | if (item) return item.roll(); 350 | } 351 | } 352 | 353 | // Handle rolls that supply the formula directly. 354 | if (dataset.roll) { 355 | let label = dataset.label ? `${game.i18n.localize('BASICFANTASYRPG.Roll')}: ${dataset.label}` : ''; 356 | let roll = new Roll(dataset.roll, this.actor.getRollData()); 357 | await roll.roll(); 358 | label += successChatMessage(roll.total, dataset.targetNumber, dataset.rollUnder); 359 | roll.toMessage({ 360 | speaker: ChatMessage.getSpeaker({ actor: this.actor }), 361 | flavor: label, 362 | rollMode: game.settings.get('core', 'rollMode'), 363 | }); 364 | return roll; 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /styles/basicfantasyrpg.css: -------------------------------------------------------------------------------- 1 | /* Custom Fonts */ 2 | @font-face { 3 | font-family: 'Soutane'; 4 | src: url('soutane-webfont.eot'); 5 | src: url('soutane-webfont.eot?#iefix') format('embedded-opentype'), 6 | url('soutane-webfont.woff') format('woff'), 7 | url('soutane-webfont.ttf') format('truetype'), 8 | url('soutane-webfont.svg#soutaneregular') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'Soutane'; 15 | src: url('soutanebold-webfont.eot'); 16 | src: url('soutanebold-webfont.eot?#iefix') format('embedded-opentype'), 17 | url('soutanebold-webfont.woff') format('woff'), 18 | url('soutanebold-webfont.ttf') format('truetype'), 19 | url('soutanebold-webfont.svg#soutanebold') format('svg'); 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | @font-face { 25 | font-family: 'Soutane'; 26 | src: url('soutanebolditalic-webfont.eot'); 27 | src: url('soutanebolditalic-webfont.eot?#iefix') format('embedded-opentype'), 28 | url('soutanebolditalic-webfont.woff') format('woff'), 29 | url('soutanebolditalic-webfont.ttf') format('truetype'), 30 | url('soutanebolditalic-webfont.svg#soutanebold_italic') format('svg'); 31 | font-weight: bold; 32 | font-style: italic; 33 | } 34 | 35 | @font-face { 36 | font-family: 'Soutane'; 37 | src: url('soutaneitalic-webfont.eot'); 38 | src: url('soutaneitalic-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('soutaneitalic-webfont.woff') format('woff'), 40 | url('soutaneitalic-webfont.ttf') format('truetype'), 41 | url('soutaneitalic-webfont.svg#soutaneitalic') format('svg'); 42 | font-weight: normal; 43 | font-style: italic; 44 | } 45 | 46 | @font-face { 47 | font-family: 'SoutaneBlack'; 48 | src: url('soutaneblack-webfont.eot'); 49 | src: url('soutaneblack-webfont.eot?#iefix') format('embedded-opentype'), 50 | url('soutaneblack-webfont.woff') format('woff'), 51 | url('soutaneblack-webfont.ttf') format('truetype'), 52 | url('soutaneblack-webfont.svg#soutaneblackregular') format('svg'); 53 | font-weight: normal; 54 | font-style: normal; 55 | } 56 | 57 | @font-face { 58 | font-family: 'TeX Gyre Adventor'; 59 | src: url('texgyreadventor-regular-webfont.eot'); 60 | src: url('texgyreadventor-regular-webfont.eot?#iefix') format('embedded-opentype'), 61 | url('texgyreadventor-regular-webfont.woff') format('woff'), 62 | url('texgyreadventor-regular-webfont.ttf') format('truetype'), 63 | url('texgyreadventor-regular-webfont.svg#tex_gyre_adventorregular') format('svg'); 64 | font-weight: normal; 65 | font-style: normal; 66 | } 67 | 68 | @font-face { 69 | font-family: 'TeX Gyre Adventor'; 70 | src: url('texgyreadventor-italic-webfont.eot'); 71 | src: url('texgyreadventor-italic-webfont.eot?#iefix') format('embedded-opentype'), 72 | url('texgyreadventor-italic-webfont.woff') format('woff'), 73 | url('texgyreadventor-italic-webfont.ttf') format('truetype'), 74 | url('texgyreadventor-italic-webfont.svg#tex_gyre_adventoritalic') format('svg'); 75 | font-weight: normal; 76 | font-style: italic; 77 | } 78 | 79 | @font-face { 80 | font-family: 'TeX Gyre Adventor'; 81 | src: url('texgyreadventor-bold-webfont.eot'); 82 | src: url('texgyreadventor-bold-webfont.eot?#iefix') format('embedded-opentype'), 83 | url('texgyreadventor-bold-webfont.woff') format('woff'), 84 | url('texgyreadventor-bold-webfont.ttf') format('truetype'), 85 | url('texgyreadventor-bold-webfont.svg#tex_gyre_adventorbold') format('svg'); 86 | font-weight: bold; 87 | font-style: normal; 88 | } 89 | 90 | @font-face { 91 | font-family: 'TeX Gyre Adventor'; 92 | src: url('texgyreadventor-bolditalic-webfont.eot'); 93 | src: url('texgyreadventor-bolditalic-webfont.eot?#iefix') format('embedded-opentype'), 94 | url('texgyreadventor-bolditalic-webfont.woff') format('woff'), 95 | url('texgyreadventor-bolditalic-webfont.ttf') format('truetype'), 96 | url('texgyreadventor-bolditalic-webfont.svg#tex_gyre_adventorbold_italic') format('svg'); 97 | font-weight: bold; 98 | font-style: italic; 99 | } 100 | 101 | /* Global styles */ 102 | @layer variables.base { 103 | body { 104 | --font-body: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 105 | --font-primary: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 106 | --font-h1: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 107 | --font-h2: "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 108 | --font-h3: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 109 | } 110 | 111 | body .game .app { 112 | --font-primary: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 113 | } 114 | } 115 | 116 | .rollable:hover, 117 | .rollable:focus { 118 | color: #000; 119 | text-shadow: 0 0 10px red; 120 | cursor: pointer; 121 | } 122 | 123 | .chat-message, 124 | .chat-message h4 { 125 | font-family: "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 126 | } 127 | 128 | .chat-message .chat-item-name { 129 | color: black; 130 | display: block; 131 | font-size: 14px; 132 | font-weight: bold; 133 | text-shadow: 1px 1px 2px #aaa; 134 | } 135 | 136 | .chat-message .chat-item-description { 137 | /* font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); */ 138 | font-size: 16px; 139 | } 140 | 141 | .basicfantasyrpg .success-text, 142 | .chat-message .chat-item-description .chat-roll-success { 143 | color: green; 144 | font-variant: small-caps; 145 | font-weight: bold; 146 | text-shadow: 0 0 2px; 147 | } 148 | 149 | .basicfantasyrpg .failure-text, 150 | .chat-message .chat-item-description .chat-roll-failure { 151 | color: darkred; 152 | font-variant: small-caps; 153 | font-weight: bold; 154 | text-shadow: 0 0 2px; 155 | } 156 | 157 | .chat-message .chat-item-description .chat-roll-target-number { 158 | font-weight: bold; 159 | text-shadow: 0 0 2px; 160 | } 161 | 162 | .grid, 163 | .grid-2col { 164 | display: grid; 165 | grid-column: span 2 / span 2; 166 | grid-template-columns: repeat(2, minmax(0, 1fr)); 167 | gap: 10px; 168 | margin: 10px 0; 169 | padding: 0; 170 | } 171 | 172 | .grid-3col { 173 | grid-column: span 3 / span 3; 174 | grid-template-columns: repeat(3, minmax(0, 1fr)); 175 | } 176 | 177 | .grid-4col { 178 | grid-column: span 4 / span 4; 179 | grid-template-columns: repeat(4, minmax(0, 1fr)); 180 | } 181 | 182 | .grid-5col { 183 | grid-column: span 5 / span 5; 184 | grid-template-columns: repeat(5, minmax(0, 1fr)); 185 | } 186 | 187 | .grid-6col { 188 | grid-column: span 6 / span 6; 189 | grid-template-columns: repeat(6, minmax(0, 1fr)); 190 | } 191 | 192 | .grid-7col { 193 | grid-column: span 7 / span 7; 194 | grid-template-columns: repeat(7, minmax(0, 1fr)); 195 | } 196 | 197 | .grid-8col { 198 | grid-column: span 8 / span 8; 199 | grid-template-columns: repeat(8, minmax(0, 1fr)); 200 | } 201 | 202 | .grid-9col { 203 | grid-column: span 9 / span 9; 204 | grid-template-columns: repeat(9, minmax(0, 1fr)); 205 | } 206 | 207 | .grid-10col { 208 | grid-column: span 10 / span 10; 209 | grid-template-columns: repeat(10, minmax(0, 1fr)); 210 | } 211 | 212 | .grid-11col { 213 | grid-column: span 11 / span 11; 214 | grid-template-columns: repeat(11, minmax(0, 1fr)); 215 | } 216 | 217 | .grid-12col { 218 | grid-column: span 12 / span 12; 219 | grid-template-columns: repeat(12, minmax(0, 1fr)); 220 | } 221 | 222 | .grid-start-2 { 223 | grid-column-start: 2; 224 | } 225 | 226 | .grid-start-3 { 227 | grid-column-start: 3; 228 | } 229 | 230 | .grid-start-4 { 231 | grid-column-start: 4; 232 | } 233 | 234 | .grid-start-5 { 235 | grid-column-start: 5; 236 | } 237 | 238 | .grid-start-6 { 239 | grid-column-start: 6; 240 | } 241 | 242 | .grid-start-7 { 243 | grid-column-start: 7; 244 | } 245 | 246 | .grid-start-8 { 247 | grid-column-start: 8; 248 | } 249 | 250 | .grid-start-9 { 251 | grid-column-start: 9; 252 | } 253 | 254 | .grid-start-10 { 255 | grid-column-start: 10; 256 | } 257 | 258 | .grid-start-11 { 259 | grid-column-start: 11; 260 | } 261 | 262 | .grid-start-12 { 263 | grid-column-start: 12; 264 | } 265 | 266 | .grid-span-2 { 267 | grid-column-end: span 2; 268 | } 269 | 270 | .grid-span-3 { 271 | grid-column-end: span 3; 272 | } 273 | 274 | .grid-span-4 { 275 | grid-column-end: span 4; 276 | } 277 | 278 | .grid-span-5 { 279 | grid-column-end: span 5; 280 | } 281 | 282 | .grid-span-6 { 283 | grid-column-end: span 6; 284 | } 285 | 286 | .grid-span-7 { 287 | grid-column-end: span 7; 288 | } 289 | 290 | .grid-span-8 { 291 | grid-column-end: span 8; 292 | } 293 | 294 | .grid-span-9 { 295 | grid-column-end: span 9; 296 | } 297 | 298 | .grid-span-10 { 299 | grid-column-end: span 10; 300 | } 301 | 302 | .grid-span-11 { 303 | grid-column-end: span 11; 304 | } 305 | 306 | .grid-span-12 { 307 | grid-column-end: span 12; 308 | } 309 | 310 | .flex-group-center, 311 | .flex-group-left, 312 | .flex-group-right { 313 | -webkit-box-pack: center; 314 | -ms-flex-pack: center; 315 | justify-content: center; 316 | -webkit-box-align: center; 317 | -ms-flex-align: center; 318 | align-items: center; 319 | text-align: center; 320 | } 321 | 322 | .flex-group-left { 323 | -webkit-box-pack: start; 324 | -ms-flex-pack: start; 325 | justify-content: flex-start; 326 | text-align: left; 327 | } 328 | 329 | .flex-group-right { 330 | -webkit-box-pack: end; 331 | -ms-flex-pack: end; 332 | justify-content: flex-end; 333 | text-align: right; 334 | } 335 | 336 | .flexshrink { 337 | -webkit-box-flex: 0; 338 | -ms-flex: 0; 339 | flex: 0; 340 | } 341 | 342 | .flex-between { 343 | -webkit-box-pack: justify; 344 | -ms-flex-pack: justify; 345 | justify-content: space-between; 346 | } 347 | 348 | .flexlarge { 349 | -webkit-box-flex: 2; 350 | -ms-flex: 2; 351 | flex: 2; 352 | } 353 | 354 | .flex-align-center { 355 | align-self: center; 356 | } 357 | 358 | .align-left { 359 | -webkit-box-pack: start; 360 | -ms-flex-pack: start; 361 | justify-content: flex-start; 362 | text-align: left; 363 | } 364 | 365 | .align-right { 366 | -webkit-box-pack: end; 367 | -ms-flex-pack: end; 368 | justify-content: flex-end; 369 | text-align: right; 370 | } 371 | 372 | .align-center { 373 | -webkit-box-pack: center; 374 | -ms-flex-pack: center; 375 | justify-content: center; 376 | text-align: center; 377 | } 378 | 379 | .vertical-line { 380 | border: none; 381 | border-left: 1px solid var(--color-border-light-primary); 382 | border-right: 1px solid var(--color-border-light-highlight); 383 | width: 0; 384 | } 385 | 386 | /* Styles limited to basicfantasyrpg sheets */ 387 | 388 | .basicfantasyrpg.sheet.actor { 389 | font-family: "Century Gothic", "TeX Gyre Adventor", "Soutane", var(--font-sans); 390 | } 391 | 392 | .basicfantasyrpg .sheet-header { 393 | -webkit-box-flex: 0; 394 | -ms-flex: 0 auto; 395 | flex: 0 auto; 396 | overflow: hidden; 397 | display: -webkit-box; 398 | display: -ms-flexbox; 399 | display: flex; 400 | -webkit-box-orient: horizontal; 401 | -webkit-box-direction: normal; 402 | -ms-flex-direction: row; 403 | flex-direction: row; 404 | -ms-flex-wrap: wrap; 405 | flex-wrap: wrap; 406 | -webkit-box-pack: start; 407 | -ms-flex-pack: start; 408 | justify-content: flex-start; 409 | margin-bottom: 10px; 410 | } 411 | 412 | .basicfantasyrpg .sheet-header .profile-img { 413 | -webkit-box-flex: 0; 414 | -ms-flex: 0 0 100px; 415 | flex: 0 0 100px; 416 | height: 100px; 417 | margin-right: 10px; 418 | } 419 | 420 | .basicfantasyrpg .sheet-header .header-fields { 421 | -webkit-box-flex: 1; 422 | -ms-flex: 1; 423 | flex: 1; 424 | } 425 | 426 | .basicfantasyrpg .sheet-header h1.charname { 427 | height: 50px; 428 | padding: 0px; 429 | margin: 5px 0; 430 | border-bottom: 0; 431 | } 432 | 433 | .basicfantasyrpg .sheet-header h1.charname input { 434 | width: 100%; 435 | height: 100%; 436 | margin: 0; 437 | } 438 | 439 | .basicfantasyrpg .sheet-tabs { 440 | -webkit-box-flex: 0; 441 | -ms-flex: 0; 442 | flex: 0; 443 | } 444 | 445 | .basicfantasyrpg .sheet-body, 446 | .basicfantasyrpg .sheet-body .description, 447 | .basicfantasyrpg .sheet-body .editor, 448 | .basicfantasyrpg .sheet-body .tab, 449 | .basicfantasyrpg .sheet-body .tab .editor { 450 | height: calc(100% - 2px); 451 | } 452 | 453 | .basicfantasyrpg .sheet-body .tab.biography { 454 | max-height: calc(100% - 53px); 455 | } 456 | 457 | .basicfantasyrpg .editor-content { 458 | font-family: "Soutane", "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 459 | font-size: 18px; 460 | overflow-y: hidden; 461 | } 462 | 463 | .basicfantasyrpg .tox .tox-editor-container { 464 | background: #fff; 465 | } 466 | 467 | .basicfantasyrpg .tox .tox-edit-area { 468 | padding: 0 8px; 469 | } 470 | 471 | .basicfantasyrpg .resource-content { 472 | white-space: nowrap; 473 | } 474 | 475 | .basicfantasyrpg .resource-label { 476 | font-weight: bold; 477 | } 478 | 479 | .basicfantasyrpg .items-header { 480 | height: 28px; 481 | margin: 2px 0; 482 | padding: 0; 483 | -webkit-box-align: center; 484 | -ms-flex-align: center; 485 | align-items: center; 486 | background: rgba(0, 0, 0, 0.05); 487 | border: 2px groove #eeede0; 488 | font-weight: bold; 489 | } 490 | 491 | .basicfantasyrpg .items-header>* { 492 | font-size: 14px; 493 | text-align: center; 494 | } 495 | 496 | .basicfantasyrpg .items-header .item-name { 497 | font-weight: bold; 498 | padding-left: 5px; 499 | text-align: left; 500 | display: -webkit-box; 501 | display: -ms-flexbox; 502 | display: flex; 503 | } 504 | 505 | .basicfantasyrpg .items-list { 506 | list-style: none; 507 | margin: 0; 508 | padding: 0; 509 | overflow-y: auto; 510 | scrollbar-width: thin; 511 | color: #444; 512 | } 513 | 514 | .basicfantasyrpg .items-list .item-list { 515 | list-style: none; 516 | margin: 0; 517 | padding: 0; 518 | } 519 | 520 | .basicfantasyrpg .items-list .item-name { 521 | -webkit-box-flex: 2; 522 | -ms-flex: 2; 523 | flex: 2; 524 | margin: 0; 525 | overflow: hidden; 526 | font-size: 13px; 527 | text-align: left; 528 | -webkit-box-align: center; 529 | -ms-flex-align: center; 530 | align-items: center; 531 | display: -webkit-box; 532 | display: -ms-flexbox; 533 | display: flex; 534 | } 535 | 536 | .basicfantasyrpg .items-list .item-detail { 537 | -webkit-box-flex: 1; 538 | -ms-flex: 1; 539 | flex: 1; 540 | margin: 0; 541 | overflow: hidden; 542 | font-size: 13px; 543 | text-align: left; 544 | -webkit-box-align: center; 545 | -ms-flex-align: center; 546 | align-items: center; 547 | display: -webkit-box; 548 | display: -ms-flexbox; 549 | display: flex; 550 | } 551 | 552 | .basicfantasyrpg .items-list .item-name h3, 553 | .basicfantasyrpg .items-list .item-name h4 { 554 | margin: 0; 555 | white-space: nowrap; 556 | overflow-x: hidden; 557 | font-family: "Century Gothic", "TeX Gyre Adventor", var(--font-sans); 558 | } 559 | 560 | .basicfantasyrpg .items-list .item-controls { 561 | display: -webkit-box; 562 | display: -ms-flexbox; 563 | display: flex; 564 | -webkit-box-flex: 0; 565 | -ms-flex: 0 0 100px; 566 | flex: 0 0 100px; 567 | -webkit-box-pack: end; 568 | -ms-flex-pack: end; 569 | justify-content: flex-end; 570 | } 571 | 572 | .basicfantasyrpg .items-list .item-controls a { 573 | font-size: 12px; 574 | text-align: center; 575 | margin: 0 6px; 576 | } 577 | 578 | .basicfantasyrpg .items-list .item { 579 | -webkit-box-align: center; 580 | -ms-flex-align: center; 581 | align-items: center; 582 | padding: 0 2px; 583 | border-bottom: 1px solid #c9c7b8; 584 | } 585 | 586 | .basicfantasyrpg .items-list .item:last-child { 587 | border-bottom: none; 588 | } 589 | 590 | .basicfantasyrpg .items-list .item .item-name { 591 | color: #191813; 592 | } 593 | 594 | .basicfantasyrpg .items-list .item .item-name .item-image { 595 | -webkit-box-flex: 0; 596 | -ms-flex: 0 0 30px; 597 | flex: 0 0 30px; 598 | height: 30px; 599 | background-size: 30px; 600 | border: none; 601 | margin-right: 5px; 602 | } 603 | 604 | .basicfantasyrpg .items-list .item-prop { 605 | text-align: center; 606 | border-left: 1px solid #c9c7b8; 607 | border-right: 1px solid #c9c7b8; 608 | font-size: 12px; 609 | } 610 | 611 | .basicfantasyrpg .items-list .item .item-name .item-image img, 612 | .basicfantasyrpg .items-list .item-prop img, 613 | .basicfantasyrpg .resources img { 614 | border: none; 615 | vertical-align: middle; 616 | } 617 | 618 | .basicfantasyrpg .items-list .items-header { 619 | height: 28px; 620 | margin: 2px 0; 621 | padding: 0; 622 | -webkit-box-align: center; 623 | -ms-flex-align: center; 624 | align-items: center; 625 | background: rgba(0, 0, 0, 0.05); 626 | border: 2px groove #eeede0; 627 | font-weight: bold; 628 | } 629 | 630 | .basicfantasyrpg .items-list .items-header>* { 631 | font-size: 12px; 632 | text-align: center; 633 | } 634 | 635 | .basicfantasyrpg .items-list .items-header .item-name { 636 | padding-left: 5px; 637 | text-align: left; 638 | } 639 | 640 | .basicfantasyrpg .item-detail { 641 | -webkit-box-flex: 0; 642 | -ms-flex: 0 0 200px; 643 | flex: 0 0 200px; 644 | padding: 0 8px; 645 | } 646 | 647 | .basicfantasyrpg .effects .item .effect-source, 648 | .basicfantasyrpg .effects .item .effect-duration, 649 | .basicfantasyrpg .effects .item .effect-controls { 650 | text-align: center; 651 | border-left: 1px solid #c9c7b8; 652 | border-right: 1px solid #c9c7b8; 653 | font-size: 12px; 654 | } 655 | 656 | .basicfantasyrpg .effects .item .effect-controls { 657 | border: none; 658 | } 659 | --------------------------------------------------------------------------------