luanti-web/site_generator/templates/world_detail_radar.template

348 lines
20 KiB
Text

<h3 id='weltradar' class="section-header-flex">
<span>Weltradar</span>
<div class="map-toggle-controls">
<button id="toggle-live-btn-%%current_world_key%%" class="map-toggle-button active">Live</button>
<button id="toggle-archive-btn-%%current_world_key%%" class="map-toggle-button">Archiv</button>
</div>
</h3>
<div id="live-map-container-%%current_world_key%%">
<div id="ol-map-container-%%current_world_key%%" class="map-view-container" style="background-color: %%map_background_color%%;"></div>
<div id="popup-%%current_world_key%%" class="ol-popup">
<a href="#" id="popup-closer-%%current_world_key%%" class="ol-popup-closer"></a>
<div id="popup-content-%%current_world_key%%" class="ol-popup-content"></div>
</div>
<div class='map-sub-info'>
%%map_sub_info_link%%
<span class='map-last-update'>Letzte Kartenaktualisierung: <span id='map-last-update-text-%%current_world_key%%'><em>wird geladen...</em></span></span>
</div>
</div>
<div id="archive-view-container-%%current_world_key%%" style="display: none;">
%%ARCHIVE_HTML%%
</div>
<script>
// Globale Referenzen
window.olMap_%%current_world_key%% = null;
window.playerMarkerSource_%%current_world_key%% = new ol.source.Vector();
window.parentAreaLayerSource_%%current_world_key%% = new ol.source.Vector();
window.subAreaLayerSource_%%current_world_key%% = new ol.source.Vector();
window.tpadMarkerSource_%%current_world_key%% = new ol.source.Vector();
window.mapData_%%current_world_key%% = {};
// Konvertiert Minetest-Koordinaten in OpenLayers-Pixel-Koordinaten
function convertMinetestToOpenLayers_%%current_world_key%%(posX, posZ) {
const mapData = window.mapData_%%current_world_key%%;
if (!mapData || !mapData.mapWidth) return null;
const percentX = (posX - mapData.minX) / mapData.extentWidth;
const percentZ = (posZ - mapData.minZ) / mapData.extentHeight;
const pixelX = percentX * mapData.mapWidth;
const pixelY = - (mapData.mapHeight - (percentZ * mapData.mapHeight));
return [pixelX, pixelY];
}
// Konvertiert OpenLayers-Pixel-Koordinaten zurück in Minetest-Koordinaten
function convertOpenLayersToMinetest_%%current_world_key%%(coordinate) {
const mapData = window.mapData_%%current_world_key%%;
if (!mapData || !mapData.mapWidth || !coordinate) return '';
const pixelX = coordinate[0];
const pixelY = coordinate[1];
const percentX = pixelX / mapData.mapWidth;
const percentZ = -pixelY / mapData.mapHeight;
const posX = Math.round((percentX * mapData.extentWidth) + mapData.minX);
const posZ = Math.round((percentZ * mapData.extentHeight) + mapData.minZ);
return `X: ${posX}, Z: ${posZ}`;
}
function createPolygonFromPos_%%current_world_key%%(pos1, pos2) {
const p1 = convertMinetestToOpenLayers_%%current_world_key%%(pos1.x, pos1.z);
const p2 = convertMinetestToOpenLayers_%%current_world_key%%(pos2.x, pos2.z);
if (!p1 || !p2) return null;
const minX = Math.min(p1[0], p2[0]);
const minY = Math.min(p1[1], p2[1]);
const maxX = Math.max(p1[0], p2[0]);
const maxY = Math.max(p1[1], p2[1]);
const coordinates = [[ [minX, minY], [maxX, minY], [maxX, maxY], [minX, maxY], [minX, minY] ]];
return new ol.geom.Polygon(coordinates);
}
document.addEventListener('DOMContentLoaded', function() {
const mapContainer = document.getElementById('ol-map-container-%%current_world_key%%');
const mapInfoPath = '/%%web_map_info_rel_path%%?v=' + new Date().getTime();
const areasPath = '/%%web_areas_json_rel_path%%?v=' + new Date().getTime();
const tpadsPath = '/%%web_tpads_txt_rel_path%%?v=' + new Date().getTime();
Promise.all([
fetch(mapInfoPath).then(res => { if (!res.ok) throw new Error('map_info.txt'); return res.text(); }),
fetch(areasPath).then(res => { if (!res.ok) return {}; return res.json(); }),
fetch(tpadsPath).then(res => { if (!res.ok) return []; return res.json(); })
]).then(([mapInfoText, areaData, tpadData]) => {
const mapInfo = (text => {
const data = {};
text.split('\n').forEach(line => {
const parts = line.split('=');
if (parts.length === 2 && parts[0] && parts[1]) data[parts[0].trim()] = parts[1].trim();
});
return data;
})(mapInfoText);
if (!mapInfo.map_dimension || !mapInfo.map_extent) throw new Error('map_info.txt ist unvollständig.');
const mapData = window.mapData_%%current_world_key%%;
const mapDim = mapInfo.map_dimension.split('x');
mapData.mapWidth = parseInt(mapDim[0], 10);
mapData.mapHeight = parseInt(mapDim[1], 10);
const mapExt = mapInfo.map_extent.split(/[+:]/);
mapData.minX = parseInt(mapExt[0], 10);
mapData.minZ = parseInt(mapExt[1], 10);
mapData.extentWidth = parseInt(mapExt[2], 10);
mapData.extentHeight = parseInt(mapExt[3], 10);
if (mapContainer && typeof ol !== 'undefined' && mapData.mapWidth > 0) {
mapContainer.innerHTML = '';
const extent = [0, -mapData.mapHeight, mapData.mapWidth, 0];
const sourceResolutions = %%RESOLUTIONS_JS_ARRAY%%;
const maxNativeZoom = sourceResolutions.length -1;
const viewResolutions = [...sourceResolutions];
const digitalZoomLevels = 3;
let lastResolution = viewResolutions[viewResolutions.length - 1];
for (let i = 0; i < digitalZoomLevels; i++) {
lastResolution = lastResolution / 2;
viewResolutions.push(lastResolution);
}
const projection = new ol.proj.Projection({ code: 'pixel-map-%%current_world_key%%', units: 'pixels', extent: extent });
const tileGrid = new ol.tilegrid.TileGrid({ origin: ol.extent.getTopLeft(extent), resolutions: sourceResolutions });
const tileSource = new ol.source.TileImage({
projection: projection,
tileGrid: tileGrid,
tileUrlFunction: function(tileCoord) {
if (!tileCoord) return '';
let z = tileCoord[0]; let x = tileCoord[1]; let y = tileCoord[2];
if (z > maxNativeZoom) {
const zoomDiff = z - maxNativeZoom;
const scale = Math.pow(2, zoomDiff);
x = Math.floor(x / scale);
y = Math.floor(y / scale);
z = maxNativeZoom;
}
return ('/%%web_tiles_rel_path%%/' + z + '/' + x + '/' + y + '.png?v=%%CACHE_BUSTER%%');
}
});
const tileLayer = new ol.layer.Tile({ source: tileSource, title: 'Basiskarte', type: 'base' });
const parentAreaStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgba(0, 100, 255, 0.8)', width: 2 }), fill: new ol.style.Fill({ color: 'rgba(0, 100, 255, 0.2)' }) });
const subAreaStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgba(0, 100, 255, 0.8)', width: 1.5, lineDash: [6, 6] }), fill: new ol.style.Fill({ color: 'transparent' }) });
const tpadStyle = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1], anchorXUnits: 'fraction', anchorYUnits: 'fraction',
src: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="turquoise" stroke="black" stroke-width="0.5" d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>',
scale: 1.5
})
});
const playerLayer = new ol.layer.Vector({ source: window.playerMarkerSource_%%current_world_key%%, style: f => f.get('style'), title: 'Spieler' });
const tpadLayer = new ol.layer.Vector({ source: window.tpadMarkerSource_%%current_world_key%%, style: tpadStyle, title: 'Teleporter' });
const parentAreaLayer = new ol.layer.Vector({ source: window.parentAreaLayerSource_%%current_world_key%%, style: parentAreaStyle, title: 'Grundstücke', visible: false });
const subAreaLayer = new ol.layer.Vector({ source: window.subAreaLayerSource_%%current_world_key%%, style: subAreaStyle, visible: false });
const overlayLayers = new ol.layer.Group({ title: 'Overlays', layers: [subAreaLayer, parentAreaLayer, tpadLayer, playerLayer] });
const view = new ol.View({ projection: projection, center: ol.extent.getCenter(extent), resolutions: viewResolutions, maxZoom: viewResolutions.length - 1 });
const popupContainer = document.getElementById('popup-%%current_world_key%%');
const popupContent = document.getElementById('popup-content-%%current_world_key%%');
const popupCloser = document.getElementById('popup-closer-%%current_world_key%%');
const overlay = new ol.Overlay({ element: popupContainer, autoPan: { animation: { duration: 250 } } });
popupCloser.onclick = () => { overlay.setPosition(undefined); popupCloser.blur(); return false; };
const mousePositionControl = new ol.control.MousePosition({
className: 'custom-mouse-position',
coordinateFormat: function(coordinate) {
return convertOpenLayersToMinetest_%%current_world_key%%(coordinate);
},
undefinedHTML: '&nbsp;'
});
const mapControls = [ new ol.control.Zoom(), new ol.control.Rotate(), new ol.control.Attribution({ collapsible: false }), new ol.control.FullScreen(), new ol.control.LayerSwitcher(), mousePositionControl ];
window.olMap_%%current_world_key%% = new ol.Map({
controls: mapControls,
layers: [tileLayer, overlayLayers],
overlays: [overlay],
target: mapContainer,
view: view
});
const map = window.olMap_%%current_world_key%%;
map.getView().fit(extent, { size: map.getSize() });
for (const id in areaData) {
const area = areaData[id];
const polygon = createPolygonFromPos_%%current_world_key%%(area.pos1, area.pos2);
if (polygon) {
const feature = new ol.Feature({ geometry: polygon, areaData: area });
window.parentAreaLayerSource_%%current_world_key%%.addFeature(feature);
}
}
tpadData.forEach(tpad => {
const coords = convertMinetestToOpenLayers_%%current_world_key%%(tpad.posX, tpad.posZ);
if (coords) {
const feature = new ol.Feature({ geometry: new ol.geom.Point(coords), tpadData: tpad });
window.tpadMarkerSource_%%current_world_key%%.addFeature(feature);
}
});
map.on('click', function(evt) {
overlay.setPosition(undefined);
popupCloser.blur();
const playerFeature = map.forEachFeatureAtPixel(evt.pixel, (f, l) => l === playerLayer ? f : undefined);
if (playerFeature) {
window.subAreaLayerSource_%%current_world_key%%.clear();
subAreaLayer.setVisible(false);
const playerData = playerFeature.get('playerData');
if (playerData) {
const statusDotClass = playerFeature.get('statusDotClass');
const lastLoginFormatted = playerFeature.get('lastLoginFormatted');
// KORREKTUR: Layout des Spieler-Popups
const popupHTML = `
<div class='popup-player-box'>
<div class="player-header"><div class="player-identity">
<img src="/images/players/${encodeURIComponent(playerData.name)}.png?v=%%CACHE_BUSTER%%" class="player-icon" onerror="this.onerror=null;this.src='/%%DEFAULT_PLAYER_SKIN_URL%%?v=%%CACHE_BUSTER%%';">
<span class="player-name-status">
<span class="status-dot ${statusDotClass}" title="Letzter Login: ${lastLoginFormatted}"></span>
<strong class="player-name">${playerData.name}</strong>
</span>
</div></div>
<hr class="privilege-separator">
<div class="popup-player-vitals">
<span class="vital"><span class="icon">❤️</span> ${playerData.hp ?? '?'}</span>
<span class="vital"><span class="icon">💨</span> ${playerData.breath ?? '?'}</span>
<span class="vital"><span class="icon">🍖</span> ${playerData.stamina ?? '?'}</span>
</div>
</div>`;
popupContent.innerHTML = popupHTML;
overlay.setPosition(playerFeature.getGeometry().getCoordinates());
}
return;
}
const tpadFeature = map.forEachFeatureAtPixel(evt.pixel, (f, l) => l === tpadLayer ? f : undefined);
if (tpadFeature) {
window.subAreaLayerSource_%%current_world_key%%.clear();
subAreaLayer.setVisible(false);
const tpadData = tpadFeature.get('tpadData');
if (tpadData) {
// KORREKTUR: Layout des TPAD-Popups
const popupHTML = `
<div class='popup-player-box'>
<div class="player-header"><div class="player-identity">
<img src="/images/tpad.png?v=%%CACHE_BUSTER%%" class="player-icon" alt="Teleporter">
<div class="player-name-status" style="display: block;">
<strong class="player-name">${tpadData.name}</strong><br>
<span style="font-size: 0.9em; color: #ccc;">Teleporter</span>
</div>
</div></div>
<hr class="privilege-separator">
<div>Besitzer: ${tpadData.owner}</div>
</div>`;
popupContent.innerHTML = popupHTML;
overlay.setPosition(tpadFeature.getGeometry().getCoordinates());
}
return;
}
window.subAreaLayerSource_%%current_world_key%%.clear();
const areaFeature = map.forEachFeatureAtPixel(evt.pixel, (f, l) => l === parentAreaLayer ? f : undefined);
if (areaFeature) {
const areaData = areaFeature.get('areaData');
if (areaData.sub_areas && areaData.sub_areas.length > 0) {
const sub_features = [];
areaData.sub_areas.forEach(sub => {
const sub_polygon = createPolygonFromPos_%%current_world_key%%(sub.pos1, sub.pos2);
if(sub_polygon) sub_features.push(new ol.Feature({ geometry: sub_polygon }));
});
window.subAreaLayerSource_%%current_world_key%%.addFeatures(sub_features);
subAreaLayer.setVisible(true);
} else {
subAreaLayer.setVisible(false);
}
let parcelsHTML = '';
if (areaData.sub_areas && areaData.sub_areas.length > 0) {
parcelsHTML += `<div style="margin-top: 5px;"><p style="margin: 0; font-weight: bold;">Parzellen:</p><ul>`;
areaData.sub_areas.forEach(sub => { parcelsHTML += `<li style="font-size: 0.9em;">${sub.name} (${sub.owner})</li>`; });
parcelsHTML += `</ul></div>`;
}
// KORREKTUR: Layout des Grundstück-Popups
const popupHTML = `
<div class='popup-player-box'>
<div class="player-header"><div class="player-identity">
<img src="/images/area.png?v=%%CACHE_BUSTER%%" class="player-icon" alt="Grundstück">
<div class="player-name-status" style="display: block;">
<strong class="player-name">${areaData.name}</strong><br>
<span style="font-size: 0.9em; color: #ccc;">Grundstück</span>
</div>
</div></div>
<hr class="privilege-separator">
<div>Besitzer: ${areaData.owner}</div>
${parcelsHTML}
</div>`;
popupContent.innerHTML = popupHTML;
overlay.setPosition(evt.coordinate);
} else {
subAreaLayer.setVisible(false);
}
});
if (window.playerListLogic_%%current_world_key%% && window.playerListLogic_%%current_world_key%%.masterPlayerData) {
window.playerListLogic_%%current_world_key%%.updatePlayerMarkers(window.playerListLogic_%%current_world_key%%.masterPlayerData);
}
}
})
.catch(error => {
console.error("Fehler beim Initialisieren der Kartendaten:", error);
mapContainer.innerHTML = "<p><em>Karte konnte nicht initialisiert werden: " + error.message + "</em></p>";
});
const liveBtn = document.getElementById('toggle-live-btn-%%current_world_key%%');
const archiveBtn = document.getElementById('toggle-archive-btn-%%current_world_key%%');
const liveContainer = document.getElementById('live-map-container-%%current_world_key%%');
const archiveContainer = document.getElementById('archive-view-container-%%current_world_key%%');
if (liveBtn && archiveBtn && liveContainer && archiveContainer) {
const setActiveButton = (activeBtn) => {
liveBtn.classList.remove('active');
archiveBtn.classList.remove('active');
activeBtn.classList.add('active');
};
liveBtn.addEventListener('click', () => {
setActiveButton(liveBtn);
liveContainer.style.display = 'block';
archiveContainer.style.display = 'none';
if (window.olMap_%%current_world_key%%) {
setTimeout(() => window.olMap_%%current_world_key%%.updateSize(), 10);
}
});
archiveBtn.addEventListener('click', () => {
setActiveButton(archiveBtn);
archiveContainer.style.display = 'block';
liveContainer.style.display = 'none';
});
}
});
</script>