Compare commits

...

15 Commits

Author SHA1 Message Date
df1e0b9952 Amélioration robustess et vérification sur import des modules
All checks were successful
Validation JSON / validate (push) Successful in 16s
Release Creation / build (release) Successful in 48s
2026-04-14 08:26:08 +02:00
b1e96af421 UPdsate pour support v14
All checks were successful
Validation JSON / validate (push) Successful in 15s
Release Creation / build (release) Successful in 49s
2026-04-11 20:23:24 +02:00
17d865b60b Fix sur maladies again
All checks were successful
Validation JSON / validate (push) Successful in 17s
Release Creation / build (release) Successful in 1m12s
2026-03-24 11:33:50 +01:00
5f4e0c7ce5 Nombreuses corrections sur les maladies et symptomes
All checks were successful
Validation JSON / validate (push) Successful in 24s
2026-03-22 20:41:58 +01:00
8862698262 Nombreuses corections de scripts...
All checks were successful
Validation JSON / validate (push) Successful in 15s
Release Creation / build (release) Successful in 57s
2026-03-07 15:02:18 +01:00
047933a610 Cleanup old gitlab files
All checks were successful
Validation JSON / validate (push) Successful in 18s
Release Creation / build (release) Successful in 50s
2026-03-07 13:29:29 +01:00
b489f65618 Corrections sur la commande /trade et synchronisation des traductions manquantes 2026-03-07 13:29:09 +01:00
406a535c76 Corrections sur la commande /trade et synchronisation des traductions manquantes 2026-03-07 09:34:41 +01:00
5bc6d0d2f5 Corrections sur les scripts avec wounds/blessures 2026-03-05 20:44:48 +01:00
d557fac83f Corrections sur Sens Aiguisé 2026-03-04 11:46:29 +01:00
f07ef0b01d Fix release 2026-03-01 10:19:24 +01:00
301cc830bc Sync to latest WFRP release 2026-02-26 21:54:28 +01:00
786afeab74 Sync to latest WFRP release 2026-02-26 21:53:55 +01:00
e0383def30 Fix Horned Rat 2026-01-29 20:31:30 +01:00
7dc51444f0 Fix Horned Rat 2026-01-29 20:30:51 +01:00
556 changed files with 20643 additions and 37753 deletions

View File

@@ -0,0 +1,89 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
- uses: RouxAntoine/checkout@v3.5.4
# Valider les JSON avant de packager
- name: Valider fr.json et module.json
run: |
python3 -mjson.tool fr.json > /dev/null
python3 -mjson.tool module.json > /dev/null
# Générer modules/loadScripts.js depuis scripts/
- name: Build (génération de loadScripts.js)
run: node scriptPacker.js
# Extraire le numéro de version depuis le tag (sans le 'v' initial)
- name: Extraire la version depuis le tag
id: get_version
uses: battila7/get-version-action@v2
# Mettre à jour version, url, manifest et download dans module.json
- name: Substituer les URLs de Manifest et Download
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'module.json'
env:
version: ${{ steps.get_version.outputs.version-without-v }}
url: https://www.uberwald.me/gitea/${{ gitea.repository }}
manifest: https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/releases/download/latest/module.json
download: https://www.uberwald.me/gitea/${{ gitea.repository }}/releases/download/${{ github.event.release.tag_name }}/foundryvtt-wh4-lang-fr-fr.zip
# Créer le zip avec uniquement les fichiers nécessaires au module Foundry
- name: Installer zip
run: |
apt update -y
apt install -y zip
- name: Créer l'archive foundryvtt-wh4-lang-fr-fr.zip
run: >
zip -r ./foundryvtt-wh4-lang-fr-fr.zip
module.json
fr.json
wh4_fr.js
patch-styles.css
README.md
LICENSE
compendium/
modules/
packs/
icons/
images/
trade/
# Publier le zip et le module.json sur la release Gitea
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Publier les assets sur la release Gitea
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./foundryvtt-wh4-lang-fr-fr.zip
module.json
api_key: '${{ secrets.ALLOW_PUSH_RELEASE }}'
# Déclarer la release sur le portail FoundryVTT
- name: Publier sur FoundryVTT
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: 'wh4-fr-translation'
version: ${{ github.event.release.tag_name }}
manifest: 'https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/releases/download/latest/module.json'
notes: 'https://www.uberwald.me/gitea/${{ gitea.repository }}/releases/tag/${{ github.event.release.tag_name }}'
compatibility-minimum: '13'
compatibility-verified: '13'

View File

@@ -0,0 +1,19 @@
name: Validation JSON
on:
push:
branches:
- '**'
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: RouxAntoine/checkout@v3.5.4
- name: Valider fr.json
run: python3 -mjson.tool fr.json > /dev/null
- name: Valider module.json
run: python3 -mjson.tool module.json > /dev/null

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.history/ .history/
.idea/ .idea/
.github/

View File

@@ -1,29 +0,0 @@
image: python:3-alpine
before_script:
- apk update
- apk add zip
stages:
- test
- build
test:
stage: test
script:
- python -mjson.tool 'fr.json' > /dev/null
- python -mjson.tool 'module.json' > /dev/null
build:
stage: build
script:
- zip wh4-fr-FR.zip -r *.js *.json *.md compendium lang tables -x ".*"
artifacts:
name: wh4-fr-FR
when: on_success
paths:
- wh4-fr-FR.zip
when: on_success
only:
- tags
- master

File diff suppressed because one or more lines are too long

View File

@@ -1354,7 +1354,7 @@
"name": "Coude fracassé" "name": "Coude fracassé"
}, },
{ {
"description": "<p style=\"text-align: justify:\"Un coup violent sur le haut de la cuisse. Gagnez 1 État @Condition[Hémorragique] et réussissez un Test de <strong>Résistance Accessible (+20)</strong> sous peine de trébucher et de gagner lÉtat @Condition[A Terre]{À Terre}.</p>", "description": "<p style=\"text-align: justify:\"Un coup violent sur le haut de la cuisse. <p>Gagnez 1 État @Condition[Hémorragique] et réussissez un Test de <strong>Résistance Accessible (+20)</strong> sous peine de trébucher et de gagner lÉtat @Condition[A Terre]{À Terre}.</p>",
"id": "Thigh Strike", "id": "Thigh Strike",
"name": "Coup à la cuisse" "name": "Coup à la cuisse"
}, },
@@ -1631,10 +1631,10 @@
{ {
"contraction": "Sur un échec d'un Test de Résistance Facile (+40) après un combat vous ayant opposé à une créature avec le Trait Infecté. Vous pouvez également développer une infection à partir d'une Infection Mineure. L'incubation est instantanée si développée à partir d'autres symptômes", "contraction": "Sur un échec d'un Test de Résistance Facile (+40) après un combat vous ayant opposé à une créature avec le Trait Infecté. Vous pouvez également développer une infection à partir d'une Infection Mineure. L'incubation est instantanée si développée à partir d'autres symptômes",
"description": "<p style=\"text-align: justify:\">Les coupures infectées et les égratignures sont monnaie courante, ce qui explique quil existe de nombreuses superstitions quant à la meilleure façon de les traiter. Les cataplasmes confectionnés à partir de fumier enveloppé dans des feuilles, la peau de crapaud, ou encore les plumes de colombe sont très répandus, tout comme le fait de frotter la blessure avec de la bonne terre de Sigmar. La plupart des physiciens considèrent ces pratiques comme de pures âneries et vont préférer des remèdes plus scientifiques, comme le mélange de la bile dun boeuf noir avec trois cuillères de lurine du patient et une demi-cuillère de sel de mer, mixture ensuite appliquée sur la blessure purulente. Les hurlements qui suivent généralement cette application sont bien la preuve de son efficacité.</p>", "description": "<p style=\"text-align: justify:\">Les coupures infectées et les égratignures sont monnaie courante, ce qui explique quil existe de nombreuses superstitions quant à la meilleure façon de les traiter. Les cataplasmes confectionnés à partir de fumier enveloppé dans des feuilles, la peau de crapaud, ou encore les plumes de colombe sont très répandus, tout comme le fait de frotter la blessure avec de la bonne terre de Sigmar. La plupart des physiciens considèrent ces pratiques comme de pures âneries et vont préférer des remèdes plus scientifiques, comme le mélange de la bile dun boeuf noir avec trois cuillères de lurine du patient et une demi-cuillère de sel de mer, mixture ensuite appliquée sur la blessure purulente. Les hurlements qui suivent généralement cette application sont bien la preuve de son efficacité.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10", "durationValue": "1d10",
"id": "Festering Wound", "id": "Festering Wound",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "1d10, ou instantanée si développée à partir dautres symptômes", "incubationValue": "1d10, ou instantanée si développée à partir dautres symptômes",
"name": "Blessure Purulente", "name": "Blessure Purulente",
"permanent": "", "permanent": "",
@@ -1643,10 +1643,10 @@
{ {
"contraction": "Sur un échec d'un Test d'Endurance Facile (+40) après avoir ingurgité de la matière infectée.", "contraction": "Sur un échec d'un Test d'Endurance Facile (+40) après avoir ingurgité de la matière infectée.",
"description": "<p style=\"text-align: justify:\">On raconte que la nourriture des halflings ne peut pas donner de <em>Courante Galopante</em>, une maladie peu râgoutante et malheureusement trop répandue au sein de l'Empire à cause du manque de soins apportés à la préparation de la nourriture. Même si ceux souffrant de « Vengeance des Rumsters » après avoir ingurgité des tourtes bon marché des halflings vendues à Altdorf vous diront le contraire, s'ils peuvent quitter les toilettes assez longtemps...</p>", "description": "<p style=\"text-align: justify:\">On raconte que la nourriture des halflings ne peut pas donner de <em>Courante Galopante</em>, une maladie peu râgoutante et malheureusement trop répandue au sein de l'Empire à cause du manque de soins apportés à la préparation de la nourriture. Même si ceux souffrant de « Vengeance des Rumsters » après avoir ingurgité des tourtes bon marché des halflings vendues à Altdorf vous diront le contraire, s'ils peuvent quitter les toilettes assez longtemps...</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10", "durationValue": "1d10",
"id": "Galloping Trots", "id": "Galloping Trots",
"incubationUnit": "Heures", "incubationUnit": "hours",
"incubationValue": "1d10", "incubationValue": "1d10",
"name": "Courante Galopante", "name": "Courante Galopante",
"permanent": "", "permanent": "",
@@ -1655,10 +1655,10 @@
{ {
"contraction": "Sur un échec d'un Test de Résistance Accessible (+20) après un combat où vous avez été blessé par des rongeurs (dont les skavens) possédant le Trait Infecté, ou sur un échec d'un Test de Résistance Facile (+40) après qu'une source infectée est entrée en contact avec votre bouche.", "contraction": "Sur un échec d'un Test de Résistance Accessible (+20) après un combat où vous avez été blessé par des rongeurs (dont les skavens) possédant le Trait Infecté, ou sur un échec d'un Test de Résistance Facile (+40) après qu'une source infectée est entrée en contact avec votre bouche.",
"description": "<p style=\"text-align: justify:\">La très redoutée <em>Fièvre du rongeur</em> est transmise, comme son nom lindique, par des rongeurs infectés et cause des irritations douloureuses et des ulcérations avant que la fièvre ne monte et que le corps ne soit secoué de spasmes. Même si cette maladie est rarement fatale, elle est débilitante et il faut patienter longtemps avant de sen remettre totalement. Cest pourquoi chacun cherchera à en atténuer les symptômes. Parmi les remèdes les plus connus, il y a, à Altdorf, lauto-flagellation, réputée soulager toutes les infections cutanées. À Talabheim, on préconise de se recouvrir dune mixture composée dun mélange de fromage de chèvre relevé de poivre glacé importé de Kislev. Dans les villes et les cités les plus importantes, la <em>Fièvre du rongeur</em> est également connue sous le nom de <em>Fièvre de la tourte</em>, car il est notoire que, bien souvent, on remplace la viande la plus chère par du rat infecté dans ce genre de mets.</p>", "description": "<p style=\"text-align: justify:\">La très redoutée <em>Fièvre du rongeur</em> est transmise, comme son nom lindique, par des rongeurs infectés et cause des irritations douloureuses et des ulcérations avant que la fièvre ne monte et que le corps ne soit secoué de spasmes. Même si cette maladie est rarement fatale, elle est débilitante et il faut patienter longtemps avant de sen remettre totalement. Cest pourquoi chacun cherchera à en atténuer les symptômes. Parmi les remèdes les plus connus, il y a, à Altdorf, lauto-flagellation, réputée soulager toutes les infections cutanées. À Talabheim, on préconise de se recouvrir dune mixture composée dun mélange de fromage de chèvre relevé de poivre glacé importé de Kislev. Dans les villes et les cités les plus importantes, la <em>Fièvre du rongeur</em> est également connue sous le nom de <em>Fièvre de la tourte</em>, car il est notoire que, bien souvent, on remplace la viande la plus chère par du rat infecté dans ce genre de mets.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "3d10+10", "durationValue": "3d10+10",
"id": "Ratte Fever", "id": "Ratte Fever",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "3d10+5", "incubationValue": "3d10+5",
"name": "Fièvre du rongeur", "name": "Fièvre du rongeur",
"permanent": "", "permanent": "",
@@ -1667,10 +1667,10 @@
{ {
"contraction": "Sur un échec d'un Test d'Endurance Facile (+40) après avoir ingéré de la matière infectée.", "contraction": "Sur un échec d'un Test d'Endurance Facile (+40) après avoir ingéré de la matière infectée.",
"description": "<p style=\"text-align: justify:\">Le <em>Flux sanglant</em> est un problème récurrent au sein de l'Empire et d'une manière générale, est considéré comme une malédiction infligée par les dieux aux impies. Cette maladie infâme oblige la pauvre victime à se vider sans cesse. Le <em>Flux sanglant</em> est endémique au sein des Armées de l'État, où il tue bien plus de soldats que les forces ennemies. Parmi les remèdes les plus fréquents, il y a l'ingurgitation de boudin pour remplacer les humeurs perdues, le « bouchonnage », et le massage des parties avec des substances grasses afin d'atténuer la douleur aigüe.</p>", "description": "<p style=\"text-align: justify:\">Le <em>Flux sanglant</em> est un problème récurrent au sein de l'Empire et d'une manière générale, est considéré comme une malédiction infligée par les dieux aux impies. Cette maladie infâme oblige la pauvre victime à se vider sans cesse. Le <em>Flux sanglant</em> est endémique au sein des Armées de l'État, où il tue bien plus de soldats que les forces ennemies. Parmi les remèdes les plus fréquents, il y a l'ingurgitation de boudin pour remplacer les humeurs perdues, le « bouchonnage », et le massage des parties avec des substances grasses afin d'atténuer la douleur aigüe.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10", "durationValue": "1d10",
"id": "The Bloody Flux", "id": "The Bloody Flux",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "2d10", "incubationValue": "2d10",
"name": "Flux Sanglant", "name": "Flux Sanglant",
"permanent": "", "permanent": "",
@@ -1679,10 +1679,10 @@
{ {
"contraction": "C'est le développement d'une autre maladie, ou cela intervient après une Blessure critique.", "contraction": "C'est le développement d'une autre maladie, ou cela intervient après une Blessure critique.",
"description": "<p style=\"text-align: justify:\">Votre sang est infecté et votre coeur répand la maladie dans votre corps. Il est possible de vous soigner par l'intermédiaire de saignées, mais certains doktors préfèrent effectuer des incisions à un endroit très précis au niveau du cou pour expulser le sang contaminé et demandent au patient d'ingérer d'énormes quantité de sang sain pour remplacer celui qui a été perdu. Que le patient accepte ou non ce remède, si aucun traitement n'est appliqué l'<em>Infection du sang</em> est mortelle et se concluera par une visite à la Guilde de Thanatopracteurs et du Culte de Morr.</p>", "description": "<p style=\"text-align: justify:\">Votre sang est infecté et votre coeur répand la maladie dans votre corps. Il est possible de vous soigner par l'intermédiaire de saignées, mais certains doktors préfèrent effectuer des incisions à un endroit très précis au niveau du cou pour expulser le sang contaminé et demandent au patient d'ingérer d'énormes quantité de sang sain pour remplacer celui qui a été perdu. Que le patient accepte ou non ce remède, si aucun traitement n'est appliqué l'<em>Infection du sang</em> est mortelle et se concluera par une visite à la Guilde de Thanatopracteurs et du Culte de Morr.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10", "durationValue": "1d10",
"id": "Blood Rot", "id": "Blood Rot",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "0", "incubationValue": "0",
"name": "Infection du Sang", "name": "Infection du Sang",
"permanent": "", "permanent": "",
@@ -1691,10 +1691,10 @@
{ {
"contraction": "Sur un échec d'un Test de Résistance Très Facile (+60) après un combat où vous subi une Blessure critique.", "contraction": "Sur un échec d'un Test de Résistance Très Facile (+60) après un combat où vous subi une Blessure critique.",
"description": "<p style=\"text-align: justify:\">Les <em>Infections mineures</em> - des blessures guérissant lentement, qui n'enflent quasiment pas et ne provoquent pas de fièvre - sont trés répandues. La plupart guérissent d'elles-mêmes, donc peu de personnes s'en soucient réellement avant qu'il ne soit trop tard et que les Portes de Morr ne s'ouvrent.</p>", "description": "<p style=\"text-align: justify:\">Les <em>Infections mineures</em> - des blessures guérissant lentement, qui n'enflent quasiment pas et ne provoquent pas de fièvre - sont trés répandues. La plupart guérissent d'elles-mêmes, donc peu de personnes s'en soucient réellement avant qu'il ne soit trop tard et que les Portes de Morr ne s'ouvrent.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10", "durationValue": "1d10",
"id": "Minor Infection", "id": "Minor Infection",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "1d10", "incubationValue": "1d10",
"name": "Infection Mineure", "name": "Infection Mineure",
"permanent": "", "permanent": "",
@@ -1703,10 +1703,10 @@
{ {
"contraction": "Effectuer un Test de Résistance Accessible (+20) pour chaque heure entamée passée dans la zone infectée, ou lorsque vous vous retrouvez en présence de fluides infectés.", "contraction": "Effectuer un Test de Résistance Accessible (+20) pour chaque heure entamée passée dans la zone infectée, ou lorsque vous vous retrouvez en présence de fluides infectés.",
"description": "<p style=\"text-align: justify:\">Les historiens affirment qu'il y a des siècles de cela, les rats ont déferlé sur l'Empire et que la <em>Peste Noire</em> s'ensuivit, décimant neuf âmes sur dix. Des recrudescences inexpliquées de cette horrible maladie apparaissent encore de nos jours et sont systématiquement accompagnées de la présence des pragmatiques nonnes blanches. Le Culte de Shallya a juré de faire tout ce qui était en son pouvoir pour éradiquer cette terrible maladie, et, en application de droits ancestraux qui lui sont conférés, des cordons sanitaires, délimités par des cordes blanches, sont déployées partout ou l'épidémie surgit, afin d'être certain que personne ne puisse entrer ou sortir de la zone de quarantaine. Et ce, jusqu'à ce que la recrudescence soit maîtrisée et que les corps aient été correctement traités.</p>", "description": "<p style=\"text-align: justify:\">Les historiens affirment qu'il y a des siècles de cela, les rats ont déferlé sur l'Empire et que la <em>Peste Noire</em> s'ensuivit, décimant neuf âmes sur dix. Des recrudescences inexpliquées de cette horrible maladie apparaissent encore de nos jours et sont systématiquement accompagnées de la présence des pragmatiques nonnes blanches. Le Culte de Shallya a juré de faire tout ce qui était en son pouvoir pour éradiquer cette terrible maladie, et, en application de droits ancestraux qui lui sont conférés, des cordons sanitaires, délimités par des cordes blanches, sont déployées partout ou l'épidémie surgit, afin d'être certain que personne ne puisse entrer ou sortir de la zone de quarantaine. Et ce, jusqu'à ce que la recrudescence soit maîtrisée et que les corps aient été correctement traités.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "3d10", "durationValue": "3d10",
"id": "The Black Plague", "id": "The Black Plague",
"incubationUnit": "Minutes", "incubationUnit": "minutes",
"incubationValue": "1d10", "incubationValue": "1d10",
"name": "Peste Noire", "name": "Peste Noire",
"permanent": "", "permanent": "",
@@ -1715,10 +1715,10 @@
{ {
"contraction": "Sur un échec d'un Test de Résistance Facile (+40) après être entré en contact avec un animal, de la peau, ou des cadavres infectés.", "contraction": "Sur un échec d'un Test de Résistance Facile (+40) après être entré en contact avec un animal, de la peau, ou des cadavres infectés.",
"description": "<p style=\"text-align: justify:\">C'est une maladie très répandue parmi les chasseurs, les fourreurs et les marchands, transmises par les moutons et le bétail, respectivement par la laine et la peau, et par les cadavres de ceux qui sont morts de cette maladie. Elle se manifeste au départ par de légères démangeaisons, trés vite remplacées par des boursufflures roses qui vont se progager sur tout le corps, et particulièrement sur le torse et les bras. Ce n'est pas la forme de vérole la plus grave de l'Empire, mais elle persiste assez longtemps et peut quelquefois s'avérer mortelle.</p>", "description": "<p style=\"text-align: justify:\">C'est une maladie très répandue parmi les chasseurs, les fourreurs et les marchands, transmises par les moutons et le bétail, respectivement par la laine et la peau, et par les cadavres de ceux qui sont morts de cette maladie. Elle se manifeste au départ par de légères démangeaisons, trés vite remplacées par des boursufflures roses qui vont se progager sur tout le corps, et particulièrement sur le torse et les bras. Ce n'est pas la forme de vérole la plus grave de l'Empire, mais elle persiste assez longtemps et peut quelquefois s'avérer mortelle.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "5d10", "durationValue": "5d10",
"id": "Packer's Pox", "id": "Packer's Pox",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "1d10", "incubationValue": "1d10",
"name": "Vérole du Tanneur", "name": "Vérole du Tanneur",
"permanent": "", "permanent": "",
@@ -1727,10 +1727,10 @@
{ {
"contraction": "Sur un échec d'un Test de Résistance Accessible (+20) lorsque vous touchez une personne infectée ou que vous échouez à ce même Test après qu'un patient contagieux a toussé ou éternué juste à côté de vous (effectue un Test par heure)", "contraction": "Sur un échec d'un Test de Résistance Accessible (+20) lorsque vous touchez une personne infectée ou que vous échouez à ce même Test après qu'un patient contagieux a toussé ou éternué juste à côté de vous (effectue un Test par heure)",
"description": "<p style=\"text-align: justify:\">Tous les ans, la plupart des villes et des cités de l'Empire subissent une épidémie de <em>Vérole urticante</em>. La maladie, qui fait apparaître des boursuflures qui démangent sur la quasi-totalité du corps, reste relativement bénigne et ne provoque que très rarement des complications, et n'est donc un réel souci que pour certains patients qui vont sinquiéter et les nobliaux oisifs. La maladie est tellement répandue qu'on trouve dans la plupart des temples de Shallya des ampoules remplies de pâte blanche qui permettent de soulager les démangeaisons des malades.</p>", "description": "<p style=\"text-align: justify:\">Tous les ans, la plupart des villes et des cités de l'Empire subissent une épidémie de <em>Vérole urticante</em>. La maladie, qui fait apparaître des boursuflures qui démangent sur la quasi-totalité du corps, reste relativement bénigne et ne provoque que très rarement des complications, et n'est donc un réel souci que pour certains patients qui vont sinquiéter et les nobliaux oisifs. La maladie est tellement répandue qu'on trouve dans la plupart des temples de Shallya des ampoules remplies de pâte blanche qui permettent de soulager les démangeaisons des malades.</p>",
"durationUnit": "Jours", "durationUnit": "days",
"durationValue": "1d10+7", "durationValue": "1d10+7",
"id": "Itching Pox", "id": "Itching Pox",
"incubationUnit": "Jours", "incubationUnit": "days",
"incubationValue": "1d10", "incubationValue": "1d10",
"name": "Vérole Urticante", "name": "Vérole Urticante",
"permanent": "Vous ne pouvez pas l'attraper une seconde fois, si vous l'avez déjà contractée dans le passé.", "permanent": "Vous ne pouvez pas l'attraper une seconde fois, si vous l'avez déjà contractée dans le passé.",

View File

@@ -69,16 +69,10 @@
"converter": "generic_localization" "converter": "generic_localization"
}, },
"durationValue": "system.duration.value", "durationValue": "system.duration.value",
"durationUnit": { "durationUnit": "system.duration.unit",
"path": "system.duration.unit",
"converter": "disease_duration_unit"
},
"contraction": "system.contraction.value", "contraction": "system.contraction.value",
"incubationValue": "system.incubation.value", "incubationValue": "system.incubation.value",
"incubationUnit": { "incubationUnit": "system.incubation.unit",
"path": "system.incubation.unit",
"converter": "disease_duration_unit"
},
"symptoms": "system.symptoms.value", "symptoms": "system.symptoms.value",
"permanent": "system.permanent.value", "permanent": "system.permanent.value",
"special": "system.special.value", "special": "system.special.value",

View File

@@ -78,16 +78,10 @@
"converter": "generic_localization" "converter": "generic_localization"
}, },
"durationValue": "system.duration.value", "durationValue": "system.duration.value",
"durationUnit": { "durationUnit": "system.duration.unit",
"path": "system.duration.unit",
"converter": "disease_duration_unit"
},
"contraction": "system.contraction.value", "contraction": "system.contraction.value",
"incubationValue": "system.incubation.value", "incubationValue": "system.incubation.value",
"incubationUnit": { "incubationUnit": "system.incubation.unit",
"path": "system.incubation.unit",
"converter": "disease_duration_unit"
},
"symptoms": "system.symptoms.value", "symptoms": "system.symptoms.value",
"permanent": "system.permanent.value", "permanent": "system.permanent.value",
"special": "system.special.value", "special": "system.special.value",

View File

@@ -70,16 +70,10 @@
"converter": "generic_localization" "converter": "generic_localization"
}, },
"durationValue": "system.duration.value", "durationValue": "system.duration.value",
"durationUnit": { "durationUnit": "system.duration.unit",
"path": "system.duration.unit",
"converter": "disease_duration_unit"
},
"contraction": "system.contraction.value", "contraction": "system.contraction.value",
"incubationValue": "system.incubation.value", "incubationValue": "system.incubation.value",
"incubationUnit": { "incubationUnit": "system.incubation.unit",
"path": "system.incubation.unit",
"converter": "disease_duration_unit"
},
"symptoms": "system.symptoms.value", "symptoms": "system.symptoms.value",
"permanent": "system.permanent.value", "permanent": "system.permanent.value",
"special": "system.special.value", "special": "system.special.value",

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Patterns pour détecter les variables JavaScript nommées "Blessures"
const patterns = [
// Déclarations de variables
/\b(let|const|var)\s+Blessures\b/g,
// Paramètres de fonction
/function\s+\w*\s*\([^)]*\bBlessures\b[^)]*\)/g,
// Arrow functions avec paramètres
/\(\s*[^)]*\bBlessures\b[^)]*\)\s*=>/g,
// Arrow function avec un seul paramètre sans parenthèses
/\bBlessures\s*=>/g,
// Destructuration d'objets
/\{\s*[^}]*\bBlessures\b[^}]*\}\s*=/g,
// Destructuration de tableaux
/\[\s*[^\]]*\bBlessures\b[^\]]*\]\s*=/g,
// Catch clause
/catch\s*\(\s*Blessures\s*\)/g,
// For...of/in loops
/for\s*\(\s*(let|const|var)?\s*Blessures\s+(of|in)\b/g,
];
function findBlessuresInFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const results = [];
lines.forEach((line, index) => {
patterns.forEach(pattern => {
pattern.lastIndex = 0; // Reset regex
if (pattern.test(line)) {
results.push({
line: index + 1,
content: line.trim()
});
}
});
});
return results;
}
function walkDirectory(dir) {
const files = fs.readdirSync(dir);
const allResults = {};
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
Object.assign(allResults, walkDirectory(filePath));
} else if (file.endsWith('.js')) {
const results = findBlessuresInFile(filePath);
if (results.length > 0) {
allResults[filePath] = results;
}
}
});
return allResults;
}
// Main
const scriptsDir = path.join(__dirname, 'scripts');
console.log('🔍 Recherche des variables JavaScript nommées "Blessures" dans le répertoire scripts/...\n');
if (!fs.existsSync(scriptsDir)) {
console.error('❌ Le répertoire scripts/ n\'existe pas');
process.exit(1);
}
const results = walkDirectory(scriptsDir);
if (Object.keys(results).length === 0) {
console.log('✅ Aucune variable JavaScript nommée "Blessures" trouvée.');
} else {
console.log(`⚠️ ${Object.keys(results).length} fichier(s) contenant des variables "Blessures" :\n`);
Object.entries(results).forEach(([filePath, occurrences]) => {
const relativePath = path.relative(__dirname, filePath);
console.log(`📄 ${relativePath}`);
occurrences.forEach(({ line, content }) => {
console.log(` Ligne ${line}: ${content}`);
});
console.log('');
});
}

92
find-tests-variables.cjs Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Patterns pour détecter les variables JavaScript nommées "Tests"
const patterns = [
// Déclarations de variables
/\b(let|const|var)\s+Tests\b/g,
// Paramètres de fonction
/function\s+\w*\s*\([^)]*\bTests\b[^)]*\)/g,
// Arrow functions avec paramètres
/\(\s*[^)]*\bTests\b[^)]*\)\s*=>/g,
// Arrow function avec un seul paramètre sans parenthèses
/\bTests\s*=>/g,
// Destructuration d'objets
/\{\s*[^}]*\bTests\b[^}]*\}\s*=/g,
// Destructuration de tableaux
/\[\s*[^\]]*\bTests\b[^\]]*\]\s*=/g,
// Catch clause
/catch\s*\(\s*Tests\s*\)/g,
// For...of/in loops
/for\s*\(\s*(let|const|var)?\s*Tests\s+(of|in)\b/g,
];
function findTestsInFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const results = [];
lines.forEach((line, index) => {
patterns.forEach(pattern => {
pattern.lastIndex = 0; // Reset regex
if (pattern.test(line)) {
results.push({
line: index + 1,
content: line.trim()
});
}
});
});
return results;
}
function walkDirectory(dir) {
const files = fs.readdirSync(dir);
const allResults = {};
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
Object.assign(allResults, walkDirectory(filePath));
} else if (file.endsWith('.js')) {
const results = findTestsInFile(filePath);
if (results.length > 0) {
allResults[filePath] = results;
}
}
});
return allResults;
}
// Main
const scriptsDir = path.join(__dirname, 'scripts');
console.log('🔍 Recherche des variables JavaScript nommées "Tests" dans le répertoire scripts/...\n');
if (!fs.existsSync(scriptsDir)) {
console.error('❌ Le répertoire scripts/ n\'existe pas');
process.exit(1);
}
const results = walkDirectory(scriptsDir);
if (Object.keys(results).length === 0) {
console.log('✅ Aucune variable JavaScript nommée "Tests" trouvée.');
} else {
console.log(`⚠️ ${Object.keys(results).length} fichier(s) contenant des variables "Tests" :\n`);
Object.entries(results).forEach(([filePath, occurrences]) => {
const relativePath = path.relative(__dirname, filePath);
console.log(`📄 ${relativePath}`);
occurrences.forEach(({ line, content }) => {
console.log(` Ligne ${line}: ${content}`);
});
console.log('');
});
}

View File

@@ -5,9 +5,6 @@
}, },
{ {
"path": "../WFRP4e-FoundryVTT" "path": "../WFRP4e-FoundryVTT"
},
{
"path": "../WarhammerLibrary-FVTT"
} }
], ],
"settings": {} "settings": {}

4021
fr.json

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
} }
], ],
"url": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr", "url": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr",
"version": "9.3.6", "version": "9.4.3",
"esmodules": [ "esmodules": [
"wh4_fr.js", "wh4_fr.js",
"modules/babele-register.js", "modules/babele-register.js",
@@ -119,12 +119,12 @@
"folders": [] "folders": []
} }
], ],
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/raw/v10/module.json", "manifest": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/releases/download/latest/module.json",
"download": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/archive/foundryvtt-wh4-lang-fr-9-3-6.zip", "download": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/releases/download/latest/foundryvtt-wh4-lang-fr-fr.zip",
"id": "wh4-fr-translation", "id": "wh4-fr-translation",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "14"
}, },
"relationships": { "relationships": {
"systems": [ "systems": [

View File

@@ -16,30 +16,27 @@ const _patch_eis = () => {
game.wfrp4e.config.symptomTreatment["swelling"] = "La plupart des traitements consistent à plonger la partie affectée, ou parfois tout le corps, dans un bain d'eau glacée pour réduire la chaleur qui accompagne les gonflements. Un <b> Test de Guérison Difficile (-20) étendu </b> nécessitant +3 DR réduit le renflement de <b> <a class ='chat-roll'> 2d10 </a> </b> heures. Chaque test dure une heure. Le patient se retrouve avec l'Etat Exténué +1 pour chaque test effectué au cours du processus. <br> <br> A la place, certains médecins saignent le patient avec une lame ou des sangsues. Un <b>Test de Guérison étendu </b> réussi nécessitant +4 SL et des Outils (médecin) réduit le renflement de (<a class ='chat-roll'> 1d10 </a> + Bonus d'Endurance du patient) heures. Chaque test a une difficulté de base <b> impossible (-50) </b> et dure une demi-heure."; game.wfrp4e.config.symptomTreatment["swelling"] = "La plupart des traitements consistent à plonger la partie affectée, ou parfois tout le corps, dans un bain d'eau glacée pour réduire la chaleur qui accompagne les gonflements. Un <b> Test de Guérison Difficile (-20) étendu </b> nécessitant +3 DR réduit le renflement de <b> <a class ='chat-roll'> 2d10 </a> </b> heures. Chaque test dure une heure. Le patient se retrouve avec l'Etat Exténué +1 pour chaque test effectué au cours du processus. <br> <br> A la place, certains médecins saignent le patient avec une lame ou des sangsues. Un <b>Test de Guérison étendu </b> réussi nécessitant +4 SL et des Outils (médecin) réduit le renflement de (<a class ='chat-roll'> 1d10 </a> + Bonus d'Endurance du patient) heures. Chaque test a une difficulté de base <b> impossible (-50) </b> et dure une demi-heure.";
game.wfrp4e.config.loreEffects["tzeentch"] = { game.wfrp4e.config.loreEffects["tzeentch"] = {
label: "Domaine de Tzeentch", name: "Domaine de Tzeentch",
icon: "modules/wfrp4e-core/icons/spells/tzeentch.png", img: "modules/wfrp4e-core/icons/spells/tzeentch.png",
transfer: true, system: {
flags: { transferData: {
wfrp4e: { type: "target"
"effectApplication": "apply", },
"effectTrigger": "oneTime", scriptData: [{
"lore": true, trigger: "immediate",
"script": ` label: "Test d'Endurance",
if (this.actor.isOwner) script: `
args.actor.setupSkill("Résistance", {context : {failure: "1 Point de Corruption reçu", success : "1 Point de Chance gagné"}}).then(setupData => { this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {appendTitle : " - " + this.effect.name, context : {failure: "1 Point de Corruption reçu", success : "1 Point de Chance gagné"}}).then(setupData => {
args.actor.basicTest(setupData).then(test => this.actor.basicTest(setupData).then(test => {
{ if (test.succeeded && this.actor.type == "character") {
if (test.result.result == "success" && args.actor.type == "character") this.actor.update({"system.status.fortune.value" : this.actor.system.status.fortune.value + 1})
{ } else if (test.failed && this.actor.type == "character") {
args.actor.update({"system.status.fortune.value" : args.actor.system.status.fortune.value + 1}) this.actor.update({"system.status.corruption.value" : this.actor.system.status.corruption.value + 1})
}
else if (test.result.result == "failure" && args.actor.type == "character")
{
args.actor.update({"system.status.corruption.value" : args.actor.system.status.corruption.value + 1})
} }
}) })
})` })
} return false;`
}]
} }
} }
} }
@@ -133,10 +130,10 @@ const __auto_patch_translation_journal_compendium = async (compmod) => {
if (game.user.isGM) { if (game.user.isGM) {
let compData = game.packs.get("WH4-fr-translation.tables-des-traductions"); let compData = game.packs.get("WH4-fr-translation.tables-des-traductions");
compData.locked = false; compData.locked = false;
let translEntries = await compData.getContent(); let translEntries = await compData.getDocuments();
for (let entryData of translEntries) { for (let entryData of translEntries) {
let mydata = foundry.utils.duplicate(entryData.data); let mydata = entryData.toObject();
mydata.content = mydata.content.replace(/wfrp4e-content/g, compmod); mydata.text.content = mydata.text.content.replace(/wfrp4e-content/g, compmod);
entryData.update(mydata); entryData.update(mydata);
} }
compData.locked = true; compData.locked = true;
@@ -175,9 +172,55 @@ const patch_core_tables = (tableList) => {
/************************************************************************************/ /************************************************************************************/
const patch_trade_gazeteer = () => { const patch_trade_gazeteer = () => {
if (game.wfrp4e.config.trade?.gazetteer) { // Translate river cargoTypes to French (DotR module registers English values)
if (game.wfrp4e.trade?.tradeData?.river?.cargoTypes) {
Object.assign(game.wfrp4e.trade.tradeData.river.cargoTypes, {
"grain": game.i18n.localize("TRADE.Grain"),
"armaments": game.i18n.localize("TRADE.Armaments"),
"luxuries": game.i18n.localize("TRADE.Luxuries"),
"metal": game.i18n.localize("TRADE.Metal"),
"timber": game.i18n.localize("TRADE.Timber"),
"wine": game.i18n.localize("TRADE.Wine"),
"brandy": game.i18n.localize("TRADE.Brandy"),
"wool": game.i18n.localize("TRADE.Wool"),
});
}
// Translate maritime cargoTypes to French (SOC module)
if (game.wfrp4e.trade?.tradeData?.maritime?.cargoTypes) {
const maritimeKeys = {
"citrusfruit": "TRADE.Citrusfruit",
"olives": "TRADE.Olives",
"saltfish": "TRADE.Saltfish",
"stone": "TRADE.Stone",
};
for (let [key, locKey] of Object.entries(maritimeKeys)) {
if (game.wfrp4e.trade.tradeData.maritime.cargoTypes[key]) {
game.wfrp4e.trade.tradeData.maritime.cargoTypes[key] = game.i18n.localize(locKey);
}
}
// Translate shared keys that may also appear in maritime
Object.assign(game.wfrp4e.trade.tradeData.maritime.cargoTypes,
Object.fromEntries(
Object.entries(game.wfrp4e.trade.tradeData.maritime.cargoTypes)
.filter(([k]) => game.wfrp4e.trade.tradeData.river?.cargoTypes?.[k])
.map(([k]) => [k, game.wfrp4e.trade.tradeData.river.cargoTypes[k]])
)
);
}
// Translate season names shown in the trade dialog
if (game.wfrp4e.trade?.seasons) {
Object.assign(game.wfrp4e.trade.seasons, {
"spring": game.i18n.localize("TRADE.Spring"),
"summer": game.i18n.localize("TRADE.Summer"),
"autumn": game.i18n.localize("TRADE.Autumn"),
"winter": game.i18n.localize("TRADE.Winter"),
});
}
// Replace the English DotR gazetteer with the French-translated one
// New API: game.wfrp4e.trade.gazetteers.river (replaces old game.wfrp4e.config.trade.gazetteer)
if (game.wfrp4e.trade?.gazetteers?.river?.length) {
fetch("modules/wh4-fr-translation/trade/gazetteer_dotr.json").then(r => r.json()).then(records => { fetch("modules/wh4-fr-translation/trade/gazetteer_dotr.json").then(r => r.json()).then(records => {
game.wfrp4e.config.trade.gazetteer = records; game.wfrp4e.trade.gazetteers.river = records;
}); });
} }
} }
@@ -358,6 +401,56 @@ Hooks.on('ready', () => {
// Patch function for effects // Patch function for effects
game.wfrp4e.utility.findKey = warhammer.utility.findKey game.wfrp4e.utility.findKey = warhammer.utility.findKey
// Patch SpellModel.computeSpellDamage to handle English characteristic bonus names.
// Babele instantiates actors TWICE: first with untranslated data (English formulas like
// "Willpower Bonus+4"), then with translated data. The first pass fails because
// characteristicsBonus values are already French ("Bonus de Force Mentale").
// This patch pre-replaces English bonus names with numeric values before the original
// formula evaluation runs, preventing SyntaxErrors and notification spam.
const __EN_BONUS_TO_CHAR = {
"weapon skill bonus": "ws", "ballistic skill bonus": "bs",
"strength bonus": "s", "toughness bonus": "t",
"initiative bonus": "i", "agility bonus": "ag",
"dexterity bonus": "dex", "intelligence bonus": "int",
"willpower bonus": "wp", "fellowship bonus": "fel"
};
const SpellModel = CONFIG.Item.dataModels?.["spell"];
if (SpellModel?.prototype?.computeSpellDamage) {
const _origComputeSpellDamage = SpellModel.prototype.computeSpellDamage;
SpellModel.prototype.computeSpellDamage = function(formula, options) {
if (typeof formula === "string") {
const actor = options?.actor || this.parent?.actor;
if (actor?.system?.characteristics) {
let f = formula.toLowerCase();
for (const [enName, ch] of Object.entries(__EN_BONUS_TO_CHAR)) {
if (f.includes(enName)) {
const bonus = actor.system.characteristics[ch]?.bonus ?? 0;
f = f.replace(enName, bonus);
}
}
formula = f;
}
}
return _origComputeSpellDamage.call(this, formula, options);
};
}
// Patch postSymptom to handle English symptom names in @Symptom[...] links.
// After i18nInit, config.symptoms values are French strings (e.g. "Fièvre"), so
// findKey("Fever", config.symptoms) fails. We normalize via game.i18n.localize first.
const _origPostSymptom = game.wfrp4e.utility.postSymptom.bind(game.wfrp4e.utility);
game.wfrp4e.utility.postSymptom = async function(symptom) {
const baseName = symptom.split("(")[0].trim();
const symkey = warhammer.utility.findKey(baseName, game.wfrp4e.config.symptoms);
if (!symkey) {
const localizedBase = game.i18n.localize(baseName);
if (localizedBase !== baseName) {
symptom = symptom.replace(baseName, localizedBase);
}
}
return _origPostSymptom(symptom);
};
/** New modifiers */ /** New modifiers */
game.wfrp4e.config.difficultyModifiers = { game.wfrp4e.config.difficultyModifiers = {
"veasy": 60, "veasy": 60,

View File

@@ -30,6 +30,20 @@ const __SELECT_BONUS_PREFIX_D = {
"agilité": 1 "agilité": 1
} }
// Map English characteristic names (as they appear in spell formulas) to WFRP4e abbreviations
const __CHAR_EN_TO_ABBR = {
"weapon skill": "ws",
"ballistic skill": "bs",
"strength": "s",
"toughness": "t",
"initiative": "i",
"agility": "ag",
"dexterity": "dex",
"intelligence": "int",
"willpower": "wp",
"fellowship": "fel"
}
/************************************************************************************/ /************************************************************************************/
export class WFRP4FrTranslation { export class WFRP4FrTranslation {
@@ -57,16 +71,23 @@ export class WFRP4FrTranslation {
if (value == "You") return "Vous"; // Hop ! if (value == "You") return "Vous"; // Hop !
if (value == "Instant") return "Instantané"; // Hop ! if (value == "Instant") return "Instantané"; // Hop !
let translw = value; let translw = value;
let re = /(.*)\s+[Bb]onus\s*(\w*)/i; let re = /(.*)\s+[Bb]onus\s*(.*)/i; // (.*) at end captures modifiers like "+4"
let res = re.exec(value); let res = re.exec(value);
let unit = ""; let unit = "";
if (res) { // Test "<charac> Bonus <unit>" pattern if (res) { // Test "<charac> Bonus <modifier>" pattern
if (res[1]) { // We have char name, then convert it if (res[1]) { // We have char name, then convert it
const charEN = res[1].trim().toLowerCase();
const abbr = __CHAR_EN_TO_ABBR[charEN];
if (abbr && game.wfrp4e?.config?.characteristicsBonus?.[abbr]) {
// Use the localized French bonus name from config (already resolved by localizeConfig at i18nInit)
translw = game.wfrp4e.config.characteristicsBonus[abbr];
} else {
translw = game.i18n.localize(res[1].trim()); translw = game.i18n.localize(res[1].trim());
let bonusPrefix = (translw.toLowerCase() in __SELECT_BONUS_PREFIX_D) ? "Bonus d'" : "Bonus de "; let bonusPrefix = (translw.toLowerCase() in __SELECT_BONUS_PREFIX_D) ? "Bonus d'" : "Bonus de ";
translw = bonusPrefix + translw translw = bonusPrefix + translw;
} }
unit = res[2]; }
unit = res[2]; // may be "+4", "-2", "2", "yards", etc.
} else { } else {
re = /(\d+) (\w+)/i; re = /(\d+) (\w+)/i;
res = re.exec(value); res = re.exec(value);
@@ -90,6 +111,8 @@ export class WFRP4FrTranslation {
if (unit == "Bonus") { // Another weird management if (unit == "Bonus") { // Another weird management
console.log("Translating bonus", unit); console.log("Translating bonus", unit);
translw = "Bonus de " + translw; translw = "Bonus de " + translw;
} else if (unit && /^[+\-*\/]/.test(unit)) {
translw += unit; // No space before operators like "+4"
} else { } else {
translw += " " + unit; translw += " " + unit;
} }
@@ -278,8 +301,25 @@ Hooks.once('init', () => {
"process_effects": (effectsData, translations, data, tc, tc_translations) => { "process_effects": (effectsData, translations, data, tc, tc_translations) => {
//console.log("Effects :", effectsData, translations, data, tc, tc_translations) //console.log("Effects :", effectsData, translations, data, tc, tc_translations)
for (let e of effectsData) { for (let e of effectsData) {
// Foundry v13 requires name; migrate legacy data where only label was stored
if (e.name == null) e.name = e.label || "";
let origName = e.name let origName = e.name
e.name = tc_translations.name || game.i18n.localize(e.name) // Symptom effects have their own name (Fever, Malaise, etc.) — don't overwrite with the parent item name
if (e.flags?.wfrp4e?.symptom) {
let symName = e.name;
let gravity = "";
if (symName.includes("(") && symName.includes(")")) {
let re = /(.*) +\((.*)\)/i;
let res = re.exec(symName);
if (res) {
symName = res[1].trim();
gravity = " (" + game.i18n.localize(res[2].trim()) + ")";
}
}
e.name = game.i18n.localize(symName) + gravity;
} else {
e.name = tc_translations.name || game.i18n.localize(e.name) || e.label || ""
}
if ( e.flags?.wfrp4e?.scriptData) { if ( e.flags?.wfrp4e?.scriptData) {
for (let script of e.flags.wfrp4e.scriptData) { for (let script of e.flags.wfrp4e.scriptData) {
if (script?.label) { if (script?.label) {
@@ -396,10 +436,18 @@ Hooks.once('init', () => {
return beast_traits return beast_traits
} }
//console.log("TRANS:", beast_traits) //console.log("TRANS:", beast_traits)
// Normalize: Foundry v13 requires ActiveEffect.name; migrate legacy v12 data
for (let trait_en of beast_traits) {
if (trait_en.effects) {
for (let eff of trait_en.effects) {
if (eff.name == null) eff.name = eff.label || "";
}
}
}
for (let trait_en of beast_traits) { for (let trait_en of beast_traits) {
let special = ""; let special = "";
let nbt = ""; let nbt = "";
let name_en = trait_en.name.trim(); // strip \r in some traits name let name_en = trait_en.name.replace(/\r/g, '').trim(); // strip \r (including internal) in some traits name
if (!trait_en.name || trait_en.name.length == 0) { if (!trait_en.name || trait_en.name.length == 0) {
console.log("Wrong item name found!!!!") console.log("Wrong item name found!!!!")
continue continue
@@ -415,19 +463,22 @@ Hooks.once('init', () => {
} else if (name_en.includes("(") && name_en.includes(")")) { // Then process specific traits name with (xxxx) inside } else if (name_en.includes("(") && name_en.includes(")")) { // Then process specific traits name with (xxxx) inside
let re = /(.*) \((.*)\)/i; let re = /(.*) \((.*)\)/i;
let res = re.exec(name_en); let res = re.exec(name_en);
if (!res) { console.warn("WFRP4E-FR | bestiary_traits: regex failed for trait:", name_en); }
else {
name_en = res[1]; // Get the root traits name name_en = res[1]; // Get the root traits name
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
} }
}
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("trait") let validCompendiums = game.wfrp4e.tags.getPacksWithTag("trait")
for (let compData of validCompendiums) { for (let compData of validCompendiums) {
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system:{description:{value: trait_en.system.description.value}} }, true) let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system:{description:{value: trait_en.system.description.value}} }, true)
if (trait_fr?.name && trait_fr?.name != name_en) { if (trait_fr?.name && trait_fr?.name != name_en) {
trait_fr.name = trait_fr.name || trait_en.name trait_fr.name = trait_fr.name || trait_en.name
trait_en.name = nbt + trait_fr.name + special; trait_en.name = nbt + trait_fr.name + special;
if ( trait_en.system?.description?.value) { if ( trait_en.system?.description?.value && trait_fr.system?.description?.value) {
trait_en.system.description.value = trait_fr.system.description.value; trait_en.system.description.value = trait_fr.system.description.value;
} }
if (trait_en.system?.specification && isNaN(trait_en.system.specification.value)) { // This is a string, so translate it if (trait_en?.system?.specification && isNaN(trait_en.system.specification?.value)) { // This is a string, so translate it
//console.log("Translating : ", trait_en.system.specification.value); //console.log("Translating : ", trait_en.system.specification.value);
trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim()); trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim());
} }
@@ -438,9 +489,12 @@ Hooks.once('init', () => {
if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside
let re = /(.*) +\((.*)\)/i; let re = /(.*) +\((.*)\)/i;
let res = re.exec(name_en); let res = re.exec(name_en);
if (!res) { console.warn("WFRP4E-FR | bestiary_traits: regex failed for skill:", name_en); }
else {
name_en = res[1].trim(); // Get the root skill name name_en = res[1].trim(); // Get the root skill name
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
} }
}
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("skill") let validCompendiums = game.wfrp4e.tags.getPacksWithTag("skill")
for (let compData of validCompendiums) { for (let compData of validCompendiums) {
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system:{description:{value: trait_en.system.description.value}} }, true) let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system:{description:{value: trait_en.system.description.value}} }, true)
@@ -484,9 +538,12 @@ Hooks.once('init', () => {
if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside
let re = /(.*) +\((.*)\)/i; let re = /(.*) +\((.*)\)/i;
let res = re.exec(name_en); let res = re.exec(name_en);
if (!res) { console.warn("WFRP4E-FR | bestiary_traits: regex failed for talent:", name_en); }
else {
name_en = res[1].trim(); // Get the root talent name, no parenthesis this time... name_en = res[1].trim(); // Get the root talent name, no parenthesis this time...
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
} }
}
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("talent") let validCompendiums = game.wfrp4e.tags.getPacksWithTag("talent")
for (let compData of validCompendiums) { for (let compData of validCompendiums) {
if (name_en === "Trapper") { if (name_en === "Trapper") {
@@ -650,15 +707,15 @@ Hooks.once('init', () => {
if (!effects) return; if (!effects) return;
for (const element of effects) { for (const element of effects) {
let effect = element; let effect = element;
let label = effect.label; let name = effect.name || effect.label;
let gravity = ""; let gravity = "";
if (label.includes("(") && label.includes(")")) { // Then process specific skills name with (xxxx) inside if (name.includes("(") && name.includes(")")) {
let re = /(.*) +\((.*)\)/i; let re = /(.*) +\((.*)\)/i;
let res = re.exec(label); let res = re.exec(name);
label = res[1].trim(); // Get the gravity name = res[1].trim();
gravity = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword gravity = " (" + game.i18n.localize(res[2].trim()) + ")";
} }
effect.label = game.i18n.localize(label) + gravity; effect.name = game.i18n.localize(name) + gravity;
} }
}, },
// Auto-translate duration // Auto-translate duration

View File

@@ -145,6 +145,39 @@ export class WH4FRPatchConfig {
} }
} }
/************************************************************************************/
static patch_symptom_severity() {
// The core module's symptomEffects scripts check for English severity terms
// ("Moderate", "Severe") in this.effect.name. French disease compendiums use
// translated severity ("Modéré(e)", "Grave"), so the checks would fail.
// We patch the relevant scriptData to also check for French severity terms.
const effects = game.wfrp4e.config.symptomEffects;
if (!effects) return;
// Patch blight: checks Moderate → easy, Severe → average, else → veasy
for (const key of ["blight", "gangrene"]) {
const effect = effects[key];
if (!effect?.system?.scriptData) continue;
for (const sd of effect.system.scriptData) {
if (sd.script && sd.script.includes('.includes("Moderate")')) {
sd.script = sd.script
.replace('includes("Moderate")', 'includes("Moderate") || this.effect.name.includes("Modéré")')
.replace('includes("Severe")', 'includes("Severe") || this.effect.name.includes("Grave")');
}
}
}
// Patch convulsions: checks Moderate → -20, else → -10
if (effects.convulsions?.system?.scriptData) {
for (const sd of effects.convulsions.system.scriptData) {
if (sd.script && sd.script.includes('.includes("Moderate")')) {
sd.script = sd.script
.replace('includes("Moderate")', 'includes("Moderate") || this.effect.name.includes("Modéré")');
}
}
}
}
/************************************************************************************/ /************************************************************************************/
static fixSpeciesTable() { static fixSpeciesTable() {
@@ -192,16 +225,15 @@ export class WH4FRPatchConfig {
} }
if (game.wfrp4e.config.loreEffects) { if (game.wfrp4e.config.loreEffects) {
game.wfrp4e.config.loreEffects["beasts"].label = "Domaine des Bêtes" game.wfrp4e.config.loreEffects["beasts"].name = "Domaine des Bêtes"
game.wfrp4e.config.loreEffects["death"].label = "Domaine de la Mort" game.wfrp4e.config.loreEffects["death"].name = "Domaine de la Mort"
game.wfrp4e.config.loreEffects["fire"].label = "Domaine du Feu" game.wfrp4e.config.loreEffects["fire"].name = "Domaine du Feu"
game.wfrp4e.config.loreEffects["metal"].label = "Domaine du Métal" game.wfrp4e.config.loreEffects["metal"].name = "Domaine du Métal"
game.wfrp4e.config.loreEffects["heavens"].label = "Domaine des Cieux" game.wfrp4e.config.loreEffects["heavens"].name = "Domaine des Cieux"
game.wfrp4e.config.loreEffects["life"].label = "Domaine de la Vie" game.wfrp4e.config.loreEffects["life"].name = "Domaine de la Vie"
game.wfrp4e.config.loreEffects["light"].label = "Domaine de la Lumière" game.wfrp4e.config.loreEffects["light"].name = "Domaine de la Lumière"
game.wfrp4e.config.loreEffects["shadow"].label = "Domaine des Ombres" game.wfrp4e.config.loreEffects["shadow"].name = "Domaine des Ombres"
game.wfrp4e.config.loreEffects["hedgecraft"].label = "Domaine de la Magie de Village" game.wfrp4e.config.loreEffects["hedgecraft"].name = "Domaine de la Sorcellerie"
game.wfrp4e.config.loreEffects["hedgecraft"].label = "Domaine de la Sorcellerie"
} }
if (game.wfrp4e.config.species) { if (game.wfrp4e.config.species) {
@@ -240,276 +272,10 @@ export class WH4FRPatchConfig {
this.patch_career(); this.patch_career();
game.wfrp4e.config.symptomEffects = { // Patch symptom severity scripts to support French severity terms
"blight": { // The core module scripts check for "Moderate"/"Severe" in effect.name,
label: "Toxine", // but French disease compendiums use "Modéré(e)"/"Grave" instead.
icon: "modules/wfrp4e-core/icons/diseases/disease.png", this.patch_symptom_severity();
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "invoke",
"symptom": true,
"script": `
let difficulty = ""
if (this.effect.label.includes("Modéré"))
difficulty = "easy"
else if (this.effect.label.includes("Sévère"))
difficulty = "average"
else
difficulty = "veasy"
if (args.actor.isOwner)
{
args.actor.setupSkill("Résistance", {absolute: {difficulty}}).then(setupData => {
args.actor.basicTest(setupData).then(test =>
{
if (test.result.outcome == "failure")
args.actor.addCondition("dead")
})
})
}`
}
}
},
"buboes": {
label: "Bubons",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prefillDialog",
"symptom": true,
"script": `
let applicableCharacteristics = ["ws", "bs", "s", "fel", "ag", "t", "dex"]
if (args.type == "weapon")
args.prefillModifiers.modifier -= 10
else if (args.type == "characteristic")
{
if (applicableCharacteristics.includes(args.item))
args.prefillModifiers.modifier -= 10
}
else if (args.type == "skill")
{
if (applicableCharacteristics.includes(args.item.characteristic.value))
args.prefillModifiers.modifier -= 10
}
`}
}
},
"convulsions": {
label: "Convulsions",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prefillDialog",
"symptom": true,
"script": `
let modifier = 0
if (this.effect.label.includes("Modéré"))
modifier = -20
else
modifier = -10
let applicableCharacteristics = ["ws", "bs", "s", "ag", "t", "dex"]
if (args.type == "weapon")
args.prefillModifiers.modifier += modifier
else if (args.type == "characteristic")
{
if (applicableCharacteristics.includes(args.item))
args.prefillModifiers.modifier += modifier
}
else if (args.type == "skill")
{
if (applicableCharacteristics.includes(args.item.characteristic.value))
args.prefillModifiers.modifier += modifier
}
}`
}
}
},
"fever": {
label: "Fièvre",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prefillDialog",
"symptom": true,
"script": `
let applicableCharacteristics = ["ws", "bs", "s", "fel", "ag", "t", "dex"]
if (args.type == "weapon")
args.prefillModifiers.modifier -= 10
else if (args.type == "characteristic")
{
if (applicableCharacteristics.includes(args.item))
args.prefillModifiers.modifier -= 10
}
else if (args.type == "skill")
{
if (applicableCharacteristics.includes(args.item.characteristic.value))
args.prefillModifiers.modifier -= 10
}`,
"otherEffects": ["blight", "wounded"]
}
}
},
"flux": {
label: "Intoxication Alimentaire",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"symptom": true
}
}
},
"lingering": {
label: "Persistant",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"symptom": true
}
}
},
"coughsAndSneezes": {
label: "Toux et éternuements",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"symptom": true
}
}
},
"gangrene": {
label: "Gangrène",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prefillDialog",
"symptom": true,
"script": `
if (args.type == "characteristic" && args.item == "fel")
{
if (args.item == "fel")
args.prefillModifiers.modifier -= 10
}
else if (args.type == "skill")
{
if (args.item.characteristic.value == "fel")
args.prefillModifiers.modifier -= 10
}
}`
}
}
},
"malaise": {
label: "Malaise",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prepareData",
"symptom": true,
"script": `
if (game.user.isUniqueGM)
{
let fatigued = args.actor.hasCondition("fatigued")
if (!fatigued)
{
args.actor.addCondition("fatigued")
ui.notifications.notify("Etat Extenué ajouté à " + args.actor.name + ", qui ne peut pas être enlevé tant que le symptôme Malaise est présent.")
}
}
`
}
}
},
"nausea": {
label: "Nausée",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "rollTest",
"symptom": true,
"script": `
if (this.actor.isOwner && args.test.result.outcome == "failure")
{
let applicableCharacteristics = ["ws", "bs", "s", "fel", "ag", "t", "dex"]
if (applicableCharacteristics.includes(args.test.result.characteristic))
this.actor.addCondition("stunned")
else if (args.test.result.skill && applicableCharacteristics.includes(args.test.result.skill.system.characteristic.value))
this.actor.addCondition("stunned")
else if (args.test.result.weapon)
this.actor.addCondition("stunned")
}
`
}
}
},
"pox": {
label: "Démangeaisons",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "prefillDialog",
"symptom": true,
"script": `
if (args.type == "characteristic" && args.item == "fel")
args.prefillModifiers.modifier -= 10
else if (args.type == "skill")
{
if (args.item.characteristic.value == "fel")
args.prefillModifiers.modifier -= 10
}
}`
}
}
},
"wounded": {
label: "Blessé",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
transfer: true,
flags: {
wfrp4e: {
"effectApplication": "actor",
"effectTrigger": "invoke",
"symptom": true,
"script": `
if (args.actor.isOwner)
{
args.actor.setupSkill("Résistance", {absolute: {difficulty : "average"}}).then(setupData => {
args.actor.basicTest(setupData).then(test =>
{
if (test.result.outcome == "failure")
fromUuid("Compendium.wfrp4e-core.diseases.kKccDTGzWzSXCBOb").then(disease => {
args.actor.createEmbeddedDocuments("Item", [disease.toObject()])
})
})
})
}`
}
}
}
}
game.wfrp4e.config.effectApplication = { game.wfrp4e.config.effectApplication = {
"actor": "Acteur", "actor": "Acteur",

View File

@@ -155,7 +155,7 @@ export default class InnRoller {
} }
// Extraire les informations du résultat // Extraire les informations du résultat
const resultText = rollResult.results[0]?.text || "Résultat inconnu"; const resultText = rollResult.results[0]?.name || rollResult.results[0]?.description || "Résultat inconnu";
const rollFormula = rollResult.roll?.formula || "1d100"; const rollFormula = rollResult.roll?.formula || "1d100";
const rollTotal = rollResult.roll?.total || 0; const rollTotal = rollResult.roll?.total || 0;
@@ -349,7 +349,7 @@ export default class InnRoller {
if (rollTable) { if (rollTable) {
try { try {
const roll = await rollTable.draw({ displayChat: false }); const roll = await rollTable.draw({ displayChat: false });
const resultText = roll.results[0]?.text || "Résultat inconnu"; const resultText = roll.results[0]?.name || roll.results[0]?.description || "Résultat inconnu";
results.push({ results.push({
category: this.getCategoryName(tableName), category: this.getCategoryName(tableName),
name: resultText, name: resultText,

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
MANIFEST-001279 MANIFEST-001339

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.937989 7fac277fe6c0 Recovering log #1277 2026/04/13-23:10:07.603169 7fddd97be6c0 Recovering log #1337
2026/01/29-10:58:42.947978 7fac277fe6c0 Delete type=3 #1275 2026/04/13-23:10:07.613544 7fddd97be6c0 Delete type=3 #1335
2026/01/29-10:58:42.948038 7fac277fe6c0 Delete type=0 #1277 2026/04/13-23:10:07.613619 7fddd97be6c0 Delete type=0 #1337
2026/01/29-11:43:15.082893 7fa9a6fef6c0 Level-0 table #1282: started 2026/04/13-23:14:52.439353 7fddca1c26c0 Level-0 table #1342: started
2026/01/29-11:43:15.082936 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK 2026/04/13-23:14:52.439375 7fddca1c26c0 Level-0 table #1342: 0 bytes OK
2026/01/29-11:43:15.089469 7fa9a6fef6c0 Delete type=0 #1280 2026/04/13-23:14:52.447401 7fddca1c26c0 Delete type=0 #1340
2026/01/29-11:43:15.099933 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.447538 7fddca1c26c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.344493 7fac277fe6c0 Recovering log #1273 2026/04/13-23:01:32.388566 7fddd97be6c0 Recovering log #1333
2026/01/29-10:52:14.354354 7fac277fe6c0 Delete type=3 #1271 2026/04/13-23:01:32.399282 7fddd97be6c0 Delete type=3 #1331
2026/01/29-10:52:14.354436 7fac277fe6c0 Delete type=0 #1273 2026/04/13-23:01:32.399346 7fddd97be6c0 Delete type=0 #1333
2026/01/29-10:55:44.810919 7fa9a6fef6c0 Level-0 table #1278: started 2026/04/13-23:07:12.207745 7fddca1c26c0 Level-0 table #1338: started
2026/01/29-10:55:44.810962 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK 2026/04/13-23:07:12.207766 7fddca1c26c0 Level-0 table #1338: 0 bytes OK
2026/01/29-10:55:44.820672 7fa9a6fef6c0 Delete type=0 #1276 2026/04/13-23:07:12.214487 7fddca1c26c0 Delete type=0 #1336
2026/01/29-10:55:44.820811 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.214671 7fddca1c26c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-001281 MANIFEST-001341

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.950367 7fac277fe6c0 Recovering log #1279 2026/04/13-23:10:07.616013 7fddcbfff6c0 Recovering log #1339
2026/01/29-10:58:42.959516 7fac277fe6c0 Delete type=3 #1277 2026/04/13-23:10:07.625710 7fddcbfff6c0 Delete type=3 #1337
2026/01/29-10:58:42.959596 7fac277fe6c0 Delete type=0 #1279 2026/04/13-23:10:07.625777 7fddcbfff6c0 Delete type=0 #1339
2026/01/29-11:43:14.910167 7fa9a6fef6c0 Level-0 table #1284: started 2026/04/13-23:14:52.426320 7fddca1c26c0 Level-0 table #1344: started
2026/01/29-11:43:14.910204 7fa9a6fef6c0 Level-0 table #1284: 0 bytes OK 2026/04/13-23:14:52.426354 7fddca1c26c0 Level-0 table #1344: 0 bytes OK
2026/01/29-11:43:14.916676 7fa9a6fef6c0 Delete type=0 #1282 2026/04/13-23:14:52.432796 7fddca1c26c0 Delete type=0 #1342
2026/01/29-11:43:14.924493 7fa9a6fef6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.447521 7fddca1c26c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.356741 7fac27fff6c0 Recovering log #1275 2026/04/13-23:01:32.401911 7fddd8fbd6c0 Recovering log #1335
2026/01/29-10:52:14.366627 7fac27fff6c0 Delete type=3 #1273 2026/04/13-23:01:32.411157 7fddd8fbd6c0 Delete type=3 #1333
2026/01/29-10:52:14.366686 7fac27fff6c0 Delete type=0 #1275 2026/04/13-23:01:32.411219 7fddd8fbd6c0 Delete type=0 #1335
2026/01/29-10:55:44.821680 7fa9a6fef6c0 Level-0 table #1280: started 2026/04/13-23:07:12.194188 7fddca1c26c0 Level-0 table #1340: started
2026/01/29-10:55:44.821714 7fa9a6fef6c0 Level-0 table #1280: 0 bytes OK 2026/04/13-23:07:12.194235 7fddca1c26c0 Level-0 table #1340: 0 bytes OK
2026/01/29-10:55:44.835173 7fa9a6fef6c0 Delete type=0 #1278 2026/04/13-23:07:12.201298 7fddca1c26c0 Delete type=0 #1338
2026/01/29-10:55:44.835325 7fa9a6fef6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.214646 7fddca1c26c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-001279 MANIFEST-001339

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.977033 7fac3cbfe6c0 Recovering log #1277 2026/04/13-23:10:07.641322 7fddd9fbf6c0 Recovering log #1337
2026/01/29-10:58:42.986296 7fac3cbfe6c0 Delete type=3 #1275 2026/04/13-23:10:07.651537 7fddd9fbf6c0 Delete type=3 #1335
2026/01/29-10:58:42.986342 7fac3cbfe6c0 Delete type=0 #1277 2026/04/13-23:10:07.651623 7fddd9fbf6c0 Delete type=0 #1337
2026/01/29-11:43:14.924604 7fa9a6fef6c0 Level-0 table #1282: started 2026/04/13-23:14:52.478583 7fddca1c26c0 Level-0 table #1342: started
2026/01/29-11:43:14.924660 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK 2026/04/13-23:14:52.478613 7fddca1c26c0 Level-0 table #1342: 0 bytes OK
2026/01/29-11:43:14.930959 7fa9a6fef6c0 Delete type=0 #1280 2026/04/13-23:14:52.484781 7fddca1c26c0 Delete type=0 #1340
2026/01/29-11:43:14.954971 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.484989 7fddca1c26c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.382212 7fac27fff6c0 Recovering log #1273 2026/04/13-23:01:32.426243 7fddcbfff6c0 Recovering log #1333
2026/01/29-10:52:14.391540 7fac27fff6c0 Delete type=3 #1271 2026/04/13-23:01:32.436951 7fddcbfff6c0 Delete type=3 #1331
2026/01/29-10:52:14.391602 7fac27fff6c0 Delete type=0 #1273 2026/04/13-23:01:32.437016 7fddcbfff6c0 Delete type=0 #1333
2026/01/29-10:55:44.848192 7fa9a6fef6c0 Level-0 table #1278: started 2026/04/13-23:07:12.201427 7fddca1c26c0 Level-0 table #1338: started
2026/01/29-10:55:44.848229 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK 2026/04/13-23:07:12.201453 7fddca1c26c0 Level-0 table #1338: 0 bytes OK
2026/01/29-10:55:44.858087 7fa9a6fef6c0 Delete type=0 #1276 2026/04/13-23:07:12.207634 7fddca1c26c0 Delete type=0 #1336
2026/01/29-10:55:44.858262 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.214659 7fddca1c26c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)

View File

View File

View File

@@ -1 +1 @@
MANIFEST-001279 MANIFEST-001339

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.926104 7fac277fe6c0 Recovering log #1277 2026/04/13-23:10:07.590923 7fddd8fbd6c0 Recovering log #1337
2026/01/29-10:58:42.935779 7fac277fe6c0 Delete type=3 #1275 2026/04/13-23:10:07.600770 7fddd8fbd6c0 Delete type=3 #1335
2026/01/29-10:58:42.935842 7fac277fe6c0 Delete type=0 #1277 2026/04/13-23:10:07.600832 7fddd8fbd6c0 Delete type=0 #1337
2026/01/29-11:43:14.916846 7fa9a6fef6c0 Level-0 table #1282: started 2026/04/13-23:14:52.419409 7fddca1c26c0 Level-0 table #1342: started
2026/01/29-11:43:14.916889 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK 2026/04/13-23:14:52.419440 7fddca1c26c0 Level-0 table #1342: 0 bytes OK
2026/01/29-11:43:14.924373 7fa9a6fef6c0 Delete type=0 #1280 2026/04/13-23:14:52.426167 7fddca1c26c0 Delete type=0 #1340
2026/01/29-11:43:14.924501 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.447511 7fddca1c26c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.331605 7fac3d3ff6c0 Recovering log #1273 2026/04/13-23:01:32.376215 7fddcbfff6c0 Recovering log #1333
2026/01/29-10:52:14.341908 7fac3d3ff6c0 Delete type=3 #1271 2026/04/13-23:01:32.386265 7fddcbfff6c0 Delete type=3 #1331
2026/01/29-10:52:14.341974 7fac3d3ff6c0 Delete type=0 #1273 2026/04/13-23:01:32.386335 7fddcbfff6c0 Delete type=0 #1333
2026/01/29-10:55:44.799059 7fa9a6fef6c0 Level-0 table #1278: started 2026/04/13-23:07:12.172988 7fddca1c26c0 Level-0 table #1338: started
2026/01/29-10:55:44.799092 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK 2026/04/13-23:07:12.173015 7fddca1c26c0 Level-0 table #1338: 0 bytes OK
2026/01/29-10:55:44.809892 7fa9a6fef6c0 Delete type=0 #1276 2026/04/13-23:07:12.180381 7fddca1c26c0 Delete type=0 #1336
2026/01/29-10:55:44.810048 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.187032 7fddca1c26c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)

View File

View File

View File

@@ -1 +1 @@
MANIFEST-001279 MANIFEST-001339

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.911172 7fac3cbfe6c0 Recovering log #1277 2026/04/13-23:10:07.578503 7fddd9fbf6c0 Recovering log #1337
2026/01/29-10:58:42.922025 7fac3cbfe6c0 Delete type=3 #1275 2026/04/13-23:10:07.588788 7fddd9fbf6c0 Delete type=3 #1335
2026/01/29-10:58:42.922081 7fac3cbfe6c0 Delete type=0 #1277 2026/04/13-23:10:07.588855 7fddd9fbf6c0 Delete type=0 #1337
2026/01/29-11:43:14.903068 7fa9a6fef6c0 Level-0 table #1282: started 2026/04/13-23:14:52.432890 7fddca1c26c0 Level-0 table #1342: started
2026/01/29-11:43:14.903107 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK 2026/04/13-23:14:52.432912 7fddca1c26c0 Level-0 table #1342: 0 bytes OK
2026/01/29-11:43:14.910024 7fa9a6fef6c0 Delete type=0 #1280 2026/04/13-23:14:52.439225 7fddca1c26c0 Delete type=0 #1340
2026/01/29-11:43:14.924484 7fa9a6fef6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.447529 7fddca1c26c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.318846 7fac277fe6c0 Recovering log #1273 2026/04/13-23:01:32.363321 7fddd9fbf6c0 Recovering log #1333
2026/01/29-10:52:14.328479 7fac277fe6c0 Delete type=3 #1271 2026/04/13-23:01:32.373224 7fddd9fbf6c0 Delete type=3 #1331
2026/01/29-10:52:14.328545 7fac277fe6c0 Delete type=0 #1273 2026/04/13-23:01:32.373285 7fddd9fbf6c0 Delete type=0 #1333
2026/01/29-10:55:44.787338 7fa9a6fef6c0 Level-0 table #1278: started 2026/04/13-23:07:12.180494 7fddca1c26c0 Level-0 table #1338: started
2026/01/29-10:55:44.787395 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK 2026/04/13-23:07:12.180514 7fddca1c26c0 Level-0 table #1338: 0 bytes OK
2026/01/29-10:55:44.798087 7fa9a6fef6c0 Delete type=0 #1276 2026/04/13-23:07:12.186759 7fddca1c26c0 Delete type=0 #1336
2026/01/29-10:55:44.798228 7fa9a6fef6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.187045 7fddca1c26c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)

View File

View File

View File

@@ -1 +1 @@
MANIFEST-000922 MANIFEST-000982

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.964451 7fac3cbfe6c0 Recovering log #920 2026/04/13-23:10:07.628261 7fddd97be6c0 Recovering log #980
2026/01/29-10:58:42.974523 7fac3cbfe6c0 Delete type=3 #918 2026/04/13-23:10:07.638743 7fddd97be6c0 Delete type=3 #978
2026/01/29-10:58:42.974583 7fac3cbfe6c0 Delete type=0 #920 2026/04/13-23:10:07.638813 7fddd97be6c0 Delete type=0 #980
2026/01/29-11:43:14.948296 7fa9a6fef6c0 Level-0 table #925: started 2026/04/13-23:14:52.447639 7fddca1c26c0 Level-0 table #985: started
2026/01/29-11:43:14.948359 7fa9a6fef6c0 Level-0 table #925: 0 bytes OK 2026/04/13-23:14:52.447658 7fddca1c26c0 Level-0 table #985: 0 bytes OK
2026/01/29-11:43:14.954859 7fa9a6fef6c0 Delete type=0 #923 2026/04/13-23:14:52.454058 7fddca1c26c0 Delete type=0 #983
2026/01/29-11:43:14.955000 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end) 2026/04/13-23:14:52.484949 7fddca1c26c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2026/01/29-10:52:14.369127 7fac277fe6c0 Recovering log #916 2026/04/13-23:01:32.413810 7fddd97be6c0 Recovering log #976
2026/01/29-10:52:14.379523 7fac277fe6c0 Delete type=3 #914 2026/04/13-23:01:32.423395 7fddd97be6c0 Delete type=3 #974
2026/01/29-10:52:14.379589 7fac277fe6c0 Delete type=0 #916 2026/04/13-23:01:32.423455 7fddd97be6c0 Delete type=0 #976
2026/01/29-10:55:44.836202 7fa9a6fef6c0 Level-0 table #921: started 2026/04/13-23:07:12.187249 7fddca1c26c0 Level-0 table #981: started
2026/01/29-10:55:44.836244 7fa9a6fef6c0 Level-0 table #921: 0 bytes OK 2026/04/13-23:07:12.187284 7fddca1c26c0 Level-0 table #981: 0 bytes OK
2026/01/29-10:55:44.847287 7fa9a6fef6c0 Delete type=0 #919 2026/04/13-23:07:12.194022 7fddca1c26c0 Delete type=0 #979
2026/01/29-10:55:44.847494 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end) 2026/04/13-23:07:12.214628 7fddca1c26c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1 @@
return !args.skill?.name.includes(game.i18n.localize("NAME.Row")) && !args.skill?.name.includes(game.i18n.localize("NAME.Sail"));

View File

@@ -0,0 +1,4 @@
if (!this.item.system.properties.qualities.fast)
this.item.system.qualities.value.push({name : 'fast'});
if (!this.item.system.properties.qualities.magical)
this.item.system.qualities.value.push({name : 'magical'});

View File

@@ -0,0 +1,9 @@
if (args.skill?.name != game.i18n.localize("NAME.Gossip"))
{
return true;
}
else
{
args.data.reversal = {allowed : true, if: "success"}; // Kind of a kludge here, the talent Tests has a specific condition, but the description simply says "any gossip test can be reversed" so check it here instead of submission
}

View File

@@ -0,0 +1,7 @@
this.actor.setupSkill(game.i18n.localize("NAME.Cool"), {skipTargets: true, appendTitle : ` - ${this.effect.name}`}).then(async test => {
await test.roll()
if (test.failed)
{
this.actor.addCondition("stunned")
}
})

View File

@@ -0,0 +1,18 @@
const balanced = game.i18n.localize("WFRP4E.YenluiBalanced");
const light = game.i18n.localize("WFRP4E.YenluiLight");
const dark = game.i18n.localize("WFRP4E.YenluiDark");
let newName, newDescription;
if (this.effect.name === game.i18n.localize("WFRP4E.YenluiDark")) {
newName = game.i18n.localize("WFRP4E.YenluiBalanced");
newDescription = game.i18n.localize("WFRP4E.YenluiBalancedDesc");
} else if (this.effect.name === game.i18n.localize("WFRP4E.YenluiBalanced")) {
newName = game.i18n.localize("WFRP4E.YenluiLight");
newDescription = game.i18n.localize("WFRP4E.YenluiLightDesc");
}
if (newName) {
await this.effect.update({name: newName});
await this.item.update({name: newName, "system.description.value": newDescription});
}

View File

@@ -1,31 +1,28 @@
if (!this.item.name.includes("(") || this.item.system.tests.value.includes("Terrain") || this.item.system.tests.value.toLowerCase().includes("(any)")) if (!this.item.name.includes("(") || this.item.system.tests.value.includes("Terrain") || this.item.system.tests.value.toLowerCase().includes("(any)")) {
{ let tests = this.item.system.tests.value
let Tests = this.item.system.tests.value
let name = this.item.name let name = this.item.name
// If name already specifies, make sure Tests value reflects that // If name already specifies, make sure Tests value reflects that
if (name.includes("(") && !name.toLowerCase().includes("(any)")) if (name.includes("(") && !name.toLowerCase().includes("(any)")) {
{
let terrain = name.split("(")[1].split(")")[0] let terrain = name.split("(")[1].split(")")[0]
tests = tests.replace("the Terrain", terrain) tests = tests.replace("the Terrain", terrain)
} }
else // If no sense specified, provide dialog choice else // If no sense specified, provide dialog choice
{ {
let choice = await ItemDialog.create(ItemDialog.objectToArray({ let choice = await ItemDialog.create(ItemDialog.objectToArray({
coastal : "Littoral", coastal: "Littoral",
deserts : "Déserts", deserts: "Déserts",
marshes : "Marécages", marshes: "Marécages",
rocky : "Rocailleux", rocky: "Rocailleux",
tundra : "Toundra", tundra: "Toundra",
woodlands : "Régions boisées" woodlands: "Régions boisées"
}, this.item.img), 1, "Choisissez un Terrain"); }, this.item.img), 1, "Choisissez un Terrain");
if (choice[0]) if (choice[0]) {
{
name = `${name.split("(")[0].trim()} (${choice[0].name})` name = `${name.split("(")[0].trim()} (${choice[0].name})`
tests = tests.replace("Terrain", choice[0].name + " Terrain") tests = tests.replace("Terrain", choice[0].name + " Terrain")
} }
} }
this.effect.updateSource({name}) this.effect.updateSource({ name })
this.item.updateSource({name, "system.tests.value" : tests}) this.item.updateSource({ name, "system.tests.value": tests })
} }

View File

@@ -0,0 +1 @@
return !args.skill?.name.includes(game.i18n.localize("NAME.Language"));

View File

@@ -14,7 +14,7 @@
let updateObj = this.actor.toObject(); let updateObj = this.actor.toObject();
let talents = (await Promise.tout([game.wfrp4e.tables.rollTable("talents"), game.wfrp4e.tables.rollTable("talents"), game.wfrp4e.tables.rollTable("talents")])).map(i => i.text) let talents = (await Promise.all([game.wfrp4e.tables.rollTable("talents"), game.wfrp4e.tables.rollTable("talents"), game.wfrp4e.tables.rollTable("talents")])).map(i => i.text)
for (let ch in characteristics) for (let ch in characteristics)
{ {

View File

@@ -0,0 +1 @@
return args.skill?.name.includes(game.i18n.localize("NAME.Channelling")) || args.skill?.name == `${game.i18n.localize("NAME.Language")} (${game.i18n.localize("SPEC.Magick")})`

View File

@@ -0,0 +1,3 @@
const qualities = foundry.utils.deepClone(args.item.system.qualities.value);
qualities.push({name:"fine", value: 1});
args.item?.update({"system.qualities.value": qualities});

View File

@@ -1,6 +1,6 @@
let test = await args.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : " - " + this.effect.name, context : {failure: "Gain de 1 état Sonné"}}) let test = await args.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : " - " + this.effect.name, context : {failure: "Gain de 1 état Sonné"}})
await Test.roll(); await test.roll();
if (Test.Échoué) if (test.failed)
{ {
args.actor.addCondition("stunned") args.actor.addCondition("stunned")
} }

View File

@@ -0,0 +1,19 @@
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : " - " + this.effect.name})
await test.roll();
if (!test.succeeded)
{
let item = await fromUuid("Compendium.wfrp4e-core.items.ZhMADOqoo0y8Q9bx")
let data = item.toObject();
if (this.item.system.location.key == "rLeg")
{
data.system.location.value = "Orteil Droit"
data.system.location.key = "rToe";
}
else if (this.item.system.location.key == "lLeg")
{
data.system.location.value = "Orteil Gauche"
data.system.location.key = "lToe";
}
this.actor.createEmbeddedDocuments("Item", [data])
}
this.effect.delete();

View File

@@ -0,0 +1,7 @@
this.actor.setupSkill(game.i18n.localize("NAME.Cool"), {skipTargets: true, appendTitle : ` - ${this.effect.name}`}).then(async test => {
await test.roll();
if (test.failed)
{
this.actor.addCondition("stunned", 3)
}
})

View File

@@ -0,0 +1 @@
return !args.skill?.name?.includes(game.i18n.localize("NAME.Sail"))

View File

@@ -0,0 +1 @@
return args.type != "channelling" && !args.skill?.name.includes(game.i18n.localize("NAME.Channelling"))

View File

@@ -0,0 +1,5 @@
if (args.item.range && args.item.range.bands)
{
args.item.range.bands[game.i18n.localize("Long Range")].modifier = 0
args.item.range.bands[game.i18n.localize("Extreme")].modifier /= 2
}

View File

@@ -0,0 +1,6 @@
if (args.test.result.castOutcome == "success" && args.test.spell.system.lore.value.includes("high"))
{
this.effect.update({name: this.effect.setSpecifier(parseInt(this.effect.specifier - 1))})
this.script.message("La valeur de Protection est maintenant de " + (this.effect.specifier - 1), {flavor: this.effect.sourceItem.name})
}

View File

@@ -0,0 +1 @@
return args.skill?.name == game.i18n.localize("NAME.Bribery") || args.skill?.name.includes(game.i18n.localize("NAME.Stealth"));

View File

@@ -0,0 +1 @@
return args.item?.name != game.i18n.localize("NAME.Navigation")

View File

@@ -0,0 +1,9 @@
if (args.totalWoundLoss > 0)
{
let test = await args.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await test.roll();
if (test.failed && parseInt(args.sourceTest?.result.SL) > 0)
{
args.actor.addCondition("stunned", parseInt(args.sourceTest?.attackerTest.result.SL))
}
}

View File

@@ -0,0 +1,19 @@
let skill = `${game.i18n.localize("NAME.Trade")} (${this.item.parenthesesText})`
let currentCareer = this.actor.system.currentCareer;
let existingSkill = this.actor.itemTypes.skill.find(i => i.name == skill);
if (!currentCareer) return
let inCurrentCareer = currentCareer.system.skills.concat(currentCareer.system.addedSkills).includes(skill);
let craftsmanAdded = this.actor.getFlag("wfrp4e", "craftsmanAdded") || {};
if (existingSkill && inCurrentCareer && !craftsmanAdded[existingSkill.name])
{
existingSkill.system.advances.costModifier = -5;
}
else
{
craftsmanAdded[skill] = true;
currentCareer.system.addedSkills.push(skill);
foundry.utils.setProperty(this.actor, "flags.wfrp4e.craftsmanAdded", craftsmanAdded)
}

View File

@@ -0,0 +1,4 @@
let item = await fromUuid("Compendium.wfrp4e-core.items.9h82z72XGo9tfgQS")
let data = item.toObject();
data.name = data.name += ` (${game.i18n.localize("SPEC.Hearing")})`
this.actor.createEmbeddedDocuments("Item", [data], {fromEffect : this.effect.id})

View File

@@ -0,0 +1 @@
return args.skill?.name.includes(game.i18n.localize("NAME.Lore"));

View File

@@ -0,0 +1,20 @@
if (!["Goblin", "Orc"].includes(this.actor.system.details.species.value)) {
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), { appendTitle: ` - ${this.effect.name}` })
await test.roll();
if (test.failed) {
let infection = await fromUuid("Compendium.wfrp4e-core.items.Item.1hQuVFZt9QnnbWzg")
this.actor.createEmbeddedDocuments("Item", [infection])
}
}
// Since wounds change when the effect is deleted, need to wait until after
// the max wounds have been recalculated to apply damage
warhammer.utility.sleep(1000).then(async () => {
let roll = await new Roll("1d10").roll({allowInteractive : false});
roll.toMessage(this.script.getChatData());
this.script.message(await this.actor.applyBasicDamage(roll.total, { damageType: game.wfrp4e.config.DAMAGE_TYPE.IGNORE_ALL, suppressMsg: true }))
})

View File

@@ -0,0 +1,18 @@
let currentCareer = this.actor.system.currentCareer;
if (!currentCareer)
{
return;
}
let talents = [game.i18n.localize("NAME.AA"),
`${game.i18n.localize("NAME.ArcaneMagic")} (${game.i18n.localize("SPEC.Any")})`,
game.i18n.localize("NAME.ChaosMagic") + " " + "(Tzeentch)",
game.i18n.localize("NAME.FastHands"),
game.i18n.localize("NAME.ID"),
game.i18n.localize("NAME.MagicalSense"),
game.i18n.localize("NAME.PettyMagic"),
game.i18n.localize("NAME.SecondSight"),
game.i18n.localize("NAME.WarWizard"),
game.i18n.localize("NAME.Witch")].filter(t => !currentCareer.system.talents.includes(t))
currentCareer.system.talents = currentCareer.system.talents.concat(talents)

View File

@@ -0,0 +1,3 @@
const qualities = foundry.utils.deepClone(args.item.system.qualities.value);
qualities.push({name:"lightweight"});
args.item?.update({"system.qualities.value": qualities});

View File

@@ -0,0 +1,3 @@
return !args.actor?.has(game.i18n.localize("NAME.Swarm")) ||
![game.wfrp4e.config.actorSizeNums.tiny, game.wfrp4e.config.actorSizeNums.ltl]
.includes(args.actor?.sizeNum)

View File

@@ -0,0 +1,14 @@
if (Number(this.actor.system.details.age.value) > 870) {
if (["t", "wp", "fel"].includes(args.characteristic))
args.fields.slBonus -= 3;
if (["ag", "dex", "int"].includes(args.characteristic))
args.fields.slBonus -= 2;
} else if (Number(this.actor.system.details.age.value) > 350) {
if (["t", "wp"].includes(args.characteristic))
args.fields.slBonus -= 2;
if (["fel"].includes(args.characteristic))
args.fields.slBonus -= 1;
} else {
if (["t", "wp"].includes(args.characteristic))
args.fields.slBonus -= 1;
}

View File

@@ -0,0 +1 @@
this.effect.updateSource({name: this.effect.setSpecifier("9")});

View File

@@ -0,0 +1,44 @@
let choice = await ItemDialog.create(ItemDialog.objectToArray(game.wfrp4e.config.locations, this.effect.img), 1, "Choose Location");
if (choice[0])
{
this.effect.updateSource({name : `${this.effect.name} (${choice[0].name})`})
this.effect.updateSource({"flags.wfrp4e.location" : choice[0].id})
}
let location = choice[0].id;
if (["lArm", "rArm"].includes(location))
{
let dropped = this.actor.itemTypes.weapon.filter(i => i.isEquipped & i.system.usesHands.includes(location));
if (dropped.length)
{
this.script.notification(`Dropped ${dropped.map(i => i.name).join(", ")}!`)
for(let weapon of dropped)
{
await weapon.system.toggleEquip();
}
}
}
if (location == "body")
{
await this.actor.addCondition("fatigued");
test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {fields : {difficulty : "hard"}, skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await test.roll();
if (test.failed)
{
this.actor.addCondition("prone");
}
}
if (location == "head")
{
await this.actor.addCondition("stunned");
test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {fields : {difficulty : "average"}, skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await test.roll();
if (test.failed)
{
this.actor.addCondition("unconscious");
}
}

View File

@@ -0,0 +1,2 @@
args.abort = true;
this.script.notification("Impossible d'attaquer cette cible !");

View File

@@ -0,0 +1 @@
return args.skill?.name != game.i18n.localize("NAME.CharmAnimal") && !args.skill?.name.includes(game.i18n.localize("NAME.AnimalTraining"));

View File

@@ -0,0 +1 @@
return !["NAME.Evaluate", "NAME.Gamble"].map(i => game.i18n.localize(i)).includes(args.skill?.name)

View File

@@ -0,0 +1,4 @@
if (this.actor.hasCondition("surprised"))
{
this.actor.setupSkill(game.i18n.localize("NAME.Cool"), {fields : {difficulty : "average"}, skipTargets: true, appendTitle : " - " + this.effect.name}).then(test => test.roll())
}

View File

@@ -0,0 +1 @@
return this.effect.name !== game.i18n.localize("WFRP4E.YenluiLight")

View File

@@ -0,0 +1 @@
return args.skill?.name == game.i18n.localize("NAME.ConsumeAlcohol");

View File

@@ -0,0 +1 @@
return args.item?.system.isRanged;

View File

@@ -0,0 +1 @@
return args.skill?.name != game.i18n.localize("NAME.Research") && !args.skill?.name?.includes(game.i18n.localize("NAME.Language"));

View File

@@ -0,0 +1 @@
this.actor.addEffectItems("Compendium.wfrp4e-core.items.Item.EO05HX7jql0g605A", this.effect, {"system.specification.value": this.actor.system.characteristics.ag.value});

View File

@@ -0,0 +1 @@
return args.skill?.name !== game.i18n.localize("NAME.Endurance");

View File

@@ -0,0 +1,10 @@
this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields: {difficulty : "average"}}).then(async test =>
{
await test.roll()
if (test.failed)
{
let char = Math.ceil(CONFIG.Dice.randomUniform() * 2) == 2 ? "s" : "t";
this.script.message(`<strong>${this.actor.name}</strong> perd 1 point de ${game.wfrp4e.config.characteristics[char]}`)
this.actor.update({[`system.characteristics.${char}.initial`] : this.actor.system.characteristics[char].initial - 1})
}
})

View File

@@ -0,0 +1 @@
return args.skill?.name?.includes(game.i18n.localize("NAME.Language")) || args.type == "cast"

View File

@@ -0,0 +1,7 @@
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "average"}, context : {failure: "1 Corruption Point Gained"}})
await test.roll();
if (test.failed && this.actor.type == "character")
{
this.actor.update({"system.status.corruption.value" : parseInt(this.actor.status.corruption.value) + 1})
this.script.message("Gain de 1 point de Corruption", {whisper : ChatMessage.getWhisperRecipients("GM")})
}

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupCharacteristic("ag", {skipTargets: true, appendTitle : ` - ${this.effect.name}`}); let test = await this.actor.setupCharacteristic("ag", {skipTargets: true, appendTitle : ` - ${this.effect.name}`});
await Test.roll(); await test.roll();
if (Test.Échoué) if (test.failed)
{ {
this.actor.addCondition("bleeding"); this.actor.addCondition("bleeding");
} }

View File

@@ -0,0 +1,10 @@
if (args.totalWoundLoss > 0)
{
let test = await args.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : " - " + this.effect.name})
await test.roll();
if (test.failed)
{
args.totalWoundLoss += 5;
args.modifiers.other.push({label : this.effect.name, value : 5})
}
}

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