// 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: `
${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}
${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 = '