MAp management and helpers
This commit is contained in:
@@ -51,15 +51,36 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
},
|
||||
};
|
||||
this._tradeGoods = null;
|
||||
this._worldNames = {};
|
||||
|
||||
if (options.defaultWorld) {
|
||||
const w = options.defaultWorld;
|
||||
this._defaultWorld = w;
|
||||
this._worldNames['pax.uwpDep'] = w.name || '';
|
||||
this._worldNames['cargo.uwpDep'] = w.name || '';
|
||||
this._worldNames['trade.uwp'] = w.name || '';
|
||||
if (w.uwp) this._formData.pax.uwpDep = w.uwp;
|
||||
if (w.zone) this._formData.pax.zoneDep = w.zone;
|
||||
if (w.uwp) this._formData.cargo.uwpDep = w.uwp;
|
||||
if (w.zone) this._formData.cargo.zoneDep = w.zone;
|
||||
if (w.uwp) this._formData.trade.uwp = w.uwp;
|
||||
if (w.zone) this._formData.trade.zone = w.zone;
|
||||
this._activeTab = 'trade';
|
||||
}
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
_registerHandlebarsHelpers();
|
||||
return {
|
||||
const ctx = {
|
||||
...this._formData,
|
||||
activeActor: buildActiveActorContext(),
|
||||
activeTab: this._activeTab,
|
||||
};
|
||||
if (this._defaultWorld) {
|
||||
ctx.defaultWorldName = this._defaultWorld.name;
|
||||
ctx.defaultWorldLoc = `${this._defaultWorld.sector || ''} ${this._defaultWorld.hex || ''}`.trim();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
async _onRender(context, options) {
|
||||
@@ -128,6 +149,12 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
this._bindWorldSearch(html);
|
||||
|
||||
// Pré-remplir les champs recherche avec le nom du monde
|
||||
if (this._defaultWorld?.name) {
|
||||
html.find('.world-search-widget[data-role="dep"] .world-search-input, .world-block-full .world-search-input')
|
||||
.val(this._defaultWorld.name);
|
||||
}
|
||||
|
||||
html.on('click', (ev) => {
|
||||
if (!$(ev.target).closest('.world-search-widget').length) {
|
||||
html.find('.world-search-results').empty();
|
||||
@@ -204,6 +231,7 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
$li.on('click', async () => {
|
||||
$results.empty();
|
||||
$input.val(w.name);
|
||||
if (uwpTarget) this._worldNames[uwpTarget] = w.name;
|
||||
const [detail, coords] = await Promise.all([
|
||||
fetchWorldDetail(w.sector, w.hex).catch(() => null),
|
||||
fetchWorldCoordinates(w.sector, w.hex).catch(() => null),
|
||||
@@ -222,11 +250,13 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
html.find('[name="cargo.uwpDep"]').val(resolvedUwp);
|
||||
html.find('[name="cargo.zoneDep"]').val(resolvedZone);
|
||||
$cargoDep.data('coords', coords);
|
||||
this._worldNames['cargo.uwpDep'] = w.name;
|
||||
|
||||
const $tradeWorld = html.find('.world-search-widget[data-uwp-target="trade.uwp"]');
|
||||
$tradeWorld.find('.world-search-input').val(w.name);
|
||||
html.find('[name="trade.uwp"]').val(resolvedUwp);
|
||||
html.find('[name="trade.zone"]').val(resolvedZone);
|
||||
this._worldNames['trade.uwp'] = w.name;
|
||||
}
|
||||
|
||||
if (parsecsTarget) {
|
||||
@@ -370,6 +400,8 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
});
|
||||
|
||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||
result.dep = { ...result.dep, name: this._worldNames['pax.uwpDep'] || '' };
|
||||
result.dest = { ...result.dest, name: this._worldNames['pax.uwpDest'] || '' };
|
||||
await this._postToChatResult(result);
|
||||
}
|
||||
|
||||
@@ -394,6 +426,8 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||
result.cargoRevenue = result.lots.reduce((s, l) => s + l.revenue, 0);
|
||||
result.dep = { ...result.dep, name: this._worldNames['cargo.uwpDep'] || '' };
|
||||
result.dest = { ...result.dest, name: this._worldNames['cargo.uwpDest'] || '' };
|
||||
await this._postToChatResult(result);
|
||||
}
|
||||
|
||||
@@ -413,6 +447,7 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||
|
||||
result.world = { ...result.world, name: this._worldNames['trade.uwp'] || '' };
|
||||
this._tradeGoods = result;
|
||||
const goodsDiv = html.find('.trade-goods-result');
|
||||
const listDiv = html.find('.trade-goods-list');
|
||||
|
||||
+129
-52
@@ -5,6 +5,9 @@
|
||||
* Les clics sur la carte affichent les détails du monde dans le chat.
|
||||
*/
|
||||
|
||||
import { searchWorlds } from './travellerMapApi.js';
|
||||
import { TravelDialog } from './travelDialog.js';
|
||||
|
||||
const { ApplicationV2 } = foundry.applications.api;
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
@@ -21,9 +24,12 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
this._sector = sector;
|
||||
this._subsector = subsector;
|
||||
this._handler = null;
|
||||
this._mapHex = null;
|
||||
this._searchTimeout = null;
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (!this._sector) return 'Carte stellaire — Traveller Map';
|
||||
return this._subsector
|
||||
? `Sous-secteur ${this._subsector} — ${this._sector}`
|
||||
: `Secteur ${this._sector}`;
|
||||
@@ -31,10 +37,15 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
|
||||
get _mapUrl() {
|
||||
const base = 'https://travellermap.com';
|
||||
if (!this._sector) return `${base}/?style=mongoose&hideui=1`;
|
||||
if (this._subsector) {
|
||||
return `${base}/?sector=${encodeURIComponent(this._sector)}&subsector=${encodeURIComponent(this._subsector)}&style=mongoose&hideui=1`;
|
||||
}
|
||||
return `${base}/go/${encodeURIComponent(this._sector)}?style=mongoose`;
|
||||
let url = `${base}/go/${encodeURIComponent(this._sector)}?style=mongoose`;
|
||||
if (this._mapHex) {
|
||||
url += `&hex=${this._mapHex}&scale=512`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/* ───── Rendu ───── */
|
||||
@@ -49,10 +60,15 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
_renderHTML() {
|
||||
return `<div class="mgt2-sector-map-outer">
|
||||
<div class="mgt2-sector-map-toolbar">
|
||||
<span class="mgt2-sector-map-label">${this._sector}${this._subsector ? ` — sous-secteur ${this._subsector}` : ''}</span>
|
||||
<div class="mgt2-sector-map-search">
|
||||
<input type="text" class="mgt2-sector-map-input" placeholder="Rechercher un monde…" autocomplete="off">
|
||||
<ul class="mgt2-sector-map-results"></ul>
|
||||
</div>
|
||||
<span class="mgt2-sector-map-label">${this._sector ? `${this._sector}${this._subsector ? ` — ss.${this._subsector}` : ''}` : 'Toute la carte'}</span>
|
||||
<span class="mgt2-sector-map-hint">Cliquez sur un hex pour voir les détails du monde</span>
|
||||
<button type="button" class="mgt2-sector-map-share" title="Partager une image fixe dans le chat"><i class="fas fa-image"></i> Partager</button>
|
||||
<button type="button" class="mgt2-sector-map-sync" title="Ouvrir la carte interactive chez tous les joueurs"><i class="fas fa-users"></i> Synchroniser</button>
|
||||
<button type="button" class="mgt2-sector-map-travel" title="Planifier un voyage"><i class="fas fa-route"></i> Voyage</button>
|
||||
</div>
|
||||
<iframe
|
||||
src="${this._mapUrl}"
|
||||
@@ -71,6 +87,24 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
this.element?.querySelector('.mgt2-sector-map-sync')?.addEventListener('click', () => {
|
||||
this._syncAll();
|
||||
});
|
||||
this.element?.querySelector('.mgt2-sector-map-travel')?.addEventListener('click', () => {
|
||||
this._openTravelDialog();
|
||||
});
|
||||
|
||||
const input = this.element?.querySelector('.mgt2-sector-map-input');
|
||||
const results = this.element?.querySelector('.mgt2-sector-map-results');
|
||||
if (input && results) {
|
||||
input.addEventListener('input', () => {
|
||||
if (this._searchTimeout) clearTimeout(this._searchTimeout);
|
||||
this._searchTimeout = setTimeout(() => this._doSearch(input, results), 300);
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
setTimeout(() => { results.innerHTML = ''; }, 200);
|
||||
});
|
||||
input.addEventListener('focus', () => {
|
||||
if (input.value.trim().length >= 2) this._doSearch(input, results);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ───── Écoute des clics IFRAME ───── */
|
||||
@@ -131,7 +165,7 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
/* ───── Carte de chat ───── */
|
||||
|
||||
static _STARPORT = { A:'Excellent', B:'Bon', C:'Routinier', D:'Médiocre', E:'Frontière', X:'Aucun' };
|
||||
static _SIZE = ['Aucun (Astéroïde)','1 600 km','3 200 km','4 800 km','6 400 km','8 000 km','9 600 km','11 200 km','12 800 km','14 400 km','16 000 km']; // A=10, F=15=Gaz géant (géré à part)
|
||||
static _SIZE = ['Aucun (Astéroïde)','1 600 km','3 200 km','4 800 km','6 400 km','8 000 km','9 600 km','11 200 km','12 800 km','14 400 km','16 000 km'];
|
||||
static _ATMO = [
|
||||
'Aucune (vide)','Trace','Très ténue (polluée)','Très ténue','Ténue (polluée)','Ténue','Standard','Standard (polluée)','Dense','Dense (polluée)',
|
||||
'Exotique','Corrosive','Insidieuse','','',''];
|
||||
@@ -164,7 +198,7 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
return `<span class="uwp-dig" title="${desc}">${val}</span>`;
|
||||
}
|
||||
|
||||
_uwpBreakdown(uwp) {
|
||||
static _uwpBreakdown(uwp) {
|
||||
if (!uwp || uwp.length < 2) return '';
|
||||
const d = SectorMapApp._hexVal;
|
||||
const s = d(uwp[0]), sz = d(uwp[1]), a = d(uwp[2]), h = d(uwp[3]);
|
||||
@@ -237,22 +271,12 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
static _STAR_TYPES = { O:'Bleu (hypergéante)', B:'Bleu-blanc', A:'Blanc', F:'Blanc-jaune', G:'Jaune (naine)', K:'Orange (naine)', M:'Rouge (naine)', L:'Brune', T:'Brune', Y:'Brune' };
|
||||
static _STAR_CLASS = { 'I':'Supergéante', 'II':'Géante brillante', 'III':'Géante', 'IV':'Sous-géante', 'V':'Naine (séquence principale)', 'VI':'Sous-naine', 'VII':'Naine blanche' };
|
||||
|
||||
|
||||
|
||||
static _BASES_HELP = {
|
||||
N:'Base navale', S:'Base scout', W:'Relais', D:'Dépôt naval',
|
||||
T:'Base TAS', C:'Consulat', P:'Base pirate', R:'Base de réparation',
|
||||
K:'Base navale (K.)', X:'Relais Xboat',
|
||||
};
|
||||
|
||||
_tooltipHelp(map, codes) {
|
||||
if (!codes) return '';
|
||||
return codes.split(/[\s,;]+/).map(c => {
|
||||
const desc = map[c.toUpperCase()];
|
||||
return `${c}${desc ? ' — ' + desc : ''}`;
|
||||
}).join(' | ');
|
||||
}
|
||||
|
||||
static _NOBILITY = {
|
||||
B:'Chevalier (Baronet)', C:'Baron', D:'Marquis', E:'Comte', F:'Duc', G:'Archiduc', H:'Empereur',
|
||||
};
|
||||
@@ -263,9 +287,7 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
'5':'Majeure', '6':'Capitale',
|
||||
};
|
||||
|
||||
/* ───── Lignes dépliables (details/summary) ───── */
|
||||
|
||||
_foldRow(label, value, detail, titleAttr) {
|
||||
static _foldRow(label, value, detail, titleAttr) {
|
||||
const valAttr = titleAttr ? ` title="${titleAttr}"` : '';
|
||||
return `<tr><td colspan="2">
|
||||
<details>
|
||||
@@ -275,21 +297,21 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
</td></tr>`;
|
||||
}
|
||||
|
||||
_decodeImportance(ix) {
|
||||
static _decodeImportance(ix) {
|
||||
if (!ix) return '';
|
||||
const m = String(ix).match(/\{?\s*(-?\d+)\s*\}?/);
|
||||
if (!m) return this._foldRow('Importance', ix, '');
|
||||
if (!m) return SectorMapApp._foldRow('Importance', ix, '');
|
||||
const val = m[1];
|
||||
const desc = SectorMapApp._IMPORTANCE[val] || '—';
|
||||
const detail = `<div class="fold-desc">Valeur d’importance économique et stratégique du monde.<br>${val} = ${desc}</div>`;
|
||||
return this._foldRow('Importance', `{ ${val} } ${desc}`, detail);
|
||||
return SectorMapApp._foldRow('Importance', `{ ${val} } ${desc}`, detail);
|
||||
}
|
||||
|
||||
_decodeEconomics(ex) {
|
||||
static _decodeEconomics(ex) {
|
||||
if (!ex) return '';
|
||||
let s = String(ex).replace(/[()\s]/g, '');
|
||||
const m = s.match(/^([\dA-F])([\dA-F])([\dA-F])([+-]\d+)$/i);
|
||||
if (!m) return this._foldRow('Économie', ex, '');
|
||||
if (!m) return SectorMapApp._foldRow('Économie', ex, '');
|
||||
const res = m[1], lab = m[2], inf = m[3], eff = m[4];
|
||||
const detail = `<table class="fold-subtable">
|
||||
<tr><td>Ressources</td><td>${res}</td></tr>
|
||||
@@ -297,13 +319,13 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
<tr><td>Infrastructure</td><td>${inf}</td></tr>
|
||||
<tr><td>Efficacité</td><td>${eff}</td></tr>
|
||||
</table>`;
|
||||
return this._foldRow('Économie', `( ${res} ${lab} ${inf} ${eff} )`, detail);
|
||||
return SectorMapApp._foldRow('Économie', `( ${res} ${lab} ${inf} ${eff} )`, detail);
|
||||
}
|
||||
|
||||
_decodeCulture(cx) {
|
||||
static _decodeCulture(cx) {
|
||||
if (!cx) return '';
|
||||
const s = String(cx).replace(/[\[\]\s]/g, '');
|
||||
if (s.length < 4) return this._foldRow('Culture', cx, '');
|
||||
if (s.length < 4) return SectorMapApp._foldRow('Culture', cx, '');
|
||||
const h = s[0].toUpperCase(), t = s[1].toUpperCase(), p = s[2].toUpperCase(), a = s[3].toUpperCase();
|
||||
const detail = `<table class="fold-subtable">
|
||||
<tr><td>Hétérogénéité</td><td>${h}</td></tr>
|
||||
@@ -311,10 +333,10 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
<tr><td>Progressisme</td><td>${p}</td></tr>
|
||||
<tr><td>Agressivité</td><td>${a}</td></tr>
|
||||
</table>`;
|
||||
return this._foldRow('Culture', `[ ${h} ${t} ${p} ${a} ]`, detail);
|
||||
return SectorMapApp._foldRow('Culture', `[ ${h} ${t} ${p} ${a} ]`, detail);
|
||||
}
|
||||
|
||||
_decodePopulation(uwp, pbg) {
|
||||
static _decodePopulation(uwp, pbg) {
|
||||
if (!uwp || uwp.length < 5) return '';
|
||||
const popUwp = SectorMapApp._hexVal(uwp[4]);
|
||||
if (popUwp < 0) return '';
|
||||
@@ -332,22 +354,22 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
const gas = pbg ? parseInt(pbg[2], 10) : null;
|
||||
const detail = `<div class="fold-desc">Population = multiplicateur (PBG: <b>${popPbg}</b>) × 10<sup>chiffre UWP (${uwp[4]})</sup><br>
|
||||
Ceintures d’astéroïdes : <b>${belts ?? '?'}</b> | Géantes gazeuses : <b>${gas ?? '?'}</b></div>`;
|
||||
return this._foldRow('Population', `${fmtMult} × ${fmtBase} = ${fmtTotal}`, detail);
|
||||
return SectorMapApp._foldRow('Population', `${fmtMult} × ${fmtBase} = ${fmtTotal}`, detail);
|
||||
}
|
||||
|
||||
_decodeNobility(nob) {
|
||||
static _decodeNobility(nob) {
|
||||
if (!nob) return '';
|
||||
const titles = [];
|
||||
for (const ch of nob) {
|
||||
const desc = SectorMapApp._NOBILITY[ch.toUpperCase()];
|
||||
if (desc) titles.push(`${ch} (${desc})`);
|
||||
}
|
||||
if (!titles.length) return this._foldRow('Noblesse', nob, '');
|
||||
if (!titles.length) return SectorMapApp._foldRow('Noblesse', nob, '');
|
||||
const detail = `<div class="fold-desc">Titres de noblesse impériale présents sur ce monde.</div>`;
|
||||
return this._foldRow('Noblesse', titles.join(', '), detail);
|
||||
return SectorMapApp._foldRow('Noblesse', titles.join(', '), detail);
|
||||
}
|
||||
|
||||
_postWorldCard(w) {
|
||||
static _buildWorldCardHTML(w) {
|
||||
const sector = w.Sector || '';
|
||||
const hex = w.Hex || '';
|
||||
const name = w.Name || '—';
|
||||
@@ -363,26 +385,22 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
: zone === 'A' ? 'Ambre'
|
||||
: 'Verte';
|
||||
|
||||
// Construction des lignes pliables
|
||||
const lines = [];
|
||||
|
||||
// UWP
|
||||
const uwpDetail = this._uwpBreakdown(uwp);
|
||||
lines.push(this._foldRow('UWP', `<span class="mono">${uwp}</span>`, uwpDetail));
|
||||
const uwpDetail = SectorMapApp._uwpBreakdown(uwp);
|
||||
lines.push(SectorMapApp._foldRow('UWP', `<span class="mono">${uwp}</span>`, uwpDetail));
|
||||
|
||||
// Champs étendus
|
||||
const ixRow = this._decodeImportance(w.Ix);
|
||||
const ixRow = SectorMapApp._decodeImportance(w.Ix);
|
||||
if (ixRow) lines.push(ixRow);
|
||||
const exRow = this._decodeEconomics(w.Ex);
|
||||
const exRow = SectorMapApp._decodeEconomics(w.Ex);
|
||||
if (exRow) lines.push(exRow);
|
||||
const cxRow = this._decodeCulture(w.Cx);
|
||||
const cxRow = SectorMapApp._decodeCulture(w.Cx);
|
||||
if (cxRow) lines.push(cxRow);
|
||||
const popRow = this._decodePopulation(uwp, pbg);
|
||||
const popRow = SectorMapApp._decodePopulation(uwp, pbg);
|
||||
if (popRow) lines.push(popRow);
|
||||
const nobRow = this._decodeNobility(w.Nobility);
|
||||
const nobRow = SectorMapApp._decodeNobility(w.Nobility);
|
||||
if (nobRow) lines.push(nobRow);
|
||||
|
||||
// Bases
|
||||
if (bases) {
|
||||
const bCodes = bases.split(/[\s,;]+/);
|
||||
const bList = bCodes.map(c => {
|
||||
@@ -390,10 +408,9 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
return `<tr><td class="mono">${c}</td><td>${desc || '—'}</td></tr>`;
|
||||
}).join('');
|
||||
const bDetail = `<table class="fold-subtable"><tbody>${bList}</tbody></table>`;
|
||||
lines.push(this._foldRow('Bases', bases, bDetail));
|
||||
lines.push(SectorMapApp._foldRow('Bases', bases, bDetail));
|
||||
}
|
||||
|
||||
// Remarques
|
||||
if (remarks) {
|
||||
const rCodes = remarks.split(/[\s,;]+/);
|
||||
const rList = rCodes.map(c => {
|
||||
@@ -401,17 +418,15 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
return `<tr><td class="mono">${c}</td><td>${desc || '—'}</td></tr>`;
|
||||
}).join('');
|
||||
const rDetail = `<table class="fold-subtable"><tbody>${rList}</tbody></table>`;
|
||||
lines.push(this._foldRow('Remarques', remarks, rDetail));
|
||||
lines.push(SectorMapApp._foldRow('Remarques', remarks, rDetail));
|
||||
}
|
||||
|
||||
// Allégeance
|
||||
if (allegiance) {
|
||||
const allegFull = w.AllegianceName || '';
|
||||
const aDetail = `<div class="fold-desc">${allegFull || allegiance}</div>`;
|
||||
lines.push(this._foldRow('Allégeance', allegFull ? `${allegiance} (${allegFull})` : allegiance, aDetail));
|
||||
lines.push(SectorMapApp._foldRow('Allégeance', allegFull ? `${allegiance} (${allegFull})` : allegiance, aDetail));
|
||||
}
|
||||
|
||||
// Étoile (les notations comme "G3 V" contiennent un espace entre sous-classe et luminosité)
|
||||
if (stellar) {
|
||||
const sList = [];
|
||||
let remaining = stellar.trim();
|
||||
@@ -424,34 +439,89 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
sList.push(`<tr><td class="mono">${m[1]}${m[2]} ${m[3]}</td><td>${type} · ${cls}</td></tr>`);
|
||||
remaining = remaining.slice(m[0].length).trimStart();
|
||||
} else {
|
||||
// Saute les tokens non reconnus (BD, compagnon, etc.)
|
||||
const next = remaining.indexOf(' ');
|
||||
if (next < 0) break;
|
||||
remaining = remaining.slice(next + 1).trimStart();
|
||||
}
|
||||
}
|
||||
const sDetail = sList.length ? `<table class="fold-subtable"><tbody>${sList.join('')}</tbody></table>` : `<div class="fold-desc">${stellar}</div>`;
|
||||
lines.push(this._foldRow('Étoile', `<span class="mono">${stellar}</span>`, sDetail));
|
||||
lines.push(SectorMapApp._foldRow('Étoile', `<span class="mono">${stellar}</span>`, sDetail));
|
||||
}
|
||||
|
||||
const html = `<section class="mgt2-world-card">
|
||||
return `<section class="mgt2-world-card">
|
||||
<div class="mgt2-world-card-header">
|
||||
<span class="mgt2-world-name">${name}</span>
|
||||
<span class="mgt2-world-hex">${sector} ${hex}</span>
|
||||
<span class="mgt2-world-zone zone-${zone.toLowerCase() || 'g'}">${zoneLabel}</span>
|
||||
</div>
|
||||
<table class="mgt2-world-card-body"><tbody>${lines.join('')}</tbody></table>
|
||||
<div class="mgt2-world-card-actions">
|
||||
<a class="mgt2-world-commerce" data-sector="${this._escapeAttr(sector)}" data-hex="${hex}" data-uwp="${uwp}" data-zone="${zone || 'normal'}" data-name="${this._escapeAttr(name)}">
|
||||
<i class="fas fa-balance-scale"></i> Commerce
|
||||
</a>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
static _escapeAttr(str) {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
_postWorldCard(w) {
|
||||
const html = SectorMapApp._buildWorldCardHTML(w);
|
||||
ChatMessage.create({
|
||||
content: html,
|
||||
whisper: [game.user.id],
|
||||
});
|
||||
}
|
||||
|
||||
/* ───── Recherche de monde ───── */
|
||||
|
||||
async _doSearch(input, results) {
|
||||
const query = input.value.trim();
|
||||
if (query.length < 2) { results.innerHTML = ''; return; }
|
||||
const worlds = await searchWorlds(query);
|
||||
if (!worlds.length) {
|
||||
results.innerHTML = '<li class="no-result">Aucun monde trouvé</li>';
|
||||
return;
|
||||
}
|
||||
results.innerHTML = worlds.slice(0, 12).map(w =>
|
||||
`<li data-sector="${w.sector}" data-hex="${w.hex}" data-name="${w.name}">
|
||||
<span class="world-name">${w.name}</span>
|
||||
<span class="world-uwp">${w.uwp}</span>
|
||||
<span class="world-sector">${w.sector}</span>
|
||||
</li>`
|
||||
).join('');
|
||||
results.querySelectorAll('li[data-sector]').forEach(li => {
|
||||
li.addEventListener('click', () => this._selectWorld(li, input, results));
|
||||
});
|
||||
}
|
||||
|
||||
async _selectWorld(li, input, results) {
|
||||
const sector = li.dataset.sector;
|
||||
const hex = li.dataset.hex;
|
||||
const name = li.dataset.name;
|
||||
results.innerHTML = '';
|
||||
input.value = name;
|
||||
|
||||
this._sector = sector;
|
||||
this._subsector = null;
|
||||
this._mapHex = hex;
|
||||
|
||||
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
|
||||
if (iframe) iframe.src = this._mapUrl;
|
||||
|
||||
ui.notifications.info(`Carte centrée sur ${name} (${sector} ${hex})`);
|
||||
}
|
||||
|
||||
/* ───── Partage ───── */
|
||||
|
||||
_shareMap() {
|
||||
if (!this._sector) {
|
||||
ui.notifications.warn('Aucun secteur sélectionné — utilisez la recherche pour centrer sur un secteur');
|
||||
return;
|
||||
}
|
||||
const posterUrl = `https://travellermap.com/api/poster?sector=${encodeURIComponent(this._sector)}${this._subsector ? `&subsector=${encodeURIComponent(this._subsector)}` : ''}&style=mongoose&scale=128&dpr=2`;
|
||||
const label = this._subsector
|
||||
? `Sous-secteur ${this._subsector} — ${this._sector}`
|
||||
@@ -484,6 +554,13 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
ui.notifications.info(`Carte synchronisée chez tous les joueurs`);
|
||||
}
|
||||
|
||||
_openTravelDialog() {
|
||||
const existing = Object.values(ui.windows).find(w => w.id === 'mgt2-travel-dialog');
|
||||
if (existing) { existing.bringToTop(); return; }
|
||||
const dialog = new TravelDialog();
|
||||
dialog.render({ force: true });
|
||||
}
|
||||
|
||||
/* ───── Nettoyage ───── */
|
||||
|
||||
close() {
|
||||
|
||||
+72
-10
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
import { SectorMapApp } from './SectorMapApp.js';
|
||||
import { postWorldCardToChat } from './worldCard.js';
|
||||
import { CommerceDialog } from './CommerceDialog.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
const ChatLogV2 = foundry.applications?.sidebar?.tabs?.ChatLog;
|
||||
@@ -23,15 +25,11 @@ async function handleSectorCommand(sector, subsector) {
|
||||
if (_pendingHandle) return;
|
||||
_pendingHandle = true;
|
||||
try {
|
||||
if (!sector?.trim()) {
|
||||
ui.notifications.warn('Usage : /sector <nom du secteur> (ex: /sector "Spinward Marches")');
|
||||
return;
|
||||
}
|
||||
if (!game.user?.isGM) {
|
||||
ui.notifications.error('Seul le MJ peut utiliser cette commande');
|
||||
return;
|
||||
}
|
||||
await openMap(sector.trim());
|
||||
await openMap(sector?.trim());
|
||||
} catch (err) {
|
||||
console.error(`${MODULE_ID} | Erreur /sector :`, err);
|
||||
ui.notifications.error(`Erreur : ${err.message}`);
|
||||
@@ -40,6 +38,14 @@ async function handleSectorCommand(sector, subsector) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSystemCommand(sector, hex) {
|
||||
if (!sector || !hex) {
|
||||
ui.notifications.warn('Usage : /system <secteur> <hex> (ex: /system "Spinward Marches" 1910)');
|
||||
return;
|
||||
}
|
||||
await postWorldCardToChat(sector, hex);
|
||||
}
|
||||
|
||||
async function handleSubsectorCommand(raw) {
|
||||
if (_pendingHandle) return;
|
||||
_pendingHandle = true;
|
||||
@@ -75,11 +81,8 @@ if (ChatLogV2?.CHAT_COMMANDS) {
|
||||
rgx: /^\/sector(?:\s+(.*))?$/i,
|
||||
fn: function() {
|
||||
const raw = arguments[1]?.[1]?.trim?.();
|
||||
if (raw) {
|
||||
handleSectorCommand(raw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
handleSectorCommand(raw || undefined);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
console.log(`${MODULE_ID} | Commande /sector enregistrée`);
|
||||
@@ -96,6 +99,20 @@ if (ChatLogV2?.CHAT_COMMANDS) {
|
||||
},
|
||||
};
|
||||
console.log(`${MODULE_ID} | Commande /subsector enregistrée`);
|
||||
|
||||
ChatLogV2.CHAT_COMMANDS['system'] = {
|
||||
rgx: /^\/system\s+(.+?)\s+(\d{4})\s*$/i,
|
||||
fn: function() {
|
||||
const sector = arguments[1]?.[1]?.trim?.();
|
||||
const hex = arguments[1]?.[2]?.trim?.();
|
||||
if (sector && hex) {
|
||||
handleSystemCommand(sector, hex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
console.log(`${MODULE_ID} | Commande /system enregistrée`);
|
||||
}
|
||||
|
||||
/* ───── Hooks de secours (v13 / fallback v14) ───── */
|
||||
@@ -114,6 +131,12 @@ Hooks.on('preCreateChatMessage', (message, data, options) => {
|
||||
handleSubsectorCommand(m[1]?.trim());
|
||||
return false;
|
||||
}
|
||||
|
||||
m = c?.match(/^\/system\s+(.+?)\s+(\d{4})\s*$/i);
|
||||
if (m) {
|
||||
handleSystemCommand(m[1]?.trim(), m[2]?.trim());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/* ───── Socket (synchronisation MJ → joueurs) ───── */
|
||||
@@ -124,6 +147,39 @@ Hooks.once('ready', () => {
|
||||
if (game.user?.isGM) return;
|
||||
openMap(data.sector, data.subsector);
|
||||
});
|
||||
|
||||
// Clics sur les liens de monde dans les journals de trajet
|
||||
document.addEventListener('click', (event) => {
|
||||
const link = event.target.closest('.mgt2-world-link');
|
||||
if (!link) return;
|
||||
event.preventDefault();
|
||||
const sector = link.dataset.sector;
|
||||
const hex = link.dataset.hex;
|
||||
if (sector && hex) {
|
||||
handleSystemCommand(sector, hex);
|
||||
}
|
||||
});
|
||||
|
||||
// Clics sur le bouton Commerce dans les cartes de monde
|
||||
document.addEventListener('click', (event) => {
|
||||
const btn = event.target.closest('.mgt2-world-commerce');
|
||||
if (!btn) return;
|
||||
event.preventDefault();
|
||||
const uwp = btn.dataset.uwp;
|
||||
const zone = btn.dataset.zone;
|
||||
const name = btn.dataset.name;
|
||||
const sector = btn.dataset.sector;
|
||||
const hex = btn.dataset.hex;
|
||||
if (uwp) {
|
||||
const existing = Object.values(ui.windows).find(w => w.id === 'mgt2-commerce');
|
||||
if (existing) { existing.bringToTop(); return; }
|
||||
const dialog = new CommerceDialog({
|
||||
defaultWorld: { uwp, zone, name, sector, hex },
|
||||
initialTab: 'trade',
|
||||
});
|
||||
dialog.render({ force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on('chatMessage', (...args) => {
|
||||
@@ -143,4 +199,10 @@ Hooks.on('chatMessage', (...args) => {
|
||||
handleSubsectorCommand(m[1]?.trim());
|
||||
return false;
|
||||
}
|
||||
|
||||
m = msg?.trim()?.match(/^\/system\s+(.+?)\s+(\d{4})\s*$/i);
|
||||
if (m) {
|
||||
handleSystemCommand(m[1]?.trim(), m[2]?.trim());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
import { searchWorlds, calcParsecs } from './travellerMapApi.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
function cleanSectorName(sector) {
|
||||
return sector?.replace(/\s*\([^)]*\)\s*$/, '').trim() || sector;
|
||||
}
|
||||
|
||||
function worldCoord(sx, sy, hx, hy) {
|
||||
return { x: (sx - 0) * 32 + (hx - 1), y: (sy - 0) * 40 + (hy - 40) };
|
||||
}
|
||||
|
||||
export class TravelDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'mgt2-travel-dialog',
|
||||
classes: ['mgt2-travel-dialog'],
|
||||
position: { width: 600, height: 500 },
|
||||
window: { icon: 'fas fa-route', title: 'Planificateur de voyage', resizable: true, controls: [] },
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: `modules/${MODULE_ID}/templates/travel-dialog.hbs`,
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._fromWorld = null;
|
||||
this._toWorld = null;
|
||||
this._lastWorlds = null;
|
||||
this._lastSegments = null;
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
this._wireSearch('from');
|
||||
this._wireSearch('to');
|
||||
this.element?.querySelector('[data-action="calculate"]')?.addEventListener('click', () => {
|
||||
this._calculateRoute();
|
||||
});
|
||||
this.element?.querySelector('[data-action="create-journal"]')?.addEventListener('click', () => {
|
||||
this._createJournal();
|
||||
});
|
||||
}
|
||||
|
||||
_wireSearch(prefix) {
|
||||
const input = this.element.querySelector(`[name="travel-${prefix}"]`);
|
||||
const results = this.element.querySelector(`.travel-${prefix}-results`);
|
||||
if (!input || !results) return;
|
||||
|
||||
let timeout = null;
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(timeout);
|
||||
const q = input.value.trim();
|
||||
if (q.length < 2) { results.innerHTML = ''; return; }
|
||||
timeout = setTimeout(async () => {
|
||||
const worlds = await searchWorlds(q);
|
||||
if (!worlds?.length) {
|
||||
results.innerHTML = '<li class="no-result">Aucun résultat</li>';
|
||||
return;
|
||||
}
|
||||
results.innerHTML = worlds.slice(0, 10).map(w =>
|
||||
`<li data-sector="${w.sector}" data-hex="${w.hex}" data-name="${w.name}">
|
||||
<span class="world-name">${w.name}</span>
|
||||
<span class="world-sector">${w.sector}</span>
|
||||
<span class="world-hex">${w.hex}</span>
|
||||
</li>`
|
||||
).join('');
|
||||
}, 300);
|
||||
});
|
||||
|
||||
results.addEventListener('click', (e) => {
|
||||
const li = e.target.closest('[data-sector]');
|
||||
if (!li) return;
|
||||
const data = { sector: li.dataset.sector, hex: li.dataset.hex, name: li.dataset.name };
|
||||
if (prefix === 'from') this._fromWorld = data;
|
||||
else this._toWorld = data;
|
||||
input.value = `${data.name} (${data.sector} ${data.hex})`;
|
||||
results.innerHTML = '';
|
||||
});
|
||||
|
||||
input.addEventListener('blur', () => {
|
||||
setTimeout(() => { results.innerHTML = ''; }, 200);
|
||||
});
|
||||
}
|
||||
|
||||
async _calculateRoute() {
|
||||
if (!this._fromWorld || !this._toWorld) {
|
||||
ui.notifications.warn('Veuillez sélectionner un monde de départ et un monde d\'arrivée');
|
||||
return;
|
||||
}
|
||||
|
||||
const jumpEl = this.element.querySelector('[name="travel-jump"]');
|
||||
const jump = parseInt(jumpEl?.value, 10) || 2;
|
||||
|
||||
const resultsEl = this.element.querySelector('.travel-results');
|
||||
if (!resultsEl) return;
|
||||
|
||||
resultsEl.innerHTML = '<div class="travel-loading"><i class="fas fa-spinner fa-spin"></i> Calcul de l\'itinéraire…</div>';
|
||||
|
||||
const startSector = cleanSectorName(this._fromWorld.sector);
|
||||
const endSector = cleanSectorName(this._toWorld.sector);
|
||||
const startLoc = `${startSector} ${this._fromWorld.hex}`;
|
||||
const endLoc = `${endSector} ${this._toWorld.hex}`;
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`https://travellermap.com/api/route?start=${encodeURIComponent(startLoc)}&end=${encodeURIComponent(endLoc)}&jump=${jump}`
|
||||
);
|
||||
|
||||
if (!resp.ok) {
|
||||
if (resp.status === 404) {
|
||||
const text = await resp.text().catch(() => 'Aucun itinéraire trouvé');
|
||||
resultsEl.innerHTML = `<div class="travel-error">${this._escapeHtml(text)}</div>`;
|
||||
} else {
|
||||
resultsEl.innerHTML = `<div class="travel-error">Erreur API (${resp.status})</div>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
if (!Array.isArray(data) || data.length < 2) {
|
||||
resultsEl.innerHTML = '<div class="travel-error">Aucun itinéraire trouvé</div>';
|
||||
return;
|
||||
}
|
||||
this._displayRoute(data, resultsEl);
|
||||
} catch (err) {
|
||||
console.error('TravelDialog | Erreur:', err);
|
||||
resultsEl.innerHTML = `<div class="travel-error">Erreur : ${this._escapeHtml(err.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
_displayRoute(worlds, resultsEl) {
|
||||
this._lastWorlds = worlds;
|
||||
const segments = [];
|
||||
let totalParsecs = 0;
|
||||
for (let i = 0; i < worlds.length - 1; i++) {
|
||||
const a = worlds[i];
|
||||
const b = worlds[i + 1];
|
||||
const fromC = worldCoord(a.SectorX, a.SectorY, a.HexX, a.HexY);
|
||||
const toC = worldCoord(b.SectorX, b.SectorY, b.HexX, b.HexY);
|
||||
const dist = calcParsecs(fromC, toC);
|
||||
totalParsecs += dist;
|
||||
segments.push({ from: a, to: b, dist });
|
||||
}
|
||||
this._lastSegments = segments;
|
||||
|
||||
let html = `<div class="travel-route-summary">
|
||||
<i class="fas fa-route"></i>
|
||||
<strong>${segments.length}</strong> saut${segments.length > 1 ? 's' : ''}
|
||||
· <strong>${totalParsecs}</strong> parsecs
|
||||
</div>`;
|
||||
|
||||
html += `<div class="travel-route-duration">
|
||||
<i class="fas fa-clock"></i> Durée estimée : <strong>${segments.length}</strong> semaine${segments.length > 1 ? 's' : ''}
|
||||
</div>`;
|
||||
|
||||
html += '<ol class="travel-jump-list">';
|
||||
segments.forEach((seg) => {
|
||||
const f = seg.from;
|
||||
const t = seg.to;
|
||||
html += `<li>
|
||||
<div class="jump-segment">
|
||||
<div class="jump-world jump-from">
|
||||
<span class="jump-world-name">${f.Name || '?'}</span>
|
||||
<span class="jump-world-detail">${f.Sector} ${f.Hex || ''}</span>
|
||||
</div>
|
||||
<div class="jump-arrow"><i class="fas fa-long-arrow-alt-right"></i></div>
|
||||
<div class="jump-world jump-to">
|
||||
<span class="jump-world-name">${t.Name || '?'}</span>
|
||||
<span class="jump-world-detail">${t.Sector} ${t.Hex || ''}</span>
|
||||
</div>
|
||||
<div class="jump-distance">Saut-${seg.dist}</div>
|
||||
</div>
|
||||
</li>`;
|
||||
});
|
||||
html += '</ol>';
|
||||
|
||||
resultsEl.innerHTML = html;
|
||||
|
||||
const journalBtn = this.element?.querySelector('.travel-journal-actions');
|
||||
if (journalBtn) journalBtn.style.display = 'block';
|
||||
}
|
||||
|
||||
async _createJournal() {
|
||||
const worlds = this._lastWorlds;
|
||||
const segments = this._lastSegments;
|
||||
if (!worlds?.length || !segments?.length) {
|
||||
ui.notifications.warn('Calculez d\'abord un itinéraire');
|
||||
return;
|
||||
}
|
||||
|
||||
const from = worlds[0];
|
||||
const to = worlds[worlds.length - 1];
|
||||
const totalParsecs = segments.reduce((s, seg) => s + seg.dist, 0);
|
||||
const totalJumps = segments.length;
|
||||
const jumpRating = this.element?.querySelector('[name="travel-jump"]')?.value || '?';
|
||||
|
||||
const lines = [];
|
||||
lines.push(`<h2>Journal de voyage</h2>`);
|
||||
lines.push(`<p><strong>Départ :</strong> ${this._worldLink(from)}</p>`);
|
||||
lines.push(`<p><strong>Destination :</strong> ${this._worldLink(to)}</p>`);
|
||||
lines.push(`<p><strong>Moteur :</strong> J-${jumpRating}</p>`);
|
||||
lines.push(`<hr>`);
|
||||
|
||||
lines.push(`<h3>Itinéraire (${totalJumps} saut${totalJumps > 1 ? 's' : ''}, ${totalParsecs} pc)</h3>`);
|
||||
lines.push(`<table><thead><tr><th>#</th><th>Départ</th><th>→</th><th>Arrivée</th><th>Distance</th></tr></thead><tbody>`);
|
||||
segments.forEach((seg, i) => {
|
||||
const f = seg.from;
|
||||
const t = seg.to;
|
||||
lines.push(`<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>${this._worldLink(f)}</td>
|
||||
<td>→</td>
|
||||
<td>${this._worldLink(t)}</td>
|
||||
<td>${seg.dist} pc</td>
|
||||
</tr>`);
|
||||
});
|
||||
lines.push(`</tbody></table>`);
|
||||
|
||||
lines.push(`<hr>`);
|
||||
lines.push(`<h3>Mondes visités</h3>`);
|
||||
lines.push(`<ul>`);
|
||||
const seen = new Set();
|
||||
for (const w of worlds) {
|
||||
const key = `${w.Sector}|${w.Hex}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
lines.push(`<li>${this._worldLink(w)}${w.UWP ? ` — UWP: ${w.UWP}` : ''}</li>`);
|
||||
}
|
||||
lines.push(`</ul>`);
|
||||
|
||||
const content = lines.join('\n');
|
||||
|
||||
try {
|
||||
const journal = await JournalEntry.create({
|
||||
name: `Voyage : ${from.Name || '?'} → ${to.Name || '?'}`,
|
||||
pages: [{
|
||||
name: 'Itinéraire',
|
||||
type: 'text',
|
||||
text: { content, format: 2 },
|
||||
}],
|
||||
});
|
||||
|
||||
if (journal) {
|
||||
ui.notifications.info(`Journal créé : ${journal.name}`);
|
||||
journal.sheet?.render(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('TravelDialog | Erreur création journal:', err);
|
||||
ui.notifications.error('Erreur lors de la création du journal');
|
||||
}
|
||||
}
|
||||
|
||||
_worldLink(w) {
|
||||
const name = w.Name || '?';
|
||||
const sector = w.Sector || '';
|
||||
const hex = w.Hex || '';
|
||||
return `<a class="mgt2-world-link" data-sector="${this._escapeAttr(sector)}" data-hex="${this._escapeAttr(hex)}">${name}</a> <em>(${sector} ${hex})</em>`;
|
||||
}
|
||||
|
||||
_escapeAttr(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/"/g, '"').replace(/&/g, '&');
|
||||
}
|
||||
|
||||
_escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
const d = document.createElement('div');
|
||||
d.textContent = str;
|
||||
return d.innerHTML;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Fonctions réutilisables pour afficher une carte de monde dans le chat.
|
||||
*/
|
||||
import { SectorMapApp } from './SectorMapApp.js';
|
||||
|
||||
const BASE_URL = 'https://travellermap.com';
|
||||
|
||||
/**
|
||||
* Récupère les données d'un monde via l'API et poste une carte détaillée dans le chat.
|
||||
* @param {string} sector Nom du secteur
|
||||
* @param {string} hex Code hex sur 4 chiffres
|
||||
* @param {object} [options] { whisper: bool }
|
||||
*/
|
||||
export async function postWorldCardToChat(sector, hex, options = {}) {
|
||||
if (!sector || !hex) {
|
||||
ui.notifications.error('Secteur et hex requis');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/data/${encodeURIComponent(sector)}/${encodeURIComponent(hex)}`;
|
||||
let resp;
|
||||
try {
|
||||
resp = await fetch(url);
|
||||
} catch (err) {
|
||||
console.error('worldCard | fetch error:', err);
|
||||
ui.notifications.error('Erreur réseau');
|
||||
return;
|
||||
}
|
||||
if (!resp.ok) {
|
||||
ui.notifications.error(`Monde introuvable : ${sector} ${hex}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
const world = data.Worlds?.[0];
|
||||
if (!world) {
|
||||
ui.notifications.error(`Aucune donnée pour ${sector} ${hex}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const html = SectorMapApp._buildWorldCardHTML(world);
|
||||
const msgData = { content: html };
|
||||
if (options.whisper !== false) msgData.whisper = [game.user.id];
|
||||
|
||||
await ChatMessage.create(msgData);
|
||||
}
|
||||
Reference in New Issue
Block a user