// GLOBAL VARIABLE TO HOLD THE CURRENT MARKER const playerMarkers = {} let currentMarker = null const client_id = Date.now() //const transform = [0.1855, 113.1, 0.1855, 167.8] const transform = mapData.transform const bounds = mapData.bounds const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0 const svgPath = mapData.svgPath const imageUrl = mapData.svgPath const minZoom = mapData.minZoom ? mapData.minZoom : 1 const maxZoom = mapData.maxZoom ? mapData.maxZoom : 6 const showElevation = false; const showStaticMarkers = false const wsgroup = "1234" let currentZoom = 3; //MARK: INIT WEBSOCKET // Initialize WebSocket connection const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`); // WebSocket event handlers ws.onmessage = function (event) { console.log(event.data); const data = JSON.parse(event.data); switch (data.type) { case "coordinates": const parsedData = data.data; localStorage.setItem("last_marker", JSON.stringify(parsedData)); loadLocalData(); break; case "location_map": const map_name = data.data.map.toLowerCase().replaceAll(" ", "-"); localStorage.setItem("last_map_name", map_name); localStorage.removeItem("last_marker"); removeAllMarkers(); // Clear all markers when the map changes if (!location.pathname.includes(map_name)) { location.pathname = `/map/${map_name}`; } else { location.reload(); } break; case "new_rade_data": console.log(data.data); const radeData = JSON.parse(data.data); localStorage.setItem("rade_data", JSON.stringify(radeData)); loadLocalData(); break; } }; ws.onopen = function (event) { ws.send(JSON.stringify({ type: "join", group: wsgroup })); }; // Function to remove all markers function removeAllMarkers() { for (const playername in playerMarkers) { if (playerMarkers.hasOwnProperty(playername)) { map.removeLayer(playerMarkers[playername]); } } Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]); console.log("Alle Marker wurden entfernt."); } // Function to load and display local marker data function loadLocalData() { const map_name = localStorage.getItem("last_map_name"); if (map_name) { if (!location.pathname.includes(map_name)) { localStorage.removeItem("last_marker"); } } else { localStorage.removeItem("last_marker"); } const markerData = localStorage.getItem("last_marker"); if (markerData) { const parsedData = JSON.parse(markerData); // Use the new addMarker function with all required parameters addMarker( parsedData.x, parsedData.y, parsedData.z, parsedData.timestamp, parsedData.preview || false, parsedData.actualmap || "Unbekannte Map", parsedData.playername || "Unnamed Player", parsedData.markercolor || false ); } } const images = { 'container_bank-cash-register': 'container_cash-register', 'container_bank-safe': 'container_safe', 'container_buried-barrel-cache': 'container_buried-barrel-cache', 'container_cash-register': 'container_cash-register', 'container_cash-register-tar2-2': 'container_cash-register', 'container_dead-civilian': 'container_dead-scav', 'container_dead-scav': 'container_dead-scav', 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate', 'container_pmc-body': 'container_dead-scav', 'container_civilian-body': 'container_dead-scav', 'container_drawer': 'container_drawer', 'container_duffle-bag': 'container_duffle-bag', 'container_grenade-box': 'container_grenade-box', 'container_ground-cache': 'container_ground-cache', 'container_jacket': 'container_jacket', 'container_lab-technician-body': 'container_dead-scav', 'container_medbag-smu06': 'container_medbag-smu06', 'container_medcase': 'container_medcase', 'container_medical-supply-crate': 'container_crate', 'container_pc-block': 'container_pc-block', 'container_plastic-suitcase': 'container_plastic-suitcase', 'container_ration-supply-crate': 'container_crate', 'container_safe': 'container_safe', 'container_scav-body': 'container_dead-scav', 'container_shturmans-stash': 'container_weapon-box', 'container_technical-supply-crate': 'container_crate', 'container_toolbox': 'container_toolbox', 'container_weapon-box': 'container_weapon-box', 'container_wooden-ammo-box': 'container_wooden-ammo-box', 'container_wooden-crate': 'container_wooden-crate', 'extract_pmc': 'extract_pmc', 'extract_scav': 'extract_scav', 'extract_shared': 'extract_shared', 'extract_transit': 'extract_transit', 'hazard': 'hazard', 'hazard_mortar': 'hazard_mortar', 'hazard_minefield': 'hazard', 'hazard_sniper': 'hazard', 'key': 'key', 'lock': 'lock', 'loose_loot': 'loose_loot', 'quest_item': 'quest_item', 'quest_objective': 'quest_objective', 'spawn_sniper_scav': 'spawn_sniper_scav', 'spawn_bloodhound': 'spawn_bloodhound', 'spawn_boss': 'spawn_boss', 'spawn_cultist-priest': 'spawn_cultist-priest', 'spawn_pmc': 'spawn_pmc', 'spawn_rogue': 'spawn_rogue', 'spawn_scav': 'spawn_scav', 'stationarygun': 'stationarygun', 'switch': 'switch', }; const categories = { 'extract_pmc': 'PMC', 'extract_shared': 'Shared', 'extract_scav': 'Scav', 'extract_transit': 'Transit', 'spawn_sniper_scav': 'Sniper Scav', 'spawn_pmc': 'PMC', 'spawn_scav': 'Scav', 'spawn_boss': 'Boss', 'quest_item': 'Item', 'quest_objective': 'Objective', 'lock': 'Locks', 'lever': 'Lever', 'stationarygun': 'Stationary Gun', 'switch': 'Switch', 'place-names': 'Place Names', }; function getCRS(transform) { let scaleX = 1; let scaleY = 1; let marginX = 0; let marginY = 0; if (transform) { scaleX = transform[0]; scaleY = transform[2] * -1; marginX = transform[1]; marginY = transform[3]; } return L.extend({}, L.CRS.Simple, { transformation: new L.Transformation(scaleX, marginX, scaleY, marginY), projection: L.extend({}, L.Projection.LonLat, { project: latLng => { return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation)); }, unproject: point => { return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1); }, }), }); } function applyRotation(latLng, rotation) { if (!latLng.lng && !latLng.lat) { return L.latLng(0, 0); } if (!rotation) { return latLng; } const angleInRadians = (rotation * Math.PI) / 180; const cosAngle = Math.cos(angleInRadians); const sinAngle = Math.sin(angleInRadians); const { lng: x, lat: y } = latLng; const rotatedX = x * cosAngle - y * sinAngle; const rotatedY = x * sinAngle + y * cosAngle; return L.latLng(rotatedY, rotatedX); } function pos(position) { return [position.z, position.x]; } function addElevation(item, popup) { if (!showElevation) { return; } const elevationContent = L.DomUtil.create('div', undefined, popup); elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`; if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) { const heightContent = L.DomUtil.create('div', undefined, popup); heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`; } } function markerIsOnLayer(marker, layer) { if (!layer) { return true; } var top = marker.options.top || marker.options.position.y; var bottom = marker.options.bottom || marker.options.position.y; for (const extent of layer.options.extents) { if (top >= extent.height[0] && bottom < extent.height[1]) { let containedType = 'partial'; if (bottom >= extent.height[0] && top <= extent.height[1]) { containedType = 'full'; } if (extent.bounds) { for (const boundsArray of extent.bounds) { const bounds = getBounds(boundsArray); if (bounds.contains(pos(marker.options.position))) { return containedType; } } } else { return containedType; } } } return false; } function markerIsOnActiveLayer(marker) { if (!marker.options.position) { return true; } const map = marker._map; // check if marker is completely contained by inactive layer const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay); for (const layer of overlays) { for (const extent of layer.options.extents) { if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) { return false; } } } // check if marker is on active overlay const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay); if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) { return true; } // check if marker is on base layer const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay); if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) { return true; } return false; } function checkMarkerForActiveLayers(event) { const marker = event.target || event; const outline = marker.options.outline; const onLevel = markerIsOnActiveLayer(marker); if (onLevel) { marker._icon?.classList.remove('off-level'); if (outline) { outline._path?.classList.remove('off-level'); } } else { marker._icon?.classList.add('off-level'); if (outline) { outline._path?.classList.add('off-level'); } } } function activateMarkerLayer(event) { const marker = event.target || event; if (markerIsOnActiveLayer(marker)) { return; } const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay); for (const layer of activeLayers) { layer.removeFrom(marker._map); } const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer); for (const layer of heightLayers) { if (markerIsOnLayer(marker, layer)) { layer.addTo(marker._map); break; } } } const getALink = (path, contents) => { const a = L.DomUtil.create('a'); a.setAttribute('href', path); a.setAttribute('target', '_blank'); a.append(contents); // a.addEventListener('click', (event) => { // navigate(path); // event.preventDefault(); // }); return a; }; function getScaledBounds(bounds, scaleFactor) { // Calculate the center point of the bounds const centerX = (bounds[0][0] + bounds[1][0]) / 2; const centerY = (bounds[0][1] + bounds[1][1]) / 2; // Calculate the new width and height const width = bounds[1][0] - bounds[0][0]; const height = bounds[1][1] - bounds[0][1]; const newWidth = width * scaleFactor; const newHeight = height * scaleFactor; // Update the coordinates of the two points defining the bounds const newBounds = [ [centerY - newHeight / 2, centerX - newWidth / 2], [centerY + newHeight / 2, centerX + newWidth / 2] ]; // console.log("Initial Rectangle:", bounds); // console.log("Scaled Rectangle:", newBounds); // console.log("Center:", L.bounds(bounds).getCenter(true)); return newBounds; } // Erstelle die Karte //const map = L.map('map').setView([0, 0], 2); const map = L.map('map', { maxBounds: getScaledBounds(svgBounds, 1.5), //maxBounds: bounds, center: [0, 0], zoom: 2, minZoom: minZoom, maxZoom: maxZoom, zoomSnap: 0.1, scrollWheelZoom: true, wheelPxPerZoomLevel: 120, crs: getCRS(transform), attributionControl: false, id: "wwoodsMap", }); const layerControl = L.control.groupedLayers(null, null, { position: 'topleft', collapsed: true, groupCheckboxes: true, groupsCollapsable: true, exclusiveOptionalGroups: ['Levels'], }).addTo(map); layerControl.on('overlayToggle', (e) => { const layerState = e.detail; if (layerState.checked) { mapViewRef.current.layer = layerState.key; } else { mapViewRef.current.layer = undefined; } }); layerControl.on('layerToggle', (e) => { const layerState = e.detail; if (!layerState.checked) { mapSettingsRef.current.hiddenLayers.push(layerState.key); } else { mapViewRef.current.layer = layerState.key; mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key); } updateSavedMapSettings(); }); layerControl.on('groupToggle', (e) => { const groupState = e.detail; for (const groupLayer of layerControl._layers) { if (groupLayer.group?.key !== groupState.key) { continue; } if (!groupState.checked) { mapSettingsRef.current.hiddenLayers.push(groupLayer.key); } else { mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key); } } if (!groupState.checked) { mapSettingsRef.current.hiddenGroups.push(groupState.key); } else { mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key); } updateSavedMapSettings(); }); layerControl.on('groupCollapseToggle', (e) => { const groupState = e.detail; if (groupState.collapsed) { mapSettingsRef.current.collapsedGroups.push(groupState.key); } else { mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key); } updateSavedMapSettings(); }); const getLayerOptions = (layerKey, groupKey, layerName) => { return { groupKey, layerKey, groupName: groupKey, layerName: layerName || categories[layerKey] || layerKey, //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)), //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)), image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined, //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)), }; }; const addLayer = (layer, layerKey, groupKey, layerName) => { layer.key = layerKey; const layerOptions = getLayerOptions(layerKey, groupKey, layerName); if (!layerOptions.layerHidden) { layer.addTo(map); } layerControl.addOverlay(layer, layerOptions.layerName, layerOptions); }; map.layerControl = layerControl; // Hinzufügen des Image-Overlays const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds)); overlay.addTo(map); function checkMarkerBounds(position, markerBounds) { if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x; if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z; if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x; if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z; } function getBounds(bounds) { if (!bounds) { return undefined; } return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]); //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]]; } function mouseHoverOutline(event) { const outline = event.target.options.outline; if (event.originalEvent.type === 'mouseover') { outline._path.classList.remove('not-shown'); } else if (!outline._path.classList.contains('force-show')) { outline._path.classList.add('not-shown'); } } function toggleForceOutline(event) { const outline = event.target.options.outline; outline._path.classList.toggle('force-show'); if (outline._path.classList.contains('force-show')) { outline._path.classList.remove('not-shown'); } activateMarkerLayer(event); } function activateMarkerLayer(event) { const marker = event.target || event; if (markerIsOnActiveLayer(marker)) { return; } const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay); for (const layer of activeLayers) { layer.removeFrom(marker._map); } const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer); for (const layer of heightLayers) { if (markerIsOnLayer(marker, layer)) { layer.addTo(marker._map); break; } } } function outlineToPoly(outline) { if (!outline) return []; return outline.map(vector => [vector.z, vector.x]); } const layerOptions = { maxZoom: maxZoom, maxNativeZoom: maxZoom, extents: [ { height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], bounds: [bounds], } ], type: 'map-layer', }; let tileLayer = false; const baseLayers = []; const tileSize = 256; let svgLayer = false; if (svgPath) { const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds; svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions); baseLayers.push(svgLayer); } const positionIsInBounds = (position) => { return getBounds(bounds).contains(pos(position)); }; let markerBounds = { 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER }, 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER } } if (mapData.labels?.length > 0) { const labelsGroup = L.layerGroup(); const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0]; for (const label of mapData.labels) { const fontSize = label.size ? label.size : 100; const height = label.position.length < 3 ? defaultHeight : label.position[2]; const rotation = label.rotation ? label.rotation : 0; L.marker(pos({ x: label.position[0], z: label.position[1] }), { icon: L.divIcon({ html: `
${label.text}
`, className: 'map-area-label', layers: baseLayers, }), interactive: false, zIndexOffset: -100000, position: { x: label.position[0], y: height, z: label.position[1], }, top: typeof label.top !== 'undefined' ? label.top : 1000, bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000, }).addTo(labelsGroup); } addLayer(labelsGroup, 'place-names', 'Landmarks'); //labelsGroup.addTo(map); } // Add spawns if (mapData.spawns.length > 0) { const spawnLayers = { 'pmc': L.layerGroup(), 'scav': L.layerGroup(), 'sniper_scav': L.layerGroup(), 'boss': L.layerGroup(), 'cultist-priest': L.layerGroup(), 'rogue': L.layerGroup(), 'bloodhound': L.layerGroup(), } for (const spawn of mapData.spawns) { if (!positionIsInBounds(spawn.position)) { continue; } let spawnType = ''; let bosses = []; if (spawn.categories.includes('boss')) { bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName)); if (bosses.length === 0) { if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) { spawnType = 'scav'; } else { console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`); continue; } } else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) { spawnType = bosses[0].normalizedName; } else { spawnType = 'boss'; } } else if (spawn.categories.includes('sniper')) { spawnType = 'sniper_scav'; } else if (spawn.sides.includes('scav')) { if (spawn.categories.includes('bot') || spawn.categories.includes('all')) { spawnType = 'scav'; } else { console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`); continue; } } else if (spawn.categories.includes('player')) { if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) { spawnType = 'pmc' } else { console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`); continue; } } else { console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`); continue; } const spawnIcon = L.icon({ iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`, iconSize: [24, 24], popupAnchor: [0, -12], }); if (spawnType === 'pmc') { spawnIcon.iconAnchor = [12, 24]; spawnIcon.popupAnchor = [0, -24]; } const popupContent = L.DomUtil.create('div') if (spawn.categories.includes('boss') && bosses.length > 0) { bosses = bosses.reduce((unique, current) => { if (!unique.some(b => b.normalizedName === current.normalizedName)) { unique.push(current) if (!categories[`spawn_${current.normalizedName}`]) { categories[`spawn_${current.normalizedName}`] = current.name } } return unique; }, []); const bossList = L.DomUtil.create('div', undefined, popupContent) for (const boss of bosses) { if (bossList.childNodes.length > 0) { const comma = L.DomUtil.create('span', undefined, bossList) comma.textContent = ', ' } bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`)) } } else { const spawnDiv = L.DomUtil.create('div', undefined, popupContent) spawnDiv.textContent = categories[`spawn_${spawnType}`] } addElevation(spawn, popupContent) const marker = L.marker(pos(spawn.position), { icon: spawnIcon, position: spawn.position, }); if (popupContent.childNodes.length > 0) { marker.bindPopup(L.popup().setContent(popupContent)) } marker.position = spawn.position marker.on('add', checkMarkerForActiveLayers) marker.on('click', activateMarkerLayer) marker.addTo(spawnLayers[spawnType]) checkMarkerBounds(spawn.position, markerBounds) } for (const key in spawnLayers) { if (Object.keys(spawnLayers[key]._layers).length > 0) { addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns') } } } //add extracts if (mapData.extracts.length > 0) { const extractLayers = { pmc: L.layerGroup(), scav: L.layerGroup(), shared: L.layerGroup(), } const zIndexOffsets = { pmc: 150, shared: 125, scav: 100, }; for (const extract of mapData.extracts) { const faction = extract.faction ?? 'shared'; if (!positionIsInBounds(extract.position)) { //continue; } const colorMap = { scav: '#ff7800', pmc: '#00e599', shared: '#00e4e5', } const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' }); const extractIcon = L.divIcon({ className: 'extract-icon', html: `${extract.name}`, iconAnchor: [12, 12] }); const extractMarker = L.marker(pos(extract.position), { icon: extractIcon, title: extract.name, zIndexOffset: zIndexOffsets[faction], position: extract.position, top: extract.top, bottom: extract.bottom, outline: rect, id: extract.id, }); extractMarker.on('mouseover', mouseHoverOutline); extractMarker.on('mouseout', mouseHoverOutline); extractMarker.on('click', toggleForceOutline); if (extract.switches?.length > 0) { const popup = L.DomUtil.create('div'); const textElement = L.DomUtil.create('div'); textElement.textContent = `${tMaps('Activated by')}:`; popup.appendChild(textElement); for (const sw of extract.switches) { const linkElement = getPoiLinkElement(sw.id, 'switch'); const nameElement = L.DomUtil.create('span'); nameElement.innerHTML = `${sw.name}`; linkElement.append(nameElement); popup.appendChild(linkElement); } addElevation(extract, popup); extractMarker.bindPopup(L.popup().setContent(popup)); } else if (showElevation) { const popup = L.DomUtil.create('div'); addElevation(extract, popup); extractMarker.bindPopup(L.popup().setContent(popup)); } extractMarker.on('add', checkMarkerForActiveLayers); L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]); checkMarkerBounds(extract.position, markerBounds); } if (mapData.transits.length > 0) { extractLayers.transit = L.layerGroup(); for (const transit of mapData.transits) { if (!positionIsInBounds(transit.position)) { //continue; } const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' }); const transitIcon = L.divIcon({ className: 'extract-icon', html: `${transit.description}`, iconAnchor: [12, 12] }); const transitMarker = L.marker(pos(transit.position), { icon: transitIcon, title: transit.description, zIndexOffset: zIndexOffsets.pmc, position: transit.position, top: transit.top, bottom: transit.bottom, outline: rect, id: transit.id, }); transitMarker.on('mouseover', mouseHoverOutline); transitMarker.on('mouseout', mouseHoverOutline); transitMarker.on('click', toggleForceOutline); if (showElevation) { const popup = L.DomUtil.create('div'); addElevation(transit, popup); transitMarker.bindPopup(L.popup().setContent(popup)); } transitMarker.on('add', checkMarkerForActiveLayers); L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit); checkMarkerBounds(transit.position, markerBounds); } } for (const key in extractLayers) { if (Object.keys(extractLayers[key]._layers).length > 0) { addLayer(extractLayers[key], `extract_${key}`, 'Extracts'); } } } // Add static items if (showStaticMarkers) { for (const category in mapData) { const markerLayer = L.layerGroup(); const items = mapData[category]; for (const item of items) { const itemIcon = L.icon({ iconUrl: `/static/maps/interactive/${category}.png`, iconSize: [24, 24], popupAnchor: [0, -12], //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level', }); L.marker(pos(item.position), { icon: itemIcon, position: item.position }) .bindPopup(L.popup().setContent(`${item.name}
Elevation: ${item.position.y}`)) .addTo(markerLayer); checkMarkerBounds(item.position, markerBounds); } if (items.length > 0) { var section; if (category.startsWith('extract')) { section = 'Extracts'; } else if (category.startsWith('spawn')) { section = 'Spawns'; } else { section = 'Lootable Items'; } markerLayer.addTo(map); addLayer(markerLayer, category, section); // layerControl.addOverlay(markerLayer, ` ${categories[category] || category}`, section); } } } // Erstelle ein neues Control-Element const customControl = L.Control.extend({ onAdd: function (map) { this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded') this.container.innerHTML = '

Add Marker to Pos:

' return this.container }, updateText: function (html) { if (this.container) { this.container.innerHTML = html } }, clearText: function () { if (this.container) { this.container.innerHTML = '' } }, onRemove: function (map) { // Nichts zu tun hier } }); const myControl = new customControl({ position: 'topright' }) map.addControl(myControl) function startCountdown(elementId) { const element = document.getElementById(elementId); if (!element) { console.error(`Element with id ${elementId} not found.`); return; } //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60 let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60 let seconds = secondsStart if (isNaN(secondsStart)) { console.error('Invalid data-seconds attribute.'); return; } const intervalId = setInterval(() => { if (!document.hidden) { //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0))) //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds })); seconds-- if (seconds <= 0) { clearInterval(intervalId); element.textContent = '0:00:00'; myControl.updateText("") document.querySelector("head > title").textContent = `${mapData.name} | Map with friends` return; } //seconds--; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; element.textContent = timeString; document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}` } }, 1000); } function addNewRadeData(new_html) { myControl.updateText(new_html) startCountdown("rade_time_remain") }; //MARK: ADDMARKER // Funktion zum Hinzufügen eines Markers function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) { // Validate position const position = { x: parseFloat(x), y: parseFloat(y), z: parseFloat(z) }; if (!positionIsInBounds(position)) { console.error("Position außerhalb der Karte:", position); return; } // SVG-Icon als String mit dynamischer Farbe let markerColor; if (markercolor) { markerColor = '#' + markercolor; } else { markerColor = 'currentColor'; } const svgString = ` `; const svgString2 = ` `; // DivIcon mit dynamischem SVG erstellen const markerIcon = L.divIcon({ html: svgString, className: 'custom-marker', iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -32] }); const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-") // Remove old marker for this player, if it exists if (playerMarkers[playerMarkerName]) { map.removeLayer(playerMarkers[playerMarkerName]); } // Create new marker playerMarkers[playerMarkerName] = L.marker(pos(position), { icon: markerIcon, position: position, title: `Koordinaten: ${x}, ${y}, ${z}`, riseOnHover: true, zIndexOffset: 400 }); // Popup mit Informationen erstellen const popupContent = `
${playername ? `Player: ${playername}` : ''} Koordinaten: X: ${x} Y: ${y} Z: ${z} ${preview ? `
` : ''} ${timestamp}
`; playerMarkers[playerMarkerName].bindPopup(popupContent, { maxWidth: 250, minWidth: 150, autoClose: true, closeOnClick: true }); // Marker zur Karte hinzufügen playerMarkers[playerMarkerName].addTo(map); // Karte auf Marker zentrieren map.setView(pos(position), map.getZoom(), { animate: true, duration: 0.5 }); console.log("Neuer Marker gesetzt für " + playername + ": " + position); } document.onload = loadLocalData() // MODAL Trigger document.addEventListener('DOMContentLoaded', () => { // Functions to open and close a modal function openModal($el) { $el.classList.add('is-active'); } function closeModal($el) { $el.classList.remove('is-active'); } function closeAllModals() { (document.querySelectorAll('.modal') || []).forEach(($modal) => { closeModal($modal); }); } // Add a click event on buttons to open a specific modal (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => { const modal = $trigger.dataset.target; const $target = document.getElementById(modal); $trigger.addEventListener('click', () => { openModal($target); }); }); // Add a click event on various child elements to close the parent modal (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => { const $target = $close.closest('.modal'); $close.addEventListener('click', () => { closeModal($target); }); }); // Add a keyboard event to close all modals document.addEventListener('keydown', (event) => { if (event.key === "Escape") { closeAllModals(); } }); });