MAp management and helpers
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -32,3 +32,73 @@
|
||||
2026/06/01-22:51:23.050617 7f52c4bfb6c0 Level-0 table #188: started
|
||||
2026/06/01-22:51:23.083717 7f52c4bfb6c0 Level-0 table #188: 3847811 bytes OK
|
||||
2026/06/01-22:51:23.090754 7f52c4bfb6c0 Delete type=0 #185
|
||||
2026/06/01-22:54:31.041731 7f52c4bfb6c0 Level-0 table #190: started
|
||||
2026/06/01-22:54:31.075783 7f52c4bfb6c0 Level-0 table #190: 3898158 bytes OK
|
||||
2026/06/01-22:54:31.085709 7f52c4bfb6c0 Delete type=0 #187
|
||||
2026/06/01-22:56:43.017283 7f52c4bfb6c0 Level-0 table #192: started
|
||||
2026/06/01-22:56:43.056579 7f52c4bfb6c0 Level-0 table #192: 3942180 bytes OK
|
||||
2026/06/01-22:56:43.067120 7f52c4bfb6c0 Delete type=0 #189
|
||||
2026/06/01-22:56:43.067709 7f52c4bfb6c0 Compacting 4@0 + 2@1 files
|
||||
2026/06/01-22:56:43.096331 7f52c4bfb6c0 Generated table #193@0: 12110 keys, 2146786 bytes
|
||||
2026/06/01-22:56:43.123233 7f52c4bfb6c0 Generated table #194@0: 13796 keys, 1794145 bytes
|
||||
2026/06/01-22:56:43.123251 7f52c4bfb6c0 Compacted 4@0 + 2@1 files => 3940931 bytes
|
||||
2026/06/01-22:56:43.135664 7f52c4bfb6c0 compacted to: files[ 0 2 2 0 0 0 0 ]
|
||||
2026/06/01-22:56:43.136005 7f52c4bfb6c0 Delete type=2 #183
|
||||
2026/06/01-22:56:43.136216 7f52c4bfb6c0 Delete type=2 #184
|
||||
2026/06/01-22:56:43.136413 7f52c4bfb6c0 Delete type=2 #186
|
||||
2026/06/01-22:56:43.136721 7f52c4bfb6c0 Delete type=2 #188
|
||||
2026/06/01-22:56:43.137001 7f52c4bfb6c0 Delete type=2 #190
|
||||
2026/06/01-22:56:43.137270 7f52c4bfb6c0 Delete type=2 #192
|
||||
2026/06/01-23:02:49.561038 7f52c4bfb6c0 Level-0 table #196: started
|
||||
2026/06/01-23:02:49.590283 7f52c4bfb6c0 Level-0 table #196: 3996082 bytes OK
|
||||
2026/06/01-23:02:49.596507 7f52c4bfb6c0 Delete type=0 #191
|
||||
2026/06/01-23:05:28.996035 7f52c4bfb6c0 Level-0 table #198: started
|
||||
2026/06/01-23:05:29.028796 7f52c4bfb6c0 Level-0 table #198: 4042529 bytes OK
|
||||
2026/06/01-23:05:29.035710 7f52c4bfb6c0 Delete type=0 #195
|
||||
2026/06/01-23:11:22.779385 7f52c4bfb6c0 Level-0 table #200: started
|
||||
2026/06/01-23:11:22.817516 7f52c4bfb6c0 Level-0 table #200: 4095432 bytes OK
|
||||
2026/06/01-23:11:22.823424 7f52c4bfb6c0 Delete type=0 #197
|
||||
2026/06/01-23:19:54.339957 7f52c4bfb6c0 Level-0 table #202: started
|
||||
2026/06/01-23:19:54.367799 7f52c4bfb6c0 Level-0 table #202: 4146311 bytes OK
|
||||
2026/06/01-23:19:54.373753 7f52c4bfb6c0 Delete type=0 #199
|
||||
2026/06/01-23:19:54.374306 7f52c4bfb6c0 Compacting 4@0 + 2@1 files
|
||||
2026/06/01-23:19:54.395646 7f52c4bfb6c0 Generated table #203@0: 11893 keys, 2146843 bytes
|
||||
2026/06/01-23:19:54.419845 7f52c4bfb6c0 Generated table #204@0: 15341 keys, 1997992 bytes
|
||||
2026/06/01-23:19:54.419877 7f52c4bfb6c0 Compacted 4@0 + 2@1 files => 4144835 bytes
|
||||
2026/06/01-23:19:54.426071 7f52c4bfb6c0 compacted to: files[ 0 2 2 0 0 0 0 ]
|
||||
2026/06/01-23:19:54.426414 7f52c4bfb6c0 Delete type=2 #193
|
||||
2026/06/01-23:19:54.426875 7f52c4bfb6c0 Delete type=2 #194
|
||||
2026/06/01-23:19:54.427165 7f52c4bfb6c0 Delete type=2 #196
|
||||
2026/06/01-23:19:54.427644 7f52c4bfb6c0 Delete type=2 #198
|
||||
2026/06/01-23:19:54.428125 7f52c4bfb6c0 Delete type=2 #200
|
||||
2026/06/01-23:19:54.428618 7f52c4bfb6c0 Delete type=2 #202
|
||||
2026/06/01-23:23:22.212670 7f52c4bfb6c0 Level-0 table #206: started
|
||||
2026/06/01-23:23:22.246031 7f52c4bfb6c0 Level-0 table #206: 4199267 bytes OK
|
||||
2026/06/01-23:23:22.252324 7f52c4bfb6c0 Delete type=0 #201
|
||||
2026/06/01-23:32:01.046035 7f52c4bfb6c0 Level-0 table #208: started
|
||||
2026/06/01-23:32:01.083523 7f52c4bfb6c0 Level-0 table #208: 4245727 bytes OK
|
||||
2026/06/01-23:32:01.090732 7f52c4bfb6c0 Delete type=0 #205
|
||||
2026/06/01-23:34:50.675178 7f52c4bfb6c0 Level-0 table #210: started
|
||||
2026/06/01-23:34:50.708986 7f52c4bfb6c0 Level-0 table #210: 4300427 bytes OK
|
||||
2026/06/01-23:34:50.715366 7f52c4bfb6c0 Delete type=0 #207
|
||||
2026/06/01-23:37:05.691865 7f52c4bfb6c0 Level-0 table #212: started
|
||||
2026/06/01-23:37:05.728371 7f52c4bfb6c0 Level-0 table #212: 4351044 bytes OK
|
||||
2026/06/01-23:37:05.735021 7f52c4bfb6c0 Delete type=0 #209
|
||||
2026/06/01-23:37:05.735982 7f52c4bfb6c0 Compacting 4@0 + 2@1 files
|
||||
2026/06/01-23:37:05.756894 7f52c4bfb6c0 Generated table #213@0: 11588 keys, 2145171 bytes
|
||||
2026/06/01-23:37:05.783035 7f52c4bfb6c0 Generated table #214@0: 16666 keys, 2163321 bytes
|
||||
2026/06/01-23:37:05.787317 7f52c4bfb6c0 Generated table #215@0: 308 keys, 41171 bytes
|
||||
2026/06/01-23:37:05.787329 7f52c4bfb6c0 Compacted 4@0 + 2@1 files => 4349663 bytes
|
||||
2026/06/01-23:37:05.793772 7f52c4bfb6c0 compacted to: files[ 0 3 2 0 0 0 0 ]
|
||||
2026/06/01-23:37:05.793970 7f52c4bfb6c0 Delete type=2 #203
|
||||
2026/06/01-23:37:05.794138 7f52c4bfb6c0 Delete type=2 #204
|
||||
2026/06/01-23:37:05.794271 7f52c4bfb6c0 Delete type=2 #206
|
||||
2026/06/01-23:37:05.794472 7f52c4bfb6c0 Delete type=2 #208
|
||||
2026/06/01-23:37:05.794734 7f52c4bfb6c0 Delete type=2 #210
|
||||
2026/06/01-23:37:05.794942 7f52c4bfb6c0 Delete type=2 #212
|
||||
2026/06/01-23:54:42.760778 7f52c4bfb6c0 Level-0 table #217: started
|
||||
2026/06/01-23:54:42.788274 7f52c4bfb6c0 Level-0 table #217: 4406061 bytes OK
|
||||
2026/06/01-23:54:42.794452 7f52c4bfb6c0 Delete type=0 #211
|
||||
2026/06/02-00:07:09.406466 7f52c4bfb6c0 Level-0 table #219: started
|
||||
2026/06/02-00:07:09.441912 7f52c4bfb6c0 Level-0 table #219: 4455911 bytes OK
|
||||
2026/06/02-00:07:09.447798 7f52c4bfb6c0 Delete type=0 #216
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
@@ -734,3 +734,26 @@ button.btn-calculate:hover,
|
||||
vertical-align: super;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* Bandeau monde sélectionné (depuis carte de chat) */
|
||||
|
||||
.world-info-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #e8e0d0;
|
||||
border: 1px solid #c9a227;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.world-info-banner i {
|
||||
color: #c9a227;
|
||||
}
|
||||
|
||||
.world-info-banner .world-info-loc {
|
||||
color: #888;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
+453
-3
@@ -366,7 +366,92 @@ button.btn-calculate:hover,
|
||||
font-size: 0.78em;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-share {
|
||||
.mgt2-sector-map-search {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-input {
|
||||
width: 180px;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8em;
|
||||
border: 1px solid #555;
|
||||
border-radius: 3px;
|
||||
background: #2c2c3e;
|
||||
color: #d9b24c;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-input::placeholder {
|
||||
color: #7a755a;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-input:focus {
|
||||
border-color: #c9a227;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
background: #2c2c3e;
|
||||
border: 1px solid #c9a227;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
z-index: 999;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results li {
|
||||
padding: 5px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.78em;
|
||||
color: #d8c79a;
|
||||
border-bottom: 1px solid #3a3a50;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results li:hover {
|
||||
background: #3a3a50;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results .no-result {
|
||||
color: #7a755a;
|
||||
font-style: italic;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results .world-name {
|
||||
font-weight: bold;
|
||||
color: #d9b24c;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results .world-uwp {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: #a99c7a;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-results .world-sector {
|
||||
margin-left: auto;
|
||||
color: #7a755a;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-share,
|
||||
.mgt2-sector-map-sync,
|
||||
.mgt2-sector-map-travel {
|
||||
background: #c9a227;
|
||||
color: #1a1a2e;
|
||||
border: none;
|
||||
@@ -378,7 +463,9 @@ button.btn-calculate:hover,
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mgt2-sector-map-share:hover {
|
||||
.mgt2-sector-map-share:hover,
|
||||
.mgt2-sector-map-sync:hover,
|
||||
.mgt2-sector-map-travel:hover {
|
||||
background: #d9b24c;
|
||||
}
|
||||
|
||||
@@ -492,7 +579,36 @@ button.btn-calculate:hover,
|
||||
}
|
||||
|
||||
.mgt2-world-card-body summary:hover {
|
||||
background: rgba(201, 162, 39, 0.08);
|
||||
background: #eae4d4;
|
||||
}
|
||||
|
||||
.mgt2-world-card-actions {
|
||||
padding: 6px 12px 8px;
|
||||
text-align: right;
|
||||
border-top: 1px solid #ddd0bc;
|
||||
}
|
||||
|
||||
.mgt2-world-commerce {
|
||||
display: inline-block;
|
||||
padding: 4px 14px;
|
||||
background: #c9a227;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.mgt2-world-commerce:hover {
|
||||
background: #b89020;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mgt2-world-commerce i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.fold-label {
|
||||
@@ -604,3 +720,337 @@ button.btn-calculate:hover,
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════
|
||||
Travel Dialog (planificateur de voyage)
|
||||
══════════════════════════════════════════════════════ */
|
||||
|
||||
#mgt2-travel-dialog .window-content {
|
||||
background: #f5f0e8;
|
||||
font-family: 'Signika', sans-serif;
|
||||
color: #222;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.travel-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.travel-worlds {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.travel-world-block {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.travel-world-block label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.9em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.travel-world-block label i {
|
||||
margin-right: 4px;
|
||||
color: #c9a227;
|
||||
}
|
||||
|
||||
.travel-search-widget input {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #b5a68b;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
font-size: 0.9em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.travel-search-widget input:focus {
|
||||
outline: none;
|
||||
border-color: #c9a227;
|
||||
box-shadow: 0 0 4px rgba(201, 162, 39, 0.4);
|
||||
}
|
||||
|
||||
.travel-search-widget {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.travel-from-results,
|
||||
.travel-to-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border: 1px solid #b5a68b;
|
||||
border-top: none;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0 0 3px 3px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.travel-from-results li,
|
||||
.travel-to-results li {
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
font-size: 0.85em;
|
||||
color: #222;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.travel-from-results li:last-child,
|
||||
.travel-to-results li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.travel-from-results li:hover,
|
||||
.travel-to-results li:hover {
|
||||
background: #e8e0d0;
|
||||
}
|
||||
|
||||
.travel-from-results .no-result,
|
||||
.travel-to-results .no-result {
|
||||
color: #888;
|
||||
cursor: default;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.travel-from-results .world-name,
|
||||
.travel-to-results .world-name {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.travel-from-results .world-sector,
|
||||
.travel-to-results .world-sector {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.travel-from-results .world-hex,
|
||||
.travel-to-results .world-hex {
|
||||
color: #888;
|
||||
font-size: 0.8em;
|
||||
font-family: monospace;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.travel-jump-selector {
|
||||
flex: 0 0 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.travel-jump-selector label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.9em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.travel-jump-selector select {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
border: 1px solid #b5a68b;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.travel-actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.travel-actions button {
|
||||
padding: 8px 24px;
|
||||
background: #c9a227;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.travel-actions button:hover {
|
||||
background: #b89020;
|
||||
}
|
||||
|
||||
.travel-actions button i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.travel-results {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 60px;
|
||||
border-top: 1px solid #d4c9b8;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.travel-loading {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
padding: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.travel-loading i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.travel-error {
|
||||
padding: 12px 16px;
|
||||
background: #fce4e4;
|
||||
border: 1px solid #e8b4b4;
|
||||
border-radius: 4px;
|
||||
color: #a33;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.travel-route-summary {
|
||||
padding: 10px 14px;
|
||||
background: #e4eed4;
|
||||
border: 1px solid #b8d498;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.travel-route-summary i {
|
||||
margin-right: 6px;
|
||||
color: #5a8a2a;
|
||||
}
|
||||
|
||||
.travel-route-duration {
|
||||
padding: 6px 14px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.travel-route-duration i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.travel-jump-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.travel-jump-list li {
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #e8e0d0;
|
||||
}
|
||||
|
||||
.travel-jump-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.jump-segment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.jump-world {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.jump-world-name {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.jump-world-detail {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.jump-to {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.jump-arrow {
|
||||
flex: 0 0 24px;
|
||||
text-align: center;
|
||||
color: #c9a227;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.jump-distance {
|
||||
flex: 0 0 60px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 8px;
|
||||
background: #eee8d8;
|
||||
border-radius: 3px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Journal de trajet */
|
||||
|
||||
.travel-journal-actions {
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #d4c9b8;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.travel-journal-actions button {
|
||||
padding: 8px 24px;
|
||||
background: #5a7a2a;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.travel-journal-actions button:hover {
|
||||
background: #4a6822;
|
||||
}
|
||||
|
||||
.travel-journal-actions button i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
a.mgt2-world-link {
|
||||
color: #6a3a8a;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
|
||||
a.mgt2-world-link:hover {
|
||||
color: #8a4aaa;
|
||||
}
|
||||
|
||||
@@ -273,6 +273,14 @@
|
||||
<div class="tab {{#if (eq activeTab "trade")}}active{{/if}}" data-tab="trade">
|
||||
<h3><i class="fas fa-balance-scale"></i> Commerce spéculatif</h3>
|
||||
|
||||
{{#if defaultWorldName}}
|
||||
<div class="world-info-banner">
|
||||
<i class="fas fa-globe"></i>
|
||||
<strong>{{defaultWorldName}}</strong>
|
||||
{{#if defaultWorldLoc}}<span class="world-info-loc">{{defaultWorldLoc}}</span>{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="world-block world-block-full">
|
||||
<div class="world-block-title"><i class="fas fa-store"></i> Monde fournisseur</div>
|
||||
<div class="world-search-widget" data-uwp-target="trade.uwp" data-zone-target="trade.zone">
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
<div class="commerce-section">
|
||||
<p class="route">
|
||||
<span class="route-uwp">{{dep.uwp}}</span>
|
||||
<span class="route-world">{{#if dep.name}}{{dep.name}} — {{/if}}<span class="route-uwp">{{dep.uwp}}</span></span>
|
||||
<i class="fas fa-arrow-right route-arrow"></i>
|
||||
<span class="route-uwp">{{dest.uwp}}</span>
|
||||
<span class="route-world">{{#if dest.name}}{{dest.name}} — {{/if}}<span class="route-uwp">{{dest.uwp}}</span></span>
|
||||
<span class="route-parsecs">{{parsecs}} parsec{{#if (gt parsecs 1)}}s{{/if}}</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -67,9 +67,9 @@
|
||||
|
||||
<div class="commerce-section">
|
||||
<p class="route">
|
||||
<span class="route-uwp">{{dep.uwp}}</span>
|
||||
<span class="route-world">{{#if dep.name}}{{dep.name}} — {{/if}}<span class="route-uwp">{{dep.uwp}}</span></span>
|
||||
<i class="fas fa-arrow-right route-arrow"></i>
|
||||
<span class="route-uwp">{{dest.uwp}}</span>
|
||||
<span class="route-world">{{#if dest.name}}{{dest.name}} — {{/if}}<span class="route-uwp">{{dest.uwp}}</span></span>
|
||||
<span class="route-parsecs">{{parsecs}} parsec{{#if (gt parsecs 1)}}s{{/if}}</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -131,7 +131,9 @@
|
||||
|
||||
<div class="commerce-section">
|
||||
<p>
|
||||
<strong>Monde :</strong> <span class="route-uwp">{{world.uwp}}</span>
|
||||
<strong>Monde :</strong>
|
||||
{{#if world.name}}<span class="route-world">{{world.name}}</span> — {{/if}}
|
||||
<span class="route-uwp">{{world.uwp}}</span>
|
||||
|
|
||||
<strong>Codes :</strong>
|
||||
{{#if world.tradeCodes.length}}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<form class="travel-form">
|
||||
<div class="travel-worlds">
|
||||
<div class="travel-world-block">
|
||||
<label><i class="fas fa-rocket"></i> Monde de départ</label>
|
||||
<div class="travel-search-widget">
|
||||
<input type="text" name="travel-from" placeholder="Rechercher un monde…" autocomplete="off">
|
||||
<ul class="travel-from-results"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="travel-jump-selector">
|
||||
<label for="travel-jump">Saut</label>
|
||||
<select name="travel-jump">
|
||||
<option value="1">J-1</option>
|
||||
<option value="2" selected>J-2</option>
|
||||
<option value="3">J-3</option>
|
||||
<option value="4">J-4</option>
|
||||
<option value="5">J-5</option>
|
||||
<option value="6">J-6</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="travel-world-block">
|
||||
<label><i class="fas fa-flag-checkered"></i> Monde d'arrivée</label>
|
||||
<div class="travel-search-widget">
|
||||
<input type="text" name="travel-to" placeholder="Rechercher un monde…" autocomplete="off">
|
||||
<ul class="travel-to-results"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="travel-actions">
|
||||
<button type="button" data-action="calculate">
|
||||
<i class="fas fa-route"></i> Calculer l'itinéraire
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="travel-results"></div>
|
||||
|
||||
<div class="travel-journal-actions" style="display:none;">
|
||||
<button type="button" data-action="create-journal">
|
||||
<i class="fas fa-book"></i> Créer un journal de trajet
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
Reference in New Issue
Block a user