Compare commits

..

12 Commits

Author SHA1 Message Date
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
548 changed files with 20518 additions and 37704 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

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"label": "Items (Lustria)",
"folders": {
"folders": {
"Careers": "Carrières",
"Spells": "Sorts",
"Trappings": "Possessions",
@@ -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

@@ -1,6 +1,6 @@
{
"label": "Items (Old World Bundle II)",
"folders": {
"folders": {
"Lore of the Beasts": "Domaine des Bêtes",
"Lore of Death": "Domaine de la Mort",
"Lore of Fire": "Domaine du Feu",
@@ -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

@@ -1,6 +1,6 @@
{
"label": "Items (Imperial Zoo)",
"folders": {
"folders": {
"Ammunition": "Munitions",
"Careers": "Carrières",
"Prayers": "Prières",
@@ -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": {}

4052
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.3.6",
"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-6.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
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)
}
if ( e.flags?.wfrp4e?.scriptData) {
for (let script of e.flags.wfrp4e.scriptData) {
if (script?.label) {
@@ -424,10 +439,10 @@ Hooks.once('init', () => {
if (trait_fr?.name && trait_fr?.name != name_en) {
trait_fr.name = trait_fr.name || trait_en.name
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;
}
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);
trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim());
}
@@ -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-001279
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.937989 7fac277fe6c0 Recovering log #1277
2026/01/29-10:58:42.947978 7fac277fe6c0 Delete type=3 #1275
2026/01/29-10:58:42.948038 7fac277fe6c0 Delete type=0 #1277
2026/01/29-11:43:15.082893 7fa9a6fef6c0 Level-0 table #1282: started
2026/01/29-11:43:15.082936 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK
2026/01/29-11:43:15.089469 7fa9a6fef6c0 Delete type=0 #1280
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/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-10:52:14.344493 7fac277fe6c0 Recovering log #1273
2026/01/29-10:52:14.354354 7fac277fe6c0 Delete type=3 #1271
2026/01/29-10:52:14.354436 7fac277fe6c0 Delete type=0 #1273
2026/01/29-10:55:44.810919 7fa9a6fef6c0 Level-0 table #1278: started
2026/01/29-10:55:44.810962 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK
2026/01/29-10:55:44.820672 7fa9a6fef6c0 Delete type=0 #1276
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/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-001281
MANIFEST-001329

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.950367 7fac277fe6c0 Recovering log #1279
2026/01/29-10:58:42.959516 7fac277fe6c0 Delete type=3 #1277
2026/01/29-10:58:42.959596 7fac277fe6c0 Delete type=0 #1279
2026/01/29-11:43:14.910167 7fa9a6fef6c0 Level-0 table #1284: started
2026/01/29-11:43:14.910204 7fa9a6fef6c0 Level-0 table #1284: 0 bytes OK
2026/01/29-11:43:14.916676 7fa9a6fef6c0 Delete type=0 #1282
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/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-10:52:14.356741 7fac27fff6c0 Recovering log #1275
2026/01/29-10:52:14.366627 7fac27fff6c0 Delete type=3 #1273
2026/01/29-10:52:14.366686 7fac27fff6c0 Delete type=0 #1275
2026/01/29-10:55:44.821680 7fa9a6fef6c0 Level-0 table #1280: started
2026/01/29-10:55:44.821714 7fa9a6fef6c0 Level-0 table #1280: 0 bytes OK
2026/01/29-10:55:44.835173 7fa9a6fef6c0 Delete type=0 #1278
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/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-001279
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.977033 7fac3cbfe6c0 Recovering log #1277
2026/01/29-10:58:42.986296 7fac3cbfe6c0 Delete type=3 #1275
2026/01/29-10:58:42.986342 7fac3cbfe6c0 Delete type=0 #1277
2026/01/29-11:43:14.924604 7fa9a6fef6c0 Level-0 table #1282: started
2026/01/29-11:43:14.924660 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK
2026/01/29-11:43:14.930959 7fa9a6fef6c0 Delete type=0 #1280
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/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-10:52:14.382212 7fac27fff6c0 Recovering log #1273
2026/01/29-10:52:14.391540 7fac27fff6c0 Delete type=3 #1271
2026/01/29-10:52:14.391602 7fac27fff6c0 Delete type=0 #1273
2026/01/29-10:55:44.848192 7fa9a6fef6c0 Level-0 table #1278: started
2026/01/29-10:55:44.848229 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK
2026/01/29-10:55:44.858087 7fa9a6fef6c0 Delete type=0 #1276
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/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)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-001279
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.926104 7fac277fe6c0 Recovering log #1277
2026/01/29-10:58:42.935779 7fac277fe6c0 Delete type=3 #1275
2026/01/29-10:58:42.935842 7fac277fe6c0 Delete type=0 #1277
2026/01/29-11:43:14.916846 7fa9a6fef6c0 Level-0 table #1282: started
2026/01/29-11:43:14.916889 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK
2026/01/29-11:43:14.924373 7fa9a6fef6c0 Delete type=0 #1280
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/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-10:52:14.331605 7fac3d3ff6c0 Recovering log #1273
2026/01/29-10:52:14.341908 7fac3d3ff6c0 Delete type=3 #1271
2026/01/29-10:52:14.341974 7fac3d3ff6c0 Delete type=0 #1273
2026/01/29-10:55:44.799059 7fa9a6fef6c0 Level-0 table #1278: started
2026/01/29-10:55:44.799092 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK
2026/01/29-10:55:44.809892 7fa9a6fef6c0 Delete type=0 #1276
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/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)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-001279
MANIFEST-001327

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.911172 7fac3cbfe6c0 Recovering log #1277
2026/01/29-10:58:42.922025 7fac3cbfe6c0 Delete type=3 #1275
2026/01/29-10:58:42.922081 7fac3cbfe6c0 Delete type=0 #1277
2026/01/29-11:43:14.903068 7fa9a6fef6c0 Level-0 table #1282: started
2026/01/29-11:43:14.903107 7fa9a6fef6c0 Level-0 table #1282: 0 bytes OK
2026/01/29-11:43:14.910024 7fa9a6fef6c0 Delete type=0 #1280
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/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-10:52:14.318846 7fac277fe6c0 Recovering log #1273
2026/01/29-10:52:14.328479 7fac277fe6c0 Delete type=3 #1271
2026/01/29-10:52:14.328545 7fac277fe6c0 Delete type=0 #1273
2026/01/29-10:55:44.787338 7fa9a6fef6c0 Level-0 table #1278: started
2026/01/29-10:55:44.787395 7fa9a6fef6c0 Level-0 table #1278: 0 bytes OK
2026/01/29-10:55:44.798087 7fa9a6fef6c0 Delete type=0 #1276
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/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-000922
MANIFEST-000970

View File

@@ -1,7 +1,7 @@
2026/01/29-10:58:42.964451 7fac3cbfe6c0 Recovering log #920
2026/01/29-10:58:42.974523 7fac3cbfe6c0 Delete type=3 #918
2026/01/29-10:58:42.974583 7fac3cbfe6c0 Delete type=0 #920
2026/01/29-11:43:14.948296 7fa9a6fef6c0 Level-0 table #925: started
2026/01/29-11:43:14.948359 7fa9a6fef6c0 Level-0 table #925: 0 bytes OK
2026/01/29-11:43:14.954859 7fa9a6fef6c0 Delete type=0 #923
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/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-10:52:14.369127 7fac277fe6c0 Recovering log #916
2026/01/29-10:52:14.379523 7fac277fe6c0 Delete type=3 #914
2026/01/29-10:52:14.379589 7fac277fe6c0 Delete type=0 #916
2026/01/29-10:55:44.836202 7fa9a6fef6c0 Level-0 table #921: started
2026/01/29-10:55:44.836244 7fa9a6fef6c0 Level-0 table #921: 0 bytes OK
2026/01/29-10:55:44.847287 7fa9a6fef6c0 Delete type=0 #919
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/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

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

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
await this.effect.update({duration:{rounds: this.actor.system.characteristics.ws.bonus}});

View File

@@ -0,0 +1,9 @@
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {fields: {difficulty : "average"}, appendTitle : " - Wounded"})
await test.roll();
if (test.failed)
{
fromUuid("Compendium.wfrp4e-core.items.kKccDTGzWzSXCBOb").then(disease => {
this.actor.createEmbeddedDocuments("Item", [disease.toObject()])
this.script.scriptNotification("Gain de " + disease.name)
})
}

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")});
`
@@ -46,4 +46,4 @@ await effet.update({
"system.scriptData": scriptData
});
await trait.update({name});
await trait.update({ name });

View File

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

View File

@@ -0,0 +1,2 @@
const traits = this.actor.itemTypes.trait.filter(t => ["bestial", "skittish"].includes(s.name.toLowerCase()));
trait.system.disabled = true;

View File

@@ -0,0 +1 @@
return !([game.i18n.localize("NAME.AnimalCare"), game.i18n.localize("NAME.CharmAnimal")].includes(args.item?.name) || args.item?.name.includes(game.i18n.localize("NAME.Ride")) || args.item?.name.includes(game.i18n.localize("NAME.AnimalTraining")));

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