Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12faf34631 | |||
| afa6e7a323 | |||
| 89f5674fb9 | |||
| cd06d2e5ec | |||
| cdddcb6aee |
@@ -12,13 +12,15 @@ jobs:
|
||||
|
||||
#- uses: actions/checkout@v3
|
||||
- uses: RouxAntoine/checkout@v3.5.4
|
||||
with:
|
||||
ref: "v13"
|
||||
|
||||
# get part of the tag after the `v`
|
||||
- name: Extract tag version number
|
||||
id: get_version
|
||||
uses: battila7/get-version-action@v2
|
||||
|
||||
# Substitute the Manifest and Download URLs in the system.json
|
||||
# Substitute the Manifest and Download URLs in the module.json
|
||||
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||
id: sub_manifest_link_version
|
||||
uses: microsoft/variable-substitution@v1
|
||||
@@ -26,9 +28,9 @@ jobs:
|
||||
files: "system.json"
|
||||
env:
|
||||
version: ${{steps.get_version.outputs.version-without-v}}
|
||||
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||
manifest: https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json
|
||||
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/bol.zip
|
||||
url: https://www.uberwald.me/gitea/public/bol
|
||||
manifest: https://www.uberwald.me/gitea/public/bol/releases/latest/system.json
|
||||
download: https://www.uberwald.me/gitea/public/bol/releases/download/${{github.event.release.tag_name}}/bol.zip
|
||||
|
||||
# Create a zip file with all files required by the module to add to the release
|
||||
- run: |
|
||||
@@ -49,15 +51,4 @@ jobs:
|
||||
files: |-
|
||||
./bol.zip
|
||||
system.json
|
||||
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
|
||||
|
||||
- name: Publish to Foundry server
|
||||
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||
with:
|
||||
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||
id: "bol"
|
||||
version: ${{github.event.release.tag_name}}
|
||||
manifest: "https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json"
|
||||
notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/bol.zip"
|
||||
compatibility-minimum: "13"
|
||||
compatibility-verified: "14"
|
||||
api_key: "${{secrets.RELEASE_TOKEN_BOL}}"
|
||||
|
||||
1
.gitignore
vendored
@@ -8,4 +8,3 @@ todo.md
|
||||
/jsconfig.json
|
||||
/package.json
|
||||
/package-lock.json
|
||||
/.github
|
||||
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 517 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 102 KiB |
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"label": "Game Aid",
|
||||
"mapping": {
|
||||
"name": "name",
|
||||
"description": "content",
|
||||
"categories": {
|
||||
"path": "categories",
|
||||
"converter": "nameCollection"
|
||||
},
|
||||
"pages": {
|
||||
"path": "pages",
|
||||
"converter": "pages"
|
||||
}
|
||||
},
|
||||
"entries": {
|
||||
"Aide du Jeu": "Game Aid",
|
||||
"8ihDiCxC47fcdKVA": {
|
||||
"name": "Game Aid",
|
||||
"pages": {
|
||||
"8agBoLYo99u530d1": {
|
||||
"name": "Credits",
|
||||
"text": "<p>This system was started by Zigmund, then taken over, completed and maintained by LeRatierBretonnien.</p><p>You can contact me on the French Foundry Discord: LeRatierBretonnien#2065</p><p>The git repository is available here: <a href=\"https://www.uberwald.me/gitea/public/bol\">https://www.uberwald.me/gitea/public/bol</a></p><p>Barbarians of Lemuria is a game by Simon Washbourne (Beyond Belief Games), translated into French by Ludospherik.</p><p>The full range is available on this page: <a href=\"http://www.ludospherik.fr/content/14-barbarians-of-lemuria\">http://www.ludospherik.fr/content/14-barbarians-of-lemuria</a></p><img src=\"systems/bol/assets/pages/b9BjVFaWB6uyyKsD-pages-8agBoLYo99u530d1-image-Rg2nVaMlglfJ6Qcm.png\" />"
|
||||
},
|
||||
"NAcpMm6NlyhwvWRA": {
|
||||
"name": "Features",
|
||||
"text": "<p>The BoL system under Foundry allows you to:</p><ul><li><p>create characters and NPCs (minions, toughs, rivals),</p></li><li><p>create creatures,</p></li><li><p>make dice rolls for attributes, aptitudes, and careers,</p></li><li><p>make dice rolls for combat attacks,</p></li><li><p>manage initiative,</p></li><li><p>make dice rolls to cast spells or perform alchemical recipes,</p></li><li><p>manage damage and lifeblood loss,</p></li><li><p>display and manage automations in chat windows, with assistance for the use of Hero Points and critical successes.</p></li></ul>"
|
||||
},
|
||||
"hdQixhZGfAytdbSg": {
|
||||
"name": "Compendiums",
|
||||
"text": "<p>The system comes with a series of compendiums providing quick access to careers, boons, and equipment.</p><p>The official cards are available, with the kind permission of Ludospherik.</p><p>To use content from a compendium, simply drag and drop the item into the world. For example: open the armours compendium, select <em>Light Armour</em>, and drag it onto a character sheet.</p><img src=\"systems/bol/assets/pages/help-en-compendium.png\" />"
|
||||
},
|
||||
"UfvTY80U49k6YFwe": {
|
||||
"name": "Dice Rolls (Attributes/Aptitudes)",
|
||||
"text": "<p>When a player clicks an attribute or aptitude, the following window appears:</p><img src=\"systems/bol/assets/pages/help-en-attribute-dialog.png\" /><ol><li><p>Attribute the player clicked on. It can be changed with this drop-down menu.</p></li><li><p>Career to apply if needed. Left-click to select, CTRL+left-click to deselect. When selected, the career rank is applied.</p></li><li><p>Available boons (i.e. those that grant a bonus die). Selection mode is identical to careers. When selected, the bonus die is applied to the roll.</p></li><li><p>Available flaws (i.e. those that impose a penalty die). Selection mode is identical to careers. When selected, the penalty die is applied to the roll.</p></li><li><p>Effects. Effects are permanent modifiers that affect certain rolls. See the corresponding chapter.</p></li><li><p>Manual addition of penalty or bonus dice.</p></li><li><p>Manual modifiers, according to the GM's ruling.</p></li><li><p>Reminder of the number of dice, based on the choices made in the dialog.</p></li><li><p>Complete list of modifiers applied to the roll.</p></li></ol>"
|
||||
},
|
||||
"4CLyyt3dtpG6YNMi": {
|
||||
"name": "Dice Rolls (Weapon)",
|
||||
"text": "<p>To make an attack with a weapon, go to the <strong>Actions</strong> tab and click the weapon's name. Targeting an opponent is recommended in order to take advantage of the system automations.</p><img src=\"systems/bol/assets/pages/help-en-weapon-dialog.png\" /><ol><li><p>Default weapon attribute. It can be changed with this drop-down menu.</p></li><li><p>Default weapon aptitude. It can be changed with this drop-down menu.</p></li><li><p>Possible weapon bonus on the attack roll.</p></li><li><p>Target defence, when an opponent is targeted.</p></li><li><p>Career to apply if needed. Left-click to select, CTRL+left-click to deselect. When selected, the career rank is applied.</p></li><li><p>Available boons (i.e. those that grant a bonus die). Selection mode is identical to careers. When selected, the bonus die is applied to the roll.</p></li><li><p>Available flaws (i.e. those that impose a penalty die). Selection mode is identical to careers. When selected, the penalty die is applied to the roll.</p></li><li><p>Effects. Effects are permanent modifiers that affect certain rolls. See the corresponding chapter.</p></li><li><p>Manual addition of penalty or bonus dice.</p></li><li><p>Information area if the weapon has the <em>Bonus Die</em> option enabled.</p></li><li><p>Agility penalty from armour and shield.</p></li><li><p>Manual modifiers, according to the GM's ruling.</p></li><li><p>Reminder of the number of dice, based on the choices made in the dialog.</p></li><li><p>Complete list of modifiers applied to the roll.</p></li></ol>"
|
||||
},
|
||||
"r003R5yIaiKxThOc": {
|
||||
"name": "Character Sheet",
|
||||
"text": "<h2>Attributes</h2><img src=\"systems/bol/assets/pages/help-en-sheet-attributes.png\" /><ol><li><p>Name + Experience area.</p></li><li><p>Tab bar for moving between sections.</p></li><li><p>Attributes area. Clicking a name opens the roll dialog, clicking a number allows editing.</p></li><li><p>Aptitudes area, same idea as attributes.</p></li><li><p>Counters area. The red value is the current one, the black value underneath is the maximum.</p></li></ol><h2>Actions</h2><img src=\"systems/bol/assets/pages/help-en-sheet-actions.png\" /><ol><li><p>Weapons. Clicking the name opens the attack dialog for that weapon.</p></li><li><p>Damage. Clicking the damage formula rolls it.</p></li><li><p>List of protections.</p></li><li><p>Clicking the protection formula rolls it.</p></li><li><p>List of shields.</p></li><li><p>Combat options the character knows. Once activated, they appear in the weapon attack dialog.</p></li></ol><h2>Traits</h2><img src=\"systems/bol/assets/pages/help-en-sheet-traits.png\" /><ol><li><p>List of careers. Left-click a career for details.</p></li><li><p>Career rank helper.</p></li><li><p>List of origins. Left-click for details.</p></li><li><p>List of boons. Left-click for details.</p></li><li><p>List of flaws. Left-click for details.</p></li><li><p>List of spoken languages. Left-click for details.</p></li><li><p>List of known combat options. Left-click for details.</p></li><li><p>List of beliefs. Left-click for details.</p></li><li><p>List of active effects. Left-click for details.</p></li></ol><h2>Equipment</h2><img src=\"systems/bol/assets/pages/help-en-sheet-equipment.png\" /><ol><li><p>Quick equipment creation controls.</p></li><li><p>Purse state (optional rule, system setting).</p></li><li><p>Weapons, with quantity management.</p></li><li><p>Shield equip / unequip control.</p></li><li><p>List of protections.</p></li><li><p>Armour equip / unequip control.</p></li></ol><h2>Spells & Alchemy</h2><p>This tab is only available if the character has the Alchemist or Sorcerer career.</p><img src=\"systems/bol/assets/pages/help-en-sheet-spells.png\" /><ol><li><p>List of spells. Left-click the name to open the dedicated roll dialog.</p></li><li><p>Reminder of circle and difficulty.</p></li><li><p>The square button opens the spell details.</p></li><li><p>List of known alchemical preparations.</p></li><li><p>Crafting progress management.</p></li><li><p>The square button opens the preparation details.</p></li></ol><h2>Description</h2><img src=\"systems/bol/assets/pages/help-en-sheet-description.png\" /><p>The description area contains free-form fields for customising the character.</p>"
|
||||
},
|
||||
"eRbEqbCW4AhU0cpm": {
|
||||
"name": "Effects",
|
||||
"text": "<p>Effects allow permanent modifiers to be applied as long as they are present on the character sheet.</p><p><span style=\"text-decoration:underline\"><span style=\"text-decoration:underline\">Example</span></span>: a character is poisoned, and the poison causes mental fatigue. In game terms, they suffer a -2 penalty to all Mind rolls. To represent this, create an <em>Effect</em> like this:</p><img src=\"systems/bol/assets/pages/help-en-effect-sheet.png\" /><ol><li><p>Create a <em>Trait</em>, and in the <em>Details</em> tab select the subtype <em>Effect</em>.</p></li><li><p>Select <em>Mind</em>.</p></li><li><p>Set a -2 modifier.</p></li></ol><p>Once placed on a character, the penalty is automatically applied whenever a Mind roll is requested. Remove the effect from the character sheet (<em>Traits</em> tab) to remove both the effect and its penalty.<br />A compendium of basic effects is available in the system.</p>"
|
||||
},
|
||||
"QmNF6p0lJf3pJoAy": {
|
||||
"name": "Commands",
|
||||
"text": "<p>The system provides commands (currently just one) for a few automated tasks.</p><p><strong><span style=\"text-decoration:underline\">This command is only available if the BoL-rulebook module is installed.</span></strong></p><h3>/adventure command</h3><img src=\"systems/bol/assets/pages/help-en-command.png\" /><ol><li><p>Type <code>/adventure</code> in the chat input area (or trigger it from a macro).</p></li><li><p>The system generates an adventure from the core rulebook tables and posts the result in chat.</p></li></ol>"
|
||||
},
|
||||
"MOWru5Dbvs4iozXm": {
|
||||
"name": "Macros",
|
||||
"text": "<p>The system exposes macros through <code>game.bol.macros</code>. They provide quick access to the most common rolls from a Foundry macro.</p><p><strong>Automatic actor selection:</strong></p><ul><li><p>if exactly one token is selected, the macro uses it;</p></li><li><p>if several tokens are selected, the macro shows a warning;</p></li><li><p>if no token is selected, a GM must select one; a player automatically falls back to their assigned character.</p></li></ul><p><strong>Available rolls</strong></p><p>Attributes (internal keys):</p><pre><code>game.bol.macros.rollAttribute(\"vigor\")\ngame.bol.macros.rollAttribute(\"agility\")\ngame.bol.macros.rollAttribute(\"mind\")\ngame.bol.macros.rollAttribute(\"appeal\")</code></pre><p>Aptitudes:</p><pre><code>game.bol.macros.rollAptitude(\"init\")\ngame.bol.macros.rollAptitude(\"melee\")\ngame.bol.macros.rollAptitude(\"ranged\")\ngame.bol.macros.rollAptitude(\"def\")</code></pre><p>Weapons, spells, and alchemy:</p><pre><code>game.bol.macros.rollWeapon(\"Spear\")\ngame.bol.macros.rollWeapon(0)\n\ngame.bol.macros.rollSpell(\"Javelin\")\ngame.bol.macros.rollSpell(0)\n\ngame.bol.macros.rollAlchemy(\"Fire\")\ngame.bol.macros.rollAlchemy(0)</code></pre><p>For weapons, spells, and alchemical preparations, name lookup is <strong>partial</strong> and <strong>case-insensitive</strong>. You may also use a numeric index.</p><p>Horoscope:</p><pre><code>game.bol.macros.rollHoroscope(\"minor\")\ngame.bol.macros.rollHoroscope(\"major\")</code></pre><p><strong>Generic macro</strong></p><pre><code>game.bol.macros.roll(\"attribute\", \"vigor\")\ngame.bol.macros.roll(\"aptitude\", \"melee\")\ngame.bol.macros.roll(\"weapon\", \"Spear\")\ngame.bol.macros.roll(\"spell\", \"Javelin\")\ngame.bol.macros.roll(\"alchemy\", 0)\ngame.bol.macros.roll(\"horoscope\", \"minor\")</code></pre><p><strong>Compatibility</strong></p><p>The legacy entry point <code>game.bol.macros.rollMacro(type, key)</code> is still available for existing macros.</p>"
|
||||
},
|
||||
"rERizrPxSAsvsZY2": {
|
||||
"name": "Initiative",
|
||||
"text": "<p>In BoL, initiative is rolled once at the start of combat.</p><p>You can request it automatically through the Combat Tracker, or roll Initiative manually and tick the corresponding box in the dialog:</p><img src=\"systems/bol/assets/pages/help-en-initiative-dialog.png\" /><p>According to the roll result, an initiative rank from 10 to 3 is assigned as follows:</p><p><strong>10</strong> - PC: Legendary Success<br /><strong>9</strong> - PC: Heroic Success<br /><strong>8</strong> - PC: Normal Success<br /><strong>7</strong> - NPC: Opponents or creatures larger than medium size<br /><strong>6</strong> - NPC: Toughs or creatures of medium size or smaller<br /><strong>5</strong> - PC: Normal Failure<br /><strong>4</strong> - NPC: Minions<br /><strong>3</strong> - PC: Critical Failure</p>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,30 +5,6 @@
|
||||
},
|
||||
|
||||
"entries": {
|
||||
"Arc de guerre de Tyrus": {
|
||||
"name": "Tyrus Warbow",
|
||||
"description": "<h1>Tyrus Warbow</h1><p> The archers of Tyrus train from a very young age in the use of this powerful bow. Tyrus archers have fun with other warriors by lending their bows to them and then watching them struggle to aim at the proverbial “barn door”.</p>"
|
||||
},
|
||||
"Épée Valgardienne (1 Main)": {
|
||||
"name": "Valgardian Blade (1H)",
|
||||
"description": "<h1>Valgardian Blade (1H)</h1><p>Around 3’-4’ in length, the Valgardian blade is used either in one hand or in both. The Valgardian bladesmiths guard the secret of their work closely, although many have tried to copy it. It is rumoured that the strength of the blades due mainly to the quality of the iron mined locally.</p>"
|
||||
},
|
||||
"Épée Valgardienne (2 mains)": {
|
||||
"name": "Valgardian Blade (2H)",
|
||||
"description": "<h1>Valgardian Blade (2H)</h1><p>Around 3’-4’ in length, the Valgardian blade is used either in one hand or in both. The Valgardian bladesmiths guard the secret of their work closely, although many have tried to copy it. It is rumoured that the strength of the blades due mainly to the quality of the iron mined locally.</p>"
|
||||
},
|
||||
"Fronde de l’Axos": {
|
||||
"name": "Axish Sling",
|
||||
"description": "<h1>Axish Sling</h1><p>Sling: The sling is inexpensive and easy to build. It is a simple leather thong whirled around the head to cast small stones or cast lead bullets with some force, at 30’ range increments. Two-handed versions are fitted onto a staff and are called staff-slings. This imparts a greater range, making the increments 60’. The Axish sling is actually little different to any other common sling; it’s just that the people of the Axos mountains are particularly proficient with them. They have however played up the idea that there is some special plant fibre that the thongs are made from that gives them their extra range.</p>"
|
||||
},
|
||||
"Hache d’abordage de Parsool (1 main)": {
|
||||
"name": "Parsool Sea Axe (1H)",
|
||||
"description": "<h1>Parsool Sea Axe (1H)</h1><p>A boarding axe used by Parsool seamen which can be used one or two-handed.</p>"
|
||||
},
|
||||
"Hache d’abordage de Parsool (2 mains)": {
|
||||
"name": "Parsool Sea Axe (2H)",
|
||||
"description": "<h1>Parsool Sea Axe (2H)</h1><p>A boarding axe used by Parsool seamen which can be used one or two-handed.</p>"
|
||||
},
|
||||
"Arbalète": {
|
||||
"name": "Crossbow",
|
||||
"description": "<h1>Crossbow</h1><p>A crossbow is a simple device for firing a short bolt or quarrel with some force and little training. They take a round to load (ready to fire on the second round).</p>"
|
||||
|
||||
2115
css/bol.css
@@ -478,7 +478,6 @@
|
||||
"BOL.chat.damageresult": "Damages of {name} : {total}",
|
||||
"BOL.chat.damagetarget": "Target : {target}",
|
||||
"BOL.chat.applydamagetotarget": "Apply damages to the target",
|
||||
"BOL.chat.selecttarget": "Choose a target:",
|
||||
"BOL.chat.fightoption": "Combat options",
|
||||
"BOL.chat.reroll": "Reroll (1 HP)",
|
||||
"BOL.chat.heroicreminder": "In addition of the actions below, you can : <ul><li>Carnage : Do a second free attack on the same opponent</li><li>Precise : 1 Malus Die on your opponent on a chosen location</li><li>Disarm</li><li>Rabble Massacre</li><li>Prone : Push your opponent on the ground (max +1 size)</li></ul>If you spent 1 Hero Point in addition, all these effects can be doubled.",
|
||||
@@ -610,10 +609,5 @@
|
||||
"BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo",
|
||||
"BOL.settings.defaultLogoPathActorSheetTooltip": "Path of the Actor sheet logo (346 x 200, default : /systems/bol/ui/logo.webp)",
|
||||
"BOL.settings.defaultLogoTopLeftPath" : "Path for main top left logo",
|
||||
"BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)",
|
||||
|
||||
"BOL.ui.charSummaryTitle": "Character Summary",
|
||||
"BOL.ui.colGroupAttributes": "Attributes",
|
||||
"BOL.ui.colGroupAptitudes": "Aptitudes",
|
||||
"BOL.ui.colGroupResources": "Resources"
|
||||
"BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)"
|
||||
}
|
||||
@@ -506,7 +506,6 @@
|
||||
"BOL.chat.damageresult": "Dommages de {name} : {total}",
|
||||
"BOL.chat.damagetarget": "Cible : {target}",
|
||||
"BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible",
|
||||
"BOL.chat.selecttarget": "Choisir une cible :",
|
||||
"BOL.chat.fightoption": "Option de combat",
|
||||
"BOL.chat.reroll": "Relancer (1 P. Heroisme)",
|
||||
"BOL.chat.heroicreminder": "En plus des actions indiquées sur les boutons ci-dessous, vous pouvez : <ul><li>Carnage : Attaquer une seconde fois le même adversaire</li><li>Coup précis : Un dé de malus à votre adversaire sur une localisation choisie</li><li>Désarmement</li><li>Massacrer la piétaille</li><li>Renversement : Renversez votre adversaire (1 taille de plus max)</li></ul>Si vous dépensez un Point d'Héroisme en plus, tout ces effets peuvent être doublés",
|
||||
@@ -651,10 +650,5 @@
|
||||
"BOL.settings.defaultLogoTopLeftPathTooltip": "Vous pouvez changer le logo BoL en haut à gauche de chaque écran (idéalement 718 x 416, défaut : /systems/bol/ui/logo2.webp)",
|
||||
|
||||
"EFFECT.StatusProne": "A terre",
|
||||
"EFFECT.StatusDead": "Mort",
|
||||
|
||||
"BOL.ui.charSummaryTitle": "Résumé des Personnages",
|
||||
"BOL.ui.colGroupAttributes": "Attributs",
|
||||
"BOL.ui.colGroupAptitudes": "Aptitudes",
|
||||
"BOL.ui.colGroupResources": "Ressources"
|
||||
"EFFECT.StatusDead": "Mort"
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import { BoLUtility } from "../system/bol-utility.js";
|
||||
*/
|
||||
export class BoLActor extends Actor {
|
||||
|
||||
static _healthLock = new Set()
|
||||
|
||||
static async create(data, options) {
|
||||
|
||||
// Case of compendium global import
|
||||
@@ -359,7 +357,7 @@ export class BoLActor extends Actor {
|
||||
ChatMessage.create({
|
||||
alias: this.name,
|
||||
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state })
|
||||
content: await renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state })
|
||||
})
|
||||
|
||||
}
|
||||
@@ -854,9 +852,6 @@ export class BoLActor extends Actor {
|
||||
|
||||
/*-------------------------------------------- */
|
||||
async manageHealthState() {
|
||||
if (BoLActor._healthLock.has(this.id)) return
|
||||
BoLActor._healthLock.add(this.id)
|
||||
try {
|
||||
let hpID = "lastHP" + this.id
|
||||
let lastHP = await this.getFlag("world", hpID)
|
||||
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
|
||||
@@ -877,7 +872,7 @@ export class BoLActor extends Actor {
|
||||
ChatMessage.create({
|
||||
alias: this.name,
|
||||
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() })
|
||||
content: await renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() })
|
||||
})
|
||||
} else {
|
||||
if (prone) {
|
||||
@@ -888,9 +883,6 @@ export class BoLActor extends Actor {
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
BoLActor._healthLock.delete(this.id)
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------- */
|
||||
@@ -912,7 +904,7 @@ export class BoLActor extends Actor {
|
||||
let msg = await ChatMessage.create({
|
||||
alias: this.name,
|
||||
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', {
|
||||
content: await renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', {
|
||||
name: this.name,
|
||||
img: this.img,
|
||||
actorId: this.id,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export { default as BoLBaseItemSheet } from "./base-item-sheet.mjs"
|
||||
export { default as BoLItemSheet } from "./item-sheet.mjs"
|
||||
export { default as BoLFeatureSheet } from "./feature-sheet.mjs"
|
||||
export { default as BoLBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||
export { default as BoLActorSheet } from "./actor-sheet.mjs"
|
||||
export { default as BoLHordeSheet } from "./horde-sheet.mjs"
|
||||
export { default as BoLVehicleSheet } from "./vehicle-sheet.mjs"
|
||||
@@ -1,144 +0,0 @@
|
||||
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
import { BoLUtility } from "../../system/bol-utility.js"
|
||||
|
||||
/**
|
||||
* Actor Sheet for BoL characters and encounters using AppV2
|
||||
*/
|
||||
export default class BoLActorSheet extends BoLBaseActorSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes],
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
template: "systems/bol/templates/actor/actor-sheet.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
tabGroups = { primary: "stats" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
const actor = this.document
|
||||
|
||||
context.data = actor.toObject()
|
||||
context.details = actor.details
|
||||
context.attributes = actor.attributes
|
||||
context.aptitudes = actor.aptitudes
|
||||
context.resources = actor.getResourcesFromType()
|
||||
context.xp = actor.system.xp
|
||||
context.equipment = actor.equipment
|
||||
context.equipmentCreature = actor.equipmentCreature
|
||||
context.weapons = actor.weapons
|
||||
context.protections = actor.protections
|
||||
context.spells = actor.spells
|
||||
context.alchemy = actor.alchemy
|
||||
context.containers = actor.containers
|
||||
context.treasure = actor.treasure
|
||||
context.boleffects = actor.boleffects
|
||||
context.alchemyrecipe = actor.alchemyrecipe
|
||||
context.horoscopes = actor.horoscopes
|
||||
context.vehicles = actor.vehicles
|
||||
context.fightoptions = actor.fightoptions
|
||||
context.ammos = actor.ammos
|
||||
context.misc = actor.misc
|
||||
context.xplog = actor.xplog
|
||||
context.combat = actor.buildCombat()
|
||||
context.initiativeRank = actor.getInitiativeRank()
|
||||
context.features = actor.buildFeatures()
|
||||
context.options = this.options
|
||||
context.editScore = this.options.editScore
|
||||
context.useBougette = (actor.type === "character" && BoLUtility.getUseBougette()) || false
|
||||
context.bougette = actor.getBougette()
|
||||
context.charType = actor.getCharType()
|
||||
context.villainy = actor.getVillainy()
|
||||
context.isUndead = actor.isUndead()
|
||||
context.biography = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.details?.biography || "", { async: true }
|
||||
)
|
||||
context.notes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.details?.notes || "", { async: true }
|
||||
)
|
||||
context.isSorcerer = actor.isSorcerer()
|
||||
context.isAlchemist = actor.isAlchemist()
|
||||
context.isAstrologer = actor.isAstrologer()
|
||||
context.isMysteries = context.isSorcerer || context.isAlchemist || context.isAstrologer
|
||||
context.isPriest = actor.isPriest()
|
||||
context.horoscopeGroupList = game.settings.get("bol", "horoscope-group")
|
||||
|
||||
console.log("ACTORDATA (AppV2)", context)
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_activateListeners() {
|
||||
super._activateListeners()
|
||||
if (!this.isEditable) return
|
||||
|
||||
// Create generic item
|
||||
this.element.querySelectorAll(".create-item").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newEquipment"), type: "item" }], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
|
||||
// Create natural weapon
|
||||
this.element.querySelectorAll(".create-natural-weapon").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
const system = foundry.utils.duplicate(game.bol.config.defaultNaturalWeapon)
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newNaturalWeapon"), type: "item", system }], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
|
||||
// Create natural protection
|
||||
this.element.querySelectorAll(".create-natural-protection").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
const system = foundry.utils.duplicate(game.bol.config.defaultNaturalProtection)
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newNaturalProtection"), type: "item", system }], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
|
||||
// Item create via header dataset (type-based creation)
|
||||
this.element.querySelectorAll(".item-create").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
ev.preventDefault()
|
||||
const header = ev.currentTarget
|
||||
const type = header.dataset.type
|
||||
const data = foundry.utils.duplicate(header.dataset)
|
||||
const name = `New ${type}`
|
||||
delete data.type
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name, type, data }])
|
||||
})
|
||||
})
|
||||
|
||||
// Add XP Log entry
|
||||
this.element.querySelectorAll(".xplog-add").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
ev.preventDefault()
|
||||
this.actor.addXPLog("other", "Nouveau", 0, 0)
|
||||
})
|
||||
})
|
||||
|
||||
// Add item by category/subtype (equipment tab headers)
|
||||
this.element.querySelectorAll(".item-add").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
ev.preventDefault()
|
||||
const { itemType, category, subtype } = ev.currentTarget.dataset
|
||||
const system = { category, subtype }
|
||||
this.actor.createEmbeddedDocuments("Item", [{
|
||||
name: game.i18n.localize("BOL.ui.newEquipment"),
|
||||
type: itemType || "item",
|
||||
system,
|
||||
}], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
import { BoLRoll } from "../../controllers/bol-rolls.js"
|
||||
import { BoLUtility } from "../../system/bol-utility.js"
|
||||
|
||||
/**
|
||||
* Base Actor Sheet for BoL system using AppV2
|
||||
* @extends {ActorSheetV2}
|
||||
*/
|
||||
export default class BoLBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
this.#dragDrop = this.#createDragDropHandlers()
|
||||
}
|
||||
|
||||
#dragDrop
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["bol", "sheet", "actor"],
|
||||
position: {
|
||||
width: 836,
|
||||
height: 807,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
navSelector: "nav[data-group=\"primary\"]",
|
||||
contentSelector: "section.sheet-body",
|
||||
initial: "stats",
|
||||
},
|
||||
],
|
||||
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
|
||||
actions: {},
|
||||
}
|
||||
|
||||
/** Tab groups state */
|
||||
tabGroups = { primary: "stats" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const actor = this.document
|
||||
return {
|
||||
actor,
|
||||
system: actor.system,
|
||||
config: game.bol.config,
|
||||
isGM: game.user.isGM,
|
||||
isEditable: this.isEditable,
|
||||
owner: this.document.isOwner,
|
||||
cssClass: this.options.classes.join(" "),
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options)
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||
this._activateTabs()
|
||||
this._activateListeners()
|
||||
this._applyBackgroundImage()
|
||||
this._activateImageEdit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply background image to the actor form
|
||||
* @private
|
||||
*/
|
||||
_applyBackgroundImage() {
|
||||
const logoUrl = BoLUtility.getLogoActorSheet()
|
||||
const form = this.element.querySelector(".bol-actor-form")
|
||||
if (form) form.style.backgroundImage = `url(${logoUrl})`
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate image editing via FilePicker
|
||||
* @private
|
||||
*/
|
||||
_activateImageEdit() {
|
||||
const img = this.element.querySelector('[data-edit="img"]')
|
||||
if (img && this.isEditable) {
|
||||
img.style.cursor = "pointer"
|
||||
img.addEventListener("click", () => {
|
||||
new FilePicker({
|
||||
type: "image",
|
||||
current: this.document.img,
|
||||
callback: (path) => this.document.update({ img: path }),
|
||||
}).browse()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate tab navigation
|
||||
* @private
|
||||
*/
|
||||
_activateTabs() {
|
||||
const nav = this.element.querySelector("nav[data-group]")
|
||||
if (!nav) return
|
||||
|
||||
const group = nav.dataset.group
|
||||
const activeTab = this.tabGroups[group] || "stats"
|
||||
|
||||
nav.querySelectorAll("[data-tab]").forEach((link) => {
|
||||
const tab = link.dataset.tab
|
||||
link.classList.toggle("active", tab === activeTab)
|
||||
link.addEventListener("click", (event) => {
|
||||
event.preventDefault()
|
||||
this.tabGroups[group] = tab
|
||||
this.render()
|
||||
})
|
||||
})
|
||||
|
||||
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach((content) => {
|
||||
content.classList.toggle("active", content.dataset.tab === activeTab)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate event listeners (replaces activateListeners with vanilla DOM)
|
||||
* @private
|
||||
*/
|
||||
_activateListeners() {
|
||||
if (!this.isEditable) return
|
||||
|
||||
// Item edit
|
||||
this.element.querySelectorAll(".item-edit").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
const item = this.actor.items.get(li?.dataset.itemId)
|
||||
item?.sheet.render(true)
|
||||
})
|
||||
})
|
||||
|
||||
// Item delete
|
||||
this.element.querySelectorAll(".item-delete").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
const itemId = li?.dataset.itemId
|
||||
Dialog.confirm({
|
||||
title: game.i18n.localize("BOL.ui.deletetitle"),
|
||||
content: game.i18n.localize("BOL.ui.confirmdelete"),
|
||||
yes: () => {
|
||||
this.actor.deleteEmbeddedDocuments("Item", [itemId])
|
||||
li?.remove()
|
||||
},
|
||||
no: () => {},
|
||||
defaultYes: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Item equip/unequip
|
||||
this.element.querySelectorAll(".item-equip").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
const item = this.actor.items.get(li?.dataset.itemId)
|
||||
if (item) this.actor.toggleEquipItem(item)
|
||||
})
|
||||
})
|
||||
|
||||
// Toggle fight option
|
||||
this.element.querySelectorAll(".toggle-fight-option").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
this.actor.toggleFightOption(li?.dataset.itemId)
|
||||
})
|
||||
})
|
||||
|
||||
// Inc/dec alchemy points
|
||||
this.element.querySelectorAll(".inc-dec-btns-alchemy").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
this.actor.spendAlchemyPoint(li?.dataset.itemId, 1)
|
||||
})
|
||||
})
|
||||
|
||||
// Inc/dec resource buttons
|
||||
this.element.querySelectorAll(".inc-dec-btns-resource").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const dataset = ev.currentTarget.dataset
|
||||
this.actor.incDecResources(dataset.target, parseInt(dataset.incr))
|
||||
})
|
||||
})
|
||||
|
||||
// Generic inc/dec buttons for item fields
|
||||
this.element.querySelectorAll(".inc-dec-btns").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
const li = ev.currentTarget.closest(".item")
|
||||
if (!li) return
|
||||
const item = this.actor.items.get(li.dataset.itemId)
|
||||
if (!item) return
|
||||
const dataset = ev.currentTarget.dataset
|
||||
const operator = dataset.operator
|
||||
const target = dataset.target
|
||||
const incr = parseInt(dataset.incr)
|
||||
const min = parseInt(dataset.min)
|
||||
const max = parseInt(dataset.max) || 10000
|
||||
// eslint-disable-next-line no-eval
|
||||
let value = eval("item." + target) || 0
|
||||
if (operator === "minus") value = value >= min + incr ? value - incr : min
|
||||
if (operator === "plus") value = value <= max - incr ? value + incr : max
|
||||
item.update({ [target]: value })
|
||||
})
|
||||
})
|
||||
|
||||
// Rollable elements
|
||||
this.element.querySelectorAll(".rollable").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => this._onRoll(ev))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clickable rolls (replaces _onRoll with vanilla DOM)
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
_onRoll(event) {
|
||||
event.preventDefault()
|
||||
const element = event.currentTarget
|
||||
const dataset = element.dataset
|
||||
const rollType = dataset.rollType
|
||||
const li = element.closest(".item")
|
||||
const itemId = li?.dataset.itemId
|
||||
|
||||
switch (rollType) {
|
||||
case "attribute":
|
||||
BoLRoll.attributeCheck(this.actor, dataset.key, event)
|
||||
break
|
||||
case "aptitude":
|
||||
BoLRoll.aptitudeCheck(this.actor, dataset.key, event)
|
||||
break
|
||||
case "weapon":
|
||||
BoLRoll.weaponCheck(this.actor, event)
|
||||
break
|
||||
case "spell":
|
||||
BoLRoll.spellCheck(this.actor, event)
|
||||
break
|
||||
case "alchemy":
|
||||
BoLRoll.alchemyCheck(this.actor, event)
|
||||
break
|
||||
case "protection":
|
||||
this.actor.rollProtection(itemId)
|
||||
break
|
||||
case "damage":
|
||||
this.actor.rollWeaponDamage(itemId)
|
||||
break
|
||||
case "aptitudexp":
|
||||
this.actor.incAptitudeXP(dataset.key)
|
||||
break
|
||||
case "attributexp":
|
||||
this.actor.incAttributeXP(dataset.key)
|
||||
break
|
||||
case "careerxp":
|
||||
this.actor.incCareerXP(itemId)
|
||||
break
|
||||
case "horoscope-minor":
|
||||
BoLRoll.horoscopeCheck(this.actor, event, "minor")
|
||||
break
|
||||
case "horoscope-major":
|
||||
BoLRoll.horoscopeCheck(this.actor, event, "major")
|
||||
break
|
||||
case "horoscope-major-group":
|
||||
BoLRoll.horoscopeCheck(this.actor, event, "majorgroup")
|
||||
break
|
||||
case "bougette":
|
||||
this.actor.rollBougette()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// #region Drag-and-Drop
|
||||
|
||||
#createDragDropHandlers() {
|
||||
return (this.options.dragDrop || []).map((dragDrop) =>
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
...dragDrop,
|
||||
permissions: {
|
||||
dragstart: this._canDragStart.bind(this),
|
||||
drop: this._canDragDrop.bind(this),
|
||||
},
|
||||
callbacks: {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
dragover: this._onDragOver.bind(this),
|
||||
drop: this._onDrop.bind(this),
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
_canDragStart(selector) {
|
||||
return this.isEditable
|
||||
}
|
||||
|
||||
_canDragDrop(selector) {
|
||||
return this.isEditable
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
/**
|
||||
* Base Item Sheet for BoL system using AppV2
|
||||
* @extends {ItemSheetV2}
|
||||
*/
|
||||
export default class BoLBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
this.#dragDrop = this.#createDragDropHandlers()
|
||||
}
|
||||
|
||||
#dragDrop
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["bol", "sheet", "item"],
|
||||
position: {
|
||||
width: 650,
|
||||
height: 780,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
navSelector: 'nav[data-group="primary"]',
|
||||
contentSelector: "section.sheet-body",
|
||||
initial: "description",
|
||||
},
|
||||
],
|
||||
actions: {
|
||||
editImage: BoLBaseItemSheet.#onEditImage,
|
||||
postItem: BoLBaseItemSheet.#onPostItem,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab groups state
|
||||
* @type {object}
|
||||
*/
|
||||
tabGroups = { primary: "description" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = {
|
||||
// Document & system
|
||||
fields: this.document.schema.fields,
|
||||
systemFields: this.document.system.schema.fields,
|
||||
item: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
|
||||
// Enriched content
|
||||
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
this.document.system.description,
|
||||
{ async: true }
|
||||
),
|
||||
|
||||
// Properties
|
||||
category: this.document.system.category,
|
||||
itemProperties: this.document.itemProperties,
|
||||
|
||||
// Config & permissions
|
||||
config: game.bol.config,
|
||||
isGM: game.user.isGM,
|
||||
isEditable: this.isEditable,
|
||||
|
||||
// CSS classes for template
|
||||
cssClass: this.options.classes.join(" "),
|
||||
|
||||
// Tab state
|
||||
tabs: this._getTabs(),
|
||||
activeTab: this.tabGroups.primary || "description"
|
||||
}
|
||||
|
||||
// Add careers if item is on an actor
|
||||
if (this.document.actor) {
|
||||
context.careers = this.document.actor.careers
|
||||
}
|
||||
|
||||
// Apply dynamic defaults based on item type
|
||||
this._applyDynamicDefaults(context)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tabs configuration
|
||||
* @returns {object[]}
|
||||
* @private
|
||||
*/
|
||||
_getTabs() {
|
||||
return [
|
||||
{ id: "description", label: "BOL.ui.tab.description", icon: "fa-solid fa-book" },
|
||||
{ id: "properties", label: "BOL.ui.tab.details", icon: "fa-solid fa-cog" }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dynamic defaults to context based on item type and category
|
||||
* @param {object} context
|
||||
* @private
|
||||
*/
|
||||
_applyDynamicDefaults(context) {
|
||||
const itemData = context.item
|
||||
|
||||
if (itemData.type === "item") {
|
||||
// Set default category
|
||||
if (!itemData.system.category) {
|
||||
itemData.system.category = "equipment"
|
||||
}
|
||||
|
||||
// Handle equipment slot
|
||||
if (itemData.system.category === "equipment" && itemData.system.properties.equipable) {
|
||||
if (!itemData.system.properties.slot) {
|
||||
itemData.system.properties.slot = "-"
|
||||
}
|
||||
}
|
||||
|
||||
// Handle spell conditions
|
||||
if (itemData.system.category === 'spell') {
|
||||
if (!itemData.system.properties.mandatoryconditions) {
|
||||
itemData.system.properties.mandatoryconditions = []
|
||||
}
|
||||
if (!itemData.system.properties.optionnalconditions) {
|
||||
itemData.system.properties.optionnalconditions = []
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
itemData.system.properties.mandatoryconditions[i] = itemData.system.properties.mandatoryconditions[i] ?? ""
|
||||
}
|
||||
for (let i = 0; i < 8; i++) {
|
||||
itemData.system.properties.optionnalconditions[i] = itemData.system.properties.optionnalconditions[i] ?? ""
|
||||
}
|
||||
}
|
||||
} else if (itemData.type === "feature") {
|
||||
// Set default subtype/category
|
||||
if (!itemData.system.subtype) {
|
||||
itemData.system.category = "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options)
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||
this._activateTabs()
|
||||
this._activateListeners()
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate tab navigation via CSS only (no re-render) to preserve unsaved form state.
|
||||
* @private
|
||||
*/
|
||||
_activateTabs() {
|
||||
const nav = this.element.querySelector('nav[data-group="primary"]')
|
||||
if (!nav) return
|
||||
const section = this.element.querySelector('section.sheet-body')
|
||||
if (!section) return
|
||||
|
||||
const activeTab = this.tabGroups.primary || "description"
|
||||
|
||||
// Set initial active state
|
||||
nav.querySelectorAll('[data-tab]').forEach(link => {
|
||||
link.classList.toggle('active', link.dataset.tab === activeTab)
|
||||
})
|
||||
section.querySelectorAll('[data-tab]').forEach(content => {
|
||||
content.classList.toggle('active', content.dataset.tab === activeTab)
|
||||
})
|
||||
|
||||
// Tab click — CSS-only switch, no render()
|
||||
nav.querySelectorAll('[data-tab]').forEach(link => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
const tab = link.dataset.tab
|
||||
this.tabGroups.primary = tab
|
||||
nav.querySelectorAll('[data-tab]').forEach(l => l.classList.toggle('active', l.dataset.tab === tab))
|
||||
section.querySelectorAll('[data-tab]').forEach(c => c.classList.toggle('active', c.dataset.tab === tab))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate custom listeners
|
||||
* @private
|
||||
*/
|
||||
_activateListeners() {
|
||||
if (!this.isEditable) return
|
||||
|
||||
// Armor quality change handler
|
||||
const armorQuality = this.element.querySelector('.armorQuality')
|
||||
if (armorQuality) {
|
||||
armorQuality.addEventListener('change', (ev) => {
|
||||
const value = ev.currentTarget.value
|
||||
const soakFormula = this.element.querySelector('.soakFormula')
|
||||
if (soakFormula && game.bol.config.soakFormulas[value]) {
|
||||
soakFormula.value = game.bol.config.soakFormulas[value]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// #region Drag-and-Drop Workflow
|
||||
|
||||
/**
|
||||
* Create drag-and-drop workflow handlers for this Application
|
||||
* @returns {DragDrop[]}
|
||||
* @private
|
||||
*/
|
||||
#createDragDropHandlers() {
|
||||
return []
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Actions
|
||||
|
||||
/**
|
||||
* Handle editing the item image
|
||||
* @param {PointerEvent} event
|
||||
* @param {HTMLElement} target
|
||||
* @private
|
||||
*/
|
||||
static async #onEditImage(event, target) {
|
||||
const fp = new FilePicker({
|
||||
current: this.document.img,
|
||||
type: "image",
|
||||
callback: (path) => {
|
||||
this.document.update({ img: path })
|
||||
},
|
||||
})
|
||||
return fp.browse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle posting the item to chat
|
||||
* @param {PointerEvent} event
|
||||
* @param {HTMLElement} target
|
||||
* @private
|
||||
*/
|
||||
static async #onPostItem(event, target) {
|
||||
const BoLUtility = (await import("../../system/bol-utility.js")).BoLUtility
|
||||
let chatData = foundry.utils.duplicate(this.document)
|
||||
if (this.document.actor) {
|
||||
chatData.actor = { id: this.document.actor.id }
|
||||
}
|
||||
BoLUtility.postItem(chatData)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import BoLBaseItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
/**
|
||||
* Item Sheet for "feature" type items (boons, careers, origins, etc.)
|
||||
* @extends {BoLBaseItemSheet}
|
||||
*/
|
||||
export default class BoLFeatureSheet extends BoLBaseItemSheet {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["bol", "sheet", "item", "item-type-feature"],
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/bol/templates/item/feature-sheet.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
|
||||
// Add feature-specific context
|
||||
context.isFeature = true
|
||||
context.isBoon = context.system.subtype === "boon"
|
||||
context.isFlaw = context.system.subtype === "flaw"
|
||||
context.isCareer = context.system.subtype === "career"
|
||||
context.isOrigin = context.system.subtype === "origin"
|
||||
context.isRace = context.system.subtype === "race"
|
||||
context.isFightOption = context.system.subtype === "fightoption"
|
||||
context.isEffect = context.system.subtype === "effect"
|
||||
context.isHoroscope = context.system.subtype === "horoscope"
|
||||
context.isXpLog = context.system.subtype === "xplog"
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
/**
|
||||
* Actor Sheet for BoL hordes using AppV2
|
||||
*/
|
||||
export default class BoLHordeSheet extends BoLBaseActorSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes],
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
template: "systems/bol/templates/actor/horde-sheet.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
tabGroups = { primary: "stats" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
const actor = this.document
|
||||
|
||||
context.options = this.options
|
||||
context.editScore = this.options.editScore
|
||||
context.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.details?.biography || "", { async: true }
|
||||
)
|
||||
|
||||
console.log("HORDE (AppV2)", context)
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_activateListeners() {
|
||||
super._activateListeners()
|
||||
if (!this.isEditable) return
|
||||
|
||||
// Item create via header dataset
|
||||
this.element.querySelectorAll(".item-create").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
ev.preventDefault()
|
||||
const header = ev.currentTarget
|
||||
const type = header.dataset.type
|
||||
const data = foundry.utils.duplicate(header.dataset)
|
||||
delete data.type
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: `New ${type}`, type, data }])
|
||||
})
|
||||
})
|
||||
|
||||
// Create generic item
|
||||
this.element.querySelectorAll(".create_item").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: "Nouvel Equipement", type: "item" }], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import BoLBaseItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
/**
|
||||
* Item Sheet for "item" type items (equipment, weapons, etc.)
|
||||
* @extends {BoLBaseItemSheet}
|
||||
*/
|
||||
export default class BoLItemSheet extends BoLBaseItemSheet {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["bol", "sheet", "item", "item-type-item"],
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/bol/templates/item/item-sheet.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
|
||||
// Add item-specific context
|
||||
context.isItem = true
|
||||
context.isEquipment = context.category === "equipment"
|
||||
context.isWeapon = context.category === "weapon"
|
||||
context.isProtection = context.category === "protection"
|
||||
context.isSpell = context.category === "spell"
|
||||
context.isAlchemy = context.category === "alchemy"
|
||||
context.isCapacity = context.category === "capacity"
|
||||
context.isMagical = context.category === "magical"
|
||||
context.isVehicle = context.category === "vehicle"
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
/**
|
||||
* Actor Sheet for BoL vehicles using AppV2
|
||||
*/
|
||||
export default class BoLVehicleSheet extends BoLBaseActorSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
classes: [...super.DEFAULT_OPTIONS.classes],
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
template: "systems/bol/templates/actor/vehicle-sheet.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
tabGroups = { primary: "stats" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
const actor = this.document
|
||||
|
||||
context.weapons = actor.vehicleWeapons
|
||||
context.options = this.options
|
||||
context.editScore = this.options.editScore
|
||||
context.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
actor.system.description || "", { async: true }
|
||||
)
|
||||
|
||||
console.log("VEHICLE (AppV2)", context) // TODO: remove before release
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_activateListeners() {
|
||||
super._activateListeners()
|
||||
if (!this.isEditable) return
|
||||
|
||||
// Item create via header dataset
|
||||
this.element.querySelectorAll(".item-create").forEach((el) => {
|
||||
el.addEventListener("click", (ev) => {
|
||||
ev.preventDefault()
|
||||
const header = ev.currentTarget
|
||||
const type = header.dataset.type
|
||||
const data = foundry.utils.duplicate(header.dataset)
|
||||
delete data.type
|
||||
this.actor.createEmbeddedDocuments("Item", [{ name: `New ${type}`, type, data }])
|
||||
})
|
||||
})
|
||||
|
||||
// Create vehicle weapon
|
||||
this.element.querySelectorAll(".vehicle-weapon-add").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
this.actor.createEmbeddedDocuments("Item", [{
|
||||
name: game.i18n.localize("BOL.ui.newEquipment"),
|
||||
type: "item",
|
||||
system: { category: "vehicleweapon" },
|
||||
}], { renderSheet: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
/* -------------------------------------------- */
|
||||
// Import Modules
|
||||
import { BoLActor } from "./actor/actor.js"
|
||||
// AppV1 actor sheets kept for reference only (AppV2 used via sheets.* below)
|
||||
import { BoLActorSheet } from "./actor/actor-sheet.js"
|
||||
import { BoLVehicleSheet } from "./actor/vehicle-sheet.js"
|
||||
import { BoLHordeSheet } from "./actor/horde-sheet.js"
|
||||
import { BoLItem } from "./item/item.js"
|
||||
// Note: Old BoLItemSheet (AppV1) is now replaced by AppV2 sheets
|
||||
import { BoLItemSheet } from "./item/item-sheet.js"
|
||||
import { System, BOL } from "./system/config.js"
|
||||
import { preloadHandlebarsTemplates } from "./system/templates.js"
|
||||
import { registerHandlebarsHelpers } from "./system/helpers.js"
|
||||
@@ -16,11 +18,8 @@ import { BoLHotbar } from "./system/bol-hotbar.js"
|
||||
import { BoLCommands } from "./system/bol-commands.js"
|
||||
import { BoLRoll } from "./controllers/bol-rolls.js"
|
||||
|
||||
// Import DataModels
|
||||
import * as models from "./models/_module.mjs"
|
||||
|
||||
// Import AppV2 Sheets
|
||||
import * as sheets from "./applications/sheets/_module.mjs"
|
||||
/* -------------------------------------------- */
|
||||
const BOL_WELCOME_MESSAGE_URL = "https://www.uberwald.me/gitea/public/bol/raw/branch/v13/welcome-message-bol.html"
|
||||
|
||||
/* -------------------------------------------- */
|
||||
Hooks.once('init', async function () {
|
||||
@@ -32,9 +31,7 @@ Hooks.once('init', async function () {
|
||||
BoLRoll,
|
||||
BoLUtility,
|
||||
macros: Macros,
|
||||
config: BOL,
|
||||
models,
|
||||
sheets
|
||||
config: BOL
|
||||
};
|
||||
|
||||
// Game socket
|
||||
@@ -53,38 +50,17 @@ Hooks.once('init', async function () {
|
||||
|
||||
// Define custom Entity classes
|
||||
CONFIG.Actor.documentClass = BoLActor;
|
||||
CONFIG.Actor.dataModels = {
|
||||
character: models.BoLCharacter,
|
||||
encounter: models.BoLEncounter,
|
||||
horde: models.BoLHorde,
|
||||
vehicle: models.BoLVehicle
|
||||
}
|
||||
|
||||
CONFIG.Item.documentClass = BoLItem;
|
||||
CONFIG.Item.dataModels = {
|
||||
item: models.BoLItem,
|
||||
feature: models.BoLFeature
|
||||
}
|
||||
|
||||
CONFIG.Combat.documentClass = BoLCombatManager;
|
||||
|
||||
// Register sheet application classes
|
||||
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
|
||||
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
|
||||
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
|
||||
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLHordeSheet, { types: ["horde"], makeDefault: true })
|
||||
foundry.documents.collections.Actors.registerSheet("bol", BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
|
||||
foundry.documents.collections.Actors.registerSheet("bol", BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
|
||||
foundry.documents.collections.Actors.registerSheet("bol", BoLHordeSheet, { types: ["horde"], makeDefault: true })
|
||||
|
||||
// Register AppV2 Item Sheets
|
||||
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
|
||||
foundry.documents.collections.Items.registerSheet("bol", sheets.BoLItemSheet, { types: ["item"], makeDefault: true });
|
||||
foundry.documents.collections.Items.registerSheet("bol", sheets.BoLFeatureSheet, { types: ["feature"], makeDefault: true });
|
||||
|
||||
// Debug: Verify AppV2 sheets are loaded
|
||||
console.log("BoL Item Sheets registered:", {
|
||||
BoLItemSheet: sheets.BoLItemSheet.name,
|
||||
BoLFeatureSheet: sheets.BoLFeatureSheet.name,
|
||||
extendsApplicationV2: sheets.BoLItemSheet.prototype instanceof foundry.applications.api.ApplicationV2
|
||||
});
|
||||
foundry.documents.collections.Items.registerSheet("bol", BoLItemSheet, { makeDefault: true });
|
||||
|
||||
// Inot useful stuff
|
||||
BoLUtility.init()
|
||||
@@ -110,22 +86,54 @@ Hooks.once('init', async function () {
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async function welcomeMessage() {
|
||||
const noRulebook = !game.modules.find(m => m.id === "bol-rulebook")
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/bol/templates/chat/chat-welcome.hbs",
|
||||
{ noRulebook }
|
||||
)
|
||||
ChatMessage.create({ user: game.user.id, whisper: [game.user.id], content })
|
||||
function welcomeMessage() {
|
||||
let content = `<div id="welcome-message-bol"><span class="rdd-roll-part">
|
||||
<strong>` + game.i18n.localize("BOL.chat.welcome1") + `</strong><p>` +
|
||||
game.i18n.localize("BOL.chat.welcome2") + "</p><p>" +
|
||||
game.i18n.localize("BOL.chat.welcome3") + "</p><p>" +
|
||||
game.i18n.localize("BOL.chat.welcome4") + "</p><p>" +
|
||||
game.i18n.localize("BOL.chat.welcome5") + "</p>" +
|
||||
game.i18n.localize("BOL.chat.welcome6")
|
||||
|
||||
let rulebook = game.modules.find(m => m.id === "bol-rulebook")
|
||||
if (!rulebook) {
|
||||
content += "<p>" + game.i18n.localize("BOL.chat.bolRulebookMessage") + "</p>"
|
||||
}
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
whisper: [game.user.id],
|
||||
content: content
|
||||
})
|
||||
|
||||
if (game.user.isGM && game.i18n.lang == 'en' && !game.modules.find(m => m.id == "babele")) {
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
whisper: [game.user.id],
|
||||
content: `<div class="bol-welcome-card"><div class="welcome-body"><p class="welcome-warning">⚠ WARNING ! English language selected, but Babele module is not installed !<br>Please install babele from the module tab in Foundry interface.</p></div></div>`
|
||||
content: `<div id="welcome-message-bol"><span class="rdd-roll-part">
|
||||
<strong>WARNING ! English language selected, but Babele module is not installed !<br>Please install babele from the module tab in Foundry interface.`
|
||||
})
|
||||
ui.notifications.warn("WARNING ! English language selected, but babele module is not installed !<br>Please install babele from the module tab in Foundry interface.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
function welcomeMessageRepository() {
|
||||
if (game.user.isGM) {
|
||||
// Try to fetch the welcome message from the github repo "welcome-message-ecryme.html"
|
||||
fetch(BOL_WELCOME_MESSAGE_URL)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
whisper: [game.user.id],
|
||||
content: html
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching welcome message:", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -141,6 +149,7 @@ Hooks.once('ready', async function () {
|
||||
)
|
||||
|
||||
welcomeMessage()
|
||||
welcomeMessageRepository();
|
||||
|
||||
// User warning
|
||||
if (!game.user.isGM && game.user.character == undefined) {
|
||||
|
||||
@@ -5,6 +5,11 @@ const _apt2attr = { init: "mind", melee: "agility", ranged: "agility", def: "vig
|
||||
/* -------------------------------------------- */
|
||||
export class BoLRoll {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static options() {
|
||||
return { classes: ["bol", "dialog"], width: 480, height: 'fit-content' };
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static getDefaultAttribute(key) {
|
||||
return _apt2attr[key]
|
||||
@@ -124,7 +129,7 @@ export class BoLRoll {
|
||||
rangeMsg = "BOL.chat.range6"
|
||||
}
|
||||
ChatMessage.create({
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', {
|
||||
content: await renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', {
|
||||
weapon: weapon,
|
||||
attackerName: _token.actor.name,
|
||||
defenderName: target.actor.name,
|
||||
@@ -191,11 +196,6 @@ export class BoLRoll {
|
||||
return;
|
||||
}
|
||||
alchemy = foundry.utils.duplicate(alchemy)
|
||||
return this.alchemyCheckWithItem(actor, alchemy)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static alchemyCheckWithItem(actor, alchemy) {
|
||||
let alchemyData = alchemy.system
|
||||
if (alchemyData.properties.pccurrent < alchemyData.properties.pccost) {
|
||||
ui.notifications.warn("Pas assez de Points de Création investis dans la Préparation !")
|
||||
@@ -308,31 +308,26 @@ export class BoLRoll {
|
||||
// Final number of dices
|
||||
this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice)
|
||||
// Bonus or Malus ?
|
||||
const nbDiceEl = document.querySelector('#roll-nbdice')
|
||||
if (nbDiceEl) {
|
||||
if (this.rollData.bmDice == 0) {
|
||||
nbDiceEl.value = "2"
|
||||
$('#roll-nbdice').val("2")
|
||||
} else {
|
||||
let letter = (this.rollData.bmDice > 0) ? "B" : "M"
|
||||
nbDiceEl.value = "2 + " + String(Math.abs(this.rollData.bmDice)) + letter
|
||||
}
|
||||
$('#roll-nbdice').val("2 + " + String(Math.abs(this.rollData.bmDice)) + letter)
|
||||
}
|
||||
let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue
|
||||
if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) {
|
||||
rollbase = ""
|
||||
}
|
||||
const modifierEl = document.querySelector('#roll-modifier')
|
||||
if (modifierEl) modifierEl.value = rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
|
||||
$('#roll-modifier').val(rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
|
||||
this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" +
|
||||
this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus + "+" + effectModifier
|
||||
this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus + "+" + effectModifier)
|
||||
|
||||
// Rebuild list of applicable effects
|
||||
// Rebuild lits of applicable effects
|
||||
let selectEffects = ""
|
||||
for (let effect of this.rollData.bolApplicableEffects) {
|
||||
selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>`
|
||||
}
|
||||
const effectsEl = document.querySelector('#applicable-effects')
|
||||
if (effectsEl) effectsEl.innerHTML = selectEffects
|
||||
$('#applicable-effects').html(selectEffects)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -365,48 +360,46 @@ export class BoLRoll {
|
||||
/* -------------------------------------------- */
|
||||
static updateArmorMalus(rollData) {
|
||||
rollData.appliedArmorMalus = 0
|
||||
const agiEl = document.querySelector('#armor-agi-malus')
|
||||
if (rollData.attribute.key == "agility") {
|
||||
if (agiEl) agiEl.style.display = ''
|
||||
$("#armor-agi-malus").show()
|
||||
rollData.appliedArmorMalus += rollData.armorAgiMalus
|
||||
} else {
|
||||
if (agiEl) agiEl.style.display = 'none'
|
||||
$("#armor-agi-malus").hide()
|
||||
}
|
||||
const initEl = document.querySelector('#armor-init-malus')
|
||||
if (rollData.aptitude && rollData.aptitude.key == "init") {
|
||||
if (initEl) initEl.style.display = ''
|
||||
$("#armor-init-malus").show()
|
||||
rollData.appliedArmorMalus += rollData.armorInitMalus
|
||||
} else {
|
||||
if (initEl) initEl.style.display = 'none'
|
||||
$("#armor-init-malus").hide()
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------ -------------- */
|
||||
static updatePPCost(rollData) {
|
||||
const el = document.querySelector('#ppcost')
|
||||
if (el) el.innerHTML = rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor)
|
||||
$('#ppcost').html(rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor))
|
||||
}
|
||||
|
||||
/* ------------------------------ -------------- */
|
||||
static rollDialogListener(html) {
|
||||
|
||||
this.updateTotalDice()
|
||||
|
||||
html.querySelector('#optcond')?.addEventListener('change', (event) => {
|
||||
html.find('#optcond').change((event) => { // Dynamic change of PP cost of spell
|
||||
let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length)
|
||||
this.rollData.ppCost = pp
|
||||
this.updatePPCost(this.rollData)
|
||||
})
|
||||
|
||||
html.querySelector('#mod')?.addEventListener('change', (event) => {
|
||||
html.find('#mod').change((event) => {
|
||||
this.rollData.mod = Number(event.currentTarget.value)
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#modRanged')?.addEventListener('change', (event) => {
|
||||
html.find('#modRanged').change((event) => {
|
||||
this.rollData.modRanged = Number(event.currentTarget.value)
|
||||
this.updateTotalDice()
|
||||
})
|
||||
|
||||
html.querySelector('#attr')?.addEventListener('change', (event) => {
|
||||
html.find('#attr').change((event) => {
|
||||
let attrKey = event.currentTarget.value
|
||||
let actor = BoLUtility.getActorFromRollData(this.rollData)
|
||||
this.rollData.attribute = foundry.utils.duplicate(actor.system.attributes[attrKey])
|
||||
@@ -414,7 +407,7 @@ export class BoLRoll {
|
||||
this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData)
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#apt')?.addEventListener('change', (event) => {
|
||||
html.find('#apt').change((event) => {
|
||||
let aptKey = event.currentTarget.value
|
||||
let actor = BoLUtility.getActorFromRollData(this.rollData)
|
||||
this.rollData.aptitude = foundry.utils.duplicate(actor.system.aptitudes[aptKey])
|
||||
@@ -423,58 +416,65 @@ export class BoLRoll {
|
||||
this.updateTotalDice()
|
||||
})
|
||||
|
||||
html.querySelector('#applyShieldMalus')?.addEventListener('click', (event) => {
|
||||
this.rollData.shieldMalus = event.currentTarget.checked ? this.rollData.shieldAttackMalus : 0
|
||||
html.find('#applyShieldMalus').click((event) => {
|
||||
if (event.currentTarget.checked) {
|
||||
this.rollData.shieldMalus = this.rollData.shieldAttackMalus
|
||||
} else {
|
||||
this.rollData.shieldMalus = 0
|
||||
}
|
||||
this.updateTotalDice()
|
||||
})
|
||||
|
||||
html.querySelector('#career')?.addEventListener('change', (event) => {
|
||||
let careers = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
|
||||
html.find('#career').change((event) => {
|
||||
let careers = $('#career').val()
|
||||
this.rollData.careerBonus = (!careers || careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)))
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#boon')?.addEventListener('change', (event) => {
|
||||
let boons = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
|
||||
html.find('#boon').change((event) => {
|
||||
let boons = $('#boon').val()
|
||||
this.rollData.nbBoons = (!boons || boons.length == 0) ? 0 : boons.length
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#flaw')?.addEventListener('change', (event) => {
|
||||
let flaws = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
|
||||
html.find('#flaw').change((event) => {
|
||||
let flaws = $('#flaw').val()
|
||||
this.rollData.nbFlaws = (!flaws || flaws.length == 0) ? 0 : flaws.length
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelectorAll('.bdice').forEach(el => el.addEventListener('click', (event) => {
|
||||
html.find('.bdice').click((event) => {
|
||||
this.rollData.mDice = 0
|
||||
this.rollData.bDice = Number(event.currentTarget.value)
|
||||
this.updateTotalDice()
|
||||
}))
|
||||
html.querySelectorAll('.mdice').forEach(el => el.addEventListener('click', (event) => {
|
||||
})
|
||||
html.find('.mdice').click((event) => {
|
||||
this.rollData.bDice = 0
|
||||
this.rollData.mDice = Number(event.currentTarget.value)
|
||||
this.updateTotalDice()
|
||||
}))
|
||||
html.querySelector('#horoscope-bonus-applied')?.addEventListener('change', (event) => {
|
||||
})
|
||||
html.find('#horoscope-bonus-applied').change((event) => {
|
||||
this.rollData.selectedHoroscope = []
|
||||
for (let option of event.currentTarget.selectedOptions) {
|
||||
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
|
||||
}
|
||||
let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
|
||||
let horoscopes = $('#horoscope-bonus-applied').val()
|
||||
this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#horoscope-malus-applied')?.addEventListener('change', (event) => {
|
||||
|
||||
html.find('#horoscope-malus-applied').change((event) => {
|
||||
this.rollData.selectedHoroscope = []
|
||||
for (let option of event.currentTarget.selectedOptions) {
|
||||
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
|
||||
}
|
||||
let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
|
||||
let horoscopes = $('#horoscope-malus-applied').val()
|
||||
this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
|
||||
this.updateTotalDice()
|
||||
})
|
||||
html.querySelector('#horoscope-group-applied')?.addEventListener('change', (event) => {
|
||||
html.find('#horoscope-group-applied').change((event) => {
|
||||
this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value
|
||||
this.updateTotalDice()
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -552,47 +552,41 @@ export class BoLRoll {
|
||||
} else {
|
||||
rollData.shieldMalus = 0
|
||||
}
|
||||
// Save & pre-initialize computed fields
|
||||
// Save
|
||||
this.rollData = rollData
|
||||
this.updateTotalDice()
|
||||
console.log("ROLLDATA", rollData)
|
||||
|
||||
// Then display+process the dialog
|
||||
const rollOptionContent = await foundry.applications.handlebars.renderTemplate(rollOptionTpl, rollData);
|
||||
// Use Hooks to reliably get the rendered HTMLElement (renderDialogV2 receives (app, element, context))
|
||||
Hooks.once('renderDialogV2', (app, element) => {
|
||||
element.classList.add('bol');
|
||||
this.rollDialogListener(element);
|
||||
});
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: { title: rollData.label },
|
||||
let d = new Dialog({
|
||||
title: rollData.label,
|
||||
content: rollOptionContent,
|
||||
rejectClose: false,
|
||||
buttons: [
|
||||
{
|
||||
type: 'button',
|
||||
rollData: rollData,
|
||||
render: html => this.rollDialogListener(html),
|
||||
buttons: {
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
icon: 'fas fa-times',
|
||||
action: 'cancel'
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
label: game.i18n.localize("BOL.ui.submit"),
|
||||
icon: 'fas fa-check',
|
||||
action: 'submit',
|
||||
callback: (event, button, dialog) => {
|
||||
console.log("Submit Roll!!!!");
|
||||
if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) {
|
||||
ui.notifications.warn("Pas assez de Points de Pouvoir !")
|
||||
return false
|
||||
callback: () => {
|
||||
}
|
||||
rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ?
|
||||
(dialog.element.querySelector('#register-init')?.checked ?? false) : false;
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize("BOL.ui.submit"),
|
||||
callback: (html) => {
|
||||
console.log("Submit Roll!!!!");
|
||||
if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) { // Check PP available
|
||||
ui.notifications.warn("Pas assez de Points de Pouvoir !")
|
||||
return
|
||||
}
|
||||
rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ? $('#register-init').is(":checked") : false;
|
||||
|
||||
const isMalus = (rollData.bmDice < 0)
|
||||
let rollbase = rollData.attrValue + rollData.aptValue
|
||||
if (rollData.weapon?.system.properties.onlymodifier) rollbase = 0
|
||||
|
||||
let rollbase = rollData.attrValue + rollData.aptValue
|
||||
if (rollData.weapon?.system.properties.onlymodifier) {
|
||||
rollbase = 0
|
||||
}
|
||||
let diceData = BoLUtility.getDiceData()
|
||||
let malusInit = rollData.combatData?.malusInit || 0
|
||||
const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus + rollData.effectModifier - malusInit
|
||||
@@ -605,8 +599,12 @@ export class BoLRoll {
|
||||
r.roll();
|
||||
}
|
||||
}
|
||||
]
|
||||
}, { classes: ['bol', 'dialog'], width: 480 });
|
||||
},
|
||||
default: onEnter,
|
||||
close: () => { }
|
||||
}, this.options());
|
||||
|
||||
return d.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,15 +698,18 @@ export class BoLDefaultRoll {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
async sendChatMessage() {
|
||||
const actor = BoLUtility.getActorFromRollData(this.rollData)
|
||||
const rollMode = game.settings.get("core", "rollMode")
|
||||
const msgFlavor = await this._buildChatMessage(this.rollData)
|
||||
const msg = await this.rollData.roll.toMessage({
|
||||
let actor = BoLUtility.getActorFromRollData(this.rollData)
|
||||
this._buildChatMessage(this.rollData).then(async msgFlavor => {
|
||||
//console.log("MSG", msgFlavor )
|
||||
let msg = await this.rollData.roll.toMessage({
|
||||
user: game.user.id,
|
||||
rollMode: game.settings.get("core", "rollMode"),
|
||||
flavor: msgFlavor,
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
}, { rollMode })
|
||||
this.rollData.roll = foundry.utils.duplicate(this.rollData.roll)
|
||||
if (msg) await msg.setFlag("world", "bol-roll-data", this.rollData)
|
||||
})
|
||||
this.rollData.roll = foundry.utils.duplicate(this.rollData.roll) // Remove object, keep data (v111 ready)
|
||||
msg.setFlag("world", "bol-roll-data", this.rollData)
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -752,12 +753,6 @@ export class BoLDefaultRoll {
|
||||
/* -------------------------------------------- */
|
||||
async sendDamageMessage() {
|
||||
let actor = BoLUtility.getActorFromRollData(this.rollData)
|
||||
// If no target, collect potential targets from active scene (excluding attacker)
|
||||
if (!this.rollData.targetId && game.scenes.current) {
|
||||
this.rollData.potentialTargets = game.scenes.current.tokens
|
||||
.filter(t => t.actor && t.actor.id !== this.rollData.actorId)
|
||||
.map(t => ({ id: t.id, actorId: t.actor.id, name: t.name, img: t.texture?.src || t.actor.img }))
|
||||
}
|
||||
this._buildDamageChatMessage(this.rollData).then(async msgFlavor => {
|
||||
let msg = await this.rollData.damageRoll.toMessage({
|
||||
user: game.user.id,
|
||||
|
||||
119
module/item/item-sheet.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { BoLUtility } from "../system/bol-utility.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ItemSheet with some very simple modifications
|
||||
* @extends {ItemSheet}
|
||||
*/
|
||||
export class BoLItemSheet extends foundry.appv1.sheets.ItemSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["bol", "sheet", "item"],
|
||||
template: "systems/bol/templates/item/item-sheet.hbs",
|
||||
width: 650,
|
||||
height: 780,
|
||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
async getData(options) {
|
||||
const data = super.getData(options)
|
||||
let itemData = foundry.utils.duplicate(data.document)
|
||||
data.config = game.bol.config
|
||||
data.item = itemData
|
||||
data.category = itemData.system.category
|
||||
data.isGM = game.user.isGM;
|
||||
data.itemProperties = this.item.itemProperties;
|
||||
data.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, { async: true })
|
||||
if (data.document.actor) {
|
||||
data.careers = data.document.actor.careers
|
||||
}
|
||||
// Dynamic default data fix/adapt
|
||||
if (itemData.type == "item") {
|
||||
if (!itemData.system.category) {
|
||||
itemData.system.category = "equipment"
|
||||
}
|
||||
if (itemData.system.category == "equipment" && itemData.system.properties.equipable) {
|
||||
if (!itemData.system.properties.slot) {
|
||||
itemData.system.properties.slot = "-"
|
||||
}
|
||||
}
|
||||
if (itemData.system.category == 'spell') {
|
||||
if (!itemData.system.properties.mandatoryconditions) {
|
||||
itemData.system.properties.mandatoryconditions = []
|
||||
}
|
||||
if (!itemData.system.properties.optionnalconditions) {
|
||||
itemData.system.properties.optionnalconditions = []
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
itemData.system.properties.mandatoryconditions[i] = itemData.system.properties.mandatoryconditions[i] ?? ""
|
||||
}
|
||||
for (let i = 0; i < 8; i++) {
|
||||
itemData.system.properties.optionnalconditions[i] = itemData.system.properties.optionnalconditions[i] ?? ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!itemData.system.subtype) {
|
||||
itemData.system.category = "origin"
|
||||
}
|
||||
}
|
||||
|
||||
console.log("ITEMDATA", data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
buttons.unshift({
|
||||
class: "post",
|
||||
icon: "fas fa-comment",
|
||||
onclick: ev => this.postItem()
|
||||
});
|
||||
return buttons
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
postItem() {
|
||||
let chatData = foundry.utils.duplicate(this.item)
|
||||
if (this.actor) {
|
||||
chatData.actor = { id: this.actor.id };
|
||||
}
|
||||
BoLUtility.postItem(chatData);
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
setPosition(options = {}) {
|
||||
const position = super.setPosition(options);
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
return position;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
|
||||
super.activateListeners(html);
|
||||
|
||||
// Everything below here is only needed if the sheet is editable
|
||||
if (!this.options.editable) return;
|
||||
// Roll handlers, click handlers, etc. would go here.
|
||||
|
||||
html.find('.armorQuality').change(ev => {
|
||||
const li = $(ev.currentTarget);
|
||||
console.log(game.bol.config.soakFormulas[li.val()]);
|
||||
$('.soakFormula').val(game.bol.config.soakFormulas[li.val()]);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export { default as BoLCharacter } from "./character.mjs"
|
||||
export { default as BoLEncounter } from "./encounter.mjs"
|
||||
export { default as BoLHorde } from "./horde.mjs"
|
||||
export { default as BoLVehicle } from "./vehicle.mjs"
|
||||
export { default as BoLItem } from "./item.mjs"
|
||||
export { default as BoLFeature } from "./feature.mjs"
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* Data model for Character actors
|
||||
*/
|
||||
export default class BoLCharacterDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
|
||||
return {
|
||||
// Details
|
||||
details: new fields.SchemaField({
|
||||
biography: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
height: new fields.StringField({ initial: "" }),
|
||||
age: new fields.StringField({ initial: "" }),
|
||||
weight: new fields.StringField({ initial: "" }),
|
||||
hair: new fields.StringField({ initial: "" }),
|
||||
eyes: new fields.StringField({ initial: "" }),
|
||||
signs: new fields.StringField({ initial: "" }),
|
||||
size: new fields.StringField({ initial: "" }),
|
||||
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
|
||||
}),
|
||||
|
||||
// Combat
|
||||
combat: new fields.SchemaField({
|
||||
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
iscritical: new fields.BooleanField({ initial: false }),
|
||||
isfumble: new fields.BooleanField({ initial: false }),
|
||||
islegendary: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
|
||||
// Character type
|
||||
chartype: new fields.StringField({ initial: "player" }),
|
||||
villainy: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Bougette
|
||||
bougette: new fields.SchemaField({
|
||||
state: new fields.StringField({ initial: "nomoney" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// XP
|
||||
xp: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "xp" }),
|
||||
label: new fields.StringField({ initial: "BOL.traits.xp" }),
|
||||
total: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
spent: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// Creation
|
||||
creation: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "creation" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.creation" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// Protection
|
||||
prot: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "prot" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// Attributes
|
||||
attributes: new fields.SchemaField({
|
||||
vigor: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "vigor" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
agility: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "agility" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
mind: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "mind" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
appeal: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "appeal" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Aptitudes
|
||||
aptitudes: new fields.SchemaField({
|
||||
init: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "init" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
melee: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "melee" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
ranged: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "ranged" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
def: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "def" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Resources
|
||||
resources: new fields.SchemaField({
|
||||
hp: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hp" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hp" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
|
||||
}),
|
||||
hero: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hero" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hero" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
faith: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "faith" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.faith" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
power: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "power" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.power" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
alchemypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "alchemypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
astrologypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "astrologypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Character"];
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* Data model for Encounter actors
|
||||
*/
|
||||
export default class BoLEncounterDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
|
||||
return {
|
||||
// Details
|
||||
details: new fields.SchemaField({
|
||||
biography: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
height: new fields.StringField({ initial: "" }),
|
||||
age: new fields.StringField({ initial: "" }),
|
||||
weight: new fields.StringField({ initial: "" }),
|
||||
hair: new fields.StringField({ initial: "" }),
|
||||
eyes: new fields.StringField({ initial: "" }),
|
||||
signs: new fields.StringField({ initial: "" }),
|
||||
size: new fields.StringField({ initial: "" }),
|
||||
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
|
||||
}),
|
||||
|
||||
// Combat
|
||||
combat: new fields.SchemaField({
|
||||
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
iscritical: new fields.BooleanField({ initial: false }),
|
||||
isfumble: new fields.BooleanField({ initial: false }),
|
||||
islegendary: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
|
||||
// Character type
|
||||
chartype: new fields.StringField({ initial: "tough" }),
|
||||
isundead: new fields.BooleanField({ initial: false }),
|
||||
villainy: new fields.BooleanField({ initial: false }),
|
||||
size: new fields.StringField({ initial: "" }),
|
||||
environment: new fields.StringField({ initial: "" }),
|
||||
|
||||
// Protection
|
||||
prot: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "prot" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// Attributes
|
||||
attributes: new fields.SchemaField({
|
||||
vigor: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "vigor" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
agility: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "agility" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
mind: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "mind" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
appeal: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "appeal" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Aptitudes
|
||||
aptitudes: new fields.SchemaField({
|
||||
init: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "init" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
melee: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "melee" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
ranged: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "ranged" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
def: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "def" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Resources
|
||||
resources: new fields.SchemaField({
|
||||
hp: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hp" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hp" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
|
||||
}),
|
||||
hero: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hero" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hero" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
faith: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "faith" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.faith" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
power: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "power" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.power" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
alchemypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "alchemypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
astrologypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "astrologypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Encounter"];
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Data model for Feature items
|
||||
*/
|
||||
export default class BoLFeatureDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
const nullableNumber = { required: false, nullable: true, initial: null };
|
||||
|
||||
return {
|
||||
// Base fields
|
||||
category: new fields.StringField({ initial: "" }),
|
||||
subtype: new fields.StringField({ initial: "default" }),
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
properties: new fields.SchemaField({
|
||||
// Career
|
||||
sorcerer: new fields.BooleanField({ initial: false }),
|
||||
alchemist: new fields.BooleanField({ initial: false }),
|
||||
priest: new fields.BooleanField({ initial: false }),
|
||||
astrologer: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Boon
|
||||
isbonusdice: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Flaw
|
||||
ismalusdice: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Fight option
|
||||
fightoptiontype: new fields.StringField({ initial: "" }),
|
||||
activated: new fields.BooleanField({ initial: false }),
|
||||
isspecial: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Effect (boleffect)
|
||||
identifier: new fields.StringField({ initial: "" }),
|
||||
modifier: new fields.StringField({ initial: "" }),
|
||||
|
||||
// Horoscope
|
||||
horoscopeanswer: new fields.StringField({ initial: "" }),
|
||||
rank: new fields.NumberField({ ...nullableNumber }),
|
||||
|
||||
// XP log
|
||||
xptype: new fields.StringField({ initial: "" }),
|
||||
xpdate: new fields.StringField({ initial: "" }),
|
||||
xpname: new fields.StringField({ initial: "" }),
|
||||
xpcost: new fields.NumberField({ ...nullableNumber }),
|
||||
xpvalue: new fields.NumberField({ ...nullableNumber }),
|
||||
}),
|
||||
|
||||
// Feature-specific fields
|
||||
rank: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Feature"];
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/**
|
||||
* Data model for Horde actors
|
||||
*/
|
||||
export default class BoLHordeDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
|
||||
return {
|
||||
// Details
|
||||
details: new fields.SchemaField({
|
||||
biography: new fields.HTMLField({ initial: "" }),
|
||||
notes: new fields.HTMLField({ initial: "" }),
|
||||
height: new fields.StringField({ initial: "" }),
|
||||
age: new fields.StringField({ initial: "" }),
|
||||
weight: new fields.StringField({ initial: "" }),
|
||||
hair: new fields.StringField({ initial: "" }),
|
||||
eyes: new fields.StringField({ initial: "" }),
|
||||
signs: new fields.StringField({ initial: "" }),
|
||||
size: new fields.StringField({ initial: "" }),
|
||||
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
|
||||
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
|
||||
}),
|
||||
|
||||
// Combat
|
||||
combat: new fields.SchemaField({
|
||||
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
iscritical: new fields.BooleanField({ initial: false }),
|
||||
isfumble: new fields.BooleanField({ initial: false }),
|
||||
islegendary: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
|
||||
// Character type
|
||||
chartype: new fields.StringField({ initial: "horde" }),
|
||||
villainy: new fields.BooleanField({ initial: false }),
|
||||
hordesize: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
hordebasehp: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
hasdamagerule: new fields.BooleanField({ initial: false }),
|
||||
damagerule: new fields.StringField({ initial: "none" }),
|
||||
|
||||
// Protection
|
||||
prot: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "prot" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
|
||||
// Attributes
|
||||
attributes: new fields.SchemaField({
|
||||
vigor: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "vigor" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
agility: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "agility" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
mind: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "mind" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
appeal: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "appeal" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Aptitudes
|
||||
aptitudes: new fields.SchemaField({
|
||||
init: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "init" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
melee: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "melee" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
ranged: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "ranged" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
def: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "def" }),
|
||||
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
}),
|
||||
|
||||
// Resources
|
||||
resources: new fields.SchemaField({
|
||||
hp: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hp" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hp" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
|
||||
}),
|
||||
hero: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hero" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.hero" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
faith: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "faith" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.faith" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
power: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "power" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.power" }),
|
||||
ismain: new fields.BooleanField({ initial: true }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
alchemypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "alchemypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
}),
|
||||
astrologypoints: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "astrologypoints" }),
|
||||
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
|
||||
ismain: new fields.BooleanField({ initial: false }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Horde"];
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Data model for Item items
|
||||
*/
|
||||
export default class BoLItemDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
const nullableNumber = { required: false, nullable: true, initial: null };
|
||||
|
||||
return {
|
||||
// Base fields
|
||||
category: new fields.StringField({ initial: "" }),
|
||||
subtype: new fields.StringField({ initial: "default" }),
|
||||
description: new fields.HTMLField({ initial: "" }),
|
||||
properties: new fields.SchemaField({
|
||||
// Base flags
|
||||
ranged: new fields.BooleanField({ initial: false }),
|
||||
melee: new fields.BooleanField({ initial: false }),
|
||||
spell: new fields.BooleanField({ initial: false }),
|
||||
protection: new fields.BooleanField({ initial: false }),
|
||||
weapon: new fields.BooleanField({ initial: false }),
|
||||
armor: new fields.BooleanField({ initial: false }),
|
||||
helm: new fields.BooleanField({ initial: false }),
|
||||
shield: new fields.BooleanField({ initial: false }),
|
||||
equipable: new fields.BooleanField({ initial: false }),
|
||||
consumable: new fields.BooleanField({ initial: false }),
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
"2H": new fields.BooleanField({ initial: false }),
|
||||
reloadable: new fields.BooleanField({ initial: false }),
|
||||
bow: new fields.BooleanField({ initial: false }),
|
||||
crossbow: new fields.BooleanField({ initial: false }),
|
||||
throwing: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Equipment
|
||||
stackable: new fields.BooleanField({ initial: false }),
|
||||
stacksize: new fields.NumberField({ ...nullableNumber }),
|
||||
slot: new fields.StringField({ initial: "-" }),
|
||||
|
||||
// Weapon flags
|
||||
natural: new fields.BooleanField({ initial: false }),
|
||||
concealable: new fields.BooleanField({ initial: false }),
|
||||
ignoreshield: new fields.BooleanField({ initial: false }),
|
||||
attackBonusDice: new fields.BooleanField({ initial: false }),
|
||||
attackMalusDice: new fields.BooleanField({ initial: false }),
|
||||
onlymodifier: new fields.BooleanField({ initial: false }),
|
||||
bashing: new fields.BooleanField({ initial: false }),
|
||||
throwable: new fields.BooleanField({ initial: false }),
|
||||
damageReroll1: new fields.BooleanField({ initial: false }),
|
||||
|
||||
// Weapon stats
|
||||
attackAttribute: new fields.StringField({ initial: "vigor" }),
|
||||
attackAptitude: new fields.StringField({ initial: "melee" }),
|
||||
attackModifiers: new fields.NumberField({ ...nullableNumber }),
|
||||
weaponSize: new fields.StringField({ initial: "unarmed" }),
|
||||
damage: new fields.StringField({ initial: "0" }),
|
||||
damageAttribute: new fields.StringField({ initial: "" }),
|
||||
damageModifiers: new fields.NumberField({ ...nullableNumber }),
|
||||
damageMultiplier: new fields.StringField({ initial: "1" }),
|
||||
range: new fields.NumberField({ ...nullableNumber }),
|
||||
reload: new fields.NumberField({ ...nullableNumber }),
|
||||
|
||||
// Protection
|
||||
armorQuality: new fields.StringField({ initial: "" }),
|
||||
soak: new fields.SchemaField({
|
||||
formula: new fields.StringField({ initial: "" }),
|
||||
value: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
modifier: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
}),
|
||||
blocking: new fields.SchemaField({
|
||||
malus: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
blocking1: new fields.BooleanField({ initial: false }),
|
||||
blockingAll: new fields.BooleanField({ initial: false }),
|
||||
}),
|
||||
modifiers: new fields.SchemaField({
|
||||
init: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
agility: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
powercost: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
social: new fields.BooleanField({ initial: false }),
|
||||
}),
|
||||
|
||||
// Spell
|
||||
circle: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
difficulty: new fields.StringField({ initial: "" }),
|
||||
ppcost: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
duration: new fields.StringField({ initial: "" }),
|
||||
nbmandatoryconditions: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
mandatoryconditions: new fields.ArrayField(new fields.StringField()),
|
||||
optionnalconditions: new fields.ArrayField(new fields.StringField()),
|
||||
|
||||
// Alchemy
|
||||
alchemytype: new fields.StringField({ initial: "" }),
|
||||
pccost: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
pccurrent: new fields.NumberField({ initial: 0, nullable: true }),
|
||||
|
||||
// Vehicle weapon
|
||||
isfiredamage: new fields.BooleanField({ initial: false }),
|
||||
ishulldamage: new fields.BooleanField({ initial: false }),
|
||||
iscrewdamage: new fields.BooleanField({ initial: false }),
|
||||
isboarding: new fields.BooleanField({ initial: false }),
|
||||
isspur: new fields.BooleanField({ initial: false }),
|
||||
isbreakrow: new fields.BooleanField({ initial: false }),
|
||||
}),
|
||||
|
||||
// Equipment fields
|
||||
quantity: new fields.NumberField({ ...requiredInteger, initial: 1 }),
|
||||
weight: new fields.NumberField({ initial: 0 }),
|
||||
price: new fields.NumberField({ initial: 0 }),
|
||||
worn: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Item"];
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Data model for Vehicle actors
|
||||
*/
|
||||
export default class BoLVehicleDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const requiredInteger = { required: true, nullable: false, integer: true };
|
||||
|
||||
return {
|
||||
vehicletype: new fields.StringField({ initial: "boat" }),
|
||||
|
||||
attributes: new fields.SchemaField({
|
||||
hull: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "hull" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.hull" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
crew: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "crew" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.crew" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "resources" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.resources" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
})
|
||||
}),
|
||||
|
||||
row: new fields.SchemaField({
|
||||
key: new fields.StringField({ initial: "row" }),
|
||||
label: new fields.StringField({ initial: "BOL.attributes.row" }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
|
||||
}),
|
||||
|
||||
spur: new fields.SchemaField({
|
||||
value: new fields.StringField({ initial: "" })
|
||||
}),
|
||||
|
||||
status: new fields.SchemaField({}),
|
||||
|
||||
description: new fields.HTMLField({ initial: "" })
|
||||
};
|
||||
}
|
||||
|
||||
static LOCALIZATION_PREFIXES = ["BOL.Vehicle"];
|
||||
}
|
||||
@@ -237,11 +237,6 @@ export class BoLUtility {
|
||||
if (chatData.img.includes("/blank.png")) {
|
||||
chatData.img = null;
|
||||
}
|
||||
// For old-format weapon items lacking stat fields, apply defaults so the chat card can display them
|
||||
if (chatData.system?.properties?.weapon && !chatData.system.properties.damage) {
|
||||
const defaults = game.bol.config.defaultNaturalWeapon?.properties ?? {};
|
||||
chatData.system.properties = Object.assign(foundry.utils.duplicate(defaults), chatData.system.properties);
|
||||
}
|
||||
// JSON object for easy creation
|
||||
chatData.jsondata = JSON.stringify(
|
||||
{
|
||||
@@ -249,7 +244,7 @@ export class BoLUtility {
|
||||
payload: chatData,
|
||||
});
|
||||
|
||||
foundry.applications.handlebars.renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => {
|
||||
renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => {
|
||||
let chatOptions = BoLUtility.chatDataSetup(html);
|
||||
ChatMessage.create(chatOptions)
|
||||
});
|
||||
@@ -395,16 +390,6 @@ export class BoLUtility {
|
||||
BoLUtility.sendAttackSuccess(rollData)
|
||||
});
|
||||
|
||||
html.on("click", '.chat-target-select', event => {
|
||||
event.preventDefault()
|
||||
const btn = event.currentTarget
|
||||
let rollData = BoLUtility.getRollDataFromMessage(event)
|
||||
rollData.targetId = btn.dataset.tokenId
|
||||
rollData.defenderId = btn.dataset.actorId
|
||||
BoLUtility.cleanupButtons(rollData.applyId)
|
||||
BoLUtility.sendAttackSuccess(rollData)
|
||||
});
|
||||
|
||||
html.on("click", '.chat-damage-roll', event => {
|
||||
event.preventDefault()
|
||||
let rollData = BoLUtility.getRollDataFromMessage(event)
|
||||
@@ -497,21 +482,20 @@ export class BoLUtility {
|
||||
|
||||
if (defenseMode == 'damage-with-armor') {
|
||||
let armorFormula = defender.getArmorFormula()
|
||||
if (armorFormula === "0") {
|
||||
rollData.armorProtect = 0
|
||||
} else {
|
||||
armorFormula = String(armorFormula) || "0"
|
||||
if (armorFormula && armorFormula != "0" ) {
|
||||
rollData.rollArmor = new Roll(armorFormula)
|
||||
await rollData.rollArmor.roll()
|
||||
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula })
|
||||
if (game.dice3d && msg) {
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula });
|
||||
if (game.dice3d) { // wait animation end when DsN is there
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id);
|
||||
}
|
||||
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
|
||||
}
|
||||
rollData.finalDamage = rollData.damageTotal - rollData.armorProtect
|
||||
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
|
||||
await defender.sufferDamage(rollData.finalDamage)
|
||||
console.log("Armor roll -> result ", rollData)
|
||||
}
|
||||
if (defenseMode == 'damage-without-armor') {
|
||||
rollData.finalDamage = rollData.damageTotal
|
||||
@@ -519,17 +503,9 @@ export class BoLUtility {
|
||||
}
|
||||
if (defenseMode == 'hero-reduce-damage') {
|
||||
let armorFormula = defender.getArmorFormula()
|
||||
if (armorFormula === "0") {
|
||||
rollData.armorProtect = 0
|
||||
} else {
|
||||
rollData.rollArmor = new Roll(armorFormula)
|
||||
await rollData.rollArmor.roll()
|
||||
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula })
|
||||
if (game.dice3d && msg) {
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||
}
|
||||
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
|
||||
}
|
||||
rollData.rollHero = new Roll("1d6")
|
||||
await rollData.rollHero.roll()
|
||||
rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect
|
||||
@@ -564,13 +540,13 @@ export class BoLUtility {
|
||||
ChatMessage.create({
|
||||
alias: defender.name,
|
||||
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults)
|
||||
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults)
|
||||
})
|
||||
console.log("Defender data : ", defenderUser)
|
||||
ChatMessage.create({
|
||||
alias: defender.name,
|
||||
whisper: BoLUtility.getOtherWhisperRecipients(defenderUser?.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults)
|
||||
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -657,7 +633,7 @@ export class BoLUtility {
|
||||
let msg = await ChatMessage.create({
|
||||
alias: defender.name,
|
||||
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
|
||||
content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', {
|
||||
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', {
|
||||
attackId: rollData.id,
|
||||
attacker: rollData.attacker,
|
||||
defender: defender,
|
||||
|
||||
@@ -188,25 +188,25 @@ BOL.rangeModifiers = {
|
||||
"-8": "BOL.dialog.utmost"
|
||||
}
|
||||
|
||||
BOL.difficultyModifiers = [
|
||||
{ value: "-12", label: "BOL.dialog.divine" },
|
||||
{ value: "-11", label: "BOL.dialog.mythic11" },
|
||||
{ value: "-10", label: "BOL.dialog.mythic" },
|
||||
{ value: "-9", label: "BOL.dialog.heroic9" },
|
||||
{ value: "-8", label: "BOL.dialog.heroic" },
|
||||
{ value: "-7", label: "BOL.dialog.formidable7" },
|
||||
{ value: "-6", label: "BOL.dialog.formidable" },
|
||||
{ value: "-5", label: "BOL.dialog.demanding5" },
|
||||
{ value: "-4", label: "BOL.dialog.demanding" },
|
||||
{ value: "-3", label: "BOL.dialog.tough3" },
|
||||
{ value: "-2", label: "BOL.dialog.tough" },
|
||||
{ value: "-1", label: "BOL.dialog.hard" },
|
||||
{ value: "0", label: "BOL.dialog.moderate" },
|
||||
{ value: "1", label: "BOL.dialog.easy" },
|
||||
{ value: "2", label: "BOL.dialog.veryeasy" },
|
||||
{ value: "3", label: "BOL.dialog.soeasy3" },
|
||||
{ value: "4", label: "BOL.dialog.soeasy" },
|
||||
]
|
||||
BOL.difficultyModifiers = {
|
||||
"4": "BOL.dialog.soeasy",
|
||||
"3": "BOL.dialog.soeasy3",
|
||||
"2": "BOL.dialog.veryeasy",
|
||||
"1": "BOL.dialog.easy",
|
||||
"0": "BOL.dialog.moderate",
|
||||
"-1": "BOL.dialog.hard",
|
||||
"-2": "BOL.dialog.tough",
|
||||
"-3": "BOL.dialog.tough3",
|
||||
"-4": "BOL.dialog.demanding",
|
||||
"-5": "BOL.dialog.demanding5",
|
||||
"-6": "BOL.dialog.formidable",
|
||||
"-7": "BOL.dialog.formidable7",
|
||||
"-8": "BOL.dialog.heroic",
|
||||
"-9": "BOL.dialog.heroic9",
|
||||
"-10": "BOL.dialog.mythic",
|
||||
"-11": "BOL.dialog.mythic11",
|
||||
"-12": "BOL.dialog.divine"
|
||||
}
|
||||
|
||||
BOL.alchemyModifiers = {
|
||||
"2": "BOL.dialog.veryeasy",
|
||||
|
||||
@@ -1,228 +1,52 @@
|
||||
import {BoLRoll} from "../controllers/bol-rolls.js";
|
||||
|
||||
/**
|
||||
* BoL Macro API — accessible via game.bol.macros
|
||||
*
|
||||
* Usage examples (in a Foundry macro):
|
||||
*
|
||||
* game.bol.macros.rollAttribute("vigor")
|
||||
* game.bol.macros.rollAttribute("mind")
|
||||
*
|
||||
* game.bol.macros.rollAptitude("melee")
|
||||
* game.bol.macros.rollAptitude("ranged")
|
||||
* game.bol.macros.rollAptitude("def")
|
||||
* game.bol.macros.rollAptitude("init")
|
||||
*
|
||||
* game.bol.macros.rollWeapon("Épée courte") // by name (partial match)
|
||||
* game.bol.macros.rollWeapon(0) // by index (first weapon)
|
||||
*
|
||||
* game.bol.macros.rollSpell("Boule de feu") // by name (partial match)
|
||||
* game.bol.macros.rollSpell(0) // by index
|
||||
*
|
||||
* game.bol.macros.rollAlchemy("Potion de soin") // by name (partial match)
|
||||
* game.bol.macros.rollAlchemy(0) // by index
|
||||
*
|
||||
* game.bol.macros.rollHoroscope("minor")
|
||||
* game.bol.macros.rollHoroscope("major")
|
||||
*
|
||||
* // Generic dispatcher:
|
||||
* game.bol.macros.roll("attribute", "vigor")
|
||||
* game.bol.macros.roll("aptitude", "melee")
|
||||
* game.bol.macros.roll("weapon", "Épée courte")
|
||||
* game.bol.macros.roll("spell", "Boule de feu")
|
||||
* game.bol.macros.roll("alchemy", 0)
|
||||
* game.bol.macros.roll("horoscope", "minor")
|
||||
*/
|
||||
export class Macros {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Resolves the actor for macro use:
|
||||
* - If multiple tokens are selected → error (always)
|
||||
* - If exactly one token is selected → use it (GM or player)
|
||||
* - If no token selected and user is GM → error (GM must select a token)
|
||||
* - If no token selected and user is a player → use their assigned character
|
||||
* @returns {Actor|null}
|
||||
* @name getSpeakersActor
|
||||
* @description
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
static getSpeakersActor() {
|
||||
const tokens = canvas.tokens?.controlled ?? []
|
||||
|
||||
static getSpeakersActor = function(){
|
||||
// Vérifie qu'un seul token est sélectionné
|
||||
const tokens = canvas.tokens.controlled;
|
||||
if (tokens.length > 1) {
|
||||
ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected'))
|
||||
return null
|
||||
ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected'));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tokens.length === 1) {
|
||||
return tokens[0].actor ?? null
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
let actor;
|
||||
// Si un token est sélectionné, le prendre comme acteur cible
|
||||
if (speaker.token) actor = game.actors.tokens[speaker.token];
|
||||
// Sinon prendre l'acteur par défaut pour l'utilisateur courrant
|
||||
if (!actor) actor = game.actors.get(speaker.actor);
|
||||
return actor;
|
||||
}
|
||||
|
||||
static rollMacro = async function (rollType, key, adv, mod){
|
||||
const actor = this.getSpeakersActor();
|
||||
// Several tokens selected
|
||||
if (actor === null) return;
|
||||
// No token selected
|
||||
if (game.user.isGM) {
|
||||
ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
|
||||
return null
|
||||
}
|
||||
if (actor === undefined) return ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"));
|
||||
|
||||
// Player: fall back to their assigned character
|
||||
const actor = game.user.character
|
||||
if (!actor) {
|
||||
ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
|
||||
return null
|
||||
}
|
||||
return actor
|
||||
}
|
||||
const actorData = {};
|
||||
actorData.data = {
|
||||
features : actor.buildFeatures()
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Finds an item on an actor by name (partial, case-insensitive) or index.
|
||||
* @param {Actor} actor
|
||||
* @param {string} type - item type: "weapon", "spell", "alchemy"
|
||||
* @param {string|number} nameOrIndex
|
||||
* @returns {object|undefined}
|
||||
*/
|
||||
static _findItem(actor, type, nameOrIndex) {
|
||||
const items = actor.items.filter(i => i.type === type)
|
||||
if (items.length === 0) {
|
||||
ui.notifications.warn(`${actor.name} : aucun(e) ${type} trouvé(e).`)
|
||||
return undefined
|
||||
if(rollType === "attribute") {
|
||||
let attribute = eval(`actor.system.attributes.${key}`);
|
||||
let rollLabel = (attribute.label) ? game.i18n.localize(attribute.label) : null;
|
||||
let description = actor.name + " - " + game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label) ;
|
||||
BoLRoll.attributeRollDialog(actor, actorData, attribute, rollLabel, description, adv, mod);
|
||||
}
|
||||
if (nameOrIndex === undefined || nameOrIndex === null) {
|
||||
if (items.length === 1) return foundry.utils.duplicate(items[0])
|
||||
const names = items.map((it, i) => `[${i}] ${it.name}`).join(', ')
|
||||
ui.notifications.warn(`Précisez le nom ou l'index : ${names}`)
|
||||
return undefined
|
||||
}
|
||||
if (typeof nameOrIndex === "number") {
|
||||
const item = items[nameOrIndex]
|
||||
if (!item) {
|
||||
ui.notifications.warn(`${actor.name} : index ${nameOrIndex} invalide pour ${type}.`)
|
||||
return undefined
|
||||
}
|
||||
return foundry.utils.duplicate(item)
|
||||
}
|
||||
const lower = String(nameOrIndex).toLowerCase()
|
||||
const found = items.find(i => i.name.toLowerCase().includes(lower))
|
||||
if (!found) {
|
||||
ui.notifications.warn(`${actor.name} : ${type} "${nameOrIndex}" introuvable.`)
|
||||
return undefined
|
||||
}
|
||||
return foundry.utils.duplicate(found)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll an attribute check.
|
||||
* @param {string} key - "vigor" | "agility" | "mind" | "appeal"
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollAttribute(key = "vigor", actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
if (!actor.system.attributes[key]) {
|
||||
ui.notifications.warn(`Attribut inconnu : "${key}". Valeurs : vigor, agility, mind, appeal`)
|
||||
return
|
||||
}
|
||||
return BoLRoll.attributeCheck(actor, key)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll an aptitude check.
|
||||
* @param {string} key - "init" | "melee" | "ranged" | "def"
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollAptitude(key = "melee", actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
if (!actor.system.aptitudes[key]) {
|
||||
ui.notifications.warn(`Aptitude inconnue : "${key}". Valeurs : init, melee, ranged, def`)
|
||||
return
|
||||
}
|
||||
return BoLRoll.aptitudeCheck(actor, key)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll a weapon attack.
|
||||
* @param {string|number} [nameOrIndex] - weapon name (partial) or index. Defaults to first weapon if only one.
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollWeapon(nameOrIndex = undefined, actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
const weapon = this._findItem(actor, "weapon", nameOrIndex)
|
||||
if (!weapon) return
|
||||
return BoLRoll.weaponCheckWithWeapon(actor, weapon)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll a spell check.
|
||||
* @param {string|number} [nameOrIndex] - spell name (partial) or index
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollSpell(nameOrIndex = undefined, actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
if ((actor.system.resources.power?.value ?? 1) <= 0) {
|
||||
ui.notifications.warn("Plus assez de points de Pouvoir !")
|
||||
return
|
||||
}
|
||||
const spell = this._findItem(actor, "spell", nameOrIndex)
|
||||
if (!spell) return
|
||||
return BoLRoll.spellCheckWithSpell(actor, spell)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll an alchemy check.
|
||||
* @param {string|number} [nameOrIndex] - alchemy item name (partial) or index
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollAlchemy(nameOrIndex = undefined, actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
const alchemy = this._findItem(actor, "alchemy", nameOrIndex)
|
||||
if (!alchemy) return
|
||||
return BoLRoll.alchemyCheckWithItem(actor, alchemy)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Roll a horoscope check.
|
||||
* @param {"minor"|"major"} [type="minor"]
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static rollHoroscope(type = "minor", actor = undefined) {
|
||||
actor = actor ?? this.getSpeakersActor()
|
||||
if (!actor) return
|
||||
return BoLRoll.horoscopeCheck(actor, undefined, type)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* Generic roll dispatcher.
|
||||
* @param {"attribute"|"aptitude"|"weapon"|"spell"|"alchemy"|"horoscope"} type
|
||||
* @param {string|number} [key] - attribute/aptitude key, item name/index, or horoscope type
|
||||
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
||||
*/
|
||||
static roll(type, key = undefined, actor = undefined) {
|
||||
switch (type) {
|
||||
case "attribute": return this.rollAttribute(key ?? "vigor", actor)
|
||||
case "aptitude": return this.rollAptitude(key ?? "melee", actor)
|
||||
case "weapon": return this.rollWeapon(key, actor)
|
||||
case "spell": return this.rollSpell(key, actor)
|
||||
case "alchemy": return this.rollAlchemy(key, actor)
|
||||
case "horoscope": return this.rollHoroscope(key ?? "minor", actor)
|
||||
default:
|
||||
ui.notifications.warn(`Type de jet inconnu : "${type}". Types valides : attribute, aptitude, weapon, spell, alchemy, horoscope`)
|
||||
else if(rollType === "aptitude") {
|
||||
let aptitude = eval(`actor.system.aptitudes.${key}`);
|
||||
let rollLabel = (aptitude.label) ? game.i18n.localize(aptitude.label) : null;
|
||||
let description = actor.name + " - " + game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label) ;
|
||||
BoLRoll.aptitudeRollDialog(actor, actorData, aptitude, rollLabel, description, adv, mod);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
// Kept for backward-compat (previously called by old macros)
|
||||
static rollMacro = async function (rollType, key) {
|
||||
const actor = Macros.getSpeakersActor()
|
||||
if (!actor) return
|
||||
return Macros.roll(rollType, key, actor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ export const preloadHandlebarsTemplates = async function () {
|
||||
"systems/bol/templates/chat/rolls/alchemy-roll-card.hbs",
|
||||
"systems/bol/templates/chat/rolls/selected-horoscope-roll-card.hbs",
|
||||
"systems/bol/templates/chat/rolls/horoscope-roll-card.hbs",
|
||||
"systems/bol/templates/chat/chat-welcome.hbs",
|
||||
"systems/bol/templates/dialogs/aptitude-roll-part.hbs",
|
||||
"systems/bol/templates/dialogs/attribute-roll-part.hbs",
|
||||
"systems/bol/templates/dialogs/mod-roll-part.hbs",
|
||||
@@ -63,8 +62,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||
"systems/bol/templates/dialogs/flaws-roll-part.hbs",
|
||||
"systems/bol/templates/dialogs/total-roll-part.hbs",
|
||||
"systems/bol/templates/dialogs/fightoptions-roll-part.hbs",
|
||||
"systems/bol/templates/dialogs/horoscope-roll-part.hbs",
|
||||
"systems/bol/templates/apps/character-summary-template.html"
|
||||
"systems/bol/templates/dialogs/horoscope-roll-part.hbs"
|
||||
];
|
||||
|
||||
// Load the template parts
|
||||
|
||||
BIN
packs/aides-de-jeu/000977.ldb
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001138
|
||||
MANIFEST-000990
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.733439 7ff3bebfd6c0 Recovering log #1135
|
||||
2026/04/11-21:28:04.743122 7ff3bebfd6c0 Delete type=3 #1133
|
||||
2026/04/11-21:28:04.743177 7ff3bebfd6c0 Delete type=0 #1135
|
||||
2026/04/11-21:28:49.377435 7ff3bdbfb6c0 Level-0 table #1141: started
|
||||
2026/04/11-21:28:49.377469 7ff3bdbfb6c0 Level-0 table #1141: 0 bytes OK
|
||||
2026/04/11-21:28:49.413622 7ff3bdbfb6c0 Delete type=0 #1139
|
||||
2026/04/11-21:28:49.456441 7ff3bdbfb6c0 Manual compaction at level-0 from '!journal!6cCdSvQgEHJ1bvX4' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.729596 7f14da7fc6c0 Recovering log #988
|
||||
2026/01/19-19:47:49.739434 7f14da7fc6c0 Delete type=3 #986
|
||||
2026/01/19-19:47:49.739512 7f14da7fc6c0 Delete type=0 #988
|
||||
2026/01/19-20:19:02.579851 7f1243fff6c0 Level-0 table #993: started
|
||||
2026/01/19-20:19:02.579872 7f1243fff6c0 Level-0 table #993: 0 bytes OK
|
||||
2026/01/19-20:19:02.585797 7f1243fff6c0 Delete type=0 #991
|
||||
2026/01/19-20:19:02.586018 7f1243fff6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
2026/04/11-21:19:06.264443 7ff3bf3fe6c0 Recovering log #1130
|
||||
2026/04/11-21:19:06.275289 7ff3bf3fe6c0 Delete type=3 #1128
|
||||
2026/04/11-21:19:06.275358 7ff3bf3fe6c0 Delete type=0 #1130
|
||||
2026/04/11-21:27:57.082652 7ff3bdbfb6c0 Level-0 table #1136: started
|
||||
2026/04/11-21:27:57.086593 7ff3bdbfb6c0 Level-0 table #1136: 2472 bytes OK
|
||||
2026/04/11-21:27:57.092696 7ff3bdbfb6c0 Delete type=0 #1134
|
||||
2026/04/11-21:27:57.111697 7ff3bdbfb6c0 Manual compaction at level-0 from '!journal!6cCdSvQgEHJ1bvX4' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at '!journal.pages!8ihDiCxC47fcdKVA.MOWru5Dbvs4iozXm' @ 325 : 1
|
||||
2026/04/11-21:27:57.111705 7ff3bdbfb6c0 Compacting 1@0 + 1@1 files
|
||||
2026/04/11-21:27:57.115555 7ff3bdbfb6c0 Generated table #1137@0: 23 keys, 27230 bytes
|
||||
2026/04/11-21:27:57.115568 7ff3bdbfb6c0 Compacted 1@0 + 1@1 files => 27230 bytes
|
||||
2026/04/11-21:27:57.121399 7ff3bdbfb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
|
||||
2026/04/11-21:27:57.121501 7ff3bdbfb6c0 Delete type=2 #1132
|
||||
2026/04/11-21:27:57.121625 7ff3bdbfb6c0 Delete type=2 #1136
|
||||
2026/04/11-21:27:57.121723 7ff3bdbfb6c0 Manual compaction at level-0 from '!journal.pages!8ihDiCxC47fcdKVA.MOWru5Dbvs4iozXm' @ 325 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.845406 7f14d8ff96c0 Recovering log #984
|
||||
2026/01/18-20:07:47.861224 7f14d8ff96c0 Delete type=3 #982
|
||||
2026/01/18-20:07:47.861280 7f14d8ff96c0 Delete type=0 #984
|
||||
2026/01/18-20:13:44.130598 7f1243fff6c0 Level-0 table #989: started
|
||||
2026/01/18-20:13:44.130625 7f1243fff6c0 Level-0 table #989: 0 bytes OK
|
||||
2026/01/18-20:13:44.157241 7f1243fff6c0 Delete type=0 #987
|
||||
2026/01/18-20:13:44.192634 7f1243fff6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/aides-de-jeu/MANIFEST-000990
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001039
|
||||
MANIFEST-000895
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.708004 7ff3be3fc6c0 Recovering log #1037
|
||||
2026/04/11-21:28:04.718300 7ff3be3fc6c0 Delete type=3 #1035
|
||||
2026/04/11-21:28:04.718366 7ff3be3fc6c0 Delete type=0 #1037
|
||||
2026/04/11-21:28:49.305595 7ff3bdbfb6c0 Level-0 table #1042: started
|
||||
2026/04/11-21:28:49.305624 7ff3bdbfb6c0 Level-0 table #1042: 0 bytes OK
|
||||
2026/04/11-21:28:49.334673 7ff3bdbfb6c0 Delete type=0 #1040
|
||||
2026/04/11-21:28:49.456420 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.702991 7f14d97fa6c0 Recovering log #893
|
||||
2026/01/19-19:47:49.714316 7f14d97fa6c0 Delete type=3 #891
|
||||
2026/01/19-19:47:49.714383 7f14d97fa6c0 Delete type=0 #893
|
||||
2026/01/19-20:19:02.560145 7f1243fff6c0 Level-0 table #898: started
|
||||
2026/01/19-20:19:02.560173 7f1243fff6c0 Level-0 table #898: 0 bytes OK
|
||||
2026/01/19-20:19:02.566093 7f1243fff6c0 Delete type=0 #896
|
||||
2026/01/19-20:19:02.585964 7f1243fff6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.240951 7ff3be3fc6c0 Recovering log #1033
|
||||
2026/04/11-21:19:06.250426 7ff3be3fc6c0 Delete type=3 #1031
|
||||
2026/04/11-21:19:06.250488 7ff3be3fc6c0 Delete type=0 #1033
|
||||
2026/04/11-21:27:57.098718 7ff3bdbfb6c0 Level-0 table #1038: started
|
||||
2026/04/11-21:27:57.098740 7ff3bdbfb6c0 Level-0 table #1038: 0 bytes OK
|
||||
2026/04/11-21:27:57.104599 7ff3bdbfb6c0 Delete type=0 #1036
|
||||
2026/04/11-21:27:57.121701 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.807797 7f14d9ffb6c0 Recovering log #889
|
||||
2026/01/18-20:07:47.824331 7f14d9ffb6c0 Delete type=3 #887
|
||||
2026/01/18-20:07:47.824381 7f14d9ffb6c0 Delete type=0 #889
|
||||
2026/01/18-20:13:44.157350 7f1243fff6c0 Level-0 table #894: started
|
||||
2026/01/18-20:13:44.157372 7f1243fff6c0 Level-0 table #894: 0 bytes OK
|
||||
2026/01/18-20:13:44.192480 7f1243fff6c0 Delete type=0 #892
|
||||
2026/01/18-20:13:44.192647 7f1243fff6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/armors/MANIFEST-000895
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001133
|
||||
MANIFEST-000989
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.608888 7ff3be3fc6c0 Recovering log #1131
|
||||
2026/04/11-21:28:04.618597 7ff3be3fc6c0 Delete type=3 #1129
|
||||
2026/04/11-21:28:04.618657 7ff3be3fc6c0 Delete type=0 #1131
|
||||
2026/04/11-21:28:49.095706 7ff3bdbfb6c0 Level-0 table #1136: started
|
||||
2026/04/11-21:28:49.095741 7ff3bdbfb6c0 Level-0 table #1136: 0 bytes OK
|
||||
2026/04/11-21:28:49.132472 7ff3bdbfb6c0 Delete type=0 #1134
|
||||
2026/04/11-21:28:49.170383 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.594444 7f14d8ff96c0 Recovering log #987
|
||||
2026/01/19-19:47:49.605020 7f14d8ff96c0 Delete type=3 #985
|
||||
2026/01/19-19:47:49.605095 7f14d8ff96c0 Delete type=0 #987
|
||||
2026/01/19-20:19:02.521702 7f1243fff6c0 Level-0 table #992: started
|
||||
2026/01/19-20:19:02.521733 7f1243fff6c0 Level-0 table #992: 0 bytes OK
|
||||
2026/01/19-20:19:02.528339 7f1243fff6c0 Delete type=0 #990
|
||||
2026/01/19-20:19:02.534634 7f1243fff6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.144409 7ff3be3fc6c0 Recovering log #1127
|
||||
2026/04/11-21:19:06.153957 7ff3be3fc6c0 Delete type=3 #1125
|
||||
2026/04/11-21:19:06.154021 7ff3be3fc6c0 Delete type=0 #1127
|
||||
2026/04/11-21:27:57.045260 7ff3bdbfb6c0 Level-0 table #1132: started
|
||||
2026/04/11-21:27:57.045286 7ff3bdbfb6c0 Level-0 table #1132: 0 bytes OK
|
||||
2026/04/11-21:27:57.051070 7ff3bdbfb6c0 Delete type=0 #1130
|
||||
2026/04/11-21:27:57.057054 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.640110 7f14da7fc6c0 Recovering log #983
|
||||
2026/01/18-20:07:47.659075 7f14da7fc6c0 Delete type=3 #981
|
||||
2026/01/18-20:07:47.659147 7f14da7fc6c0 Delete type=0 #983
|
||||
2026/01/18-20:13:43.881522 7f1243fff6c0 Level-0 table #988: started
|
||||
2026/01/18-20:13:43.881548 7f1243fff6c0 Level-0 table #988: 0 bytes OK
|
||||
2026/01/18-20:13:43.921371 7f1243fff6c0 Delete type=0 #986
|
||||
2026/01/18-20:13:43.921513 7f1243fff6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/boons/MANIFEST-000989
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001132
|
||||
MANIFEST-000988
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.620767 7ff3bfbff6c0 Recovering log #1130
|
||||
2026/04/11-21:28:04.630558 7ff3bfbff6c0 Delete type=3 #1128
|
||||
2026/04/11-21:28:04.630621 7ff3bfbff6c0 Delete type=0 #1130
|
||||
2026/04/11-21:28:49.058278 7ff3bdbfb6c0 Level-0 table #1135: started
|
||||
2026/04/11-21:28:49.058305 7ff3bdbfb6c0 Level-0 table #1135: 0 bytes OK
|
||||
2026/04/11-21:28:49.095507 7ff3bdbfb6c0 Delete type=0 #1133
|
||||
2026/04/11-21:28:49.170373 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.609310 7f14da7fc6c0 Recovering log #986
|
||||
2026/01/19-19:47:49.619302 7f14da7fc6c0 Delete type=3 #984
|
||||
2026/01/19-19:47:49.619373 7f14da7fc6c0 Delete type=0 #986
|
||||
2026/01/19-20:19:02.515027 7f1243fff6c0 Level-0 table #991: started
|
||||
2026/01/19-20:19:02.515048 7f1243fff6c0 Level-0 table #991: 0 bytes OK
|
||||
2026/01/19-20:19:02.521555 7f1243fff6c0 Delete type=0 #989
|
||||
2026/01/19-20:19:02.534623 7f1243fff6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.155820 7ff3bfbff6c0 Recovering log #1126
|
||||
2026/04/11-21:19:06.166134 7ff3bfbff6c0 Delete type=3 #1124
|
||||
2026/04/11-21:19:06.166197 7ff3bfbff6c0 Delete type=0 #1126
|
||||
2026/04/11-21:27:57.031682 7ff3bdbfb6c0 Level-0 table #1131: started
|
||||
2026/04/11-21:27:57.031756 7ff3bdbfb6c0 Level-0 table #1131: 0 bytes OK
|
||||
2026/04/11-21:27:57.038391 7ff3bdbfb6c0 Delete type=0 #1129
|
||||
2026/04/11-21:27:57.057035 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.664234 7f14d8ff96c0 Recovering log #982
|
||||
2026/01/18-20:07:47.679427 7f14d8ff96c0 Delete type=3 #980
|
||||
2026/01/18-20:07:47.679484 7f14d8ff96c0 Delete type=0 #982
|
||||
2026/01/18-20:13:43.851971 7f1243fff6c0 Level-0 table #987: started
|
||||
2026/01/18-20:13:43.851994 7f1243fff6c0 Level-0 table #987: 0 bytes OK
|
||||
2026/01/18-20:13:43.881382 7f1243fff6c0 Delete type=0 #985
|
||||
2026/01/18-20:13:43.921506 7f1243fff6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/boonsflawscreatures/MANIFEST-000988
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001132
|
||||
MANIFEST-000988
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.644730 7ff3bfbff6c0 Recovering log #1130
|
||||
2026/04/11-21:28:04.655618 7ff3bfbff6c0 Delete type=3 #1128
|
||||
2026/04/11-21:28:04.655815 7ff3bfbff6c0 Delete type=0 #1130
|
||||
2026/04/11-21:28:49.132705 7ff3bdbfb6c0 Level-0 table #1135: started
|
||||
2026/04/11-21:28:49.132747 7ff3bdbfb6c0 Level-0 table #1135: 0 bytes OK
|
||||
2026/04/11-21:28:49.170220 7ff3bdbfb6c0 Delete type=0 #1133
|
||||
2026/04/11-21:28:49.170391 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.635172 7f14d8ff96c0 Recovering log #986
|
||||
2026/01/19-19:47:49.644889 7f14d8ff96c0 Delete type=3 #984
|
||||
2026/01/19-19:47:49.644941 7f14d8ff96c0 Delete type=0 #986
|
||||
2026/01/19-20:19:02.508864 7f1243fff6c0 Level-0 table #991: started
|
||||
2026/01/19-20:19:02.508913 7f1243fff6c0 Level-0 table #991: 0 bytes OK
|
||||
2026/01/19-20:19:02.514913 7f1243fff6c0 Delete type=0 #989
|
||||
2026/01/19-20:19:02.534608 7f1243fff6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.179631 7ff3bebfd6c0 Recovering log #1126
|
||||
2026/04/11-21:19:06.189451 7ff3bebfd6c0 Delete type=3 #1124
|
||||
2026/04/11-21:19:06.189498 7ff3bebfd6c0 Delete type=0 #1126
|
||||
2026/04/11-21:27:57.051143 7ff3bdbfb6c0 Level-0 table #1131: started
|
||||
2026/04/11-21:27:57.051164 7ff3bdbfb6c0 Level-0 table #1131: 0 bytes OK
|
||||
2026/04/11-21:27:57.056960 7ff3bdbfb6c0 Delete type=0 #1129
|
||||
2026/04/11-21:27:57.057062 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.704039 7f14d8ff96c0 Recovering log #982
|
||||
2026/01/18-20:07:47.721242 7f14d8ff96c0 Delete type=3 #980
|
||||
2026/01/18-20:07:47.721902 7f14d8ff96c0 Delete type=0 #982
|
||||
2026/01/18-20:13:43.777269 7f1243fff6c0 Level-0 table #987: started
|
||||
2026/01/18-20:13:43.777312 7f1243fff6c0 Level-0 table #987: 0 bytes OK
|
||||
2026/01/18-20:13:43.819988 7f1243fff6c0 Delete type=0 #985
|
||||
2026/01/18-20:13:43.921486 7f1243fff6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/careers/MANIFEST-000988
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001130
|
||||
MANIFEST-000986
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.822069 7ff3bebfd6c0 Recovering log #1128
|
||||
2026/04/11-21:28:04.831801 7ff3bebfd6c0 Delete type=3 #1126
|
||||
2026/04/11-21:28:04.831849 7ff3bebfd6c0 Delete type=0 #1128
|
||||
2026/04/11-21:28:49.681591 7ff3bdbfb6c0 Level-0 table #1133: started
|
||||
2026/04/11-21:28:49.681658 7ff3bdbfb6c0 Level-0 table #1133: 0 bytes OK
|
||||
2026/04/11-21:28:49.718700 7ff3bdbfb6c0 Delete type=0 #1131
|
||||
2026/04/11-21:28:49.761295 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.822315 7f14d9ffb6c0 Recovering log #984
|
||||
2026/01/19-19:47:49.833348 7f14d9ffb6c0 Delete type=3 #982
|
||||
2026/01/19-19:47:49.833401 7f14d9ffb6c0 Delete type=0 #984
|
||||
2026/01/19-20:19:02.619154 7f1243fff6c0 Level-0 table #989: started
|
||||
2026/01/19-20:19:02.619176 7f1243fff6c0 Level-0 table #989: 0 bytes OK
|
||||
2026/01/19-20:19:02.626822 7f1243fff6c0 Delete type=0 #987
|
||||
2026/01/19-20:19:02.639748 7f1243fff6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.351355 7ff3bfbff6c0 Recovering log #1124
|
||||
2026/04/11-21:19:06.361887 7ff3bfbff6c0 Delete type=3 #1122
|
||||
2026/04/11-21:19:06.361938 7ff3bfbff6c0 Delete type=0 #1124
|
||||
2026/04/11-21:27:57.154739 7ff3bdbfb6c0 Level-0 table #1129: started
|
||||
2026/04/11-21:27:57.154770 7ff3bdbfb6c0 Level-0 table #1129: 0 bytes OK
|
||||
2026/04/11-21:27:57.160975 7ff3bdbfb6c0 Delete type=0 #1127
|
||||
2026/04/11-21:27:57.175182 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:48.004635 7f14d9ffb6c0 Recovering log #980
|
||||
2026/01/18-20:07:48.021334 7f14d9ffb6c0 Delete type=3 #978
|
||||
2026/01/18-20:07:48.021403 7f14d9ffb6c0 Delete type=0 #980
|
||||
2026/01/18-20:13:44.336530 7f1243fff6c0 Level-0 table #985: started
|
||||
2026/01/18-20:13:44.336556 7f1243fff6c0 Level-0 table #985: 0 bytes OK
|
||||
2026/01/18-20:13:44.372711 7f1243fff6c0 Delete type=0 #983
|
||||
2026/01/18-20:13:44.502487 7f1243fff6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
|
||||
|
||||
BIN
packs/effets-exemples/MANIFEST-000986
Normal file
@@ -1 +1 @@
|
||||
MANIFEST-001133
|
||||
MANIFEST-000989
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:28:04.683176 7ff3bebfd6c0 Recovering log #1131
|
||||
2026/04/11-21:28:04.693337 7ff3bebfd6c0 Delete type=3 #1129
|
||||
2026/04/11-21:28:04.693387 7ff3bebfd6c0 Delete type=0 #1131
|
||||
2026/04/11-21:28:49.233539 7ff3bdbfb6c0 Level-0 table #1136: started
|
||||
2026/04/11-21:28:49.233570 7ff3bdbfb6c0 Level-0 table #1136: 0 bytes OK
|
||||
2026/04/11-21:28:49.271356 7ff3bdbfb6c0 Delete type=0 #1134
|
||||
2026/04/11-21:28:49.305506 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
|
||||
2026/01/19-19:47:49.675978 7f14d8ff96c0 Recovering log #987
|
||||
2026/01/19-19:47:49.685972 7f14d8ff96c0 Delete type=3 #985
|
||||
2026/01/19-19:47:49.686034 7f14d8ff96c0 Delete type=0 #987
|
||||
2026/01/19-20:19:02.553787 7f1243fff6c0 Level-0 table #992: started
|
||||
2026/01/19-20:19:02.553814 7f1243fff6c0 Level-0 table #992: 0 bytes OK
|
||||
2026/01/19-20:19:02.559887 7f1243fff6c0 Delete type=0 #990
|
||||
2026/01/19-20:19:02.560019 7f1243fff6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/04/11-21:19:06.215905 7ff3be3fc6c0 Recovering log #1127
|
||||
2026/04/11-21:19:06.225729 7ff3be3fc6c0 Delete type=3 #1125
|
||||
2026/04/11-21:19:06.225801 7ff3be3fc6c0 Delete type=0 #1127
|
||||
2026/04/11-21:27:57.070299 7ff3bdbfb6c0 Level-0 table #1132: started
|
||||
2026/04/11-21:27:57.070342 7ff3bdbfb6c0 Level-0 table #1132: 0 bytes OK
|
||||
2026/04/11-21:27:57.076462 7ff3bdbfb6c0 Delete type=0 #1130
|
||||
2026/04/11-21:27:57.082540 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
|
||||
2026/01/18-20:07:47.766393 7f14d8ff96c0 Recovering log #983
|
||||
2026/01/18-20:07:47.781538 7f14d8ff96c0 Delete type=3 #981
|
||||
2026/01/18-20:07:47.781589 7f14d8ff96c0 Delete type=0 #983
|
||||
2026/01/18-20:13:43.983716 7f1243fff6c0 Level-0 table #988: started
|
||||
2026/01/18-20:13:43.983740 7f1243fff6c0 Level-0 table #988: 0 bytes OK
|
||||
2026/01/18-20:13:44.023789 7f1243fff6c0 Delete type=0 #986
|
||||
2026/01/18-20:13:44.053229 7f1243fff6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
|
||||
|
||||