import type L from 'leaflet' import { HnHCRS, HnHMaxZoom, HnHMinZoom, TileSize } from '~/lib/LeafletCustomTypes' import { SmartTileLayer } from '~/lib/SmartTileLayer' import type { MapInfo } from '~/types/api' type SmartTileLayerInstance = InstanceType /** Known marker icon paths (without .png) to preload so markers render without broken images. */ const MARKER_ICON_PATHS = [ 'gfx/terobjs/mm/custom', 'gfx/terobjs/mm/tower', 'gfx/terobjs/mm/village', 'gfx/terobjs/mm/dungeon', 'gfx/terobjs/mm/cave', 'gfx/terobjs/mm/settlement', 'gfx/invobjs/small/bush', 'gfx/invobjs/small/bumling', ] /** * Preloads marker icon images so they are in the browser cache before markers render. * Call from client only. resolvePath should produce absolute URLs for static assets. */ export function preloadMarkerIcons(resolvePath: (path: string) => string): void { if (import.meta.server) return for (const base of MARKER_ICON_PATHS) { const url = resolvePath(`${base}.png`) const img = new Image() img.src = url } } export interface MapInitResult { map: L.Map layer: SmartTileLayerInstance overlayLayer: SmartTileLayerInstance markerLayer: L.LayerGroup backendBase: string } export async function initLeafletMap( element: HTMLElement, mapsList: MapInfo[], initialMapId: number ): Promise { const L = (await import('leaflet')).default const map = L.map(element, { minZoom: HnHMinZoom, maxZoom: HnHMaxZoom, crs: HnHCRS, attributionControl: false, zoomControl: false, inertia: true, zoomAnimation: true, fadeAnimation: true, markerZoomAnimation: true, }) const runtimeConfig = useRuntimeConfig() const apiBase = (runtimeConfig.public.apiBase as string) ?? '/map/api' const backendBase = apiBase.replace(/\/api\/?$/, '') || '/map' const tileUrl = `${backendBase}/grids/{map}/{z}/{x}_{y}.png?{cache}` const layer = new SmartTileLayer(tileUrl, { minZoom: 1, maxZoom: 6, maxNativeZoom: 6, zoomOffset: 0, zoomReverse: true, tileSize: TileSize, updateWhenIdle: true, keepBuffer: 4, }) layer.map = initialMapId layer.invalidTile = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' layer.addTo(map) const overlayLayer = new SmartTileLayer(tileUrl, { minZoom: 1, maxZoom: 6, maxNativeZoom: 6, zoomOffset: 0, zoomReverse: true, tileSize: TileSize, opacity: 0.5, updateWhenIdle: true, keepBuffer: 4, }) overlayLayer.map = -1 overlayLayer.invalidTile = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' overlayLayer.addTo(map) const markerLayer = L.layerGroup() markerLayer.addTo(map) markerLayer.setZIndex(600) const baseURL = useRuntimeConfig().app.baseURL ?? '/' const markerIconPath = baseURL.endsWith('/') ? baseURL : baseURL + '/' L.Icon.Default.imagePath = markerIconPath const resolvePath = (path: string) => { const p = path.startsWith('/') ? path : `/${path}` return baseURL === '/' ? p : `${baseURL.replace(/\/$/, '')}${p}` } preloadMarkerIcons(resolvePath) return { map, layer, overlayLayer, markerLayer, backendBase } }