Compare commits
12 Commits
foundryvtt
...
v10
| Author | SHA1 | Date | |
|---|---|---|---|
| 301cc830bc | |||
| 786afeab74 | |||
| e0383def30 | |||
| 7dc51444f0 | |||
| 752a6701c0 | |||
| 0e8237c233 | |||
| df26a699a0 | |||
| 0a1fa37e49 | |||
| 5b7fba4c87 | |||
| d710061eeb | |||
| d6f2feca07 | |||
| f525b6c07a |
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": {}
|
||||
}
|
||||
6
fr.json
6
fr.json
@@ -1192,6 +1192,10 @@
|
||||
"CHAT.ExpReceivedNoReason":"Vous avez reçu <b>{amount}</b> points d'expérience",
|
||||
"CHAT.CriticalDeflection":"Déviation de Critique",
|
||||
"CHAT.DamageToArmour":"1 Dommages appliqués à {item} ({type})",
|
||||
"CHAT.CastSpell":"Incanter {spell}",
|
||||
"CHAT.Dispel":"Dissiper {spell}",
|
||||
"CHAT.DissolutionTable":"Lancer sur la Table de Dissolution du Corps et de l'Esprit pour votre Espèce:<br>@Table[corruption]",
|
||||
"CHAT.InvokePrayer":"Invoquer {prayer}",
|
||||
|
||||
"Error.SpeciesSkills" : "Impossible d'ajouter des compétences pour les races",
|
||||
"Error.SpeciesTalents" : "Impossible d'ajouter des talents pour les races",
|
||||
@@ -2348,6 +2352,8 @@
|
||||
"EFFECT.AffectTheSourceOfFearName":"Tests qui affectent {name}",
|
||||
"EFFECT.DeletingEffectItems":"Suppression des items d'effets: {items}",
|
||||
"EFFECT.BlackpowderShock":"Contre-coup de Poudre Noire",
|
||||
"EFFECT.BonusModifier":"Modificateur de Bonus",
|
||||
"EFFECT.CharacteristicsBonus":"Caractéristiques (Modificateur de Bonus)",
|
||||
|
||||
"GRIEVANCE.Warning1":"Attention",
|
||||
"GRIEVANCE.Warning2":": Cette information est envoyé sur l'espace Github, qui est un espace publique, donc le Tag Discord est préférable. Sinon, contactez moi (MooMan) directement. Si vous avez l'impression que le bug concerne le module FR, contactez LeRatierBretonnier (Discord Foundry FR)",
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
}
|
||||
],
|
||||
"url": "https://www.uberwald.me/gitea/public/foundryvtt-wh4-lang-fr-fr",
|
||||
"version": "9.3.3",
|
||||
"version": "9.4.0",
|
||||
"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-7.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);
|
||||
|
||||
@@ -248,16 +248,16 @@ Hooks.once('init', () => {
|
||||
let translw = translItem?.name || undefined
|
||||
if (translw && translw != s1) {
|
||||
let res2 = re.exec(translw);
|
||||
transl = res2[1] + "(" + subword + ")";
|
||||
transl = res2[1].trim() + " (" + subword + ")";
|
||||
} else {
|
||||
s1 = res[1].trim() + " ( )";
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: s1, type: "skill" }, true)
|
||||
translw = translItem?.name || undefined
|
||||
if(translw) {
|
||||
let res2 = re.exec(translw);
|
||||
transl = res2[1] + "(" + subword + ")";
|
||||
transl = res2[1].trim() + " (" + subword + ")";
|
||||
} else {
|
||||
transl = res[1] + " (" + subword + ")";
|
||||
transl = res[1].trim() + " (" + subword + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +366,7 @@ Hooks.once('init', () => {
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: s1 }, true)
|
||||
let translw = translItem?.name || undefined
|
||||
if (translw && translw != s1) {
|
||||
transl = translw + " (" + subword + ")";
|
||||
transl = translw.trim() + " (" + subword + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,8 +424,10 @@ Hooks.once('init', () => {
|
||||
if (trait_fr?.name && trait_fr?.name != name_en) {
|
||||
trait_fr.name = trait_fr.name || trait_en.name
|
||||
trait_en.name = nbt + trait_fr.name + special;
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
if (trait_en.system?.specification && isNaN(trait_en.system.specification.value)) { // This is a string, so translate it
|
||||
if ( trait_en.system?.description?.value && trait_fr.system?.description?.value) {
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
}
|
||||
if (trait_en?.system?.specification && isNaN(trait_en.system.specification?.value)) { // This is a string, so translate it
|
||||
//console.log("Translating : ", trait_en.system.specification.value);
|
||||
trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim());
|
||||
}
|
||||
|
||||
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-001231
|
||||
MANIFEST-001287
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.396780 7f93eb7fe6c0 Recovering log #1229
|
||||
2026/01/05-17:20:14.456467 7f93eb7fe6c0 Delete type=3 #1227
|
||||
2026/01/05-17:20:14.456537 7f93eb7fe6c0 Delete type=0 #1229
|
||||
2026/01/05-17:22:12.851160 7f93e9ffb6c0 Level-0 table #1234: started
|
||||
2026/01/05-17:22:12.851180 7f93e9ffb6c0 Level-0 table #1234: 0 bytes OK
|
||||
2026/01/05-17:22:12.857087 7f93e9ffb6c0 Delete type=0 #1232
|
||||
2026/01/05-17:22:12.870416 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.449653 7f78b77fe6c0 Recovering log #1285
|
||||
2026/01/29-22:56:42.460601 7f78b77fe6c0 Delete type=3 #1283
|
||||
2026/01/29-22:56:42.460653 7f78b77fe6c0 Delete type=0 #1285
|
||||
2026/01/29-22:57:35.335489 7f78b67fc6c0 Level-0 table #1290: started
|
||||
2026/01/29-22:57:35.335514 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
|
||||
2026/01/29-22:57:35.341828 7f78b67fc6c0 Delete type=0 #1288
|
||||
2026/01/29-22:57:35.348187 7f78b67fc6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.254078 7f93eaffd6c0 Recovering log #1224
|
||||
2026/01/04-22:01:24.350065 7f93eaffd6c0 Delete type=3 #1222
|
||||
2026/01/04-22:01:24.350146 7f93eaffd6c0 Delete type=0 #1224
|
||||
2026/01/04-22:02:13.377643 7f93e9ffb6c0 Level-0 table #1230: started
|
||||
2026/01/04-22:02:13.377670 7f93e9ffb6c0 Level-0 table #1230: 0 bytes OK
|
||||
2026/01/04-22:02:13.384805 7f93e9ffb6c0 Delete type=0 #1228
|
||||
2026/01/04-22:02:13.385131 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.937265 7fac3cbfe6c0 Recovering log #1281
|
||||
2026/01/29-20:23:41.947854 7fac3cbfe6c0 Delete type=3 #1279
|
||||
2026/01/29-20:23:41.947908 7fac3cbfe6c0 Delete type=0 #1281
|
||||
2026/01/29-20:30:28.237866 7fa9a6fef6c0 Level-0 table #1286: started
|
||||
2026/01/29-20:30:28.237897 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
|
||||
2026/01/29-20:30:28.274449 7fa9a6fef6c0 Delete type=0 #1284
|
||||
2026/01/29-20:30:28.305057 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!3IgmiprzLB6Lwenc' @ 72057594037927935 : 1 .. '!journal.pages!suuYN87Al1ZZWtQQ.jhgNnhWhrkOpKs1B' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-001233
|
||||
MANIFEST-001289
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.458755 7f93eb7fe6c0 Recovering log #1231
|
||||
2026/01/05-17:20:14.498635 7f93eb7fe6c0 Delete type=3 #1229
|
||||
2026/01/05-17:20:14.498701 7f93eb7fe6c0 Delete type=0 #1231
|
||||
2026/01/05-17:22:12.845184 7f93e9ffb6c0 Level-0 table #1236: started
|
||||
2026/01/05-17:22:12.845214 7f93e9ffb6c0 Level-0 table #1236: 0 bytes OK
|
||||
2026/01/05-17:22:12.851070 7f93e9ffb6c0 Delete type=0 #1234
|
||||
2026/01/05-17:22:12.870407 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.465768 7f78b7fff6c0 Recovering log #1287
|
||||
2026/01/29-22:56:42.475309 7f78b7fff6c0 Delete type=3 #1285
|
||||
2026/01/29-22:56:42.475364 7f78b7fff6c0 Delete type=0 #1287
|
||||
2026/01/29-22:57:35.362417 7f78b67fc6c0 Level-0 table #1292: started
|
||||
2026/01/29-22:57:35.362439 7f78b67fc6c0 Level-0 table #1292: 0 bytes OK
|
||||
2026/01/29-22:57:35.369784 7f78b67fc6c0 Delete type=0 #1290
|
||||
2026/01/29-22:57:35.376148 7f78b67fc6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.357771 7f93ebfff6c0 Recovering log #1226
|
||||
2026/01/04-22:01:24.468440 7f93ebfff6c0 Delete type=3 #1224
|
||||
2026/01/04-22:01:24.468504 7f93ebfff6c0 Delete type=0 #1226
|
||||
2026/01/04-22:02:13.391738 7f93e9ffb6c0 Level-0 table #1232: started
|
||||
2026/01/04-22:02:13.391763 7f93e9ffb6c0 Level-0 table #1232: 0 bytes OK
|
||||
2026/01/04-22:02:13.397715 7f93e9ffb6c0 Delete type=0 #1230
|
||||
2026/01/04-22:02:13.410782 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.951251 7fac277fe6c0 Recovering log #1283
|
||||
2026/01/29-20:23:41.961856 7fac277fe6c0 Delete type=3 #1281
|
||||
2026/01/29-20:23:41.961911 7fac277fe6c0 Delete type=0 #1283
|
||||
2026/01/29-20:30:28.274586 7fa9a6fef6c0 Level-0 table #1288: started
|
||||
2026/01/29-20:30:28.274614 7fa9a6fef6c0 Level-0 table #1288: 0 bytes OK
|
||||
2026/01/29-20:30:28.304924 7fa9a6fef6c0 Delete type=0 #1286
|
||||
2026/01/29-20:30:28.305066 7fa9a6fef6c0 Manual compaction at level-0 from '!folders!3uquYH73ttCdoH0I' @ 72057594037927935 : 1 .. '!items!ylFhk7mGZOnAJTUT' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-001231
|
||||
MANIFEST-001287
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.575428 7f93ea7fc6c0 Recovering log #1229
|
||||
2026/01/05-17:20:14.619638 7f93ea7fc6c0 Delete type=3 #1227
|
||||
2026/01/05-17:20:14.619716 7f93ea7fc6c0 Delete type=0 #1229
|
||||
2026/01/05-17:22:12.876750 7f93e9ffb6c0 Level-0 table #1234: started
|
||||
2026/01/05-17:22:12.876780 7f93e9ffb6c0 Level-0 table #1234: 0 bytes OK
|
||||
2026/01/05-17:22:12.884154 7f93e9ffb6c0 Delete type=0 #1232
|
||||
2026/01/05-17:22:12.900968 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.493062 7f78b6ffd6c0 Recovering log #1285
|
||||
2026/01/29-22:56:42.503149 7f78b6ffd6c0 Delete type=3 #1283
|
||||
2026/01/29-22:56:42.503203 7f78b6ffd6c0 Delete type=0 #1285
|
||||
2026/01/29-22:57:35.355694 7f78b67fc6c0 Level-0 table #1290: started
|
||||
2026/01/29-22:57:35.355726 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
|
||||
2026/01/29-22:57:35.362323 7f78b67fc6c0 Delete type=0 #1288
|
||||
2026/01/29-22:57:35.376141 7f78b67fc6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.562817 7f93eb7fe6c0 Recovering log #1224
|
||||
2026/01/04-22:01:24.662382 7f93eb7fe6c0 Delete type=3 #1222
|
||||
2026/01/04-22:01:24.662455 7f93eb7fe6c0 Delete type=0 #1224
|
||||
2026/01/04-22:02:13.397968 7f93e9ffb6c0 Level-0 table #1230: started
|
||||
2026/01/04-22:02:13.398010 7f93e9ffb6c0 Level-0 table #1230: 0 bytes OK
|
||||
2026/01/04-22:02:13.404471 7f93e9ffb6c0 Delete type=0 #1228
|
||||
2026/01/04-22:02:13.410790 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.979254 7fac277fe6c0 Recovering log #1281
|
||||
2026/01/29-20:23:41.990432 7fac277fe6c0 Delete type=3 #1279
|
||||
2026/01/29-20:23:41.990499 7fac277fe6c0 Delete type=0 #1281
|
||||
2026/01/29-20:30:28.407331 7fa9a6fef6c0 Level-0 table #1286: started
|
||||
2026/01/29-20:30:28.407362 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
|
||||
2026/01/29-20:30:28.441323 7fa9a6fef6c0 Delete type=0 #1284
|
||||
2026/01/29-20:30:28.642713 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!cZtNgayIw2QFhC9u' @ 72057594037927935 : 1 .. '!journal.pages!cZtNgayIw2QFhC9u.ts265H1XkisLgdow' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-001231
|
||||
MANIFEST-001287
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.340612 7f93eb7fe6c0 Recovering log #1229
|
||||
2026/01/05-17:20:14.394663 7f93eb7fe6c0 Delete type=3 #1227
|
||||
2026/01/05-17:20:14.394743 7f93eb7fe6c0 Delete type=0 #1229
|
||||
2026/01/05-17:22:12.857180 7f93e9ffb6c0 Level-0 table #1234: started
|
||||
2026/01/05-17:22:12.857204 7f93e9ffb6c0 Level-0 table #1234: 0 bytes OK
|
||||
2026/01/05-17:22:12.863647 7f93e9ffb6c0 Delete type=0 #1232
|
||||
2026/01/05-17:22:12.870424 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.435103 7f78cc9ff6c0 Recovering log #1285
|
||||
2026/01/29-22:56:42.445358 7f78cc9ff6c0 Delete type=3 #1283
|
||||
2026/01/29-22:56:42.445414 7f78cc9ff6c0 Delete type=0 #1285
|
||||
2026/01/29-22:57:35.328574 7f78b67fc6c0 Level-0 table #1290: started
|
||||
2026/01/29-22:57:35.328607 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
|
||||
2026/01/29-22:57:35.335392 7f78b67fc6c0 Delete type=0 #1288
|
||||
2026/01/29-22:57:35.348176 7f78b67fc6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.160908 7f93ea7fc6c0 Recovering log #1224
|
||||
2026/01/04-22:01:24.250354 7f93ea7fc6c0 Delete type=3 #1222
|
||||
2026/01/04-22:01:24.250444 7f93ea7fc6c0 Delete type=0 #1224
|
||||
2026/01/04-22:02:13.371437 7f93e9ffb6c0 Level-0 table #1230: started
|
||||
2026/01/04-22:02:13.371468 7f93e9ffb6c0 Level-0 table #1230: 0 bytes OK
|
||||
2026/01/04-22:02:13.377536 7f93e9ffb6c0 Delete type=0 #1228
|
||||
2026/01/04-22:02:13.385115 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.924353 7fac3d3ff6c0 Recovering log #1281
|
||||
2026/01/29-20:23:41.933825 7fac3d3ff6c0 Delete type=3 #1279
|
||||
2026/01/29-20:23:41.933878 7fac3d3ff6c0 Delete type=0 #1281
|
||||
2026/01/29-20:30:28.174496 7fa9a6fef6c0 Level-0 table #1286: started
|
||||
2026/01/29-20:30:28.174527 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
|
||||
2026/01/29-20:30:28.203862 7fa9a6fef6c0 Delete type=0 #1284
|
||||
2026/01/29-20:30:28.305039 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!50u8VAjdmovyr0hx' @ 72057594037927935 : 1 .. '!journal.pages!yzw9I0r3hCK7PJnz.sPNCYj2nR3Cp3jHd' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-001231
|
||||
MANIFEST-001287
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.280921 7f93ea7fc6c0 Recovering log #1229
|
||||
2026/01/05-17:20:14.337958 7f93ea7fc6c0 Delete type=3 #1227
|
||||
2026/01/05-17:20:14.338043 7f93ea7fc6c0 Delete type=0 #1229
|
||||
2026/01/05-17:22:12.838440 7f93e9ffb6c0 Level-0 table #1234: started
|
||||
2026/01/05-17:22:12.838473 7f93e9ffb6c0 Level-0 table #1234: 0 bytes OK
|
||||
2026/01/05-17:22:12.844706 7f93e9ffb6c0 Delete type=0 #1232
|
||||
2026/01/05-17:22:12.844991 7f93e9ffb6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.421350 7f78b6ffd6c0 Recovering log #1285
|
||||
2026/01/29-22:56:42.431020 7f78b6ffd6c0 Delete type=3 #1283
|
||||
2026/01/29-22:56:42.431080 7f78b6ffd6c0 Delete type=0 #1285
|
||||
2026/01/29-22:57:35.341932 7f78b67fc6c0 Level-0 table #1290: started
|
||||
2026/01/29-22:57:35.341953 7f78b67fc6c0 Level-0 table #1290: 0 bytes OK
|
||||
2026/01/29-22:57:35.348055 7f78b67fc6c0 Delete type=0 #1288
|
||||
2026/01/29-22:57:35.348196 7f78b67fc6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.060098 7f93ebfff6c0 Recovering log #1224
|
||||
2026/01/04-22:01:24.158104 7f93ebfff6c0 Delete type=3 #1222
|
||||
2026/01/04-22:01:24.158187 7f93ebfff6c0 Delete type=0 #1224
|
||||
2026/01/04-22:02:13.363838 7f93e9ffb6c0 Level-0 table #1230: started
|
||||
2026/01/04-22:02:13.363872 7f93e9ffb6c0 Level-0 table #1230: 0 bytes OK
|
||||
2026/01/04-22:02:13.371335 7f93e9ffb6c0 Delete type=0 #1228
|
||||
2026/01/04-22:02:13.385100 7f93e9ffb6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.909492 7fac277fe6c0 Recovering log #1281
|
||||
2026/01/29-20:23:41.920402 7fac277fe6c0 Delete type=3 #1279
|
||||
2026/01/29-20:23:41.920469 7fac277fe6c0 Delete type=0 #1281
|
||||
2026/01/29-20:30:28.203984 7fa9a6fef6c0 Level-0 table #1286: started
|
||||
2026/01/29-20:30:28.204007 7fa9a6fef6c0 Level-0 table #1286: 0 bytes OK
|
||||
2026/01/29-20:30:28.237718 7fa9a6fef6c0 Delete type=0 #1284
|
||||
2026/01/29-20:30:28.305049 7fa9a6fef6c0 Manual compaction at level-0 from '!tables!4l60Lxv8cpsyy2Cg' @ 72057594037927935 : 1 .. '!tables.results!tfaYKDZqu7kgZvRG.yvbwKursaixh2dby' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000874
|
||||
MANIFEST-000930
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/05-17:20:14.509126 7f93ea7fc6c0 Recovering log #872
|
||||
2026/01/05-17:20:14.568971 7f93ea7fc6c0 Delete type=3 #870
|
||||
2026/01/05-17:20:14.569021 7f93ea7fc6c0 Delete type=0 #872
|
||||
2026/01/05-17:22:12.863773 7f93e9ffb6c0 Level-0 table #877: started
|
||||
2026/01/05-17:22:12.863802 7f93e9ffb6c0 Level-0 table #877: 0 bytes OK
|
||||
2026/01/05-17:22:12.870319 7f93e9ffb6c0 Delete type=0 #875
|
||||
2026/01/05-17:22:12.870431 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-22:56:42.478978 7f78b77fe6c0 Recovering log #928
|
||||
2026/01/29-22:56:42.489559 7f78b77fe6c0 Delete type=3 #926
|
||||
2026/01/29-22:56:42.489615 7f78b77fe6c0 Delete type=0 #928
|
||||
2026/01/29-22:57:35.369875 7f78b67fc6c0 Level-0 table #933: started
|
||||
2026/01/29-22:57:35.369900 7f78b67fc6c0 Level-0 table #933: 0 bytes OK
|
||||
2026/01/29-22:57:35.376019 7f78b67fc6c0 Delete type=0 #931
|
||||
2026/01/29-22:57:35.376158 7f78b67fc6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/01/04-22:01:24.471625 7f93eaffd6c0 Recovering log #867
|
||||
2026/01/04-22:01:24.559238 7f93eaffd6c0 Delete type=3 #865
|
||||
2026/01/04-22:01:24.559306 7f93eaffd6c0 Delete type=0 #867
|
||||
2026/01/04-22:02:13.404597 7f93e9ffb6c0 Level-0 table #873: started
|
||||
2026/01/04-22:02:13.404646 7f93e9ffb6c0 Level-0 table #873: 0 bytes OK
|
||||
2026/01/04-22:02:13.410707 7f93e9ffb6c0 Delete type=0 #871
|
||||
2026/01/04-22:02:13.410796 7f93e9ffb6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||
2026/01/29-20:23:41.966258 7fac3cbfe6c0 Recovering log #924
|
||||
2026/01/29-20:23:41.975859 7fac3cbfe6c0 Delete type=3 #922
|
||||
2026/01/29-20:23:41.975913 7fac3cbfe6c0 Delete type=0 #924
|
||||
2026/01/29-20:30:28.375267 7fa9a6fef6c0 Level-0 table #929: started
|
||||
2026/01/29-20:30:28.375310 7fa9a6fef6c0 Level-0 table #929: 0 bytes OK
|
||||
2026/01/29-20:30:28.407196 7fa9a6fef6c0 Delete type=0 #927
|
||||
2026/01/29-20:30:28.642703 7fa9a6fef6c0 Manual compaction at level-0 from '!journal!056ILNNrLiPq3Gi3' @ 72057594037927935 : 1 .. '!journal.pages!yfZxl4I7XAuUF6r3.apXmOlZRmGT4GreB' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
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");
|
||||
@@ -1,19 +0,0 @@
|
||||
let test = await this.actor.setupSkill(game.i18n.localize("NAME.Endurance"), {skipTargets: true, appendTitle : " - " + this.effect.name})
|
||||
await test.roll();
|
||||
if (!test.succeeded)
|
||||
{
|
||||
let item = await fromUuid("Compendium.wfrp4e-core.items.ZhMADOqoo0y8Q9bx")
|
||||
let data = item.toObject();
|
||||
if (this.item.system.location.key == "rLeg")
|
||||
{
|
||||
data.system.location.value = "Right Toe"
|
||||
data.system.location.key = "rToe";
|
||||
}
|
||||
else if (this.item.system.location.key == "lLeg")
|
||||
{
|
||||
data.system.location.value = "Left Toe"
|
||||
data.system.location.key = "lToe";
|
||||
}
|
||||
this.actor.createEmbeddedDocuments("Item", [data])
|
||||
}
|
||||
this.effect.delete();
|
||||
@@ -1 +0,0 @@
|
||||
args.options.ballockKnife = true;
|
||||
@@ -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", 3)
|
||||
}
|
||||
})
|
||||
@@ -1,2 +0,0 @@
|
||||
if (args.effect.conditionId == "bleeding")
|
||||
args.data.damage -= 1
|
||||
@@ -1 +0,0 @@
|
||||
return !args.skill?.name?.includes(game.i18n.localize("NAME.Sail"))
|
||||
@@ -1,10 +0,0 @@
|
||||
let choice = await ItemDialog.create(ItemDialog.objectToArray(game.wfrp4e.config.locations), 1, "Choose Location");
|
||||
|
||||
let location = choice[0].id;
|
||||
|
||||
let itemTargeted = this.actor.items.get(this.effect.getFlag("wfrp4e", "itemTargets")[0])
|
||||
|
||||
if (itemTargeted)
|
||||
{
|
||||
itemTargeted.update({[`system.APdamage.${location}`] : itemTargeted.system.APdamage[location] + 1})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
return args.options.reload
|
||||
@@ -1 +0,0 @@
|
||||
return args.type != "channelling" && !args.skill?.name.includes(game.i18n.localize("NAME.Channelling"))
|
||||
@@ -1,5 +0,0 @@
|
||||
if (args.item.range && args.item.range.bands)
|
||||
{
|
||||
args.item.range.bands[game.i18n.localize("Long Range")].modifier = 0
|
||||
args.item.range.bands[game.i18n.localize("Extreme")].modifier /= 2
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
let item = await fromUuid("Compendium.wfrp4e-core.items.eWPN3CV2Eddwz8aM")
|
||||
let data = item.toObject();
|
||||
data.system.location.value = "Back"
|
||||
this.actor.createEmbeddedDocuments("Item", [data], {fromEffect: this.effect.id})
|
||||
@@ -1 +0,0 @@
|
||||
game.wfrp4e.utility.postCorruptionTest(this.item.system.specification.value, {speaker : {alias: this.actor.prototypeToken.name}})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user