7fc8d2c09c
- 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
109 lines
3.2 KiB
JavaScript
109 lines
3.2 KiB
JavaScript
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;
|
|
}
|
|
});
|
|
});
|