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",
|
||||
"version": "9.3.2",
|
||||
"version": "9.3.5",
|
||||
"esmodules": [
|
||||
"wh4_fr.js",
|
||||
"modules/babele-register.js",
|
||||
"modules/addon-register.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",
|
||||
"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",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
|
||||
@@ -82,39 +82,8 @@ const _patch_up_in_arms = () => {
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
/* Manages /auberge command */
|
||||
const _manage_inn_roll = async (content, msg) => {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
/* Module /auberge géré par modules/inn/inn-init.js */
|
||||
// L'ancienne implémentation a été migrée vers le module inn pour cohérence avec /voyage
|
||||
|
||||
/************************************************************************************/
|
||||
let __eis_tables = {
|
||||
@@ -262,17 +231,13 @@ const __check_fix_wrong_modules = (chatFlag, patchFinished) => {
|
||||
}
|
||||
} else if (game.user.isGM && patchFinished) {
|
||||
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,
|
||||
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) => {
|
||||
let columns = table.columns;
|
||||
@@ -366,14 +331,8 @@ const __add_actors_translation = () => {
|
||||
|
||||
|
||||
/************************************************************************************/
|
||||
/* Hook for specific command */
|
||||
Hooks.on("chatMessage", (html, content, msg) => {
|
||||
|
||||
if (content.toLowerCase().includes('auberge')) {
|
||||
_manage_inn_roll(content, msg);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
/* Hook for specific command - Module /auberge migré vers modules/inn/ */
|
||||
// La commande /auberge est désormais gérée par le module inn-init.js
|
||||
|
||||
/************************************************************************************/
|
||||
/* Additionnal hooks ready */
|
||||
@@ -426,7 +385,9 @@ Hooks.on('ready', () => {
|
||||
"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, 20000, true, true);
|
||||
setTimeout(__add_actors_translation, 21000, false, true);
|
||||
|
||||
@@ -4,6 +4,9 @@ export class WH4FRPatchConfig {
|
||||
/************************************************************************************/
|
||||
static translateSkillList(skillList) {
|
||||
|
||||
if (!skillList || skillList.length == 0) {
|
||||
return skillList;
|
||||
}
|
||||
let compendiumName = 'wfrp4e-core.items'
|
||||
|
||||
let newList = [];
|
||||
@@ -43,6 +46,9 @@ export class WH4FRPatchConfig {
|
||||
/************************************************************************************/
|
||||
static translateTalentList(talentList) {
|
||||
|
||||
if (!talentList || talentList.length == 0) {
|
||||
return talentList;
|
||||
}
|
||||
let compendiumName = 'wfrp4e-core.items'
|
||||
|
||||
let newList = [];
|
||||
@@ -76,6 +82,9 @@ export class WH4FRPatchConfig {
|
||||
|
||||
/************************************************************************************/
|
||||
static patch_subspecies() {
|
||||
if (!game.wfrp4e?.config?.subspecies) {
|
||||
return
|
||||
}
|
||||
for (let speciesName in game.wfrp4e.config.subspecies) {
|
||||
let subspeciesList = game.wfrp4e.config.subspecies[speciesName];
|
||||
for (let subspeciesName in subspeciesList) {
|
||||
@@ -92,6 +101,9 @@ export class WH4FRPatchConfig {
|
||||
|
||||
/************************************************************************************/
|
||||
static patch_species_skills() {
|
||||
if (!game.wfrp4e?.config?.speciesSkills) {
|
||||
return
|
||||
}
|
||||
console.log("Patching species skills....");
|
||||
for (let speciesName in game.wfrp4e.config.speciesSkills) {
|
||||
let speciesComp = game.wfrp4e.config.speciesSkills[speciesName];
|
||||
@@ -102,6 +114,9 @@ export class WH4FRPatchConfig {
|
||||
|
||||
/************************************************************************************/
|
||||
static patch_species_talents() {
|
||||
if (!game.wfrp4e?.config?.speciesTalents) {
|
||||
return
|
||||
}
|
||||
for (let speciesName in game.wfrp4e.config.speciesTalents) {
|
||||
let speciesTalents = game.wfrp4e.config.speciesTalents[speciesName];
|
||||
game.wfrp4e.config.speciesTalents[speciesName] = this.translateTalentList(speciesTalents);
|
||||
@@ -112,7 +127,7 @@ export class WH4FRPatchConfig {
|
||||
static patch_career() {
|
||||
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 key in row) {
|
||||
if (key != "range") {
|
||||
@@ -133,7 +148,10 @@ export class WH4FRPatchConfig {
|
||||
/************************************************************************************/
|
||||
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);
|
||||
for (let result of newResults) {
|
||||
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
|
||||
2025/10/13-20:51:30.036172 7f189ffff6c0 Delete type=3 #1206
|
||||
2025/10/13-20:51:30.036225 7f189ffff6c0 Delete type=0 #1208
|
||||
2025/10/13-20:58:55.958283 7f189e7fc6c0 Level-0 table #1213: started
|
||||
2025/10/13-20:58:55.958313 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
||||
2025/10/13-20:58:55.964593 7f189e7fc6c0 Delete type=0 #1211
|
||||
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:26:43.527176 7f93eaffd6c0 Recovering log #1257
|
||||
2026/01/07-15:26:43.536993 7f93eaffd6c0 Delete type=3 #1255
|
||||
2026/01/07-15:26:43.537037 7f93eaffd6c0 Delete type=0 #1257
|
||||
2026/01/07-15:53:28.412551 7f93e9ffb6c0 Level-0 table #1262: started
|
||||
2026/01/07-15:53:28.412580 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||
2026/01/07-15:53:28.419118 7f93e9ffb6c0 Delete type=0 #1260
|
||||
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
|
||||
2025/10/13-20:40:04.069741 7f189f7fe6c0 Delete type=3 #1202
|
||||
2025/10/13-20:40:04.069791 7f189f7fe6c0 Delete type=0 #1204
|
||||
2025/10/13-20:45:59.586645 7f189e7fc6c0 Level-0 table #1209: started
|
||||
2025/10/13-20:45:59.586692 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
||||
2025/10/13-20:45:59.620022 7f189e7fc6c0 Delete type=0 #1207
|
||||
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:09:19.276373 7f93eaffd6c0 Recovering log #1253
|
||||
2026/01/07-15:09:19.291800 7f93eaffd6c0 Delete type=3 #1251
|
||||
2026/01/07-15:09:19.291847 7f93eaffd6c0 Delete type=0 #1253
|
||||
2026/01/07-15:13:49.642315 7f93e9ffb6c0 Level-0 table #1258: started
|
||||
2026/01/07-15:13:49.642358 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||
2026/01/07-15:13:49.649460 7f93e9ffb6c0 Delete type=0 #1256
|
||||
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
|
||||
2025/10/13-20:51:30.049183 7f189ffff6c0 Delete type=3 #1208
|
||||
2025/10/13-20:51:30.049237 7f189ffff6c0 Delete type=0 #1210
|
||||
2025/10/13-20:58:55.972493 7f189e7fc6c0 Level-0 table #1215: started
|
||||
2025/10/13-20:58:55.972514 7f189e7fc6c0 Level-0 table #1215: 0 bytes OK
|
||||
2025/10/13-20:58:55.978671 7f189e7fc6c0 Delete type=0 #1213
|
||||
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:26:43.539401 7f93ebfff6c0 Recovering log #1259
|
||||
2026/01/07-15:26:43.549699 7f93ebfff6c0 Delete type=3 #1257
|
||||
2026/01/07-15:26:43.549758 7f93ebfff6c0 Delete type=0 #1259
|
||||
2026/01/07-15:53:28.419273 7f93e9ffb6c0 Level-0 table #1264: started
|
||||
2026/01/07-15:53:28.419304 7f93e9ffb6c0 Level-0 table #1264: 0 bytes OK
|
||||
2026/01/07-15:53:28.426268 7f93e9ffb6c0 Delete type=0 #1262
|
||||
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
|
||||
2025/10/13-20:40:04.082320 7f189f7fe6c0 Delete type=3 #1204
|
||||
2025/10/13-20:40:04.082396 7f189f7fe6c0 Delete type=0 #1206
|
||||
2025/10/13-20:45:59.620883 7f189e7fc6c0 Level-0 table #1211: started
|
||||
2025/10/13-20:45:59.620913 7f189e7fc6c0 Level-0 table #1211: 0 bytes OK
|
||||
2025/10/13-20:45:59.655200 7f189e7fc6c0 Delete type=0 #1209
|
||||
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:09:19.293766 7f93ea7fc6c0 Recovering log #1255
|
||||
2026/01/07-15:09:19.310998 7f93ea7fc6c0 Delete type=3 #1253
|
||||
2026/01/07-15:09:19.311072 7f93ea7fc6c0 Delete type=0 #1255
|
||||
2026/01/07-15:13:49.675722 7f93e9ffb6c0 Level-0 table #1260: started
|
||||
2026/01/07-15:13:49.675750 7f93e9ffb6c0 Level-0 table #1260: 0 bytes OK
|
||||
2026/01/07-15:13:49.682225 7f93e9ffb6c0 Delete type=0 #1258
|
||||
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
|
||||
2025/10/13-20:51:30.074202 7f18a4ffa6c0 Delete type=3 #1206
|
||||
2025/10/13-20:51:30.074263 7f18a4ffa6c0 Delete type=0 #1208
|
||||
2025/10/13-20:58:55.985678 7f189e7fc6c0 Level-0 table #1213: started
|
||||
2025/10/13-20:58:55.985714 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
||||
2025/10/13-20:58:55.992909 7f189e7fc6c0 Delete type=0 #1211
|
||||
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:26:43.564495 7f93eb7fe6c0 Recovering log #1257
|
||||
2026/01/07-15:26:43.574446 7f93eb7fe6c0 Delete type=3 #1255
|
||||
2026/01/07-15:26:43.574512 7f93eb7fe6c0 Delete type=0 #1257
|
||||
2026/01/07-15:53:28.432738 7f93e9ffb6c0 Level-0 table #1262: started
|
||||
2026/01/07-15:53:28.432770 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||
2026/01/07-15:53:28.439930 7f93e9ffb6c0 Delete type=0 #1260
|
||||
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
|
||||
2025/10/13-20:40:04.107962 7f18a4ffa6c0 Delete type=3 #1202
|
||||
2025/10/13-20:40:04.108019 7f18a4ffa6c0 Delete type=0 #1204
|
||||
2025/10/13-20:45:59.692466 7f189e7fc6c0 Level-0 table #1209: started
|
||||
2025/10/13-20:45:59.692489 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
||||
2025/10/13-20:45:59.729240 7f189e7fc6c0 Delete type=0 #1207
|
||||
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:09:19.333002 7f93ea7fc6c0 Recovering log #1253
|
||||
2026/01/07-15:09:19.348597 7f93ea7fc6c0 Delete type=3 #1251
|
||||
2026/01/07-15:09:19.348662 7f93ea7fc6c0 Delete type=0 #1253
|
||||
2026/01/07-15:13:49.682345 7f93e9ffb6c0 Level-0 table #1258: started
|
||||
2026/01/07-15:13:49.682371 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||
2026/01/07-15:13:49.688691 7f93e9ffb6c0 Delete type=0 #1256
|
||||
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
|
||||
2025/10/13-20:51:30.023727 7f189ffff6c0 Delete type=3 #1206
|
||||
2025/10/13-20:51:30.023794 7f189ffff6c0 Delete type=0 #1208
|
||||
2025/10/13-20:58:55.964704 7f189e7fc6c0 Level-0 table #1213: started
|
||||
2025/10/13-20:58:55.964734 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
||||
2025/10/13-20:58:55.972375 7f189e7fc6c0 Delete type=0 #1211
|
||||
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:26:43.514726 7f93ea7fc6c0 Recovering log #1257
|
||||
2026/01/07-15:26:43.524436 7f93ea7fc6c0 Delete type=3 #1255
|
||||
2026/01/07-15:26:43.524498 7f93ea7fc6c0 Delete type=0 #1257
|
||||
2026/01/07-15:53:28.406072 7f93e9ffb6c0 Level-0 table #1262: started
|
||||
2026/01/07-15:53:28.406116 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||
2026/01/07-15:53:28.412401 7f93e9ffb6c0 Delete type=0 #1260
|
||||
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
|
||||
2025/10/13-20:40:04.057386 7f189f7fe6c0 Delete type=3 #1202
|
||||
2025/10/13-20:40:04.057445 7f189f7fe6c0 Delete type=0 #1204
|
||||
2025/10/13-20:45:59.550286 7f189e7fc6c0 Level-0 table #1209: started
|
||||
2025/10/13-20:45:59.550321 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
||||
2025/10/13-20:45:59.585680 7f189e7fc6c0 Delete type=0 #1207
|
||||
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:09:19.258501 7f93eb7fe6c0 Recovering log #1253
|
||||
2026/01/07-15:09:19.274117 7f93eb7fe6c0 Delete type=3 #1251
|
||||
2026/01/07-15:09:19.274190 7f93eb7fe6c0 Delete type=0 #1253
|
||||
2026/01/07-15:13:49.656004 7f93e9ffb6c0 Level-0 table #1258: started
|
||||
2026/01/07-15:13:49.656029 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||
2026/01/07-15:13:49.662317 7f93e9ffb6c0 Delete type=0 #1256
|
||||
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
|
||||
2025/10/13-20:51:30.010745 7f18a4ffa6c0 Delete type=3 #1206
|
||||
2025/10/13-20:51:30.010844 7f18a4ffa6c0 Delete type=0 #1208
|
||||
2025/10/13-20:58:55.951435 7f189e7fc6c0 Level-0 table #1213: started
|
||||
2025/10/13-20:58:55.951483 7f189e7fc6c0 Level-0 table #1213: 0 bytes OK
|
||||
2025/10/13-20:58:55.957942 7f189e7fc6c0 Delete type=0 #1211
|
||||
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:26:43.501310 7f93eb7fe6c0 Recovering log #1257
|
||||
2026/01/07-15:26:43.512173 7f93eb7fe6c0 Delete type=3 #1255
|
||||
2026/01/07-15:26:43.512224 7f93eb7fe6c0 Delete type=0 #1257
|
||||
2026/01/07-15:53:28.398067 7f93e9ffb6c0 Level-0 table #1262: started
|
||||
2026/01/07-15:53:28.398117 7f93e9ffb6c0 Level-0 table #1262: 0 bytes OK
|
||||
2026/01/07-15:53:28.405830 7f93e9ffb6c0 Delete type=0 #1260
|
||||
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
|
||||
2025/10/13-20:40:04.043812 7f18a4ffa6c0 Delete type=3 #1202
|
||||
2025/10/13-20:40:04.043882 7f18a4ffa6c0 Delete type=0 #1204
|
||||
2025/10/13-20:45:59.516419 7f189e7fc6c0 Level-0 table #1209: started
|
||||
2025/10/13-20:45:59.516460 7f189e7fc6c0 Level-0 table #1209: 0 bytes OK
|
||||
2025/10/13-20:45:59.549549 7f189e7fc6c0 Delete type=0 #1207
|
||||
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:09:19.238733 7f93ebfff6c0 Recovering log #1253
|
||||
2026/01/07-15:09:19.256250 7f93ebfff6c0 Delete type=3 #1251
|
||||
2026/01/07-15:09:19.256331 7f93ebfff6c0 Delete type=0 #1253
|
||||
2026/01/07-15:13:49.649560 7f93e9ffb6c0 Level-0 table #1258: started
|
||||
2026/01/07-15:13:49.649585 7f93e9ffb6c0 Level-0 table #1258: 0 bytes OK
|
||||
2026/01/07-15:13:49.655885 7f93e9ffb6c0 Delete type=0 #1256
|
||||
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
|
||||
2025/10/13-20:51:30.062133 7f18a4ffa6c0 Delete type=3 #849
|
||||
2025/10/13-20:51:30.062204 7f18a4ffa6c0 Delete type=0 #851
|
||||
2025/10/13-20:58:55.978922 7f189e7fc6c0 Level-0 table #856: started
|
||||
2025/10/13-20:58:55.978979 7f189e7fc6c0 Level-0 table #856: 0 bytes OK
|
||||
2025/10/13-20:58:55.985424 7f189e7fc6c0 Delete type=0 #854
|
||||
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:26:43.552530 7f93eaffd6c0 Recovering log #900
|
||||
2026/01/07-15:26:43.562158 7f93eaffd6c0 Delete type=3 #898
|
||||
2026/01/07-15:26:43.562231 7f93eaffd6c0 Delete type=0 #900
|
||||
2026/01/07-15:53:28.426399 7f93e9ffb6c0 Level-0 table #905: started
|
||||
2026/01/07-15:53:28.426431 7f93e9ffb6c0 Level-0 table #905: 0 bytes OK
|
||||
2026/01/07-15:53:28.432585 7f93e9ffb6c0 Delete type=0 #903
|
||||
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
|
||||
2025/10/13-20:40:04.095584 7f18a4ffa6c0 Delete type=3 #845
|
||||
2025/10/13-20:40:04.095650 7f18a4ffa6c0 Delete type=0 #847
|
||||
2025/10/13-20:45:59.656500 7f189e7fc6c0 Level-0 table #852: started
|
||||
2025/10/13-20:45:59.656553 7f189e7fc6c0 Level-0 table #852: 0 bytes OK
|
||||
2025/10/13-20:45:59.691824 7f189e7fc6c0 Delete type=0 #850
|
||||
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:09:19.313936 7f93eaffd6c0 Recovering log #896
|
||||
2026/01/07-15:09:19.330494 7f93eaffd6c0 Delete type=3 #894
|
||||
2026/01/07-15:09:19.330571 7f93eaffd6c0 Delete type=0 #896
|
||||
2026/01/07-15:13:49.669421 7f93e9ffb6c0 Level-0 table #901: started
|
||||
2026/01/07-15:13:49.669459 7f93e9ffb6c0 Level-0 table #901: 0 bytes OK
|
||||
2026/01/07-15:13:49.675608 7f93e9ffb6c0 Delete type=0 #899
|
||||
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;
|
||||
align-self: 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