Compare commits

..

110 Commits

Author SHA1 Message Date
5d30482ce3 Add new translations 2023-12-04 21:38:10 +01:00
3fb8e92428 Add new translations 2023-12-04 21:37:29 +01:00
7738746994 Add new translations 2023-12-04 21:30:12 +01:00
761179bc5a Now use v11 compendiums 2023-10-28 18:52:13 +02:00
a4d7c13383 Now use v11 compendiums 2023-10-28 18:47:52 +02:00
ee6bf1a4d4 Now use v11 compendiums 2023-10-28 18:46:51 +02:00
469948e296 Now use v11 compendiums 2023-10-28 18:46:29 +02:00
def271b8b9 Initiative 2023-10-15 17:56:01 +02:00
c9ed514ed6 Sync avec mater
Merge branch 'v10' of https://www.uberwald.me/gitea/public/bol into v10
2023-10-15 17:51:31 +02:00
2ffd3f35c8 Initiative 2023-10-15 17:51:07 +02:00
e3b281f195 Merge pull request 'fix: utilisation d’une variable non définie' (#24) from DjLeChuck/bol:fix-damage-without-armor-apply into v10
Reviewed-on: #24
2023-10-15 16:28:23 +02:00
29ecb04ec3 fix: utilisation d’une variable non définie 2023-10-15 16:25:00 +02:00
ca3064dc0a Merge pull request 'fix: data-attribut corrompu sur l’encaissement sans armure' (#23) from DjLeChuck/bol:fix-defense-request-card into v10
Reviewed-on: #23
2023-10-15 16:15:58 +02:00
9fd8751124 fix: data-attribut corrompu sur l’encaissement sans armure 2023-10-15 16:14:55 +02:00
2d63b63b86 Merge pull request 'fix-creatures-compendium' (#22) from DjLeChuck/bol:fix-creatures-compendium into v10
Reviewed-on: #22
2023-10-15 16:13:39 +02:00
b44250e728 fix: tailles du Phong et du Behemathon
- Phong : petité créature et non grande
- Behemathon : monstrueuse créature et non grande
2023-10-15 16:11:56 +02:00
63c3e24296 fix: typage des créatures
- Utilisation de "cartype" et non "type"
2023-10-15 16:08:41 +02:00
8a87f6deab Fix #21 2023-10-15 16:02:26 +02:00
759b099bdf Fix #20 2023-10-15 16:01:31 +02:00
ade88fb54b Corrections sur encaissement 2023-09-16 09:40:08 +02:00
f1a3f2df36 Rollback to previous release system 2023-08-26 21:45:33 +02:00
a357c6ddfc Add gitea CI/CD 2023-08-26 19:17:02 +02:00
26a6c7fc28 Add gitea CI/CD 2023-08-26 19:11:37 +02:00
6fcac36580 Add gitea CI/CD 2023-08-26 19:08:53 +02:00
34924b07c0 Add gitea CI/CD 2023-08-26 19:04:48 +02:00
66bf299ea4 Add gitea CI/CD 2023-08-26 19:01:51 +02:00
37ab0d01dc Add gitea CI/CD 2023-08-26 19:00:25 +02:00
2e3a97de04 Add gitea CI/CD 2023-08-26 18:11:41 +02:00
01dbe76f59 Minor fixes 2023-08-26 18:03:08 +02:00
ca33defd75 Update v11 2023-06-23 09:02:45 +02:00
2e616e3e31 Update v11 2023-06-23 08:37:56 +02:00
3be7f4bf9f Update v11 2023-06-23 08:37:50 +02:00
1b8e0840b0 Update v11 2023-06-22 23:05:50 +02:00
2cdd096c98 Update v11 2023-06-22 23:05:36 +02:00
8fcb17b566 Update v11 2023-06-22 23:05:04 +02:00
ad79dbb015 New combat options 2023-06-22 20:36:40 +02:00
73ccf44dd4 New combat options 2023-06-22 20:34:17 +02:00
ec3649799f v10/v11 compat 2023-05-25 16:29:43 +02:00
3f5f090bb9 Points de creation OK again 2023-05-24 14:00:24 +02:00
268ac0a25a Review initiative 2023-05-01 18:50:32 +02:00
59ee6684ab Fix #19 - Horoscope majeur 2023-04-29 21:48:51 +02:00
41fbc094bb Combat Tracker + power enhancements 2023-04-06 20:15:04 +02:00
5b91041a3f Fix options de combat 2023-04-04 13:41:22 +02:00
e7f3851daa Fix dice default 2023-03-29 23:04:02 +02:00
75d562f922 Nouvelle option d'ignorer les dommages 2023-03-25 08:42:14 +01:00
ba7e25e8c7 Fix and +/- 2023-03-18 13:51:46 +01:00
83d3f17dd0 Messages de recuperation 2023-03-18 10:24:30 +01:00
0c502a2188 Meilleure gestion initiative 2023-03-15 17:35:07 +01:00
5df2b7e624 Minot fixes 2023-02-19 19:35:03 +01:00
f6554f6945 New release 2023-02-03 23:55:59 +01:00
fbe232266f Spell changes to support v1.7 2023-02-01 10:16:21 +01:00
4b9c3bcd1e Spell changes to support v1.7 2023-02-01 10:15:56 +01:00
05f09aa3f5 Add dice selection 2023-01-26 20:50:56 +01:00
727db74545 Update sheet 2023-01-23 20:50:48 +01:00
ccb3a458c0 Add pages 2023-01-05 17:00:11 +01:00
1ac9605f08 Fix init creature + minor changes 2023-01-04 09:57:13 +01:00
1bbefd3499 Astrologie ! 2022-12-25 18:00:42 +01:00
bcb377db7a Gestion de l'astrologie 2022-12-23 23:24:09 +01:00
7a8cf9f8fd Welcome page and init info message 2022-12-23 16:38:41 +01:00
d74f7784bb Fix bougette sur PNJ/Creatures 2022-12-04 13:49:00 +01:00
6d6fec99b0 Fix bougette sur PNJ/Creatures 2022-12-04 13:48:37 +01:00
884823a1bc Init again !!!! 2022-12-02 13:16:42 +01:00
399c22d623 Init again !!!! 2022-12-02 13:15:48 +01:00
085265df5d initiative bugfix 2022-12-01 23:57:33 +01:00
593db9ba5b initiative bugfix 2022-12-01 23:46:27 +01:00
ac96f3ca67 Enhance initiative + fix combat 2022-11-30 20:58:27 +01:00
b2fe67ab05 Enhance tokens/actor management + auto effects 2022-11-30 12:12:44 +01:00
47178d7359 Use bougette v1.2 2022-11-29 15:23:05 +01:00
e1c7304551 Add a PC list summary 2022-11-25 20:47:28 +01:00
7b4e5bcbfa Add a PC list summary 2022-11-25 15:50:12 +01:00
31bd83b0ab Add a PC list summary 2022-11-25 15:50:07 +01:00
e35187433e Fix malus dice (again) and scroll rendering 2022-11-25 14:24:03 +01:00
5e7dc3ad9d Display effects in main sheet (left) and manage it for defense and damages 2022-11-25 08:49:37 +01:00
add6893864 Logo is rotating again 2022-11-25 07:37:44 +01:00
3a591e750a Ajout aide + effets 2022-11-23 21:37:46 +01:00
fe1cda67c4 Ajout aide + effets 2022-11-23 21:34:51 +01:00
985aba0318 Various enhancements 2022-11-23 15:30:31 +01:00
5d8cc300e9 Various enhancements 2022-11-23 15:27:08 +01:00
aa27168c2b fix flaws usage 2022-11-19 22:00:14 +01:00
1135e3aff2 fix flaws usage 2022-11-19 21:56:51 +01:00
30e316d34e Remove to chat button 2022-11-19 19:22:16 +01:00
f3bd84c5c9 Corrections sur creatures/npc et armes de vehicules 2022-10-10 14:14:32 +02:00
985d393de5 Corrections sur creatures/npc et armes de vehicules 2022-10-10 08:46:41 +02:00
815b5ff2ac Ajout distance 2022-10-08 17:43:09 +02:00
edf8325109 Inc version 2022-09-28 23:26:41 +02:00
895aa01419 Enable links in editor 2022-09-27 20:47:30 +02:00
9631e5e72a Merge conteneur 2022-09-26 08:28:47 +02:00
716cc53b29 Merge conteneur 2022-09-26 08:24:39 +02:00
44952d4410 Fix hotbar + tooltip 2022-09-25 21:14:39 +02:00
a7991bce92 Fix hotbar + tooltip 2022-09-25 21:13:15 +02:00
a1c20019e8 v10 init fix 2022-09-19 23:23:54 +02:00
af8576a4a1 Fix initfor combat 2022-09-09 23:49:01 +02:00
6c35ddfb0b rework ID 2022-09-02 17:31:12 +02:00
0cbcaee45e rework ID 2022-09-02 16:21:18 +02:00
904ff821e1 rework ID 2022-09-02 16:19:16 +02:00
5c13cde07a rework ID 2022-09-02 16:08:56 +02:00
51c1e205e9 rework ID 2022-09-01 22:40:29 +02:00
90a83f4571 rework ID 2022-09-01 22:23:58 +02:00
3311bc091a rework ID 2022-09-01 21:26:10 +02:00
fa8f9869a6 v10 sync 2022-08-31 23:00:41 +02:00
36b905134e v10 sync 2022-08-31 22:46:19 +02:00
eeb0a906e7 v10 sync 2022-08-31 22:24:56 +02:00
c552411d61 Allow configurable logo 2022-07-17 18:24:29 +02:00
d3ae59f70d Allow configurable logo 2022-07-17 18:20:05 +02:00
cf7d76fdba Fix d6BB combat roll 2022-07-16 11:06:34 +02:00
89ec404ca8 v10 branch - Update manifest 2022-07-16 11:05:15 +02:00
8f60aa95ee Fix d6BB combat roll 2022-07-16 11:03:33 +02:00
d888c6a2eb v10 branch - Update manifest 2022-07-13 08:07:16 +02:00
165e41fef6 v10 branch - Update manifest 2022-07-13 08:07:04 +02:00
19bb0798c8 Sync 2022-07-07 13:33:31 +02:00
250 changed files with 4018 additions and 6264 deletions

54
.gitea/workflows/main.yml Normal file
View File

@ -0,0 +1,54 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
with:
ref: 'v10'
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the module.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'system.json'
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/public/bol
manifest: https://www.uberwald.me/gitea/public/bol/releases/latest/system.json
download: https://www.uberwald.me/gitea/public/bol/releases/download/${{github.event.release.tag_name}}/bol.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./bol.zip system.json template.json README.md LICENSE assets/ css/ fonts/ images/ lang/ module/ packs/ styles/ templates/ ui/
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./bol.zip
system.json
api_key: '${{secrets.RELEASE_TOKEN_UBERWALD}}'

118
.gitignore vendored
View File

@ -1,108 +1,10 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
package-lock.json
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# BOL Data
#/data/
.vscode/settings.json
.idea
.history
todo.md
/.vscode
/ignored/
/node_modules/
/jsconfig.json
/package.json
/package-lock.json

View File

@ -40,4 +40,4 @@ Maps : Emmanuel Roudier.
# Developmement
Zigmund, LeRatierBretonnien
LeRatierBretonnien, Zigmund (historical)

BIN
assets/bol_monnaies_v1_2.pdf Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

38
changelog.md Normal file
View File

@ -0,0 +1,38 @@
Changes :
# v11.1.2
- Ajout des traductions manquantes en anglais
# v11.1.1
- Re-organisation des compendiums + ajouts de nouveaux equipements
- Correction sur la langue Démonique (1 Parlé, 1 Lu/Ecrit)
# v11.1.0
- Foundry v11 seulement
- Re-organisation des compendiums
- Correction sur les créatures
- Correction sur l'encaissement sans armure
- Ajout d'une note d'explication sur l'initiative
# v11.0.8
- Correction sur les malus de bouclier (blocage)
- Corrrection sur le malus d'init des boucliers qui était mal affiché dans la fiche d'item
# v10.4.0
- Ajout de la gestion d'effets
- Aide intégré
- Intégration du PDF de la bougette
# v10.3.3
- Nouvelles clés de traduction
- Lorsqu'une arme a un dé bonus, prise en compte plus claire du dé bonus et affichage de l'information dans la fenêtre de jet
- Lorsqu'une arme relance les 1 sur ses dégats, l'information est affichée dans le tchat
- Termes corrects pour les PNJs (ie rival)
- Nouveaux équipements issus du Dieu Voilé

View File

@ -1,7 +1,6 @@
/* ----------------------------------------- */
/* LOCAL FONTS */
/* ----------------------------------------- */
@import "../node_modules/rpg-awesome/css/rpg-awesome.min.css";
@font-face {
font-family: 'Contrail One';
font-style: normal;
@ -59,8 +58,6 @@ a:hover {
top: -50px;
left: calc(50% - 100px);
opacity: 0.7;
-webkit-animation: rotation 10s infinite linear;
animation: rotation 10s infinite linear;
}
#pause h3 {
font-family: "IMFellDWPicaSC-Regular", serif;
@ -389,16 +386,16 @@ ul.no-bullets {
background-color: lightgray;
}
.bol h1.form-header {
font-size: 2.5em;
font-size: 2.2em;
font-weight: 700;
}
.bol h2.form-header {
font-size: 2em;
font-size: 1.8em;
font-weight: 500;
border-bottom: 1px groove #eeede0;
}
.bol h3.form-header {
font-size: 1.5em;
font-size: 1.2em;
font-weight: 500;
border-bottom: 1px groove #eeede0;
}
@ -444,12 +441,16 @@ ul.no-bullets {
.bol .inc-dec-btns {
color: #4b4a44;
}
.summmary-number {
padding-left: 4rem;
}
/* Items List */
.items-list {
list-style: none;
margin: 7px 0;
padding: 0;
overflow-y: auto;
overflow-y:hidden;
/*overflow-y: auto;*/
}
.items-list .item-header {
font-family: 'Signika', sans-serif;
@ -520,6 +521,18 @@ ul.no-bullets {
.items-list .item .item-control {
color: #4b4a44;
}
.items-list .item-name-fixed-medium {
min-width: 8rem;
width: 8rem;
}
.items-list .item-field-fixed-short {
max-width: 3rem;
min-width: 3rem;
width: 3rem;
}
.bougette-dice-img {
color:rgba(150, 44, 44, 0.70);
}
/* ----------------------------------------- */
/* Premade colors */
@ -769,7 +782,7 @@ body.system-bol img#logo {
min-height: 700px;
height: 700px;
}
.bol.sheet.actor .window-content form {
.bol.sheet.actor .window-content .bol-actor-form {
background-image: url("/systems/bol/ui/logo.webp");
background-repeat: no-repeat;
background-size: 190px 115px;
@ -1030,9 +1043,18 @@ body.system-bol img#logo {
justify-content: flex-start;
flex-direction: column;
position: absolute;
top: 2.75rem;
/*transform: translate(0, -30%);*/
top: -4rem;
max-width: 250px;
left: 4rem;
}
.tokenhudext.right2 {
justify-content: flex-start;
flex-direction: column;
position: absolute;
top: -4rem;
left: 12rem;
}
.control-icon.tokenhudicon {
width: fit-content;
height: fit-content;
@ -1049,5 +1071,15 @@ body.system-bol img#logo {
z-index: 2;
}
.bol-hud-menu label {
font-size: 0.75rem;
font-size: 0.6rem;
}
.bol-margin-tb-2 {
margin-top: 2px;
margin-bottom: 2px;
}
.character-summary-container {
  opacity: 0.95;
}
.character-summary-rollable {
text-decoration: underline;
}

4
images/.directory Normal file
View File

@ -0,0 +1,4 @@
[Dolphin]
Timestamp=2022,7,17,14,58,4.757
Version=4
VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 372 KiB

View File

@ -156,6 +156,8 @@
"BOL.ui.armorAgiMalus": "Rüschtung+Schild-Malus (Geschick)",
"BOL.ui.armorInitMalus": "Rüstungsmalus (Init)",
"BOL.ui.attackValue": "Angriffswert",
"BOL.ui.initMalus": "Init malus",
"BOL.ui.createEquipment": "Create Equipment",
"BOL.featureCategory.origins": "Herkünfte",
"BOL.featureCategory.races": "Rassen",
@ -354,6 +356,16 @@
"BOL.vehicleCategory.boat" : "Schiff",
"BOL.vehicleCategory.other" : "Anderes",
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
"BOL.ui.horoscopesBonus": "Horoscopes (Bonus)",
"BOL.ui.horoscopesMalus": "Horoscopes (Malus)",
"BOL.ui.groupHoroscope": "Horoscrope de Groupe de ",
"BOL.range.PointBlank": "Direkt",
"BOL.range.Short": "Kurz",
@ -454,5 +466,25 @@
"BOL.chat.welcome2": "Zum Spielen werden zwingend die passenden BoL Bücher benötigt. Die französischen Originalbücher für dieses System gibt es hier: http://www.ludospherik.fr/content/14-barbarians-of-lemuria<br>Die deutsche Übersetzung gibt es bei Truant Spiele: https://truant.com/produkt/barbarians-of-lemuria-2",
"BOL.chat.welcome3": "Die enthaltenen Karten wurden von Guillaume Tavernier und Ludospherik zur Verfügung gestellt. Vielen Dank dafür!",
"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.settings.rollArmor": "Roll for armor",
"BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",
"BOL.settings.useBougette": "Use Bougette (fan-made French rule)",
"BOL.settings.useBougetteTooltip": "Use the Bougette value, as described in Annales Lemurienne from LeRatierBretonnien (https://www.lahiette.com/leratierbretonnien/)",
"BOL.settings.removeDead": "Automatically remove dead NPCs by end of the round",
"BOL.settings.removeDeadTooltip": "Remove NPCs Automatically when HP are 0 or less, at the end of each round",
"BOL.settings.diceFormula": "Dice Formula",
"BOL.settings.diceFormulaTooltip": "Main dice formula (2d6 per default)",
"BOL.settings.diceSuccessValue" : "Success value",
"BOL.settings.diceSuccessValueTooltip": "Value of the success threshold (9 per default for 2d6)",
"BOL.settings.diceCriticalValue" : "Critical success value",
"BOL.settings.diceCriticalValueTooltip": "Minimum value for critical success (12 per default for 2d6)",
"BOL.settings.diceCriticalFailure" : "Critical failure value",
"BOL.settings.diceCriticalFailureTooltip": "Maximum value for critical failure (2 per default for 2d6)",
"BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo",
"BOL.settings.defaultLogoPathActorSheetTooltip": "Path of the Actor sheet logo (346 x 200, default : /systems/bol/ui/logo.webp)",
"BOL.settings.defaultLogoTopLeftPath" : "Path for main top left logo",
"BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)"
}

View File

@ -6,8 +6,8 @@
"ITEM.TypeWeapon": "Weapon",
"ITEM.TypeArmor": "Armor",
"BOL.attributes.vigor": "Vigor",
"BOL.attributes.halfvigor" : "Half-Vigor",
"BOL.attributes.vigor": "Strength",
"BOL.attributes.halfvigor" : "Half-Strength",
"BOL.attributes.agility": "Agility",
"BOL.attributes.mind": "Mind",
"BOL.attributes.appeal": "Appeal",
@ -33,6 +33,18 @@
"BOL.ui.tab.description": "Description",
"BOL.ui.tab.details": "Details",
"BOL.ui.tab.spellalchemy": "Spells & Alchemy",
"BOL.ui.tab.astrologer": "Astrologer",
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
"BOL.ui.horoscopesBonus": "Horoscopes (Bonus)",
"BOL.ui.horoscopesMalus": "Horoscopes (Malus)",
"BOL.ui.groupHoroscope": "Horoscrope de Groupe de ",
"BOL.ui.properties": "Properties",
"BOL.ui.description": "Description",
@ -115,6 +127,7 @@
"BOL.ui.isSorcerer": "Is Sorcerer ?",
"BOL.ui.isAlchemist": "Is Alchemist ?",
"BOL.ui.isPriest": "Is Priest/Druid ?",
"BOL.ui.isAstrologer": "Is Astrologer?",
"BOL.ui.circle": "Circle",
"BOL.ui.spells": "Spells",
"BOL.ui.focusSpell": "Cast a spell",
@ -151,6 +164,10 @@
"BOL.ui.armorAgiMalus": "Armor+Shield Modifier (Agi)",
"BOL.ui.armorInitMalus": "Armor Modifier (Init)",
"BOL.ui.attackValue": "Attack Value",
"BOL.ui.weaponbonus": "Cette arme bénéficie déja d'un Dé de Bonus (Arme Favorite prise en compte, par exemple)",
"BOL.ui.initMalus": "Init malus",
"BOL.ui.isspecial": "Spécial ?",
"BOL.ui.createEquipment": "Create Equipment",
"BOL.featureCategory.origins": "Origins",
"BOL.featureCategory.races": "Races",
@ -182,6 +199,7 @@
"BOL.fightOptionTypes.fulldefense": "Full Defense",
"BOL.fightOptionTypes.defense": "Defensive Posture",
"BOL.fightOptionTypes.attack": "Offensive Posture",
"BOL.fightOptionTypes.other": "Other",
"BOL.itemCategory.object": "Object",
"BOL.itemCategory.equipment": "Equipment",
@ -450,6 +468,25 @@
"BOL.chat.welcome2": "Books are necessary to play, and ca be found here : http://www.ludospherik.fr/content/14-barbarians-of-lemuria",
"BOL.chat.welcome3": "The integrated maps are authorized by Guillaume Tavernier and Ludospherik. Thanks to them !.",
"BOL.chat.welcome4": "All support for this system is available on this Discord server : https://discord.gg/pPSDNJk",
"BOL.chat.welcome5": "Good game in Lemuria !"
"BOL.chat.welcome5": "Good game in Lemuria !",
"BOL.chat.welcome6": "",
"BOL.settings.rollArmor": "Roll for armor",
"BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",
"BOL.settings.useBougette": "Use Bougette (fan-made French rule)",
"BOL.settings.useBougetteTooltip": "Use the Bougette value, as described in Annales Lemurienne from LeRatierBretonnien (https://www.lahiette.com/leratierbretonnien/)",
"BOL.settings.removeDead": "Automatically remove dead NPCs by end of the round",
"BOL.settings.removeDeadTooltip": "Remove NPCs Automatically when HP are 0 or less, at the end of each round",
"BOL.settings.diceFormula": "Dice Formula",
"BOL.settings.diceFormulaTooltip": "Main dice formula (2d6 per default)",
"BOL.settings.diceSuccessValue" : "Success value",
"BOL.settings.diceSuccessValueTooltip": "Value of the success threshold (9 per default for 2d6)",
"BOL.settings.diceCriticalValue" : "Critical success value",
"BOL.settings.diceCriticalValueTooltip": "Minimum value for critical success (12 per default for 2d6)",
"BOL.settings.diceCriticalFailure" : "Critical failure value",
"BOL.settings.diceCriticalFailureTooltip": "Maximum value for critical failure (2 per default for 2d6)",
"BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo",
"BOL.settings.defaultLogoPathActorSheetTooltip": "Path of the Actor sheet logo (346 x 200, default : /systems/bol/ui/logo.webp)",
"BOL.settings.defaultLogoTopLeftPath" : "Path for main top left logo",
"BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)"
}

View File

@ -1,15 +1,26 @@
{
"ACTOR.TypeCharacter": "Personnage",
"ACTOR.TypeEncounter": "Rencontre",
"ITEM.TypeItem": "Objet",
"ITEM.TypeFeature": "Trait",
"ITEM.TypeWeapon": "Arme",
"ITEM.TypeArmor": "Armure",
"TYPES": {
"Actor": {
"character": "Personnage",
"npc": "PNJ",
"vehicle": "Véhicule"
},
"Item": {
"item": "Objet",
"feature": "Trait",
"weapon": "Arme",
"armure": "Armure"
}
},
"BOL.attributes.vigor": "Vigueur",
"BOL.attributes.halfvigor": "Demi-vigueur",
"BOL.attributes.agility": "Agilité",
"BOL.attributes.mind": "Esprit",
"BOL.attributes.appeal": "Aura",
"BOL.attributes.hull": "Coque",
"BOL.attributes.crew": "Equipage",
"BOL.attributes.resources": "Resources",
"BOL.attributes.row": "Rames",
"BOL.aptitudes.init": "Initiative",
"BOL.aptitudes.melee": "Mêlée",
"BOL.aptitudes.ranged": "Tir",
@ -22,7 +33,9 @@
"BOL.resources.power": "Pouvoir",
"BOL.resources.villainy": "Vilénie",
"BOL.resources.alchemypoints": "Points de Creation",
"BOL.resources.astrologypoints": "Points d'Astrologie",
"BOL.traits.xp": "Expérience",
"BOL.ui.tab.stats": "Attributs",
"BOL.ui.tab.combat": "Combat",
"BOL.ui.tab.actions": "Actions",
@ -30,7 +43,19 @@
"BOL.ui.tab.equipment": "Equipement",
"BOL.ui.tab.description": "Description",
"BOL.ui.tab.details": "Details",
"BOL.ui.tab.spellalchemy": "Sorts&Alchimie",
"BOL.ui.tab.spellalchemy": "Mystères",
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
"BOL.ui.horoscopesBonus": "Horoscopes (Bonus)",
"BOL.ui.horoscopesMalus": "Horoscopes (Malus)",
"BOL.ui.groupHoroscope": "Horoscrope de Groupe de ",
"BOL.ui.properties": "Propriétés",
"BOL.ui.description": "Description",
"BOL.ui.actions": "Actions",
@ -86,8 +111,8 @@
"BOL.ui.success": "Succès",
"BOL.ui.failure": "Échec",
"BOL.ui.fumble": "Échec critique",
"BOL.ui.critical": "Succès critique",
"BOL.ui.criticallegend": "Succès légendaire !",
"BOL.ui.critical": "Succès Héroïque",
"BOL.ui.criticallegend": "Succès Légendaire !",
"BOL.ui.maneuvers": "Actions de combat",
"BOL.ui.stacksize": "Taille de pile (max)",
"BOL.ui.weapons": "Armes",
@ -108,12 +133,17 @@
"BOL.ui.difficulty": "Difficulté",
"BOL.ui.spellProperties": "Propriétés du Sort",
"BOL.ui.duration": "Durée",
"BOL.ui.spellkeep": "Prolongation",
"BOL.ui.concentrate": "Concentration",
"BOL.ui.spellkeep": "Prolongation possible ?",
"BOL.ui.concentrate": "Concentration à maintenir ?",
"BOL.ui.aggressive": "Sort aggressif ?",
"BOL.ui.registerInit": "Enregistrer comme Init. de combat",
"BOL.ui.initMalus": "Malus d'initiative",
"BOL.ui.magicnewrules": "Règles supplémentaires (cf. supplément fan-made Sorcellerie!)",
"BOL.ui.isSorcerer": "Carrière de Sorcier ?",
"BOL.ui.isAlchemist": "Carrière d'Alchimiste ?",
"BOL.ui.isPriest": "Carrière de Prêtre/Druide ?",
"BOL.ui.isAstrologer": "Carrière d'Astrologue?",
"BOL.ui.circle": "Cercle",
"BOL.ui.spells": "Sorts",
"BOL.ui.focusSpell": "Lance un sort",
@ -131,6 +161,21 @@
"BOL.ui.alchemyCostTotal": "Points de Création nécessaires pour la Préparation",
"BOL.ui.alchemyInvest": "Points de Création investis",
"BOL.ui.alchemyCurrent": "Points de Création actuel dans la Préparation",
"BOL.ui.astrology": "Astrologie et Horoscopes",
"BOL.ui.astrologyMinor": "Etablir un Horoscope Mineur",
"BOL.ui.astrologyMajor": "Etablir un Horoscope Majeur",
"BOL.ui.astrologyMajorGroup": "Etablir un Horoscope Majeur de Groupe",
"BOL.ui.makeHoroscope": "Etablir un Horoscope",
"BOL.ui.astrologerRank": "Rang de l'Astrologue",
"BOL.ui.horoscopeCost": "Cout en Points d'Astrologie",
"BOL.ui.minor": "Mineur",
"BOL.ui.major": "Majeur",
"BOL.ui.majorgroup": "Majeur de Groupe",
"BOL.ui.horoscopeGroup": "Horoscopes de Groupe",
"BOL.ui.horoscopeDiceRemaining": "Dés restants",
"BOL.ui.horoscopeDiceMax": "Dés Max",
"BOL.ui.astrologyNoPoints": "Vous n'avez pas assez de Points d'Astrologie!",
"BOL.ui.advance": "Avancement",
"BOL.ui.isbonusdice": "Fourni un dé bonus?",
"BOL.ui.ismalusdice": "Fourni un dé malus?",
@ -151,6 +196,36 @@
"BOL.ui.armorAgiMalus": "Malus d'Armure+Bouclier (Agi)",
"BOL.ui.armorInitMalus": "Malus d'Armure (Init)",
"BOL.ui.attackValue": "Valeur d'attaque",
"BOL.ui.vehicleWeapons": "Armes de véhicules",
"BOL.ui.hullDamage": "D.coque",
"BOL.ui.crewDamage": "D.équipage",
"BOL.ui.fireDamage": "Feu ?",
"BOL.ui.weaponbonus": "Cette arme bénéficie déja d'un Dé de Bonus (Arme Favorite prise en compte, par exemple)",
"BOL.ui.creature": "Creature",
"BOL.ui.rabble": "Piétaille",
"BOL.ui.tough": "Coriace",
"BOL.ui.villain": "Rival",
"BOL.ui.attributaptitude": "Attribut ou Aptitude",
"BOL.ui.always": "Tout les jets (ie toujours)",
"BOL.ui.effectbonusmalus": "Bonus ou Malus à appliquer",
"BOL.ui.boleffects": "Effets (automatiques)",
"BOL.ui.modifier": "Modificateur",
"BOL.ui.effects": "Effets en cours",
"BOL.ui.pcname": "PJs",
"BOL.ui.npcname": "PNJs",
"BOL.ui.pclistbutton": "Vue compacte",
"BOL.ui.noactorfound": "PNJ inconnu, le PNJ doit être présent dans le monde pour s'afficher ici.",
"BOL.ui.deletetitle": "Suppression",
"BOL.ui.confirmdelete": "Vous êtes sûr de vouloir supprimer cet item ?",
"BOL.ui.nomorealchemypoints": "Plus assez de Points de Création !",
"BOL.ui.armornoformula": "L'armure {protect.name} n'a pas de formule pour la protection !",
"BOL.ui.selectactor": "Selectionnez votre personnage pour utiliser la macro",
"BOL.ui.itemnotfound": "Impossible de trouver l'objet de cette macro",
"BOL.ui.noinit": "Pas d'initiative trouvée, veuillez en enregistrer une.",
"BOL.ui.warninitiative": "Votre initiative n'est pas disponible. Effectuez un jet d'Initiative pour ce combat.",
"BOL.ui.isspecial": "Spéciale ?",
"BOL.ui.createEquipment": "Créer un Equipement",
"BOL.featureCategory.origins": "Origines",
"BOL.featureCategory.races": "Races",
"BOL.featureCategory.careers": "Carrières",
@ -163,6 +238,7 @@
"BOL.bougette.easylife": "A l'aise",
"BOL.bougette.luxury": "Luxe&Volupté",
"BOL.bougette.rich": "Richissime",
"BOL.featureSubtypes.origin": "Origine",
"BOL.featureSubtypes.race": "Race",
"BOL.featureSubtypes.career": "Carrière",
@ -171,6 +247,11 @@
"BOL.featureSubtypes.language": "Langue",
"BOL.featureSubtypes.gods": "Dieux & Foi",
"BOL.featureSubtypes.fightOption": "Option de Combat",
"BOL.featureSubtypes.effect": "Effet",
"BOL.featureSubtypes.effects": "Effets",
"BOL.featureSubtypes.boleffect": "Effet",
"BOL.featureSubtypes.horoscope": "Horoscope",
"BOL.fightOptionTypes.armor": "Attaque au défaut d'armure",
"BOL.fightOptionTypes.intrepid": "Attaque intrépide",
"BOL.fightOptionTypes.twoweaponsdef": "Combat à 2 armes (Défense)",
@ -178,6 +259,8 @@
"BOL.fightOptionTypes.fulldefense": "Défense totale",
"BOL.fightOptionTypes.defense": "Posture défensive",
"BOL.fightOptionTypes.attack": "Posture offensive",
"BOL.fightOptionTypes.other": "Autres",
"BOL.itemCategory.object": "Objet",
"BOL.itemCategory.equipment": "Équipement",
"BOL.itemCategory.consumable": "Consommable",
@ -186,12 +269,15 @@
"BOL.itemCategory.other": "Autre",
"BOL.itemCategory.capacity": "Capacité",
"BOL.itemCategory.alchemy": "Préparation Alchimique",
"BOL.itemCategory.vehicleweapon": "Armes de Véhicule",
"BOL.combatCategory.protections": "Protections",
"BOL.combatCategory.shields": "Boucliers",
"BOL.combatCategory.melee": "Armes de contact",
"BOL.combatCategory.ranged": "Armes à distance",
"BOL.combatCategory.fightOptions": "Options de combat",
"BOL.combatCategory.natural": "Armes Naturelless",
"BOL.equipmentCategory.weapon": "Arme",
"BOL.equipmentCategory.armor": "Armure",
"BOL.equipmentCategory.protection": "Protection",
@ -203,21 +289,26 @@
"BOL.equipmentCategory.container": "Conteneur",
"BOL.equipmentCategory.currency": "Monnaie",
"BOL.equipmentCategory.other": "Autre",
"BOL.protectionCategory.armor": "Armure",
"BOL.protectionCategory.shield": "Bouclier",
"BOL.protectionCategory.helm": "Casque",
"BOL.protectionCategory.other": "Autre",
"BOL.spellItem.charm": "Charme",
"BOL.spellItem.circle1": "Premier Cercle",
"BOL.spellItem.circle2": "Second Cercle",
"BOL.spellItem.circle3": "Troisième Cercle",
"BOL.alchemyItem.common": "Courante",
"BOL.alchemyItem.scarce": "Rare",
"BOL.alchemyItem.legend": "Légendaire",
"BOL.alchemyItem.mythic": "Mythique",
"BOL.weaponCategory.melee": "Arme de mêlée",
"BOL.weaponCategory.ranged": "Arme de tir",
"BOL.weaponCategory.other": "Autre",
"BOL.itemProperty.damageMultiplier": "Multiplicateur de dommages",
"BOL.itemProperty.attackBonusDice": "Dé de Bonus d'attaque",
"BOL.itemProperty.equipable": "Équipable",
@ -270,6 +361,18 @@
"BOL.itemProperty.difficulty": "Difficulté",
"BOL.itemProperty.natural": "Arme naturelle",
"BOL.itemProperty.onlymodifier": "Modificateur uniquement (ie attaques de créatures)",
"BOL.itemProperty.vehicleDamageType": "Type de dommages de véhicules",
"BOL.itemProperty.isfiredamage": "Dommages de Feu",
"BOL.itemProperty.ishulldamage": "Dommages à la coque",
"BOL.itemProperty.iscrewdamage": "Dommages à l'équipage",
"BOL.itemProperty.hulldamage": "Valeur des dommages à la coque",
"BOL.itemProperty.crewdamage": "Valeur des dommages à l'équipage",
"BOL.itemProperty.hullDamageMultiplier": "Multiplicateur",
"BOL.itemProperty.crewDamageMultiplier": "Multiplicateur",
"BOL.itemProperty.isboarding": "Abordage",
"BOL.itemProperty.isspur": "Eperonnage",
"BOL.itemProperty.isbreakrow": "Briser les rames",
"BOL.itemStat.quantity": "Quantité",
"BOL.itemStat.weight": "Poids",
"BOL.itemStat.price": "Prix",
@ -279,21 +382,26 @@
"BOL.itemStat.soak": "Valeur de protection",
"BOL.itemStat.blocking": "Bloquage",
"BOL.itemStat.modifiers": "Modificateurs",
"BOL.weaponSize.unarmed": "Mains nues",
"BOL.weaponSize.improvised": "Arme improvisée",
"BOL.weaponSize.light": "Légère",
"BOL.weaponSize.medium": "Moyenne",
"BOL.weaponSize.heavy": "Lourde",
"BOL.itemModifiers.init": "Malus (Initiative)",
"BOL.itemModifiers.social": "Malus (Social)",
"BOL.itemModifiers.agility": "Malus (Agilité)",
"BOL.itemModifiers.powercost": "Malus (Coût supplémentaire en PP)",
"BOL.itemBlocking.malus": "Social",
"BOL.itemBlocking.nbAttacksPerRound": "Agilité",
"BOL.soakFormula.none": "-",
"BOL.soakFormula.light": "Légère (Annule d6-3 dégâts subis)",
"BOL.soakFormula.medium": "Moyenne (Annule d6-2 dégâts subis)",
"BOL.soakFormula.heavy": "Lourde (Annule d6-1 dégâts subis)",
"BOL.armorQuality.none": "-",
"BOL.armorQuality.light": "Légère",
"BOL.armorQuality.lightQ": "Légère de qualité",
@ -307,6 +415,7 @@
"BOL.armorQuality.heavyQ": "Lourde de qualité",
"BOL.armorQuality.heavySup": "Lourde Supérieure",
"BOL.armorQuality.heavyLeg": "Lourde Légendaire",
"BOL.equipmentSlots.none": "-",
"BOL.equipmentSlots.head": "Tête",
"BOL.equipmentSlots.neck": "Cou",
@ -323,10 +432,12 @@
"BOL.equipmentSlots.feet": "Pieds",
"BOL.equipmentSlots.finder": "Doigt",
"BOL.equipmentSlots.ear": "Oreille",
"BOL.vehicleCategory.mount": "Monture terrestre",
"BOL.vehicleCategory.flying": "Monture volante",
"BOL.vehicleCategory.boat": "Bateau",
"BOL.vehicleCategory.other": "Autre",
"BOL.range.PointBlank": "Bout portant",
"BOL.range.Short": "Courte",
"BOL.range.Medium": "Moyenne",
@ -334,6 +445,7 @@
"BOL.range.VeryLong": "Très longue",
"BOL.range.Extreme": "Extrême",
"BOL.range.Maximum": "Maximale",
"BOL.notification.MacroMultipleTokensSelected": "Vous avez sélectionné plusieurs tokens",
"BOL.notification.MacroNoActorAvailable": "Aucun acteur n'a pu être ciblé",
"BOL.notification.MacroNoTokenSelected": "Vous devez sélectionner un token",
@ -356,10 +468,10 @@
"BOL.chat.isdead": "{name} est mort !",
"BOL.chat.epitaph": "Que son nom soit honoré sur les champs de batailles de Lémurie !",
"BOL.chat.vitalityzero": "La Vitalité de {name} est {hp} : il va s'écrouler au sol et sombrer dans l'inconscience !",
"BOL.chat.vitalityheroism": "Vous pouvez dépenser 1 Point d'Héroisme pour reprendre vos esprits pendant 1 round.",
"BOL.chat.vitalityheroism": "Vous pouvez dépenser 1 Point d'Héroisme/Vilainie pour reprendre vos esprits pendant 1 round.",
"BOL.chat.vitalityheroismhint": "Dans ce cas votre vitalité remonte à son maximum divisé par 2 (arrondi au supérieur).",
"BOL.chat.vitalitydying": "La Vitalité de {name} est de {hp} ! Il est mourant ...",
"BOL.chat.vitalitydyingheroism": "Vous pouvez cependant dépenser 1 Point d'Héroisme pour Défier la Mort (cf. page 58).",
"BOL.chat.vitalitydyingheroism": "Vous pouvez cependant dépenser 1 Point d'Héroisme/Vilainie pour Défier la Mort (cf. page 58).",
"BOL.chat.alchemytitle": "Préparation Alchimique : {name}",
"BOL.chat.alchemypoints": "Points de Création Investis : {pcCostCurrent}",
"BOL.chat.alchemysuccess": "La préparation alchimique a été réalisée avec succès !<br>Créez l'item ou l'effet correspondant dans votre Inventaire.<br>L'avancement dans la préparation a été remis à 0.",
@ -372,25 +484,70 @@
"BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible",
"BOL.chat.fightoption": "Option de combat",
"BOL.chat.reroll": "Relancer (1 P. Heroisme)",
"BOL.chat.toheroic": "Transformer en succés Héroïque (1 P. Héroisme)",
"BOL.chat.tolegend": "Transformer en succes Légendaire (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.toheroic": "Transformer en succés Héroïque (1 P. Héroisme/Vilainie)",
"BOL.chat.tolegend": "Transformer en succes Légendaire (1 P. Heroisme/Vilainie)",
"BOL.chat.hurttitle": "{name} va encaisser {damageTotal} dégats !",
"BOL.chat.armordefault": "C'est une attaque au défaut de l'armure : vous devez encaisser SANS la protection de l'armure !",
"BOL.chat.witharmor": "Encaisser avec la protection de l'armure",
"BOL.chat.withoutarmor": "Encaisser sans la protection de l'armure",
"BOL.chat.shakeoff": "Juste une égratignure (1 Point d'Héroisme)",
"BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme)",
"BOL.chat.shakeoff": "Juste une égratignure (1 Point d'Héroisme/Vilainie)",
"BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme/Vilainie)",
"BOL.chat.damagesummary": "Dégats subis par {name}",
"BOL.chat.protectvalue": "Protection de l'armure",
"BOL.chat.noprotectvalue": "Aucune protection d'armure !",
"BOL.chat.heroreducedamage": "Un point d'héroisme dépensé, pour une réduction des dommages supplémentaire de {total}.",
"BOL.chat.herosplintered": "Aucun dommage encaissé, grâce à la parade in-extremis avec {weaponHero.name}. L'arme a été détruite pendant cette parade ! Un point d'héroisme a également été dépensé.",
"BOL.chat.heroreducedamage": "Un point d'héroisme/vilainie dépensé, pour une réduction des dommages supplémentaire de {total}.",
"BOL.chat.herosplintered": "Aucun dommage encaissé, grâce à la parade in-extremis avec {weaponHero.name}. L'arme a été détruite pendant cette parade ! Un point d'héroisme/vilainie a également été dépensé.",
"BOL.chat.finaldamage": "Encaissement final : {finalDamage} dégats !",
"BOL.chat.spell": "Sort",
"BOL.chat.spellcost": "Cout en Points de Pouvoir",
"BOL.chat.spellremaining": "Points de Pouvoir restants",
"BOL.chat.nodamagesummary": "Aucun dégats n'a été subi ....",
"BOL.chat.damageresume": "{name} a subi des blessures ...",
"BOL.chat.fumblemessage": "Si vous acceptez les conséquences d'un echec catastrophique (au choix du MJ), vous pourrez bénéficier d'1 point d'Héroïsme supplémentaire",
"BOL.chat.rangeinfo": "Tir/Lancer de {attackerName} vers {defenderName}",
"BOL.chat.rangeweaponinfo": "Arme : {weaponName} - Portée de base {weaponRange}",
"BOL.chat.rangeout": "Modificateur : Hors de portée",
"BOL.chat.range0": "Modificateur : Bout portant (+1)",
"BOL.chat.range1": "Modificateur : Portée courte (0)",
"BOL.chat.range2": "Modificateur : Portée moyenne (-1)",
"BOL.chat.range3": "Modificateur : Portée longue (-2)",
"BOL.chat.range4": "Modificateur : Portée très longue (-4)",
"BOL.chat.range5": "Modificateur : Portée extrême (-6)",
"BOL.chat.range6": "Modificateur : Portée maximale (-8)",
"BOL.chat.rangeprefix": "Distance évaluée : ",
"BOL.chat.rangevisible": "La ligne de vue est dégagée entre les protagonistes.",
"BOL.chat.rangenotvisible": "La ligne de vue est bloquée entre les protagonistes.",
"BOL.chat.rangetitle": "Information MJ",
"BOL.chat.weaponreroll1": "Pour information, cette arme relance les 1 sur ses dégâts.",
"BOL.chat.rollbougette": "Jet de Bougette",
"BOL.chat.bougettesuccess": "Votre bougette reste inchangée !",
"BOL.chat.bougettefailure": "Vous avez trop dépensé, votre bougette s'est réduite...",
"BOL.chat.initiative": "Rang d'intiative (10 à 1)",
"BOL.chat.horoscope": "Horoscope",
"BOL.chat.horoscopepoints": "Coût : {points} Points d'Astrologie",
"BOL.chat.horoscopeminorsuccess": "Votre horoscope mineur est un succès : éditez le nom de l'horoscope sur votre fiche. Vous bénéficiez d'1 dé Bonus pour cette situation.",
"BOL.chat.horoscopeminorfailure": "Votre horoscope mineur est un échec : éditez le nom de l'horoscope sur votre fiche. Vous souffrez d'1 dé Malus pour cette situation.",
"BOL.chat.horoscopemajorsuccess": "Votre horoscope majeur est un succès : {horoscopeName} bénéficie d'1 point d'Héroisme de plus pour cette aventure. Ce point a été ajouté automatiquement.",
"BOL.chat.horoscopemajorfailure": "Votre horoscope majeur est un échec : {horoscopeName} a perdu 1 point d'Héroisme pour cette aventure. Ce point a été enlevé automatiquement.",
"BOL.chat.horoscopemajorgroupsuccess": "Votre horoscope majeur de groupe est un succès. Vous et vos amis bénéficiez de {careerBonus} dés bonus pendant cette aventure.",
"BOL.chat.horoscopemajorgroupfailure": "Votre horoscope majeur de groupe est un échec. Vous et vos amis souffrez de {careerBonus} dés malus pendant cette aventure.",
"BOL.chat.usedhoroscope": "Horoscope utilisé",
"BOL.chat.horoscopedeleted": "Le(s) Horoscopes utilisé(s) a/ont été supprimé(s) automatiquement.",
"BOL.chat.criticaloptions": "Succès critique !! Vous pouvez faire (1 option au choix) :",
"BOL.chat.criticalcarnage": "Faire un Carnage : vous avez une attaque gratuite supplémentaire. Cette seconde attaque ne peut bénéficier d'un Point d'Héroisme/vilainie.",
"BOL.chat.criticalplus6": "Coup Dévastateur : +6 aux dommages (cf bouton ci-dessous).",
"BOL.chat.criticalprecise": "Coup Précis : Vous frappez pour diminuer les capacités de votre adversaire. Décrivez ce que vous faites, et si le MJ l'accepte, votre opposant subira un Dé de Malus pour les actions concernées.",
"BOL.chat.criticalunarm": "Désarmement : Si votre adversaire a une arme en main, vous le désarmez.",
"BOL.chat.criticalrabble": "Massacrer la piétaille : Si vous combattez de la Piétaille, les résultats des dommages indiquent le nombre d'adversaires mis hors de combat.",
"BOL.chat.criticalpush": "Renversement : Si la taille le permet, vous poussez votre adversaire au sol, il souffrira d'1 Dé de Malus pour toutes ses actions au round suivant.",
"BOL.chat.criticalup": "Transformer en Légendaire : En dépensant 1 point d'Héroisme/Vilainie, vous pouvez transformer ce Succès Héroïque en Légendaire, qui vous permet de prendre 2 options dans la liste ci-dessus (cf. bouton pour un +12 aux dommages par exemple).",
"BOL.chat.criticalinfo": "C'est un succès Héroïque ou Légendaire ! Choisissez vos options et effets !",
"BOL.chat.criticalbuttonjournal": "Succès Héroïque/Légendaire",
"BOL.chat.losshp": "{name} a perdu {lossHP} points de Vitalité. Si il se repose quelques minutes, il peut récupérer {recupHP} points de Vitalité.",
"BOL.chat.applyrecup": "Récupérer pendant quelques minutes (+{recupHP} Vitalité)",
"BOL.chat.inforecup": "{name} vient de récupérer {recupHP} points de Vitalité après quelques minutes de repos.",
"BOL.dialog.soeasy": "Inmanquable (+4)",
"BOL.dialog.veryeasy": "Trés Facile (+2)",
@ -426,9 +583,29 @@
"BOL.ui.bionotes": "Notes",
"BOL.chat.welcome1": "Bienvenue dans Barbarians of Lemuria (Ludospherik version)",
"BOL.chat.welcome2": "Les livres nécessaires pour jouer sont disponibles sur le site de l'éditeur : http://www.ludospherik.fr/content/14-barbarians-of-lemuria",
"BOL.chat.welcome2": "Les livres nécessaires pour jouer sont disponibles sur le site de <a href='http://www.ludospherik.fr/content/14-barbarians-of-lemuria'>l'éditeur Ludospherik.</a>",
"BOL.chat.welcome3": "Les cartes intégrées au système le sont grace à l'aimable autorisation de leur auteur Guillaume Tavernier et des éditions Ludospherik. Merci à eux !.",
"BOL.chat.welcome4": "Tout le support et le suivi de ce système est disponible via le Discord Foundry FR : https://discord.gg/pPSDNJk",
"BOL.chat.welcome5": "Bon jeu en Lemurie !"
"BOL.chat.welcome4": "Tout le support et le suivi de ce système est disponible via le <a href='https://discord.gg/pPSDNJk'>Discord Foundry FR</a>.",
"BOL.chat.welcome5": "Consulter l'aide en ligne pour plus d'informations : @UUID[Compendium.bol.aides-de-jeu.97rugQOtiwt8zPfQ]{Aide du Jeu}.",
"BOL.chat.welcome6": "Bon jeu en Lemurie !",
"BOL.chat.nodamage": "Ne pas appliquer les dommages",
"BOL.settings.rollArmor": "Effectuer des jets pour les armures",
"BOL.settings.rollArmorTooltip": "Effectue un jet de dés pour les armures (valeur fixe si désactivé)",
"BOL.settings.useBougette": "Utiliser la Bougette (règle fan-made)",
"BOL.settings.useBougetteTooltip": "Utilise un indicateur de Bougette, comme décrit dans l'aide de jeu Annales Lemurienne du RatierBretonnien (https://www.lahiette.com/leratierbretonnien/)",
"BOL.settings.removeDead": "Enlever les PNJs morts automatiquement au round suivant",
"BOL.settings.removeDeadTooltip": "Supprime les PNJ (piétaille, créatures, coriaces) automatiquement du combat lorsqu'ils sont à 0 Vitalité ou moins, lors du passage au round suivant",
"BOL.settings.diceFormula": "Formule de dés",
"BOL.settings.diceFormulaTooltip": "Formule de dés utilisée pour les jets de dés (par défaut 2d6)",
"BOL.settings.diceSuccessValue" : "Seuil de succès",
"BOL.settings.diceSuccessValueTooltip": "Seuil de succès pour les jets de dés (par défaut 9 pour 2d6)",
"BOL.settings.diceCriticalValue" : "Seuil de succès critique",
"BOL.settings.diceCriticalValueTooltip": "Seuil de succès critique pour les jets de dés (par défaut 12 pour 2d6)",
"BOL.settings.diceCriticalFailure" : "Valeur max d'échec critique",
"BOL.settings.diceCriticalFailureTooltip": "Valeur max d'échec critique pour les jets de dés (par défaut 2 pour 2d6)",
"BOL.settings.defaultLogoActorSheetPath" : "Chemin du logo des fiches de perso",
"BOL.settings.defaultLogoPathActorSheetTooltip": "Vous pouvez changer le logo BoL des fiches de perso, pour jouer dans un autre univers (idéalement 346 x 200, défaut : /systems/bol/ui/logo.webp)",
"BOL.settings.defaultLogoTopLeftPath" : "Chemin du logo haut gauche",
"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)"
}

View File

@ -12,7 +12,7 @@ export class BoLActorSheet extends ActorSheet {
return mergeObject(super.defaultOptions, {
classes: ["bol", "sheet", "actor"],
template: "systems/bol/templates/actor/actor-sheet.hbs",
width: 600,
width: 860,
height: 600,
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }]
@ -25,6 +25,13 @@ export class BoLActorSheet extends ActorSheet {
activateListeners(html) {
super.activateListeners(html);
function onLoad() {
let logoSheet = BoLUtility.getLogoActorSheet()
$(".bol-actor-form").css("backgroundImage",`url(${logoSheet})`)
}
// Setup everything onload
$(function () { onLoad(); });
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
@ -53,6 +60,12 @@ export class BoLActorSheet extends ActorSheet {
const li = $(ev.currentTarget).parents(".item");
this.actor.spendAlchemyPoint(li.data("itemId"), 1)
})
html.find(".inc-dec-btns-resource").click((ev) => {
const dataset = ev.currentTarget.dataset;
const target = dataset.target
const incr = parseInt(dataset.incr)
this.actor.incDecResources(target, incr)
})
// Incr./Decr. career ranks
html.find(".inc-dec-btns").click((ev) => {
@ -87,8 +100,8 @@ export class BoLActorSheet extends ActorSheet {
// Delete Inventory Item
html.find('.item-delete').click(ev => {
Dialog.confirm({
title: "Suppression",
content: `Vous êtes sûr de vouloir supprimer cet item ?`,
title: game.i18n.localize("BOL.ui.deletetitle"),
content: game.i18n.localize("BOL.ui.confirmdelete"),
yes: () => {
const li = $(ev.currentTarget).parents(".item");
this.actor.deleteEmbeddedDocuments("Item", [li.data("itemId")])
@ -107,7 +120,7 @@ export class BoLActorSheet extends ActorSheet {
/* -------------------------------------------- */
/** @override */
getData(options) {
async getData(options) {
const data = super.getData(options)
const actorData = duplicate(data)
let formData = duplicate(data)
@ -118,6 +131,7 @@ export class BoLActorSheet extends ActorSheet {
formData.attributes = this.actor.attributes
formData.aptitudes = this.actor.aptitudes
formData.resources = this.actor.getResourcesFromType()
formData.xp = this.actor.system.xp
formData.equipment = this.actor.equipment
formData.equipmentCreature = this.actor.equipmentCreature
formData.weapons = this.actor.weapons
@ -126,25 +140,33 @@ export class BoLActorSheet extends ActorSheet {
formData.alchemy = this.actor.alchemy
formData.containers = this.actor.containers
formData.treasure = this.actor.treasure
formData.boleffects = this.actor.boleffects
formData.alchemyrecipe = this.actor.alchemyrecipe
formData.horoscopes = this.actor.horoscopes
formData.vehicles = this.actor.vehicles
formData.fightoptions = this.actor.fightoptions
formData.ammos = this.actor.ammos
formData.misc = this.actor.misc
formData.combat = this.actor.buildCombat()
formData.combatCreature = this.actor.buildCombatCreature()
formData.initiativeRank = this.actor.getInitiativeRank()
//formData.combatCreature = this.actor.buildCombatCreature()
formData.features = this.actor.buildFeatures()
formData.isGM = game.user.isGM
formData.options = this.options
formData.owner = this.document.isOwner
formData.editScore = this.options.editScore
formData.useBougette = BoLUtility.getUseBougette()
formData.useBougette = (this.actor.type == "character" && BoLUtility.getUseBougette()) || false
formData.bougette = this.actor.getBougette()
formData.charType = this.actor.getCharType()
formData.villainy = this.actor.getVillainy()
formData.villainy = this.actor.getVillainy()
formData.biography = await TextEditor.enrichHTML(this.object.system.details?.biography || "", {async: true})
formData.notes = await TextEditor.enrichHTML(this.object.system.details.notes || "", {async: true})
formData.isSorcerer = this.actor.isSorcerer()
formData.isAlchemist = this.actor.isAlchemist()
formData.isAstrologer = this.actor.isAstrologer()
formData.isMysteries = formData.isSorcerer || formData.isAlchemist || formData.isAstrologer
formData.isPriest = this.actor.isPriest()
formData.horoscopeGroupList = game.settings.get("bol", "horoscope-group")
formData.isGM = game.user.isGM
@ -226,10 +248,22 @@ export class BoLActorSheet extends ActorSheet {
case "attributexp":
this.actor.incAttributeXP(dataset.key)
break;
case "bougette":
this.actor.rollBougette()
break;
case "careerxp":
this.actor.incCareerXP( li.data("item-id"))
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
default: break;
}
}

View File

@ -1,4 +1,4 @@
import { BoLDefaultRoll } from "../controllers/bol-rolls.js";
import { BoLDefaultRoll, BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js";
/**
@ -21,19 +21,80 @@ export class BoLActor extends Actor {
super.prepareData()
}
/* -------------------------------------------- */
async _preCreate(data, options, user) {
await super._preCreate(data, options, user);
// Configure prototype token settings
const prototypeToken = {};
if (this.type === "character") Object.assign(prototypeToken, {
sight: { enabled: true }, actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
});
this.updateSource({ prototypeToken });
}
/* -------------------------------------------- */
isHeroAdversary() {
if (this.type === 'character') {
return true
}
if (this.type === 'encounter' && this.chartype == "adversary") {
return true
}
return false
}
/* -------------------------------------------- */
getCharType() {
if (this.type === 'character') {
return 'player'
return "player"
}
return 'tough'
return this.system.chartype
}
/* -------------------------------------------- */
getVillainy() {
if (this.type === 'character') {
return false
if (this.type === 'encounter' && this.chartype == "adversary") {
return true
}
return false
}
/* -------------------------------------------- */
getInitiativeMalus() {
if (this.type === 'encounter' && (this.chartype == "adversary" || this.chartype == "tough")) {
return this.system.aptitudes.init.value
}
return 0
}
/* -------------------------------------------- */
getBougette() {
if (this.type == "character") {
let b = duplicate(this.system.bougette)
b.label = game.i18n.localize(game.bol.config.bougetteState[String(this.system.bougette.value)])
b.diceImg = "icons/dice/" + game.bol.config.bougetteDice[String(this.system.bougette.value)] + "black.svg"
return b
}
return undefined
}
/* -------------------------------------------- */
async rollBougette() {
if (this.type == "character") {
let attribute = duplicate(this.system.attributes.vigor)
let rollData = BoLRoll.getCommonRollData(this, "bougette", attribute, undefined)
rollData.formula = game.bol.config.bougetteDice[String(this.system.bougette.value)]
let r = new BoLDefaultRoll(rollData)
r.roll()
}
}
/* -------------------------------------------- */
decBougette() {
if (this.type == "character") {
let bougette = duplicate(this.system.bougette)
bougette.value = Math.max(Number(bougette.value) - 1, 0)
this.update({ 'system.bougette': bougette })
}
return true
}
/* -------------------------------------------- */
@ -41,31 +102,57 @@ export class BoLActor extends Actor {
if (this.type == 'character') {
let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus
if (this.system.resources.hp.max != newVitality) {
this.update({ 'system.resources.hp.max': newVitality })
let actor = this
setTimeout(function () { actor.update({ 'system.resources.hp.max': newVitality }) }, 800)
}
let newPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus
if (this.system.resources.power.max != newPower) {
this.update({ 'system.resources.power.max': newPower })
let actor = this
setTimeout(function () { actor.update({ 'system.resources.power.max': newPower }) }, 800)
}
}
}
/* -------------------------------------------- */
prepareDerivedData() {
super.prepareDerivedData()
this.updateResourcesData()
this.manageHealthState();
if (this.type == "vehicle") {
} else {
super.prepareDerivedData()
if (this.id) {
this.updateResourcesData()
this.manageHealthState()
}
}
}
/* -------------------------------------------- */
get details() {
return this.system.details
}
addEffectModifiers(myList, dataPath) {
for (let attr of myList) {
attr.numModifier = 0
attr.diceModifier = ""
let effects = this.items.filter(i => i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier == dataPath + attr.key)
for (let effect of effects) {
if (Number(effect.system.properties.modifier)) {
attr.numModifier += Number(effect.system.properties.modifier)
} else {
attr.diceModifier += "+" + effect.system.properties.modifier
}
}
}
}
get attributes() {
return Object.values(this.system.attributes)
let attrList = duplicate(Object.values(this.system.attributes))
this.addEffectModifiers(attrList, "system.attributes.")
return attrList
}
get aptitudes() {
return Object.values(this.system.aptitudes)
let aptList = Object.values(this.system.aptitudes)
this.addEffectModifiers(aptList, "system.aptitudes.")
return aptList
}
/* -------------------------------------------- */
@ -98,6 +185,13 @@ export class BoLActor extends Actor {
if (fo && fo.system.properties.fightoptiontype == "attack") {
defMod += -1
}
// Apply defense effects
for (let i of this.items) {
if (i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier.includes("aptitudes.def")) {
defMod += Number(i.system.properties.modifier)
}
}
console.log("Defense : ", defMod)
return this.system.aptitudes.def.value + defMod
}
@ -186,30 +280,35 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */
get armorMalusValue() { // used for Fight Options
for (let armor of this.armors) {
if (armor.system.properties.armorQuality.includes("light")) {
if (armor.system.properties.armorQuality?.includes("light")) {
return 1
}
if (armor.system.properties.armorQuality.includes("medium")) {
if (armor.system.properties.armorQuality?.includes("medium")) {
return 2
}
if (armor.system.properties.armorQuality.includes("heavy")) {
if (armor.system.properties.armorQuality?.includes("heavy")) {
return 3
}
}
return 0
}
get resources() {
return Object.values(this.system.resources)
}
get boleffects() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "boleffect")
}
get horoscopes() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "horoscope")
}
get boons() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "boon");
return duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "boon") || []);
}
get flaws() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw");
return duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw") || []);
}
get careers() {
return duplicate( this.items.filter(i => i.type === "feature" && i.system.subtype === "career") || [])
return duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "career") || [])
}
get origins() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "origin");
@ -233,7 +332,7 @@ export class BoLActor extends Actor {
return this.items.filter(i => i.type === "item")
}
get equipmentCreature() {
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && (( i.system.subtype === "weapon" && i.system.properties.natural === true) || (i.system.subtype === "armor")) )
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && ((i.system.subtype === "weapon" && i.system.properties.natural === true) || (i.system.subtype === "armor")))
}
get armors() {
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "armor");
@ -244,9 +343,11 @@ export class BoLActor extends Actor {
get shields() {
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "shield");
}
get vehicleWeapons() {
return this.items.filter(i => i.type === "item" && i.system.category === "vehicleweapon")
}
get weapons() {
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "weapon");
return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "weapon")
}
get protections() {
return this.armors.concat(this.helms).concat(this.shields)
@ -288,10 +389,11 @@ export class BoLActor extends Actor {
}
get bonusBoons() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "boon" && i.system.properties.isbonusdice);
let boons = this.items.filter(i => i.type === "feature" && i.system.subtype === "boon" && i.system.properties.isbonusdice)
return duplicate(boons || [])
}
get malusFlaws() {
return this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw" && i.system.properties.ismalusdice);
return duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw" && i.system.properties.ismalusdice) || []);
}
isSorcerer() {
@ -304,6 +406,11 @@ export class BoLActor extends Actor {
return true
return false
}
isAstrologer() {
if (this.careers.find(item => item.system.properties.astrologer == true))
return true
return false
}
isPriest() {
if (this.careers.find(item => item.system.properties.priest == true))
return true
@ -322,6 +429,23 @@ export class BoLActor extends Actor {
return ppCostArmor
}
/*-------------------------------------------- */
getDamageAttributeValue(attrDamage) {
let attrDamageValue = 0
if (attrDamage.includes("vigor")) {
attrDamageValue = this.system.attributes.vigor.value
if (attrDamage.includes("half")) {
attrDamageValue = Math.floor(attrDamageValue / 2)
}
// Apply vigor effects
for (let i of this.items) {
if (i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier.includes("vigor")) {
attrDamageValue += Number(i.system.properties.modifier)
}
}
}
return attrDamageValue
}
/*-------------------------------------------- */
getArmorAgiMalus() {
let malusAgi = 0
for (let armor of this.protections) {
@ -346,7 +470,7 @@ export class BoLActor extends Actor {
spendPowerPoint(ppCost) {
let newPP = this.system.resources.power.value - ppCost
newPP = (newPP < 0) ? 0 : newPP
this.update({ 'data.resources.power.value': newPP })
this.update({ 'system.resources.power.value': newPP })
return newPP
}
@ -354,7 +478,89 @@ export class BoLActor extends Actor {
resetAlchemyStatus(alchemyId) {
let alchemy = this.items.get(alchemyId)
if (alchemy) {
this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'data.properties.pccurrent': 0 }])
this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'system.properties.pccurrent': 0 }])
}
}
/*-------------------------------------------- */
spentAstrologyPoints(points) {
let astrology = duplicate(this.system.resources.astrologypoints)
astrology.value -= points
astrology.value = Math.max(astrology.value, 0)
this.update({ 'system.resources.astrologypoints': astrology })
}
/*-------------------------------------------- */
getHoroscopesBonus() {
let astro = this.items.filter(it => it.type == "feature" && it.system.subtype == "horoscope" && !it.system.properties.ishoroscopemajor
&& it.system.properties.horoscopeanswer == "favorable")
return astro
}
/*-------------------------------------------- */
getHoroscopesMalus() {
let astro = this.items.filter(it => it.type == "feature" && it.system.subtype == "horoscope" && !it.system.properties.ishoroscopemajor
&& it.system.properties.horoscopeanswer == "unfavorable")
return astro
}
/*-------------------------------------------- */
manageHoroscope(rollData) {
//Spent points
this.spentAstrologyPoints(rollData.astrologyPointsCost)
if (rollData.horoscopeType == "minor") {
let horoscope = {
name: "SITUATION A SPECIFIER", type: "feature",
img: "icons/magic/perception/eye-ringed-glow-angry-large-red.webp",
system: {
subtype: "horoscope", properties: {
ishoroscopemajor: false,
horoscopeanswer: (rollData.isSuccess) ? "favorable" : "unfavorable",
rank: rollData.careerBonus
}
}
}
this.createEmbeddedDocuments('Item', [horoscope])
}
if (rollData.horoscopeType == "major") {
let actorHoroscope = this
if (rollData.targetId) {
let token = game.scenes.current.tokens.get(rollData.targetId)
actorHoroscope = token.actor
}
if (rollData.isSuccess) {
actorHoroscope.addHeroPoints(1)
} else {
actorHoroscope.subHeroPoints(1)
}
rollData.horoscopeName = actorHoroscope.name
}
if (rollData.horoscopeType == "majorgroup") {
let rID = randomID(16)
let horoscopes = duplicate(game.settings.get("bol", "horoscope-group"))
horoscopes[rID] = {
id: rID,
name: game.i18n.localize("BOL.ui.groupHoroscope") + this.name,
maxDice: rollData.careerBonus,
availableDice: rollData.careerBonus,
type: (rollData.isSuccess) ? "bonus" : "malus"
}
game.settings.set("bol", "horoscope-group", horoscopes)
}
}
/*-------------------------------------------- */
getAstrologyPoints() {
return this.system.resources.astrologypoints.value
}
/*-------------------------------------------- */
removeHoroscopeMinor(rollData) {
let toDel = []
for (let horo of rollData.selectedHoroscope) {
toDel.push(horo._id)
}
if (toDel.length > 0) {
this.deleteEmbeddedDocuments('Item', toDel)
}
}
@ -368,13 +574,20 @@ export class BoLActor extends Actor {
newPC = (newPC < 0) ? 0 : newPC
this.update({ 'data.resources.alchemypoints.value': newPC })
newPC = alchemy.system.properties.pccurrent + pcCost
await this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'data.properties.pccurrent': newPC }])
await this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'system.properties.pccurrent': newPC }])
} else {
ui.notifications.warn("Plus assez de Points de Création !")
ui.notifications.warn(game.i18n.localize("BOL.ui.nomorealchemypoints"))
}
}
}
/*-------------------------------------------- */
getAstrologerBonus() {
let astrologer = this.careers.find(item => item.system.properties.astrologer == true)
if (astrologer) {
return astrologer.system.rank
}
return 0;
}
/*-------------------------------------------- */
getAlchemistBonus() {
let sorcerer = this.careers.find(item => item.system.properties.alchemist == true)
@ -394,25 +607,28 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */
heroReroll() {
if (this.type == 'character') {
if (this.type == 'character' || this.system.villainy == 'adversary') {
return this.system.resources.hero.value > 0;
} else {
if (this.system.type == 'adversary') {
return this.system.resources.hero.value > 0;
}
}
return false
}
/*-------------------------------------------- */
getHeroPoints() {
if (this.type == 'character' || this.system.villainy == 'adversary') {
return this.system.resources.hero.value
}
return 0
}
/*-------------------------------------------- */
getResourcesFromType() {
let resources = {};
if (this.type == 'encounter') {
resources['hp'] = this.system.resources.hp;
if (this.system.type != 'base') {
if (this.system.chartype != 'base') {
resources['faith'] = this.system.resources.faith
resources['power'] = this.system.resources.power
}
if (this.system.type == 'adversary') {
if (this.system.chartype == 'adversary') {
resources['hero'] = duplicate(this.system.resources.hero)
resources['hero'].label = "BOL.resources.villainy"
}
@ -463,6 +679,11 @@ export class BoLActor extends Actor {
"label": "BOL.featureSubtypes.gods",
"ranked": false,
"items": this.godsfaith
},
"boleffects": {
"label": "BOL.featureSubtypes.effects",
"ranked": false,
"items": this.boleffects
}
}
}
@ -478,6 +699,15 @@ export class BoLActor extends Actor {
"options": false,
"items": this.melee
},
"natural": {
"label": "BOL.combatCategory.natural",
"weapon": true,
"protection": false,
"blocking": false,
"ranged": false,
"options": false,
"items": this.natural
},
"ranged": {
"label": "BOL.combatCategory.ranged",
"weapon": true,
@ -517,28 +747,6 @@ export class BoLActor extends Actor {
}
}
buildCombatCreature() {
return {
"natural": {
"label": "BOL.combatCategory.natural",
"weapon": true,
"protection": false,
"blocking": false,
"ranged": false,
"options": false,
"items": this.natural
},
"protections": {
"label": "BOL.combatCategory.protections",
"weapon": false,
"protection": true,
"blocking": false,
"ranged": false,
"options": false,
"items": this.protections
},
}
}
/*-------------------------------------------- */
buildRollList() {
@ -558,33 +766,173 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */
buildListeActions() {
return this.melee.concat(this.ranged).concat(this.natural)
return this.melee.concat(this.ranged).concat(this.natural).concat(this.fightoptions)
}
/*-------------------------------------------- */
async manageHealthState() {
let hpID = "lastHP" + this.id
let lastHP = await this.getFlag("world", hpID)
if (lastHP != this.system.resources.hp.value && game.user.isGM ) { // Only GM sends this
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
await this.setFlag("world", hpID, this.system.resources.hp.value)
let prone = this.effects.find(ef => ef.label == "EFFECT.StatusProne")
let dead = this.effects.find(ef => ef.label == "EFFECT.StatusDead")
if (this.system.resources.hp.value <= 0) {
if (!prone) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ label: 'EFFECT.StatusProne', icon: 'icons/svg/falling.svg', flags: { core: { statusId: 'prone' } } }
])
}
if (this.system.resources.hp.value < -5 && !dead) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ label: 'EFFECT.StatusDead', icon: 'icons/svg/skull.svg', flags: { core: { statusId: 'dead' } } }
])
}
ChatMessage.create({
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 })
content: await renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() })
})
} else {
if (prone) {
await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id])
}
if (dead) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id])
}
}
}
}
/*-------------------------------------------- */
registerInit(initScore, isCritical, isFumble) {
this.update({ 'system.combat.lastinit': initScore, 'system.combat.iscritical': isCritical, 'system.combat.isfumble': isFumble })
async registerInit(rollData) {
rollData.actor = undefined // Cleanup if present
await this.setFlag("world", "last-initiative", rollData)
}
/*-------------------------------------------- */
getLastInitData() {
return this.system.combat
storeVitaliteCombat() {
this.setFlag("world", "vitalite-before-combat", duplicate(this.system.resources.hp))
}
/*-------------------------------------------- */
async displayRecuperation() {
let previousHP = this.getFlag("world", "vitalite-before-combat")
let lossHP = previousHP.value - this.system.resources.hp.value
//console.log(">>>>> RECUP INFO", previousHP, this.system.resources.hp.value)
if (previousHP && lossHP > 0 && this.system.resources.hp.value > 0) {
let msg = await ChatMessage.create({
alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', {
name: this.name,
img: this.img,
actorId: this.id,
lossHP: lossHP,
recupHP: Math.ceil(lossHP / 2)
})
})
}
this.unsetFlag("world", "vitalite-before-combat")
}
/*-------------------------------------------- */
async applyRecuperation(recupHP) {
let hp = duplicate(this.system.resources.hp)
//console.log("RECUP !!!!", hp, recupHP)
hp.value += Number(recupHP)
hp.value = Math.min(hp.value, hp.max)
this.update({ 'system.resources.hp': hp })
let msg = await ChatMessage.create({
alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: game.i18n.format("BOL.chat.inforecup", { name: this.name, recupHP: recupHP })
})
}
/*-------------------------------------------- */
clearInitiative() {
this.unsetFlag("world", "last-initiative")
}
/*-------------------------------------------- */
getSize() {
if (this.system.details.size.length > 0 && game.bol.config.creatureSize[this.system.details.size]) {
return game.bol.config.creatureSize[this.system.details.size].order
}
return game.bol.config.creatureSize["medium"].order // Medium size per default
}
/*-------------------------------------------- */
checkNumeric(myObject) {
if (myObject) {
for (let key in myObject) {
if (myObject[key].value === null) {
myObject[key].value = 0
}
if (myObject[key].value === NaN) {
myObject[key].value = 0
}
}
}
}
/*-------------------------------------------- */
_preUpdate(data, options, userId) {
if (data.system?.attributes) {
this.checkNumeric(data.system.attributes)
}
if (data.system?.aptitudes) {
this.checkNumeric(data.system.aptitudes)
}
if (data.system?.resources) {
this.checkNumeric(data.system.resources)
}
super._preUpdate(data, options, userId)
}
/*-------------------------------------------- */
getInitiativeRank(rollData = undefined, isCombat = false, combatData) {
let fvttInit = 4 // Pietaille par defaut
if (this.type == 'character') {
fvttInit = 5
if (!rollData) {
if (isCombat) {
if (game.user.isGM) {
if (this.hasPlayerOwner) {
game.socket.emit("system.bol", { name: "msg_request_init_roll", data: { actorId: this.id, combatData } })
} else {
BoLRoll.aptitudeCheck(this, "init", undefined, combatData);
}
}
}
} else {
if (rollData.isLegendary) {
fvttInit = 10
} else if (rollData.isCritical) {
fvttInit = 9
} else if (rollData.isSuccess) {
fvttInit = 8
} else if (rollData.isFumble) {
fvttInit = 3
}
}
}
if (this.getCharType() == 'adversary') {
fvttInit = 7
}
if (this.getCharType() == 'tough') {
fvttInit = 6
}
if (this.getCharType() == 'creature') {
let mySize = this.getSize()
let sizeSmall = game.bol.config.creatureSize["small"].order
let sizeMedium = game.bol.config.creatureSize["medium"].order
if (mySize >= sizeSmall && mySize <= sizeMedium) {
fvttInit = 6
}
if (mySize > sizeMedium) {
fvttInit = 7
}
}
return fvttInit
}
/*-------------------------------------------- */
@ -593,11 +941,22 @@ export class BoLActor extends Actor {
newHeroP = (newHeroP < 0) ? 0 : newHeroP;
await this.update({ 'system.resources.hero.value': newHeroP });
}
/*-------------------------------------------- */
async addHeroPoints(nb) {
let newHeroP = this.system.resources.hero.value + nb;
newHeroP = (newHeroP < 0) ? 0 : newHeroP;
await this.update({ 'system.resources.hero.value': newHeroP });
}
/*-------------------------------------------- */
incDecResources(target, value) {
let newValue = this.system.resources[target].value + value
this.update({ [`system.resources.${target}.value`]: newValue })
}
/*-------------------------------------------- */
async sufferDamage(damage) {
let newHP = this.system.resources.hp.value - damage
await this.update({ 'system.resources.hp.value': newHP })
await this.update({ 'system.resources.hp.value': newHP })
}
/* -------------------------------------------- */
@ -610,13 +969,13 @@ export class BoLActor extends Actor {
} else if (protect.system.subtype == 'armor') {
if (BoLUtility.getRollArmor()) {
if (!protect.system.properties.soak.formula || protect.system.properties.soak.formula == "") {
ui.notifications.warn(`L'armure ${protect.name} n'a pas de formule pour la protection !`)
ui.notifications.warn(game.i18n.localize("BOL.ui.armornoformula", protect.name))
} else {
formula += "+" + " max(" + protect.system.properties.soak.formula +",0)"
formula += "+" + " max(" + protect.system.properties.soak.formula + ",0)"
}
} else {
if (protect.system.properties.soak.value == undefined) {
ui.notifications.warn(`L'armure ${protect.name} n'a pas de valeur fixe pour la protection !`)
ui.notifications.warn(game.i18n.localize("BOL.ui.armornoformula", protect.name))
} else {
formula += "+ " + protect.system.properties.soak.value
}
@ -631,7 +990,7 @@ export class BoLActor extends Actor {
rollProtection(itemId) {
let armor = duplicate(this.items.get(itemId))
if (armor) {
let armorFormula = "max("+armor.system.properties.soak.formula + ", 0)"
let armorFormula = "max(" + armor.system.properties.soak.formula + ", 0)"
let rollArmor = new Roll(armorFormula)
rollArmor.roll({ async: false }).toMessage()
}

View File

@ -0,0 +1,229 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js";
export class BoLVehicleSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["bol", "sheet", "actor"],
template: "systems/bol/templates/actor/vehicle-sheet.hbs",
width: 860,
height: 600,
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }]
});
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
function onLoad() {
let logoSheet = BoLUtility.getLogoActorSheet()
$(".bol-actor-form").css("backgroundImage",`url(${logoSheet})`)
}
// Setup everything onload
$(function () { onLoad(); });
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Add Inventory Item
html.find('.item-create').click(this._onItemCreate.bind(this));
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.items.get(li.data("itemId"));
item.sheet.render(true);
})
// Equip/Unequip item
html.find('.item-equip').click(this._onToggleEquip.bind(this));
html.find('.create_item').click(ev => {
this.actor.createEmbeddedDocuments('Item', [{ name: "Nouvel Equipement", type: "item" }], { renderSheet: true });
});
html.find(".toggle-fight-option").click((ev) => {
const li = $(ev.currentTarget).parents(".item")
this.actor.toggleFightOption(li.data("itemId"))
})
html.find(".inc-dec-btns-alchemy").click((ev) => {
const li = $(ev.currentTarget).parents(".item");
this.actor.spendAlchemyPoint(li.data("itemId"), 1)
})
// Incr./Decr. career ranks
html.find(".inc-dec-btns").click((ev) => {
const li = $(ev.currentTarget).parents(".item");
if (li) {
const item = this.actor.items.get(li.data("itemId"));
if (item) {
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
let value = eval("item." + target)
value = value || 0
//console.log("IncDec", item, target, value, operator, min, max)
if (operator === "minus") {
if (value >= min + incr) value -= incr;
else value = min;
}
if (operator === "plus") {
if (value <= max - incr) value += incr;
else value = max;
}
let update = { [`${target}`]: value };
item.update(update);
}
}
});
// Delete Inventory Item
html.find('.item-delete').click(ev => {
Dialog.confirm({
title: "Suppression",
content: `Vous êtes sûr de vouloir supprimer cet item ?`,
yes: () => {
const li = $(ev.currentTarget).parents(".item");
this.actor.deleteEmbeddedDocuments("Item", [li.data("itemId")])
li.slideUp(200, () => this.render(false));
},
no: () => { },
defaultYes: false,
});
});
// Rollable abilities.
html.find('.rollable').click(this._onRoll.bind(this));
}
/* -------------------------------------------- */
/** @override */
async getData(options) {
const data = super.getData(options)
const actorData = duplicate(data)
let formData = duplicate(data)
formData.config = game.bol.config
formData.name = this.actor.name
formData.img = this.actor.img
formData.system = duplicate(this.actor.system)
formData.weapons = this.actor.vehicleWeapons
formData.isGM = game.user.isGM
formData.options = this.options
formData.owner = this.document.isOwner
formData.editScore = this.options.editScore
formData.description = await TextEditor.enrichHTML(this.actor.system.description, {async: true})
formData.isGM = game.user.isGM
console.log("VEHICLEDATA", formData)
return formData;
}
/* -------------------------------------------- */
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
* @private
*/
_onItemCreate(event) {
event.preventDefault();
const header = event.currentTarget;
// Get the type of item to create.
const type = header.dataset.type;
// Grab any data associated with this control.
const data = duplicate(header.dataset);
// Initialize a default name.
const name = `New ${type.capitalize()}`;
// Prepare the item object.
const itemData = {
name: name,
type: type,
data: data
};
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.data["type"];
// Finally, create the item!
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
_onToggleEquip(event) {
event.preventDefault();
const li = $(event.currentTarget).closest(".item");
const item = this.actor.items.get(li.data("itemId"));
return this.actor.toggleEquipItem(item);
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
_onRoll(event) {
event.preventDefault();
const element = event.currentTarget
const dataset = element.dataset
const rollType = dataset.rollType
const li = $(event.currentTarget).closest(".item")
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(li.data("item-id"))
break;
case "damage":
this.actor.rollWeaponDamage(li.data("item-id"))
break;
case "aptitudexp":
this.actor.incAptitudeXP(dataset.key)
break;
case "attributexp":
this.actor.incAttributeXP(dataset.key)
break;
case "careerxp":
this.actor.incCareerXP( li.data("item-id"))
break;
default: break;
}
}
/** @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;
}
}

View File

@ -2,6 +2,7 @@
// Import Modules
import { BoLActor } from "./actor/actor.js"
import { BoLActorSheet } from "./actor/actor-sheet.js"
import { BoLVehicleSheet } from "./actor/vehicle-sheet.js"
import { BoLItem } from "./item/item.js"
import { BoLItemSheet } from "./item/item-sheet.js"
import { System, BOL } from "./system/config.js"
@ -15,6 +16,7 @@ import { BoLTokenHud } from "./system/bol-action-hud.js"
import { BoLHotbar } from "./system/bol-hotbar.js"
import { BoLAdventureGenerator } from "./system/bol-adventure-generator.js"
import { BoLCommands} from "./system/bol-commands.js"
import { BoLCharacterSummary} from "./system/bol-character-summary.js"
/* -------------------------------------------- */
Hooks.once('init', async function () {
@ -39,7 +41,7 @@ Hooks.once('init', async function () {
*/
CONFIG.Combat.initiative = {
formula: "2d6+@attributes.mind.value+@aptitudes.init.value",
decimals: 3
decimals: 2
};
// Define custom Entity classes
@ -49,7 +51,9 @@ Hooks.once('init', async function () {
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("bol", BoLActorSheet, { makeDefault: true });
Actors.registerSheet("bol", BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
Actors.registerSheet("bol", BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("bol", BoLItemSheet, { makeDefault: true });
@ -90,6 +94,9 @@ function registerUsageCount( registerKey ) {
}
// Simple API counter
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"`
//$.ajaxSetup({
//headers: { 'Access-Control-Allow-Origin': '*' }
//})
$.ajax(regURL)
}
}
@ -104,13 +111,19 @@ function welcomeMessage() {
game.i18n.localize("BOL.chat.welcome2") + "<p>" +
game.i18n.localize("BOL.chat.welcome3") + "<p>" +
game.i18n.localize("BOL.chat.welcome4") + "</p>" +
game.i18n.localize("BOL.chat.welcome5") + "</p>"
game.i18n.localize("BOL.chat.welcome5") + "<br>" +
game.i18n.localize("BOL.chat.welcome6")
} )
}
/* -------------------------------------------- */
Hooks.once('ready', async function () {
registerUsageCount('bol')
BoLUtility.ready()
BoLCharacterSummary.ready()
registerUsageCount(game.system.id)
welcomeMessage()
})

View File

@ -7,7 +7,7 @@ export class BoLRoll {
/* -------------------------------------------- */
static options() {
return { classes: ["bol", "dialog"], width: 480, height: 540 };
return { classes: ["bol", "dialog"], width: 480, height: 'fit-content' };
}
/* -------------------------------------------- */
@ -16,55 +16,132 @@ export class BoLRoll {
}
/* -------------------------------------------- */
static attributeCheck(actor, key) {
static updateApplicableEffects(rollData) {
let appEffects = []
for (let effect of rollData.bolEffects) {
if (effect.system.properties.identifier == "always") {
appEffects.push(effect)
} else if (effect.system.properties.identifier.includes(rollData.attribute.key)) {
appEffects.push(effect)
} else if (rollData.aptitude && effect.system.properties.identifier.includes(rollData.aptitude.key)) {
appEffects.push(effect)
}
}
return appEffects
}
let attribute = eval(`actor.system.attributes.${key}`)
let label = (attribute.label) ? game.i18n.localize(attribute.label) : null
let description = game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label)
/* -------------------------------------------- */
static buildHoroscopeGroupList() {
let horoscopes = game.settings.get("bol", "horoscope-group")
let horoList = [{ id: -1, name: "Aucun", type: "malus", nbDice: 0 }]
for (let id in horoscopes) {
let horo = horoscopes[id]
for (let i = 0; i < horo.availableDice; i++) {
horoList.push({ id: id, name: horo.name, type: horo.type, nbDice: i + 1 })
}
}
return horoList
}
/* -------------------------------------------- */
static getCommonRollData(actor, mode, attribute, aptitude = undefined) {
let rollData = {
mode: "attribute",
mode: mode,
actorId: actor.id,
tokenId: actor.token?.id,
img: actor.img,
attribute: attribute,
attrValue: attribute.value,
aptValue: 0,
label: label,
careerBonus: 0,
description: description,
horoscopeBonus: 0,
horoscopeMalus: 0,
selectedHoroscope: [],
armorAgiMalus: actor.getArmorAgiMalus(),
armorInitMalus: actor.getArmorInitMalus(),
mod: 0
horoscopeBonusList: actor.getHoroscopesBonus(),
horoscopeMalusList: actor.getHoroscopesMalus(),
adv: "0",
mod: 0,
modRanged: 0,
aptValue: 0,
bolEffects: actor.boleffects,
horoscopeGroupList: this.buildHoroscopeGroupList()
}
if (aptitude) {
rollData.aptitude = aptitude
rollData.aptValue = aptitude.value
}
rollData.bolApplicableEffects = this.updateApplicableEffects(rollData)
return rollData
}
/* -------------------------------------------- */
static attributeCheck(actor, key, event, combatData) {
let attribute = eval(`actor.system.attributes.${key}`)
let rollData = this.getCommonRollData(actor, "attribute", attribute)
rollData.description = game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label)
rollData.label = (attribute.label) ? game.i18n.localize(attribute.label) : null
console.log(">>>>>>>>>>", rollData, actor)
return this.displayRollDialog(rollData)
}
/* -------------------------------------------- */
static aptitudeCheck(actor, key) {
static aptitudeCheck(actor, key, event, combatData) {
let aptitude = eval(`actor.system.aptitudes.${key}`)
let attrKey = this.getDefaultAttribute(key)
let attribute = eval(`actor.system.attributes.${attrKey}`)
let label = (aptitude.label) ? game.i18n.localize(aptitude.label) : null;
let description = game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label);
return this.displayRollDialog(
{
mode: "aptitude",
actorId: actor.id,
img: actor.img,
attribute: attribute,
aptitude: aptitude,
attrValue: attribute.value,
aptValue: aptitude.value,
armorAgiMalus: actor.getArmorAgiMalus(),
armorInitMalus: actor.getArmorInitMalus(),
label: label,
careerBonus: 0,
description: description,
mod: 0
let rollData = this.getCommonRollData(actor, "aptitude", attribute, aptitude)
rollData.label = (aptitude.label) ? game.i18n.localize(aptitude.label) : null
rollData.description = game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label)
rollData.combatData = combatData // For initiative mainly
return this.displayRollDialog(rollData)
}
/* -------------------------------------------- */
static async detectDistance(weapon, target) {
let visible, dist
if (target && (weapon.system.properties.ranged || weapon.system.properties.throwing)) {
console.log("target", target, weapon)
visible = canvas.effects.visibility.testVisibility(target.center, { object: _token })
dist = Number(canvas.grid.measureDistances([{ ray: new Ray(_token.center, target.center) }], { gridSpaces: false })).toFixed(2)
let range = Number(weapon.system.properties.range)
let rangeMsg = "BOL.chat.rangeout"
if (dist <= range) {
rangeMsg = "BOL.chat.range0"
} else if (dist < range * 2) {
rangeMsg = "BOL.chat.range1"
} else if (dist < range * 3) {
rangeMsg = "BOL.chat.range2"
} else if (dist < range * 4) {
rangeMsg = "BOL.chat.range3"
} else if (dist < range * 5) {
rangeMsg = "BOL.chat.range4"
} else if (dist < range * 6) {
rangeMsg = "BOL.chat.range5"
} else if (dist < range * 7) {
rangeMsg = "BOL.chat.range6"
}
ChatMessage.create({
content: await renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', {
weapon: weapon,
attackerName: _token.actor.name,
defenderName: target.actor.name,
weaponRange: weapon.system.properties.range,
visible: visible,
distance: dist,
rangeMsg: rangeMsg
})
})
}
}
/* -------------------------------------------- */
@ -76,36 +153,30 @@ export class BoLRoll {
let attribute = eval(`actor.system.attributes.${weaponData.properties.attackAttribute}`)
let aptitude = eval(`actor.system.aptitudes.${weaponData.properties.attackAptitude}`)
let rollData = this.getCommonRollData(actor, "weapon", attribute, aptitude)
// Compute distance
this.detectDistance(weapon, target)
// Manage specific case
let fightOption = actor.getActiveFightOption()
if (fightOption && fightOption.system.fightoptiontype == "fulldefense") {
if (fightOption && fightOption.system.properties.fightoptiontype == "fulldefense") {
ui.notifications.warn(`{{actor.name}} est en Défense Totale ! Il ne peut pas attaquer ce round.`)
return
}
// Build the roll structure
let rolldata = {
mode: "weapon",
actorId: actor.id,
img: actor.img,
weapon: weapon,
isRanged: weaponData.properties.ranged || weaponData.properties.throwing,
targetId: target?.id,
fightOption: fightOption,
careerBonus: 0,
defenderId: target?.actor.id,
attribute: attribute,
aptitude: aptitude,
attrValue: attribute.value,
aptValue: aptitude.value,
armorAgiMalus: actor.getArmorAgiMalus(),
armorInitMalus: actor.getArmorInitMalus(),
mod: 0,
modRanged: 0,
label: (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'),
description: game.i18n.localize('BOL.ui.weaponAttack') + " : " + weapon.name,
}
return this.displayRollDialog(rolldata)
// Update the roll structure
rollData.weapon = weapon
rollData.isRanged = weaponData.properties.ranged || weaponData.properties.throwing
rollData.targetId = target?.id
rollData.fightOption = fightOption
rollData.defenderId = target?.actor.id
rollData.label = (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName')
rollData.description = game.i18n.localize('BOL.ui.weaponAttack') + " : " + weapon.name
return this.displayRollDialog(rollData)
}
/* -------------------------------------------- */
static weaponCheck(actor, event) {
const li = $(event.currentTarget).parents(".item")
@ -127,55 +198,65 @@ export class BoLRoll {
return;
}
alchemy = duplicate(alchemy)
let alchemyData = alchemy.data
let alchemyData = alchemy.system
if (alchemyData.properties.pccurrent < alchemyData.properties.pccost) {
ui.notifications.warn("Pas assez de Points de Cration investis dans la Préparation !")
ui.notifications.warn("Pas assez de Points de Création investis dans la Préparation !")
return
}
let alchemyDef = {
mode: "alchemy",
actorId: actor.id,
img: actor.img,
alchemy: alchemy,
attribute: actor.system.attributes.mind,
attrValue: actor.system.attributes.mind.value,
aptValue: 0,
careerBonus: actor.getAlchemistBonus(),
pcCost: Number(alchemyData.properties.pccost),
pcCostCurrent: Number(alchemyData.properties.pccurrent),
mod: Number(alchemyData.properties.difficulty),
armorAgiMalus: actor.getArmorAgiMalus(),
armorInitMalus: actor.getArmorInitMalus(),
label: alchemy.name,
description: game.i18n.localize('BOL.ui.makeAlchemy') + "+" + alchemy.name,
}
console.log("ALCHEMY!", alchemyDef);
return this.displayRollDialog(alchemyDef);
let rollData = this.getCommonRollData(actor, "alchemy", actor.system.attributes.mind)
rollData.alchemy = alchemy
rollData.careerBonus = actor.getAlchemistBonus()
rollData.pcCost = Number(alchemyData.properties.pccost)
rollData.pcCostCurrent = Number(alchemyData.properties.pccurrent)
rollData.mod = Number(alchemyData.properties.difficulty)
rollData.label = alchemy.name
rollData.description = game.i18n.localize('BOL.ui.makeAlchemy') + "+" + alchemy.name
console.log("ALCHEMY!", rollData);
return this.displayRollDialog(rollData);
}
/* -------------------------------------------- */
static spellCheckWithSpell( actor, spell ) {
let spellDef = {
mode: "spell",
actorId: actor.id,
img: actor.img,
spell: spell,
attribute: actor.system.attributes.mind,
attrValue: actor.system.attributes.mind.value,
aptValue: 0,
ppCurrent: Number(actor.system.resources.power.value),
careerBonus: actor.getSorcererBonus(),
ppCostArmor: actor.getPPCostArmor(),
ppCost: Number(spell.system.properties.ppcost),
mod: Number(spell.system.properties.difficulty),
armorAgiMalus: actor.getArmorAgiMalus(),
armorInitMalus: actor.getArmorInitMalus(),
label: spell.name,
description: game.i18n.localize('BOL.ui.focusSpell') + " : " + spell.name,
static horoscopeCheck(actor, event, horoscopeType) {
let target = BoLUtility.getTarget()
let cost = (horoscopeType == "minor") ? 1 : 2
if (cost > actor.getAstrologyPoints()) {
ui.notifications.warn(game.i18n.localize("BOL.ui.astrologyNoPoints"))
return
}
let rollData = this.getCommonRollData(actor, "horoscope", actor.system.attributes.mind)
rollData.careerBonus = actor.getAstrologerBonus()
rollData.horoscopeType = horoscopeType
rollData.horoscopeTypeLabel = "BOL.ui." + horoscopeType
rollData.astrologyPointsCost = cost
rollData.label = game.i18n.localize('BOL.ui.makeHoroscope')
rollData.description = game.i18n.localize('BOL.ui.makeHoroscope') + " " + game.i18n.localize(rollData.horoscopeTypeLabel)
rollData.targetId = target?.id
console.log("HOROSCOPE!", rollData);
return this.displayRollDialog(rollData);
}
/* -------------------------------------------- */
static spellCheckWithSpell(actor, spell) {
let rollData = this.getCommonRollData(actor, "spell", actor.system.attributes.mind)
rollData.spell = spell
rollData.ppCurrent = Number(actor.system.resources.power.value),
rollData.careerBonus = actor.getSorcererBonus(),
rollData.ppCostArmor = actor.getPPCostArmor(),
rollData.ppCost = Number(spell.system.properties.ppcost),
rollData.mod = Number(spell.system.properties.difficulty),
rollData.label = spell.name,
rollData.description = game.i18n.localize('BOL.ui.focusSpell') + " : " + spell.name
//console.log("SPELL!", spellDef)
return this.displayRollDialog(spellDef)
return this.displayRollDialog(rollData)
}
/* -------------------------------------------- */
@ -191,7 +272,7 @@ export class BoLRoll {
return
}
spell = duplicate(spell)
return this.spellCheckWithSpell( actor, spell)
return this.spellCheckWithSpell(actor, spell)
}
/* -------------------------------------------- */
@ -200,8 +281,35 @@ export class BoLRoll {
this.updateArmorMalus(this.rollData)
this.updatePPCost(this.rollData)
// get basic dices from boons/flaws
let effectModifier = 0
this.rollData.bmDice = this.rollData.nbBoons - this.rollData.nbFlaws + this.rollData.bDice - this.rollData.mDice
// add applicable bonus/malus dices effects
for (let effect of this.rollData.bolApplicableEffects) {
if (effect.system.properties.modifier == "1B") {
this.rollData.bmDice++;
} else if (effect.system.properties.modifier == "2B") {
this.rollData.bmDice += 2;
} else if (effect.system.properties.modifier == "1M") {
this.rollData.bmDice--;
} else if (effect.system.properties.modifier == "2M") {
this.rollData.bmDice -= 2;
} else {
effectModifier += Number(effect.system.properties.modifier)
}
}
this.rollData.bmDice += this.rollData.horoscopeBonus
this.rollData.bmDice -= this.rollData.horoscopeMalus
if (this.rollData.selectedGroupHoroscopeIndex && this.rollData.selectedGroupHoroscopeIndex > 0) {
let horo = this.rollData.horoscopeGroupList[this.rollData.selectedGroupHoroscopeIndex]
this.rollData.bmDice += (horo.type == "malus") ? -horo.nbDice : horo.nbDice;
}
// Keep track of the final effect modifier
this.rollData.effectModifier = effectModifier
// Final number of dices
this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice)
// Bonus or Malus ?
if (this.rollData.bmDice == 0) {
$('#roll-nbdice').val("2")
} else {
@ -209,12 +317,19 @@ export class BoLRoll {
$('#roll-nbdice').val("2 + " + String(Math.abs(this.rollData.bmDice)) + letter)
}
let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue
if ( this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier ) {
if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) {
rollbase = ""
}
$('#roll-modifier').val(rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" +
this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus)
this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus + "+" + effectModifier)
// Rebuild lits of applicable effects
let selectEffects = ""
for (let effect of this.rollData.bolApplicableEffects) {
selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>`
}
$('#applicable-effects').html(selectEffects)
}
/* -------------------------------------------- */
@ -274,7 +389,7 @@ export class BoLRoll {
html.find('#optcond').change((event) => { // Dynamic change of PP cost of spell
let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length)
this.rollData.ppCost = pp
this.updatePPCost( this.rollData)
this.updatePPCost(this.rollData)
})
html.find('#mod').change((event) => {
@ -288,16 +403,18 @@ export class BoLRoll {
html.find('#attr').change((event) => {
let attrKey = event.currentTarget.value
let actor = game.actors.get( this.rollData.actorId)
let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.attribute = duplicate(actor.system.attributes[attrKey])
this.rollData.attrValue = actor.system.attributes[attrKey].value
this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData)
this.updateTotalDice()
})
html.find('#apt').change((event) => {
let aptKey = event.currentTarget.value
let actor = game.actors.get( this.rollData.actorId)
let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.aptitude = duplicate(actor.system.aptitudes[aptKey])
this.rollData.aptValue = actor.system.aptitudes[aptKey].value
this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData)
this.updateTotalDice()
})
@ -307,6 +424,7 @@ export class BoLRoll {
} else {
this.rollData.shieldMalus = 0
}
this.updateTotalDice()
})
html.find('#career').change((event) => {
@ -334,22 +452,52 @@ export class BoLRoll {
this.rollData.mDice = Number(event.currentTarget.value)
this.updateTotalDice()
})
html.find('#horoscope-bonus-applied').change((event) => {
this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
}
let horoscopes = $('#horoscope-bonus-applied').val()
this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice()
})
html.find('#horoscope-malus-applied').change((event) => {
this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
}
let horoscopes = $('#horoscope-malus-applied').val()
this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice()
})
html.find('#horoscope-group-applied').change((event) => {
this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value
this.updateTotalDice()
})
}
/* -------------------------------------------- */
static preProcessWeapon(rollData, defender) {
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
if (rollData.attackBonusDice) {
rollData.adv = "1B"
rollData.bDice = 1
}
if (defender) { // If target is selected
rollData.defence = defender.defenseValue
rollData.armorMalus = defender.armorMalusValue
rollData.defenderHeroPoints = defender.getHeroPoints()
rollData.shieldBlock = 'none'
let shields = defender.shields
//console.log("Defender stats", defender)
for (let shield of shields) {
rollData.shieldBlock = (shield.system.properties.blocking.blockingAll) ? 'blockall' : 'blockone';
rollData.shieldAttackMalus = (shield.system.properties.blocking.malus) ? shield.system.properties.blocking.malus : 1;
rollData.applyShieldMalus = false
}
}
}
@ -362,9 +510,9 @@ export class BoLRoll {
// initialize default flags/values
const rollOptionTpl = `systems/bol/templates/dialogs/${rollData.mode}-roll-dialog.hbs`
let actor = game.actors.get( rollData.actorId )
let actor = BoLUtility.getActorFromRollData(rollData)
let defender
if ( rollData.targetId) {
if (rollData.targetId) {
let token = game.scenes.current.tokens.get(rollData.targetId)
defender = token.actor
}
@ -381,11 +529,7 @@ export class BoLRoll {
rollData.nbBoons = 0
rollData.nbFlaws = 0
rollData.nbDice = 0
if (rollData.shieldBlock == 'blockall') {
rollData.shieldMalus = rollData.shieldAttackMalus;
} else {
rollData.shieldMalus = 0
}
rollData.isHeroAdversary = actor.isHeroAdversary()
rollData.careerBonus = rollData.careerBonus ?? 0
rollData.modRanged = rollData.modRanged ?? 0
rollData.mod = rollData.mod ?? 0
@ -398,6 +542,12 @@ export class BoLRoll {
this.preProcessFightOption(rollData)
this.updateArmorMalus(rollData)
this.updatePPCost(rollData)
// Prepare blocking case
if (rollData.shieldBlock == 'blockall') {
rollData.shieldMalus = rollData.shieldAttackMalus;
} else {
rollData.shieldMalus = 0
}
// Save
this.rollData = rollData
console.log("ROLLDATA", rollData)
@ -424,20 +574,21 @@ export class BoLRoll {
ui.notifications.warn("Pas assez de Points de Pouvoir !")
return
}
rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ? $('#register-init').is(":checked") : false;
const isMalus = rollData.mDice > 0
rollData.nbDice += (rollData.attackBonusDice) ? 1 : 0
const isMalus = (rollData.bmDice < 0)
let rollbase = rollData.attrValue + rollData.aptValue
if ( rollData.weapon && rollData.weapon.system.properties.onlymodifier ) {
if (rollData.weapon?.system.properties.onlymodifier) {
rollbase = 0
}
const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus
const formula = (isMalus) ? rollData.nbDice + "d6kl2 + " + modifiers : rollData.nbDice + "d6kh2 + " + modifiers
}
let diceData = BoLUtility.getDiceData()
let malusInit = rollData.combatData?.malusInit || 0
const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus + rollData.effectModifier - malusInit
const formula = (isMalus) ? rollData.nbDice + "d" + diceData.diceFormula + "kl2 + " + modifiers : rollData.nbDice + "d" + diceData.diceFormula + "kh2 + " + modifiers
rollData.formula = formula
rollData.modifiers = modifiers
console.log("Rolldata before", rollData)
let r = new BoLDefaultRoll(rollData);
r.roll();
@ -463,10 +614,10 @@ export class BoLDefaultRoll {
this.rollData.isFumble = false;
}
if (this.rollData.optionsId) {
BoLUtility.cleanupButtons( this.rollData.optionsId)
BoLUtility.cleanupButtons(this.rollData.optionsId)
}
if (this.rollData.applyId) {
BoLUtility.cleanupButtons( this.rollData.applyId)
BoLUtility.cleanupButtons(this.rollData.applyId)
}
this.rollData.optionsId = randomID(16)
this.rollData.applyId = randomID(16)
@ -476,48 +627,71 @@ export class BoLDefaultRoll {
async roll() {
const r = new Roll(this.rollData.formula)
// console.log("Roll formula", this.rollData.formula)
//console.log("Roll formula", this.rollData.formula)
await r.roll({ "async": false })
let diceData = BoLUtility.getDiceData()
//console.log("DICEDATA", diceData)
const activeDice = r.terms[0].results.filter(r => r.active)
const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b)
this.rollData.roll = r
this.rollData.isSuccess = (r.total >= 9)
this.rollData.isCritical = (diceTotal === 12)
this.rollData.isRealCritical = (diceTotal === 12)
this.rollData.isHeroic = (diceTotal === 12)
this.rollData.isSuccess = (r.total >= diceData.successValue)
this.rollData.isCritical = (diceTotal >= diceData.criticalSuccessValue)
this.rollData.isRealCritical = (diceTotal >= diceData.criticalSuccessValue)
this.rollData.isHeroic = (diceTotal >= diceData.criticalSuccessValue)
this.rollData.isLegendary = false
this.rollData.isFumble = (diceTotal === 2)
this.rollData.isFumble = (diceTotal <= diceData.criticalFailureValue)
this.rollData.isFailure = !this.rollData.isSuccess
let actor = game.actors.get( this.rollData.actorId)
let actor = BoLUtility.getActorFromRollData(this.rollData)
if (this.rollData.reroll == undefined) {
this.rollData.reroll = actor.heroReroll()
}
if (this.rollData.registerInit) {
actor.registerInit(r.total, this.rollData.isCritical, this.rollData.isFumble)
await actor.registerInit(this.rollData)
this.rollData.initiativeRank = actor.getInitiativeRank(this.rollData)
if (this.rollData.combatData) { // If combatData present
let combat = game.combats.get(this.rollData.combatData.combatId)
console.log("SET INIT!!!!!", this.rollData.initiativeRank)
combat.setInitiative(this.rollData.combatData.combatantId, this.rollData.initiativeRank)
}
}
if (this.rollData.isSuccess && this.rollData.mode == "spell") { // PP cost management
this.rollData.remainingPP = actor.spendPowerPoint(this.rollData.ppCost + this.rollData.ppCostArmor)
}
if (this.rollData.mode == "alchemy") { // PP cost management
actor.resetAlchemyStatus(this.rollData.alchemy._id)
}
if (this.rollData.mode == "bougette" && this.rollData.isFailure) {
actor.decBougette()
}
await this.sendChatMessage()
if (this.rollData.mode == "horoscope") { // PP cost management
actor.manageHoroscope(this.rollData)
}
if (this.rollData.selectedHoroscope.length > 0) { // PP cost management
actor.removeHoroscopeMinor(this.rollData)
}
if (this.rollData.selectedGroupHoroscopeIndex && this.rollData.selectedGroupHoroscopeIndex > 0) { // PP cost management
BoLUtility.removeGroupHoroscope(this.rollData)
}
}
/* -------------------------------------------- */
async sendChatMessage() {
let actor = game.actors.get( this.rollData.actorId)
this._buildChatMessage(this.rollData).then( async msgFlavor => {
let actor = BoLUtility.getActorFromRollData(this.rollData)
this._buildChatMessage(this.rollData).then(async msgFlavor => {
//console.log("MSG", msgFlavor )
let msg = await this.rollData.roll.toMessage({
user: game.user.id,
rollMode: game.settings.get("core", "rollMode"),
//whisper: BoLUtility.getWhisperRecipientsAndGMs(this.rollData.actor.name),
flavor: msgFlavor,
speaker: ChatMessage.getSpeaker({ actor: actor }),
})
this.rollData.roll = duplicate(this.rollData.roll) // Remove object, keep data (v111 ready)
msg.setFlag("world", "bol-roll-data", this.rollData)
})
}
@ -525,12 +699,15 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */
upgradeToLegendary() {
// Force to Critical roll
let diceData = BoLUtility.getDiceData()
let maxValue = Number(diceData.diceFormula) * 2
this.rollData.isCritical = true
this.rollData.isLegendary = true
this.rollData.isRealCritical = false
this.rollData.isSuccess = true
this.rollData.isFailure = false
this.rollData.roll = new Roll("12+" + this.rollData.modifiers)
this.rollData.roll = new Roll(maxValue + "+" + this.rollData.modifiers)
this.rollData.reroll = false
this.sendChatMessage()
}
@ -538,17 +715,20 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */
upgradeToHeroic() {
// Force to Critical roll
let diceData = BoLUtility.getDiceData()
let maxValue = Number(diceData.diceFormula) * 2
this.rollData.isCritical = true
this.rollData.isHeroic = true
this.rollData.isLegendary = false
this.rollData.isRealCritical = false
this.rollData.isSuccess = true
this.rollData.isFailure = false
this.rollData.roll = new Roll("12+" + this.rollData.modifiers)
this.rollData.roll = new Roll(maxValue + "+" + this.rollData.modifiers)
this.rollData.reroll = false
this.sendChatMessage()
}
/* -------------------------------------------- */
setSuccess(flag) {
this.rollData.isSuccess = flag
@ -556,7 +736,7 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */
async sendDamageMessage() {
let actor = game.actors.get( this.rollData.actorId)
let actor = BoLUtility.getActorFromRollData(this.rollData)
this._buildDamageChatMessage(this.rollData).then(async msgFlavor => {
let msg = await this.rollData.damageRoll.toMessage({
user: game.user.id,
@ -564,22 +744,16 @@ export class BoLDefaultRoll {
speaker: ChatMessage.getSpeaker({ actor: actor }),
flags: { msgType: "default" }
})
this.rollData.damageRoll = duplicate(this.rollData.damageRoll)
this.rollData.actor = undefined // Cleanup
msg.setFlag("world", "bol-roll-data", this.rollData)
})
}
/* -------------------------------------------- */
getDamageAttributeValue(attrDamage, actorId = undefined) {
let attrDamageValue = 0
let actor = game.actors.get( (actorId) ? actorId: this.rollData.actorId)
if (attrDamage.includes("vigor")) {
attrDamageValue = actor.system.attributes.vigor.value
if (attrDamage.includes("half")) {
attrDamageValue = Math.floor(attrDamageValue / 2)
}
}
return attrDamageValue
let actor = BoLUtility.getActorFromRollData(this.rollData)
return actor.getDamageAttributeValue(attrDamage)
}
/* -------------------------------------------- */
@ -601,13 +775,13 @@ export class BoLDefaultRoll {
let weaponFormula = BoLUtility.getDamageFormula(this.rollData.weapon.system, this.rollData.fightOption)
let damageFormula = weaponFormula + "+" + bonusDmg + "+" + attrDamageValue
console.log("DAMAGE !!!", damageFormula, attrDamageValue, this.rollData)
//console.log("Formula", weaponFormula, damageFormula, this.rollData.weapon.data.data.properties.damage)
this.rollData.damageFormula = damageFormula
this.rollData.damageRoll = new Roll(damageFormula)
await this.rollData.damageRoll.roll({ "async": false })
this.rollData.damageTotal = this.rollData.damageRoll.total
console.log("DAMAGE !!!", damageFormula, attrDamageValue, this.rollData)
}
BoLUtility.cleanupButtons(this.rollData.optionsId)
this.sendDamageMessage()
@ -622,8 +796,8 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */
_buildChatMessage(rollData) {
const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs';
return renderTemplate(rollMessageTpl, rollData);
const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'
return renderTemplate(rollMessageTpl, rollData)
}
}

View File

@ -19,7 +19,7 @@ export class BoLItemSheet extends ItemSheet {
/* -------------------------------------------- */
/** @override */
getData(options) {
async getData(options) {
const data = super.getData(options)
let itemData = duplicate(data.document)
data.config = game.bol.config
@ -27,22 +27,23 @@ export class BoLItemSheet extends ItemSheet {
data.category = itemData.system.category
data.isGM = game.user.isGM;
data.itemProperties = this.item.itemProperties;
data.description = await TextEditor.enrichHTML(this.object.system.description, { async: true })
// 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.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) {
if (!itemData.system.properties.mandatoryconditions) {
itemData.system.properties.mandatoryconditions = []
}
if(!itemData.system.properties.optionnalconditions) {
if (!itemData.system.properties.optionnalconditions) {
itemData.system.properties.optionnalconditions = []
}
for (let i = 0; i < 4; i++) {
@ -63,7 +64,27 @@ export class BoLItemSheet extends ItemSheet {
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = duplicate(this.item)
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
BoLUtility.postItem(chatData);
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);

View File

@ -1,5 +1,6 @@
/* -------------------------------------------- */
import { BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js";
/* -------------------------------------------- */
export class BoLTokenHud {
@ -30,10 +31,17 @@ export class BoLTokenHud {
(event) => {
let actionIndex = Number(event.currentTarget.attributes['data-action-index'].value)
let action = hudData.actionsList[actionIndex]
const weapon = actor.items.get( action._id )
BoLRoll.weaponCheckWithWeapon(hudData.actor, duplicate(weapon))
//console.log("Clicked", action)
} )
const actionItem = actor.items.get(action._id)
if (actionItem.system.subtype == "weapon") {
BoLRoll.weaponCheckWithWeapon(hudData.actor, duplicate(actionItem))
} else if (actionItem.system.subtype == "fightoption") {
let chatData = duplicate(actionItem)
if (actionItem.actor) {
chatData.actor = { id: actionItem.actor._id };
}
BoLUtility.postItem(chatData);
}
})
const controlIconTarget = html.find('.control-icon[data-action=target]');
// att+apt+career
@ -41,18 +49,18 @@ export class BoLTokenHud {
(event) => {
let rollIndex = Number(event.currentTarget.attributes['data-roll-index'].value)
let roll = hudData.rollsList[rollIndex]
if ( roll.type == "aptitude") {
BoLRoll.aptitudeCheck(actor, roll.key )
} else if ( roll.type == "attribute") {
BoLRoll.attributeCheck(actor, roll.key )
if (roll.type == "aptitude") {
BoLRoll.aptitudeCheck(actor, roll.key)
} else if (roll.type == "attribute") {
BoLRoll.attributeCheck(actor, roll.key)
}
})
}
/* -------------------------------------------- */
static async addTokenHudExtensions(app, html, tokenId) {
const controlIconCombat = html.find('.control-icon[data-action=combat]')
if (controlIconCombat.length>0 ) {
const controlIconCombat = html.find('.control-icon[data-action=combat]')
if (controlIconCombat.length > 0) {
BoLTokenHud.addExtensionHud(app, html, tokenId);
}
}
@ -61,9 +69,9 @@ export class BoLTokenHud {
static async _configureSubMenu(insertionPoint, template, hudData, onMenuItem) {
const hud = $(await renderTemplate(template, hudData))
const list = hud.find('div.bol-hud-list')
BoLTokenHud._toggleHudListActive(hud, list);
hud.find('img.bol-hud-togglebutton').click(event => BoLTokenHud._toggleHudListActive(hud, list));
list.find('.bol-hud-menu').click(onMenuItem);

View File

@ -40,7 +40,7 @@ export class BoLCalendar extends Application {
constructor() {
super();
// position
this.calendarPos = duplicate(game.settings.get(SYSTEM_RDD, "calendar-pos"));
this.calendarPos = duplicate(game.settings.get("bol", "calendar-pos"));
if (this.calendarPos == undefined || this.calendarPos.top == undefined) {
this.calendrierPos = BoLCalendar.createCalendarPos()
game.settings.set("bol", "calendar-pos", this.calendarPos)

View File

@ -0,0 +1,162 @@
/* -------------------------------------------- */
import { BoLUtility } from "./bol-utility.js";
import { BoLRoll } from "../controllers/bol-rolls.js";
/* -------------------------------------------- */
export class BoLCharacterSummary extends Application {
/* -------------------------------------------- */
static displayPCSummary(){
game.bol.charSummary.render(true)
}
/* -------------------------------------------- */
updatePCSummary(){
if ( this.rendered) {
this.render(true)
}
}
/* -------------------------------------------- */
static createSummaryPos() {
return { top: 200, left: 200 };
}
/* -------------------------------------------- */
static ready() {
if ( !game.user.isGM ) { // Uniquement si GM
return
}
let charSummary = new BoLCharacterSummary()
game.bol.charSummary = charSummary
}
/* -------------------------------------------- */
constructor() {
super();
//game.settings.set("world", "character-summary-data", {npcList: [], x:0, y:0})
this.settings = game.settings.get("world", "character-summary-data")
}
/* -------------------------------------------- */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
template: "systems/bol/templates/apps/character-summary-template.html",
popOut: true,
resizable: true,
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
classes: ["bol", "dialog"], width: 820, height: 'fit-content'
})
}
/* -------------------------------------------- */
getData() {
let formData = super.getData();
formData.pcs = game.actors.filter( ac => ac.type == "character" && ac.hasPlayerOwner )
formData.npcs = []
let newList = []
let toUpdate = false
for( let actorId of this.settings.npcList ) {
let actor = game.actors.get(actorId)
if (actor) {
formData.npcs.push( actor )
newList.push(actorId)
} else {
toUpdate = true
}
}
formData.config = game.bol.config
formData.horoscopeGroupList = game.settings.get("bol", "horoscope-group")
if ( toUpdate ) {
this.settings.npcList = newList
//console.log("Going to update ...", this.settings)
game.settings.set("world", "character-summary-data", this.settings)
}
return formData
}
/* -------------------------------------------- */
updateNPC() {
game.settings.set("world", "character-summary-data", game.bol.charSummary.settings)
game.bol.charSummary.close()
setTimeout( function() { game.bol.charSummary.render(true)}, 500)
}
/* -------------------------------------------- */
async _onDrop(event) {
//console.log("Dragged data are : ", dragData)
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse( data)
let actor = fromUuidSync(dataItem.uuid)
if (actor) {
game.bol.charSummary.settings.npcList.push( actor.id )
game.bol.charSummary.updateNPC()
} else {
ui.notifications.warn( game.i18n.localize("BOL.ui.noactorfound") )
}
}
/* -------------------------------------------- */
/** @override */
async activateListeners(html) {
super.activateListeners(html);
html.find('.actor-open').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
actor.sheet.render(true)
})
html.find('.summary-roll').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
let type = $(event.currentTarget).data("type")
let key = $(event.currentTarget).data("key")
if ( type == "attribute") {
BoLRoll.attributeCheck(actor, key, event)
} else if (type == "aptitude") {
BoLRoll.aptitudeCheck(actor, key, event)
}
})
html.find('.actor-delete').click(event => {
const li = $(event.currentTarget).parents(".item");
let actorId = li.data("actor-id")
let newList = game.bol.charSummary.settings.npcList.filter(id => id != actorId)
game.bol.charSummary.settings.npcList = newList
game.bol.charSummary.updateNPC()
})
html.find('#horoscope-group-edit-available').change(event => {
const horoId = $(event.currentTarget).data("horo-id")
let newValue = event.currentTarget.value
let horoscopes = duplicate(game.settings.get("bol", "horoscope-group"))
if ( horoId && horoscopes[horoId]) {
horoscopes[horoId].availableDice = Number(newValue)
if (newValue <= 0) {
horoscopes[horoId] = undefined
}
game.settings.set("bol", "horoscope-group", horoscopes)
setTimeout(function() { BoLUtility.updateSheets()}, 800 )
}
})
html.find('#horoscope-group-edit-max').change(event => {
const horoId = $(event.currentTarget).data("horo-id")
let newValue = event.currentTarget.value
let horoscopes = duplicate(game.settings.get("bol", "horoscope-group"))
if ( horoId && horoscopes[horoId]) {
horoscopes[horoId].maxDice = Number(newValue)
if (newValue <= 0) {
horoscopes[horoId] = undefined
}
game.settings.set("bol", "horoscope-group", horoscopes)
setTimeout(function() { BoLUtility.updateSheets()}, 800 )
}
})
}
}

View File

@ -10,41 +10,25 @@ Init order =
3 - Echec critique
*/
import { BoLUtility } from "../system/bol-utility.js";
export class BoLCombatManager extends Combat {
/************************************************************************************/
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
console.log(`${game.data.system.data.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
console.log(`${game.system.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
// Structure input data
ids = typeof ids === "string" ? [ids] : ids;
const currentId = this.combatant._id;
// Get initiative malus from tough/adversary
let malusInit = 0
for (let combatant of this.combatants) {
malusInit = Math.max(malusInit, combatant.actor.getInitiativeMalus())
}
// calculate initiative
for (let cId = 0; cId < ids.length; cId++) {
const combatant = this.combatants.get(ids[cId]);
let fvttInit = 5
if (combatant.actor.type == 'character') {
let initData = combatant.actor.getLastInitData()
console.log("Init data !!!", initData)
if (initData.isLegendary) {
fvttInit = 10
} else if (initData.isCritical) {
fvttInit = 9
} else if (initData.lastinit >= 9) {
fvttInit = 8
} else if (initData.isFumble) {
fvttInit = 3
}
} else {
fvttInit = 4 // Pietaille par defaut
if ( combatant.actor.getSubtype == 'adversary') {
fvttInit = 7
}
if ( combatant.actor.getSubtype == 'tough') {
fvttInit = 6
}
}
for (let cId = 0; cId < ids.length; cId++) {
const combatant = this.combatants.get(ids[cId])
let fvttInit = combatant.actor.getInitiativeRank(false, true, { combatId: this.id, combatantId: combatant.id, malusInit })
fvttInit += (cId / 100)
await this.updateEmbeddedDocuments("Combatant", [{ _id: ids[cId], initiative: fvttInit }]);
}
@ -52,14 +36,49 @@ export class BoLCombatManager extends Combat {
/************************************************************************************/
nextRound() {
let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get( c.data.actorId )
actor.clearRoundModifiers()
if (game.user.isGM) {
let combatants = this.combatants.contents
let autoRemoveDead = game.settings.get("bol", "auto-remove-dead") // Optionnal auto-removal of dead char.
for (let c of combatants) {
//let actor = game.actors.get(c.actorId)
c.actor.clearRoundModifiers()
let toRemove = []
if (autoRemoveDead && c.actor.type == "encounter" && (c.actor.system.chartype == "tough" || c.actor.system.chartype == "creature" || c.actor.system.chartype == "base") && c.actor.system.resources.hp.value <= 0) {
toRemove.push(c.id || c._id)
}
//console.log("REM", autoRemoveDead, toRemove, c.actor)
if (toRemove.length > 0) {
this.deleteEmbeddedDocuments('Combatant', toRemove)
}
}
}
super.nextRound()
}
/************************************************************************************/
startCombat() {
if (game.user.isGM) {
let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get(c.actorId)
actor.storeVitaliteCombat()
}
}
return super.startCombat()
}
/*-***********************************************************************************/
_onDelete() {
if (game.user.isGM) {
let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get(c.actorId)
actor.clearInitiative()
actor.displayRecuperation()
}
}
super._onDelete()
}
}

View File

@ -1,5 +1,6 @@
/* -------------------------------------------- */
import { BoLAdventureGenerator } from "./bol-adventure-generator.js"
import { BoLCharacterSummary } from "./bol-character-summary.js"
/* -------------------------------------------- */
export class BoLCommands {
@ -8,6 +9,7 @@ export class BoLCommands {
if (!game.bol.commands) {
const bolCommands = new BoLCommands()
bolCommands.registerCommand({ path: ["/adventure"], func: (content, msg, params) => BoLAdventureGenerator.createAdventure(), descr: "Nouvelle idée d'aventure!" });
bolCommands.registerCommand({ path: ["/pcview"], func: (content, msg, params) => BoLCharacterSummary.displayPCSummary(), descr: "Affiche la liste des PJs!" });
game.bol.commands = bolCommands
}

View File

@ -2,6 +2,21 @@ import { BoLRoll } from "../controllers/bol-rolls.js";
export class BoLHotbar {
static async assignToHotBar( item, slot) {
let command = `game.bol.BoLHotbar.rollMacro("${item.name}", "${item.type}");`
let macro = game.macros.contents.find(m => (m.name === item.name) && (m.command === command))
if (!macro) {
macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command
}, { displaySheet: false })
}
await game.user.assignHotbarMacro(macro, slot);
}
/**
* Create a macro when dropping an entity on the hotbar
* Item - open roll dialog for item
@ -10,55 +25,20 @@ export class BoLHotbar {
*/
static init( ) {
Hooks.on("hotbarDrop", async (bar, documentData, slot) => {
Hooks.on("hotbarDrop", (bar, documentData, slot) => {
// Create item macro if rollable item - weapon, spell, prayer, trait, or skill
if (documentData.type == "Item") {
console.log("Drop done !!!", bar, documentData, slot)
let item = documentData.data
let command = `game.bol.BoLHotbar.rollMacro("${item.name}", "${item.type}");`
let macro = game.macros.contents.find(m => (m.name === item.name) && (m.command === command))
if (!macro) {
macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command
}, { displaySheet: false })
let item = fromUuidSync(documentData.uuid)
if (item == undefined) {
item = this.actor.items.get(documentData.uuid)
}
game.user.assignHotbarMacro(macro, slot);
}
// Create a macro to open the actor sheet of the actor dropped on the hotbar
else if (documentData.type == "Actor") {
let actor = game.actors.get(documentData.id);
let command = `game.actors.get("${documentData.id}").sheet.render(true)`
let macro = game.macros.contents.find(m => (m.name === actor.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: actor.data.name,
type: "script",
img: actor.data.img,
command: command
}, { displaySheet: false })
game.user.assignHotbarMacro(macro, slot);
if (item && (item.system.subtype === "weapon" || item.system.category === "spell")) {
this.assignToHotBar( item, slot )
return false
}
}
// Create a macro to open the journal sheet of the journal dropped on the hotbar
else if (documentData.type == "JournalEntry") {
let journal = game.journal.get(documentData.id);
let command = `game.journal.get("${documentData.id}").sheet.render(true)`
let macro = game.macros.contents.find(m => (m.name === journal.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: journal.data.name,
type: "script",
img: "systems/bol/icons/images/icone_parchement_vierge.webp",
command: command
}, { displaySheet: false })
game.user.assignHotbarMacro(macro, slot);
}
}
return false;
});
return true
})
}
/** Roll macro */
@ -68,18 +48,18 @@ export class BoLHotbar {
if (speaker.token) actor = game.actors.tokens[speaker.token]
if (!actor) actor = game.actors.get(speaker.actor)
if (!actor) {
return ui.notifications.warn(`Selectionnez votre personnage pour utiliser la macro`)
return ui.notifications.warn( game.i18n.localize("BOL.ui.selectactor") )
}
let item = actor.items.find(it => it.name === itemName && it.type == itemType)
if (!item ) {
return ui.notifications.warn(`Impossible de trouver l'objet de cette macro`)
return ui.notifications.warn( game.i18n.localize("BOL.ui.itemnotfound") )
}
// Trigger the item roll
if (item.data.data.category === "equipment" && item.data.data.subtype === "weapon") {
if (item.system.category === "equipment" && item.system.subtype === "weapon") {
return BoLRoll.weaponCheckWithWeapon( actor, item)
}
if (item.data.data.category === "spell") {
if (item.system.category === "spell") {
return BoLRoll.spellCheckWithSpell( actor, item)
}
}

View File

@ -1,40 +1,168 @@
import { BoLDefaultRoll } from "../controllers/bol-rolls.js";
import { BoLRoll, BoLDefaultRoll } from "../controllers/bol-rolls.js";
// Spell circle to min PP cost
const __circle2minpp = { 0: 0, 1: 2, 2: 6, 3: 11 }
const __validDices = { "6": 1, "8": 1, "10": 1, "12": 1 }
export class BoLUtility {
/* -------------------------------------------- */
static init() {
this.attackStore = {}
game.settings.register("bol", "rollArmor", {
name: "Effectuer des jets pour les armures",
hint: "Effectue un jet de dés pour les armures (valeur fixe si désactivé)",
name: game.i18n.localize("BOL.settings.rollArmor"),
hint: game.i18n.localize("BOL.settings.rollArmorTooltip"),
scope: "world",
config: true,
default: true,
type: Boolean,
onChange: lang => window.location.reload()
});
})
game.settings.register("bol", "useBougette", {
name: "Utiliser la Bougette (règle fan-made)",
hint: "Utilise un indicateur de Bougette, comme décrit dans l'aide de jeu Gold&Glory du RatierBretonnien (https://www.lahiette.com/leratierbretonnien/)",
name: game.i18n.localize("BOL.settings.useBougette"),
hint: game.i18n.localize("BOL.settings.useBougetteTooltip"),
scope: "world",
config: true,
default: false,
type: Boolean,
onChange: lang => window.location.reload()
});
})
game.settings.register("bol", "auto-remove-dead", {
name: game.i18n.localize("BOL.settings.removeDead"),
hint: game.i18n.localize("BOL.settings.removeDeadTooltip"),
scope: "world",
config: true,
default: false,
type: Boolean
})
game.settings.register("bol", "dice-formula", {
name: game.i18n.localize("BOL.settings.diceFormula"),
hint: game.i18n.localize("BOL.settings.diceFormulaTooltip"),
scope: "world",
config: true,
default: "6",
type: String,
choices: { "6": "2d6", "8": "2d8", "10": "2d10", "12": "2d12", "20": "2d20" },
onChange: value => {
BoLUtility.setDiceFormula(value)
}
})
game.settings.register("bol", "dice-success-value", {
name: game.i18n.localize("BOL.settings.diceSuccessValue"),
hint: game.i18n.localize("BOL.settings.diceSuccessValueTooltip"),
scope: "world",
config: true,
default: 9,
range: {
min: 2,
max: 40,
step: 1
},
type: Number,
onChange: value => {
BoLUtility.setSuccessValue(value)
}
})
game.settings.register("bol", "dice-critical-success-value", {
name: game.i18n.localize("BOL.settings.diceCriticalValue"),
hint: game.i18n.localize("BOL.settings.diceCriticalValueTooltip"),
scope: "world",
config: true,
default: 12,
range: {
min: 2,
max: 40,
step: 1
},
type: Number,
onChange: value => {
BoLUtility.setCriticalSuccessValue(value)
}
})
game.settings.register("bol", "dice-critical-failure-value", {
name: game.i18n.localize("BOL.settings.diceCriticalFailure"),
hint: game.i18n.localize("BOL.settings.diceCriticalFailureTooltip"),
scope: "world",
config: true,
default: 2,
range: {
min: 2,
max: 40,
step: 1
},
type: Number,
onChange: value => {
BoLUtility.setCriticalFailureValue(value)
}
})
game.settings.register("world", "character-summary-data", {
name: "character-summary-data",
scope: "world",
config: false,
default: { npcList: [], x: 200, y: 200 },
type: Object
})
game.settings.register("bol", "logoActorSheet", {
name: game.i18n.localize("BOL.settings.defaultLogoActorSheetPath"),
hint: game.i18n.localize("BOL.settings.defaultLogoPathActorSheetTooltip"),
scope: "world",
config: true,
default: "/systems/bol/ui/logo.webp",
type: String,
onChange: lang => window.location.reload()
})
game.settings.register("bol", "logoTopLeft", {
name: game.i18n.localize("BOL.settings.defaultLogoTopLeftPath"),
hint: game.i18n.localize("BOL.settings.defaultLogoTopLeftPathTooltip"),
scope: "world",
config: true,
default: "/systems/bol/ui/logo2.webp",
type: String,
onChange: lang => window.location.reload()
})
game.settings.register("bol", "horoscope-group", {
name: "horoscope-group",
scope: "world",
config: false,
default: {},
type: Object
})
this.rollArmor = game.settings.get("bol", "rollArmor") // Roll armor or not
this.useBougette = game.settings.get("bol", "useBougette") // Use optionnal bougette rules
this.actorSheetLogo = game.settings.get("bol", "logoActorSheet") || "/systems/bol/ui/logo.webp"
this.logoTopLeft = game.settings.get("bol", "logoTopLeft") || "/systems/bol/ui/logo2.webp"
this.diceFormula = game.settings.get("bol", "dice-formula")
this.successValue = Number(game.settings.get("bol", "dice-success-value"))
this.criticalSuccessValue = Number(game.settings.get("bol", "dice-critical-success-value"))
this.criticalFailureValue = Number(game.settings.get("bol", "dice-critical-failure-value"))
}
/* -------------------------------------------- */
static setDiceFormula(value) {
this.diceFormula = value
}
static setSuccessValue(value) {
this.successValue = Number(value)
}
static setCriticalSuccessValue(value) {
this.criticalSuccessValue = Number(value)
}
static setCriticalFailureValue(value) {
this.criticalFailureValue = Number(value)
}
static getDiceData() {
let df = this.diceFormula
if (!__validDices[String(this.diceFormula)]) {
df = "6"
}
return {
diceFormula: df,
successValue: this.successValue,
criticalSuccessValue: this.criticalSuccessValue,
criticalFailureValue: this.criticalFailureValue
}
}
/* -------------------------------------------- */
static getRollArmor() {
return this.rollArmor
@ -43,34 +171,72 @@ export class BoLUtility {
static getUseBougette() {
return this.useBougette
}
/* -------------------------------------------- */
static getLogoActorSheet() {
return this.actorSheetLogo
}
/* -------------------------------------------- */
static getLogoTopLeft() {
return this.logoTopLeft
}
/* -------------------------------------------- */
static getActorFromRollData(rollData) {
let actor = game.actors.get(rollData.actorId)
if (rollData.tokenId) {
let token = canvas.tokens.placeables.find(t => t.id == rollData.tokenId)
if (token) {
actor = token.actor
}
}
return actor
}
/* -------------------------------------------- */
static async ready() {
//$("#logo").attr("src", this.getLogoTopLeft() )
$("#logo").css("content", `url(${this.getLogoTopLeft()})`)
CONFIG.statusEffects = duplicate(game.bol.config.statusEffects)
}
/* -------------------------------------------- */
static templateData(it) {
return BoLUtility.data(it)?.data ?? {}
}
static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
let chatData = {
user: game.user.id,
rollMode: modeOverride || game.settings.get("core", "rollMode"),
content: content
};
/* -------------------------------------------- */
static data(it) {
if (it instanceof Actor || it instanceof Item || it instanceof Combatant) {
return it.data;
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
}
return it;
return chatData;
}
/* -------------------------------------------- */
static storeRoll(roll) {
this.rollTab[roll.id] = roll
}
static postItem(chatData) {
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
/* -------------------------------------------- */
static getRoll(rollId) {
return this.rollTab[roll.id]
renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => {
let chatOptions = BoLUtility.chatDataSetup(html);
ChatMessage.create(chatOptions, "selfroll")
});
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
@ -115,7 +281,7 @@ export class BoLUtility {
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id);
return game.users.filter(filter).map(user => user.id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
@ -131,7 +297,7 @@ export class BoLUtility {
let users = []
for (let user of game.users) {
if (!user.isGM && user.name != name) {
users.push(user.data._id)
users.push(user.id)
}
}
return users
@ -153,13 +319,13 @@ export class BoLUtility {
}
/* -------------------------------------------- */
static sendAttackSuccess(attackDef) {
if (attackDef.targetId) {
static sendAttackSuccess(rollData) {
if (rollData.targetId) {
// Broadcast to GM or process it directly in case of GM defense
if (!game.user.isGM) {
game.socket.emit("system.bol", { name: "msg_attack_success", data: duplicate(attackDef) })
game.socket.emit("system.bol", { name: "msg_attack_success", data: duplicate(rollData) })
} else {
BoLUtility.processAttackSuccess(attackDef)
BoLUtility.processAttackSuccess(rollData)
}
}
}
@ -185,6 +351,14 @@ export class BoLUtility {
let message = game.messages.get(messageId)
return message.getFlag("world", "bol-roll-data")
}
/* -------------------------------------------- */
static requestInitRoll(actorId, combatData) {
let actor = game.actors.get(actorId)
if (actor && actor.isOwner) {
ui.notifications.info(game.i18n.localize("BOL.ui.warninitiative"))
BoLRoll.aptitudeCheck(actor, "init", undefined, combatData)
}
}
/* -------------------------------------------- */
static cleanupButtons(id) {
@ -240,10 +414,15 @@ export class BoLUtility {
html.on("click", '.damage-handling', event => {
event.preventDefault()
let attr = event.currentTarget.attributes['data-attack-id']
if ( !attr) {
ui.notifications.warn("Impossible de trouver l'attaque correspondante, erreur de suivi de combat.")
return
}
let attackId = event.currentTarget.attributes['data-attack-id'].value
let defenseMode = event.currentTarget.attributes['data-defense-mode'].value
let weaponId = (event.currentTarget.attributes['data-weapon-id']) ? event.currentTarget.attributes['data-weapon-id'].value : -1
// Remove message for all
let msgId = BoLUtility.findChatMessageId(event.currentTarget)
if (game.user.isGM) {
@ -252,6 +431,19 @@ export class BoLUtility {
game.socket.emit("system.bol", { name: "msg_damage_handling", data: { msgId: msgId, attackId: attackId, defenseMode: defenseMode, weaponId: weaponId } })
}
})
html.on("click", '.recup-vitalite', event => {
event.preventDefault()
let actorId = event.currentTarget.attributes['data-actor-id'].value
let recupHP = event.currentTarget.attributes['data-recup-hp'].value
let actor = game.actors.get(actorId)
let messageId = BoLUtility.findChatMessageId(event.currentTarget)
BoLUtility.removeChatMessageId(messageId)
actor.applyRecuperation(recupHP)
})
}
/* -------------------------------------------- */
@ -259,68 +451,70 @@ export class BoLUtility {
if (!game.user.isGM) {
return
}
BoLUtility.removeChatMessageId( msgId )
let message = game.messages.get(msgId)
let rollData = message.getFlag("world", "bol-roll-data")
BoLUtility.removeChatMessageId(msgId)
console.log("Damage Handling", attackId, defenseMode, weaponId)
// Only GM process this
let attackDef = this.attackStore[attackId]
if (attackDef && attackDef.defenderId) {
if (attackDef.defenseDone) {
// Only GM process this
if (rollData && rollData.defenderId) {
if (rollData.defenseDone || defenseMode == 'damage-not-applied') {
return
} // ?? Why ???
attackDef.defenseDone = true
attackDef.defenseMode = defenseMode
let token = game.scenes.current.tokens.get(attackDef.targetId)
rollData.defenseDone = true
rollData.defenseMode = defenseMode
let token = game.scenes.current.tokens.get(rollData.targetId)
let defender = token.actor
if (defenseMode == 'damage-with-armor') {
let armorFormula = defender.getArmorFormula()
attackDef.rollArmor = new Roll(armorFormula)
attackDef.rollArmor.roll({ async: false })
attackDef.armorProtect = (attackDef.rollArmor.total < 0) ? 0 : attackDef.rollArmor.total
attackDef.finalDamage = attackDef.damageTotal - attackDef.armorProtect
attackDef.finalDamage = (attackDef.finalDamage < 0) ? 0 : attackDef.finalDamage
defender.sufferDamage(attackDef.finalDamage)
console.log("Armor roll -> result ", attackDef)
rollData.rollArmor = new Roll(armorFormula)
rollData.rollArmor.roll({ async: false })
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
rollData.finalDamage = rollData.damageTotal - rollData.armorProtect
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
defender.sufferDamage(rollData.finalDamage)
console.log("Armor roll -> result ", rollData)
}
if (defenseMode == 'damage-without-armor') {
attackDef.finalDamage = attackDef.damageTotal
defender.sufferDamage(attackDef.finalDamage)
rollData.finalDamage = rollData.damageTotal
defender.sufferDamage(rollData.finalDamage)
}
if (defenseMode == 'hero-reduce-damage') {
let armorFormula = defender.getArmorFormula()
attackDef.rollArmor = new Roll(armorFormula)
attackDef.rollArmor.roll({ async: false })
attackDef.armorProtect = (attackDef.rollArmor.total < 0) ? 0 : attackDef.rollArmor.total
attackDef.rollHero = new Roll("1d6")
attackDef.rollHero.roll({ async: false })
attackDef.finalDamage = attackDef.damageTotal - attackDef.rollHero.total - attackDef.armorProtect
attackDef.finalDamage = (attackDef.finalDamage < 0) ? 0 : attackDef.finalDamage
defender.sufferDamage(attackDef.finalDamage)
rollData.rollArmor = new Roll(armorFormula)
rollData.rollArmor.roll({ async: false })
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
rollData.rollHero = new Roll("1d6")
rollData.rollHero.roll({ async: false })
rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
defender.sufferDamage(rollData.finalDamage)
defender.subHeroPoints(1)
}
if (defenseMode == 'hero-in-extremis') {
attackDef.finalDamage = 0;
attackDef.weaponHero = defender.weapons.find(item => item._id == weaponId);
rollData.finalDamage = 0;
rollData.weaponHero = defender.weapons.find(item => item._id == weaponId);
defender.deleteEmbeddedDocuments("Item", [weaponId]);
}
let defenderUser
for (let user of game.users) {
if ( user.character && user.character.id == defender.id ) {
if (user.character && user.character.id == defender.id) {
defenderUser = user
}
}
}
let damageResults = {
attackId: attackDef.id,
attacker: attackDef.attacker,
rollArmor: attackDef.rollArmor,
rollHero: attackDef.rollHero,
weaponHero: attackDef.weaponHero,
armorProtect: attackDef.armorProtect,
attackId: rollData.id,
attacker: rollData.attacker,
rollArmor: rollData.rollArmor,
rollHero: rollData.rollHero,
weaponHero: rollData.weaponHero,
armorProtect: rollData.armorProtect,
name: defender.name,
defender: defender,
defenseMode: attackDef.defenseMode,
finalDamage: attackDef.finalDamage
defenseMode: rollData.defenseMode,
finalDamage: rollData.finalDamage
}
ChatMessage.create({
alias: defender.name,
@ -364,7 +558,7 @@ export class BoLUtility {
}
/* -------------------------------------------- */
static isRangedWeapon(weapon) {
return weapon.data.type == 'ranged' || weapon.data.thrown;
return weapon.system.type == 'ranged' || weapon.system.thrown;
}
/* -------------------------------------------- */
@ -407,28 +601,29 @@ export class BoLUtility {
}
/* -------------------------------------------- */
static async processAttackSuccess(attackDef) {
console.log("Attack success processing", attackDef)
if (!game.user.isGM || !attackDef.defenderId) { // Only GM process this
static async processAttackSuccess(rollData) {
console.log("Attack success processing", rollData)
if (!game.user.isGM || !rollData.defenderId) { // Only GM process this
return
}
// Build and send the defense message to the relevant people (ie GM + defender)
let defender = game.actors.get(attackDef.defenderId)
console.log("DEF WEP", attackDef, defender)
let defender = game.actors.get(rollData.defenderId)
let defenderWeapons = defender.weapons || []
this.attackStore[attackDef.id] = attackDef // Store !
ChatMessage.create({
let msg = await ChatMessage.create({
alias: defender.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', {
attackId: attackDef.id,
attacker: attackDef.attacker,
attackId: rollData.id,
attacker: rollData.attacker,
defender: defender,
defenderHeroPoints:defender.getHeroPoints(),
defenderWeapons: defenderWeapons,
damageTotal: attackDef.damageRoll.total,
damagesIgnoresArmor: attackDef.damagesIgnoresArmor,
damageTotal: rollData.damageTotal,
damagesIgnoresArmor: rollData.damagesIgnoresArmor,
})
})
msg.setFlag("world", "bol-roll-data", rollData)
console.log("DEF WEP", rollData, defender)
}
/* -------------------------------------------- */
@ -439,6 +634,9 @@ export class BoLUtility {
if (sockmsg.name == "msg_cleanup_buttons") {
$(`#${sockmsg.data.id}`).hide() // Hide the options roll buttons
}
if (sockmsg.name == "msg_request_init_roll") {
this.requestInitRoll(sockmsg.data.actorId, sockmsg.data.combatData)
}
if (sockmsg.name == "msg_damage_handling") {
BoLUtility.processDamageHandling(sockmsg.data.attackId, sockmsg.data.defenseMode, sockmsg.data.weaponId, sockmsg.data.msgId)
}
@ -446,15 +644,15 @@ export class BoLUtility {
/* -------------------------------------------- */
static computeSpellCost(spell, nbOptCond = 0) {
let pp = spell.data.properties.ppcost
let minpp = __circle2minpp[spell.data.properties.circle]
let pp = spell.system.properties.ppcost
let minpp = __circle2minpp[spell.system.properties.circle]
pp = (pp - nbOptCond < minpp) ? minpp : pp - nbOptCond
return pp
}
/* -------------------------------------------- */
static getDamageFormula(weaponData, fightOption) {
let upgradeDamage = (fightOption && fightOption.data.properties.fightoptiontype == "twoweaponsatt")
let upgradeDamage = (fightOption && fightOption.system.properties.fightoptiontype == "twoweaponsatt")
let damageString = weaponData.properties.damage
let modifier = weaponData.properties.damageModifiers ?? 0
let multiplier = weaponData.properties.damageMultiplier ?? 1
@ -466,7 +664,7 @@ export class BoLUtility {
let formula = damageString
if (damageString.includes("d") || damageString.includes("D")) {
var myReg = new RegExp('(\\d+)[dD]([\\d]+)([MB]*)?([\\+\\d]*)?', 'g')
let myReg = new RegExp('(\\d+)[dD]([\\d]+)([MB]*)?([\\+\\d]*)?', 'g')
let res = myReg.exec(damageString)
let nbDice = parseInt(res[1])
let postForm = 'kh' + nbDice
@ -484,11 +682,21 @@ export class BoLUtility {
nbDice++
modIndex = 4
}
if (res[3] == 'MM') {
postForm = 'kl' + nbDice
nbDice += 2
modIndex = 4
}
if (res[3] == 'B') {
postForm = 'kh' + nbDice
nbDice++
modIndex = 4
}
if (res[3] == 'BB') {
postForm = 'kh' + nbDice
nbDice += 2
modIndex = 4
}
}
formula = "(" + nbDice + "d" + res[2] + reroll + postForm + "+" + modifier + ") *" + multiplier
}
@ -496,31 +704,53 @@ export class BoLUtility {
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Are you sure to remove this Item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Yes, remove it",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Cancel"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await this.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static async searchItem(dataItem) {
let item
if (dataItem.pack) {
let id = dataItem.id || dataItem._id
let items = await this.loadCompendium(dataItem.pack, item => item.id == id)
item = items[0] || undefined
} else {
item = game.items.get(dataItem.id)
}
return item
}
/* -------------------------------------------- */
static updateSheets() {
// Then force opened actor refresh if needed
for (let actor of game.actors) {
if (actor.sheet.rendered) {
actor.sheet.render()
}
}
game.bol.charSummary.updatePCSummary() // Refresh if needed
}
/* -------------------------------------------- */
static removeGroupHoroscope(rollData) {
let horo = rollData.horoscopeGroupList[rollData.selectedGroupHoroscopeIndex]
let horoscopes = duplicate(game.settings.get("bol", "horoscope-group"))
let toChange = duplicate(horoscopes[horo.id])
toChange.availableDice -= horo.nbDice // Remove the dice
if (toChange.availableDice <= 0) {
horoscopes[horo.id] = undefined
} else {
horoscopes[horo.id] = toChange
}
game.settings.set("bol", "horoscope-group", horoscopes)
this.updateSheets()
}
}

View File

@ -25,6 +25,10 @@ BOL.damageMultiplier = {
"2": "x2",
"3": "x3",
"4": "x4",
"5": "x5",
"6": "x6",
"7": "7",
"8": "x8"
}
BOL.spellType = {
@ -111,6 +115,14 @@ BOL.aptitudes = {
"def" : "BOL.aptitudes.def"
}
BOL.resources = {
"hp" : "BOL.resources.hp",
"hero" : "BOL.resources.hero",
"faith" : "BOL.resources.faith",
"power" : "BOL.resources.power",
"alchemypoints" : "BOL.resources.alchemypoints"
}
BOL.weaponSizes = {
"unarmed" : "BOL.weaponSize.unarmed",
"improvised" : "BOL.weaponSize.improvised",
@ -131,6 +143,7 @@ BOL.itemCategories = {
"spell" : "BOL.itemCategory.spell",
"alchemy" : "BOL.itemCategory.alchemy",
"vehicle" : "BOL.itemCategory.vehicle",
"vehicleweapon": "BOL.itemCategory.vehicleweapon",
"other" : "BOL.itemCategory.other"
}
@ -255,7 +268,9 @@ BOL.featureSubtypes = {
"flaw" : "BOL.featureSubtypes.flaw",
"language" : "BOL.featureSubtypes.language",
"godsfaith" : "BOL.featureSubtypes.gods",
"fightoption" : "BOL.featureSubtypes.fightOption"
"fightoption" : "BOL.featureSubtypes.fightOption",
"boleffect": "BOL.featureSubtypes.effect",
"horoscope": "BOL.featureSubtypes.horoscope",
}
BOL.fightOptionTypes = {
@ -266,6 +281,7 @@ BOL.fightOptionTypes = {
"fulldefense": "BOL.fightOptionTypes.fulldefense",
"defense": "BOL.fightOptionTypes.defense",
"attack": "BOL.fightOptionTypes.attack",
"other": "BOL.fightOptionTypes.other"
}
BOL.itemIcons = {
@ -283,26 +299,163 @@ BOL.actorIcons = {
}
BOL.bougetteState = {
"nomoney": "BOL.bougette.nomoney",
"tolive": "BOL.bougette.tolive",
"easylife": "BOL.bougette.easylife",
"luxury": "BOL.bougette.luxury",
"rich": "BOL.bougette.rich"
"0": "BOL.bougette.nomoney",
"1": "BOL.bougette.tolive",
"2": "BOL.bougette.easylife",
"3": "BOL.bougette.luxury",
"4": "BOL.bougette.rich"
}
BOL.bougetteDice = {
"0": "0",
"1": "2d6-1",
"2": "2d6",
"3": "2d6+1",
"4": "2d6+2"
}
BOL.creatureSize = {
"tiny": "BOL.size.tiny",
"verysmall": "BOL.size.verysmall",
"small": "BOL.size.small",
"medium": "BOL.size.medium",
"large": "BOL.size.large",
"verylarge": "BOL.size.verylarge",
"huge": "BOL.size.huge",
"massive": "BOL.size.massive",
"enormous": "BOL.size.enormous",
"gigantic": "BOL.size.gigantic",
"immense": "BOL.size.immense",
"colossal": "BOL.size.colossal"
"tiny": {order: 1, label: "BOL.size.tiny"},
"verysmall": {order: 2, label: "BOL.size.verysmall"},
"small": {order: 3, label: "BOL.size.small"},
"medium": {order: 4, label: "BOL.size.medium"},
"large": {order: 5, label: "BOL.size.large"},
"verylarge": {order: 6, label: "BOL.size.verylarge"},
"huge": {order: 7, label: "BOL.size.huge"},
"massive": {order: 8, label: "BOL.size.massive"},
"enormous": {order: 9, label: "BOL.size.enormous"},
"gigantic": {order: 10, label: "BOL.size.gigantic"},
"immense": {order: 11, label: "BOL.size.immense"},
"colossal": {order: 12, label: "BOL.size.colossal"}
}
BOL.horoscopeAnswer = {
"favorable": "BOL.ui.horoscopefavorable",
"unfavorable": "BOL.ui.horoscopeunfavorable",
}
BOL.bolEffectModifier = {
"-8": "-8",
"-6": "-6",
"-4": "-4",
"-2": "-2",
"-1": "-1",
"1B": "1B",
"2B": "2B",
"1M": "1M",
"2M": "2M",
"+1": "+1",
"+2": "+2",
"+4": "+4",
"+6": "+6",
"+8": "+8",
}
BOL.statusEffects = [
{
"id": "dead",
"label": "EFFECT.StatusDead",
"icon": "icons/svg/skull.svg"
},
{
"id": "unconscious",
"label": "EFFECT.StatusUnconscious",
"icon": "icons/svg/unconscious.svg"
},
{
"id": "sleep",
"label": "EFFECT.StatusAsleep",
"icon": "icons/svg/sleep.svg"
},
{
"id": "stun",
"label": "EFFECT.StatusStunned",
"icon": "icons/svg/daze.svg"
},
{
"id": "prone",
"label": "EFFECT.StatusProne",
"icon": "icons/svg/falling.svg"
},
{
"id": "restrain",
"label": "EFFECT.StatusRestrained",
"icon": "icons/svg/net.svg"
},
{
"id": "paralysis",
"label": "EFFECT.StatusParalysis",
"icon": "icons/svg/paralysis.svg"
},
{
"id": "fly",
"label": "EFFECT.StatusFlying",
"icon": "icons/svg/wing.svg"
},
{
"id": "blind",
"label": "EFFECT.StatusBlind",
"icon": "icons/svg/blind.svg"
},
{
"id": "deaf",
"label": "EFFECT.StatusDeaf",
"icon": "icons/svg/deaf.svg"
},
{
"id": "silence",
"label": "EFFECT.StatusSilenced",
"icon": "icons/svg/silenced.svg"
},
{
"id": "fear",
"label": "EFFECT.StatusFear",
"icon": "icons/svg/terror.svg"
},
{
"id": "burning",
"label": "EFFECT.StatusBurning",
"icon": "icons/svg/fire.svg"
},
{
"id": "frozen",
"label": "EFFECT.StatusFrozen",
"icon": "icons/svg/frozen.svg"
},
{
"id": "shock",
"label": "EFFECT.StatusShocked",
"icon": "icons/svg/lightning.svg"
},
{
"id": "disease",
"label": "EFFECT.StatusDisease",
"icon": "icons/svg/biohazard.svg"
},
{
"id": "poison",
"label": "EFFECT.StatusPoison",
"icon": "icons/svg/poison.svg"
},
{
"id": "curse",
"label": "EFFECT.StatusCursed",
"icon": "icons/svg/sun.svg"
},
{
"id": "invisible",
"label": "EFFECT.StatusInvisible",
"icon": "icons/svg/invisible.svg"
},
{
"id": "target",
"label": "EFFECT.StatusTarget",
"icon": "icons/svg/target.svg"
},
{
"id": "eye",
"label": "EFFECT.StatusMarked",
"icon": "icons/svg/eye.svg"
}
]
BOL.debug = false;

View File

@ -76,6 +76,10 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('count', function (list) {
return list.length;
})
Handlebars.registerHelper('countKeys', function (obj) {
return Object.keys(obj).length;
})
Handlebars.registerHelper('isEnabled', function (configKey) {
return game.settings.get("bol", configKey);
})
@ -103,12 +107,18 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('sub', function (a, b) {
return parseInt(a) - parseInt(b);
})
Handlebars.registerHelper('abbrev2', function (a) {
return a.substring(0,2);
})
Handlebars.registerHelper('abbrev3', function (a) {
return a.substring(0,3);
})
Handlebars.registerHelper('valueAtIndex', function (arr, idx) {
return arr[idx];
})
Handlebars.registerHelper('includesKey', function (items, type, key) {
// console.log(items);
return items.filter(i => i.type === type).map(i => i.data.key).includes(key);
return items.filter(i => i.type === type).map(i => i.system.key).includes(key);
})
Handlebars.registerHelper('includes', function (array, val) {
return array.includes(val);
@ -123,7 +133,14 @@ export const registerHandlebarsHelpers = function () {
}
return false
})
Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase() + text.slice(1)
})
Handlebars.registerHelper('upperFirstOnly', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase()
})
}

View File

@ -49,7 +49,7 @@ export default function registerHooks() {
let macro = game.macros.entities.find(m => (m.name === actor.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: actor.data.name,
name: actor.name,
type: "script",
img: "icons/svg/dice-target.svg",
command: command
@ -65,9 +65,9 @@ export default function registerHooks() {
let macro = game.macros.entities.find(m => (m.name === journal.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: journal.data.name,
name: journal.name,
type: "script",
img: (journal.data.img) ? journal.data.img : "icons/svg/book.svg",
img: (journal.img) ? journal.img : "icons/svg/book.svg",
command: command
}, {displaySheet: false})
game.user.assignHotbarMacro(macro, slot);
@ -75,4 +75,18 @@ export default function registerHooks() {
}
return false;
});
/********************************************************************************** */
Hooks.on("renderActorDirectory", (app, html, data) => {
if (game.user.isGM) {
const button = document.createElement('button');
button.style.width = '95%';
button.innerHTML = game.i18n.localize("BOL.ui.pclistbutton")
button.addEventListener('click', () => {
game.bol.charSummary.render(true)
})
html.find('.header-actions').after(button)
}
})
}

View File

@ -37,13 +37,13 @@ export class Macros {
};
if(rollType === "attribute") {
let attribute = eval(`actor.data.data.attributes.${key}`);
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.data.data.aptitudes.${key}`);
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);

View File

@ -16,8 +16,12 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/actor/parts/tabs/actor-equipment.hbs",
"systems/bol/templates/actor/parts/tabs/actor-spellalchemy.hbs",
"systems/bol/templates/actor/parts/tabs/actor-biodata.hbs",
"systems/bol/templates/actor/parts/tabs/actor-horoscope-group.hbs",
"systems/bol/templates/actor/parts/tabs/creature-stats.hbs",
"systems/bol/templates/actor/parts/tabs/creature-actions.hbs",
"systems/bol/templates/actor/parts/tabs/vehicle-stats.hbs",
"systems/bol/templates/actor/parts/tabs/vehicle-description.hbs",
"systems/bol/templates/actor/parts/tabs/vehicle-weapons.hbs",
// ITEMS
"systems/bol/templates/item/parts/item-header.hbs",
"systems/bol/templates/item/parts/properties/feature-properties.hbs",
@ -33,23 +37,30 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/item/parts/properties/feature/career-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/boon-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/flaw-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/effect-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/origin-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/race-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/fightoption-properties.hbs",
"systems/bol/templates/item/parts/properties/item/weapon-vehicle-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/horoscope-properties.hbs",
// DIALOGS
"systems/bol/templates/chat/rolls/attack-damage-card.hbs",
"systems/bol/templates/chat/rolls/spell-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/horoscope-roll-card.hbs",
"systems/bol/templates/dialogs/aptitude-roll-part.hbs",
"systems/bol/templates/dialogs/attribute-roll-part.hbs",
"systems/bol/templates/dialogs/mod-roll-part.hbs",
"systems/bol/templates/dialogs/adv-roll-part.hbs",
"systems/bol/templates/dialogs/career-roll-part.hbs",
"systems/bol/templates/dialogs/effect-roll-part.hbs",
"systems/bol/templates/dialogs/boons-roll-part.hbs",
"systems/bol/templates/dialogs/flaws-roll-part.hbs",
"systems/bol/templates/dialogs/total-roll-part.hbs",
"systems/bol/templates/dialogs/fightoptions-roll-part.hbs",
"systems/bol/templates/dialogs/horoscope-roll-part.hbs"
];
// Load the template parts

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1 @@
MANIFEST-000107

0
packs/aides-de-jeu/LOCK Normal file
View File

8
packs/aides-de-jeu/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.430277 7f84f7fff6c0 Recovering log #105
2023/12/04-21:16:34.440834 7f84f7fff6c0 Delete type=3 #103
2023/12/04-21:16:34.440885 7f84f7fff6c0 Delete type=0 #105
2023/12/04-21:29:36.294789 7f84f5ffb6c0 Level-0 table #110: started
2023/12/04-21:29:36.294815 7f84f5ffb6c0 Level-0 table #110: 0 bytes OK
2023/12/04-21:29:36.301173 7f84f5ffb6c0 Delete type=0 #108
2023/12/04-21:29:36.301317 7f84f5ffb6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!Yl1RKQb0BjVUtilk.kNb9ZrMbgONi1mlE' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.301343 7f84f5ffb6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!Yl1RKQb0BjVUtilk.kNb9ZrMbgONi1mlE' @ 0 : 0; will stop at (end)

View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.144165 7f5610ff96c0 Recovering log #101
2023/10/28-18:46:58.154293 7f5610ff96c0 Delete type=3 #99
2023/10/28-18:46:58.154395 7f5610ff96c0 Delete type=0 #101
2023/10/28-18:47:32.356263 7f56037fe6c0 Level-0 table #106: started
2023/10/28-18:47:32.356293 7f56037fe6c0 Level-0 table #106: 0 bytes OK
2023/10/28-18:47:32.362824 7f56037fe6c0 Delete type=0 #104
2023/10/28-18:47:32.375967 7f56037fe6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!Yl1RKQb0BjVUtilk.kNb9ZrMbgONi1mlE' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.376034 7f56037fe6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!Yl1RKQb0BjVUtilk.kNb9ZrMbgONi1mlE' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/armors/000005.ldb Normal file

Binary file not shown.

0
packs/armors/000016.log Normal file
View File

1
packs/armors/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000014

0
packs/armors/LOCK Normal file
View File

8
packs/armors/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.402100 7f84f6ffd6c0 Recovering log #12
2023/12/04-21:16:34.413052 7f84f6ffd6c0 Delete type=3 #10
2023/12/04-21:16:34.413101 7f84f6ffd6c0 Delete type=0 #12
2023/12/04-21:29:36.274254 7f84f5ffb6c0 Level-0 table #17: started
2023/12/04-21:29:36.274306 7f84f5ffb6c0 Level-0 table #17: 0 bytes OK
2023/12/04-21:29:36.281091 7f84f5ffb6c0 Delete type=0 #15
2023/12/04-21:29:36.301284 7f84f5ffb6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.301326 7f84f5ffb6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

8
packs/armors/LOG.old Normal file
View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.118619 7f5603fff6c0 Recovering log #8
2023/10/28-18:46:58.129553 7f5603fff6c0 Delete type=3 #6
2023/10/28-18:46:58.129618 7f5603fff6c0 Delete type=0 #8
2023/10/28-18:47:32.323904 7f56037fe6c0 Level-0 table #13: started
2023/10/28-18:47:32.323933 7f56037fe6c0 Level-0 table #13: 0 bytes OK
2023/10/28-18:47:32.329984 7f56037fe6c0 Delete type=0 #11
2023/10/28-18:47:32.349515 7f56037fe6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.349581 7f56037fe6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@ -1,59 +0,0 @@
{"_id":"039ZF3E3MtAGwbiX","name":"Intrépide","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Intrépide</h1><p>votre personnage est insensible à la peur. Même une peur magiquement induite na aucune prise sur lui.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"2bSL4HJMu8Zh8UKq","name":"Attirant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Attirant</h1><p>vous êtes particulièrement beau ou séduisant. Vous bénéficiez dun dé de bonus dans les situations où lapparence peut jouer un rôle.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"3fRMKd0xX0NL0kxO","name":"Vigueur céruléenne","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Vigueur céruléenne</h1><p>vous êtes grand et fort. Ajoutez 1 à votre vigueur. Votre score maximum en vigueur passe à 6 au lieu de 5, et votre score de départ maximum en vigueur peut être de 4 (au lieu de 3).</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"3jxkJo7vakII1kw0","name":"Renard du désert","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Renard du désert</h1><p>lorsque vous vous trouvez dans un désert, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"44bJY4xBP9ICdxrR","name":"Vue perçante","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Vue perçante</h1><p>à chaque fois que vous effectuez un jet daction sous esprit pour percevoir quelque chose grâce à votre vue, vous bénéficiez dun dé de bonus.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"4eCfIflrqYcQMQEE","name":"Laboratoire fourni","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Laboratoire fourni</h1><p>vous disposez dun laboratoire de qualité pour mener vos expériences. Vous bénéficiez dun dé de bonus aux jets pour créer des préparations alchimiques ou des instruments mécaniques lorsque vous vous trouvez dans votre laboratoire. Celui-ci doit toutefois être réapprovisionné de temps à autre, ce qui vous conduit à partir à laventure pour trouver les moyens financiers et matériels de refaire vos stocks.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"4j27jk1sauYKXUUY","name":"Magie des Rois-Sorciers","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Magie des Rois-Sorciers</h1><p>vous avez atteint une certaine compréhension des antiques secrets des Rois-Sorciers. Vous bénéficiez dun dé de bonus pour lancer des sortilèges. Si vous choisissez cet avantage, vous devez prendre un désavantage supplémentaire.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"76IT7lAHNjPHvoxu","name":"Savant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Savant</h1><p>ajoutez 1 à votre esprit. Votre score maximum en esprit passe à 6 au lieu de 5, et votre score de départ maximum en esprit peut être de 4 (au lieu de 3).</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"7iHc6ZbgRDTcWx1x","name":"Poings dacier","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Poings dacier</h1><p>vous avez les poings durs comme la pierre après des années dentraînement ou à force de vous bagarrer dans toutes les tavernes de la ville. Vous ajoutez votre vigueur aux dégâts quand vous combattez à mains nues.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"88DKwHbEHNr2zkb4","name":"Gamin des rues","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Gamin des rues</h1><p>vous êtes un enfant des rues. Vous bénéficiez dun dé de bonus quand vous interagissez avec les gens des bas-fonds de la cité, ou pour des activités comme filer quelquun ou remarquer un détail dans la rue (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"8LbWRVmpo2byaOae","name":"Colosse","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Colosse</h1><p>vous savez mobiliser votre vigueur pour accomplir de véritables tours de force. Vous bénéficiez dun dé de bonus pour briser, lever, tirer ou pousser des objets.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"8TKFwebDXkT4r3TW","name":"Dur à cuire","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Dur à cuire</h1><p>vous avez la robustesse dun bronyx. Ajoutez 2 points à votre vitalité.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"969U7SVd2JSBkHDq","name":"Artiste","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Artiste</h1><p>vous avez une sensibilité dartiste. Vous bénéficiez dun dé de bonus pour créer ou estimer des objets dart.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"DTZhsPUxseKuahCa","name":"Amis dans la pègre","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Amis dans la pègre</h1><p>vous comptez des amis peu recommandables parmi les différentes pègres qui gangrènent la Lémurie. Ils peuvent vous aider à trouver un receleur, vous fournir une planque, etc.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"DpO9z1NEg9C8MHGY","name":"Amis haut placés","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Amis haut placés</h1><p>vous jouissez de contacts au sein des plus hauts échelons de la société. Même sils ne seront pas en général disposés à risquer leur tête pour vous, ils pourront vous apporter de laide (comme vous obtenir une audience auprès dun autre personnage important, vous fournir des informations, jouer de leur influence auprès de la noblesse locale, etc.). Bien sûr, ces amis attendront parfois en retour une faveur de votre part...</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"DxCf9WsPm4tGRGri","name":"Athlète","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Athlète</h1><p>vous bénéficiez dun dé de bonus quand vous entreprenez des activités athlétiques (autres que le combat) comme courir, nager, grimper ou sauter.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"FaACBPxznjBInrNN","name":"Doigts de fée","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Doigts de fée</h1><p>vous bénéficiez dun dé de bonus pour toutes les tâches exigeant une grande dextérité, comme le vol à la tire, la fabrication dobjets, le jonglage ou la triche aux cartes et aux dés.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"GyNOCPaFw7bfjDUE","name":"Tireur puissant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Tireur puissant</h1><p>avec un type darme à distance choisi (arc, fronde, javelot, etc.), vous ajoutez votre vigueur aux dégâts.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"HiD90AY8nD7OXXmr","name":"Bien né","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Bien né</h1><p>vous avez grandi dans les palais et les cours, parmi la noblesse et la haute société. Vous bénéficiez dun dé de bonus pour agir selon les règles de létiquette et de la courtoisie.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"HkJU2TaccPnBtZPh","name":"Montagnard","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Montagnard</h1><p>vous avez grandi dans les montagnes. Lorsque vous vous trouvez dans un paysage montagneux, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"IQdY6CJwUpXofsbM","name":"Ami des céruléens","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Ami des céruléens</h1><p>vous avez grandi à proximité des géants bleus, ou bien des circonstances particulières vous ont valu leur amitié, et ils vous considèrent comme un des leurs. Vous bénéficiez dun dé de bonus quand vous interagissez avec les nomades bleus.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"J08pVCsXzQYKijOF","name":"Résistant à la magie","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Résistant à la magie</h1><p>si vous êtes visé par un sortilège, lancez un d6. Sur un 6, le sortilège na aucun effet sur vous.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"JrxFjMUc9emmaiJe","name":"Ouïe fine","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Ouïe fine</h1><p>à chaque fois que vous effectuez un jet daction sous esprit pour percevoir quelque chose grâce à votre ouïe, vous bénéficiez dun dé de bonus.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"JxFxp2CS8dluwC16","name":"Bagarreur","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Bagarreur</h1><p>vous êtes un boxeur et un lutteur compétent. Vous bénéficiez dun dé de bonus à lattaque quand vous combattez à mains nues.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"LNP6F8z4j15HuuNC","name":"Fêtard","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Fêtard</h1><p>vous connaissez bien les auberges et les tavernes, et vous nêtes pas le dernier quand il sagit de lever le coude. Vous bénéficiez dun dé de bonus pour recueillir des informations, trouver des contacts ou obtenir des biens et des services quand vous vous trouvez dans une taverne. De plus, vous tenez très bien lalcool et bénéficiez également dun dé de bonus pour résister aux effets de la boisson.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"Ota12xlPiqDfM3te","name":"Maître du déguisement","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Maître du déguisement</h1><p>vous bénéficiez dun dé de bonus quand vous tentez de dissimuler votre identité. De plus, si vous souhaitez intervenir promptement dans une scène où votre personnage nétait pas présent, vous pouvez dépenser 1 point dhéroïsme pour faire partie du décor, par exemple déguisé en garde de la patrouille ou en bourgeois passant dans la rue. En fait, vous étiez là depuis le début, mais incognito!</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"Td0r46VMDXd5Z65z","name":"Tigre des neiges","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Tigre des neiges</h1><p>vous avez grandi dans la toundra gelée. Lorsque vous vous trouvez dans un paysage neigeux, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"VXVu8CTas4nhfUST","name":"Sentir la magie","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Sentir la magie</h1><p>vous bénéficiez dun dé de bonus quand vous essayez de reconnaître (ou de traquer) un sorcier, un effet ou un artefact magique.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"W4onU67kbMUL3WVa","name":"Résistant aux poisons","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Résistant aux poisons</h1><p>vous bénéficiez dun dé de bonus pour résister aux effets des drogues, des venins, des toxines, ainsi que de lalcool.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"WSXuMtTEvjC2qOH7","name":"Pied marin","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Pied marin</h1><p>vous avez grandi dans un environnement de navires et de bateaux. Vous bénéficiez dun dé de bonus pour manœuvrer une embarcation ou entreprendre des activités physiques à bord dun navire (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"WTl5A8SbC6o2bRna","name":"Fortuné","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Fortuné</h1><p>vous disposez dune source de revenus ou dun héritage. Vous bénéficiez dun dé de bonus quand vous cherchez à acquérir des biens ou des services dans votre cité dorigine.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"XXaAGtAadv5Ltlht","name":"Santé de fer","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Santé de fer</h1><p>vous êtes immunisé à toutes les maladies, y compris celles dorigine magique.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"XfGnKJU87Tv9AKtL","name":"Bibliothèque savante","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Bibliothèque savante</h1><p>vous disposez dune bibliothèque de qualité pour mener vos recherches. Vous bénéficiez dun dé de bonus pour recueillir des informations lorsque vous vous trouvez dans votre bibliothèque. Vous devez toutefois enrichir régulièrement vos collections, ce qui vous conduit à partir à laventure pour trouver les moyens dacquérir de nouveaux manuscrits.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"XiUi4LnrOswsEpXR","name":"Vigilant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Vigilant</h1><p>vous jouissez dune excellente capacité de réaction face au danger. Vous bénéficiez dun dé de bonus à vos jets de réaction.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"aYv5FEDWzYRwIwRm","name":"Né en selle","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Né en selle</h1><p>vous bénéficiez dun dé de bonus pour chevaucher des montures et agir en selle (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"cDe8mZ7vjpI1o1lZ","name":"Mains guérisseuses","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Mains guérisseuses</h1><p>vous bénéficiez dun dé de bonus sur les tests destinés à aider un blessé à récupérer de ses blessures, dun empoisonnement, etc. Vous devez posséder la carrière médecin pour prendre cet avantage.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"dpS4VXHUGddCq7FY","name":"Intimidant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Intimidant</h1><p>vous bénéficiez dun dé de bonus à chaque fois que vous essayez de forcer quelquun à vous donner une information ou à faire une chose à laquelle il répugne.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"dslHEmEqSZN8IsPa","name":"Fils des plaines","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Fils des plaines</h1><p>vous avez grandi dans les plaines. Lorsque vous vous trouvez dans un paysage de plaine, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"eH6ER2AVPrhzZGn1","name":"Combat à laveugle","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Combat à laveugle</h1><p>pas de lumière ? Aucun problème. En vous basant sur les odeurs, les bruits, les déplacements dair, vous ne faites plus quun avec le monde qui vous entoure. Votre personnage ignore les malus imposés par le MJ pour combattre dans lobscurité.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"ft3lNO5aDd9eK40k","name":"Roi de la jungle","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Roi de la jungle</h1><p>vous avez grandi dans la jungle. Lorsque vous vous trouvez dans un paysage de jungle, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"fzsDuaqZtvt4wue4","name":"Arme favorite","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Arme favorite</h1><p>vous disposez dune arme de qualité (une grande épée valgardienne, un arc long de Tyrus, une fronde dAxos, un kriss halakhi, un khastok de Malakut, une hache dabordage de Parsool, une rapière de Satarla, ou une arme spécialement fabriquée pour vous ou dont vous avez hérité) et vous vous êtes entraîné à son maniement depuis lenfance. Vous bénéficiez dun dé de bonus lorsque vous utilisez cette arme (ou, si elle est perdue, volée ou détruite, une arme de substitution qui devra reproduire ses qualités uniques, ce qui risque dêtre particulièrement onéreux).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"giB7dYrcKv12m13K","name":"Peau dure","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Peau dure</h1><p>vous avez une peau particulièrement résistante et épaisse qui vous confère +1 en protection, même quand vous ne portez pas darmure.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"glCazKBiZ4TA5Tnt","name":"Inspirateur","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Inspirateur</h1><p>vous savez motiver vos amis et vos partisans. Cela peut se faire au moyen dune invocation aux dieux, dun discours enflammé ou dune musique inspirante, à moins que cela ne vienne simplement de votre présence charismatique. Pour le round qui suit lutilisation de cet avantage, vos compagnons bénéficient dun dé de bonus à tous leurs jets dattaque (à condition dêtre à portée de voix). Cet avantage ne peut être utilisé gratuitement quune fois par jour ; toute utilisation supplémentaire exigera la dépense de 1 point dhéroïsme.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"jkgnvC8cAK7K5Ck9","name":"Récupération rapide","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Récupération rapide</h1><p>vous jouissiez dune constitution hors norme. Lorsque vous récupérez après un combat, vous regagnez 1 point de vitalité supplémentaire (qui sajoute à la récupération normale, égale à la moitié des points perdus). De plus, si vous êtes blessé, vous récupérez 1 point de vitalité par jour, peu importe les activités que vous entreprenez.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"lDSYZ54BnXt4VYlE","name":"Cri de guerre","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Cri de guerre</h1><p>le personnage pousse son cri de guerre pour effrayer les ennemis à portée de voix. Ces derniers subissent un dé de malus sur tous leurs jets dattaque durant le round qui suit le cri de guerre. Cet avantage ne peut être utilisé gratuitement quune fois par jour ; toute utilisation supplémentaire exigera la dépense de 1 point dhéroïsme.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"mwK667DIyh2QGqPV","name":"Beau parleur","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Beau parleur</h1><p>vous êtes très persuasif et excellent menteur. Vous bénéficiez dun dé de bonus quand vous voulez mentir, escroquer, baratiner ou tromper quelquun.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"nl7sgrsCy0T0P127","name":"Pouvoir du Néant","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Pouvoir du Néant</h1><p>vous avez porté le regard sur labîme de noirceur du Néant, et recevez deux points de pouvoir supplémentaires. Si vous choisissez cet avantage, vous devez aussi prendre un désavantage supplémentaire.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"oFnXU6VPTG5QypMz","name":"Agilité de lhomme-oiseau","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Agilité de lhomme-oiseau</h1><p>ajoutez 1 à votre agilité. Votre score maximum en agilité passe à 6 au lieu de 5, et votre score de départ maximum en agilité peut être de 4 (au lieu de 3).</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"oiASQBPLrFXYEVuP","name":"Ami des bêtes","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Ami des bêtes</h1><p>vous possédez une affinité naturelle avec les animaux. Vous bénéficiez dun dé de bonus quand vous interagissez avec eux. Si vous êtes dresseur, vous pouvez avoir avec vous deux ou trois compagnons animaux de taille petite, ou un seul de taille moyenne ou grande.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"qGI3aI5xA0mqHXdU","name":"Discret","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Discret</h1>\n<p>vous &ecirc;tes vif et agile. Vous b&eacute;n&eacute;ficiez d&rsquo;un d&eacute; de bonus dans les situations o&ugrave; vous essayez de faire preuve de discr&eacute;tion.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"sggncrizxp9b84sY","name":"Érudit","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Érudit</h1><p>quand vous cherchez à vous souvenir dun fait relevant de votre domaine de compétence, vous bénéficiez dun dé de bonus.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"snSCU6lBKz8nzqeQ","name":"Baudrier de guerre","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Baudrier de guerre</h1><p>cet avantage autorise votre personnage à ne porter quun pagne ou un bikini en cotte de mailles et un simple baudrier (léquivalent dune armure légère au mieux), tout en considérant quil sagit dune armure moyenne en terme de protection, et ce sans subir le moindre malus darmure.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"wZjOWMREZRw1tLO0","name":"Marqué par les dieux","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Marqué par les dieux</h1><p>les dieux vous accordent leur faveur. Vous gagnez 1 point dhéroïsme supplémentaire.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"xAyqso8n6MaJNTJs","name":"Perspicace","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Perspicace</h1><p>vous êtes doué pour percer à jour les menteurs. Quand quelquun tente de vous mentir ou de vous entourlouper, vous bénéficiez dun dé de bonus pour le remarquer. Cela ne veut pas dire que vous connaîtrez forcément la vérité, mais vous saurez au moins quon est en train de vous mentir.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"yEsGuSGUDVYFdzRV","name":"Roi de lévasion","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Roi de lévasion</h1><p>cordes, chaînes, menottes, prison, rien ne vous retient bien longtemps. Que ce soit par vos talents ou par un coup de chance, vous finissez toujours par vous libérer. Vous bénéficiez dun dé de bonus lorsque vous tentez de vous évader ou de vous libérer de vos liens.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"ywZsgCLCszvZWzfB","name":"Odorat développé","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Odorat développé</h1><p>à chaque fois que vous effectuez un jet daction sous esprit pour percevoir quelque chose grâce à votre odorat, vous bénéficiez dun dé de bonus.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"yxkhtcJA6ziEBsHa","name":"Pisteur des marais","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Pisteur des marais</h1><p>vous avez grandi dans les marais. Lorsque vous vous trouvez dans un paysage de marécage, vous bénéficiez dun dé de bonus pour pister, piéger ou chasser, ainsi que pour dautres activités similaires de survie (mais pas pour combattre).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"yy7b6rYzyXTXAc5K","name":"Outillage","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Outillage</h1><p>vous possédez un jeu doutils adaptés à votre métier ou votre artisanat. Vous bénéficiez dun dé de bonus quand vous entreprenez une action où lemploi de ces outils peut se révéler efficace.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}
{"_id":"zgspy1QKaxdEetEw","name":"Vision nocturne","type":"feature","img":"/systems/bol/ui/icons/boon.webp","data":{"category":null,"subtype":"boon","description":"<h1>Vision nocturne</h1><p>vous bénéficiez dun dé de bonus quand lobscurité impose un malus à la vision.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"EEnCVoPAR7pMjRym":3},"flags":{}}

BIN
packs/boons/000005.ldb Normal file

Binary file not shown.

0
packs/boons/000108.log Normal file
View File

1
packs/boons/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000106

0
packs/boons/LOCK Normal file
View File

8
packs/boons/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.244782 7f84f6ffd6c0 Recovering log #104
2023/12/04-21:16:34.255803 7f84f6ffd6c0 Delete type=3 #102
2023/12/04-21:16:34.255859 7f84f6ffd6c0 Delete type=0 #104
2023/12/04-21:29:36.227120 7f84f5ffb6c0 Level-0 table #109: started
2023/12/04-21:29:36.227155 7f84f5ffb6c0 Level-0 table #109: 0 bytes OK
2023/12/04-21:29:36.233490 7f84f5ffb6c0 Delete type=0 #107
2023/12/04-21:29:36.247612 7f84f5ffb6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.247638 7f84f5ffb6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

8
packs/boons/LOG.old Normal file
View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.021346 7f5603fff6c0 Recovering log #100
2023/10/28-18:46:58.032316 7f5603fff6c0 Delete type=3 #98
2023/10/28-18:46:58.032370 7f5603fff6c0 Delete type=0 #100
2023/10/28-18:47:32.275625 7f56037fe6c0 Level-0 table #105: started
2023/10/28-18:47:32.275699 7f56037fe6c0 Level-0 table #105: 0 bytes OK
2023/10/28-18:47:32.281755 7f56037fe6c0 Delete type=0 #103
2023/10/28-18:47:32.288117 7f56037fe6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.298397 7f56037fe6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

BIN
packs/boons/MANIFEST-000106 Normal file

Binary file not shown.

View File

@ -1,8 +0,0 @@
{"name":"Attaque Timide","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"flaw","description":"<p>Attaque timide : la cr&eacute;ature subit un d&eacute; de malus &nbsp;aux jets d&rsquo;attaque.</p>","properties":{"isbonusdice":true,"ismalusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{},"_id":"CoqlfsDV1gL5swbK"}
{"name":"Prédateur","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>&nbsp;la cr&eacute;ature lance un d&eacute; de bonus pour tout ce qui concerne le pistage d&rsquo;une proie.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{},"_id":"DFdLY7fkVZDIf7rU"}
{"name":"Camouflage","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>Camouflage : la cr&eacute;ature est difficile &agrave; rep&eacute;rer &nbsp;en raison de la coloration ou de la texture de sa&nbsp;peau ou de son pelage (d&eacute; de bonus pour se cacher).</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.b0PsoNEd8a0dN2Wm"}},"_id":"P2pgGD4FVKzOZ4uM"}
{"name":"Attaque féroce","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>&nbsp;la cr&eacute;ature b&eacute;n&eacute;ficie d&rsquo;un d&eacute; de&nbsp;bonus aux jets d&rsquo;attaque.</p>","properties":{"isbonusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.b0PsoNEd8a0dN2Wm"}},"_id":"RqEO7ELH72MZBZFV"}
{"name":"Attaque Venimeuse","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>Attaque venimeuse : la description de la &nbsp;cr&eacute;ature indique les effets de son venin</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.b0PsoNEd8a0dN2Wm"}},"_id":"U77iMP7V529zbJt9"}
{"name":"Attaque Multiple","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>Attaques multiples : la cr&eacute;ature poss&egrave;de <span style=\"font-family: var(--font-primary); font-size: var(--font-size-14);\">&nbsp;deux types d&rsquo;attaque totalement diff&eacute;rents qui&nbsp;</span>n&eacute;cessitent deux jets d&rsquo;attaque s&eacute;par&eacute;s.</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.b0PsoNEd8a0dN2Wm"}},"_id":"USkChPye9J4sZxwC"}
{"name":"Déficience","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"flaw","description":"<p>D&eacute;ficience : la cr&eacute;ature est afflig&eacute;e d&rsquo;un odorat,&nbsp;d&rsquo;une audition ou d&rsquo;une vision d&eacute;ficiente et subit&nbsp;un d&eacute; de malus quand elle doit y faire appel.</p>","properties":{"isbonusdice":true,"ismalusdice":true},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.P0r8grkVY7xu5HOS"}},"_id":"htSqR7I7F5GVIelK"}
{"name":"Attaque Spéciale","type":"feature","img":"icons/svg/item-bag.svg","data":{"category":null,"subtype":"boon","description":"<p>Attaque sp&eacute;ciale : la cr&eacute;ature attaque selon une m&eacute;thode inhabituelle. La nature de cette attaque est expliqu&eacute;e dans sa description.</p>\n<p>&nbsp;</p>","properties":{"isbonusdice":false},"rank":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.b0PsoNEd8a0dN2Wm"}},"_id":"yofwG0YrsL902G77"}

Binary file not shown.

View File

View File

@ -0,0 +1 @@
MANIFEST-000106

View File

View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.258787 7f84f77fe6c0 Recovering log #104
2023/12/04-21:16:34.268652 7f84f77fe6c0 Delete type=3 #102
2023/12/04-21:16:34.268705 7f84f77fe6c0 Delete type=0 #104
2023/12/04-21:29:36.233597 7f84f5ffb6c0 Level-0 table #109: started
2023/12/04-21:29:36.233621 7f84f5ffb6c0 Level-0 table #109: 0 bytes OK
2023/12/04-21:29:36.239701 7f84f5ffb6c0 Delete type=0 #107
2023/12/04-21:29:36.247623 7f84f5ffb6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.247651 7f84f5ffb6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.033991 7f5611ffb6c0 Recovering log #100
2023/10/28-18:46:58.043491 7f5611ffb6c0 Delete type=3 #98
2023/10/28-18:46:58.043553 7f5611ffb6c0 Delete type=0 #100
2023/10/28-18:47:32.281934 7f56037fe6c0 Level-0 table #105: started
2023/10/28-18:47:32.281973 7f56037fe6c0 Level-0 table #105: 0 bytes OK
2023/10/28-18:47:32.287993 7f56037fe6c0 Delete type=0 #103
2023/10/28-18:47:32.298372 7f56037fe6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.298448 7f56037fe6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
packs/careers/000005.ldb Normal file

Binary file not shown.

0
packs/careers/000108.log Normal file
View File

1
packs/careers/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000106

0
packs/careers/LOCK Normal file
View File

8
packs/careers/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.285844 7f84f67fc6c0 Recovering log #104
2023/12/04-21:16:34.295915 7f84f67fc6c0 Delete type=3 #102
2023/12/04-21:16:34.295974 7f84f67fc6c0 Delete type=0 #104
2023/12/04-21:29:36.267733 7f84f5ffb6c0 Level-0 table #109: started
2023/12/04-21:29:36.267761 7f84f5ffb6c0 Level-0 table #109: 0 bytes OK
2023/12/04-21:29:36.273875 7f84f5ffb6c0 Delete type=0 #107
2023/12/04-21:29:36.274078 7f84f5ffb6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.274142 7f84f5ffb6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

8
packs/careers/LOG.old Normal file
View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.057967 7f56117fa6c0 Recovering log #100
2023/10/28-18:46:58.068576 7f56117fa6c0 Delete type=3 #98
2023/10/28-18:46:58.068630 7f56117fa6c0 Delete type=0 #100
2023/10/28-18:47:32.298557 7f56037fe6c0 Level-0 table #105: started
2023/10/28-18:47:32.298612 7f56037fe6c0 Level-0 table #105: 0 bytes OK
2023/10/28-18:47:32.304559 7f56037fe6c0 Delete type=0 #103
2023/10/28-18:47:32.323741 7f56037fe6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.323783 7f56037fe6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
packs/cartes/000005.ldb Normal file

Binary file not shown.

0
packs/cartes/000108.log Normal file
View File

1
packs/cartes/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000106

0
packs/cartes/LOCK Normal file
View File

8
packs/cartes/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.444268 7f84f67fc6c0 Recovering log #104
2023/12/04-21:16:34.454337 7f84f67fc6c0 Delete type=3 #102
2023/12/04-21:16:34.454390 7f84f67fc6c0 Delete type=0 #104
2023/12/04-21:29:36.307716 7f84f5ffb6c0 Level-0 table #109: started
2023/12/04-21:29:36.307782 7f84f5ffb6c0 Level-0 table #109: 0 bytes OK
2023/12/04-21:29:36.314556 7f84f5ffb6c0 Delete type=0 #107
2023/12/04-21:29:36.327965 7f84f5ffb6c0 Manual compaction at level-0 from '!scenes!1ZDXKpKixS12xzW3' @ 72057594037927935 : 1 .. '!scenes.tiles!78gEyXvSfBJi9iL1.rJglQmZXB0eRIL8y' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.328032 7f84f5ffb6c0 Manual compaction at level-1 from '!scenes!1ZDXKpKixS12xzW3' @ 72057594037927935 : 1 .. '!scenes.tiles!78gEyXvSfBJi9iL1.rJglQmZXB0eRIL8y' @ 0 : 0; will stop at (end)

8
packs/cartes/LOG.old Normal file
View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.157567 7f56117fa6c0 Recovering log #100
2023/10/28-18:46:58.168362 7f56117fa6c0 Delete type=3 #98
2023/10/28-18:46:58.168445 7f56117fa6c0 Delete type=0 #100
2023/10/28-18:47:32.362933 7f56037fe6c0 Level-0 table #105: started
2023/10/28-18:47:32.362962 7f56037fe6c0 Level-0 table #105: 0 bytes OK
2023/10/28-18:47:32.369115 7f56037fe6c0 Delete type=0 #103
2023/10/28-18:47:32.375986 7f56037fe6c0 Manual compaction at level-0 from '!scenes!1ZDXKpKixS12xzW3' @ 72057594037927935 : 1 .. '!scenes.tiles!78gEyXvSfBJi9iL1.rJglQmZXB0eRIL8y' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.376049 7f56037fe6c0 Manual compaction at level-1 from '!scenes!1ZDXKpKixS12xzW3' @ 72057594037927935 : 1 .. '!scenes.tiles!78gEyXvSfBJi9iL1.rJglQmZXB0eRIL8y' @ 0 : 0; will stop at (end)

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
packs/creatures/000005.ldb Normal file

Binary file not shown.

View File

1
packs/creatures/CURRENT Normal file
View File

@ -0,0 +1 @@
MANIFEST-000106

0
packs/creatures/LOCK Normal file
View File

8
packs/creatures/LOG Normal file
View File

@ -0,0 +1,8 @@
2023/12/04-21:16:34.555441 7f84f77fe6c0 Recovering log #104
2023/12/04-21:16:34.566877 7f84f77fe6c0 Delete type=3 #102
2023/12/04-21:16:34.566941 7f84f77fe6c0 Delete type=0 #104
2023/12/04-21:29:36.355366 7f84f5ffb6c0 Level-0 table #109: started
2023/12/04-21:29:36.355392 7f84f5ffb6c0 Level-0 table #109: 0 bytes OK
2023/12/04-21:29:36.362705 7f84f5ffb6c0 Delete type=0 #107
2023/12/04-21:29:36.362912 7f84f5ffb6c0 Manual compaction at level-0 from '!actors!1QGaindSWLCT4QXD' @ 72057594037927935 : 1 .. '!actors.items!yGlDTCiMiaH4vJTH.mvXGkWoCcRGjUpYV' @ 0 : 0; will stop at (end)
2023/12/04-21:29:36.362955 7f84f5ffb6c0 Manual compaction at level-1 from '!actors!1QGaindSWLCT4QXD' @ 72057594037927935 : 1 .. '!actors.items!yGlDTCiMiaH4vJTH.mvXGkWoCcRGjUpYV' @ 0 : 0; will stop at (end)

8
packs/creatures/LOG.old Normal file
View File

@ -0,0 +1,8 @@
2023/10/28-18:46:58.256101 7f5611ffb6c0 Recovering log #100
2023/10/28-18:46:58.266925 7f5611ffb6c0 Delete type=3 #98
2023/10/28-18:46:58.267031 7f5611ffb6c0 Delete type=0 #100
2023/10/28-18:47:32.403078 7f56037fe6c0 Level-0 table #105: started
2023/10/28-18:47:32.403107 7f56037fe6c0 Level-0 table #105: 0 bytes OK
2023/10/28-18:47:32.410392 7f56037fe6c0 Delete type=0 #103
2023/10/28-18:47:32.416800 7f56037fe6c0 Manual compaction at level-0 from '!actors!1QGaindSWLCT4QXD' @ 72057594037927935 : 1 .. '!actors.items!yGlDTCiMiaH4vJTH.mvXGkWoCcRGjUpYV' @ 0 : 0; will stop at (end)
2023/10/28-18:47:32.416838 7f56037fe6c0 Manual compaction at level-1 from '!actors!1QGaindSWLCT4QXD' @ 72057594037927935 : 1 .. '!actors.items!yGlDTCiMiaH4vJTH.mvXGkWoCcRGjUpYV' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1 @@
MANIFEST-000106

View File

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