Sebastiankay commited on
Commit
bcf0371
·
verified ·
1 Parent(s): 10c3049

Update static/map.js

Browse files
Files changed (1) hide show
  1. static/map.js +944 -0
static/map.js CHANGED
@@ -0,0 +1,944 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GLOBAL VARIABLE TO HOLD THE CURRENT MARKER
2
+ const playerMarkers = {}
3
+ let currentMarker = null
4
+ const client_id = Date.now()
5
+ //const transform = [0.1855, 113.1, 0.1855, 167.8]
6
+ const transform = mapData.transform
7
+ const bounds = mapData.bounds
8
+ const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds
9
+ const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0
10
+ const svgPath = mapData.svgPath
11
+ const imageUrl = mapData.svgPath
12
+ const minZoom = mapData.minZoom ? mapData.minZoom : 1
13
+ const maxZoom = mapData.maxZoom ? mapData.maxZoom : 6
14
+ const showElevation = false;
15
+ const showStaticMarkers = false
16
+ const wsgroup = "1234"
17
+ let currentZoom = 3;
18
+ //MARK: INIT WEBSOCKET
19
+ // Initialize WebSocket connection
20
+ const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
21
+ // WebSocket event handlers
22
+ ws.onmessage = function (event) {
23
+ console.log(event.data);
24
+ const data = JSON.parse(event.data);
25
+ switch (data.type) {
26
+ case "coordinates":
27
+ const parsedData = data.data;
28
+ localStorage.setItem("last_marker", JSON.stringify(parsedData));
29
+ loadLocalData();
30
+ break;
31
+ case "location_map":
32
+ const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
33
+ localStorage.setItem("last_map_name", map_name);
34
+ localStorage.removeItem("last_marker");
35
+ removeAllMarkers(); // Clear all markers when the map changes
36
+ if (!location.pathname.includes(map_name)) {
37
+ location.pathname = `/map/${map_name}`;
38
+ } else {
39
+ location.reload();
40
+ }
41
+ break;
42
+ case "new_rade_data":
43
+ console.log(data.data);
44
+ const radeData = JSON.parse(data.data);
45
+ localStorage.setItem("rade_data", JSON.stringify(radeData));
46
+ loadLocalData();
47
+ break;
48
+ }
49
+ };
50
+ ws.onopen = function (event) {
51
+ ws.send(JSON.stringify({ type: "join", group: wsgroup }));
52
+ };
53
+ // Function to remove all markers
54
+ function removeAllMarkers() {
55
+ for (const playername in playerMarkers) {
56
+ if (playerMarkers.hasOwnProperty(playername)) {
57
+ map.removeLayer(playerMarkers[playername]);
58
+ }
59
+ }
60
+ Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
61
+ console.log("Alle Marker wurden entfernt.");
62
+ }
63
+ // Function to load and display local marker data
64
+ function loadLocalData() {
65
+ const map_name = localStorage.getItem("last_map_name");
66
+ if (map_name) {
67
+ if (!location.pathname.includes(map_name)) {
68
+ localStorage.removeItem("last_marker");
69
+ }
70
+ } else {
71
+ localStorage.removeItem("last_marker");
72
+ }
73
+ const markerData = localStorage.getItem("last_marker");
74
+ if (markerData) {
75
+ const parsedData = JSON.parse(markerData);
76
+ // Use the new addMarker function with all required parameters
77
+ addMarker(
78
+ parsedData.x,
79
+ parsedData.y,
80
+ parsedData.z,
81
+ parsedData.timestamp,
82
+ parsedData.preview || false,
83
+ parsedData.actualmap || "Unbekannte Map",
84
+ parsedData.playername || "Unnamed Player",
85
+ parsedData.markercolor || false
86
+ );
87
+ }
88
+ }
89
+ const images = {
90
+ 'container_bank-cash-register': 'container_cash-register',
91
+ 'container_bank-safe': 'container_safe',
92
+ 'container_buried-barrel-cache': 'container_buried-barrel-cache',
93
+ 'container_cash-register': 'container_cash-register',
94
+ 'container_cash-register-tar2-2': 'container_cash-register',
95
+ 'container_dead-civilian': 'container_dead-scav',
96
+ 'container_dead-scav': 'container_dead-scav',
97
+ 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
98
+ 'container_pmc-body': 'container_dead-scav',
99
+ 'container_civilian-body': 'container_dead-scav',
100
+ 'container_drawer': 'container_drawer',
101
+ 'container_duffle-bag': 'container_duffle-bag',
102
+ 'container_grenade-box': 'container_grenade-box',
103
+ 'container_ground-cache': 'container_ground-cache',
104
+ 'container_jacket': 'container_jacket',
105
+ 'container_lab-technician-body': 'container_dead-scav',
106
+ 'container_medbag-smu06': 'container_medbag-smu06',
107
+ 'container_medcase': 'container_medcase',
108
+ 'container_medical-supply-crate': 'container_crate',
109
+ 'container_pc-block': 'container_pc-block',
110
+ 'container_plastic-suitcase': 'container_plastic-suitcase',
111
+ 'container_ration-supply-crate': 'container_crate',
112
+ 'container_safe': 'container_safe',
113
+ 'container_scav-body': 'container_dead-scav',
114
+ 'container_shturmans-stash': 'container_weapon-box',
115
+ 'container_technical-supply-crate': 'container_crate',
116
+ 'container_toolbox': 'container_toolbox',
117
+ 'container_weapon-box': 'container_weapon-box',
118
+ 'container_wooden-ammo-box': 'container_wooden-ammo-box',
119
+ 'container_wooden-crate': 'container_wooden-crate',
120
+ 'extract_pmc': 'extract_pmc',
121
+ 'extract_scav': 'extract_scav',
122
+ 'extract_shared': 'extract_shared',
123
+ 'extract_transit': 'extract_transit',
124
+ 'hazard': 'hazard',
125
+ 'hazard_mortar': 'hazard_mortar',
126
+ 'hazard_minefield': 'hazard',
127
+ 'hazard_sniper': 'hazard',
128
+ 'key': 'key',
129
+ 'lock': 'lock',
130
+ 'loose_loot': 'loose_loot',
131
+ 'quest_item': 'quest_item',
132
+ 'quest_objective': 'quest_objective',
133
+ 'spawn_sniper_scav': 'spawn_sniper_scav',
134
+ 'spawn_bloodhound': 'spawn_bloodhound',
135
+ 'spawn_boss': 'spawn_boss',
136
+ 'spawn_cultist-priest': 'spawn_cultist-priest',
137
+ 'spawn_pmc': 'spawn_pmc',
138
+ 'spawn_rogue': 'spawn_rogue',
139
+ 'spawn_scav': 'spawn_scav',
140
+ 'stationarygun': 'stationarygun',
141
+ 'switch': 'switch',
142
+ };
143
+ const categories = {
144
+ 'extract_pmc': 'PMC',
145
+ 'extract_shared': 'Shared',
146
+ 'extract_scav': 'Scav',
147
+ 'extract_transit': 'Transit',
148
+ 'spawn_sniper_scav': 'Sniper Scav',
149
+ 'spawn_pmc': 'PMC',
150
+ 'spawn_scav': 'Scav',
151
+ 'spawn_boss': 'Boss',
152
+ 'quest_item': 'Item',
153
+ 'quest_objective': 'Objective',
154
+ 'lock': 'Locks',
155
+ 'lever': 'Lever',
156
+ 'stationarygun': 'Stationary Gun',
157
+ 'switch': 'Switch',
158
+ 'place-names': 'Place Names',
159
+ };
160
+ function getCRS(transform) {
161
+ let scaleX = 1;
162
+ let scaleY = 1;
163
+ let marginX = 0;
164
+ let marginY = 0;
165
+ if (transform) {
166
+ scaleX = transform[0];
167
+ scaleY = transform[2] * -1;
168
+ marginX = transform[1];
169
+ marginY = transform[3];
170
+ }
171
+ return L.extend({}, L.CRS.Simple, {
172
+ transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
173
+ projection: L.extend({}, L.Projection.LonLat, {
174
+ project: latLng => {
175
+ return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation));
176
+ },
177
+ unproject: point => {
178
+ return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1);
179
+ },
180
+ }),
181
+ });
182
+ }
183
+ function applyRotation(latLng, rotation) {
184
+ if (!latLng.lng && !latLng.lat) {
185
+ return L.latLng(0, 0);
186
+ }
187
+ if (!rotation) {
188
+ return latLng;
189
+ }
190
+ const angleInRadians = (rotation * Math.PI) / 180;
191
+ const cosAngle = Math.cos(angleInRadians);
192
+ const sinAngle = Math.sin(angleInRadians);
193
+ const { lng: x, lat: y } = latLng;
194
+ const rotatedX = x * cosAngle - y * sinAngle;
195
+ const rotatedY = x * sinAngle + y * cosAngle;
196
+ return L.latLng(rotatedY, rotatedX);
197
+ }
198
+ function pos(position) {
199
+ return [position.z, position.x];
200
+ }
201
+ function addElevation(item, popup) {
202
+ if (!showElevation) {
203
+ return;
204
+ }
205
+ const elevationContent = L.DomUtil.create('div', undefined, popup);
206
+ elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
207
+ if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
208
+ const heightContent = L.DomUtil.create('div', undefined, popup);
209
+ heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
210
+ }
211
+ }
212
+ function markerIsOnLayer(marker, layer) {
213
+ if (!layer) {
214
+ return true;
215
+ }
216
+ var top = marker.options.top || marker.options.position.y;
217
+ var bottom = marker.options.bottom || marker.options.position.y;
218
+ for (const extent of layer.options.extents) {
219
+ if (top >= extent.height[0] && bottom < extent.height[1]) {
220
+ let containedType = 'partial';
221
+ if (bottom >= extent.height[0] && top <= extent.height[1]) {
222
+ containedType = 'full';
223
+ }
224
+ if (extent.bounds) {
225
+ for (const boundsArray of extent.bounds) {
226
+ const bounds = getBounds(boundsArray);
227
+ if (bounds.contains(pos(marker.options.position))) {
228
+ return containedType;
229
+ }
230
+ }
231
+ } else {
232
+ return containedType;
233
+ }
234
+ }
235
+ }
236
+ return false;
237
+ }
238
+ function markerIsOnActiveLayer(marker) {
239
+ if (!marker.options.position) {
240
+ return true;
241
+ }
242
+ const map = marker._map;
243
+ // check if marker is completely contained by inactive layer
244
+ const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
245
+ for (const layer of overlays) {
246
+ for (const extent of layer.options.extents) {
247
+ if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
248
+ return false;
249
+ }
250
+ }
251
+ }
252
+ // check if marker is on active overlay
253
+ const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
254
+ if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
255
+ return true;
256
+ }
257
+ // check if marker is on base layer
258
+ const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay);
259
+ if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
260
+ return true;
261
+ }
262
+ return false;
263
+ }
264
+ function checkMarkerForActiveLayers(event) {
265
+ const marker = event.target || event;
266
+ const outline = marker.options.outline;
267
+ const onLevel = markerIsOnActiveLayer(marker);
268
+ if (onLevel) {
269
+ marker._icon?.classList.remove('off-level');
270
+ if (outline) {
271
+ outline._path?.classList.remove('off-level');
272
+ }
273
+ } else {
274
+ marker._icon?.classList.add('off-level');
275
+ if (outline) {
276
+ outline._path?.classList.add('off-level');
277
+ }
278
+ }
279
+ }
280
+ function activateMarkerLayer(event) {
281
+ const marker = event.target || event;
282
+ if (markerIsOnActiveLayer(marker)) {
283
+ return;
284
+ }
285
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
286
+ for (const layer of activeLayers) {
287
+ layer.removeFrom(marker._map);
288
+ }
289
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
290
+ for (const layer of heightLayers) {
291
+ if (markerIsOnLayer(marker, layer)) {
292
+ layer.addTo(marker._map);
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ const getALink = (path, contents) => {
298
+ const a = L.DomUtil.create('a');
299
+ a.setAttribute('href', path);
300
+ a.setAttribute('target', '_blank');
301
+ a.append(contents);
302
+ // a.addEventListener('click', (event) => {
303
+ // navigate(path);
304
+ // event.preventDefault();
305
+ // });
306
+ return a;
307
+ };
308
+ function getScaledBounds(bounds, scaleFactor) {
309
+ // Calculate the center point of the bounds
310
+ const centerX = (bounds[0][0] + bounds[1][0]) / 2;
311
+ const centerY = (bounds[0][1] + bounds[1][1]) / 2;
312
+ // Calculate the new width and height
313
+ const width = bounds[1][0] - bounds[0][0];
314
+ const height = bounds[1][1] - bounds[0][1];
315
+ const newWidth = width * scaleFactor;
316
+ const newHeight = height * scaleFactor;
317
+ // Update the coordinates of the two points defining the bounds
318
+ const newBounds = [
319
+ [centerY - newHeight / 2, centerX - newWidth / 2],
320
+ [centerY + newHeight / 2, centerX + newWidth / 2]
321
+ ];
322
+ // console.log("Initial Rectangle:", bounds);
323
+ // console.log("Scaled Rectangle:", newBounds);
324
+ // console.log("Center:", L.bounds(bounds).getCenter(true));
325
+ return newBounds;
326
+ }
327
+ // Erstelle die Karte
328
+ //const map = L.map('map').setView([0, 0], 2);
329
+ const map = L.map('map', {
330
+ maxBounds: getScaledBounds(svgBounds, 1.5),
331
+ //maxBounds: bounds,
332
+ center: [0, 0],
333
+ zoom: 2,
334
+ minZoom: minZoom,
335
+ maxZoom: maxZoom,
336
+ zoomSnap: 0.1,
337
+ scrollWheelZoom: true,
338
+ wheelPxPerZoomLevel: 120,
339
+ crs: getCRS(transform),
340
+ attributionControl: false,
341
+ id: "wwoodsMap",
342
+ });
343
+ const layerControl = L.control.groupedLayers(null, null, {
344
+ position: 'topleft',
345
+ collapsed: true,
346
+ groupCheckboxes: true,
347
+ groupsCollapsable: true,
348
+ exclusiveOptionalGroups: ['Levels'],
349
+ }).addTo(map);
350
+ layerControl.on('overlayToggle', (e) => {
351
+ const layerState = e.detail;
352
+ if (layerState.checked) {
353
+ mapViewRef.current.layer = layerState.key;
354
+ } else {
355
+ mapViewRef.current.layer = undefined;
356
+ }
357
+ });
358
+ layerControl.on('layerToggle', (e) => {
359
+ const layerState = e.detail;
360
+ if (!layerState.checked) {
361
+ mapSettingsRef.current.hiddenLayers.push(layerState.key);
362
+ } else {
363
+ mapViewRef.current.layer = layerState.key;
364
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key);
365
+ }
366
+ updateSavedMapSettings();
367
+ });
368
+ layerControl.on('groupToggle', (e) => {
369
+ const groupState = e.detail;
370
+ for (const groupLayer of layerControl._layers) {
371
+ if (groupLayer.group?.key !== groupState.key) {
372
+ continue;
373
+ }
374
+ if (!groupState.checked) {
375
+ mapSettingsRef.current.hiddenLayers.push(groupLayer.key);
376
+ } else {
377
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key);
378
+ }
379
+ }
380
+ if (!groupState.checked) {
381
+ mapSettingsRef.current.hiddenGroups.push(groupState.key);
382
+ } else {
383
+ mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key);
384
+ }
385
+ updateSavedMapSettings();
386
+ });
387
+ layerControl.on('groupCollapseToggle', (e) => {
388
+ const groupState = e.detail;
389
+ if (groupState.collapsed) {
390
+ mapSettingsRef.current.collapsedGroups.push(groupState.key);
391
+ } else {
392
+ mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key);
393
+ }
394
+ updateSavedMapSettings();
395
+ });
396
+ const getLayerOptions = (layerKey, groupKey, layerName) => {
397
+ return {
398
+ groupKey,
399
+ layerKey,
400
+ groupName: groupKey,
401
+ layerName: layerName || categories[layerKey] || layerKey,
402
+ //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)),
403
+ //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)),
404
+ image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined,
405
+ //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)),
406
+ };
407
+ };
408
+ const addLayer = (layer, layerKey, groupKey, layerName) => {
409
+ layer.key = layerKey;
410
+ const layerOptions = getLayerOptions(layerKey, groupKey, layerName);
411
+ if (!layerOptions.layerHidden) {
412
+ layer.addTo(map);
413
+ }
414
+ layerControl.addOverlay(layer, layerOptions.layerName, layerOptions);
415
+ };
416
+ map.layerControl = layerControl;
417
+ // Hinzufügen des Image-Overlays
418
+ const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds));
419
+ overlay.addTo(map);
420
+ function checkMarkerBounds(position, markerBounds) {
421
+ if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x;
422
+ if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z;
423
+ if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x;
424
+ if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z;
425
+ }
426
+ function getBounds(bounds) {
427
+ if (!bounds) {
428
+ return undefined;
429
+ }
430
+ return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
431
+ //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]];
432
+ }
433
+ function mouseHoverOutline(event) {
434
+ const outline = event.target.options.outline;
435
+ if (event.originalEvent.type === 'mouseover') {
436
+ outline._path.classList.remove('not-shown');
437
+ } else if (!outline._path.classList.contains('force-show')) {
438
+ outline._path.classList.add('not-shown');
439
+ }
440
+ }
441
+ function toggleForceOutline(event) {
442
+ const outline = event.target.options.outline;
443
+ outline._path.classList.toggle('force-show');
444
+ if (outline._path.classList.contains('force-show')) {
445
+ outline._path.classList.remove('not-shown');
446
+ }
447
+ activateMarkerLayer(event);
448
+ }
449
+ function activateMarkerLayer(event) {
450
+ const marker = event.target || event;
451
+ if (markerIsOnActiveLayer(marker)) {
452
+ return;
453
+ }
454
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
455
+ for (const layer of activeLayers) {
456
+ layer.removeFrom(marker._map);
457
+ }
458
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
459
+ for (const layer of heightLayers) {
460
+ if (markerIsOnLayer(marker, layer)) {
461
+ layer.addTo(marker._map);
462
+ break;
463
+ }
464
+ }
465
+ }
466
+ function outlineToPoly(outline) {
467
+ if (!outline) return [];
468
+ return outline.map(vector => [vector.z, vector.x]);
469
+ }
470
+ const layerOptions = {
471
+ maxZoom: maxZoom,
472
+ maxNativeZoom: maxZoom,
473
+ extents: [
474
+ {
475
+ height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
476
+ bounds: [bounds],
477
+ }
478
+ ],
479
+ type: 'map-layer',
480
+ };
481
+ let tileLayer = false;
482
+ const baseLayers = [];
483
+ const tileSize = 256;
484
+ let svgLayer = false;
485
+ if (svgPath) {
486
+ const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds;
487
+ svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions);
488
+ baseLayers.push(svgLayer);
489
+ }
490
+ const positionIsInBounds = (position) => {
491
+ return getBounds(bounds).contains(pos(position));
492
+ };
493
+ let markerBounds = {
494
+ 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER },
495
+ 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER }
496
+ }
497
+ if (mapData.labels?.length > 0) {
498
+ const labelsGroup = L.layerGroup();
499
+ const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0];
500
+ for (const label of mapData.labels) {
501
+ const fontSize = label.size ? label.size : 100;
502
+ const height = label.position.length < 3 ? defaultHeight : label.position[2];
503
+ const rotation = label.rotation ? label.rotation : 0;
504
+ L.marker(pos({ x: label.position[0], z: label.position[1] }), {
505
+ 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, }),
506
+ interactive: false,
507
+ zIndexOffset: -100000,
508
+ position: {
509
+ x: label.position[0],
510
+ y: height,
511
+ z: label.position[1],
512
+ },
513
+ top: typeof label.top !== 'undefined' ? label.top : 1000,
514
+ bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000,
515
+ }).addTo(labelsGroup);
516
+ }
517
+ addLayer(labelsGroup, 'place-names', 'Landmarks');
518
+ //labelsGroup.addTo(map);
519
+ }
520
+ // Add spawns
521
+ if (mapData.spawns.length > 0) {
522
+ const spawnLayers = {
523
+ 'pmc': L.layerGroup(),
524
+ 'scav': L.layerGroup(),
525
+ 'sniper_scav': L.layerGroup(),
526
+ 'boss': L.layerGroup(),
527
+ 'cultist-priest': L.layerGroup(),
528
+ 'rogue': L.layerGroup(),
529
+ 'bloodhound': L.layerGroup(),
530
+ }
531
+ for (const spawn of mapData.spawns) {
532
+ if (!positionIsInBounds(spawn.position)) {
533
+ continue;
534
+ }
535
+ let spawnType = '';
536
+ let bosses = [];
537
+ if (spawn.categories.includes('boss')) {
538
+ bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName));
539
+ if (bosses.length === 0) {
540
+ if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) {
541
+ spawnType = 'scav';
542
+ }
543
+ else {
544
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
545
+ continue;
546
+ }
547
+ }
548
+ else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) {
549
+ spawnType = bosses[0].normalizedName;
550
+ }
551
+ else {
552
+ spawnType = 'boss';
553
+ }
554
+ } else if (spawn.categories.includes('sniper')) {
555
+ spawnType = 'sniper_scav';
556
+ } else if (spawn.sides.includes('scav')) {
557
+ if (spawn.categories.includes('bot') || spawn.categories.includes('all')) {
558
+ spawnType = 'scav';
559
+ }
560
+ else {
561
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
562
+ continue;
563
+ }
564
+ }
565
+ else if (spawn.categories.includes('player')) {
566
+ if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) {
567
+ spawnType = 'pmc'
568
+ }
569
+ else {
570
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
571
+ continue;
572
+ }
573
+ }
574
+ else {
575
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
576
+ continue;
577
+ }
578
+ const spawnIcon = L.icon({
579
+ iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`,
580
+ iconSize: [24, 24],
581
+ popupAnchor: [0, -12],
582
+ });
583
+ if (spawnType === 'pmc') {
584
+ spawnIcon.iconAnchor = [12, 24];
585
+ spawnIcon.popupAnchor = [0, -24];
586
+ }
587
+ const popupContent = L.DomUtil.create('div')
588
+ if (spawn.categories.includes('boss') && bosses.length > 0) {
589
+ bosses = bosses.reduce((unique, current) => {
590
+ if (!unique.some(b => b.normalizedName === current.normalizedName)) {
591
+ unique.push(current)
592
+ if (!categories[`spawn_${current.normalizedName}`]) {
593
+ categories[`spawn_${current.normalizedName}`] = current.name
594
+ }
595
+ }
596
+ return unique;
597
+ }, []);
598
+ const bossList = L.DomUtil.create('div', undefined, popupContent)
599
+ for (const boss of bosses) {
600
+ if (bossList.childNodes.length > 0) {
601
+ const comma = L.DomUtil.create('span', undefined, bossList)
602
+ comma.textContent = ', '
603
+ }
604
+ bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`))
605
+ }
606
+ }
607
+ else {
608
+ const spawnDiv = L.DomUtil.create('div', undefined, popupContent)
609
+ spawnDiv.textContent = categories[`spawn_${spawnType}`]
610
+ }
611
+ addElevation(spawn, popupContent)
612
+ const marker = L.marker(pos(spawn.position), {
613
+ icon: spawnIcon,
614
+ position: spawn.position,
615
+ });
616
+ if (popupContent.childNodes.length > 0) {
617
+ marker.bindPopup(L.popup().setContent(popupContent))
618
+ }
619
+ marker.position = spawn.position
620
+ marker.on('add', checkMarkerForActiveLayers)
621
+ marker.on('click', activateMarkerLayer)
622
+ marker.addTo(spawnLayers[spawnType])
623
+ checkMarkerBounds(spawn.position, markerBounds)
624
+ }
625
+ for (const key in spawnLayers) {
626
+ if (Object.keys(spawnLayers[key]._layers).length > 0) {
627
+ addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns')
628
+ }
629
+ }
630
+ }
631
+ //add extracts
632
+ if (mapData.extracts.length > 0) {
633
+ const extractLayers = {
634
+ pmc: L.layerGroup(),
635
+ scav: L.layerGroup(),
636
+ shared: L.layerGroup(),
637
+ }
638
+ const zIndexOffsets = {
639
+ pmc: 150,
640
+ shared: 125,
641
+ scav: 100,
642
+ };
643
+ for (const extract of mapData.extracts) {
644
+ const faction = extract.faction ?? 'shared';
645
+ if (!positionIsInBounds(extract.position)) {
646
+ //continue;
647
+ }
648
+ const colorMap = {
649
+ scav: '#ff7800',
650
+ pmc: '#00e599',
651
+ shared: '#00e4e5',
652
+ }
653
+ const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' });
654
+ const extractIcon = L.divIcon({
655
+ className: 'extract-icon',
656
+ html: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`,
657
+ iconAnchor: [12, 12]
658
+ });
659
+ const extractMarker = L.marker(pos(extract.position), {
660
+ icon: extractIcon,
661
+ title: extract.name,
662
+ zIndexOffset: zIndexOffsets[faction],
663
+ position: extract.position,
664
+ top: extract.top,
665
+ bottom: extract.bottom,
666
+ outline: rect,
667
+ id: extract.id,
668
+ });
669
+ extractMarker.on('mouseover', mouseHoverOutline);
670
+ extractMarker.on('mouseout', mouseHoverOutline);
671
+ extractMarker.on('click', toggleForceOutline);
672
+ if (extract.switches?.length > 0) {
673
+ const popup = L.DomUtil.create('div');
674
+ const textElement = L.DomUtil.create('div');
675
+ textElement.textContent = `${tMaps('Activated by')}:`;
676
+ popup.appendChild(textElement);
677
+ for (const sw of extract.switches) {
678
+ const linkElement = getPoiLinkElement(sw.id, 'switch');
679
+ const nameElement = L.DomUtil.create('span');
680
+ nameElement.innerHTML = `<strong>${sw.name}</strong>`;
681
+ linkElement.append(nameElement);
682
+ popup.appendChild(linkElement);
683
+ }
684
+ addElevation(extract, popup);
685
+ extractMarker.bindPopup(L.popup().setContent(popup));
686
+ } else if (showElevation) {
687
+ const popup = L.DomUtil.create('div');
688
+ addElevation(extract, popup);
689
+ extractMarker.bindPopup(L.popup().setContent(popup));
690
+ }
691
+ extractMarker.on('add', checkMarkerForActiveLayers);
692
+ L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]);
693
+ checkMarkerBounds(extract.position, markerBounds);
694
+ }
695
+ if (mapData.transits.length > 0) {
696
+ extractLayers.transit = L.layerGroup();
697
+ for (const transit of mapData.transits) {
698
+ if (!positionIsInBounds(transit.position)) {
699
+ //continue;
700
+ }
701
+ const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' });
702
+ const transitIcon = L.divIcon({
703
+ className: 'extract-icon',
704
+ html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`,
705
+ iconAnchor: [12, 12]
706
+ });
707
+ const transitMarker = L.marker(pos(transit.position), {
708
+ icon: transitIcon,
709
+ title: transit.description,
710
+ zIndexOffset: zIndexOffsets.pmc,
711
+ position: transit.position,
712
+ top: transit.top,
713
+ bottom: transit.bottom,
714
+ outline: rect,
715
+ id: transit.id,
716
+ });
717
+ transitMarker.on('mouseover', mouseHoverOutline);
718
+ transitMarker.on('mouseout', mouseHoverOutline);
719
+ transitMarker.on('click', toggleForceOutline);
720
+ if (showElevation) {
721
+ const popup = L.DomUtil.create('div');
722
+ addElevation(transit, popup);
723
+ transitMarker.bindPopup(L.popup().setContent(popup));
724
+ }
725
+ transitMarker.on('add', checkMarkerForActiveLayers);
726
+ L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit);
727
+ checkMarkerBounds(transit.position, markerBounds);
728
+ }
729
+ }
730
+ for (const key in extractLayers) {
731
+ if (Object.keys(extractLayers[key]._layers).length > 0) {
732
+ addLayer(extractLayers[key], `extract_${key}`, 'Extracts');
733
+ }
734
+ }
735
+ }
736
+ // Add static items
737
+ if (showStaticMarkers) {
738
+ for (const category in mapData) {
739
+ const markerLayer = L.layerGroup();
740
+ const items = mapData[category];
741
+ for (const item of items) {
742
+ const itemIcon = L.icon({
743
+ iconUrl: `/static/maps/interactive/${category}.png`,
744
+ iconSize: [24, 24],
745
+ popupAnchor: [0, -12],
746
+ //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level',
747
+ });
748
+ L.marker(pos(item.position), { icon: itemIcon, position: item.position })
749
+ .bindPopup(L.popup().setContent(`${item.name}<br>Elevation: ${item.position.y}`))
750
+ .addTo(markerLayer);
751
+ checkMarkerBounds(item.position, markerBounds);
752
+ }
753
+ if (items.length > 0) {
754
+ var section;
755
+ if (category.startsWith('extract')) {
756
+ section = 'Extracts';
757
+ }
758
+ else if (category.startsWith('spawn')) {
759
+ section = 'Spawns';
760
+ }
761
+ else {
762
+ section = 'Lootable Items';
763
+ }
764
+ markerLayer.addTo(map);
765
+ addLayer(markerLayer, category, section);
766
+ // layerControl.addOverlay(markerLayer, `<img src='${process.env.PUBLIC_URL}/maps/interactive/${category}.png' class='control-item-image' /> ${categories[category] || category}`, section);
767
+ }
768
+ }
769
+ }
770
+ // Erstelle ein neues Control-Element
771
+ const customControl = L.Control.extend({
772
+ onAdd: function (map) {
773
+ this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded')
774
+ this.container.innerHTML = '<h2>Add Marker to Pos: </h2>'
775
+ return this.container
776
+ },
777
+ updateText: function (html) {
778
+ if (this.container) {
779
+ this.container.innerHTML = html
780
+ }
781
+ },
782
+ clearText: function () {
783
+ if (this.container) {
784
+ this.container.innerHTML = ''
785
+ }
786
+ },
787
+ onRemove: function (map) {
788
+ // Nichts zu tun hier
789
+ }
790
+ });
791
+ const myControl = new customControl({ position: 'topright' })
792
+ map.addControl(myControl)
793
+ function startCountdown(elementId) {
794
+ const element = document.getElementById(elementId);
795
+ if (!element) {
796
+ console.error(`Element with id ${elementId} not found.`);
797
+ return;
798
+ }
799
+ //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60
800
+ let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60
801
+ let seconds = secondsStart
802
+ if (isNaN(secondsStart)) {
803
+ console.error('Invalid data-seconds attribute.');
804
+ return;
805
+ }
806
+ const intervalId = setInterval(() => {
807
+ if (!document.hidden) {
808
+ //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0)))
809
+ //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds }));
810
+ seconds--
811
+ if (seconds <= 0) {
812
+ clearInterval(intervalId);
813
+ element.textContent = '0:00:00';
814
+ myControl.updateText("")
815
+ document.querySelector("head > title").textContent = `${mapData.name} | Map with friends`
816
+ return;
817
+ }
818
+ //seconds--;
819
+ const hours = Math.floor(seconds / 3600);
820
+ const minutes = Math.floor((seconds % 3600) / 60);
821
+ const remainingSeconds = seconds % 60;
822
+ const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
823
+ element.textContent = timeString;
824
+ document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}`
825
+ }
826
+ }, 1000);
827
+ }
828
+ function addNewRadeData(new_html) {
829
+ myControl.updateText(new_html)
830
+ startCountdown("rade_time_remain")
831
+ };
832
+ //MARK: ADDMARKER
833
+ // Funktion zum Hinzufügen eines Markers
834
+ function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
835
+ // Validate position
836
+ const position = {
837
+ x: parseFloat(x),
838
+ y: parseFloat(y),
839
+ z: parseFloat(z)
840
+ };
841
+ if (!positionIsInBounds(position)) {
842
+ console.error("Position außerhalb der Karte:", position);
843
+ return;
844
+ }
845
+ // SVG-Icon als String mit dynamischer Farbe
846
+ let markerColor;
847
+ if (markercolor) {
848
+ markerColor = '#' + markercolor;
849
+ } else {
850
+ markerColor = 'currentColor';
851
+ }
852
+ const svgString = `
853
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
854
+ <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"/>
855
+ </svg>
856
+ `;
857
+ const svgString2 = `
858
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg">
859
+ <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'/>
860
+ </svg>
861
+ `;
862
+ // DivIcon mit dynamischem SVG erstellen
863
+ const markerIcon = L.divIcon({
864
+ html: svgString,
865
+ className: 'custom-marker',
866
+ iconSize: [32, 32],
867
+ iconAnchor: [16, 32],
868
+ popupAnchor: [0, -32]
869
+ });
870
+ const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-")
871
+ // Remove old marker for this player, if it exists
872
+ if (playerMarkers[playerMarkerName]) {
873
+ map.removeLayer(playerMarkers[playerMarkerName]);
874
+ }
875
+ // Create new marker
876
+ playerMarkers[playerMarkerName] = L.marker(pos(position), {
877
+ icon: markerIcon,
878
+ position: position,
879
+ title: `Koordinaten: ${x}, ${y}, ${z}`,
880
+ riseOnHover: true,
881
+ zIndexOffset: 400
882
+ });
883
+ // Popup mit Informationen erstellen
884
+ const popupContent = `
885
+ <div class="marker-popup">
886
+ ${playername ? `<strong>Player: ${playername}</strong>` : ''}
887
+ <strong>Koordinaten:</strong>
888
+ <span>X: ${x} Y: ${y} Z: ${z}</span>
889
+ ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''}
890
+ <small>${timestamp}</small>
891
+ </div>
892
+ `;
893
+ playerMarkers[playerMarkerName].bindPopup(popupContent, {
894
+ maxWidth: 250,
895
+ minWidth: 150,
896
+ autoClose: true,
897
+ closeOnClick: true
898
+ });
899
+ // Marker zur Karte hinzufügen
900
+ playerMarkers[playerMarkerName].addTo(map);
901
+ // Karte auf Marker zentrieren
902
+ map.setView(pos(position), map.getZoom(), {
903
+ animate: true,
904
+ duration: 0.5
905
+ });
906
+ console.log("Neuer Marker gesetzt für " + playername + ": " + position);
907
+ }
908
+ document.onload = loadLocalData()
909
+ // MODAL Trigger
910
+ document.addEventListener('DOMContentLoaded', () => {
911
+ // Functions to open and close a modal
912
+ function openModal($el) {
913
+ $el.classList.add('is-active');
914
+ }
915
+ function closeModal($el) {
916
+ $el.classList.remove('is-active');
917
+ }
918
+ function closeAllModals() {
919
+ (document.querySelectorAll('.modal') || []).forEach(($modal) => {
920
+ closeModal($modal);
921
+ });
922
+ }
923
+ // Add a click event on buttons to open a specific modal
924
+ (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
925
+ const modal = $trigger.dataset.target;
926
+ const $target = document.getElementById(modal);
927
+ $trigger.addEventListener('click', () => {
928
+ openModal($target);
929
+ });
930
+ });
931
+ // Add a click event on various child elements to close the parent modal
932
+ (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
933
+ const $target = $close.closest('.modal');
934
+ $close.addEventListener('click', () => {
935
+ closeModal($target);
936
+ });
937
+ });
938
+ // Add a keyboard event to close all modals
939
+ document.addEventListener('keydown', (event) => {
940
+ if (event.key === "Escape") {
941
+ closeAllModals();
942
+ }
943
+ });
944
+ });