Compare commits

..

9 Commits

Author SHA1 Message Date
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
100 changed files with 16392 additions and 37708 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

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é"
},
{
"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",
"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",
"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",
"id": "Festering Wound",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "1d10, ou instantanée si développée à partir dautres symptômes",
"name": "Blessure Purulente",
"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.",
"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",
"id": "Galloping Trots",
"incubationUnit": "Heures",
"incubationUnit": "hours",
"incubationValue": "1d10",
"name": "Courante Galopante",
"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.",
"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",
"id": "Ratte Fever",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "3d10+5",
"name": "Fièvre du rongeur",
"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.",
"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",
"id": "The Bloody Flux",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "2d10",
"name": "Flux Sanglant",
"permanent": "",
@@ -1679,10 +1679,10 @@
{
"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>",
"durationUnit": "Jours",
"durationUnit": "days",
"durationValue": "1d10",
"id": "Blood Rot",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "0",
"name": "Infection du Sang",
"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.",
"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",
"id": "Minor Infection",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "1d10",
"name": "Infection Mineure",
"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.",
"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",
"id": "The Black Plague",
"incubationUnit": "Minutes",
"incubationUnit": "minutes",
"incubationValue": "1d10",
"name": "Peste Noire",
"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.",
"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",
"id": "Packer's Pox",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "1d10",
"name": "Vérole du Tanneur",
"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)",
"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",
"id": "Itching Pox",
"incubationUnit": "Jours",
"incubationUnit": "days",
"incubationValue": "1d10",
"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é.",

View File

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

View File

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

View File

@@ -70,16 +70,10 @@
"converter": "generic_localization"
},
"durationValue": "system.duration.value",
"durationUnit": {
"path": "system.duration.unit",
"converter": "disease_duration_unit"
},
"durationUnit": "system.duration.unit",
"contraction": "system.contraction.value",
"incubationValue": "system.incubation.value",
"incubationUnit": {
"path": "system.incubation.unit",
"converter": "disease_duration_unit"
},
"incubationUnit": "system.incubation.unit",
"symptoms": "system.symptoms.value",
"permanent": "system.permanent.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": "../WarhammerLibrary-FVTT"
}
],
"settings": {}

276
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",
"version": "9.4.0",
"version": "9.4.3",
"esmodules": [
"wh4_fr.js",
"modules/babele-register.js",
@@ -119,8 +119,8 @@
"folders": []
}
],
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/raw/v10/module.json",
"download": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/archive/foundryvtt-wh4-lang-fr-9-3-7.zip",
"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/releases/download/latest/foundryvtt-wh4-lang-fr-fr.zip",
"id": "wh4-fr-translation",
"compatibility": {
"minimum": "13",

View File

@@ -133,10 +133,10 @@ const __auto_patch_translation_journal_compendium = async (compmod) => {
if (game.user.isGM) {
let compData = game.packs.get("WH4-fr-translation.tables-des-traductions");
compData.locked = false;
let translEntries = await compData.getContent();
let translEntries = await compData.getDocuments();
for (let entryData of translEntries) {
let mydata = foundry.utils.duplicate(entryData.data);
mydata.content = mydata.content.replace(/wfrp4e-content/g, compmod);
let mydata = entryData.toObject();
mydata.text.content = mydata.text.content.replace(/wfrp4e-content/g, compmod);
entryData.update(mydata);
}
compData.locked = true;
@@ -175,9 +175,55 @@ const patch_core_tables = (tableList) => {
/************************************************************************************/
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 => {
game.wfrp4e.config.trade.gazetteer = records;
game.wfrp4e.trade.gazetteers.river = records;
});
}
}
@@ -358,6 +404,22 @@ Hooks.on('ready', () => {
// Patch function for effects
game.wfrp4e.utility.findKey = warhammer.utility.findKey
// 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 */
game.wfrp4e.config.difficultyModifiers = {
"veasy": 60,

View File

@@ -279,7 +279,22 @@ Hooks.once('init', () => {
//console.log("Effects :", effectsData, translations, data, tc, tc_translations)
for (let e of effectsData) {
let origName = 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)
}
if ( e.flags?.wfrp4e?.scriptData) {
for (let script of e.flags.wfrp4e.scriptData) {
if (script?.label) {
@@ -650,15 +665,15 @@ Hooks.once('init', () => {
if (!effects) return;
for (const element of effects) {
let effect = element;
let label = effect.label;
let name = effect.name || effect.label;
let gravity = "";
if (label.includes("(") && label.includes(")")) { // Then process specific skills name with (xxxx) inside
if (name.includes("(") && name.includes(")")) {
let re = /(.*) +\((.*)\)/i;
let res = re.exec(label);
label = res[1].trim(); // Get the gravity
gravity = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
let res = re.exec(name);
name = res[1].trim();
gravity = " (" + game.i18n.localize(res[2].trim()) + ")";
}
effect.label = game.i18n.localize(label) + gravity;
effect.name = game.i18n.localize(name) + gravity;
}
},
// 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() {
@@ -240,276 +273,10 @@ export class WH4FRPatchConfig {
this.patch_career();
game.wfrp4e.config.symptomEffects = {
"blight": {
label: "Toxine",
icon: "modules/wfrp4e-core/icons/diseases/disease.png",
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()])
})
})
})
}`
}
}
}
}
// Patch symptom severity scripts to support French severity terms
// The core module scripts check for "Moderate"/"Severe" in effect.name,
// but French disease compendiums use "Modéré(e)"/"Grave" instead.
this.patch_symptom_severity();
game.wfrp4e.config.effectApplication = {
"actor": "Acteur",

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
MANIFEST-001287
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.449653 7f78b77fe6c0 Recovering log #1285
2026/01/29-22:56:42.460601 7f78b77fe6c0 Delete type=3 #1283
2026/01/29-22:56:42.460653 7f78b77fe6c0 Delete type=0 #1285
2026/01/29-22:57:35.335489 7f78b67fc6c0 Level-0 table #1290: started
2026/01/29-22:57:35.335514 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
2026/01/29-22:57:35.341828 7f78b67fc6c0 Delete type=0 #1288
2026/01/29-22:57:35.348187 7f78b67fc6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.352679 7f766abff6c0 Recovering log #1325
2026/03/22-16:42:23.362109 7f766abff6c0 Delete type=3 #1323
2026/03/22-16:42:23.362175 7f766abff6c0 Delete type=0 #1325
2026/03/22-16:47:51.291160 7f7668bfb6c0 Level-0 table #1330: started
2026/03/22-16:47:51.291261 7f7668bfb6c0 Level-0 table #1330: 0 bytes OK
2026/03/22-16:47:51.297984 7f7668bfb6c0 Delete type=0 #1328
2026/03/22-16:47:51.312338 7f7668bfb6c0 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-20:23:41.937265 7fac3cbfe6c0 Recovering log #1281
2026/01/29-20:23:41.947854 7fac3cbfe6c0 Delete type=3 #1279
2026/01/29-20:23:41.947908 7fac3cbfe6c0 Delete type=0 #1281
2026/01/29-20:30:28.237866 7fa9a6fef6c0 Level-0 table #1286: started
2026/01/29-20:30:28.237897 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
2026/01/29-20:30:28.274449 7fa9a6fef6c0 Delete type=0 #1284
2026/01/29-20:30:28.305057 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.688691 7f76693fc6c0 Recovering log #1321
2026/03/22-16:36:37.776281 7f76693fc6c0 Delete type=3 #1319
2026/03/22-16:36:37.776343 7f76693fc6c0 Delete type=0 #1321
2026/03/22-16:36:43.868900 7f7668bfb6c0 Level-0 table #1326: started
2026/03/22-16:36:43.868921 7f7668bfb6c0 Level-0 table #1326: 0 bytes OK
2026/03/22-16:36:43.928319 7f7668bfb6c0 Delete type=0 #1324
2026/03/22-16:36:43.978234 7f7668bfb6c0 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-001289
MANIFEST-001329

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.465768 7f78b7fff6c0 Recovering log #1287
2026/01/29-22:56:42.475309 7f78b7fff6c0 Delete type=3 #1285
2026/01/29-22:56:42.475364 7f78b7fff6c0 Delete type=0 #1287
2026/01/29-22:57:35.362417 7f78b67fc6c0 Level-0 table #1292: started
2026/01/29-22:57:35.362439 7f78b67fc6c0 Level-0 table #1292: 0 bytes OK
2026/01/29-22:57:35.369784 7f78b67fc6c0 Delete type=0 #1290
2026/01/29-22:57:35.376148 7f78b67fc6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.364546 7f7669bfd6c0 Recovering log #1327
2026/03/22-16:42:23.374494 7f7669bfd6c0 Delete type=3 #1325
2026/03/22-16:42:23.374542 7f7669bfd6c0 Delete type=0 #1327
2026/03/22-16:47:51.298142 7f7668bfb6c0 Level-0 table #1332: started
2026/03/22-16:47:51.298174 7f7668bfb6c0 Level-0 table #1332: 0 bytes OK
2026/03/22-16:47:51.304520 7f7668bfb6c0 Delete type=0 #1330
2026/03/22-16:47:51.312349 7f7668bfb6c0 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-20:23:41.951251 7fac277fe6c0 Recovering log #1283
2026/01/29-20:23:41.961856 7fac277fe6c0 Delete type=3 #1281
2026/01/29-20:23:41.961911 7fac277fe6c0 Delete type=0 #1283
2026/01/29-20:30:28.274586 7fa9a6fef6c0 Level-0 table #1288: started
2026/01/29-20:30:28.274614 7fa9a6fef6c0 Level-0 table #1288: 0 bytes OK
2026/01/29-20:30:28.304924 7fa9a6fef6c0 Delete type=0 #1286
2026/01/29-20:30:28.305066 7fa9a6fef6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.779289 7f766abff6c0 Recovering log #1323
2026/03/22-16:36:37.874995 7f766abff6c0 Delete type=3 #1321
2026/03/22-16:36:37.875069 7f766abff6c0 Delete type=0 #1323
2026/03/22-16:36:43.761021 7f7668bfb6c0 Level-0 table #1328: started
2026/03/22-16:36:43.761049 7f7668bfb6c0 Level-0 table #1328: 0 bytes OK
2026/03/22-16:36:43.810545 7f7668bfb6c0 Delete type=0 #1326
2026/03/22-16:36:43.978196 7f7668bfb6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-001287
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.493062 7f78b6ffd6c0 Recovering log #1285
2026/01/29-22:56:42.503149 7f78b6ffd6c0 Delete type=3 #1283
2026/01/29-22:56:42.503203 7f78b6ffd6c0 Delete type=0 #1285
2026/01/29-22:57:35.355694 7f78b67fc6c0 Level-0 table #1290: started
2026/01/29-22:57:35.355726 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
2026/01/29-22:57:35.362323 7f78b67fc6c0 Delete type=0 #1288
2026/01/29-22:57:35.376141 7f78b67fc6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.390520 7f766abff6c0 Recovering log #1325
2026/03/22-16:42:23.399767 7f766abff6c0 Delete type=3 #1323
2026/03/22-16:42:23.399819 7f766abff6c0 Delete type=0 #1325
2026/03/22-16:47:51.312439 7f7668bfb6c0 Level-0 table #1330: started
2026/03/22-16:47:51.312465 7f7668bfb6c0 Level-0 table #1330: 0 bytes OK
2026/03/22-16:47:51.318661 7f7668bfb6c0 Delete type=0 #1328
2026/03/22-16:47:51.342631 7f7668bfb6c0 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-20:23:41.979254 7fac277fe6c0 Recovering log #1281
2026/01/29-20:23:41.990432 7fac277fe6c0 Delete type=3 #1279
2026/01/29-20:23:41.990499 7fac277fe6c0 Delete type=0 #1281
2026/01/29-20:30:28.407331 7fa9a6fef6c0 Level-0 table #1286: started
2026/01/29-20:30:28.407362 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
2026/01/29-20:30:28.441323 7fa9a6fef6c0 Delete type=0 #1284
2026/01/29-20:30:28.642713 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.968048 7f7669bfd6c0 Recovering log #1321
2026/03/22-16:36:38.054640 7f7669bfd6c0 Delete type=3 #1319
2026/03/22-16:36:38.054710 7f7669bfd6c0 Delete type=0 #1321
2026/03/22-16:36:43.810785 7f7668bfb6c0 Level-0 table #1326: started
2026/03/22-16:36:43.810821 7f7668bfb6c0 Level-0 table #1326: 0 bytes OK
2026/03/22-16:36:43.868750 7f7668bfb6c0 Delete type=0 #1324
2026/03/22-16:36:43.978217 7f7668bfb6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-001287
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.435103 7f78cc9ff6c0 Recovering log #1285
2026/01/29-22:56:42.445358 7f78cc9ff6c0 Delete type=3 #1283
2026/01/29-22:56:42.445414 7f78cc9ff6c0 Delete type=0 #1285
2026/01/29-22:57:35.328574 7f78b67fc6c0 Level-0 table #1290: started
2026/01/29-22:57:35.328607 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
2026/01/29-22:57:35.335392 7f78b67fc6c0 Delete type=0 #1288
2026/01/29-22:57:35.348176 7f78b67fc6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.338520 7f766abff6c0 Recovering log #1325
2026/03/22-16:42:23.349125 7f766abff6c0 Delete type=3 #1323
2026/03/22-16:42:23.349194 7f766abff6c0 Delete type=0 #1325
2026/03/22-16:47:51.284088 7f7668bfb6c0 Level-0 table #1330: started
2026/03/22-16:47:51.284128 7f7668bfb6c0 Level-0 table #1330: 0 bytes OK
2026/03/22-16:47:51.290785 7f7668bfb6c0 Delete type=0 #1328
2026/03/22-16:47:51.312323 7f7668bfb6c0 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-20:23:41.924353 7fac3d3ff6c0 Recovering log #1281
2026/01/29-20:23:41.933825 7fac3d3ff6c0 Delete type=3 #1279
2026/01/29-20:23:41.933878 7fac3d3ff6c0 Delete type=0 #1281
2026/01/29-20:30:28.174496 7fa9a6fef6c0 Level-0 table #1286: started
2026/01/29-20:30:28.174527 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
2026/01/29-20:30:28.203862 7fa9a6fef6c0 Delete type=0 #1284
2026/01/29-20:30:28.305039 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.600423 7f7669bfd6c0 Recovering log #1321
2026/03/22-16:36:37.685656 7f7669bfd6c0 Delete type=3 #1319
2026/03/22-16:36:37.685721 7f7669bfd6c0 Delete type=0 #1321
2026/03/22-16:36:43.698690 7f7668bfb6c0 Level-0 table #1326: started
2026/03/22-16:36:43.698733 7f7668bfb6c0 Level-0 table #1326: 0 bytes OK
2026/03/22-16:36:43.734104 7f7668bfb6c0 Delete type=0 #1324
2026/03/22-16:36:43.760901 7f7668bfb6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-001287
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.421350 7f78b6ffd6c0 Recovering log #1285
2026/01/29-22:56:42.431020 7f78b6ffd6c0 Delete type=3 #1283
2026/01/29-22:56:42.431080 7f78b6ffd6c0 Delete type=0 #1285
2026/01/29-22:57:35.341932 7f78b67fc6c0 Level-0 table #1290: started
2026/01/29-22:57:35.341953 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
2026/01/29-22:57:35.348055 7f78b67fc6c0 Delete type=0 #1288
2026/01/29-22:57:35.348196 7f78b67fc6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.326365 7f766abff6c0 Recovering log #1325
2026/03/22-16:42:23.336381 7f766abff6c0 Delete type=3 #1323
2026/03/22-16:42:23.336459 7f766abff6c0 Delete type=0 #1325
2026/03/22-16:47:51.277178 7f7668bfb6c0 Level-0 table #1330: started
2026/03/22-16:47:51.277256 7f7668bfb6c0 Level-0 table #1330: 0 bytes OK
2026/03/22-16:47:51.283812 7f7668bfb6c0 Delete type=0 #1328
2026/03/22-16:47:51.283961 7f7668bfb6c0 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-20:23:41.909492 7fac277fe6c0 Recovering log #1281
2026/01/29-20:23:41.920402 7fac277fe6c0 Delete type=3 #1279
2026/01/29-20:23:41.920469 7fac277fe6c0 Delete type=0 #1281
2026/01/29-20:30:28.203984 7fa9a6fef6c0 Level-0 table #1286: started
2026/01/29-20:30:28.204007 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
2026/01/29-20:30:28.237718 7fa9a6fef6c0 Delete type=0 #1284
2026/01/29-20:30:28.305049 7fa9a6fef6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.504471 7f76693fc6c0 Recovering log #1321
2026/03/22-16:36:37.597157 7f76693fc6c0 Delete type=3 #1319
2026/03/22-16:36:37.597217 7f76693fc6c0 Delete type=0 #1321
2026/03/22-16:36:43.734281 7f7668bfb6c0 Level-0 table #1326: started
2026/03/22-16:36:43.734305 7f7668bfb6c0 Level-0 table #1326: 0 bytes OK
2026/03/22-16:36:43.760674 7f7668bfb6c0 Delete type=0 #1324
2026/03/22-16:36:43.760915 7f7668bfb6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-000930
MANIFEST-000970

View File

@@ -1,7 +1,7 @@
2026/01/29-22:56:42.478978 7f78b77fe6c0 Recovering log #928
2026/01/29-22:56:42.489559 7f78b77fe6c0 Delete type=3 #926
2026/01/29-22:56:42.489615 7f78b77fe6c0 Delete type=0 #928
2026/01/29-22:57:35.369875 7f78b67fc6c0 Level-0 table #933: started
2026/01/29-22:57:35.369900 7f78b67fc6c0 Level-0 table #933: 0 bytes OK
2026/01/29-22:57:35.376019 7f78b67fc6c0 Delete type=0 #931
2026/01/29-22:57:35.376158 7f78b67fc6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
2026/03/22-16:42:23.377345 7f766abff6c0 Recovering log #968
2026/03/22-16:42:23.388209 7f766abff6c0 Delete type=3 #966
2026/03/22-16:42:23.388272 7f766abff6c0 Delete type=0 #968
2026/03/22-16:47:51.304649 7f7668bfb6c0 Level-0 table #973: started
2026/03/22-16:47:51.304679 7f7668bfb6c0 Level-0 table #973: 0 bytes OK
2026/03/22-16:47:51.312155 7f7668bfb6c0 Delete type=0 #971
2026/03/22-16:47:51.312359 7f7668bfb6c0 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-20:23:41.966258 7fac3cbfe6c0 Recovering log #924
2026/01/29-20:23:41.975859 7fac3cbfe6c0 Delete type=3 #922
2026/01/29-20:23:41.975913 7fac3cbfe6c0 Delete type=0 #924
2026/01/29-20:30:28.375267 7fa9a6fef6c0 Level-0 table #929: started
2026/01/29-20:30:28.375310 7fa9a6fef6c0 Level-0 table #929: 0 bytes OK
2026/01/29-20:30:28.407196 7fa9a6fef6c0 Delete type=0 #927
2026/01/29-20:30:28.642703 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
2026/03/22-16:36:37.879467 7f7669bfd6c0 Recovering log #964
2026/03/22-16:36:37.964554 7f7669bfd6c0 Delete type=3 #962
2026/03/22-16:36:37.964638 7f7669bfd6c0 Delete type=0 #964
2026/03/22-16:36:43.928514 7f7668bfb6c0 Level-0 table #969: started
2026/03/22-16:36:43.928542 7f7668bfb6c0 Level-0 table #969: 0 bytes OK
2026/03/22-16:36:43.977973 7f7668bfb6c0 Delete type=0 #967
2026/03/22-16:36:43.978253 7f7668bfb6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)

View File

@@ -1,11 +1,9 @@
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
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 name = this.item.name
// 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]
tests = tests.replace("the Terrain", terrain)
}
@@ -19,8 +17,7 @@ if (!this.item.name.includes("(") || this.item.system.tests.value.includes("Terr
tundra: "Toundra",
woodlands: "Régions boisées"
}, this.item.img), 1, "Choisissez un Terrain");
if (choice[0])
{
if (choice[0]) {
name = `${name.split("(")[0].trim()} (${choice[0].name})`
tests = tests.replace("Terrain", choice[0].name + " Terrain")
}

View File

@@ -14,7 +14,7 @@
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)
{

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é"}})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
args.actor.addCondition("stunned")
}

View File

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

View File

@@ -10,17 +10,17 @@ scriptData[0].script = `
let chatData = {whisper: ChatMessage.getWhisperRecipients("GM")};
let message = "";
let Blessures = foundry.utils.duplicate(this.actor.status.Blessures);
let wounds = foundry.utils.duplicate(this.actor.status.wounds);
let regenRoll = await new Roll("1d10").roll({allowInteractive : false});
let regen = regenRoll.total;
if (Blessures.value >= Blessures.max)
if (wounds.value >= wounds.max)
return;
if (Blessures.value > 0) {
Blessures.value += Math.floor(regen / 2);
if (Blessures.value > Blessures.max) {
Blessures.value = Blessures.max;
if (wounds.value > 0) {
wounds.value += Math.floor(regen / 2);
if (wounds.value > wounds.max) {
wounds.value = wounds.max;
}
message += \`<b>\${this.actor.name}</b> regagne \${regen} Blessures.\`;
@@ -29,7 +29,7 @@ scriptData[0].script = `
}
} else if (regen >= 8) {
message += \`<b>\${this.actor.name}</b> a obtenu un \${regen} et regagne 1 Blessure.\`;
Blessures.value += 1;
wounds.value += 1;
if (regen === 10) {
message += "<br>De plus, il régénère une Blessure Critique.";
}
@@ -37,7 +37,7 @@ scriptData[0].script = `
message += \`<b>\${this.actor.name}</b> Résultat de régénération de \${regen} - Aucun effet.\`;
}
await this.actor.update({"system.status.wounds": Blessures});
await this.actor.update({"system.status.wounds": wounds});
this.script.message(message, {whisper: ChatMessage.getWhisperRecipients("GM")});
`

View File

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

View File

@@ -22,7 +22,7 @@ let Test = await actor.setupSkill('Calme', {
fields: {difficulty: 'easy'},
characteristic: 'wp',
});
await Test.roll();
await test.roll();
if (!Test.succeeded) {
Test.result.other.push(`<b>${actor.name}</b> est devenu @Condition[Stunned] par la vue.`);

View File

@@ -10,12 +10,12 @@ const test = await this.actor.setupSkill(game.i18n.localize("NAME.Résistance"),
}
});
await Test.roll();
await test.roll();
if (Test.Échoué) {
if (test.failed) {
await this.actor.addEffectItems(bloodyFluxUUID, this.effet);
} else {
const SL = Test.result.SL;
const SL = test.result.SL;
const heal = 1 + SL;
await this.actor.modifyWounds(heal);
this.script.message(`Butcher a soigné ${heal} Blessures.`);

View File

@@ -1,12 +1,12 @@
// Imbibing this substance grants the user the utilisateur d Creature Trait.
const hasutilisateur d = this.actor.has("Insensible à la douleur");
if (hasutilisateur d === undefined)
// Imbibing this substance grants the user the Painless Creature Trait.
const hasPainless = this.actor.has("Insensible à la douleur");
if (hasPainless === undefined)
{
let item = await fromUuid("Compendium.wfrp4e-core.items.wMwSRDmgiF2IdCJr");
let data = item.toObject()
this.actor.createEmbeddedDocuments("Item", [data], {fromEffect: this.effect.id})
this.script.message(
this.script.scriptMessage(
`<p><strong>${this.actor.prototypeToken.name}</strong> a acquis le Trait de Créature Insensible à la douleur. Cet
effet dure une heure, après quoi il se dissipe et l'effet complet
de toutes les blessures du buveur s'abat d'un coup.</p>

View File

@@ -7,9 +7,9 @@ const test = await args.actor.setupSkill(game.i18n.localize("NAME.Résistance"),
}
});
await Test.roll();
await test.roll();
if (Test.Échoué) {
if (test.failed) {
args.actor.addCondition("poisoned");
const speaker = ChatMessage.getSpeaker({actor: args.actor});
this.script.message(`<p>${speaker.alias} a reçu 1 état @Condition[Poisoned] de Venin d'Araignée.</p><p>Les victimes réduites à 0 blessures et qui souffrent d'un état @Condition[Poisoned] de ces flèches deviennent @Condition[Unconcious], mais ne risquent pas la mort à cause des états @Condition[Poisoned] restants comme ce serait normalement le cas.</p>`);

View File

@@ -1,7 +1,7 @@
if (args.test.result.SL < 0)
{
this.script.message(`Gain de ${Math.abs(args.test.result.SL)} Points de Corruption`, {whisper : ChatMessage.getWhisperRecipients("GM")})
if (args.Test.Échoué && this.actor.type == "character")
if (args.test.failed && this.actor.type == "character")
{
this.actor.update({"system.status.corruption.value" : parseInt(this.actor.status.corruption.value) + Math.abs(args.test.result.SL)})
}

View File

@@ -13,9 +13,9 @@ let test = await actor.setupCharacteristic("s", {
}
});
await Test.roll();
if (Test.succeeded) {
let SL = parseInt(Test.result.SL);
await test.roll();
if (test.succeeded) {
let SL = parseInt(test.result.SL);
let name = this.effect.name.replace(/\d+/, rating => parseInt(rating) - SL);
await this.effect.update({name});
}

View File

@@ -1,11 +1,11 @@
let test = await this.actor.setupCharacteristic("s", {appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "difficult"}});
await Test.roll();
await test.roll();
if (Test.Échoué)
if (test.failed)
{
if (Test.isCriticalFumble == "fumble")
if (test.isCriticalFumble == "fumble")
{
return this.script.message(`<strong>${this.actor.name}</strong> meurt alors qu'il est entraîné dans l'Aethyr (à moins qu'il ne dépense un point de Destinée pour éviter cela).`);
}

View File

@@ -22,7 +22,7 @@ let Test = await actor.setupSkill('Calme', {
fields: {difficulty: 'easy'},
characteristic: 'wp',
});
await Test.roll();
await test.roll();
if (!Test.succeeded) {
Test.result.other.push(`<b>${actor.name}</b> est devenu fasciné par la vue et incapable d'effectuer une quelconque action autre que de se déplacer vers la lumière.`);

View File

@@ -1,4 +1,4 @@
if (args.test.characteristicKey == "wp" && args.Test.Échoué && args.Test.result.SL <= -3)
if (args.test.characteristicKey == "wp" && args.test.failed && args.test.result.SL <= -3)
{
this.script.notification("Ajout de A Terre");
this.actor.addCondition("prone")

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
if (Test.succeeded)
await test.roll();
if (test.succeeded)
{
this.script.message("Peut réaliser une Action ou un Déplacement (choisissez-en un)")
}

View File

@@ -1 +1 @@
this.script.scirptMessage(await this.actor.applyBasicDamage(20, {suppressMsg: true});
this.script.scriptMessage(await this.actor.applyBasicDamage(20, {suppressMsg: true}));

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Dodge"), {fields : {difficulty : "hard"}, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
await test.roll();
if (Test.Échoué) {
if (test.failed) {
this.script.scriptMessage(await this.actor.applyBasicDamage(20, {suppressMsg: true}));
this.script.scriptMessage(`${this.actor.name} est victime de @UUID[Compendium.wfrp4e-core.journals.JournalEntry.NS3YGlJQxwTggjRX.JournalEntryPage.WCivInLZrqEtZzF4#drowning-and-suffocation]{Suffocation}`);
}

View File

@@ -1,6 +1,6 @@
if (args.test.characteristicKey == "wp")
{
if (args.Test.Échoué)
if (args.test.failed)
{
this.actor.addSystemEffect("convulsions")
this.script.message(`Test de FM échoué, <b>${this.actor.prototypeToken.name}</b> reçoit @Symptom[Convulsions] pour [[1d10]] heures`)

View File

@@ -9,7 +9,7 @@ if (god)
{
let prayers = await warhammer.utility.findAllItems("prayer", "Chargement des Prières", true, ["system.type.value", "system.god.value"])
let blessings = prayers.filter(p => p.system.god.value.split(",").map(i => i.trim().toLowerCase()).includes(god.toLowerCase()) && p.system.type.value == "blessing")
let configBlessings = await Promise.tout((game.wfrp4e.config.godBlessings[god.toLowerCase()] || []).map(fromUuid));
let configBlessings = await Promise.all((game.wfrp4e.config.godBlessings[god.toLowerCase()] || []).map(fromUuid));
if (god == "Foi Antique")
{
blessings = await ItemDialog.create(prayers.filter(i => i.system.type.value == "blessing"), 6, {text : "Sélectionnez 6 Bénédictions", title : "Béni"})

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
this.script.message(await game.wfrp4e.tables.formatChatRoll("enrage-beast"))
}

View File

@@ -1,7 +1,7 @@
if (this.actor.Species.toLowerCase() != "skaven") {
this.actor.setupCharacteristic("t", {skipTargets: true, appendTitle : ` - Utilise ${this.effect.name}`, fields: { difficulty: "difficult" } }).then(async Test => {
await Test.roll()
if (Test.Échoué)
await test.roll()
if (test.failed)
{
let toughnessLost = Math.ceil(CONFIG.Dice.randomUniform() * 10)
this.actor.update({ "system.characteristics.t.initial": this.actor.characteristics.t.initial - toughnessLost })

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupCharacteristic("wp", {fields: {difficulty : "average"}, skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
let stuns = Math.max(1, Math.abs(Test.result.SL))
let stuns = Math.max(1, Math.abs(test.result.SL))
this.actor.addCondition("stunned", stuns)
}

View File

@@ -5,8 +5,8 @@ if (this.actor.uuid == this.effect.sourceActor.uuid)
if (this.actor.has("À Sang Froid") && !this.actor.hasSystemEffect("nausea")) {
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Résistance"), {appendTitle : `- ${this.effect.name}`})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
let myRoll = await new Roll("1d10").roll({allowInteractive : false});

View File

@@ -5,15 +5,15 @@ let wounds = foundry.utils.duplicate(this.actor.status.wounds)
let regenRoll = await new Roll("1d10").roll({allowInteractive : false});
let regen = regenRoll.total;
if (Blessures.value >= Blessures.max)
if (wounds.value >= wounds.max)
return
if (Blessures.value > 0)
if (wounds.value > 0)
{
Blessures.value += regen
if (Blessures.value > Blessures.max)
wounds.value += regen
if (wounds.value > wounds.max)
{
Blessures.value = Blessures.max
wounds.value = wounds.max
}
message += `<b>${this.actor.name}</b> regagne ${regen} Point de Blessures.`
@@ -25,7 +25,7 @@ if (Blessures.value > 0)
else if (regen >= 8)
{
message += `<b>${this.actor.name}</b> a fait un ${regen} et regagne 1 Point de Blessures.`
Blessures.value += 1
wounds.value += 1
if (regen == 10)
{
message += `<br>De plus, il guérit d'une Blessure Critique.`
@@ -36,5 +36,5 @@ else
message += `<b>${this.actor.name}</b> a fait un ${regen} et ne régénère pas de Point de Blessures.`
}
await this.actor.update({ "system.status.wounds": Blessures })
await this.actor.update({ "system.status.wounds": wounds })
this.script.message(message, { whisper: ChatMessage.getWhisperRecipients("GM") })

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupSkill("Résistance", {fields : {difficulty : "difficult"}, appendTitle : ` - ${this.effect.name}`});
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
await this.actor.addCondition("blinded");
}

View File

@@ -1,7 +1,7 @@
const test = await this.actor.setupCharacteristic("int", {fields: {difficulty: "easy"}, skipTargets: true, appendTitle : ` - ${this.effect.name}`});
await Test.roll();
await test.roll();
if (Test.Échoué) {
if (test.failed) {
this.actor.addCondition('stunned');
}

View File

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

View File

@@ -7,9 +7,9 @@ const test = await this.actor.setupSkill(game.i18n.localize("NAME.Résistance"),
}
});
await Test.roll();
await test.roll();
if (Test.Échoué) {
const SL = Number(Test.result.SL);
if (test.failed) {
const SL = Number(test.result.SL);
this.script.message(`Butcher perd ${SL} dents.`);
}

View File

@@ -1 +1 @@
,args.fields.slBonus++;,args.fields.slBonus++;,args.fields.slBonus++;
args.fields.slBonus++;

View File

@@ -7,8 +7,8 @@ let difficulty = ""
difficulty = "veasy"
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Résistance"), {context : {failure : this.actor.name + " meurt de la Pourriture"}, fields: {difficulty}, appendTitle : " - Pourriture"})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
this.actor.addCondition("dead");
}

View File

@@ -7,8 +7,8 @@ let difficulty = ""
difficulty = "veasy"
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Résistance"), {context : {failure : this.actor.name + " meurt de la Pourriture"}, fields: {difficulty}, appendTitle : " - Pourriture"})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
this.actor.addCondition("dead");
}

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupCharacteristic("i", {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "easy"}})
await Test.roll();
await test.roll();
if (!Test.succeeded)
if (!test.succeeded)
{
this.actor.addCondition("stunned");
}

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupCharacteristic("ag", {fields : {difficulty : "hard"}});
await Test.roll();
await test.roll();
if (Test.Échoué)
if (test.failed)
{
await this.actor.addCondition("bleeding")
await this.actor.addCondition("entangled")

View File

@@ -9,8 +9,8 @@ if (SL >= 3)
let test = await args.actor.setupCharacteristic("wp", {fields: {difficulty}, skipTargets: true, appendTitle : " - " + this.effect.name, context : {failure: "Reçoit un état Sonné"}})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
args.actor.addCondition("stunned");
}

View File

@@ -12,7 +12,7 @@ const test = await this.actor.setupCharacteristic("t", {
}
});
await Test.roll();
if (Test.failure) {
await test.roll();
if (test.failure) {
await this.actor.addCondition("prone");
}

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupSkill("Résistance", {fields : {difficulty : "difficult", appendTitle : ` - ${this.effect.name}`}});
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
await this.actor.addCondition("blinded");
}

View File

@@ -1,8 +1,8 @@
let test = await this.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
await test.roll();
let opposedResult = Test.opposedMessages[0]?.system.opposedHandler?.resultMessage?.system.opposedTest?.result
let opposedResult = test.opposedMessages[0]?.system.opposedHandler?.resultMessage?.system.opposedTest?.result
if (opposedResult?.winner == "attacker")
{

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupCharacteristic("t", { appendTitle: ` - ${this.effect.name}`, fields: { difficulty: "challenging" } })
await Test.roll();
await test.roll();
if (Test.Échoué)
if (test.failed)
{
let ageAdded = Math.ceil(CONFIG.Dice.randomUniform() * 10) + Math.ceil(CONFIG.Dice.randomUniform() * 10)
let ws = Math.ceil(CONFIG.Dice.randomUniform() * 10)

View File

@@ -1,5 +1,5 @@
if (args.opposedTest.result.differenceSL >= 0 && args.opposedTest.result.differenceSL <= 2 && args.opposedTest.result.winner == "attacker")
{
this.script.message(`Becomes lodged in the armour or flesh of the opponent. See @UUID[${this.item.uuid}]{${this.item.name}}.`, speaker : {alias : this.item.name}, {blind: true, whisper : ChatMessage.getWhisperRecipients("GM")})
this.script.message(`Becomes lodged in the armour or flesh of the opponent. See @UUID[${this.item.uuid}]{${this.item.name}}.`, {speaker : {alias : this.item.name}, blind: true, whisper : ChatMessage.getWhisperRecipients("GM")})
}

View File

@@ -1,7 +1,7 @@
let test = await this.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : ` - ${this.effect.name}`})
await Test.roll();
await test.roll();
// Kind of insane but whatever
let opposedResult = Test.opposedMessages[0]?.system.opposedHandler?.resultMessage?.system.opposedTest?.result
let opposedResult = test.opposedMessages[0]?.system.opposedHandler?.resultMessage?.system.opposedTest?.result
return opposedResult?.winner == "attacker";

View File

@@ -1,6 +1,6 @@
if (args.test.characteristicKey == "wp")
{
if (args.Test.Échoué)
if (args.test.failed)
{
let item = await fromUuid("Compendium.wfrp4e-core.items.AGcJl5rHjkyIQBPP")
let data = item.toObject();

View File

@@ -1,9 +1,9 @@
let location = this.item.system.location.key
let test = await this.actor.setupCharacteristic("dex", {context : {failure : `<strong>${this.effect.name}</strong>: Lâchez l'objet!`}, skipTargets: true, appendTitle : " - " + this.effect.name, fields : {difficulty : "average"}})
await Test.roll();
await test.roll();
if (location && Test.Échoué)
if (location && test.failed)
{
let dropped = this.item.system.weaponsAtLocation;

View File

@@ -1,6 +1,6 @@
if (args.test.characteristicKey == "wp")
{
if (args.Test.Échoué)
if (args.test.failed)
{
this.actor.createEmbeddedDocuments("ActiveEffect", [game.wfrp4e.config.symptomEffects["malaise"]])
this.script.message(`Test de FM échoué, <b>${this.actor.prototypeToken.name}</b> gains @Condition[Malaise] for [[1d10]] hours`, {whisper: ChatMessage.getWhisperRecipients("GM")})

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Cool"), {appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "difficult", slBonus : -1 * this.effect.sourceTest.result.SL}})
await Test.roll();
if (Test.succeeded)
await test.roll();
if (test.succeeded)
{
this.script.notification(`Résistance à ${this.effect.name}`);
}

View File

@@ -1,8 +1,8 @@
if (args.totalWoundLoss > 0)
{
args.actor.setupCharacteristic("t", {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields: { difficulty: "difficult" } }).then(async Test => {
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
await args.actor.addCondition("poisoned")
this.script.message(await args.actor.applyBasicDamage(3, {suppressMsg : true, damageType: game.wfrp4e.config.DAMAGE_TYPE.IGNORE_ALL }))

View File

@@ -1,6 +1,6 @@
let test = await this.actor.setupCharacteristic("t", {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "hard"}})
await Test.roll();
if (Test.Échoué)
await test.roll();
if (test.failed)
{
let roll = await new Roll("1d10").roll({allowInteractive : false});
roll.toMessage(this.script.getChatData())

View File

@@ -1,2 +1,2 @@
args.totalWoundLoss = Math.max(0, args.totalWoundLoss - 1)
args.modifiers.other.push({label : this.effect.name, value : -1)
args.modifiers.other.push({label : this.effect.name, value : -1})

View File

@@ -1,8 +1,8 @@
let test = await this.actor.setupCharacteristic("t", {skipTargets: true, appendTitle : ` - ${this.effect.name}`, fields : {difficulty : "vhard"}});
await Test.roll();
await test.roll();
CorruptionMessageModel.createCorruptionMessage("minor", this.script.getChatData())
if (Test.Échoué)
if (test.failed)
{
this.actor.addCondition("unconscious");
}

View File

@@ -1,6 +1,6 @@
if (!this.item.name.includes("(") || this.item.system.tests.value.includes("(Sense)") || 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
// If name already specifies, make sure Tests value reflects that

2009
tools/check-tests-log.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

111
tools/check-tests.js Normal file
View File

@@ -0,0 +1,111 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPTS_DIR = path.join(__dirname, '..', 'scripts');
const LOG_FILE = path.join(__dirname, 'check-tests-log.txt');
const log = [];
function writeLog(message) {
console.log(message);
log.push(message);
}
function getAllJsFiles(dir) {
const files = [];
function traverse(currentDir) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
traverse(fullPath);
} else if (entry.isFile() && entry.name.endsWith('.js')) {
files.push(fullPath);
}
}
}
traverse(dir);
return files;
}
function checkForTests(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
return content.includes('let Tests');
}
// Programme principal
writeLog('='.repeat(60));
writeLog('VÉRIFICATION DE "let Tests" DANS LES SCRIPTS');
writeLog('='.repeat(60));
writeLog(`Date: ${new Date().toISOString()}`);
writeLog(`Répertoire: ${SCRIPTS_DIR}\n`);
const allFiles = getAllJsFiles(SCRIPTS_DIR);
writeLog(`Total de fichiers trouvés: ${allFiles.length}\n`);
const filesWithTests = [];
const filesWithoutTests = [];
allFiles.forEach(filePath => {
const relativePath = path.relative(path.join(__dirname, '..'), filePath);
if (checkForTests(filePath)) {
filesWithTests.push(relativePath);
} else {
filesWithoutTests.push(relativePath);
}
});
// Afficher les résultats
writeLog('=== FICHIERS CONTENANT "let Tests" ===\n');
if (filesWithTests.length > 0) {
filesWithTests.forEach(file => {
writeLog(`${file}`);
});
} else {
writeLog('Aucun fichier trouvé.');
}
writeLog(`\nTotal: ${filesWithTests.length} fichier(s)\n`);
writeLog('='.repeat(60));
writeLog('=== FICHIERS NE CONTENANT PAS "let Tests" ===\n');
if (filesWithoutTests.length > 0) {
filesWithoutTests.forEach(file => {
writeLog(`${file}`);
});
} else {
writeLog('Aucun fichier trouvé.');
}
writeLog(`\nTotal: ${filesWithoutTests.length} fichier(s)\n`);
writeLog('='.repeat(60));
writeLog('RÉSUMÉ');
writeLog('='.repeat(60));
writeLog(`Fichiers avec "let Tests": ${filesWithTests.length}`);
writeLog(`Fichiers sans "let Tests": ${filesWithoutTests.length}`);
writeLog(`Total de fichiers analysés: ${allFiles.length}`);
writeLog('='.repeat(60));
// Sauvegarder le log
fs.writeFileSync(LOG_FILE, log.join('\n'), 'utf-8');
writeLog(`\nLog sauvegardé dans: ${LOG_FILE}`);
// Exporter les résultats en JSON
const results = {
date: new Date().toISOString(),
totalFiles: allFiles.length,
filesWithTests: filesWithTests,
filesWithoutTests: filesWithoutTests
};
const resultsFile = path.join(__dirname, 'check-tests-results.json');
fs.writeFileSync(resultsFile, JSON.stringify(results, null, 2), 'utf-8');
writeLog(`Résultats sauvegardés dans: ${resultsFile}`);