132 lines
4.2 KiB
JavaScript
132 lines
4.2 KiB
JavaScript
/**
|
||
* MGT2 Commerce – Client API Traveller Map
|
||
*
|
||
* Documentation : https://travellermap.com/doc/api
|
||
*
|
||
* Deux fonctions exportées :
|
||
* searchWorlds(query) → [{name, sector, hex, uwp, zone}]
|
||
* fetchWorldDetail(sector, hex) → {uwp, zone} (zone: 'normal'|'amber'|'red')
|
||
*/
|
||
|
||
const BASE_URL = 'https://travellermap.com';
|
||
|
||
/** Convertit la zone Traveller Map ('A', 'R', '') vers notre convention. */
|
||
function normalizeZone(z) {
|
||
if (z === 'A') return 'amber';
|
||
if (z === 'R') return 'red';
|
||
return 'normal';
|
||
}
|
||
|
||
/**
|
||
* Recherche des mondes par nom via l'API Traveller Map.
|
||
*
|
||
* @param {string} query Nom partiel ou complet (ex. "Regina")
|
||
* @returns {Promise<Array<{name:string, sector:string, hex:string, uwp:string}>>}
|
||
* Tableau de résultats (uniquement les mondes, pas secteurs/subsecteurs).
|
||
* Vide si erreur réseau.
|
||
*/
|
||
export async function searchWorlds(query) {
|
||
if (!query || query.trim().length < 2) return [];
|
||
|
||
const url = `${BASE_URL}/api/search?q=${encodeURIComponent(query.trim())}`;
|
||
|
||
let data;
|
||
try {
|
||
const resp = await fetch(url);
|
||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||
data = await resp.json();
|
||
} catch (err) {
|
||
console.warn('MGT2 Commerce | Traveller Map search error:', err);
|
||
return [];
|
||
}
|
||
|
||
const items = data?.Results?.Items ?? [];
|
||
return items
|
||
.filter(item => item.World) // garder uniquement les mondes
|
||
.map(item => {
|
||
const w = item.World;
|
||
// Formatter le code hex sur 4 chiffres (HexX→XX, HexY→YY)
|
||
const hex = String(w.HexX).padStart(2, '0') + String(w.HexY).padStart(2, '0');
|
||
return {
|
||
name: w.Name,
|
||
sector: w.Sector,
|
||
hex,
|
||
uwp: w.Uwp ?? '',
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Récupère les coordonnées absolues d'un monde en parsecs.
|
||
* Utilise l'endpoint /api/coordinates.
|
||
*
|
||
* @param {string} sector Nom du secteur (ex. "Spinward Marches")
|
||
* @param {string} hex Code hex 4 chiffres (ex. "1910")
|
||
* @returns {Promise<{x:number, y:number}|null>}
|
||
*/
|
||
export async function fetchWorldCoordinates(sector, hex) {
|
||
const url = `${BASE_URL}/api/coordinates?sector=${encodeURIComponent(sector)}&hex=${encodeURIComponent(hex)}`;
|
||
|
||
try {
|
||
const resp = await fetch(url);
|
||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||
const data = await resp.json();
|
||
if (data?.x == null || data?.y == null) return null;
|
||
return { x: data.x, y: data.y };
|
||
} catch (err) {
|
||
console.warn('MGT2 Commerce | Traveller Map coordinates error:', err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Calcule la distance en parsecs entre deux mondes à partir de leurs
|
||
* coordonnées absolues (retournées par fetchWorldCoordinates).
|
||
* Utilise la formule de distance en coordonnées cubiques pour grille hexagonale.
|
||
*
|
||
* Dans le système Traveller Map : les colonnes impaires (hx impair) sont
|
||
* décalées vers le bas. En coordonnées API, hx impair ↔ x pair.
|
||
* Conversion offset→cube : q=x, r=y−⌈x/2⌉, s=−q−r.
|
||
* Distance = max(|Δq|, |Δr|, |Δs|).
|
||
*
|
||
* @param {{x:number,y:number}} c1
|
||
* @param {{x:number,y:number}} c2
|
||
* @returns {number} Distance en parsecs (entier, minimum 1)
|
||
*/
|
||
export function calcParsecs(c1, c2) {
|
||
const q1 = c1.x, r1 = c1.y - Math.ceil(c1.x / 2);
|
||
const q2 = c2.x, r2 = c2.y - Math.ceil(c2.x / 2);
|
||
const dq = q2 - q1, dr = r2 - r1, ds = -dq - dr;
|
||
return Math.max(1, Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds)));
|
||
}
|
||
|
||
/**
|
||
* Récupère les détails d'un monde pour obtenir la zone de voyage.
|
||
* Utilise l'endpoint /data/{sector}/{hex} (jump=0).
|
||
*
|
||
* @param {string} sector Nom du secteur (ex. "Spinward Marches")
|
||
* @param {string} hex Code hex 4 chiffres (ex. "1910")
|
||
* @returns {Promise<{uwp:string, zone:string}>}
|
||
*/
|
||
export async function fetchWorldDetail(sector, hex) {
|
||
const url = `${BASE_URL}/data/${encodeURIComponent(sector)}/${hex}`;
|
||
|
||
let data;
|
||
try {
|
||
const resp = await fetch(url);
|
||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||
data = await resp.json();
|
||
} catch (err) {
|
||
console.warn('MGT2 Commerce | Traveller Map detail error:', err);
|
||
return null;
|
||
}
|
||
|
||
const world = data?.Worlds?.[0];
|
||
if (!world) return null;
|
||
|
||
return {
|
||
uwp: world.UWP ?? '',
|
||
zone: normalizeZone(world.Zone ?? ''),
|
||
};
|
||
}
|