Compare commits

..

16 Commits

Author SHA1 Message Date
405bcd7cf5 AJout d'une aide pour les macros
All checks were successful
Release Creation / build (release) Successful in 55s
2026-04-11 21:29:40 +02:00
846e6f88b7 AJout d'une aide pour les macros 2026-04-11 21:29:06 +02:00
1a0f4cd3e9 Foundryv14 migration
All checks were successful
Release Creation / build (release) Successful in 44s
2026-04-01 23:01:25 +02:00
8ef5c3c516 Foundryv14 migration
All checks were successful
Release Creation / build (release) Successful in 52s
2026-04-01 22:59:46 +02:00
0eb952e43e BOL : Fix damage error, rework roll dialog and new damage targetting
All checks were successful
Release Creation / build (release) Successful in 56s
2026-03-16 20:19:33 +01:00
a549262d25 BOL : Fix damage error, rework roll dialog and new damage targetting 2026-03-16 20:16:58 +01:00
7adc1b3f07 Auto-release
All checks were successful
Release Creation / build (release) Successful in 2m22s
2026-03-01 09:41:06 +01:00
6c70dc147c DataModels + Appv2 migration : OK 2026-03-01 01:12:00 +01:00
1ffb8b08fc DataModels + Appv2 migration : OK 2026-02-28 21:00:06 +01:00
8017bb207d Datamodel + Appv2 migration, WIP 2026-01-13 08:10:04 +01:00
364278527d Datamodel + Appv2 migration, WIP 2026-01-13 08:09:11 +01:00
93d35abde2 Datamodel + Appv2 migration, WIP 2026-01-12 23:33:35 +01:00
0409be64eb Datamodel + Appv2 migration, WIP 2026-01-12 23:33:14 +01:00
ed76a49e7d Datamodel + Appv2 migration, WIP 2026-01-12 23:32:51 +01:00
2abd2c881a Sync bol compendiums 2026-01-12 23:01:53 +01:00
3fa5ca66d1 Minor fix for chronicles v1 2025-12-04 18:26:26 +01:00
283 changed files with 8952 additions and 2623 deletions

View File

@@ -12,15 +12,13 @@ jobs:
#- uses: actions/checkout@v3 #- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4 - uses: RouxAntoine/checkout@v3.5.4
with:
ref: "v13"
# get part of the tag after the `v` # get part of the tag after the `v`
- name: Extract tag version number - name: Extract tag version number
id: get_version id: get_version
uses: battila7/get-version-action@v2 uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the module.json # Substitute the Manifest and Download URLs in the system.json
- name: Substitute Manifest and Download Links For Versioned Ones - name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1 uses: microsoft/variable-substitution@v1
@@ -28,9 +26,9 @@ jobs:
files: "system.json" files: "system.json"
env: env:
version: ${{steps.get_version.outputs.version-without-v}} version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/public/bol url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/bol/releases/latest/system.json manifest: https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/public/bol/releases/download/${{github.event.release.tag_name}}/bol.zip download: https://www.uberwald.me/gitea/${{gitea.repository}}/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 # Create a zip file with all files required by the module to add to the release
- run: | - run: |
@@ -51,4 +49,15 @@ jobs:
files: |- files: |-
./bol.zip ./bol.zip
system.json system.json
api_key: "${{secrets.RELEASE_TOKEN_BOL}}" 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"

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ todo.md
/jsconfig.json /jsconfig.json
/package.json /package.json
/package-lock.json /package-lock.json
/.github

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,63 @@
{
"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 &amp; 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>"
}
}
}
}
}

View File

@@ -1,10 +1,34 @@
{ {
"label": "Weapons", "label": "Weapons",
"mapping": { "mapping": {
"description": "system.description" "description": "system.description"
}, },
"entries": { "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 lAxos": {
"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; its 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 dabordage 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 dabordage 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": { "Arbalète": {
"name": "Crossbow", "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>" "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>"

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
[Dolphin]
Timestamp=2024,11,2,20,30,2.2800000000000002
Version=4
ViewMode=1
VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails

View File

@@ -111,7 +111,7 @@
"BOL.ui.defender": "Verteidiger", "BOL.ui.defender": "Verteidiger",
"BOL.ui.difficulty": "Schwierigkeit", "BOL.ui.difficulty": "Schwierigkeit",
"BOL.ui.spellProperties": "Zaubereigenschaften", "BOL.ui.spellProperties": "Zaubereigenschaften",
"BOL.ui.duration": "Dauer", "BOL.ui.duration": "Dauer",
"BOL.ui.spellkeep": "Aufrechterhalten", "BOL.ui.spellkeep": "Aufrechterhalten",
"BOL.ui.concentrate": "Konzentrieren", "BOL.ui.concentrate": "Konzentrieren",
"BOL.ui.registerInit": "Register Init.", "BOL.ui.registerInit": "Register Init.",
@@ -134,7 +134,7 @@
"BOL.ui.makeAlchemy": "Alchemika herstellen", "BOL.ui.makeAlchemy": "Alchemika herstellen",
"BOL.ui.alchemyCostTotal": "Alchemiepunkte ingesamt", "BOL.ui.alchemyCostTotal": "Alchemiepunkte ingesamt",
"BOL.ui.alchemyInvest": "Alchemiepunkte investieren", "BOL.ui.alchemyInvest": "Alchemiepunkte investieren",
"BOL.ui.alchemyCurrent": "Aktuelle Alchemiepunkte in Objekten", "BOL.ui.alchemyCurrent": "Aktuelle Alchemiepunkte in Objekten",
"BOL.ui.advance": "Status", "BOL.ui.advance": "Status",
"BOL.ui.isadvantage": "Gibt zusätzlichen Würfel?", "BOL.ui.isadvantage": "Gibt zusätzlichen Würfel?",
"BOL.ui.isbonusdice": "Gibt zusätzlichen Würfel?", "BOL.ui.isbonusdice": "Gibt zusätzlichen Würfel?",
@@ -153,8 +153,8 @@
"BOL.ui.status": "Status", "BOL.ui.status": "Status",
"BOL.ui.toactivated": "Aktiv (>Deaktivieren)", "BOL.ui.toactivated": "Aktiv (>Deaktivieren)",
"BOL.ui.todeactivated": "Inaktiv (>Aktivieren)", "BOL.ui.todeactivated": "Inaktiv (>Aktivieren)",
"BOL.ui.armorAgiMalus": "Rüschtung+Schild-Malus (Geschick)", "BOL.ui.armorAgiMalus": "Rüschtung+Schild-Malus (Geschick)",
"BOL.ui.armorInitMalus": "Rüstungsmalus (Init)", "BOL.ui.armorInitMalus": "Rüstungsmalus (Init)",
"BOL.ui.attackValue": "Angriffswert", "BOL.ui.attackValue": "Angriffswert",
"BOL.ui.initMalus": "Init malus", "BOL.ui.initMalus": "Init malus",
"BOL.ui.createEquipment": "Create Equipment", "BOL.ui.createEquipment": "Create Equipment",
@@ -177,7 +177,7 @@
"BOL.featureSubtypes.fightOption": "Kampfoption", "BOL.featureSubtypes.fightOption": "Kampfoption",
"BOL.bougette.nomoney": "Mittellos", "BOL.bougette.nomoney": "Mittellos",
"BOL.bougette.tolive": "Zum Überleben", "BOL.bougette.tolive": "Zum Überleben",
"BOL.bougette.easylife": "Einfaches Leben", "BOL.bougette.easylife": "Einfaches Leben",
"BOL.bougette.luxury" : "Luxuriöses Leben", "BOL.bougette.luxury" : "Luxuriöses Leben",
"BOL.bougette.rich": "Reich!", "BOL.bougette.rich": "Reich!",
@@ -224,14 +224,14 @@
"BOL.protectionCategory.other": "Verschiedenes", "BOL.protectionCategory.other": "Verschiedenes",
"BOL.spellItem.charm": "Zauber", "BOL.spellItem.charm": "Zauber",
"BOL.spellItem.circle1": "Erster Kreis", "BOL.spellItem.circle1": "Erster Kreis",
"BOL.spellItem.circle2": "Zweiter Kreis", "BOL.spellItem.circle2": "Zweiter Kreis",
"BOL.spellItem.circle3": "Dritter Kreis", "BOL.spellItem.circle3": "Dritter Kreis",
"BOL.alchemyItem.common": "Häufig", "BOL.alchemyItem.common": "Häufig",
"BOL.alchemyItem.scarce": "Selten", "BOL.alchemyItem.scarce": "Selten",
"BOL.alchemyItem.legend": "Legendär", "BOL.alchemyItem.legend": "Legendär",
"BOL.alchemyItem.mythic": "Mystisch", "BOL.alchemyItem.mythic": "Mystisch",
"BOL.weaponCategory.melee": "Nahkampf", "BOL.weaponCategory.melee": "Nahkampf",
"BOL.weaponCategory.ranged": "Fernkampf", "BOL.weaponCategory.ranged": "Fernkampf",
@@ -290,6 +290,7 @@
"BOL.itemProperty.difficulty": "Schwierigkeit", "BOL.itemProperty.difficulty": "Schwierigkeit",
"BOL.itemProperty.natural": "Natürliche Waffe", "BOL.itemProperty.natural": "Natürliche Waffe",
"BOL.itemProperty.onlymodifier": "Nur Modifikator (d.h. Angriffe von Kreaturen)", "BOL.itemProperty.onlymodifier": "Nur Modifikator (d.h. Angriffe von Kreaturen)",
"BOL.itemProperty.attackMalusDice": "Angriffsmalus (Würfel)",
"BOL.itemStat.quantity": "Anzahl", "BOL.itemStat.quantity": "Anzahl",
"BOL.itemStat.weight": "Gewicht", "BOL.itemStat.weight": "Gewicht",
@@ -359,7 +360,7 @@
"BOL.ui.astrologerPoints": "Points d'Astrologie", "BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels", "BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?", "BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse", "BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)", "BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)", "BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes", "BOL.ui.horoscopes": "Horoscopes",
@@ -468,7 +469,7 @@
"BOL.chat.welcome4": "Im Discord findet ihr Support für die FoundryVTT-Implementierung dieses Systems: https://discord.gg/pPSDNJk", "BOL.chat.welcome4": "Im Discord findet ihr Support für die FoundryVTT-Implementierung dieses Systems: https://discord.gg/pPSDNJk",
"BOL.chat.welcome5": "Auf ein gutes Spiel in Lemuria!", "BOL.chat.welcome5": "Auf ein gutes Spiel in Lemuria!",
"BOL.chat.welcome6": "", "BOL.chat.welcome6": "",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !", "BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"BOL.settings.rollArmor": "Roll for armor", "BOL.settings.rollArmor": "Roll for armor",
"BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked", "BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",

View File

@@ -365,6 +365,7 @@
"BOL.itemProperty.difficulty": "Difficulty", "BOL.itemProperty.difficulty": "Difficulty",
"BOL.itemProperty.natural": "Natural weapon", "BOL.itemProperty.natural": "Natural weapon",
"BOL.itemProperty.onlymodifier": "Modifier only (ie creatures attacks)", "BOL.itemProperty.onlymodifier": "Modifier only (ie creatures attacks)",
"BOL.itemProperty.attackMalusDice": "Attack Malus (Dice)",
"BOL.itemStat.quantity": "Quantity", "BOL.itemStat.quantity": "Quantity",
"BOL.itemStat.weight": "Weight", "BOL.itemStat.weight": "Weight",
@@ -477,6 +478,7 @@
"BOL.chat.damageresult": "Damages of {name} : {total}", "BOL.chat.damageresult": "Damages of {name} : {total}",
"BOL.chat.damagetarget": "Target : {target}", "BOL.chat.damagetarget": "Target : {target}",
"BOL.chat.applydamagetotarget": "Apply damages to the target", "BOL.chat.applydamagetotarget": "Apply damages to the target",
"BOL.chat.selecttarget": "Choose a target:",
"BOL.chat.fightoption": "Combat options", "BOL.chat.fightoption": "Combat options",
"BOL.chat.reroll": "Reroll (1 HP)", "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.", "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.",
@@ -608,5 +610,10 @@
"BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo", "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.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.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.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"
} }

View File

@@ -132,7 +132,7 @@
"BOL.ui.defender": "Defensor", "BOL.ui.defender": "Defensor",
"BOL.ui.difficulty": "Dificultad", "BOL.ui.difficulty": "Dificultad",
"BOL.ui.spellProperties": "Propiedades Conjuro", "BOL.ui.spellProperties": "Propiedades Conjuro",
"BOL.ui.duration": "Duración", "BOL.ui.duration": "Duración",
"BOL.ui.spellkeep": "Mantenimiento", "BOL.ui.spellkeep": "Mantenimiento",
"BOL.ui.concentrate": "Concentración", "BOL.ui.concentrate": "Concentración",
"BOL.ui.registerInit": "Registrar Inic.", "BOL.ui.registerInit": "Registrar Inic.",
@@ -156,7 +156,7 @@
"BOL.ui.makeAlchemy": "Realizar Alquimia", "BOL.ui.makeAlchemy": "Realizar Alquimia",
"BOL.ui.alchemyCostTotal": "Coste Total Puntos Alquimia", "BOL.ui.alchemyCostTotal": "Coste Total Puntos Alquimia",
"BOL.ui.alchemyInvest": "Invertir Puntos Alquimia", "BOL.ui.alchemyInvest": "Invertir Puntos Alquimia",
"BOL.ui.alchemyCurrent": "Puntos Alquimia actuales en Objeto", "BOL.ui.alchemyCurrent": "Puntos Alquimia actuales en Objeto",
"BOL.ui.advance": "Estado", "BOL.ui.advance": "Estado",
"BOL.ui.isadvantage": "¿Da un dado ventaja?", "BOL.ui.isadvantage": "¿Da un dado ventaja?",
"BOL.ui.bonusmalus": "Ventaja/desventaja adicional", "BOL.ui.bonusmalus": "Ventaja/desventaja adicional",
@@ -245,7 +245,7 @@
"BOL.featureSubtypes.xplog": "Diario PX", "BOL.featureSubtypes.xplog": "Diario PX",
"BOL.bougette.nomoney": "Nada", "BOL.bougette.nomoney": "Nada",
"BOL.bougette.tolive": "Vivir justo", "BOL.bougette.tolive": "Vivir justo",
"BOL.bougette.easylife": "Vida simple", "BOL.bougette.easylife": "Vida simple",
"BOL.bougette.luxury" : "Vida lujosa", "BOL.bougette.luxury" : "Vida lujosa",
"BOL.bougette.rich": "¡Rico!", "BOL.bougette.rich": "¡Rico!",
@@ -293,15 +293,15 @@
"BOL.protectionCategory.helm": "Casco", "BOL.protectionCategory.helm": "Casco",
"BOL.protectionCategory.other": "Otro", "BOL.protectionCategory.other": "Otro",
"BOL.spellItem.charm": "Truco", "BOL.spellItem.charm": "Truco",
"BOL.spellItem.circle1": "Primer Círculo", "BOL.spellItem.circle1": "Primer Círculo",
"BOL.spellItem.circle2": "Segundo Círculo", "BOL.spellItem.circle2": "Segundo Círculo",
"BOL.spellItem.circle3": "Tercer Círculo", "BOL.spellItem.circle3": "Tercer Círculo",
"BOL.alchemyItem.common": "Común", "BOL.alchemyItem.common": "Común",
"BOL.alchemyItem.scarce": "Escaso", "BOL.alchemyItem.scarce": "Escaso",
"BOL.alchemyItem.legend": "Legendario", "BOL.alchemyItem.legend": "Legendario",
"BOL.alchemyItem.mythic": "Mítico", "BOL.alchemyItem.mythic": "Mítico",
"BOL.weaponCategory.melee": "Melé", "BOL.weaponCategory.melee": "Melé",
"BOL.weaponCategory.ranged": "Distancia", "BOL.weaponCategory.ranged": "Distancia",
@@ -359,6 +359,7 @@
"BOL.itemProperty.difficulty": "Dificultad", "BOL.itemProperty.difficulty": "Dificultad",
"BOL.itemProperty.natural": "Arma natural", "BOL.itemProperty.natural": "Arma natural",
"BOL.itemProperty.onlymodifier": "Sólo modificador (ej criatura)", "BOL.itemProperty.onlymodifier": "Sólo modificador (ej criatura)",
"BOL.itemProperty.attackMalusDice": "Dado Desventaja Ataque",
"BOL.itemStat.quantity": "Cantidad", "BOL.itemStat.quantity": "Cantidad",
"BOL.itemStat.weight": "Peso", "BOL.itemStat.weight": "Peso",
@@ -452,7 +453,7 @@
"BOL.size.colossal": "Colosal", "BOL.size.colossal": "Colosal",
"BOL.chat.fightactive": "¡Activa la opción de combate {foName} este asalto!", "BOL.chat.fightactive": "¡Activa la opción de combate {foName} este asalto!",
"BOL.chat.fightunactive": "¡Desactiva la opción de combate {foName} este asalto!", "BOL.chat.fightunactive": "¡Desactiva la opción de combate {foName} este asalto!",
"BOL.chat.isdead": "¡{name} esta muerto!", "BOL.chat.isdead": "¡{name} esta muerto!",
"BOL.chat.epitaph": "¡Guardar el honor de su nombre y su memoria!", "BOL.chat.epitaph": "¡Guardar el honor de su nombre y su memoria!",
"BOL.chat.vitalityzero": "Vitalidad de {name} es {hp}: ¡va a caer inconsciente!", "BOL.chat.vitalityzero": "Vitalidad de {name} es {hp}: ¡va a caer inconsciente!",
@@ -534,7 +535,7 @@
"BOL.chat.criticalinfo": "¡Esto es un éxito Asombroso o Legendario! Escoge tus opciones y efectos", "BOL.chat.criticalinfo": "¡Esto es un éxito Asombroso o Legendario! Escoge tus opciones y efectos",
"BOL.chat.criticalbuttonjournal": "Éxito Asombroso/Legendario", "BOL.chat.criticalbuttonjournal": "Éxito Asombroso/Legendario",
"BOL.chat.armorRoll": "Tirada de Armadura", "BOL.chat.armorRoll": "Tirada de Armadura",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !", "BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"BOL.dialog.soeasy": "Demasiado fácil (+4)", "BOL.dialog.soeasy": "Demasiado fácil (+4)",
"BOL.dialog.veryeasy": "Muy fácil (+2)", "BOL.dialog.veryeasy": "Muy fácil (+2)",
@@ -554,7 +555,7 @@
"BOL.dialog.long": "Larga (-2)", "BOL.dialog.long": "Larga (-2)",
"BOL.dialog.distant": "Distante (-4)", "BOL.dialog.distant": "Distante (-4)",
"BOL.dialog.extreme": "Extrema (-6)", "BOL.dialog.extreme": "Extrema (-6)",
"BOL.dialog.utmost": "Límite (-8)", "BOL.dialog.utmost": "Límite (-8)",
"BOL.ui.name": "Nombre", "BOL.ui.name": "Nombre",
"BOL.ui.xp": "Experiencia", "BOL.ui.xp": "Experiencia",

View File

@@ -394,7 +394,8 @@
"BOL.itemProperty.isboarding": "Abordage", "BOL.itemProperty.isboarding": "Abordage",
"BOL.itemProperty.isspur": "Eperonnage", "BOL.itemProperty.isspur": "Eperonnage",
"BOL.itemProperty.isbreakrow": "Briser les rames", "BOL.itemProperty.isbreakrow": "Briser les rames",
"BOL.itemProperty.attackMalusDice": "Malus d'attaque (Dés)",
"BOL.itemStat.quantity": "Quantité", "BOL.itemStat.quantity": "Quantité",
"BOL.itemStat.weight": "Poids", "BOL.itemStat.weight": "Poids",
"BOL.itemStat.price": "Prix", "BOL.itemStat.price": "Prix",
@@ -505,6 +506,7 @@
"BOL.chat.damageresult": "Dommages de {name} : {total}", "BOL.chat.damageresult": "Dommages de {name} : {total}",
"BOL.chat.damagetarget": "Cible : {target}", "BOL.chat.damagetarget": "Cible : {target}",
"BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible", "BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible",
"BOL.chat.selecttarget": "Choisir une cible :",
"BOL.chat.fightoption": "Option de combat", "BOL.chat.fightoption": "Option de combat",
"BOL.chat.reroll": "Relancer (1 P. Heroisme)", "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", "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",
@@ -649,5 +651,10 @@
"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)", "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.StatusProne": "A terre",
"EFFECT.StatusDead": "Mort" "EFFECT.StatusDead": "Mort",
"BOL.ui.charSummaryTitle": "Résumé des Personnages",
"BOL.ui.colGroupAttributes": "Attributs",
"BOL.ui.colGroupAptitudes": "Aptitudes",
"BOL.ui.colGroupResources": "Ressources"
} }

View File

@@ -7,6 +7,8 @@ import { BoLUtility } from "../system/bol-utility.js";
*/ */
export class BoLActor extends Actor { export class BoLActor extends Actor {
static _healthLock = new Set()
static async create(data, options) { static async create(data, options) {
// Case of compendium global import // Case of compendium global import
@@ -357,7 +359,7 @@ export class BoLActor extends Actor {
ChatMessage.create({ ChatMessage.create({
alias: this.name, alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state }) 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 })
}) })
} }
@@ -852,36 +854,42 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */ /*-------------------------------------------- */
async manageHealthState() { async manageHealthState() {
let hpID = "lastHP" + this.id if (BoLActor._healthLock.has(this.id)) return
let lastHP = await this.getFlag("world", hpID) BoLActor._healthLock.add(this.id)
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this try {
await this.setFlag("world", hpID, this.system.resources.hp.value) let hpID = "lastHP" + this.id
let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne")) let lastHP = await this.getFlag("world", hpID)
let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead")) if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
if (this.system.resources.hp.value <= 0) { await this.setFlag("world", hpID, this.system.resources.hp.value)
if (!prone) { let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne"))
await this.createEmbeddedDocuments("ActiveEffect", [ let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead"))
{ name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' } if (this.system.resources.hp.value <= 0) {
]) if (!prone) {
} await this.createEmbeddedDocuments("ActiveEffect", [
if (this.system.resources.hp.value < -5 && !dead) { { name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' }
await this.createEmbeddedDocuments("ActiveEffect", [ ])
{ name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' } }
]) if (this.system.resources.hp.value < -5 && !dead) {
} await this.createEmbeddedDocuments("ActiveEffect", [
ChatMessage.create({ { name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' }
alias: this.name, ])
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), }
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() }) ChatMessage.create({
}) alias: this.name,
} else { whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
if (prone) { 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() })
await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id]) })
} } else {
if (dead) { if (prone) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id]) await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id])
}
if (dead) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id])
}
} }
} }
} finally {
BoLActor._healthLock.delete(this.id)
} }
} }
@@ -904,7 +912,7 @@ export class BoLActor extends Actor {
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
alias: this.name, alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', { content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', {
name: this.name, name: this.name,
img: this.img, img: this.img,
actorId: this.id, actorId: this.id,

View File

@@ -0,0 +1,7 @@
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"

View File

@@ -0,0 +1,144 @@
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 })
})
})
}
}

View File

@@ -0,0 +1,307 @@
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
}

View File

@@ -0,0 +1,255 @@
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
}

View File

@@ -0,0 +1,39 @@
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
}
}

View File

@@ -0,0 +1,65 @@
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 })
})
})
}
}

View File

@@ -0,0 +1,38 @@
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
}
}

View File

@@ -0,0 +1,70 @@
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 })
})
})
}
}

View File

@@ -1,11 +1,9 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { BoLActor } from "./actor/actor.js" import { BoLActor } from "./actor/actor.js"
import { BoLActorSheet } from "./actor/actor-sheet.js" // AppV1 actor sheets kept for reference only (AppV2 used via sheets.* below)
import { BoLVehicleSheet } from "./actor/vehicle-sheet.js"
import { BoLHordeSheet } from "./actor/horde-sheet.js"
import { BoLItem } from "./item/item.js" import { BoLItem } from "./item/item.js"
import { BoLItemSheet } from "./item/item-sheet.js" // Note: Old BoLItemSheet (AppV1) is now replaced by AppV2 sheets
import { System, BOL } from "./system/config.js" import { System, BOL } from "./system/config.js"
import { preloadHandlebarsTemplates } from "./system/templates.js" import { preloadHandlebarsTemplates } from "./system/templates.js"
import { registerHandlebarsHelpers } from "./system/helpers.js" import { registerHandlebarsHelpers } from "./system/helpers.js"
@@ -18,6 +16,12 @@ import { BoLHotbar } from "./system/bol-hotbar.js"
import { BoLCommands } from "./system/bol-commands.js" import { BoLCommands } from "./system/bol-commands.js"
import { BoLRoll } from "./controllers/bol-rolls.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"
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.once('init', async function () { Hooks.once('init', async function () {
@@ -28,7 +32,9 @@ Hooks.once('init', async function () {
BoLRoll, BoLRoll,
BoLUtility, BoLUtility,
macros: Macros, macros: Macros,
config: BOL config: BOL,
models,
sheets
}; };
// Game socket // Game socket
@@ -47,17 +53,38 @@ Hooks.once('init', async function () {
// Define custom Entity classes // Define custom Entity classes
CONFIG.Actor.documentClass = BoLActor; 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.documentClass = BoLItem;
CONFIG.Item.dataModels = {
item: models.BoLItem,
feature: models.BoLFeature
}
CONFIG.Combat.documentClass = BoLCombatManager; CONFIG.Combat.documentClass = BoLCombatManager;
// Register sheet application classes // Register sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("bol", BoLActorSheet, { types: ["character", "encounter"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", BoLVehicleSheet, { types: ["vehicle"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", BoLHordeSheet, { types: ["horde"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLHordeSheet, { types: ["horde"], makeDefault: true })
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("bol", BoLItemSheet, { makeDefault: true }); 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
});
// Inot useful stuff // Inot useful stuff
BoLUtility.init() BoLUtility.init()
@@ -83,35 +110,22 @@ Hooks.once('init', async function () {
/* -------------------------------------------- */ /* -------------------------------------------- */
function welcomeMessage() { async function welcomeMessage() {
let content = `<div id="welcome-message-bol"><span class="rdd-roll-part"> const noRulebook = !game.modules.find(m => m.id === "bol-rulebook")
<strong>` + game.i18n.localize("BOL.chat.welcome1") + `</strong><p>` + const content = await foundry.applications.handlebars.renderTemplate(
game.i18n.localize("BOL.chat.welcome2") + "</p><p>" + "systems/bol/templates/chat/chat-welcome.hbs",
game.i18n.localize("BOL.chat.welcome3") + "</p><p>" + { noRulebook }
game.i18n.localize("BOL.chat.welcome4") + "</p><p>" + )
game.i18n.localize("BOL.chat.welcome5") + "</p>" + ChatMessage.create({ user: game.user.id, whisper: [game.user.id], content })
game.i18n.localize("BOL.chat.welcome6")
let rulebook = game.modules.find( m => m.id === "bol-rulebook") if (game.user.isGM && game.i18n.lang == 'en' && !game.modules.find(m => m.id == "babele")) {
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({ ChatMessage.create({
user: game.user.id, user: game.user.id,
whisper: [game.user.id], whisper: [game.user.id],
content: `<div id="welcome-message-bol"><span class="rdd-roll-part"> 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>`
<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.") ui.notifications.warn("WARNING ! English language selected, but babele module is not installed !<br>Please install babele from the module tab in Foundry interface.")
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View File

@@ -5,11 +5,6 @@ const _apt2attr = { init: "mind", melee: "agility", ranged: "agility", def: "vig
/* -------------------------------------------- */ /* -------------------------------------------- */
export class BoLRoll { export class BoLRoll {
/* -------------------------------------------- */
static options() {
return { classes: ["bol", "dialog"], width: 480, height: 'fit-content' };
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static getDefaultAttribute(key) { static getDefaultAttribute(key) {
return _apt2attr[key] return _apt2attr[key]
@@ -19,9 +14,9 @@ export class BoLRoll {
static updateApplicableEffects(rollData) { static updateApplicableEffects(rollData) {
let appEffects = [] let appEffects = []
for (let effect of rollData.bolEffects) { for (let effect of rollData.bolEffects) {
if ( (effect.system.properties.identifier == "always") || if ((effect.system.properties.identifier == "always") ||
(effect.system.properties.identifier.includes(rollData.attribute.key)) || (effect.system.properties.identifier.includes(rollData.attribute.key)) ||
(rollData.aptitude && effect.system.properties.identifier.includes(rollData.aptitude.key)) ){ (rollData.aptitude && effect.system.properties.identifier.includes(rollData.aptitude.key))) {
appEffects.push(effect) appEffects.push(effect)
} }
} }
@@ -76,7 +71,7 @@ export class BoLRoll {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static attributeCheck(actor, key="vigor", event=undefined, combatData=undefined) { static attributeCheck(actor, key = "vigor", event = undefined, combatData = undefined) {
let attribute = eval(`actor.system.attributes.${key}`) let attribute = eval(`actor.system.attributes.${key}`)
@@ -89,7 +84,7 @@ export class BoLRoll {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static aptitudeCheck(actor, key="init", event=undefined, combatData=undefined) { static aptitudeCheck(actor, key = "init", event = undefined, combatData = undefined) {
let aptitude = eval(`actor.system.aptitudes.${key}`) let aptitude = eval(`actor.system.aptitudes.${key}`)
let attrKey = this.getDefaultAttribute(key) let attrKey = this.getDefaultAttribute(key)
@@ -129,7 +124,7 @@ export class BoLRoll {
rangeMsg = "BOL.chat.range6" rangeMsg = "BOL.chat.range6"
} }
ChatMessage.create({ ChatMessage.create({
content: await renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', { content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', {
weapon: weapon, weapon: weapon,
attackerName: _token.actor.name, attackerName: _token.actor.name,
defenderName: target.actor.name, defenderName: target.actor.name,
@@ -196,6 +191,11 @@ export class BoLRoll {
return; return;
} }
alchemy = foundry.utils.duplicate(alchemy) alchemy = foundry.utils.duplicate(alchemy)
return this.alchemyCheckWithItem(actor, alchemy)
}
/* -------------------------------------------- */
static alchemyCheckWithItem(actor, alchemy) {
let alchemyData = alchemy.system let alchemyData = alchemy.system
if (alchemyData.properties.pccurrent < alchemyData.properties.pccost) { if (alchemyData.properties.pccurrent < alchemyData.properties.pccost) {
ui.notifications.warn("Pas assez de Points de Création investis dans la Préparation !") ui.notifications.warn("Pas assez de Points de Création investis dans la Préparation !")
@@ -308,26 +308,31 @@ export class BoLRoll {
// Final number of dices // Final number of dices
this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice) this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice)
// Bonus or Malus ? // Bonus or Malus ?
if (this.rollData.bmDice == 0) { const nbDiceEl = document.querySelector('#roll-nbdice')
$('#roll-nbdice').val("2") if (nbDiceEl) {
} else { if (this.rollData.bmDice == 0) {
let letter = (this.rollData.bmDice > 0) ? "B" : "M" nbDiceEl.value = "2"
$('#roll-nbdice').val("2 + " + String(Math.abs(this.rollData.bmDice)) + letter) } else {
let letter = (this.rollData.bmDice > 0) ? "B" : "M"
nbDiceEl.value = "2 + " + String(Math.abs(this.rollData.bmDice)) + letter
}
} }
let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue
if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) { if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) {
rollbase = "" rollbase = ""
} }
$('#roll-modifier').val(rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" + const modifierEl = document.querySelector('#roll-modifier')
if (modifierEl) modifierEl.value = rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" + 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 lits of applicable effects // Rebuild list of applicable effects
let selectEffects = "" let selectEffects = ""
for (let effect of this.rollData.bolApplicableEffects) { for (let effect of this.rollData.bolApplicableEffects) {
selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>` selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>`
} }
$('#applicable-effects').html(selectEffects) const effectsEl = document.querySelector('#applicable-effects')
if (effectsEl) effectsEl.innerHTML = selectEffects
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -360,46 +365,48 @@ export class BoLRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
static updateArmorMalus(rollData) { static updateArmorMalus(rollData) {
rollData.appliedArmorMalus = 0 rollData.appliedArmorMalus = 0
const agiEl = document.querySelector('#armor-agi-malus')
if (rollData.attribute.key == "agility") { if (rollData.attribute.key == "agility") {
$("#armor-agi-malus").show() if (agiEl) agiEl.style.display = ''
rollData.appliedArmorMalus += rollData.armorAgiMalus rollData.appliedArmorMalus += rollData.armorAgiMalus
} else { } else {
$("#armor-agi-malus").hide() if (agiEl) agiEl.style.display = 'none'
} }
const initEl = document.querySelector('#armor-init-malus')
if (rollData.aptitude && rollData.aptitude.key == "init") { if (rollData.aptitude && rollData.aptitude.key == "init") {
$("#armor-init-malus").show() if (initEl) initEl.style.display = ''
rollData.appliedArmorMalus += rollData.armorInitMalus rollData.appliedArmorMalus += rollData.armorInitMalus
} else { } else {
$("#armor-init-malus").hide() if (initEl) initEl.style.display = 'none'
} }
} }
/* ------------------------------ -------------- */ /* ------------------------------ -------------- */
static updatePPCost(rollData) { static updatePPCost(rollData) {
$('#ppcost').html(rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor)) const el = document.querySelector('#ppcost')
if (el) el.innerHTML = rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor)
} }
/* ------------------------------ -------------- */ /* ------------------------------ -------------- */
static rollDialogListener(html) { static rollDialogListener(html) {
this.updateTotalDice() this.updateTotalDice()
html.find('#optcond').change((event) => { // Dynamic change of PP cost of spell html.querySelector('#optcond')?.addEventListener('change', (event) => {
let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length) let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length)
this.rollData.ppCost = pp this.rollData.ppCost = pp
this.updatePPCost(this.rollData) this.updatePPCost(this.rollData)
}) })
html.find('#mod').change((event) => { html.querySelector('#mod')?.addEventListener('change', (event) => {
this.rollData.mod = Number(event.currentTarget.value) this.rollData.mod = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#modRanged').change((event) => { html.querySelector('#modRanged')?.addEventListener('change', (event) => {
this.rollData.modRanged = Number(event.currentTarget.value) this.rollData.modRanged = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#attr').change((event) => { html.querySelector('#attr')?.addEventListener('change', (event) => {
let attrKey = event.currentTarget.value let attrKey = event.currentTarget.value
let actor = BoLUtility.getActorFromRollData(this.rollData) let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.attribute = foundry.utils.duplicate(actor.system.attributes[attrKey]) this.rollData.attribute = foundry.utils.duplicate(actor.system.attributes[attrKey])
@@ -407,7 +414,7 @@ export class BoLRoll {
this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData) this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#apt').change((event) => { html.querySelector('#apt')?.addEventListener('change', (event) => {
let aptKey = event.currentTarget.value let aptKey = event.currentTarget.value
let actor = BoLUtility.getActorFromRollData(this.rollData) let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.aptitude = foundry.utils.duplicate(actor.system.aptitudes[aptKey]) this.rollData.aptitude = foundry.utils.duplicate(actor.system.aptitudes[aptKey])
@@ -416,65 +423,58 @@ export class BoLRoll {
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#applyShieldMalus').click((event) => { html.querySelector('#applyShieldMalus')?.addEventListener('click', (event) => {
if (event.currentTarget.checked) { this.rollData.shieldMalus = event.currentTarget.checked ? this.rollData.shieldAttackMalus : 0
this.rollData.shieldMalus = this.rollData.shieldAttackMalus
} else {
this.rollData.shieldMalus = 0
}
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#career').change((event) => { html.querySelector('#career')?.addEventListener('change', (event) => {
let careers = $('#career').val() let careers = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.careerBonus = (!careers || careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))) this.rollData.careerBonus = (!careers || careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)))
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#boon').change((event) => { html.querySelector('#boon')?.addEventListener('change', (event) => {
let boons = $('#boon').val() let boons = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.nbBoons = (!boons || boons.length == 0) ? 0 : boons.length this.rollData.nbBoons = (!boons || boons.length == 0) ? 0 : boons.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#flaw').change((event) => { html.querySelector('#flaw')?.addEventListener('change', (event) => {
let flaws = $('#flaw').val() let flaws = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.nbFlaws = (!flaws || flaws.length == 0) ? 0 : flaws.length this.rollData.nbFlaws = (!flaws || flaws.length == 0) ? 0 : flaws.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('.bdice').click((event) => { html.querySelectorAll('.bdice').forEach(el => el.addEventListener('click', (event) => {
this.rollData.mDice = 0 this.rollData.mDice = 0
this.rollData.bDice = Number(event.currentTarget.value) this.rollData.bDice = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) }))
html.find('.mdice').click((event) => { html.querySelectorAll('.mdice').forEach(el => el.addEventListener('click', (event) => {
this.rollData.bDice = 0 this.rollData.bDice = 0
this.rollData.mDice = Number(event.currentTarget.value) this.rollData.mDice = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) }))
html.find('#horoscope-bonus-applied').change((event) => { html.querySelector('#horoscope-bonus-applied')?.addEventListener('change', (event) => {
this.rollData.selectedHoroscope = [] this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) { for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)])) this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
} }
let horoscopes = $('#horoscope-bonus-applied').val() let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.querySelector('#horoscope-malus-applied')?.addEventListener('change', (event) => {
html.find('#horoscope-malus-applied').change((event) => {
this.rollData.selectedHoroscope = [] this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) { for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)])) this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
} }
let horoscopes = $('#horoscope-malus-applied').val() let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#horoscope-group-applied').change((event) => { html.querySelector('#horoscope-group-applied')?.addEventListener('change', (event) => {
this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value
this.updateTotalDice() this.updateTotalDice()
}) })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -482,10 +482,15 @@ export class BoLRoll {
if (rollData.mode == "weapon") { if (rollData.mode == "weapon") {
rollData.weaponModifier = rollData.weapon.system.properties.attackModifiers ?? 0 rollData.weaponModifier = rollData.weapon.system.properties.attackModifiers ?? 0
rollData.attackBonusDice = rollData.weapon.system.properties.attackBonusDice rollData.attackBonusDice = rollData.weapon.system.properties.attackBonusDice
rollData.attackMalusDice = rollData.weapon.system.properties.attackMalusDice
if (rollData.attackBonusDice) { if (rollData.attackBonusDice) {
rollData.adv = "1B" rollData.adv = "1B"
rollData.bDice = 1 rollData.bDice = 1
} }
if (rollData.attackMalusDice) {
rollData.adv = "1M"
rollData.mDice = 1
}
if (defender) { // If target is selected if (defender) { // If target is selected
rollData.defence = defender.defenseValue rollData.defence = defender.defenseValue
rollData.armorMalus = defender.armorMalusValue rollData.armorMalus = defender.armorMalusValue
@@ -534,6 +539,7 @@ export class BoLRoll {
rollData.id = foundry.utils.randomID(16) rollData.id = foundry.utils.randomID(16)
rollData.weaponModifier = 0 rollData.weaponModifier = 0
rollData.attackBonusDice = false rollData.attackBonusDice = false
rollData.attackMalusDice = false
rollData.armorMalus = 0 rollData.armorMalus = 0
// Specific stuff // Specific stuff
this.preProcessWeapon(rollData, defender) this.preProcessWeapon(rollData, defender)
@@ -546,41 +552,47 @@ export class BoLRoll {
} else { } else {
rollData.shieldMalus = 0 rollData.shieldMalus = 0
} }
// Save // Save & pre-initialize computed fields
this.rollData = rollData this.rollData = rollData
this.updateTotalDice()
console.log("ROLLDATA", rollData) console.log("ROLLDATA", rollData)
// Then display+process the dialog // Then display+process the dialog
const rollOptionContent = await foundry.applications.handlebars.renderTemplate(rollOptionTpl, rollData); const rollOptionContent = await foundry.applications.handlebars.renderTemplate(rollOptionTpl, rollData);
let d = new Dialog({ // Use Hooks to reliably get the rendered HTMLElement (renderDialogV2 receives (app, element, context))
title: rollData.label, Hooks.once('renderDialogV2', (app, element) => {
element.classList.add('bol');
this.rollDialogListener(element);
});
return foundry.applications.api.DialogV2.wait({
window: { title: rollData.label },
content: rollOptionContent, content: rollOptionContent,
rollData: rollData, rejectClose: false,
render: html => this.rollDialogListener(html), buttons: [
buttons: { {
cancel: { type: 'button',
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("BOL.ui.cancel"), label: game.i18n.localize("BOL.ui.cancel"),
callback: () => { icon: 'fas fa-times',
} action: 'cancel'
}, },
submit: { {
icon: '<i class="fas fa-check"></i>', type: 'submit',
label: game.i18n.localize("BOL.ui.submit"), label: game.i18n.localize("BOL.ui.submit"),
callback: (html) => { icon: 'fas fa-check',
action: 'submit',
callback: (event, button, dialog) => {
console.log("Submit Roll!!!!"); console.log("Submit Roll!!!!");
if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) { // Check PP available if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) {
ui.notifications.warn("Pas assez de Points de Pouvoir !") ui.notifications.warn("Pas assez de Points de Pouvoir !")
return return false
} }
rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ? $('#register-init').is(":checked") : false; rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ?
(dialog.element.querySelector('#register-init')?.checked ?? false) : false;
const isMalus = (rollData.bmDice < 0) const isMalus = (rollData.bmDice < 0)
let rollbase = rollData.attrValue + rollData.aptValue let rollbase = rollData.attrValue + rollData.aptValue
if (rollData.weapon?.system.properties.onlymodifier) { if (rollData.weapon?.system.properties.onlymodifier) rollbase = 0
rollbase = 0
}
let diceData = BoLUtility.getDiceData() let diceData = BoLUtility.getDiceData()
let malusInit = rollData.combatData?.malusInit || 0 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 const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus + rollData.effectModifier - malusInit
@@ -593,12 +605,8 @@ export class BoLRoll {
r.roll(); r.roll();
} }
} }
}, ]
default: onEnter, }, { classes: ['bol', 'dialog'], width: 480 });
close: () => { }
}, this.options());
return d.render(true);
} }
} }
@@ -634,7 +642,7 @@ export class BoLDefaultRoll {
const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b) const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b)
this.rollData.roll = r this.rollData.roll = r
this.rollData.isFumble = (diceTotal <= diceData.criticalFailureValue) this.rollData.isFumble = (diceTotal <= diceData.criticalFailureValue)
if ( this.rollData.isFumble ) { if (this.rollData.isFumble) {
this.rollData.isSuccess = false this.rollData.isSuccess = false
this.rollData.isCritical = false this.rollData.isCritical = false
this.rollData.isRealCritical = false this.rollData.isRealCritical = false
@@ -642,7 +650,7 @@ export class BoLDefaultRoll {
this.rollData.isFailure = true this.rollData.isFailure = true
} else { } else {
this.rollData.isCritical = (diceTotal >= diceData.criticalSuccessValue) this.rollData.isCritical = (diceTotal >= diceData.criticalSuccessValue)
if ( this.rollData.isCritical) { if (this.rollData.isCritical) {
this.rollData.isSuccess = true this.rollData.isSuccess = true
} else { } else {
this.rollData.isSuccess = (r.total >= diceData.successValue) this.rollData.isSuccess = (r.total >= diceData.successValue)
@@ -692,18 +700,15 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
async sendChatMessage() { async sendChatMessage() {
let actor = BoLUtility.getActorFromRollData(this.rollData) const actor = BoLUtility.getActorFromRollData(this.rollData)
this._buildChatMessage(this.rollData).then(async msgFlavor => { const rollMode = game.settings.get("core", "rollMode")
//console.log("MSG", msgFlavor ) const msgFlavor = await this._buildChatMessage(this.rollData)
let msg = await this.rollData.roll.toMessage({ const msg = await this.rollData.roll.toMessage({
user: game.user.id, flavor: msgFlavor,
rollMode: game.settings.get("core", "rollMode"), speaker: ChatMessage.getSpeaker({ actor: actor }),
flavor: msgFlavor, }, { rollMode })
speaker: ChatMessage.getSpeaker({ actor: actor }), 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)
})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -747,6 +752,12 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
async sendDamageMessage() { async sendDamageMessage() {
let actor = BoLUtility.getActorFromRollData(this.rollData) 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 => { this._buildDamageChatMessage(this.rollData).then(async msgFlavor => {
let msg = await this.rollData.damageRoll.toMessage({ let msg = await this.rollData.damageRoll.toMessage({
user: game.user.id, user: game.user.id,

View File

@@ -1,119 +0,0 @@
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()]);
});
}
}

View File

@@ -0,0 +1,6 @@
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"

192
module/models/character.mjs Normal file
View File

@@ -0,0 +1,192 @@
/**
* 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"];
}

173
module/models/encounter.mjs Normal file
View File

@@ -0,0 +1,173 @@
/**
* 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"];
}

55
module/models/feature.mjs Normal file
View File

@@ -0,0 +1,55 @@
/**
* 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"];
}

174
module/models/horde.mjs Normal file
View File

@@ -0,0 +1,174 @@
/**
* 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"];
}

113
module/models/item.mjs Normal file
View File

@@ -0,0 +1,113 @@
/**
* 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"];
}

55
module/models/vehicle.mjs Normal file
View File

@@ -0,0 +1,55 @@
/**
* 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"];
}

View File

@@ -237,6 +237,11 @@ export class BoLUtility {
if (chatData.img.includes("/blank.png")) { if (chatData.img.includes("/blank.png")) {
chatData.img = null; 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 // JSON object for easy creation
chatData.jsondata = JSON.stringify( chatData.jsondata = JSON.stringify(
{ {
@@ -244,7 +249,7 @@ export class BoLUtility {
payload: chatData, payload: chatData,
}); });
renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => { foundry.applications.handlebars.renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => {
let chatOptions = BoLUtility.chatDataSetup(html); let chatOptions = BoLUtility.chatDataSetup(html);
ChatMessage.create(chatOptions) ChatMessage.create(chatOptions)
}); });
@@ -390,6 +395,16 @@ export class BoLUtility {
BoLUtility.sendAttackSuccess(rollData) 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 => { html.on("click", '.chat-damage-roll', event => {
event.preventDefault() event.preventDefault()
let rollData = BoLUtility.getRollDataFromMessage(event) let rollData = BoLUtility.getRollDataFromMessage(event)
@@ -482,13 +497,17 @@ export class BoLUtility {
if (defenseMode == 'damage-with-armor') { if (defenseMode == 'damage-with-armor') {
let armorFormula = defender.getArmorFormula() let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula) if (armorFormula === "0") {
await rollData.rollArmor.roll() rollData.armorProtect = 0
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula }); } else {
if (game.dice3d) { // wait animation end when DsN is there rollData.rollArmor = new Roll(armorFormula)
await game.dice3d.waitFor3DAnimationByMessageID(msg.id); 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.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
rollData.finalDamage = rollData.damageTotal - rollData.armorProtect rollData.finalDamage = rollData.damageTotal - rollData.armorProtect
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
await defender.sufferDamage(rollData.finalDamage) await defender.sufferDamage(rollData.finalDamage)
@@ -500,9 +519,17 @@ export class BoLUtility {
} }
if (defenseMode == 'hero-reduce-damage') { if (defenseMode == 'hero-reduce-damage') {
let armorFormula = defender.getArmorFormula() let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula) if (armorFormula === "0") {
await rollData.rollArmor.roll() rollData.armorProtect = 0
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total } 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") rollData.rollHero = new Roll("1d6")
await rollData.rollHero.roll() await rollData.rollHero.roll()
rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect
@@ -537,13 +564,13 @@ export class BoLUtility {
ChatMessage.create({ ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults) content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults)
}) })
console.log("Defender data : ", defenderUser) console.log("Defender data : ", defenderUser)
ChatMessage.create({ ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getOtherWhisperRecipients(defenderUser?.name), whisper: BoLUtility.getOtherWhisperRecipients(defenderUser?.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults) content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults)
}) })
} }
} }
@@ -630,7 +657,7 @@ export class BoLUtility {
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', { content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', {
attackId: rollData.id, attackId: rollData.id,
attacker: rollData.attacker, attacker: rollData.attacker,
defender: defender, defender: defender,

View File

@@ -188,25 +188,25 @@ BOL.rangeModifiers = {
"-8": "BOL.dialog.utmost" "-8": "BOL.dialog.utmost"
} }
BOL.difficultyModifiers = { BOL.difficultyModifiers = [
"4": "BOL.dialog.soeasy", { value: "-12", label: "BOL.dialog.divine" },
"3": "BOL.dialog.soeasy3", { value: "-11", label: "BOL.dialog.mythic11" },
"2": "BOL.dialog.veryeasy", { value: "-10", label: "BOL.dialog.mythic" },
"1": "BOL.dialog.easy", { value: "-9", label: "BOL.dialog.heroic9" },
"0": "BOL.dialog.moderate", { value: "-8", label: "BOL.dialog.heroic" },
"-1": "BOL.dialog.hard", { value: "-7", label: "BOL.dialog.formidable7" },
"-2": "BOL.dialog.tough", { value: "-6", label: "BOL.dialog.formidable" },
"-3": "BOL.dialog.tough3", { value: "-5", label: "BOL.dialog.demanding5" },
"-4": "BOL.dialog.demanding", { value: "-4", label: "BOL.dialog.demanding" },
"-5": "BOL.dialog.demanding5", { value: "-3", label: "BOL.dialog.tough3" },
"-6": "BOL.dialog.formidable", { value: "-2", label: "BOL.dialog.tough" },
"-7": "BOL.dialog.formidable7", { value: "-1", label: "BOL.dialog.hard" },
"-8": "BOL.dialog.heroic", { value: "0", label: "BOL.dialog.moderate" },
"-9": "BOL.dialog.heroic9", { value: "1", label: "BOL.dialog.easy" },
"-10": "BOL.dialog.mythic", { value: "2", label: "BOL.dialog.veryeasy" },
"-11": "BOL.dialog.mythic11", { value: "3", label: "BOL.dialog.soeasy3" },
"-12": "BOL.dialog.divine" { value: "4", label: "BOL.dialog.soeasy" },
} ]
BOL.alchemyModifiers = { BOL.alchemyModifiers = {
"2": "BOL.dialog.veryeasy", "2": "BOL.dialog.veryeasy",

View File

@@ -1,52 +1,228 @@
import {BoLRoll} from "../controllers/bol-rolls.js"; 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 { export class Macros {
/**
* @name getSpeakersActor /* -------------------------------------------- */
* @description /**
* * Resolves the actor for macro use:
* @returns * - If multiple tokens are selected → error (always)
*/ * - If exactly one token is selected → use it (GM or player)
static getSpeakersActor = function(){ * - If no token selected and user is GM → error (GM must select a token)
// Vérifie qu'un seul token est sélectionné * - If no token selected and user is a player → use their assigned character
const tokens = canvas.tokens.controlled; * @returns {Actor|null}
if (tokens.length > 1) { */
ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected')); static getSpeakersActor() {
return null; const tokens = canvas.tokens?.controlled ?? []
}
if (tokens.length > 1) {
const speaker = ChatMessage.getSpeaker(); ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected'))
let actor; return null
// 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){ if (tokens.length === 1) {
const actor = this.getSpeakersActor(); return tokens[0].actor ?? null
// Several tokens selected
if (actor === null) return;
// No token selected
if (actor === undefined) return ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"));
const actorData = {};
actorData.data = {
features : actor.buildFeatures()
};
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);
}
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);
}
} }
// No token selected
if (game.user.isGM) {
ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
return null
}
// 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
}
/* -------------------------------------------- */
/**
* 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 (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`)
}
}
/* -------------------------------------------- */
// 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)
}
} }

View File

@@ -52,6 +52,7 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/chat/rolls/alchemy-roll-card.hbs", "systems/bol/templates/chat/rolls/alchemy-roll-card.hbs",
"systems/bol/templates/chat/rolls/selected-horoscope-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/rolls/horoscope-roll-card.hbs",
"systems/bol/templates/chat/chat-welcome.hbs",
"systems/bol/templates/dialogs/aptitude-roll-part.hbs", "systems/bol/templates/dialogs/aptitude-roll-part.hbs",
"systems/bol/templates/dialogs/attribute-roll-part.hbs", "systems/bol/templates/dialogs/attribute-roll-part.hbs",
"systems/bol/templates/dialogs/mod-roll-part.hbs", "systems/bol/templates/dialogs/mod-roll-part.hbs",
@@ -62,7 +63,8 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/dialogs/flaws-roll-part.hbs", "systems/bol/templates/dialogs/flaws-roll-part.hbs",
"systems/bol/templates/dialogs/total-roll-part.hbs", "systems/bol/templates/dialogs/total-roll-part.hbs",
"systems/bol/templates/dialogs/fightoptions-roll-part.hbs", "systems/bol/templates/dialogs/fightoptions-roll-part.hbs",
"systems/bol/templates/dialogs/horoscope-roll-part.hbs" "systems/bol/templates/dialogs/horoscope-roll-part.hbs",
"systems/bol/templates/apps/character-summary-template.html"
]; ];
// Load the template parts // Load the template parts

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000845 MANIFEST-001138

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:19.095586 7f684bfff6c0 Recovering log #843 2026/04/11-21:28:04.733439 7ff3bebfd6c0 Recovering log #1135
2025/09/28-20:36:19.105352 7f684bfff6c0 Delete type=3 #841 2026/04/11-21:28:04.743122 7ff3bebfd6c0 Delete type=3 #1133
2025/09/28-20:36:19.105403 7f684bfff6c0 Delete type=0 #843 2026/04/11-21:28:04.743177 7ff3bebfd6c0 Delete type=0 #1135
2025/09/28-20:41:30.063852 7f684affd6c0 Level-0 table #848: started 2026/04/11-21:28:49.377435 7ff3bdbfb6c0 Level-0 table #1141: started
2025/09/28-20:41:30.063889 7f684affd6c0 Level-0 table #848: 0 bytes OK 2026/04/11-21:28:49.377469 7ff3bdbfb6c0 Level-0 table #1141: 0 bytes OK
2025/09/28-20:41:30.118895 7f684affd6c0 Delete type=0 #846 2026/04/11-21:28:49.413622 7ff3bdbfb6c0 Delete type=0 #1139
2025/09/28-20:41:30.235372 7f684affd6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:30.235429 7f684affd6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,14 @@
2025/09/28-19:00:39.507577 7f6850df96c0 Recovering log #839 2026/04/11-21:19:06.264443 7ff3bf3fe6c0 Recovering log #1130
2025/09/28-19:00:39.517799 7f6850df96c0 Delete type=3 #837 2026/04/11-21:19:06.275289 7ff3bf3fe6c0 Delete type=3 #1128
2025/09/28-19:00:39.517876 7f6850df96c0 Delete type=0 #839 2026/04/11-21:19:06.275358 7ff3bf3fe6c0 Delete type=0 #1130
2025/09/28-20:31:38.626261 7f684affd6c0 Level-0 table #844: started 2026/04/11-21:27:57.082652 7ff3bdbfb6c0 Level-0 table #1136: started
2025/09/28-20:31:38.626292 7f684affd6c0 Level-0 table #844: 0 bytes OK 2026/04/11-21:27:57.086593 7ff3bdbfb6c0 Level-0 table #1136: 2472 bytes OK
2025/09/28-20:31:38.660273 7f684affd6c0 Delete type=0 #842 2026/04/11-21:27:57.092696 7ff3bdbfb6c0 Delete type=0 #1134
2025/09/28-20:31:38.731786 7f684affd6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 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
2025/09/28-20:31:38.731828 7f684affd6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 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)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000750 MANIFEST-001039

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:19.070862 7f6850df96c0 Recovering log #748 2026/04/11-21:28:04.708004 7ff3be3fc6c0 Recovering log #1037
2025/09/28-20:36:19.080610 7f6850df96c0 Delete type=3 #746 2026/04/11-21:28:04.718300 7ff3be3fc6c0 Delete type=3 #1035
2025/09/28-20:36:19.080853 7f6850df96c0 Delete type=0 #748 2026/04/11-21:28:04.718366 7ff3be3fc6c0 Delete type=0 #1037
2025/09/28-20:41:30.119156 7f684affd6c0 Level-0 table #753: started 2026/04/11-21:28:49.305595 7ff3bdbfb6c0 Level-0 table #1042: started
2025/09/28-20:41:30.119206 7f684affd6c0 Level-0 table #753: 0 bytes OK 2026/04/11-21:28:49.305624 7ff3bdbfb6c0 Level-0 table #1042: 0 bytes OK
2025/09/28-20:41:30.175528 7f684affd6c0 Delete type=0 #751 2026/04/11-21:28:49.334673 7ff3bdbfb6c0 Delete type=0 #1040
2025/09/28-20:41:30.235393 7f684affd6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:30.235439 7f684affd6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,7 @@
2025/09/28-19:00:39.481080 7f68515fa6c0 Recovering log #744 2026/04/11-21:19:06.240951 7ff3be3fc6c0 Recovering log #1033
2025/09/28-19:00:39.490819 7f68515fa6c0 Delete type=3 #742 2026/04/11-21:19:06.250426 7ff3be3fc6c0 Delete type=3 #1031
2025/09/28-19:00:39.490889 7f68515fa6c0 Delete type=0 #744 2026/04/11-21:19:06.250488 7ff3be3fc6c0 Delete type=0 #1033
2025/09/28-20:31:38.660427 7f684affd6c0 Level-0 table #749: started 2026/04/11-21:27:57.098718 7ff3bdbfb6c0 Level-0 table #1038: started
2025/09/28-20:31:38.660458 7f684affd6c0 Level-0 table #749: 0 bytes OK 2026/04/11-21:27:57.098740 7ff3bdbfb6c0 Level-0 table #1038: 0 bytes OK
2025/09/28-20:31:38.697127 7f684affd6c0 Delete type=0 #747 2026/04/11-21:27:57.104599 7ff3bdbfb6c0 Delete type=0 #1036
2025/09/28-20:31:38.731806 7f684affd6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:31:38.731842 7f684affd6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000844 MANIFEST-001133

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:18.974017 7f684bfff6c0 Recovering log #841 2026/04/11-21:28:04.608888 7ff3be3fc6c0 Recovering log #1131
2025/09/28-20:36:18.983619 7f684bfff6c0 Delete type=3 #839 2026/04/11-21:28:04.618597 7ff3be3fc6c0 Delete type=3 #1129
2025/09/28-20:36:18.983684 7f684bfff6c0 Delete type=0 #841 2026/04/11-21:28:04.618657 7ff3be3fc6c0 Delete type=0 #1131
2025/09/28-20:41:29.577241 7f684affd6c0 Level-0 table #847: started 2026/04/11-21:28:49.095706 7ff3bdbfb6c0 Level-0 table #1136: started
2025/09/28-20:41:29.577297 7f684affd6c0 Level-0 table #847: 0 bytes OK 2026/04/11-21:28:49.095741 7ff3bdbfb6c0 Level-0 table #1136: 0 bytes OK
2025/09/28-20:41:29.689996 7f684affd6c0 Delete type=0 #845 2026/04/11-21:28:49.132472 7ff3bdbfb6c0 Delete type=0 #1134
2025/09/28-20:41:29.690239 7f684affd6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:29.690259 7f684affd6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

View File

@@ -1,15 +1,7 @@
2025/09/28-19:00:39.378965 7f684b7fe6c0 Recovering log #837 2026/04/11-21:19:06.144409 7ff3be3fc6c0 Recovering log #1127
2025/09/28-19:00:39.388796 7f684b7fe6c0 Delete type=3 #835 2026/04/11-21:19:06.153957 7ff3be3fc6c0 Delete type=3 #1125
2025/09/28-19:00:39.388851 7f684b7fe6c0 Delete type=0 #837 2026/04/11-21:19:06.154021 7ff3be3fc6c0 Delete type=0 #1127
2025/09/28-20:31:38.077200 7f684affd6c0 Level-0 table #842: started 2026/04/11-21:27:57.045260 7ff3bdbfb6c0 Level-0 table #1132: started
2025/09/28-20:31:38.106456 7f684affd6c0 Level-0 table #842: 21551 bytes OK 2026/04/11-21:27:57.045286 7ff3bdbfb6c0 Level-0 table #1132: 0 bytes OK
2025/09/28-20:31:38.173778 7f684affd6c0 Delete type=0 #840 2026/04/11-21:27:57.051070 7ff3bdbfb6c0 Delete type=0 #1130
2025/09/28-20:31:38.296697 7f684affd6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:31:38.296765 7f684affd6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at '!items!zgspy1QKaxdEetEw' @ 533 : 1
2025/09/28-20:31:38.296773 7f684affd6c0 Compacting 1@1 + 1@2 files
2025/09/28-20:31:38.314941 7f684affd6c0 Generated table #843@1: 61 keys, 20970 bytes
2025/09/28-20:31:38.315007 7f684affd6c0 Compacted 1@1 + 1@2 files => 20970 bytes
2025/09/28-20:31:38.359525 7f684affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/09/28-20:31:38.359642 7f684affd6c0 Delete type=2 #772
2025/09/28-20:31:38.359796 7f684affd6c0 Delete type=2 #842
2025/09/28-20:31:38.413526 7f684affd6c0 Manual compaction at level-1 from '!items!zgspy1QKaxdEetEw' @ 533 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/boons/MANIFEST-001133 Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000843 MANIFEST-001132

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:18.986296 7f68515fa6c0 Recovering log #841 2026/04/11-21:28:04.620767 7ff3bfbff6c0 Recovering log #1130
2025/09/28-20:36:18.996511 7f68515fa6c0 Delete type=3 #839 2026/04/11-21:28:04.630558 7ff3bfbff6c0 Delete type=3 #1128
2025/09/28-20:36:18.996565 7f68515fa6c0 Delete type=0 #841 2026/04/11-21:28:04.630621 7ff3bfbff6c0 Delete type=0 #1130
2025/09/28-20:41:29.510619 7f684affd6c0 Level-0 table #846: started 2026/04/11-21:28:49.058278 7ff3bdbfb6c0 Level-0 table #1135: started
2025/09/28-20:41:29.510655 7f684affd6c0 Level-0 table #846: 0 bytes OK 2026/04/11-21:28:49.058305 7ff3bdbfb6c0 Level-0 table #1135: 0 bytes OK
2025/09/28-20:41:29.577001 7f684affd6c0 Delete type=0 #844 2026/04/11-21:28:49.095507 7ff3bdbfb6c0 Delete type=0 #1133
2025/09/28-20:41:29.690227 7f684affd6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:29.690265 7f684affd6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,7 @@
2025/09/28-19:00:39.391787 7f68515fa6c0 Recovering log #837 2026/04/11-21:19:06.155820 7ff3bfbff6c0 Recovering log #1126
2025/09/28-19:00:39.401841 7f68515fa6c0 Delete type=3 #835 2026/04/11-21:19:06.166134 7ff3bfbff6c0 Delete type=3 #1124
2025/09/28-19:00:39.401917 7f68515fa6c0 Delete type=0 #837 2026/04/11-21:19:06.166197 7ff3bfbff6c0 Delete type=0 #1126
2025/09/28-20:31:38.207299 7f684affd6c0 Level-0 table #842: started 2026/04/11-21:27:57.031682 7ff3bdbfb6c0 Level-0 table #1131: started
2025/09/28-20:31:38.207340 7f684affd6c0 Level-0 table #842: 0 bytes OK 2026/04/11-21:27:57.031756 7ff3bdbfb6c0 Level-0 table #1131: 0 bytes OK
2025/09/28-20:31:38.236246 7f684affd6c0 Delete type=0 #840 2026/04/11-21:27:57.038391 7ff3bdbfb6c0 Delete type=0 #1129
2025/09/28-20:31:38.296733 7f684affd6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:31:38.359869 7f684affd6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000843 MANIFEST-001132

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:19.010449 7f684bfff6c0 Recovering log #841 2026/04/11-21:28:04.644730 7ff3bfbff6c0 Recovering log #1130
2025/09/28-20:36:19.020407 7f684bfff6c0 Delete type=3 #839 2026/04/11-21:28:04.655618 7ff3bfbff6c0 Delete type=3 #1128
2025/09/28-20:36:19.020468 7f684bfff6c0 Delete type=0 #841 2026/04/11-21:28:04.655815 7ff3bfbff6c0 Delete type=0 #1130
2025/09/28-20:41:29.402024 7f684affd6c0 Level-0 table #846: started 2026/04/11-21:28:49.132705 7ff3bdbfb6c0 Level-0 table #1135: started
2025/09/28-20:41:29.402100 7f684affd6c0 Level-0 table #846: 0 bytes OK 2026/04/11-21:28:49.132747 7ff3bdbfb6c0 Level-0 table #1135: 0 bytes OK
2025/09/28-20:41:29.461676 7f684affd6c0 Delete type=0 #844 2026/04/11-21:28:49.170220 7ff3bdbfb6c0 Delete type=0 #1133
2025/09/28-20:41:29.690194 7f684affd6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:29.690247 7f684affd6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,7 @@
2025/09/28-19:00:39.416295 7f684b7fe6c0 Recovering log #837 2026/04/11-21:19:06.179631 7ff3bebfd6c0 Recovering log #1126
2025/09/28-19:00:39.426667 7f684b7fe6c0 Delete type=3 #835 2026/04/11-21:19:06.189451 7ff3bebfd6c0 Delete type=3 #1124
2025/09/28-19:00:39.426733 7f684b7fe6c0 Delete type=0 #837 2026/04/11-21:19:06.189498 7ff3bebfd6c0 Delete type=0 #1126
2025/09/28-20:31:38.173916 7f684affd6c0 Level-0 table #842: started 2026/04/11-21:27:57.051143 7ff3bdbfb6c0 Level-0 table #1131: started
2025/09/28-20:31:38.173944 7f684affd6c0 Level-0 table #842: 0 bytes OK 2026/04/11-21:27:57.051164 7ff3bdbfb6c0 Level-0 table #1131: 0 bytes OK
2025/09/28-20:31:38.207071 7f684affd6c0 Delete type=0 #840 2026/04/11-21:27:57.056960 7ff3bdbfb6c0 Delete type=0 #1129
2025/09/28-20:31:38.296715 7f684affd6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:31:38.359859 7f684affd6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000841 MANIFEST-001130

View File

@@ -1,8 +1,7 @@
2025/09/28-20:36:19.181411 7f684bfff6c0 Recovering log #839 2026/04/11-21:28:04.822069 7ff3bebfd6c0 Recovering log #1128
2025/09/28-20:36:19.191010 7f684bfff6c0 Delete type=3 #837 2026/04/11-21:28:04.831801 7ff3bebfd6c0 Delete type=3 #1126
2025/09/28-20:36:19.191120 7f684bfff6c0 Delete type=0 #839 2026/04/11-21:28:04.831849 7ff3bebfd6c0 Delete type=0 #1128
2025/09/28-20:41:30.575328 7f684affd6c0 Level-0 table #844: started 2026/04/11-21:28:49.681591 7ff3bdbfb6c0 Level-0 table #1133: started
2025/09/28-20:41:30.575368 7f684affd6c0 Level-0 table #844: 0 bytes OK 2026/04/11-21:28:49.681658 7ff3bdbfb6c0 Level-0 table #1133: 0 bytes OK
2025/09/28-20:41:30.629444 7f684affd6c0 Delete type=0 #842 2026/04/11-21:28:49.718700 7ff3bdbfb6c0 Delete type=0 #1131
2025/09/28-20:41:30.719129 7f684affd6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:41:30.719168 7f684affd6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,7 @@
2025/09/28-19:00:39.597169 7f684b7fe6c0 Recovering log #835 2026/04/11-21:19:06.351355 7ff3bfbff6c0 Recovering log #1124
2025/09/28-19:00:39.607338 7f684b7fe6c0 Delete type=3 #833 2026/04/11-21:19:06.361887 7ff3bfbff6c0 Delete type=3 #1122
2025/09/28-19:00:39.607411 7f684b7fe6c0 Delete type=0 #835 2026/04/11-21:19:06.361938 7ff3bfbff6c0 Delete type=0 #1124
2025/09/28-20:31:38.950626 7f684affd6c0 Level-0 table #840: started 2026/04/11-21:27:57.154739 7ff3bdbfb6c0 Level-0 table #1129: started
2025/09/28-20:31:38.950657 7f684affd6c0 Level-0 table #840: 0 bytes OK 2026/04/11-21:27:57.154770 7ff3bdbfb6c0 Level-0 table #1129: 0 bytes OK
2025/09/28-20:31:38.990008 7f684affd6c0 Delete type=0 #838 2026/04/11-21:27:57.160975 7ff3bdbfb6c0 Delete type=0 #1127
2025/09/28-20:31:39.025068 7f684affd6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 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)
2025/09/28-20:31:39.025113 7f684affd6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)

Binary file not shown.

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