'use strict'; const fs = require('fs'); const path = require('path'); const prefabSync = require('./prefab-sync'); const bakeIgnore = require('./bake-ignore'); const IMPORT_DEBOUNCE_MS = 900; /** levelId → debounce timer */ const importTimers = new Map(); let prefabWatcher = null; let broadcastUnsubs = []; function addBroadcastListener(message, handler) { const fn = Editor.Message.__protected__?.addBroadcastListener || Editor.Message.addBroadcastListener; if (typeof fn !== 'function') { console.warn(`[level-map-editor] addBroadcastListener unavailable for ${message}`); return null; } fn.call(Editor.Message, message, handler); return () => { const rm = Editor.Message.__protected__?.removeBroadcastListener || Editor.Message.removeBroadcastListener; if (typeof rm === 'function') rm.call(Editor.Message, message, handler); }; } function scheduleImportFromPrefab(levelId, source) { if (!levelId || bakeIgnore.shouldIgnoreImport(levelId)) return; if (importTimers.has(levelId)) clearTimeout(importTimers.get(levelId)); importTimers.set( levelId, setTimeout(() => { importTimers.delete(levelId); runImportFromPrefab(levelId, source); }, IMPORT_DEBOUNCE_MS), ); } function runImportFromPrefab(levelId, source) { if (bakeIgnore.shouldIgnoreImport(levelId)) return null; try { const result = prefabSync.importLevelFromPrefab(levelId); if (!result.ok) { console.warn(`[level-map-editor] prefab import skipped Level${levelId}: ${result.reason}`); return result; } console.log( `[level-map-editor] prefab → JSON Level${levelId} (ground ${result.ground}, border ${result.border}, theme ${result.theme || '-'}) [${source}]`, ); void prefabSync.refreshDatabaseAsset(); Editor.Message.broadcast('level-map-editor:prefab-synced', { levelId, source, ground: result.ground, border: result.border, theme: result.theme, }); return result; } catch (e) { console.error(`[level-map-editor] prefab import failed Level${levelId}`, e); return { ok: false, reason: e.message, levelId }; } } async function handleAssetDbEvent(...args) { let url = ''; for (const arg of args) { if (typeof arg === 'string') { if (arg.includes('level-prefabs/Level') && arg.endsWith('.prefab')) url = arg; else if (arg.startsWith('db://') && arg.includes('Level') && arg.endsWith('.prefab')) url = arg; } else if (arg && typeof arg === 'object') { if (typeof arg.url === 'string') url = arg.url; else if (typeof arg.path === 'string') url = arg.path; } } if (!url && typeof args[0] === 'string' && !args[0].includes('/')) { try { url = await Editor.Message.request('asset-db', 'query-url', args[0]); } catch { /* ignore */ } } const levelId = prefabSync.extractLevelIdFromUrl(url); if (levelId) scheduleImportFromPrefab(levelId, 'asset-db'); } function watchPrefabDirectory() { const dir = path.join(Editor.Project.path, prefabSync.PREFAB_DIR_REL); if (!fs.existsSync(dir)) return; try { prefabWatcher = fs.watch(dir, (_event, filename) => { const levelId = prefabSync.extractLevelIdFromFilename(filename); if (levelId) scheduleImportFromPrefab(levelId, 'fs-watch'); }); } catch (e) { console.warn('[level-map-editor] prefab fs.watch failed', e); } } function registerAssetListeners() { const events = [ 'asset-db:asset-change', 'asset-db:asset-changed', 'asset-db:assets-changed', 'asset-db:asset-add', 'asset-db:assets-created', ]; for (const message of events) { const unsub = addBroadcastListener(message, (...args) => { void handleAssetDbEvent(...args); }); if (unsub) broadcastUnsubs.push(unsub); } } exports.methods = { openPanel() { const id = 'level-map-editor'; try { Editor.Panel.open(id); console.log(`[${id}] Panel.open('${id}')`); } catch (e) { console.error(`[${id}] open failed:`, e); } }, markBakePending(levelId, durationMs) { bakeIgnore.markBakePending(levelId, durationMs); }, importLevelFromPrefab(levelId) { return runImportFromPrefab(Number(levelId), 'manual'); }, }; exports.load = function () { prefabSync.clearSpriteUuidCache(); registerAssetListeners(); watchPrefabDirectory(); console.log('[level-map-editor] extension loaded (v1.7.1, prefab→JSON sync enabled)'); }; exports.unload = function () { for (const unsub of broadcastUnsubs) { try { unsub(); } catch { /* ignore */ } } broadcastUnsubs = []; if (prefabWatcher) { try { prefabWatcher.close(); } catch { /* ignore */ } prefabWatcher = null; } for (const timer of importTimers.values()) clearTimeout(timer); importTimers.clear(); console.log('[level-map-editor] extension unloaded'); };