Compare commits
9 Commits
foundryvtt
...
v10
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e8237c233 | |||
| df26a699a0 | |||
| 0a1fa37e49 | |||
| 5b7fba4c87 | |||
| d710061eeb | |||
| d6f2feca07 | |||
| f525b6c07a | |||
| c8119601d8 | |||
| d1da169fa3 |
14
foundryvtt-wh4-lang-fr-fr.code-workspace
Normal file
14
foundryvtt-wh4-lang-fr-fr.code-workspace
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../WFRP4e-FoundryVTT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../WarhammerLibrary-FVTT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
@@ -8,8 +8,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"url": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr",
|
"url": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr",
|
||||||
"version": "9.3.2",
|
"version": "9.3.5",
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
|
"wh4_fr.js",
|
||||||
"modules/babele-register.js",
|
"modules/babele-register.js",
|
||||||
"modules/addon-register.js",
|
"modules/addon-register.js",
|
||||||
"modules/import-stat-2.js",
|
"modules/import-stat-2.js",
|
||||||
@@ -119,7 +120,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/raw/v10/module.json",
|
"manifest": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/raw/v10/module.json",
|
||||||
"download": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/archive/foundryvtt-wh4-lang-fr-9-3-2.zip",
|
"download": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr/archive/foundryvtt-wh4-lang-fr-9-3-5.zip",
|
||||||
"id": "wh4-fr-translation",
|
"id": "wh4-fr-translation",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "13",
|
||||||
|
|||||||
@@ -82,39 +82,8 @@ const _patch_up_in_arms = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
/* Manages /auberge command */
|
/* Module /auberge géré par modules/inn/inn-init.js */
|
||||||
const _manage_inn_roll = async (content, msg) => {
|
// L'ancienne implémentation a été migrée vers le module inn pour cohérence avec /voyage
|
||||||
// Split input into arguments
|
|
||||||
let command = content.split(" ").map(function (item) {
|
|
||||||
return item.trim();
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("COMMANDES", command);
|
|
||||||
if (command[0] == "/auberge" && command[1]) {
|
|
||||||
msg["type"] = 0;
|
|
||||||
msg["rollMode"] = "gmroll";
|
|
||||||
let compendium = game.packs.get('wh4-fr-translation.plats-dauberges')
|
|
||||||
game.packs.get(compendium);
|
|
||||||
let rollList = await compendium.getDocuments()
|
|
||||||
for (const element of rollList) {
|
|
||||||
let rollTab = element;
|
|
||||||
console.log("Got compendium...", rollList, rollTab.name);
|
|
||||||
if (rollTab.name.toLowerCase().includes(command[1].toLowerCase())) {
|
|
||||||
let my_rollTable;
|
|
||||||
await compendium.getDocument(rollTab._id).then(mytab => my_rollTable = mytab);
|
|
||||||
my_rollTable.draw({ rollMode: "gmroll" });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (content.includes("/auberge")) {
|
|
||||||
msg["type"] = 0;
|
|
||||||
msg["rollMode"] = "gmroll";
|
|
||||||
msg["content"] = "Syntaxe : /auberge MOT_CLE, avec MOT_CLE parmi:<br>BoissonsBase, BoissonsFortes, Desserts, PlatsCommuns, PlatsExcellents, PlatsMaritimes, PlatsMédiocres, PlatsQualité, PlatsRivières<br>Des raccourcis sont possibles avec une partie du nom : /auberge Base (correspond à BoissonBase) ou /auberge Mari (correspond à PlatsMaritimes), etc."
|
|
||||||
ChatMessage.create(msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
let __eis_tables = {
|
let __eis_tables = {
|
||||||
@@ -262,17 +231,13 @@ const __check_fix_wrong_modules = (chatFlag, patchFinished) => {
|
|||||||
}
|
}
|
||||||
} else if (game.user.isGM && patchFinished) {
|
} else if (game.user.isGM && patchFinished) {
|
||||||
ChatMessage.create({
|
ChatMessage.create({
|
||||||
content: "<div>Les modules WFRP4E ont été <strong>patchés avec succés</strong>. Vous pouvez y aller et que <strong>Shallya vous garde !</strong></div><div>Derniers changements : Support WFRP4E v8.3.X</div></ul>",
|
content: "<div>Les modules WFRP4E ont été <strong>patchés avec succés</strong>. Vous pouvez y aller et que <strong>Shallya vous garde !</strong><div><div>Changements v9.3.5 : <ul><li>Ajout de la commande /voyage !</li><li>Améliorations de la commande /auberge</li><li>Les joueurs doivent désormais pouvoir créer leur persos</li><li>Très grosses mise à jour des scripts d'Effets et de leur traduction</li></ul></div>",
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
whisper: ChatMessage.getWhisperRecipients("GM")
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const __history = [
|
|
||||||
"Nouveautés 9.0.0: <ul><li>Support Foundry v13 et diverses petites corrections !</li></ul>"
|
|
||||||
]
|
|
||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
const convertColumnToMulti = (table) => {
|
const convertColumnToMulti = (table) => {
|
||||||
let columns = table.columns;
|
let columns = table.columns;
|
||||||
@@ -366,14 +331,8 @@ const __add_actors_translation = () => {
|
|||||||
|
|
||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
/* Hook for specific command */
|
/* Hook for specific command - Module /auberge migré vers modules/inn/ */
|
||||||
Hooks.on("chatMessage", (html, content, msg) => {
|
// La commande /auberge est désormais gérée par le module inn-init.js
|
||||||
|
|
||||||
if (content.toLowerCase().includes('auberge')) {
|
|
||||||
_manage_inn_roll(content, msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
/* Additionnal hooks ready */
|
/* Additionnal hooks ready */
|
||||||
@@ -426,7 +385,9 @@ Hooks.on('ready', () => {
|
|||||||
"doom": "Maudit (-40)"
|
"doom": "Maudit (-40)"
|
||||||
}
|
}
|
||||||
|
|
||||||
game.wfrp4e.warnDialog.render(true, { focus: true, left: 20, top: 20 });
|
if (game.user.isGM) {
|
||||||
|
game.wfrp4e.warnDialog.render(true, { focus: true, left: 20, top: 20 });
|
||||||
|
}
|
||||||
//setTimeout( __check_fix_wrong_modules, 2000, true, false);
|
//setTimeout( __check_fix_wrong_modules, 2000, true, false);
|
||||||
setTimeout(__check_fix_wrong_modules, 20000, true, true);
|
setTimeout(__check_fix_wrong_modules, 20000, true, true);
|
||||||
setTimeout(__add_actors_translation, 21000, false, true);
|
setTimeout(__add_actors_translation, 21000, false, true);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export class WH4FRPatchConfig {
|
|||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static translateSkillList(skillList) {
|
static translateSkillList(skillList) {
|
||||||
|
|
||||||
|
if (!skillList || skillList.length == 0) {
|
||||||
|
return skillList;
|
||||||
|
}
|
||||||
let compendiumName = 'wfrp4e-core.items'
|
let compendiumName = 'wfrp4e-core.items'
|
||||||
|
|
||||||
let newList = [];
|
let newList = [];
|
||||||
@@ -43,6 +46,9 @@ export class WH4FRPatchConfig {
|
|||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static translateTalentList(talentList) {
|
static translateTalentList(talentList) {
|
||||||
|
|
||||||
|
if (!talentList || talentList.length == 0) {
|
||||||
|
return talentList;
|
||||||
|
}
|
||||||
let compendiumName = 'wfrp4e-core.items'
|
let compendiumName = 'wfrp4e-core.items'
|
||||||
|
|
||||||
let newList = [];
|
let newList = [];
|
||||||
@@ -76,6 +82,9 @@ export class WH4FRPatchConfig {
|
|||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static patch_subspecies() {
|
static patch_subspecies() {
|
||||||
|
if (!game.wfrp4e?.config?.subspecies) {
|
||||||
|
return
|
||||||
|
}
|
||||||
for (let speciesName in game.wfrp4e.config.subspecies) {
|
for (let speciesName in game.wfrp4e.config.subspecies) {
|
||||||
let subspeciesList = game.wfrp4e.config.subspecies[speciesName];
|
let subspeciesList = game.wfrp4e.config.subspecies[speciesName];
|
||||||
for (let subspeciesName in subspeciesList) {
|
for (let subspeciesName in subspeciesList) {
|
||||||
@@ -92,6 +101,9 @@ export class WH4FRPatchConfig {
|
|||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static patch_species_skills() {
|
static patch_species_skills() {
|
||||||
|
if (!game.wfrp4e?.config?.speciesSkills) {
|
||||||
|
return
|
||||||
|
}
|
||||||
console.log("Patching species skills....");
|
console.log("Patching species skills....");
|
||||||
for (let speciesName in game.wfrp4e.config.speciesSkills) {
|
for (let speciesName in game.wfrp4e.config.speciesSkills) {
|
||||||
let speciesComp = game.wfrp4e.config.speciesSkills[speciesName];
|
let speciesComp = game.wfrp4e.config.speciesSkills[speciesName];
|
||||||
@@ -102,6 +114,9 @@ export class WH4FRPatchConfig {
|
|||||||
|
|
||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static patch_species_talents() {
|
static patch_species_talents() {
|
||||||
|
if (!game.wfrp4e?.config?.speciesTalents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
for (let speciesName in game.wfrp4e.config.speciesTalents) {
|
for (let speciesName in game.wfrp4e.config.speciesTalents) {
|
||||||
let speciesTalents = game.wfrp4e.config.speciesTalents[speciesName];
|
let speciesTalents = game.wfrp4e.config.speciesTalents[speciesName];
|
||||||
game.wfrp4e.config.speciesTalents[speciesName] = this.translateTalentList(speciesTalents);
|
game.wfrp4e.config.speciesTalents[speciesName] = this.translateTalentList(speciesTalents);
|
||||||
@@ -112,7 +127,7 @@ export class WH4FRPatchConfig {
|
|||||||
static patch_career() {
|
static patch_career() {
|
||||||
let compendiumName = 'wfrp4e-core.items'
|
let compendiumName = 'wfrp4e-core.items'
|
||||||
|
|
||||||
if (game.wfrp4e.tables.career) {
|
if (game.wfrp4e?.tables?.career) {
|
||||||
for (let row of game.wfrp4e.tables.career.rows) {
|
for (let row of game.wfrp4e.tables.career.rows) {
|
||||||
for (let key in row) {
|
for (let key in row) {
|
||||||
if (key != "range") {
|
if (key != "range") {
|
||||||
@@ -133,7 +148,10 @@ export class WH4FRPatchConfig {
|
|||||||
/************************************************************************************/
|
/************************************************************************************/
|
||||||
static fixSpeciesTable() {
|
static fixSpeciesTable() {
|
||||||
|
|
||||||
let speciesTable = game.wfrp4e.tables.findTable("species");
|
let speciesTable = game.wfrp4e?.tables?.findTable("species");
|
||||||
|
if (!speciesTable?.results) {
|
||||||
|
return
|
||||||
|
}
|
||||||
let newResults = foundry.utils.duplicate(speciesTable.results);
|
let newResults = foundry.utils.duplicate(speciesTable.results);
|
||||||
for (let result of newResults) {
|
for (let result of newResults) {
|
||||||
result.name = game.i18n.localize(result.name);
|
result.name = game.i18n.localize(result.name);
|
||||||
|
|||||||
410
modules/inn/InnRoller.js
Normal file
410
modules/inn/InnRoller.js
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
/**
|
||||||
|
* InnRoller
|
||||||
|
* Classe de gestion des jets de tables d'auberge pour WFRP4e
|
||||||
|
* Module de traduction française
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class InnRoller {
|
||||||
|
static tableNames = {
|
||||||
|
'boissonsbase': 'BoissonsBase',
|
||||||
|
'boissonsfortes': 'BoissonsFortes',
|
||||||
|
'desserts': 'Desserts',
|
||||||
|
'platscommuns': 'PlatsCommuns',
|
||||||
|
'platsexcellents': 'PlatsExcellents',
|
||||||
|
'platsmaritimes': 'PlatsMaritimes',
|
||||||
|
'platsmediocres': 'PlatsMédiocres',
|
||||||
|
'platsqualite': 'PlatsQualité',
|
||||||
|
'platsrivieres': 'PlatsRivières'
|
||||||
|
};
|
||||||
|
|
||||||
|
static displayNames = {
|
||||||
|
'BoissonsBase': 'Boissons de Base',
|
||||||
|
'BoissonsFortes': 'Boissons Fortes',
|
||||||
|
'Desserts': 'Desserts',
|
||||||
|
'PlatsCommuns': 'Plats Communs',
|
||||||
|
'PlatsExcellents': 'Plats Excellents',
|
||||||
|
'PlatsMaritimes': 'Plats Maritimes',
|
||||||
|
'PlatsMédiocres': 'Plats Médiocres',
|
||||||
|
'PlatsQualité': 'Plats de Qualité',
|
||||||
|
'PlatsRivières': 'Plats de Rivières'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient le nom d'affichage formaté pour une table
|
||||||
|
* @param {String} tableName
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
static getDisplayName(tableName) {
|
||||||
|
return this.displayNames[tableName] || tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalise le nom d'une table (enlève accents, espaces, met en minuscules)
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
static normalizeTableName(name) {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize("NFD")
|
||||||
|
.replace(/[\u0300-\u036f]/g, "")
|
||||||
|
.replace(/\s+/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve la table correspondant au mot-clé
|
||||||
|
* @param {String} keyword
|
||||||
|
* @returns {String|null}
|
||||||
|
*/
|
||||||
|
static findTableByKeyword(keyword) {
|
||||||
|
if (!keyword) return null;
|
||||||
|
|
||||||
|
const normalized = this.normalizeTableName(keyword);
|
||||||
|
|
||||||
|
// Recherche exacte
|
||||||
|
if (this.tableNames[normalized]) {
|
||||||
|
return this.tableNames[normalized];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recherche partielle
|
||||||
|
for (let [key, value] of Object.entries(this.tableNames)) {
|
||||||
|
if (key.includes(normalized) || normalized.includes(key)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lance un jet sur une table d'auberge
|
||||||
|
* @param {String} keyword Mot-clé pour identifier la table
|
||||||
|
*/
|
||||||
|
static async rollInnTable(keyword) {
|
||||||
|
console.log(`InnRoller: rollInnTable appelé avec keyword="${keyword}"`);
|
||||||
|
|
||||||
|
// Si pas de keyword, afficher l'aide
|
||||||
|
if (!keyword) {
|
||||||
|
this.displayHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rechercher la table
|
||||||
|
const tableName = this.findTableByKeyword(keyword);
|
||||||
|
|
||||||
|
if (!tableName) {
|
||||||
|
this.displayHelp();
|
||||||
|
ui.notifications.warn(`Table d'auberge introuvable pour le mot-clé: "${keyword}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`InnRoller: Table trouvée: ${tableName}`);
|
||||||
|
|
||||||
|
// Charger le compendium
|
||||||
|
const compendium = game.packs.get('wh4-fr-translation.plats-dauberges');
|
||||||
|
|
||||||
|
if (!compendium) {
|
||||||
|
ui.notifications.error("Compendium 'plats-dauberges' introuvable");
|
||||||
|
console.error("InnRoller: Compendium wh4-fr-translation.plats-dauberges non trouvé");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les tables
|
||||||
|
const tables = await compendium.getDocuments();
|
||||||
|
|
||||||
|
// Trouver la table correspondante
|
||||||
|
const rollTable = tables.find(t => t.name === tableName);
|
||||||
|
|
||||||
|
if (!rollTable) {
|
||||||
|
ui.notifications.error(`Table "${tableName}" non trouvée dans le compendium`);
|
||||||
|
console.error(`InnRoller: Table ${tableName} non trouvée`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`InnRoller: Jet sur la table ${rollTable.name}`);
|
||||||
|
|
||||||
|
// Effectuer le jet sans affichage automatique
|
||||||
|
try {
|
||||||
|
const roll = await rollTable.draw({ displayChat: false });
|
||||||
|
console.log(`InnRoller: Jet effectué avec succès`, roll);
|
||||||
|
|
||||||
|
// Créer un message personnalisé
|
||||||
|
await this.displayRollResult(rollTable.name, roll);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("InnRoller: Erreur lors du jet:", error);
|
||||||
|
ui.notifications.error("Erreur lors du jet sur la table d'auberge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche le résultat d'un jet de table avec un style personnalisé
|
||||||
|
* @param {String} tableName Nom de la table
|
||||||
|
* @param {Object} rollResult Résultat du jet
|
||||||
|
*/
|
||||||
|
static async displayRollResult(tableName, rollResult) {
|
||||||
|
// Déterminer l'icône en fonction du type de table
|
||||||
|
let icon = "fa-utensils";
|
||||||
|
let category = "Plat";
|
||||||
|
|
||||||
|
if (tableName.toLowerCase().includes('boisson')) {
|
||||||
|
icon = "fa-wine-glass";
|
||||||
|
category = "Boisson";
|
||||||
|
} else if (tableName.toLowerCase().includes('dessert')) {
|
||||||
|
icon = "fa-birthday-cake";
|
||||||
|
category = "Dessert";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraire les informations du résultat
|
||||||
|
const resultText = rollResult.results[0]?.text || "Résultat inconnu";
|
||||||
|
const rollFormula = rollResult.roll?.formula || "1d100";
|
||||||
|
const rollTotal = rollResult.roll?.total || 0;
|
||||||
|
|
||||||
|
// Construire le message HTML simplifié
|
||||||
|
let message = `<div class="wfrp4e-inn-result">`;
|
||||||
|
message += `<div class="message-header">`;
|
||||||
|
message += `<i class="fas ${icon}"></i> `;
|
||||||
|
message += `<span class="flavor-text">${category}: ${tableName}</span>`;
|
||||||
|
message += `</div>`;
|
||||||
|
message += `<div class="inn-dish-name">${resultText}</div>`;
|
||||||
|
message += `<div class="inn-roll-info"><i class="fas fa-dice"></i> ${rollFormula} = ${rollTotal}</div>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
// Créer le message dans le chat
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
speaker: ChatMessage.getSpeaker(),
|
||||||
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche l'aide pour la commande /auberge avec liste cliquable
|
||||||
|
*/
|
||||||
|
static displayHelp() {
|
||||||
|
let message = `<div class="wfrp4e-inn-help">`;
|
||||||
|
message += `<h3><i class="fas fa-utensils"></i> Aide pour /auberge</h3>`;
|
||||||
|
message += `<p><strong>Usage:</strong> <code>/auberge [mot_clé]</code></p>`;
|
||||||
|
|
||||||
|
// Bouton Menu
|
||||||
|
message += `<div style="margin: 0.8em 0;">`;
|
||||||
|
message += `<a class="action-link inn-menu-quick-btn" data-action="clickAubergeMenu" data-quality="menu">`;
|
||||||
|
message += `<i class="fas fa-book-open"></i> Générer un menu complet`;
|
||||||
|
message += `</a>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
message += `<hr>`;
|
||||||
|
|
||||||
|
// Section avec liste cliquable
|
||||||
|
message += `<h4><i class="fas fa-list"></i> Tables disponibles</h4>`;
|
||||||
|
message += `<div class="wfrp4e-inn-table-grid">`;
|
||||||
|
|
||||||
|
const sortedTables = Object.values(this.tableNames).sort();
|
||||||
|
for (let tableName of sortedTables) {
|
||||||
|
const normalized = this.normalizeTableName(tableName);
|
||||||
|
const displayName = this.getDisplayName(tableName);
|
||||||
|
message += `<a class="action-link inn-table-btn" data-action="clickAuberge" data-table="${normalized}">`;
|
||||||
|
message += `<i class="fas fa-dice"></i> ${displayName}`;
|
||||||
|
message += `</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `</div>`;
|
||||||
|
message += `<hr>`;
|
||||||
|
message += `<p style="font-size: 0.9em; margin-top: 0.5em;"><em>Vous pouvez aussi taper <code>/auberge [mot_clé]</code> directement (ex: <code>/auberge base</code>)</em></p>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste toutes les tables disponibles
|
||||||
|
*/
|
||||||
|
static listTables() {
|
||||||
|
let message = `<div class="wfrp4e-inn-list">`;
|
||||||
|
message += `<h3><i class="fas fa-list"></i> Tables d'auberge disponibles</h3>`;
|
||||||
|
message += `<div class="wfrp4e-inn-table-grid">`;
|
||||||
|
|
||||||
|
const sortedTables = Object.values(this.tableNames).sort();
|
||||||
|
for (let tableName of sortedTables) {
|
||||||
|
const normalized = this.normalizeTableName(tableName);
|
||||||
|
const displayName = this.getDisplayName(tableName);
|
||||||
|
message += `<a class="action-link inn-table-btn" data-action="clickAuberge" data-table="${normalized}">`;
|
||||||
|
message += `<i class="fas fa-dice"></i> ${displayName}`;
|
||||||
|
message += `</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `</div>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche le choix de qualité pour générer un menu complet
|
||||||
|
*/
|
||||||
|
static displayMenuChoice() {
|
||||||
|
let message = `<div class="wfrp4e-inn-menu-choice">`;
|
||||||
|
message += `<h3><i class="fas fa-book-open"></i> Menu de l'auberge</h3>`;
|
||||||
|
message += `<p>Choisissez la qualité du menu :</p>`;
|
||||||
|
message += `<div class="inn-menu-buttons">`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="mediocre">`;
|
||||||
|
message += `<i class="fas fa-drumstick-bite"></i> Menu Médiocre`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat médiocre + Boisson de base</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="commun">`;
|
||||||
|
message += `<i class="fas fa-utensils"></i> Menu Commun`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat commun + Boisson de base + Dessert</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="qualite">`;
|
||||||
|
message += `<i class="fas fa-crown"></i> Menu de Qualité`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat de qualité + Boisson forte + Dessert</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="fluvial">`;
|
||||||
|
message += `<i class="fas fa-fish"></i> Menu Fluvial`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat de rivière + Boisson de base + Dessert</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="maritime">`;
|
||||||
|
message += `<i class="fas fa-anchor"></i> Menu Maritime`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat maritime + Boisson forte + Dessert</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `<a class="action-link inn-menu-btn" data-action="clickAubergeMenu" data-quality="excellent">`;
|
||||||
|
message += `<i class="fas fa-gem"></i> Menu Excellent`;
|
||||||
|
message += `<br><span class="inn-menu-desc">Plat excellent + Boisson forte + Dessert</span>`;
|
||||||
|
message += `</a>`;
|
||||||
|
|
||||||
|
message += `</div>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un menu complet selon la qualité choisie
|
||||||
|
* @param {String} quality - 'mediocre', 'commun', ou 'qualite'
|
||||||
|
*/
|
||||||
|
static async generateMenu(quality) {
|
||||||
|
console.log(`InnRoller: generateMenu appelé avec quality="${quality}"`);
|
||||||
|
|
||||||
|
let tables = [];
|
||||||
|
let menuName = "";
|
||||||
|
|
||||||
|
// Définir les tables à tirer selon la qualité
|
||||||
|
switch(quality) {
|
||||||
|
case 'mediocre':
|
||||||
|
menuName = "Menu Médiocre";
|
||||||
|
tables = ['PlatsMédiocres', 'BoissonsBase'];
|
||||||
|
break;
|
||||||
|
case 'commun':
|
||||||
|
menuName = "Menu Commun";
|
||||||
|
tables = ['PlatsCommuns', 'BoissonsBase', 'Desserts'];
|
||||||
|
break;
|
||||||
|
case 'qualite':
|
||||||
|
menuName = "Menu de Qualité";
|
||||||
|
tables = ['PlatsQualité', 'BoissonsFortes', 'Desserts'];
|
||||||
|
break;
|
||||||
|
case 'fluvial':
|
||||||
|
menuName = "Menu Fluvial";
|
||||||
|
tables = ['PlatsRivières', 'BoissonsBase', 'Desserts'];
|
||||||
|
break;
|
||||||
|
case 'maritime':
|
||||||
|
menuName = "Menu Maritime";
|
||||||
|
tables = ['PlatsMaritimes', 'BoissonsFortes', 'Desserts'];
|
||||||
|
break;
|
||||||
|
case 'excellent':
|
||||||
|
menuName = "Menu Excellent";
|
||||||
|
tables = ['PlatsExcellents', 'BoissonsFortes', 'Desserts'];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ui.notifications.error(`Qualité de menu inconnue: ${quality}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger le compendium
|
||||||
|
const compendium = game.packs.get('wh4-fr-translation.plats-dauberges');
|
||||||
|
if (!compendium) {
|
||||||
|
ui.notifications.error("Compendium 'plats-dauberges' introuvable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTables = await compendium.getDocuments();
|
||||||
|
let results = [];
|
||||||
|
|
||||||
|
// Effectuer les jets sur chaque table
|
||||||
|
for (let tableName of tables) {
|
||||||
|
const rollTable = allTables.find(t => t.name === tableName);
|
||||||
|
if (rollTable) {
|
||||||
|
try {
|
||||||
|
const roll = await rollTable.draw({ displayChat: false });
|
||||||
|
const resultText = roll.results[0]?.text || "Résultat inconnu";
|
||||||
|
results.push({
|
||||||
|
category: this.getCategoryName(tableName),
|
||||||
|
name: resultText,
|
||||||
|
tableName: tableName
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`InnRoller: Erreur lors du jet sur ${tableName}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher le menu complet
|
||||||
|
this.displayMenuResult(menuName, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient le nom de catégorie pour une table
|
||||||
|
* @param {String} tableName
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
static getCategoryName(tableName) {
|
||||||
|
if (tableName.includes('Boisson')) return 'Boisson';
|
||||||
|
if (tableName.includes('Dessert')) return 'Dessert';
|
||||||
|
if (tableName.includes('Plat')) return 'Plat';
|
||||||
|
return 'Item';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche le résultat d'un menu complet
|
||||||
|
* @param {String} menuName
|
||||||
|
* @param {Array} results
|
||||||
|
*/
|
||||||
|
static async displayMenuResult(menuName, results) {
|
||||||
|
let message = `<div class="wfrp4e-inn-menu-result">`;
|
||||||
|
message += `<div class="message-header">`;
|
||||||
|
message += `<i class="fas fa-book-open"></i> `;
|
||||||
|
message += `<span class="flavor-text">${menuName}</span>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
message += `<div class="inn-menu-items">`;
|
||||||
|
for (let result of results) {
|
||||||
|
message += `<div class="inn-menu-item">`;
|
||||||
|
let icon = result.category === 'Boisson' ? 'fa-wine-glass' :
|
||||||
|
result.category === 'Dessert' ? 'fa-birthday-cake' : 'fa-utensils';
|
||||||
|
message += `<i class="fas ${icon}"></i> `;
|
||||||
|
message += `<strong>${result.category}:</strong> ${result.name}`;
|
||||||
|
message += `</div>`;
|
||||||
|
}
|
||||||
|
message += `</div>`;
|
||||||
|
message += `</div>`;
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
speaker: ChatMessage.getSpeaker(),
|
||||||
|
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
148
modules/inn/README.md
Normal file
148
modules/inn/README.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Module Inn (Auberge)
|
||||||
|
|
||||||
|
Module de gestion des jets sur les tables d'auberge pour WFRP4e - Traduction française.
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### Commande `/auberge`
|
||||||
|
|
||||||
|
La commande `/auberge` permet d'effectuer des jets sur les tables d'auberge (plats et boissons).
|
||||||
|
|
||||||
|
**Syntaxe :**
|
||||||
|
```
|
||||||
|
/auberge [mot_clé]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemples
|
||||||
|
|
||||||
|
- `/auberge` - Affiche l'aide avec toutes les tables disponibles **et cliquables**
|
||||||
|
- `/auberge help` ou `/auberge aide` - Affiche l'aide avec liste cliquable
|
||||||
|
- `/auberge list` ou `/auberge liste` - Liste toutes les tables avec liens cliquables
|
||||||
|
- `/auberge menu` - **Génère un menu complet** (6 types disponibles)
|
||||||
|
- `/auberge base` - Lance un jet sur la table "BoissonsBase"
|
||||||
|
- `/auberge fortes` - Lance un jet sur la table "BoissonsFortes"
|
||||||
|
- `/auberge mari` - Lance un jet sur la table "PlatsMaritimes"
|
||||||
|
|
||||||
|
> **Note :** Quand vous tapez `/auberge` sans argument, une liste cliquable s'affiche dans le chat. Vous pouvez cliquer directement sur une table pour effectuer un jet.
|
||||||
|
|
||||||
|
### Génération de menus complets
|
||||||
|
|
||||||
|
La commande `/auberge menu` permet de générer automatiquement un menu complet :
|
||||||
|
|
||||||
|
1. Tapez `/auberge menu` dans le chat
|
||||||
|
2. Cliquez sur le type de menu souhaité :
|
||||||
|
- **Menu Médiocre** 🍗 : Plat médiocre + Boisson de base
|
||||||
|
- **Menu Commun** 🍽️ : Plat commun + Boisson de base + Dessert
|
||||||
|
- **Menu de Qualité** 👑 : Plat de qualité + Boisson forte + Dessert
|
||||||
|
- **Menu Fluvial** 🐟 : Plat de rivière + Boisson de base + Dessert
|
||||||
|
- **Menu Maritime** ⚓ : Plat maritime + Boisson forte + Dessert
|
||||||
|
- **Menu Excellent** 💎 : Plat excellent + Boisson forte + Dessert
|
||||||
|
3. Le menu complet est généré automatiquement avec un jet sur chaque table concernée
|
||||||
|
|
||||||
|
### Tables disponibles
|
||||||
|
|
||||||
|
- **BoissonsBase** (`boissonsbase`, `base`)
|
||||||
|
- **BoissonsFortes** (`boissonsfortes`, `fortes`)
|
||||||
|
- **Desserts** (`desserts`)
|
||||||
|
- **PlatsCommuns** (`platscommuns`, `communs`)
|
||||||
|
- **PlatsExcellents** (`platsexcellents`, `excellents`)
|
||||||
|
- **PlatsMaritimes** (`platsmaritimes`, `maritimes`, `mari`)
|
||||||
|
- **PlatsMédiocres** (`platsmediocres`, `mediocres`)
|
||||||
|
- **PlatsQualité** (`platsqualite`, `qualite`)
|
||||||
|
- **PlatsRivières** (`platsrivieres`, `rivieres`)
|
||||||
|
|
||||||
|
### Raccourcis
|
||||||
|
|
||||||
|
Le système accepte des raccourcis et ignore les accents :
|
||||||
|
- `mari` → PlatsMaritimes
|
||||||
|
- `qualité` ou `qualite` → PlatsQualité
|
||||||
|
- `médiocres` ou `mediocres` → PlatsMédiocres
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Le module suit la même architecture que le module TravelV2 (commande `/voyage`) :
|
||||||
|
|
||||||
|
```
|
||||||
|
modules/inn/
|
||||||
|
├── inn-init.js # Initialisation et enregistrement de la commande
|
||||||
|
└── InnRoller.js # Logique métier des jets de tables
|
||||||
|
```
|
||||||
|
|
||||||
|
### Affichage personnalisé
|
||||||
|
|
||||||
|
Les résultats des jets sont affichés avec un **rendu visuel personnalisé** :
|
||||||
|
- 🍷 Icône adaptée au type (boisson, plat, dessert)
|
||||||
|
- 🎨 Carte stylisée avec dégradés et bordures
|
||||||
|
- 🎲 Affichage du jet de dés (formule et total)
|
||||||
|
- 📋 Nom de la table et du plat mis en valeur
|
||||||
|
- 🎉 Message de conclusion thématique
|
||||||
|
|
||||||
|
Le système détecte automatiquement le type de plat/boisson et adapte l'icône :
|
||||||
|
- **Boissons** : 🍷 Verre de vin
|
||||||
|
- **Desserts** : 🎂 Gâteau
|
||||||
|
- **Plats** : 🍴 Couverts
|
||||||
|
|
||||||
|
### Fichiers principaux
|
||||||
|
|
||||||
|
- **inn-init.js** :
|
||||||
|
- Enregistre la commande `/auberge` via `game.wfrp4e.commands`
|
||||||
|
- Gère les hooks pour les clics sur les liens de tables
|
||||||
|
- Expose `game.wfrp4e.inn` pour accès programmatique
|
||||||
|
|
||||||
|
- **InnRoller.js** :
|
||||||
|
- Gestion des jets sur les tables d'auberge
|
||||||
|
- Normalisation des noms de tables
|
||||||
|
- Recherche par mots-clés
|
||||||
|
- Affichage de l'aide et de la liste des tables
|
||||||
|
|
||||||
|
## Intégration
|
||||||
|
|
||||||
|
Le module est initialisé dans `wh4_fr.js` :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { initInn } from './modules/inn/inn-init.js';
|
||||||
|
|
||||||
|
Hooks.once("init", function() {
|
||||||
|
initInn();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dépendances
|
||||||
|
|
||||||
|
- Compendium : `wh4-fr-translation.plats-dauberges`
|
||||||
|
- Système WFRP4e avec support de `game.wfrp4e.commands`
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
La commande `/auberge` est réservée au MJ (GM).
|
||||||
|
|
||||||
|
## API Programmatique
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Afficher l'aide
|
||||||
|
game.wfrp4e.inn.displayHelp();
|
||||||
|
|
||||||
|
// Lister les tables
|
||||||
|
game.wfrp4e.inn.listTables();
|
||||||
|
|
||||||
|
// Effectuer un jet
|
||||||
|
game.wfrp4e.inn.rollInnTable('base');
|
||||||
|
|
||||||
|
// Trouver une table par mot-clé
|
||||||
|
const tableName = game.wfrp4e.inn.findTableByKeyword('mari');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration depuis l'ancien système
|
||||||
|
|
||||||
|
L'ancienne implémentation dans `addon-register.js` (`_manage_inn_roll`) a été remplacée par ce module pour :
|
||||||
|
- Cohérence avec le module TravelV2
|
||||||
|
- Meilleure maintenabilité
|
||||||
|
- Support des commandes WFRP4e natives
|
||||||
|
- Interface utilisateur améliorée
|
||||||
|
|
||||||
|
## Styles CSS
|
||||||
|
|
||||||
|
Les styles sont définis dans `patch-styles.css` avec les classes :
|
||||||
|
- `.wfrp4e-inn-help` - Aide de la commande
|
||||||
|
- `.wfrp4e-inn-list` - Liste des tables
|
||||||
|
- `.wfrp4e-inn-table-list` - Liste avec liens cliquables
|
||||||
93
modules/inn/inn-init.js
Normal file
93
modules/inn/inn-init.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import InnRoller from './InnRoller.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisation du module Inn (Auberge)
|
||||||
|
*/
|
||||||
|
export function initInn() {
|
||||||
|
console.log("Inn: Initialisation du module d'auberge");
|
||||||
|
|
||||||
|
// Hook pour initialiser au démarrage
|
||||||
|
Hooks.once('ready', async () => {
|
||||||
|
console.log("Inn: Module d'auberge prêt");
|
||||||
|
|
||||||
|
// Exposer la classe globalement
|
||||||
|
game.wfrp4e = game.wfrp4e || {};
|
||||||
|
game.wfrp4e.inn = InnRoller;
|
||||||
|
|
||||||
|
console.log("Inn: Classe accessible via game.wfrp4e.inn");
|
||||||
|
|
||||||
|
// Enregistrer la commande dans le système WFRP4e si disponible
|
||||||
|
if (game.wfrp4e?.commands) {
|
||||||
|
console.log("Inn: Enregistrement de la commande /auberge");
|
||||||
|
game.wfrp4e.commands.add({
|
||||||
|
auberge: {
|
||||||
|
description: "Jets sur les tables d'auberge (FR)",
|
||||||
|
args: ["table"],
|
||||||
|
defaultArg: "table",
|
||||||
|
callback: (table) => {
|
||||||
|
// Vérifier que l'utilisateur est GM
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
ui.notifications.warn("La commande /auberge est réservée au MJ.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Inn: Commande /auberge exécutée avec table="${table}"`);
|
||||||
|
|
||||||
|
// Convertir null en undefined
|
||||||
|
table = table || undefined;
|
||||||
|
|
||||||
|
// Si pas de table spécifiée, afficher l'aide
|
||||||
|
if (!table) {
|
||||||
|
InnRoller.displayHelp();
|
||||||
|
} else if (table === 'help' || table === 'aide') {
|
||||||
|
InnRoller.displayHelp();
|
||||||
|
} else if (table === 'list' || table === 'liste') {
|
||||||
|
InnRoller.listTables();
|
||||||
|
} else if (table === 'menu') {
|
||||||
|
InnRoller.displayMenuChoice();
|
||||||
|
} else {
|
||||||
|
InnRoller.rollInnTable(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("Inn: Commande /auberge enregistrée avec succès");
|
||||||
|
} else {
|
||||||
|
console.warn("Inn: game.wfrp4e.commands non disponible");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hook pour gérer les clics sur les liens de tables d'auberge
|
||||||
|
Hooks.on('renderChatMessage', (message, html, data) => {
|
||||||
|
// Ajouter un listener pour les clics sur les liens d'auberge
|
||||||
|
html.find('a[data-action="clickAuberge"]').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const tableKey = $(event.currentTarget).data('table');
|
||||||
|
console.log(`Inn: Clic sur la table ${tableKey}`);
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
InnRoller.rollInnTable(tableKey);
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn("Seul le MJ peut utiliser les tables d'auberge.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajouter un listener pour les clics sur les boutons de menu
|
||||||
|
html.find('a[data-action="clickAubergeMenu"]').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const quality = $(event.currentTarget).data('quality');
|
||||||
|
console.log(`Inn: Clic sur menu de qualité "${quality}"`);
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
// Si quality === "menu", afficher le choix, sinon générer directement
|
||||||
|
if (quality === 'menu') {
|
||||||
|
InnRoller.displayMenuChoice();
|
||||||
|
} else {
|
||||||
|
InnRoller.generateMenu(quality);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn("Seul le MJ peut utiliser les tables d'auberge.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
339
modules/travelv2/TravelDistanceV2.js
Normal file
339
modules/travelv2/TravelDistanceV2.js
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/**
|
||||||
|
* TravelDistanceV2
|
||||||
|
* Classe de gestion du calcul des distances de voyage pour WFRP4e
|
||||||
|
* Version adaptée pour le module de traduction française
|
||||||
|
*/
|
||||||
|
import { PathFinder } from './pathfinding.js';
|
||||||
|
|
||||||
|
export default class TravelDistanceV2 {
|
||||||
|
static roadGraph = null; // Graphe pour les routes terrestres uniquement
|
||||||
|
static waterGraph = null; // Graphe pour les voies fluviales et maritimes
|
||||||
|
static mixedGraph = null; // Graphe combinant tous les modes de transport
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge les données de voyage depuis le fichier JSON
|
||||||
|
*/
|
||||||
|
static async loadTravelData() {
|
||||||
|
try {
|
||||||
|
console.log("TravelV2: Début du chargement des données...");
|
||||||
|
const response = await fetch('modules/wh4-fr-translation/modules/travelv2/travel_data.json');
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.travel_data = await response.json();
|
||||||
|
console.log(`TravelV2: ${this.travel_data.length} routes chargées avec succès`);
|
||||||
|
|
||||||
|
// Construire les 3 graphes pour le pathfinding
|
||||||
|
this.roadGraph = PathFinder.buildGraph(this.travel_data, 'road');
|
||||||
|
this.waterGraph = PathFinder.buildGraph(this.travel_data, 'water');
|
||||||
|
this.mixedGraph = PathFinder.buildGraph(this.travel_data, 'mixed');
|
||||||
|
|
||||||
|
console.log(`TravelV2: Graphe routier: ${Object.keys(this.roadGraph).length} villes`);
|
||||||
|
console.log(`TravelV2: Graphe fluvial/maritime: ${Object.keys(this.waterGraph).length} villes`);
|
||||||
|
console.log(`TravelV2: Graphe mixte: ${Object.keys(this.mixedGraph).length} villes`);
|
||||||
|
|
||||||
|
ui.notifications.info(`TravelV2: ${this.travel_data.length} routes de voyage chargées`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("TravelV2: Erreur lors du chargement des données de voyage:", error);
|
||||||
|
ui.notifications.error("Erreur lors du chargement des données de voyage. Vérifiez la console.");
|
||||||
|
this.travel_data = []; // Initialiser avec un tableau vide pour éviter les erreurs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne une chaîne lisible pour le niveau de danger
|
||||||
|
* @param {String} dangerLevel
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
static dangerToString(dangerLevel) {
|
||||||
|
if (dangerLevel == "") return "Très bas";
|
||||||
|
if (dangerLevel == '!') return "Bas";
|
||||||
|
if (dangerLevel == '!!') return "Moyen";
|
||||||
|
if (dangerLevel == '!!!') return "Élevé";
|
||||||
|
return "Très élevé";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arrondit la durée à une valeur entière ou .5
|
||||||
|
* @param {Number} duration
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
static roundDuration(duration) {
|
||||||
|
let trunc = Math.trunc(duration);
|
||||||
|
let frac = duration - trunc;
|
||||||
|
let adjust = 0;
|
||||||
|
if (frac > 0.75) adjust = 1;
|
||||||
|
else if (frac >= 0.25) adjust = 0.5;
|
||||||
|
return trunc + adjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche les distances de voyage entre deux villes ou liste les destinations
|
||||||
|
* @param {String} fromTown Ville de départ
|
||||||
|
* @param {String} toTown Ville d'arrivée (optionnel)
|
||||||
|
*/
|
||||||
|
static displayTravelDistance(fromTown, toTown) {
|
||||||
|
// Vérifier que les données sont chargées
|
||||||
|
if (!this.travel_data || this.travel_data.length === 0) {
|
||||||
|
ui.notifications.error("Les données de voyage ne sont pas encore chargées. Veuillez patienter...");
|
||||||
|
console.error("TravelV2: travel_data n'est pas chargé");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`TravelV2: displayTravelDistance appelé avec fromTown="${fromTown}", toTown="${toTown}"`);
|
||||||
|
console.log(`TravelV2: fromTown type: ${typeof fromTown}, valeur falsy: ${!fromTown}`);
|
||||||
|
console.log(`TravelV2: toTown type: ${typeof toTown}, valeur falsy: ${!toTown}`);
|
||||||
|
console.log(`TravelV2: ${this.travel_data.length} routes disponibles`);
|
||||||
|
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
console.log("TravelV2: Vérification des conditions...");
|
||||||
|
console.log(`TravelV2: toTown ? ${!!toTown}`);
|
||||||
|
console.log(`TravelV2: fromTown == 'help' ? ${fromTown == 'help'}`);
|
||||||
|
console.log(`TravelV2: fromTown ? ${!!fromTown}`);
|
||||||
|
console.log(`TravelV2: else (pas de fromTown) ? ${!fromTown && !toTown}`);
|
||||||
|
|
||||||
|
if (toTown) {
|
||||||
|
console.log("TravelV2: Branche: Affichage des détails entre deux villes");
|
||||||
|
// Afficher les détails de voyage entre deux villes spécifiques
|
||||||
|
const originalFrom = fromTown;
|
||||||
|
const originalTo = toTown;
|
||||||
|
fromTown = fromTown.toLowerCase();
|
||||||
|
toTown = toTown.toLowerCase();
|
||||||
|
|
||||||
|
// Chercher d'abord une route directe
|
||||||
|
let directRoute = null;
|
||||||
|
for (const travel of this.travel_data) {
|
||||||
|
if (travel.from.toLowerCase() == fromTown && travel.to.toLowerCase() == toTown) {
|
||||||
|
directRoute = travel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directRoute) {
|
||||||
|
// Route directe trouvée - affichage classique avec toutes les options
|
||||||
|
message += this.formatDirectRoute(directRoute);
|
||||||
|
} else {
|
||||||
|
// Pas de route directe - calculer les 3 types de trajets
|
||||||
|
console.log("TravelV2: Pas de route directe, calcul des itinéraires...");
|
||||||
|
|
||||||
|
const mixedPath = PathFinder.dijkstra(this.mixedGraph, originalFrom, originalTo, 'days');
|
||||||
|
const roadPath = PathFinder.dijkstra(this.roadGraph, originalFrom, originalTo, 'days');
|
||||||
|
const waterPath = PathFinder.dijkstra(this.waterGraph, originalFrom, originalTo, 'days');
|
||||||
|
|
||||||
|
if (!mixedPath && !roadPath && !waterPath) {
|
||||||
|
message += `<p><strong>Aucun chemin trouvé entre ${originalFrom} et ${originalTo}</strong></p>`;
|
||||||
|
message += `<p>Il n'existe pas de route reliant ces deux villes dans les données disponibles.</p>`;
|
||||||
|
} else {
|
||||||
|
message += `<div class="voyage-main-title">De ${originalFrom} à ${originalTo}</div>`;
|
||||||
|
message += `<p><em>Différentes options de voyage disponibles :</em></p><hr class="voyage-separator">`;
|
||||||
|
|
||||||
|
// Option 1 : Trajet mixte optimal (le plus rapide)
|
||||||
|
if (mixedPath) {
|
||||||
|
message += this.formatMultiStepRoute(mixedPath, originalFrom, originalTo, 'mixed');
|
||||||
|
message += `<hr class="voyage-separator">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 2 : Trajet 100% terrestre
|
||||||
|
if (roadPath) {
|
||||||
|
message += this.formatMultiStepRoute(roadPath, originalFrom, originalTo, 'road');
|
||||||
|
message += `<hr class="voyage-separator">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 3 : Trajet 100% eau (fleuve/mer)
|
||||||
|
if (waterPath) {
|
||||||
|
message += this.formatMultiStepRoute(waterPath, originalFrom, originalTo, 'water');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fromTown && fromTown == "help") {
|
||||||
|
console.log("TravelV2: Branche: Affichage de l'aide");
|
||||||
|
// Afficher l'aide
|
||||||
|
message += `<p><strong>Aide pour /voyage</strong><br>`;
|
||||||
|
message += `Usage: <code>/voyage [ville_départ] [ville_arrivée]</code><br><br>`;
|
||||||
|
message += `Exemples:<br>`;
|
||||||
|
message += `<code>/voyage</code> - Liste toutes les villes de départ<br>`;
|
||||||
|
message += `<code>/voyage Altdorf</code> - Liste les destinations depuis Altdorf<br>`;
|
||||||
|
message += `<code>/voyage Altdorf Nuln</code> - Affiche les détails de voyage entre Altdorf et Nuln`;
|
||||||
|
message += `</p>`;
|
||||||
|
} else if (fromTown) {
|
||||||
|
console.log("TravelV2: Branche: Liste des destinations depuis une ville");
|
||||||
|
// Lister toutes les destinations possibles depuis une ville (avec pathfinding)
|
||||||
|
const normalizedFrom = PathFinder.findCityInGraph(this.roadGraph, fromTown);
|
||||||
|
|
||||||
|
if (normalizedFrom) {
|
||||||
|
message += `<div class="voyage-destinations-title">Destinations depuis ${normalizedFrom}</div>`;
|
||||||
|
message += `<p><em>Toutes les villes accessibles (routes directes et itinéraires calculés)</em></p>`;
|
||||||
|
|
||||||
|
// Récupérer toutes les villes du graphe sauf la ville de départ
|
||||||
|
const allCities = Object.keys(this.roadGraph)
|
||||||
|
.filter(city => city.toLowerCase() !== normalizedFrom.toLowerCase())
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
// Afficher toutes les destinations
|
||||||
|
for (const city of allCities) {
|
||||||
|
message += `<p><a class="action-link" data-action="clickVoyage" data-from="${normalizedFrom}" data-to="${city}"><i class="fas fa-map-marked-alt"></i> ${city}</a></p>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message += `<p><strong>Ville inconnue: ${fromTown}</strong></p>`;
|
||||||
|
message += `<p>Cette ville n'existe pas dans la base de données.</p>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("TravelV2: Branche: Liste de toutes les villes de départ");
|
||||||
|
// Lister toutes les villes de départ
|
||||||
|
message += `<div class="voyage-destinations-title">Sélectionnez une ville de départ</div>`;
|
||||||
|
let uniqTown = {};
|
||||||
|
|
||||||
|
for (const travel of this.travel_data) {
|
||||||
|
if (uniqTown[travel.from] == undefined) {
|
||||||
|
uniqTown[travel.from] = 1;
|
||||||
|
message += `<p><a class="action-link" data-action="clickVoyage" data-from="${travel.from}"><i class="fas fa-list"></i> ${travel.from}</a></p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`TravelV2: Message généré (longueur: ${message.length})`);
|
||||||
|
|
||||||
|
if (message.length === 0) {
|
||||||
|
console.warn("TravelV2: Aucune donnée trouvée pour les critères fournis");
|
||||||
|
ui.notifications.warn("Aucune route trouvée pour ces critères");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
whisper: [game.user.id], // Afficher uniquement pour le GM qui a lancé la commande
|
||||||
|
speaker: { alias: "Outil de voyage" }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("TravelV2: ChatMessage créé avec succès");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère le clic sur un lien de voyage
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
*/
|
||||||
|
static handleTravelClick(event, target) {
|
||||||
|
let fromTown = target.dataset.from;
|
||||||
|
let toTown = target.dataset.to;
|
||||||
|
TravelDistanceV2.displayTravelDistance(fromTown, toTown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate l'affichage d'une route directe
|
||||||
|
* @param {Object} travel - Données de la route
|
||||||
|
* @returns {String} HTML formaté
|
||||||
|
*/
|
||||||
|
static formatDirectRoute(travel) {
|
||||||
|
let message = `<p><strong>De ${travel.from} à ${travel.to}</strong> (Route directe)`;
|
||||||
|
|
||||||
|
if (travel.road_distance != "") {
|
||||||
|
let road_horse_heavy_days = this.roundDuration(travel.road_days * 0.8);
|
||||||
|
let road_horse_fast_days = this.roundDuration(travel.road_days * 0.65);
|
||||||
|
let road_feet_days = this.roundDuration(travel.road_days * 1.25);
|
||||||
|
let road_danger_string = this.dangerToString(travel.road_danger);
|
||||||
|
let road_danger_feet_string = this.dangerToString(travel.road_danger + "!");
|
||||||
|
|
||||||
|
message += `<br><br><strong>Par route:</strong>`;
|
||||||
|
message += `<br>Distance: ${travel.road_distance} km`;
|
||||||
|
message += `<br>Durée (chariot): ${travel.road_days} jours - Danger: ${road_danger_string}`;
|
||||||
|
message += `<br>Durée (cheval de charge): ${road_horse_heavy_days} jours - Danger: ${road_danger_string}`;
|
||||||
|
message += `<br>Durée (cheval rapide): ${road_horse_fast_days} jours - Danger: ${road_danger_string}`;
|
||||||
|
message += `<br>Durée (à pied): ${road_feet_days} jours - Danger: ${road_danger_feet_string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (travel.river_distance != "") {
|
||||||
|
let river_danger_string = this.dangerToString(travel.river_danger);
|
||||||
|
message += `<br><br><strong>Par rivière:</strong>`;
|
||||||
|
message += `<br>Distance: ${travel.river_distance} km`;
|
||||||
|
message += `<br>Durée: ${travel.river_days} jours - Danger: ${river_danger_string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (travel.sea_distance != "") {
|
||||||
|
let sea_danger_string = this.dangerToString(travel.sea_danger);
|
||||||
|
message += `<br><br><strong>Par mer:</strong>`;
|
||||||
|
message += `<br>Distance: ${travel.sea_distance} km`;
|
||||||
|
message += `<br>Durée: ${travel.sea_days} jours - Danger: ${sea_danger_string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += "</p>";
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate l'affichage d'une route multi-étapes
|
||||||
|
* @param {Object} pathResult - Résultat du pathfinding
|
||||||
|
* @param {String} fromCity - Ville de départ
|
||||||
|
* @param {String} toCity - Ville d'arrivée
|
||||||
|
* @param {String} routeType - Type de route ('mixed', 'road', 'water')
|
||||||
|
* @returns {String} HTML formaté
|
||||||
|
*/
|
||||||
|
static formatMultiStepRoute(pathResult, fromCity, toCity, routeType = 'road') {
|
||||||
|
// Déterminer le titre selon le type
|
||||||
|
let routeTitle = "";
|
||||||
|
|
||||||
|
if (routeType === 'mixed') {
|
||||||
|
routeTitle = "🌟 Itinéraire optimal (tous modes de transport)";
|
||||||
|
} else if (routeType === 'road') {
|
||||||
|
routeTitle = "🛤️ Itinéraire 100% terrestre";
|
||||||
|
} else if (routeType === 'water') {
|
||||||
|
routeTitle = "⛵ Itinéraire 100% fluvial/maritime";
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = `<div class="voyage-route-title">${routeTitle}</div>`;
|
||||||
|
message += `<p><strong>${fromCity} → ${toCity}</strong> (${pathResult.steps} étape${pathResult.steps > 1 ? 's' : ''})`;
|
||||||
|
|
||||||
|
// Résumé du voyage
|
||||||
|
const totalDistance = Math.round(pathResult.totalDistance);
|
||||||
|
const totalDays = Math.round(pathResult.totalDays);
|
||||||
|
const danger_string = this.dangerToString(pathResult.maxDanger);
|
||||||
|
|
||||||
|
// Pour les routes terrestres, afficher les variantes de durée
|
||||||
|
const includesRoad = !pathResult.modesUsed || pathResult.modesUsed.includes('road');
|
||||||
|
|
||||||
|
message += `<br><br><strong>📊 Résumé du voyage:</strong>`;
|
||||||
|
message += `<br>Distance totale: ${totalDistance} km`;
|
||||||
|
|
||||||
|
if (includesRoad) {
|
||||||
|
const road_horse_heavy_days = this.roundDuration(pathResult.totalDays * 0.8);
|
||||||
|
const road_horse_fast_days = this.roundDuration(pathResult.totalDays * 0.65);
|
||||||
|
const road_feet_days = this.roundDuration(pathResult.totalDays * 1.25);
|
||||||
|
const danger_feet_string = this.dangerToString(pathResult.maxDanger + "!");
|
||||||
|
|
||||||
|
message += `<br>Durée (chariot): ${totalDays} jours - Danger max: ${danger_string}`;
|
||||||
|
message += `<br>Durée (cheval de charge): ${road_horse_heavy_days} jours - Danger max: ${danger_string}`;
|
||||||
|
message += `<br>Durée (cheval rapide): ${road_horse_fast_days} jours - Danger max: ${danger_string}`;
|
||||||
|
message += `<br>Durée (à pied): ${road_feet_days} jours - Danger max: ${danger_feet_string}`;
|
||||||
|
} else {
|
||||||
|
message += `<br>Durée totale: ${totalDays} jours - Danger max: ${danger_string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Détails des étapes avec mode de transport
|
||||||
|
message += `<br><br><strong>🗺️ Itinéraire détaillé:</strong>`;
|
||||||
|
for (let i = 0; i < pathResult.path.length; i++) {
|
||||||
|
const step = pathResult.path[i];
|
||||||
|
const stepNum = i + 1;
|
||||||
|
const stepDanger = this.dangerToString(step.danger);
|
||||||
|
|
||||||
|
// Icône selon le mode de transport
|
||||||
|
let modeIcon = "🛤️";
|
||||||
|
let modeName = "route";
|
||||||
|
if (step.mode === 'river') {
|
||||||
|
modeIcon = "🚣";
|
||||||
|
modeName = "fleuve";
|
||||||
|
} else if (step.mode === 'sea') {
|
||||||
|
modeIcon = "⛵";
|
||||||
|
modeName = "mer";
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `<br><br><em>Étape ${stepNum}:</em> ${step.from} → ${step.to}`;
|
||||||
|
message += `<br> ${modeIcon} Par ${modeName}: ${Math.round(step.distance)} km, ${step.days} jour${step.days > 1 ? 's' : ''} - Danger: ${stepDanger}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += "</p>";
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
modules/travelv2/debug-display.js
Normal file
30
modules/travelv2/debug-display.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Script de débogage pour vérifier l'affichage du message
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Vérifier que les données sont chargées
|
||||||
|
console.log("=== DEBUG DISPLAY ===");
|
||||||
|
console.log("travel_data:", game.wfrp4e.travelv2.travel_data);
|
||||||
|
console.log("Nombre de routes:", game.wfrp4e.travelv2.travel_data?.length);
|
||||||
|
|
||||||
|
// Générer le message manuellement comme le fait la fonction
|
||||||
|
let message = "";
|
||||||
|
message += `<h3>Sélectionnez une ville de départ</h3>`;
|
||||||
|
let uniqTown = {};
|
||||||
|
|
||||||
|
for (var travel of game.wfrp4e.travelv2.travel_data) {
|
||||||
|
if (uniqTown[travel.from] == undefined) {
|
||||||
|
uniqTown[travel.from] = 1;
|
||||||
|
message += `<p><a class="action-link" data-action="clickTravel2" data-from="${travel.from}"><i class="fas fa-list"></i> ${travel.from}</a></p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Message généré:", message);
|
||||||
|
console.log("Longueur du message:", message.length);
|
||||||
|
console.log("Nombre de villes uniques:", Object.keys(uniqTown).length);
|
||||||
|
|
||||||
|
// Tester ChatMessage.create
|
||||||
|
ChatMessage.create({
|
||||||
|
content: message,
|
||||||
|
whisper: game.user.isGM ? [] : [game.user.id]
|
||||||
|
});
|
||||||
69
modules/travelv2/diagnostic.js
Normal file
69
modules/travelv2/diagnostic.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Script de diagnostic pour TravelV2
|
||||||
|
*
|
||||||
|
* Copiez-collez ce code dans la console de Foundry VTT (F12)
|
||||||
|
* pour diagnostiquer les problèmes de chargement
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.log("=== Diagnostic TravelV2 ===\n");
|
||||||
|
|
||||||
|
// Test 1: Vérifier si la classe existe
|
||||||
|
console.log("1. Classe TravelDistanceV2 existe ?");
|
||||||
|
if (typeof TravelDistanceV2 !== 'undefined') {
|
||||||
|
console.log(" ✓ OUI - La classe est disponible");
|
||||||
|
} else {
|
||||||
|
console.log(" ✗ NON - La classe n'est pas chargée !");
|
||||||
|
console.log(" → Vérifiez que le module est activé et rechargez (F5)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Vérifier les données
|
||||||
|
console.log("\n2. Données chargées ?");
|
||||||
|
if (typeof TravelDistanceV2 !== 'undefined' && TravelDistanceV2.travel_data) {
|
||||||
|
console.log(` ✓ OUI - ${TravelDistanceV2.travel_data.length} routes chargées`);
|
||||||
|
console.log(` → Première route: ${TravelDistanceV2.travel_data[0]?.from} → ${TravelDistanceV2.travel_data[0]?.to}`);
|
||||||
|
} else {
|
||||||
|
console.log(" ✗ NON - Les données ne sont pas chargées");
|
||||||
|
console.log(" → Essayez de les charger manuellement:");
|
||||||
|
console.log(" → await TravelDistanceV2.loadTravelData()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Tester le chargement manuel
|
||||||
|
console.log("\n3. Test de chargement manuel:");
|
||||||
|
console.log(" Exécutez: await TravelDistanceV2.loadTravelData()");
|
||||||
|
console.log(" Puis vérifiez avec: TravelDistanceV2.travel_data.length");
|
||||||
|
|
||||||
|
// Test 4: Tester le chemin du fichier
|
||||||
|
console.log("\n4. Vérification du chemin du fichier:");
|
||||||
|
const path = 'modules/foundryvtt-wh4-lang-fr-fr/modules/travelv2/travel_data.json';
|
||||||
|
console.log(` Chemin: ${path}`);
|
||||||
|
console.log(" Test de fetch...");
|
||||||
|
fetch(path)
|
||||||
|
.then(response => {
|
||||||
|
console.log(` ✓ Fichier accessible - Status: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log(` ✓ JSON valide - ${data.length} routes trouvées`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(` ✗ Erreur: ${error.message}`);
|
||||||
|
console.log(" → Vérifiez que le fichier travel_data.json existe bien");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 5: Afficher l'état du module
|
||||||
|
console.log("\n5. État du module:");
|
||||||
|
console.log(` game.modules = ${game.modules ? 'Disponible' : 'Non disponible'}`);
|
||||||
|
const frModule = game.modules.get('foundryvtt-wh4-lang-fr-fr');
|
||||||
|
if (frModule) {
|
||||||
|
console.log(` ✓ Module trouvé: ${frModule.title}`);
|
||||||
|
console.log(` → Actif: ${frModule.active}`);
|
||||||
|
} else {
|
||||||
|
console.log(" ✗ Module 'foundryvtt-wh4-lang-fr-fr' non trouvé");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n=== Fin du diagnostic ===");
|
||||||
|
console.log("\nCommandes utiles:");
|
||||||
|
console.log("• Charger les données: await TravelDistanceV2.loadTravelData()");
|
||||||
|
console.log("• Vérifier les données: TravelDistanceV2.travel_data");
|
||||||
|
console.log("• Tester l'affichage: TravelDistanceV2.displayTravelDistance()");
|
||||||
|
console.log("• Tester avec ville: TravelDistanceV2.displayTravelDistance('Altdorf')");
|
||||||
8
modules/travelv2/index.js
Normal file
8
modules/travelv2/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Index du module TravelV2
|
||||||
|
*
|
||||||
|
* Ce fichier exporte tous les composants du module TravelV2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as TravelDistanceV2 } from './TravelDistanceV2.js';
|
||||||
|
export { initTravelV2 } from './travelv2-init.js';
|
||||||
218
modules/travelv2/pathfinding.js
Normal file
218
modules/travelv2/pathfinding.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* Algorithmes de calcul de plus court chemin pour les voyages
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe pour le calcul de plus court chemin (algorithme de Dijkstra)
|
||||||
|
*/
|
||||||
|
export class PathFinder {
|
||||||
|
/**
|
||||||
|
* Construit un graphe à partir des données de voyage
|
||||||
|
* @param {Array} travelData - Données de voyage
|
||||||
|
* @param {String|Array} modes - Mode(s) de transport ('road', 'river', 'sea', ['road', 'river', 'sea'], 'water')
|
||||||
|
* @returns {Object} Graphe avec adjacence et poids
|
||||||
|
*/
|
||||||
|
static buildGraph(travelData, modes = 'road') {
|
||||||
|
const graph = {};
|
||||||
|
|
||||||
|
// Normaliser modes en tableau
|
||||||
|
let modeList = [];
|
||||||
|
if (modes === 'water') {
|
||||||
|
modeList = ['river', 'sea'];
|
||||||
|
} else if (modes === 'mixed') {
|
||||||
|
modeList = ['road', 'river', 'sea'];
|
||||||
|
} else if (Array.isArray(modes)) {
|
||||||
|
modeList = modes;
|
||||||
|
} else {
|
||||||
|
modeList = [modes];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const route of travelData) {
|
||||||
|
const from = route.from;
|
||||||
|
const to = route.to;
|
||||||
|
|
||||||
|
// Initialiser les nœuds
|
||||||
|
if (!graph[from]) {
|
||||||
|
graph[from] = [];
|
||||||
|
}
|
||||||
|
if (!graph[to]) {
|
||||||
|
graph[to] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour chaque mode de transport disponible
|
||||||
|
for (const mode of modeList) {
|
||||||
|
const distanceKey = `${mode}_distance`;
|
||||||
|
const daysKey = `${mode}_days`;
|
||||||
|
const distance = route[distanceKey];
|
||||||
|
const days = route[daysKey];
|
||||||
|
|
||||||
|
// Ignorer les routes sans ce mode de transport
|
||||||
|
if (!distance || distance === "" || !days || days === "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter les arêtes (bidirectionnelles)
|
||||||
|
graph[from].push({
|
||||||
|
destination: to,
|
||||||
|
distance: parseFloat(distance),
|
||||||
|
days: parseFloat(days),
|
||||||
|
danger: route[`${mode}_danger`] || "",
|
||||||
|
mode: mode
|
||||||
|
});
|
||||||
|
|
||||||
|
graph[to].push({
|
||||||
|
destination: from,
|
||||||
|
distance: parseFloat(distance),
|
||||||
|
days: parseFloat(days),
|
||||||
|
danger: route[`${mode}_danger`] || "",
|
||||||
|
mode: mode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Algorithme de Dijkstra pour trouver le plus court chemin
|
||||||
|
* @param {Object} graph - Graphe d'adjacence
|
||||||
|
* @param {String} start - Ville de départ
|
||||||
|
* @param {String} end - Ville d'arrivée
|
||||||
|
* @param {String} metric - Métrique à minimiser ('distance' ou 'days')
|
||||||
|
* @returns {Object|null} Chemin trouvé avec détails ou null si pas de chemin
|
||||||
|
*/
|
||||||
|
static dijkstra(graph, start, end, metric = 'days') {
|
||||||
|
// Normaliser les noms de villes
|
||||||
|
const normalizedStart = this.findCityInGraph(graph, start);
|
||||||
|
const normalizedEnd = this.findCityInGraph(graph, end);
|
||||||
|
|
||||||
|
if (!normalizedStart || !normalizedEnd) {
|
||||||
|
console.warn(`PathFinder: Ville non trouvée - start: ${start}, end: ${end}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation
|
||||||
|
const distances = {};
|
||||||
|
const previous = {};
|
||||||
|
const visited = new Set();
|
||||||
|
const queue = [];
|
||||||
|
|
||||||
|
// Initialiser toutes les distances à l'infini
|
||||||
|
for (const node in graph) {
|
||||||
|
distances[node] = Infinity;
|
||||||
|
previous[node] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
distances[normalizedStart] = 0;
|
||||||
|
queue.push({ node: normalizedStart, distance: 0 });
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
// Trouver le nœud avec la plus petite distance
|
||||||
|
queue.sort((a, b) => a.distance - b.distance);
|
||||||
|
const { node: current } = queue.shift();
|
||||||
|
|
||||||
|
if (visited.has(current)) continue;
|
||||||
|
visited.add(current);
|
||||||
|
|
||||||
|
// Si on a atteint la destination
|
||||||
|
if (current === normalizedEnd) {
|
||||||
|
return this.reconstructPath(previous, normalizedStart, normalizedEnd, graph, metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explorer les voisins
|
||||||
|
const neighbors = graph[current] || [];
|
||||||
|
for (const neighbor of neighbors) {
|
||||||
|
if (visited.has(neighbor.destination)) continue;
|
||||||
|
|
||||||
|
const weight = neighbor[metric]; // 'distance' ou 'days'
|
||||||
|
const newDistance = distances[current] + weight;
|
||||||
|
|
||||||
|
if (newDistance < distances[neighbor.destination]) {
|
||||||
|
distances[neighbor.destination] = newDistance;
|
||||||
|
previous[neighbor.destination] = {
|
||||||
|
from: current,
|
||||||
|
edge: neighbor
|
||||||
|
};
|
||||||
|
queue.push({ node: neighbor.destination, distance: newDistance });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pas de chemin trouvé
|
||||||
|
console.warn(`PathFinder: Aucun chemin trouvé entre ${start} et ${end}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve une ville dans le graphe (insensible à la casse)
|
||||||
|
* @param {Object} graph - Graphe
|
||||||
|
* @param {String} cityName - Nom de la ville
|
||||||
|
* @returns {String|null} Nom normalisé de la ville ou null
|
||||||
|
*/
|
||||||
|
static findCityInGraph(graph, cityName) {
|
||||||
|
const lowerName = cityName.toLowerCase();
|
||||||
|
for (const city in graph) {
|
||||||
|
if (city.toLowerCase() === lowerName) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconstruit le chemin à partir des prédécesseurs
|
||||||
|
* @param {Object} previous - Map des prédécesseurs
|
||||||
|
* @param {String} start - Ville de départ
|
||||||
|
* @param {String} end - Ville d'arrivée
|
||||||
|
* @param {Object} graph - Graphe d'adjacence
|
||||||
|
* @param {String} metric - Métrique utilisée
|
||||||
|
* @returns {Object} Détails du chemin
|
||||||
|
*/
|
||||||
|
static reconstructPath(previous, start, end, graph, metric) {
|
||||||
|
const path = [];
|
||||||
|
let current = end;
|
||||||
|
let totalDistance = 0;
|
||||||
|
let totalDays = 0;
|
||||||
|
let maxDanger = "";
|
||||||
|
const modesUsed = new Set();
|
||||||
|
|
||||||
|
// Reconstruire le chemin en remontant
|
||||||
|
while (current !== start) {
|
||||||
|
const prev = previous[current];
|
||||||
|
if (!prev) break;
|
||||||
|
|
||||||
|
const mode = prev.edge.mode || 'road';
|
||||||
|
modesUsed.add(mode);
|
||||||
|
|
||||||
|
path.unshift({
|
||||||
|
from: prev.from,
|
||||||
|
to: current,
|
||||||
|
distance: prev.edge.distance,
|
||||||
|
days: prev.edge.days,
|
||||||
|
danger: prev.edge.danger,
|
||||||
|
mode: mode
|
||||||
|
});
|
||||||
|
|
||||||
|
totalDistance += prev.edge.distance;
|
||||||
|
totalDays += prev.edge.days;
|
||||||
|
|
||||||
|
// Calculer le danger maximum
|
||||||
|
const dangerLevel = (prev.edge.danger || "").length;
|
||||||
|
const currentMaxLevel = maxDanger.length;
|
||||||
|
if (dangerLevel > currentMaxLevel) {
|
||||||
|
maxDanger = prev.edge.danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = prev.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: path,
|
||||||
|
totalDistance: totalDistance,
|
||||||
|
totalDays: totalDays,
|
||||||
|
maxDanger: maxDanger,
|
||||||
|
steps: path.length,
|
||||||
|
modesUsed: Array.from(modesUsed)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
53
modules/travelv2/test.js
Normal file
53
modules/travelv2/test.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Fichier de test pour le module TravelV2
|
||||||
|
*
|
||||||
|
* Pour tester le module dans la console de développement de Foundry VTT:
|
||||||
|
*
|
||||||
|
* 1. Charger le module
|
||||||
|
* 2. Ouvrir la console (F12)
|
||||||
|
* 3. Exécuter ces tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Test 1: Vérifier que la classe est chargée
|
||||||
|
console.log("Test 1: Classe TravelDistanceV2 disponible?");
|
||||||
|
console.log(typeof TravelDistanceV2 !== 'undefined' ? "✓ OK" : "✗ ÉCHEC");
|
||||||
|
|
||||||
|
// Test 2: Vérifier que les données sont chargées
|
||||||
|
console.log("\nTest 2: Données de voyage chargées?");
|
||||||
|
console.log(TravelDistanceV2.travel_data ? "✓ OK - " + TravelDistanceV2.travel_data.length + " routes trouvées" : "✗ ÉCHEC");
|
||||||
|
|
||||||
|
// Test 3: Tester dangerToString
|
||||||
|
console.log("\nTest 3: Test de dangerToString");
|
||||||
|
console.log("'' -> " + TravelDistanceV2.dangerToString(""));
|
||||||
|
console.log("'!' -> " + TravelDistanceV2.dangerToString("!"));
|
||||||
|
console.log("'!!' -> " + TravelDistanceV2.dangerToString("!!"));
|
||||||
|
console.log("'!!!' -> " + TravelDistanceV2.dangerToString("!!!"));
|
||||||
|
|
||||||
|
// Test 4: Tester roundDuration
|
||||||
|
console.log("\nTest 4: Test de roundDuration");
|
||||||
|
console.log("22.1 -> " + TravelDistanceV2.roundDuration(22.1));
|
||||||
|
console.log("22.3 -> " + TravelDistanceV2.roundDuration(22.3));
|
||||||
|
console.log("22.5 -> " + TravelDistanceV2.roundDuration(22.5));
|
||||||
|
console.log("22.8 -> " + TravelDistanceV2.roundDuration(22.8));
|
||||||
|
|
||||||
|
// Test 5: Tester displayTravelDistance avec une ville
|
||||||
|
console.log("\nTest 5: Affichage des destinations depuis Altdorf");
|
||||||
|
// TravelDistanceV2.displayTravelDistance("Altdorf");
|
||||||
|
console.log("Exécutez manuellement: TravelDistanceV2.displayTravelDistance('Altdorf')");
|
||||||
|
|
||||||
|
// Test 6: Tester displayTravelDistance avec deux villes
|
||||||
|
console.log("\nTest 6: Affichage du trajet Altdorf -> Nuln");
|
||||||
|
// TravelDistanceV2.displayTravelDistance("Altdorf", "Nuln");
|
||||||
|
console.log("Exécutez manuellement: TravelDistanceV2.displayTravelDistance('Altdorf', 'Nuln')");
|
||||||
|
|
||||||
|
// Test 7: Tester la commande help
|
||||||
|
console.log("\nTest 7: Affichage de l'aide");
|
||||||
|
// TravelDistanceV2.displayTravelDistance("help");
|
||||||
|
console.log("Exécutez manuellement: TravelDistanceV2.displayTravelDistance('help')");
|
||||||
|
|
||||||
|
console.log("\n=== Tests terminés ===");
|
||||||
|
console.log("\nPour tester la commande complète, tapez dans le chat:");
|
||||||
|
console.log("/travel2");
|
||||||
|
console.log("/travel2 Altdorf");
|
||||||
|
console.log("/travel2 Altdorf Nuln");
|
||||||
|
console.log("/travel2 help");
|
||||||
2745
modules/travelv2/travel_data.json
Normal file
2745
modules/travelv2/travel_data.json
Normal file
File diff suppressed because it is too large
Load Diff
67
modules/travelv2/travelv2-init.js
Normal file
67
modules/travelv2/travelv2-init.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import TravelDistanceV2 from './TravelDistanceV2.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisation du module TravelV2
|
||||||
|
*/
|
||||||
|
export function initTravelV2() {
|
||||||
|
console.log("TravelV2: Initialisation du module de voyage");
|
||||||
|
|
||||||
|
// Hook pour charger les données au démarrage
|
||||||
|
Hooks.once('ready', async () => {
|
||||||
|
console.log("TravelV2: Chargement des données de voyage");
|
||||||
|
|
||||||
|
// Exposer la classe globalement pour accès depuis la console
|
||||||
|
game.wfrp4e = game.wfrp4e || {};
|
||||||
|
game.wfrp4e.travelv2 = TravelDistanceV2;
|
||||||
|
|
||||||
|
await TravelDistanceV2.loadTravelData();
|
||||||
|
|
||||||
|
console.log("TravelV2: Classe accessible via game.wfrp4e.travelv2");
|
||||||
|
|
||||||
|
// Enregistrer la commande dans le système WFRP4e si disponible
|
||||||
|
if (game.wfrp4e?.commands) {
|
||||||
|
console.log("TravelV2: Enregistrement de la commande /voyage");
|
||||||
|
game.wfrp4e.commands.add({
|
||||||
|
voyage: {
|
||||||
|
description: "Outil de calcul de distances de voyage (FR)",
|
||||||
|
args: ["from", "to"],
|
||||||
|
defaultArg: "from",
|
||||||
|
callback: (from, to) => {
|
||||||
|
// Vérifier que l'utilisateur est GM
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
ui.notifications.warn("La commande /voyage est réservée au MJ.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`TravelV2: Commande /voyage exécutée`);
|
||||||
|
console.log(`TravelV2: from =`, from, `(type: ${typeof from})`);
|
||||||
|
console.log(`TravelV2: to =`, to, `(type: ${typeof to})`);
|
||||||
|
console.log(`TravelV2: from === null ?`, from === null);
|
||||||
|
console.log(`TravelV2: to === null ?`, to === null);
|
||||||
|
|
||||||
|
// Convertir null en undefined pour que la logique fonctionne
|
||||||
|
from = from || undefined;
|
||||||
|
to = to || undefined;
|
||||||
|
|
||||||
|
TravelDistanceV2.displayTravelDistance(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("TravelV2: Commande /voyage enregistrée avec succès");
|
||||||
|
} else {
|
||||||
|
console.warn("TravelV2: game.wfrp4e.commands non disponible");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hook pour ajouter un gestionnaire de clics sur les liens de voyage
|
||||||
|
Hooks.on('renderChatMessage', (message, html, data) => {
|
||||||
|
// Ajouter un listener pour les clics sur les liens de voyage
|
||||||
|
html.find('a[data-action="clickVoyage"]').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
TravelDistanceV2.handleTravelClick(event, target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("TravelV2: Module de voyage initialisé");
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
MANIFEST-001210
|
MANIFEST-001259
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.026251 7f189ffff6c0 Recovering log #1208
|
2026/01/07-15:26:43.527176 7f93eaffd6c0 Recovering log #1257
|
||||||
2025/10/13-20:51:30.036172 7f189ffff6c0 Delete type=3 #1206
|
2026/01/07-15:26:43.536993 7f93eaffd6c0 Delete type=3 #1255
|
||||||
2025/10/13-20:51:30.036225 7f189ffff6c0 Delete type=0 #1208
|
2026/01/07-15:26:43.537037 7f93eaffd6c0 Delete type=0 #1257
|
||||||
2025/10/13-20:58:55.958283 7f189e7fc6c0 Level-0 table #1213: started
|
2026/01/07-15:53:28.412551 7f93e9ffb6c0 Level-0 table #1262: started
|
||||||
2025/10/13-20:58:55.958313 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
2026/01/07-15:53:28.412580 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||||
2025/10/13-20:58:55.964593 7f189e7fc6c0 Delete type=0 #1211
|
2026/01/07-15:53:28.419118 7f93e9ffb6c0 Delete type=0 #1260
|
||||||
2025/10/13-20:58:55.985538 7f189e7fc6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.432727 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.059843 7f189f7fe6c0 Recovering log #1204
|
2026/01/07-15:09:19.276373 7f93eaffd6c0 Recovering log #1253
|
||||||
2025/10/13-20:40:04.069741 7f189f7fe6c0 Delete type=3 #1202
|
2026/01/07-15:09:19.291800 7f93eaffd6c0 Delete type=3 #1251
|
||||||
2025/10/13-20:40:04.069791 7f189f7fe6c0 Delete type=0 #1204
|
2026/01/07-15:09:19.291847 7f93eaffd6c0 Delete type=0 #1253
|
||||||
2025/10/13-20:45:59.586645 7f189e7fc6c0 Level-0 table #1209: started
|
2026/01/07-15:13:49.642315 7f93e9ffb6c0 Level-0 table #1258: started
|
||||||
2025/10/13-20:45:59.586692 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
2026/01/07-15:13:49.642358 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||||
2025/10/13-20:45:59.620022 7f189e7fc6c0 Delete type=0 #1207
|
2026/01/07-15:13:49.649460 7f93e9ffb6c0 Delete type=0 #1256
|
||||||
2025/10/13-20:45:59.620203 7f189e7fc6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.662442 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/aides-de-jeu-fr/MANIFEST-001259
Normal file
BIN
packs/aides-de-jeu-fr/MANIFEST-001259
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-001212
|
MANIFEST-001261
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.039766 7f189ffff6c0 Recovering log #1210
|
2026/01/07-15:26:43.539401 7f93ebfff6c0 Recovering log #1259
|
||||||
2025/10/13-20:51:30.049183 7f189ffff6c0 Delete type=3 #1208
|
2026/01/07-15:26:43.549699 7f93ebfff6c0 Delete type=3 #1257
|
||||||
2025/10/13-20:51:30.049237 7f189ffff6c0 Delete type=0 #1210
|
2026/01/07-15:26:43.549758 7f93ebfff6c0 Delete type=0 #1259
|
||||||
2025/10/13-20:58:55.972493 7f189e7fc6c0 Level-0 table #1215: started
|
2026/01/07-15:53:28.419273 7f93e9ffb6c0 Level-0 table #1264: started
|
||||||
2025/10/13-20:58:55.972514 7f189e7fc6c0 Level-0 table #1215: 0 bytes OK
|
2026/01/07-15:53:28.419304 7f93e9ffb6c0 Level-0 table #1264: 0 bytes OK
|
||||||
2025/10/13-20:58:55.978671 7f189e7fc6c0 Delete type=0 #1213
|
2026/01/07-15:53:28.426268 7f93e9ffb6c0 Delete type=0 #1262
|
||||||
2025/10/13-20:58:55.985570 7f189e7fc6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.440100 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.073059 7f189f7fe6c0 Recovering log #1206
|
2026/01/07-15:09:19.293766 7f93ea7fc6c0 Recovering log #1255
|
||||||
2025/10/13-20:40:04.082320 7f189f7fe6c0 Delete type=3 #1204
|
2026/01/07-15:09:19.310998 7f93ea7fc6c0 Delete type=3 #1253
|
||||||
2025/10/13-20:40:04.082396 7f189f7fe6c0 Delete type=0 #1206
|
2026/01/07-15:09:19.311072 7f93ea7fc6c0 Delete type=0 #1255
|
||||||
2025/10/13-20:45:59.620883 7f189e7fc6c0 Level-0 table #1211: started
|
2026/01/07-15:13:49.675722 7f93e9ffb6c0 Level-0 table #1260: started
|
||||||
2025/10/13-20:45:59.620913 7f189e7fc6c0 Level-0 table #1211: 0 bytes OK
|
2026/01/07-15:13:49.675750 7f93e9ffb6c0 Level-0 table #1260: 0 bytes OK
|
||||||
2025/10/13-20:45:59.655200 7f189e7fc6c0 Delete type=0 #1209
|
2026/01/07-15:13:49.682225 7f93e9ffb6c0 Delete type=0 #1258
|
||||||
2025/10/13-20:45:59.655399 7f189e7fc6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.688884 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/antidotes-and-remedes/MANIFEST-001261
Normal file
BIN
packs/antidotes-and-remedes/MANIFEST-001261
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-001210
|
MANIFEST-001259
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.065149 7f18a4ffa6c0 Recovering log #1208
|
2026/01/07-15:26:43.564495 7f93eb7fe6c0 Recovering log #1257
|
||||||
2025/10/13-20:51:30.074202 7f18a4ffa6c0 Delete type=3 #1206
|
2026/01/07-15:26:43.574446 7f93eb7fe6c0 Delete type=3 #1255
|
||||||
2025/10/13-20:51:30.074263 7f18a4ffa6c0 Delete type=0 #1208
|
2026/01/07-15:26:43.574512 7f93eb7fe6c0 Delete type=0 #1257
|
||||||
2025/10/13-20:58:55.985678 7f189e7fc6c0 Level-0 table #1213: started
|
2026/01/07-15:53:28.432738 7f93e9ffb6c0 Level-0 table #1262: started
|
||||||
2025/10/13-20:58:55.985714 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
2026/01/07-15:53:28.432770 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||||
2025/10/13-20:58:55.992909 7f189e7fc6c0 Delete type=0 #1211
|
2026/01/07-15:53:28.439930 7f93e9ffb6c0 Delete type=0 #1260
|
||||||
2025/10/13-20:58:56.016686 7f189e7fc6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.446625 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.098338 7f18a4ffa6c0 Recovering log #1204
|
2026/01/07-15:09:19.333002 7f93ea7fc6c0 Recovering log #1253
|
||||||
2025/10/13-20:40:04.107962 7f18a4ffa6c0 Delete type=3 #1202
|
2026/01/07-15:09:19.348597 7f93ea7fc6c0 Delete type=3 #1251
|
||||||
2025/10/13-20:40:04.108019 7f18a4ffa6c0 Delete type=0 #1204
|
2026/01/07-15:09:19.348662 7f93ea7fc6c0 Delete type=0 #1253
|
||||||
2025/10/13-20:45:59.692466 7f189e7fc6c0 Level-0 table #1209: started
|
2026/01/07-15:13:49.682345 7f93e9ffb6c0 Level-0 table #1258: started
|
||||||
2025/10/13-20:45:59.692489 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
2026/01/07-15:13:49.682371 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||||
2025/10/13-20:45:59.729240 7f189e7fc6c0 Delete type=0 #1207
|
2026/01/07-15:13:49.688691 7f93e9ffb6c0 Delete type=0 #1256
|
||||||
2025/10/13-20:45:59.729423 7f189e7fc6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.688891 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/apothicarium/MANIFEST-001259
Normal file
BIN
packs/apothicarium/MANIFEST-001259
Normal file
Binary file not shown.
0
packs/dons-de-rhya/001261.log
Normal file
0
packs/dons-de-rhya/001261.log
Normal file
@@ -1 +1 @@
|
|||||||
MANIFEST-001210
|
MANIFEST-001259
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.014097 7f189ffff6c0 Recovering log #1208
|
2026/01/07-15:26:43.514726 7f93ea7fc6c0 Recovering log #1257
|
||||||
2025/10/13-20:51:30.023727 7f189ffff6c0 Delete type=3 #1206
|
2026/01/07-15:26:43.524436 7f93ea7fc6c0 Delete type=3 #1255
|
||||||
2025/10/13-20:51:30.023794 7f189ffff6c0 Delete type=0 #1208
|
2026/01/07-15:26:43.524498 7f93ea7fc6c0 Delete type=0 #1257
|
||||||
2025/10/13-20:58:55.964704 7f189e7fc6c0 Level-0 table #1213: started
|
2026/01/07-15:53:28.406072 7f93e9ffb6c0 Level-0 table #1262: started
|
||||||
2025/10/13-20:58:55.964734 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
2026/01/07-15:53:28.406116 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||||
2025/10/13-20:58:55.972375 7f189e7fc6c0 Delete type=0 #1211
|
2026/01/07-15:53:28.412401 7f93e9ffb6c0 Delete type=0 #1260
|
||||||
2025/10/13-20:58:55.985556 7f189e7fc6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.419261 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.047360 7f189f7fe6c0 Recovering log #1204
|
2026/01/07-15:09:19.258501 7f93eb7fe6c0 Recovering log #1253
|
||||||
2025/10/13-20:40:04.057386 7f189f7fe6c0 Delete type=3 #1202
|
2026/01/07-15:09:19.274117 7f93eb7fe6c0 Delete type=3 #1251
|
||||||
2025/10/13-20:40:04.057445 7f189f7fe6c0 Delete type=0 #1204
|
2026/01/07-15:09:19.274190 7f93eb7fe6c0 Delete type=0 #1253
|
||||||
2025/10/13-20:45:59.550286 7f189e7fc6c0 Level-0 table #1209: started
|
2026/01/07-15:13:49.656004 7f93e9ffb6c0 Level-0 table #1258: started
|
||||||
2025/10/13-20:45:59.550321 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
2026/01/07-15:13:49.656029 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||||
2025/10/13-20:45:59.585680 7f189e7fc6c0 Delete type=0 #1207
|
2026/01/07-15:13:49.662317 7f93e9ffb6c0 Delete type=0 #1256
|
||||||
2025/10/13-20:45:59.585866 7f189e7fc6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.662464 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/dons-de-rhya/MANIFEST-001259
Normal file
BIN
packs/dons-de-rhya/MANIFEST-001259
Normal file
Binary file not shown.
0
packs/dons-de-rhya/lost/001220.log
Normal file
0
packs/dons-de-rhya/lost/001220.log
Normal file
0
packs/plats-dauberges/001261.log
Normal file
0
packs/plats-dauberges/001261.log
Normal file
@@ -1 +1 @@
|
|||||||
MANIFEST-001210
|
MANIFEST-001259
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.000150 7f18a4ffa6c0 Recovering log #1208
|
2026/01/07-15:26:43.501310 7f93eb7fe6c0 Recovering log #1257
|
||||||
2025/10/13-20:51:30.010745 7f18a4ffa6c0 Delete type=3 #1206
|
2026/01/07-15:26:43.512173 7f93eb7fe6c0 Delete type=3 #1255
|
||||||
2025/10/13-20:51:30.010844 7f18a4ffa6c0 Delete type=0 #1208
|
2026/01/07-15:26:43.512224 7f93eb7fe6c0 Delete type=0 #1257
|
||||||
2025/10/13-20:58:55.951435 7f189e7fc6c0 Level-0 table #1213: started
|
2026/01/07-15:53:28.398067 7f93e9ffb6c0 Level-0 table #1262: started
|
||||||
2025/10/13-20:58:55.951483 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
2026/01/07-15:53:28.398117 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||||
2025/10/13-20:58:55.957942 7f189e7fc6c0 Delete type=0 #1211
|
2026/01/07-15:53:28.405830 7f93e9ffb6c0 Delete type=0 #1260
|
||||||
2025/10/13-20:58:55.958168 7f189e7fc6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.412533 7f93e9ffb6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.033243 7f18a4ffa6c0 Recovering log #1204
|
2026/01/07-15:09:19.238733 7f93ebfff6c0 Recovering log #1253
|
||||||
2025/10/13-20:40:04.043812 7f18a4ffa6c0 Delete type=3 #1202
|
2026/01/07-15:09:19.256250 7f93ebfff6c0 Delete type=3 #1251
|
||||||
2025/10/13-20:40:04.043882 7f18a4ffa6c0 Delete type=0 #1204
|
2026/01/07-15:09:19.256331 7f93ebfff6c0 Delete type=0 #1253
|
||||||
2025/10/13-20:45:59.516419 7f189e7fc6c0 Level-0 table #1209: started
|
2026/01/07-15:13:49.649560 7f93e9ffb6c0 Level-0 table #1258: started
|
||||||
2025/10/13-20:45:59.516460 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
2026/01/07-15:13:49.649585 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||||
2025/10/13-20:45:59.549549 7f189e7fc6c0 Delete type=0 #1207
|
2026/01/07-15:13:49.655885 7f93e9ffb6c0 Delete type=0 #1256
|
||||||
2025/10/13-20:45:59.549682 7f189e7fc6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.662454 7f93e9ffb6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/plats-dauberges/MANIFEST-001259
Normal file
BIN
packs/plats-dauberges/MANIFEST-001259
Normal file
Binary file not shown.
0
packs/plats-dauberges/lost/001220.log
Normal file
0
packs/plats-dauberges/lost/001220.log
Normal file
0
packs/tables-des-traductions/000904.log
Normal file
0
packs/tables-des-traductions/000904.log
Normal file
@@ -1 +1 @@
|
|||||||
MANIFEST-000853
|
MANIFEST-000902
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:51:30.051499 7f18a4ffa6c0 Recovering log #851
|
2026/01/07-15:26:43.552530 7f93eaffd6c0 Recovering log #900
|
||||||
2025/10/13-20:51:30.062133 7f18a4ffa6c0 Delete type=3 #849
|
2026/01/07-15:26:43.562158 7f93eaffd6c0 Delete type=3 #898
|
||||||
2025/10/13-20:51:30.062204 7f18a4ffa6c0 Delete type=0 #851
|
2026/01/07-15:26:43.562231 7f93eaffd6c0 Delete type=0 #900
|
||||||
2025/10/13-20:58:55.978922 7f189e7fc6c0 Level-0 table #856: started
|
2026/01/07-15:53:28.426399 7f93e9ffb6c0 Level-0 table #905: started
|
||||||
2025/10/13-20:58:55.978979 7f189e7fc6c0 Level-0 table #856: 0 bytes OK
|
2026/01/07-15:53:28.426431 7f93e9ffb6c0 Level-0 table #905: 0 bytes OK
|
||||||
2025/10/13-20:58:55.985424 7f189e7fc6c0 Delete type=0 #854
|
2026/01/07-15:53:28.432585 7f93e9ffb6c0 Delete type=0 #903
|
||||||
2025/10/13-20:58:55.985583 7f189e7fc6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
2026/01/07-15:53:28.440113 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2025/10/13-20:40:04.084989 7f18a4ffa6c0 Recovering log #847
|
2026/01/07-15:09:19.313936 7f93eaffd6c0 Recovering log #896
|
||||||
2025/10/13-20:40:04.095584 7f18a4ffa6c0 Delete type=3 #845
|
2026/01/07-15:09:19.330494 7f93eaffd6c0 Delete type=3 #894
|
||||||
2025/10/13-20:40:04.095650 7f18a4ffa6c0 Delete type=0 #847
|
2026/01/07-15:09:19.330571 7f93eaffd6c0 Delete type=0 #896
|
||||||
2025/10/13-20:45:59.656500 7f189e7fc6c0 Level-0 table #852: started
|
2026/01/07-15:13:49.669421 7f93e9ffb6c0 Level-0 table #901: started
|
||||||
2025/10/13-20:45:59.656553 7f189e7fc6c0 Level-0 table #852: 0 bytes OK
|
2026/01/07-15:13:49.669459 7f93e9ffb6c0 Level-0 table #901: 0 bytes OK
|
||||||
2025/10/13-20:45:59.691824 7f189e7fc6c0 Delete type=0 #850
|
2026/01/07-15:13:49.675608 7f93e9ffb6c0 Delete type=0 #899
|
||||||
2025/10/13-20:45:59.691954 7f189e7fc6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
2026/01/07-15:13:49.688874 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs/tables-des-traductions/MANIFEST-000902
Normal file
BIN
packs/tables-des-traductions/MANIFEST-000902
Normal file
Binary file not shown.
0
packs/tables-des-traductions/lost/000863.log
Normal file
0
packs/tables-des-traductions/lost/000863.log
Normal file
312
patch-styles.css
312
patch-styles.css
@@ -7,4 +7,314 @@
|
|||||||
color: darkolivegreen;
|
color: darkolivegreen;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Styles pour le module de voyage TravelV2 */
|
||||||
|
.voyage-main-title {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voyage-route-title {
|
||||||
|
font-size: 1.15em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voyage-section-title {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voyage-destinations-title {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voyage-separator {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour le module Inn (Auberge) */
|
||||||
|
.wfrp4e-inn-help h3,
|
||||||
|
.wfrp4e-inn-list h3 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
color: #8b4513;
|
||||||
|
border-bottom: 2px solid #d2691e;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-help ul,
|
||||||
|
.wfrp4e-inn-list ul {
|
||||||
|
margin-left: 1.5em;
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-help li,
|
||||||
|
.wfrp4e-inn-table-list li {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-help hr {
|
||||||
|
margin: 1em 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-help code {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #d63384;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-help h4 {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0.8em 0 0.5em 0;
|
||||||
|
color: var(--color-warm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grille de boutons pour les tables */
|
||||||
|
.wfrp4e-inn-table-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-table-btn {
|
||||||
|
display: block;
|
||||||
|
padding: 0.6em 0.8em;
|
||||||
|
background: rgba(139, 69, 19, 0.12);
|
||||||
|
border: 2px solid rgba(139, 69, 19, 0.35);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95em;
|
||||||
|
color: var(--color-warm);
|
||||||
|
transition: all 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-table-btn:hover {
|
||||||
|
background: rgba(139, 69, 19, 0.2);
|
||||||
|
border-color: rgba(139, 69, 19, 0.5);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-table-btn i {
|
||||||
|
margin-right: 0.4em;
|
||||||
|
color: var(--color-warm-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anciens styles - à supprimer ou garder pour compatibilité */
|
||||||
|
.wfrp4e-inn-table-list {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-table-list li {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-table-list a.action-link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.3em 0.8em;
|
||||||
|
background-color: #8b4513;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-table-list a.action-link:hover {
|
||||||
|
background-color: #a0522d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-table-list a.action-link i {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-keyword {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: var(--color-grey2);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour les résultats de jets d'auberge - Thème WFRP4e */
|
||||||
|
.wfrp4e-inn-result {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-result .message-header {
|
||||||
|
text-shadow: 0px 0px 1px #00000087;
|
||||||
|
border: 2px solid rgba(62, 0, 0, 0.3);
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-result .flavor-text {
|
||||||
|
color: var(--color-warm);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-result .message-header i {
|
||||||
|
margin-right: 0.3em;
|
||||||
|
color: var(--color-warm-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-dish-name {
|
||||||
|
font-size: 1.15em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.6em;
|
||||||
|
margin: 0.3em 0;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-roll-info {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: var(--color-grey2);
|
||||||
|
margin-top: 0.4em;
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-roll-info i {
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour le choix de menu */
|
||||||
|
.wfrp4e-inn-menu-choice {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-menu-choice h3 {
|
||||||
|
color: var(--color-warm);
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-btn {
|
||||||
|
display: block;
|
||||||
|
padding: 0.6em 1em;
|
||||||
|
background: rgba(139, 69, 19, 0.1);
|
||||||
|
border: 2px solid rgba(139, 69, 19, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-btn:hover {
|
||||||
|
background: rgba(139, 69, 19, 0.2);
|
||||||
|
border-color: rgba(139, 69, 19, 0.5);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-btn i {
|
||||||
|
margin-right: 0.4em;
|
||||||
|
color: var(--color-warm-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--color-grey2);
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour le résultat de menu */
|
||||||
|
.wfrp4e-inn-menu-result {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-menu-result .message-header {
|
||||||
|
text-shadow: 0px 0px 1px #00000087;
|
||||||
|
border: 2px solid rgba(62, 0, 0, 0.3);
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfrp4e-inn-menu-result .flavor-text {
|
||||||
|
color: var(--color-warm);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-item {
|
||||||
|
padding: 0.5em;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-item i {
|
||||||
|
margin-right: 0.4em;
|
||||||
|
color: var(--color-warm-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-item strong {
|
||||||
|
color: var(--color-warm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton rapide Menu dans l'aide */
|
||||||
|
.inn-menu-quick-btn {
|
||||||
|
display: block;
|
||||||
|
padding: 0.7em 1em;
|
||||||
|
background: rgba(139, 69, 19, 0.15);
|
||||||
|
border: 2px solid rgba(139, 69, 19, 0.4);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.05em;
|
||||||
|
color: var(--color-warm);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-quick-btn:hover {
|
||||||
|
background: rgba(139, 69, 19, 0.25);
|
||||||
|
border-color: rgba(139, 69, 19, 0.6);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inn-menu-quick-btn i {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
color: var(--color-warm-3);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
let item = await fromUuid("Compendium.wfrp4e-core.items.weczkAMPlTjX7lqU")
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [item])
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return args.item?.system?.isRanged && args.data.targets[0]?.actor?.sizeNum < 3
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// The imbiber immediately
|
|
||||||
// takes 3 Poisoned Conditions that cannot be resisted at first,
|
|
||||||
await this.actor.addCondition("poisoned", 3)
|
|
||||||
|
|
||||||
// recovers a number of Wounds equal to their Toughness Bonus,
|
|
||||||
await this.actor.modifyWounds(this.actor.system.characteristics.t.bonus)
|
|
||||||
|
|
||||||
// and acquires the Regenerate Creature Trait.
|
|
||||||
const hasRegenerate = this.actor.has("Regenerate")
|
|
||||||
if (hasRegenerate === undefined) {
|
|
||||||
fromUuid("Compendium.wfrp4e-core.items.SfUUdOGjdYpr3KSR").then(trait => {
|
|
||||||
let traitItem = trait.toObject()
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [traitItem], {fromEffect: this.effect.id})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.script.scriptMessage(`<p><strong>${this.actor.prototypeToken.name}</strong> has
|
|
||||||
<ul>
|
|
||||||
<li>gained 3 Poisoned Conditions that cannot be resisted at first</li>
|
|
||||||
<li>recovered ${this.actor.system.characteristics.t.bonus} Wounds</li>
|
|
||||||
<li>acquired the Regenerate Creature Trait.</li>
|
|
||||||
</ul>
|
|
||||||
It’s up to Ranald if their regenerating can outpace their poisoning.</p>
|
|
||||||
<p>When all Poisoned Conditions are lost, so too is Regenerate.</p>`,
|
|
||||||
{ whisper: ChatMessage.getWhisperRecipients("GM"), blind: true })
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
this.actor.addCondition("blinded", 3)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
let item = await fromUuid("Compendium.wfrp4e-core.items.8piWcBKFlQ2J1E3A")
|
|
||||||
let data = item.toObject();
|
|
||||||
data.system.location.key= this.item.system.location.key
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [data])
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
if (!args.flags.quietenedApplied)
|
|
||||||
{
|
|
||||||
args.fields.modifier += 10;
|
|
||||||
args.flags.quietenedApplied = true
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return !args.options.terror && !args.extendedTest?.flags.wfrp4e?.fear
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return !args.skill?.name.includes(game.i18n.localize("NAME.Row")) && !args.skill?.name.includes(game.i18n.localize("NAME.Sail"));
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
let spells = await warhammer.utility.findAllItems("spell", "Loading Spells")
|
|
||||||
|
|
||||||
let text = (await game.wfrp4e.tables.rollTable("random-caster", {hideDSN: true})).result
|
|
||||||
|
|
||||||
lore = Array.from(text.matchAll(/{(.+?)}/gm))[0][1]
|
|
||||||
|
|
||||||
if (text == "GM's Choice")
|
|
||||||
{
|
|
||||||
return this.script.scriptNotification(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spellsWithLore.length > 0)
|
|
||||||
{
|
|
||||||
let spellsWithLore = spells.filter(i => game.wfrp4e.config.magicLores[i.system.lore.value] == lore)
|
|
||||||
let selectedSpell = spellsWithLore[Math.floor(CONFIG.Dice.randomUniform() * spellsWithLore.length)]
|
|
||||||
this.script.scriptNotification(selectedSpell.name);
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [selectedSpell])
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ui.notifications.notify(`Could not find ${lore} spell. Try Again`)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
let item = await fromUuid("Compendium.wfrp4e-core.items.4CMKeDTDrRQZbPIJ")
|
|
||||||
let fixation = (await game.wfrp4e.tables.rollTable("fixations"))
|
|
||||||
let data = item.toObject();
|
|
||||||
data.system.specification.value = fixation.result;
|
|
||||||
this.item.updateSource({name : this.item.name += ` (${fixation.result})`});
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [data], {fromEffect : this.effect.id})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return !["t", "wp"].includes(args.characteristic)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
let table = game.wfrp4e.tables.findTable("mutatephys");
|
|
||||||
if (!table)
|
|
||||||
{
|
|
||||||
return ui.notifications.error("Mutation table not found, please ensure a table with the `mutatephys` key is imported in the world.")
|
|
||||||
}
|
|
||||||
let result = (await table.roll()).results[0];
|
|
||||||
let uuid = `Compendium.${result.documentCollection}.${result.documentId}`
|
|
||||||
let item = await fromUuid(uuid);
|
|
||||||
|
|
||||||
if (item)
|
|
||||||
{
|
|
||||||
this.script.scriptNotification(`${item.name} added`)
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [item])
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ui.notifications.error("Item could not be found: " + uuid)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
let location = this.item.system.location.key;
|
|
||||||
|
|
||||||
if (location)
|
|
||||||
{
|
|
||||||
let dropped = this.item.system.weaponsAtLocation;
|
|
||||||
|
|
||||||
if (dropped.length)
|
|
||||||
{
|
|
||||||
this.script.scriptNotification(`Dropped ${dropped.map(i => i.name).join(", ")}!`)
|
|
||||||
for(let weapon of dropped)
|
|
||||||
{
|
|
||||||
await weapon.system.toggleEquip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let roll = await new Roll("max(1, 1d10 - @system.characteristics.t.bonus)", this.actor).roll()
|
|
||||||
|
|
||||||
roll.toMessage(this.script.getChatData({flavor : `${this.effect.name} (Duration)`}));
|
|
||||||
|
|
||||||
this.effect.updateSource({"duration.rounds" : roll.total})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
if (args.skill?.name != game.i18n.localize("NAME.Gossip"))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.data.canReverse = true; // 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
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
if (args.applyAP && args.modifiers.ap.metal)
|
|
||||||
{
|
|
||||||
args.modifiers.ap.ignored += args.modifiers.ap.metal
|
|
||||||
args.modifiers.ap.details.push("<strong>" + this.effect.name + "</strong>: Ignore Metal (" + args.modifiers.ap.metal + ")");
|
|
||||||
args.modifiers.ap.metal = 0
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
let item = await fromUuid("Compendium.wfrp4e-core.items.GbDyBCu8ZjDp6dkj")
|
|
||||||
let data = item.toObject();
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [data], {fromEffect : this.effect.id})
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
let item1 = await fromUuid("Compendium.wfrp4e-core.items.3S4OYOZLauXctmev")
|
|
||||||
let item2 = await fromUuid("Compendium.wfrp4e-core.items.7mCcI3q7hgWcmbBU")
|
|
||||||
|
|
||||||
let data1 = item1.toObject();
|
|
||||||
data1.system.location.key = this.item.system.location.key
|
|
||||||
|
|
||||||
let data2 = item2.toObject();
|
|
||||||
data2.system.location.key = this.item.system.location.key
|
|
||||||
|
|
||||||
this.actor.createEmbeddedDocuments("Item", [data1, data2], {fromEffect: this.effect.id})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
args.fields.modifier -= 20;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
if (!args.flags.strikeToStun)
|
|
||||||
{
|
|
||||||
args.flags.strikeToStun = true
|
|
||||||
args.fields.modifier += 20;
|
|
||||||
args.fields.hitLocation = "head";
|
|
||||||
}
|
|
||||||
args.fields.successBonus++;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return args.options.terror || args.extendedTest?.flags.wfrp4e?.fear
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
let type = this.item.getFlag("wfrp4e", "breath");
|
|
||||||
|
|
||||||
if (["fire", "electricity", "poison"].includes(type))
|
|
||||||
{
|
|
||||||
args.applyAP = false;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
args.fields.modifier -= 20
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
let state = !this.effect.disabled;
|
|
||||||
this.effect.update({"disabled": state});
|
|
||||||
|
|
||||||
if (state)
|
|
||||||
return ui.notifications.info("EFFECT.CreatureBackInWater", {localize: true})
|
|
||||||
|
|
||||||
return ui.notifications.info("EFFECT.CreatureOutOfWater", {localize: true});
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
if (!this.item.name.includes("(") || this.item.system.tests.value.includes("Terrain"))
|
|
||||||
{
|
|
||||||
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("("))
|
|
||||||
{
|
|
||||||
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 : "Coastal",
|
|
||||||
deserts : "Deserts",
|
|
||||||
marshes : "Marshes",
|
|
||||||
rocky : "Rocky",
|
|
||||||
tundra : "Tundra",
|
|
||||||
woodlands : "Woodlands"
|
|
||||||
}, this.item.img), 1, "Choose Terrain");
|
|
||||||
if (choice[0])
|
|
||||||
{
|
|
||||||
name = `${name.split("(")[0].trim()} (${choice[0].name})`
|
|
||||||
tests = tests.replace("the Terrain", choice[0].name + " Terrain")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.effect.updateSource({name})
|
|
||||||
this.item.updateSource({name, "system.tests.value" : tests})
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return !args.skill?.name.includes(game.i18n.localize("NAME.Language"));
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
let characteristics = {
|
|
||||||
"ws" : 5,
|
|
||||||
"bs" : 5,
|
|
||||||
"s" : 5,
|
|
||||||
"t" : 0,
|
|
||||||
"i" : 5,
|
|
||||||
"ag" : 5,
|
|
||||||
"dex" : 5,
|
|
||||||
"int" : 0,
|
|
||||||
"wp" : 5,
|
|
||||||
"fel" : 5
|
|
||||||
}
|
|
||||||
let items = []
|
|
||||||
|
|
||||||
let updateObj = this.actor.toObject();
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
updateObj.system.characteristics[ch].modifier += characteristics[ch];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let talent of talents)
|
|
||||||
{
|
|
||||||
let talentItem = await game.wfrp4e.utility.findTalent(talent)
|
|
||||||
if (talentItem)
|
|
||||||
{
|
|
||||||
items.push(talentItem.toObject());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ui.notifications.warn(`Could not find ${talent}`, {permanent : true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
await this.actor.update(updateObj)
|
|
||||||
this.actor.createEmbeddedDocuments("Item", items);
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return args.characteristic != "t"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
args.actor.details.move.run += 4
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
await this.actor.addCondition("ablaze", 2)
|
|
||||||
await this.script.scriptMessage(await this.actor.applyBasicDamage(this.effect.sourceTest.result.damage, {suppressMsg: true}))
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return args.skill?.name.includes(game.i18n.localize("NAME.Channelling")) || args.skill?.name == `${game.i18n.localize("NAME.Language")} (${game.i18n.localize("SPEC.Magick")})`
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return args.characteristic != "wp"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
let test = await this.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : ` ${this.effect.name}`})
|
|
||||||
await test.roll()
|
|
||||||
if (test.succeeded)
|
|
||||||
{
|
|
||||||
this.effect.delete();
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
args.options.cardsharp = true;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
if (args.opposedTest.result.hitloc.value == this.item.system.location.key && args.totalWoundLoss > 0)
|
|
||||||
{
|
|
||||||
args.actor.addCondition("bleeding", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
return ["ws", "bs", "s", "ag", "t", "dex"].includes(args.characteristic)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
let test = await args.actor.setupCharacteristic("wp", {skipTargets: true, appendTitle : " - " + this.effect.name, context : {failure: "Gained a Stunned Condition"}})
|
|
||||||
await test.roll();
|
|
||||||
if (test.failed)
|
|
||||||
{
|
|
||||||
args.actor.addCondition("stunned")
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
this.actor.status.addArmour(1, {source: this.effect, magical : true})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
this.script.scriptNotification(`Cannot enter ${this.effect.name}!`);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
let healed = parseInt(this.effect.sourceTest.result.SL)
|
|
||||||
this.actor.modifyWounds(healed)
|
|
||||||
this.script.scriptMessage(`Healed ${healed} Wounds`)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
let damage = this.effect.sourceActor.hasCondition("fatigued") ? 6 : 10;
|
|
||||||
|
|
||||||
let loc = "body"
|
|
||||||
|
|
||||||
let APatLoc = this.actor.system.status.armour[loc];
|
|
||||||
|
|
||||||
let metalAP = APatLoc.layers.reduce((metal, layer) => metal += ((layer.metal && !layer.magical) ? layer.value : 0), 0)
|
|
||||||
|
|
||||||
let APused = Math.max(0, APatLoc.value - metalAP); // remove metal AP at location;
|
|
||||||
|
|
||||||
damage -= (APused + this.actor.system.characteristics.t.bonus)
|
|
||||||
|
|
||||||
let msg = await this.actor.applyBasicDamage(damage, {suppressMsg : true, damageType : game.wfrp4e.config.DAMAGE_TYPE.IGNORE_ALL});
|
|
||||||
msg += ` (ignored ${metalAP} metal AP on ${game.wfrp4e.config.locations[loc]})`
|
|
||||||
this.script.scriptMessage(msg)
|
|
||||||
|
|
||||||
let test = await this.actor.setupSkill("Endurance", {fields : {difficulty : "difficult"}, appendTitle : ` - ${this.effect.name}`});
|
|
||||||
await test.roll();
|
|
||||||
if (test.failed)
|
|
||||||
this.actor.addCondition("stunned");
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user