| |
| const playerMarkers = {} |
| let currentMarker = null |
| const client_id = Date.now() |
| |
| 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; |
| |
| |
| const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`); |
| |
| 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(); |
| 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 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 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); |
| |
| 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; |
| |
| 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; |
| } |
| } |
| } |
| |
| const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay); |
| if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) { |
| return true; |
| } |
| |
| 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); |
| |
| |
| |
| |
| return a; |
| }; |
| function getScaledBounds(bounds, scaleFactor) { |
| |
| const centerX = (bounds[0][0] + bounds[1][0]) / 2; |
| const centerY = (bounds[0][1] + bounds[1][1]) / 2; |
| |
| const width = bounds[1][0] - bounds[0][0]; |
| const height = bounds[1][1] - bounds[0][1]; |
| const newWidth = width * scaleFactor; |
| const newHeight = height * scaleFactor; |
| |
| const newBounds = [ |
| [centerY - newHeight / 2, centerX - newWidth / 2], |
| [centerY + newHeight / 2, centerX + newWidth / 2] |
| ]; |
| |
| |
| |
| return newBounds; |
| } |
| |
| |
| const map = L.map('map', { |
| maxBounds: getScaledBounds(svgBounds, 1.5), |
| |
| 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, |
| |
| |
| image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined, |
| |
| }; |
| }; |
| 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; |
| |
| 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]]); |
| |
| } |
| 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: `<div class="label" style="font-size: ${fontSize}%; transform: translate3d(-50%, -50%, 0) rotate(${rotation}deg)">${label.text}</div>`, 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'); |
| |
| } |
| |
| 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') |
| } |
| } |
| } |
| |
| 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)) { |
| |
| } |
| 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: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`, |
| 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 = `<strong>${sw.name}</strong>`; |
| 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)) { |
| |
| } |
| const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' }); |
| const transitIcon = L.divIcon({ |
| className: 'extract-icon', |
| html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`, |
| 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'); |
| } |
| } |
| } |
| |
| 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], |
| |
| }); |
| L.marker(pos(item.position), { icon: itemIcon, position: item.position }) |
| .bindPopup(L.popup().setContent(`${item.name}<br>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); |
| |
| } |
| } |
| } |
| |
| 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 = '<h2>Add Marker to Pos: </h2>' |
| return this.container |
| }, |
| updateText: function (html) { |
| if (this.container) { |
| this.container.innerHTML = html |
| } |
| }, |
| clearText: function () { |
| if (this.container) { |
| this.container.innerHTML = '' |
| } |
| }, |
| onRemove: function (map) { |
| |
| } |
| }); |
| 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 = 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-- |
| if (seconds <= 0) { |
| clearInterval(intervalId); |
| element.textContent = '0:00:00'; |
| myControl.updateText("") |
| document.querySelector("head > title").textContent = `${mapData.name} | Map with friends` |
| return; |
| } |
| |
| 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") |
| }; |
| |
| |
| function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) { |
| |
| const position = { |
| x: parseFloat(x), |
| y: parseFloat(y), |
| z: parseFloat(z) |
| }; |
| if (!positionIsInBounds(position)) { |
| console.error("Position außerhalb der Karte:", position); |
| return; |
| } |
| |
| let markerColor; |
| if (markercolor) { |
| markerColor = '#' + markercolor; |
| } else { |
| markerColor = 'currentColor'; |
| } |
| const svgString = ` |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg"> |
| <path d="M60.8 50.5 38 72.9 15.2 50.5 8.1 3.9 38 13.3l29.8-9.4-7 46.6z" style="stroke-linecap:round;stroke-linejoin:round;fill:#fff;stroke:#fff;stroke-width:4.4px"/><path d="M58.8 49.4 38 69.9 17.1 49.4 10.7 6.9 38 15.5l27.2-8.6-6.4 42.5z" style="fill:${markerColor};stroke:#000;stroke-width:4px;stroke-linecap:round;stroke-linejoin:round"/> |
| </svg> |
| `; |
| const svgString2 = ` |
| <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg"> |
| <path fill='${markerColor}' d='M172.3 501.7C27 291 0 269.4 0 192 0 86 86 0 192 0s192 86 192 192c0 77.4-27 99-172.3 309.7-9.5 13.8-29.9 13.8-39.5 0zM192 272c44.2 0 80-35.8 80-80s-35.8-80-80-80-80 35.8-80 80 35.8 80 80 80z'/> |
| </svg> |
| `; |
| |
| const markerIcon = L.divIcon({ |
| html: svgString, |
| className: 'custom-marker', |
| iconSize: [32, 32], |
| iconAnchor: [16, 32], |
| popupAnchor: [0, -32] |
| }); |
| const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-") |
| |
| if (playerMarkers[playerMarkerName]) { |
| map.removeLayer(playerMarkers[playerMarkerName]); |
| } |
| |
| playerMarkers[playerMarkerName] = L.marker(pos(position), { |
| icon: markerIcon, |
| position: position, |
| title: `Koordinaten: ${x}, ${y}, ${z}`, |
| riseOnHover: true, |
| zIndexOffset: 400 |
| }); |
| |
| const popupContent = ` |
| <div class="marker-popup"> |
| ${playername ? `<strong>Player: ${playername}</strong>` : ''} |
| <strong>Koordinaten:</strong> |
| <span>X: ${x} Y: ${y} Z: ${z}</span> |
| ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''} |
| <small>${timestamp}</small> |
| </div> |
| `; |
| playerMarkers[playerMarkerName].bindPopup(popupContent, { |
| maxWidth: 250, |
| minWidth: 150, |
| autoClose: true, |
| closeOnClick: true |
| }); |
| |
| playerMarkers[playerMarkerName].addTo(map); |
| |
| map.setView(pos(position), map.getZoom(), { |
| animate: true, |
| duration: 0.5 |
| }); |
| console.log("Neuer Marker gesetzt für " + playername + ": " + position); |
| } |
| document.onload = loadLocalData() |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| 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); |
| }); |
| } |
| |
| (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => { |
| const modal = $trigger.dataset.target; |
| const $target = document.getElementById(modal); |
| $trigger.addEventListener('click', () => { |
| openModal($target); |
| }); |
| }); |
| |
| (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); |
| }); |
| }); |
| |
| document.addEventListener('keydown', (event) => { |
| if (event.key === "Escape") { |
| closeAllModals(); |
| } |
| }); |
| }); |