feat: batch NPC import, ally/enemy gen, map sync, UI shortcuts
- batch NPC creator: import images from dir → actors (dedup, folder) - ally/enemy generator: data tables, D66 specials, tests (39 pass) - sector map sync: DialogV2 player view, socket v13/v14, sync click pos - actor/scene directory: shortcut buttons (Commerce, PNJ, Map, Sync vue) - closeImagePopout: GM closes player portraits via socket - canvasSync: force same canvas view on all players - FilePicker: v14 folder picker support with v13 fallback - cleanup: debug logs, template dedup, return guards
This commit is contained in:
+114
-40
@@ -19,12 +19,15 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
window: { icon: 'fas fa-map', resizable: true, controls: [] },
|
||||
};
|
||||
|
||||
constructor(sector, subsector) {
|
||||
constructor(sector, subsector, { readOnly = false } = {}) {
|
||||
super();
|
||||
this._sector = sector;
|
||||
this._subsector = subsector;
|
||||
this._readOnly = readOnly;
|
||||
this._handler = null;
|
||||
this._mapHex = null;
|
||||
this._mapCenter = null;
|
||||
this._mapScale = null;
|
||||
this._searchTimeout = null;
|
||||
}
|
||||
|
||||
@@ -37,13 +40,16 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
|
||||
get _mapUrl() {
|
||||
const base = 'https://travellermap.com';
|
||||
const cb = this._readOnly ? '' : '&callback=postmessage';
|
||||
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}/?sector=${encodeURIComponent(this._sector)}&subsector=${encodeURIComponent(this._subsector)}&style=mongoose&hideui=1${cb}`;
|
||||
}
|
||||
let url = `${base}/go/${encodeURIComponent(this._sector)}?style=mongoose`;
|
||||
let url = `${base}/?sector=${encodeURIComponent(this._sector)}&style=mongoose&hideui=1${cb}`;
|
||||
if (this._mapHex) {
|
||||
url += `&hex=${this._mapHex}&scale=512`;
|
||||
} else if (this._mapCenter && this._mapScale) {
|
||||
url += `¢er=${this._mapCenter}&scale=${this._mapScale}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
@@ -58,6 +64,20 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
}
|
||||
|
||||
_renderHTML() {
|
||||
if (this._readOnly) {
|
||||
return `<div class="mgt2-sector-map-outer">
|
||||
<div class="mgt2-sector-map-toolbar mgt2-sector-map-toolbar--ro">
|
||||
<span class="mgt2-sector-map-label">${SectorMapApp._escapeAttr(this.title)}</span>
|
||||
<span class="mgt2-sector-map-hint">Carte partagée par le MJ</span>
|
||||
</div>
|
||||
<iframe
|
||||
src="${this._mapUrl}"
|
||||
class="mgt2-sector-map-frame"
|
||||
allow="clipboard-write"
|
||||
referrerpolicy="no-referrer">
|
||||
</iframe>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="mgt2-sector-map-outer">
|
||||
<div class="mgt2-sector-map-toolbar">
|
||||
<div class="mgt2-sector-map-search">
|
||||
@@ -66,8 +86,7 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
</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-sync" disabled title="Cliquez d'abord sur un hex pour définir la position à synchroniser"><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
|
||||
@@ -80,10 +99,11 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
}
|
||||
|
||||
async _onRender(context, options) {
|
||||
if (this._readOnly) {
|
||||
this._listen();
|
||||
return;
|
||||
}
|
||||
this._listen();
|
||||
this.element?.querySelector('.mgt2-sector-map-share')?.addEventListener('click', () => {
|
||||
this._shareMap();
|
||||
});
|
||||
this.element?.querySelector('.mgt2-sector-map-sync')?.addEventListener('click', () => {
|
||||
this._syncAll();
|
||||
});
|
||||
@@ -112,16 +132,24 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
_listen() {
|
||||
if (this._handler) return;
|
||||
this._handler = (event) => {
|
||||
// Accept messages from travellermap or any origin (for testing)
|
||||
const d = event.data || {};
|
||||
if (d.type === 'viewport') {
|
||||
this._sector = d.sector || this._sector;
|
||||
this._subsector = d.subsector || this._subsector;
|
||||
this._mapHex = d.hex || null;
|
||||
this._mapCenter = (d.x != null && d.y != null) ? `${d.x},${d.y}` : null;
|
||||
this._mapScale = d.scale || null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Click on map
|
||||
const wx = d.x ?? d.location?.x;
|
||||
const wy = d.y ?? d.location?.y;
|
||||
if (wx == null || wy == null) return;
|
||||
const x = Number(wx);
|
||||
const y = Number(wy);
|
||||
if (isNaN(x) || isNaN(y)) return;
|
||||
console.log('SectorMapApp | click at', x, y, 'from', event.origin);
|
||||
this._onMapClick({x, y}).catch(err => {
|
||||
this._onMapClick({ x, y }).catch(err => {
|
||||
console.error('SectorMapApp | click handler failed:', err);
|
||||
});
|
||||
};
|
||||
@@ -149,6 +177,13 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
const sectorName = meta.Names?.[0]?.Text;
|
||||
if (!sectorName) { console.error('SectorMapApp | no Names[0].Text in metadata', meta); return; }
|
||||
|
||||
// Store click position for sync
|
||||
this._sector = sectorName;
|
||||
this._subsector = null;
|
||||
this._mapHex = String(hx).padStart(2, '0') + String(hy).padStart(2, '0');
|
||||
this._mapCenter = `${wx},${wy}`;
|
||||
this._mapScale = loc.scale || 512;
|
||||
|
||||
const hex = String(hx).padStart(2, '0') + String(hy).padStart(2, '0');
|
||||
const resp = await fetch(
|
||||
`https://travellermap.com/data/${encodeURIComponent(sectorName)}/${hex}`
|
||||
@@ -159,9 +194,19 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
const world = data.Worlds?.[0];
|
||||
if (!world) { console.error('SectorMapApp | no Worlds in data', data); return; }
|
||||
|
||||
this._enableSyncButton(world.Name || hex);
|
||||
|
||||
this._postWorldCard(world);
|
||||
}
|
||||
|
||||
_enableSyncButton(worldName) {
|
||||
const btn = this.element?.querySelector('.mgt2-sector-map-sync');
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.title = worldName ? `Synchroniser la vue sur ${worldName}` : 'Ouvrir la carte interactive chez tous les joueurs';
|
||||
}
|
||||
}
|
||||
|
||||
/* ───── Carte de chat ───── */
|
||||
|
||||
static _STARPORT = { A:'Excellent', B:'Bon', C:'Routinier', D:'Médiocre', E:'Frontière', X:'Aucun' };
|
||||
@@ -508,6 +553,9 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
this._sector = sector;
|
||||
this._subsector = null;
|
||||
this._mapHex = hex;
|
||||
this._mapCenter = null;
|
||||
this._mapScale = null;
|
||||
this._enableSyncButton(name);
|
||||
|
||||
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
|
||||
if (iframe) iframe.src = this._mapUrl;
|
||||
@@ -517,41 +565,67 @@ export class SectorMapApp extends ApplicationV2 {
|
||||
|
||||
/* ───── Partage ───── */
|
||||
|
||||
_shareMap() {
|
||||
if (!this._sector) {
|
||||
ui.notifications.warn('Aucun secteur sélectionné — utilisez la recherche pour centrer sur un secteur');
|
||||
async _syncAll() {
|
||||
if (!game.socket?.emit) {
|
||||
ui.notifications.warn('Socket indisponible — impossible de synchroniser');
|
||||
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}`
|
||||
: `Secteur ${this._sector}`;
|
||||
try {
|
||||
// Tentative de récupération du zoom via getViewport
|
||||
const viewport = await new Promise((resolve) => {
|
||||
const handler = (event) => {
|
||||
if (event.data?.type === 'viewport') {
|
||||
window.removeEventListener('message', handler);
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', handler);
|
||||
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
|
||||
if (iframe?.contentWindow) {
|
||||
iframe.contentWindow.postMessage(
|
||||
{ source: 'travellermap', type: 'getViewport' },
|
||||
'*'
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.removeEventListener('message', handler);
|
||||
resolve(null);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
const html = `<section class="mgt2-shared-map">
|
||||
<div class="mgt2-world-card-header">
|
||||
<span class="mgt2-world-name">${label}</span>
|
||||
</div>
|
||||
<div class="mgt2-shared-map-image">
|
||||
<img src="${posterUrl}" alt="${label}">
|
||||
</div>
|
||||
<div class="mgt2-shared-map-footer">
|
||||
<a href="https://travellermap.com/go/${encodeURIComponent(this._sector)}" target="_blank" rel="noopener">
|
||||
Ouvrir sur Traveller Map <i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
</div>
|
||||
</section>`;
|
||||
if (viewport) {
|
||||
this._sector = viewport.sector || this._sector;
|
||||
this._subsector = viewport.subsector || this._subsector;
|
||||
this._mapHex = viewport.hex || this._mapHex;
|
||||
this._mapCenter = (viewport.x != null && viewport.y != null) ? `${viewport.x},${viewport.y}` : this._mapCenter;
|
||||
this._mapScale = viewport.scale || this._mapScale;
|
||||
}
|
||||
|
||||
ChatMessage.create({ content: html, rollMode: 'public' });
|
||||
ui.notifications.info(`Carte partagée avec les joueurs`);
|
||||
const payload = {
|
||||
_type: 'sectorMapSync',
|
||||
sector: this._sector,
|
||||
subsector: this._subsector,
|
||||
mapHex: this._mapHex,
|
||||
mapCenter: this._mapCenter,
|
||||
mapScale: this._mapScale,
|
||||
};
|
||||
const players = game.users.filter(u => !u.isGM && u.active);
|
||||
for (const player of players) {
|
||||
game.socket.emit(`module.${MODULE_ID}`, payload);
|
||||
}
|
||||
ui.notifications.info(`Vue synchronisée chez ${players.length} joueur(s)`);
|
||||
} catch (err) {
|
||||
console.error(`${MODULE_ID} | Erreur socket sync:`, err);
|
||||
ui.notifications.error('Erreur lors de la synchronisation');
|
||||
}
|
||||
}
|
||||
|
||||
_syncAll() {
|
||||
game.socket.emit(`module.${MODULE_ID}`, {
|
||||
type: 'sectorMapSync',
|
||||
sector: this._sector,
|
||||
subsector: this._subsector,
|
||||
});
|
||||
ui.notifications.info(`Carte synchronisée chez tous les joueurs`);
|
||||
updatePosition(sector, subsector, mapHex, mapUrl) {
|
||||
this._sector = sector;
|
||||
this._subsector = subsector;
|
||||
this._mapHex = mapHex || null;
|
||||
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
|
||||
if (iframe) iframe.src = mapUrl || this._mapUrl;
|
||||
}
|
||||
|
||||
_openTravelDialog() {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { CommerceDialog } from './CommerceDialog.js';
|
||||
import { NpcDialog } from './NpcDialog.js';
|
||||
import { SectorMapApp } from './SectorMapApp.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
@@ -41,6 +45,23 @@ class BatchNpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
this._applyTheme(html);
|
||||
|
||||
html.find('[data-action="browse-directory"]').on('click', async () => {
|
||||
// Foundry v14: FilePicker avec pickFolders pour sélectionner un répertoire
|
||||
try {
|
||||
const fp = new FilePicker({
|
||||
type: 'folder',
|
||||
current: this._formData.batch.directory || '',
|
||||
callback: (path) => {
|
||||
this._formData.batch.directory = path;
|
||||
html.find('[name="batch.directory"]').val(path);
|
||||
},
|
||||
pickFolders: true,
|
||||
field: null,
|
||||
button: null,
|
||||
});
|
||||
fp.render(true);
|
||||
return;
|
||||
} catch (_e) { /* fallback v13 */ }
|
||||
// Fallback v13: selection d'un fichier → extraction du dossier parent
|
||||
const fp = new FilePicker({
|
||||
type: 'image',
|
||||
current: this._formData.batch.directory || '',
|
||||
@@ -205,14 +226,75 @@ Hooks.on('renderActorDirectory', (app, html, data) => {
|
||||
<i class="fas fa-users"></i> Importer PNJs par lots
|
||||
</button>`);
|
||||
|
||||
const shortcuts = [
|
||||
{ icon: 'fa-balance-scale', label: 'Commerce', abbr: 'Commerce', action: 'commerce', cls: 'mgt2-shortcut-commerce', title: 'Ouvrir le commerce' },
|
||||
{ icon: 'fa-user', label: 'PNJ rapide', abbr: 'PNJ', action: 'npc', cls: 'mgt2-shortcut-pnj', title: 'Générer un PNJ rapide' },
|
||||
{ icon: 'fa-random', label: 'Rencontre', abbr: 'Rencontre', action: 'encounter', cls: 'mgt2-shortcut-rencontre', title: 'Générer une rencontre' },
|
||||
{ icon: 'fa-briefcase', label: 'Mission', abbr: 'Mission', action: 'mission', cls: 'mgt2-shortcut-mission', title: 'Générer une mission' },
|
||||
{ icon: 'fa-image', label: 'Fermer portraits', abbr: 'Fermer img', action: 'closePortraits', cls: 'mgt2-shortcut-closeimg', title: 'Fermer les portraits affichés aux joueurs' },
|
||||
];
|
||||
|
||||
const grid = $(`<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:4px 8px;"></div>`);
|
||||
shortcuts.forEach(s => {
|
||||
const b = $(`<button class="${s.cls}" title="${s.title}" style="background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;padding:4px 6px;font-size:0.78em;cursor:pointer;border-radius:3px;text-align:center;">
|
||||
<i class="fas ${s.icon}"></i> ${s.abbr}
|
||||
</button>`).on('click', () => {
|
||||
if (s.action === 'commerce') new CommerceDialog().render({ force: true });
|
||||
else if (s.action === 'closePortraits') {
|
||||
game.socket.emit(`module.${MODULE_ID}`, { _type: 'closeImagePopout' });
|
||||
ui.notifications.info('Fermeture des portraits envoyée aux joueurs');
|
||||
}
|
||||
else new NpcDialog({ initialTab: s.action }).render({ force: true });
|
||||
});
|
||||
grid.append(b);
|
||||
});
|
||||
|
||||
const footer = $html.find('.directory-footer');
|
||||
if (footer.length) {
|
||||
footer.append(btn);
|
||||
footer.append(grid);
|
||||
} else {
|
||||
$html.find('.directory-header').after(btn);
|
||||
$html.find('.directory-header').after(grid);
|
||||
}
|
||||
|
||||
btn.on('click', () => {
|
||||
new BatchNpcDialog().render({ force: true });
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on('renderSceneDirectory', (app, html, data) => {
|
||||
if (!game.user?.isGM) return;
|
||||
const $html = html instanceof jQuery ? html : $(html);
|
||||
if ($html.find('.mgt2-sector-btn').length) return;
|
||||
|
||||
const sectorBtn = $(`<button class="mgt2-sector-btn" style="margin:4px 8px;flex:0;background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;cursor:pointer;">
|
||||
<i class="fas fa-map"></i> Traveller Map
|
||||
</button>`).on('click', () => {
|
||||
new SectorMapApp().render({ force: true });
|
||||
});
|
||||
|
||||
const canvasBtn = $(`<button class="mgt2-canvas-sync-btn" style="margin:4px 8px;flex:0;background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;cursor:pointer;">
|
||||
<i class="fas fa-eye"></i> Sync vue MJ
|
||||
</button>`).on('click', () => {
|
||||
const pivot = canvas.stage?.pivot;
|
||||
const scale = canvas.stage?.scale?.x;
|
||||
if (!pivot || !scale) { ui.notifications.warn('Canvas pas encore prêt'); return; }
|
||||
const players = game.users.filter(u => !u.isGM && u.active).map(u => u.id);
|
||||
if (!players.length) { ui.notifications.warn('Aucun joueur connecté'); return; }
|
||||
game.socket.emit(`module.${MODULE_ID}`, {
|
||||
_type: 'canvasSync',
|
||||
x: pivot.x,
|
||||
y: pivot.y,
|
||||
scale,
|
||||
});
|
||||
ui.notifications.info(`Vue synchronisée chez ${players.length} joueur(s)`);
|
||||
});
|
||||
|
||||
const grid = $(`<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:4px 8px;"></div>`);
|
||||
grid.append(sectorBtn, canvasBtn);
|
||||
|
||||
const footer = $html.find('.directory-footer');
|
||||
if (footer.length) footer.append(grid);
|
||||
else $html.find('.directory-header').after(grid);
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ Hooks.once('init', () => {
|
||||
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
|
||||
`modules/${MODULE_ID}/templates/ally-enemy-result.hbs`,
|
||||
`modules/${MODULE_ID}/templates/batch-npc-dialog.hbs`,
|
||||
`modules/${MODULE_ID}/templates/player-map.hbs`,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+4
-10
@@ -16,9 +16,10 @@ let _pendingHandle = false;
|
||||
|
||||
/* ───── Fonctions partagées ───── */
|
||||
|
||||
async function openMap(sector, subsector) {
|
||||
const app = new SectorMapApp(sector, subsector);
|
||||
async function openMap(sector, subsector, { readOnly = false } = {}) {
|
||||
const app = new SectorMapApp(sector, subsector, { readOnly });
|
||||
await app.render({ force: true });
|
||||
return app;
|
||||
}
|
||||
|
||||
async function handleSectorCommand(sector, subsector) {
|
||||
@@ -139,16 +140,9 @@ Hooks.on('preCreateChatMessage', (message, data, options) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* ───── Socket (synchronisation MJ → joueurs) ───── */
|
||||
/* ───── Socket (synchronisation MJ → joueurs) ───── voir sectorSocket.js ───── */
|
||||
|
||||
Hooks.once('ready', () => {
|
||||
game.socket.on(`module.${MODULE_ID}`, (data) => {
|
||||
if (data?.type !== 'sectorMapSync') return;
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
let _playerDialog = null;
|
||||
|
||||
class PlayerMapDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'mgt2-sector-map-player',
|
||||
classes: ['mgt2-sector-map'],
|
||||
position: { width: 960, height: 720 },
|
||||
window: { icon: 'fas fa-map', resizable: true },
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: `modules/${MODULE_ID}/templates/player-map.hbs`,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(url, sector) {
|
||||
super();
|
||||
this._mapUrl = url;
|
||||
this._sectorLabel = sector ? `Secteur ${sector}` : 'Carte stellaire';
|
||||
}
|
||||
|
||||
get title() { return this._sectorLabel; }
|
||||
|
||||
async _prepareContext() {
|
||||
return { url: this._mapUrl };
|
||||
}
|
||||
|
||||
updateMap(url) {
|
||||
this._mapUrl = url;
|
||||
const iframe = this.element?.querySelector('iframe');
|
||||
if (iframe) iframe.src = url;
|
||||
}
|
||||
}
|
||||
|
||||
function buildMapUrl(sector, subsector, mapHex, mapCenter, mapScale) {
|
||||
const base = 'https://travellermap.com';
|
||||
if (!sector) return `${base}/?style=mongoose&hideui=1`;
|
||||
if (subsector) {
|
||||
let url = `${base}/?sector=${encodeURIComponent(sector)}&subsector=${encodeURIComponent(subsector)}&style=mongoose&hideui=1`;
|
||||
if (mapCenter && mapScale) url += `¢er=${mapCenter}&scale=${mapScale}`;
|
||||
return url;
|
||||
}
|
||||
let url = `${base}/?sector=${encodeURIComponent(sector)}&style=mongoose&hideui=1`;
|
||||
if (mapHex) {
|
||||
url += `&hex=${mapHex}&scale=512`;
|
||||
} else if (mapCenter && mapScale) {
|
||||
url += `¢er=${mapCenter}&scale=${mapScale}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
Hooks.once('ready', () => {
|
||||
console.log(`${MODULE_ID} | sectorSocket pret`);
|
||||
|
||||
game.socket.on(`module.${MODULE_ID}`, (data) => {
|
||||
if (game.user?.isGM) return;
|
||||
|
||||
if (data?._type === 'sectorMapSync') {
|
||||
try {
|
||||
const url = buildMapUrl(data.sector, data.subsector, data.mapHex, data.mapCenter, data.mapScale);
|
||||
if (_playerDialog) {
|
||||
_playerDialog.updateMap(url);
|
||||
_playerDialog.bringToTop?.();
|
||||
console.log(`${MODULE_ID} | Carte mise a jour`);
|
||||
return;
|
||||
}
|
||||
|
||||
_playerDialog = new PlayerMapDialog(url, data.sector);
|
||||
_playerDialog.render({ force: true }).then(() => {
|
||||
console.log(`${MODULE_ID} | Dialog affiche`);
|
||||
}).catch(err => {
|
||||
console.error(`${MODULE_ID} | Erreur render:`, err);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`${MODULE_ID} | Erreur:`, err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?._type === 'canvasSync') {
|
||||
try {
|
||||
const { x, y, scale } = data;
|
||||
console.log(`${MODULE_ID} | Canvas sync recu:`, x, y, scale);
|
||||
canvas.animatePan({ x, y, scale, duration: 0 });
|
||||
} catch (err) {
|
||||
console.error(`${MODULE_ID} | Erreur canvas sync:`, err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?._type === 'closeImagePopout') {
|
||||
try {
|
||||
let closed = 0;
|
||||
document.querySelectorAll('.image-popout, [class*="image-popout"], [id*="image-popout"]').forEach(el => {
|
||||
el.remove(); closed++;
|
||||
});
|
||||
console.log(`${MODULE_ID} | ${closed} portrait(s) fermé(s)`);
|
||||
} catch (err) {
|
||||
console.error(`${MODULE_ID} | Erreur close portraits:`, err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ function buildStartupNoticeHtml() {
|
||||
<div style="padding:6px 8px;border-left:4px solid #c9a227;background:#f5f0e8;color:#2c2c3e;line-height:1.45;">
|
||||
<strong>MgT2e – outils</strong>
|
||||
<span style="color:#6a5422;">·</span>
|
||||
<code>/commerce</code> <code>/pnj</code> <code>/rencontre</code> <code>/mission</code> <code>/sector</code> <code>/subsector</code>
|
||||
<code>/commerce</code> <code>/pnj</code> <code>/rencontre</code> <code>/mission</code> <code>/sector</code>
|
||||
<div style="margin-top:2px;font-size:0.92em;color:#6a5422;">
|
||||
par <a href="https://www.uberwald.me" target="_blank" rel="noopener noreferrer">LeRatierBretonnien / Uberwald</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user