/** * 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 += `
Aucun chemin trouvé entre ${originalFrom} et ${originalTo}
`; message += `Il n'existe pas de route reliant ces deux villes dans les données disponibles.
`; } else { message += `Différentes options de voyage disponibles :
Aide pour /voyage
`;
message += `Usage: /voyage [ville_départ] [ville_arrivée]
`;
message += `Exemples:
`;
message += `/voyage - Liste toutes les villes de départ
`;
message += `/voyage Altdorf - Liste les destinations depuis Altdorf
`;
message += `/voyage Altdorf Nuln - Affiche les détails de voyage entre Altdorf et Nuln`;
message += `
Toutes les villes accessibles (routes directes et itinéraires calculés)
`; // 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 += ``; } } else { message += `Ville inconnue: ${fromTown}
`; message += `Cette ville n'existe pas dans la base de données.
`; } } else { console.log("TravelV2: Branche: Liste de toutes les villes de départ"); // Lister toutes les villes de départ message += `De ${travel.from} à ${travel.to} (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 += `
Par route:`;
message += `
Distance: ${travel.road_distance} km`;
message += `
Durée (chariot): ${travel.road_days} jours - Danger: ${road_danger_string}`;
message += `
Durée (cheval de charge): ${road_horse_heavy_days} jours - Danger: ${road_danger_string}`;
message += `
Durée (cheval rapide): ${road_horse_fast_days} jours - Danger: ${road_danger_string}`;
message += `
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 += `
Par rivière:`;
message += `
Distance: ${travel.river_distance} km`;
message += `
Durée: ${travel.river_days} jours - Danger: ${river_danger_string}`;
}
if (travel.sea_distance != "") {
let sea_danger_string = this.dangerToString(travel.sea_danger);
message += `
Par mer:`;
message += `
Distance: ${travel.sea_distance} km`;
message += `
Durée: ${travel.sea_days} jours - Danger: ${sea_danger_string}`;
}
message += "
${fromCity} → ${toCity} (${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 += `
📊 Résumé du voyage:`;
message += `
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 += `
Durée (chariot): ${totalDays} jours - Danger max: ${danger_string}`;
message += `
Durée (cheval de charge): ${road_horse_heavy_days} jours - Danger max: ${danger_string}`;
message += `
Durée (cheval rapide): ${road_horse_fast_days} jours - Danger max: ${danger_string}`;
message += `
Durée (à pied): ${road_feet_days} jours - Danger max: ${danger_feet_string}`;
} else {
message += `
Durée totale: ${totalDays} jours - Danger max: ${danger_string}`;
}
// Détails des étapes avec mode de transport
message += `
🗺️ Itinéraire détaillé:`;
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 += `
Étape ${stepNum}: ${step.from} → ${step.to}`;
message += `
${modeIcon} Par ${modeName}: ${Math.round(step.distance)} km, ${step.days} jour${step.days > 1 ? 's' : ''} - Danger: ${stepDanger}`;
}
message += "