Update README.md, check_dependencies.sh, colors.txt, and 3 more files for new alpha, get rid of IMagick for vips
This commit is contained in:
parent
33f64cc57d
commit
a3556c44e1
6 changed files with 1317 additions and 1048 deletions
48
README.md
48
README.md
|
|
@ -7,14 +7,14 @@ This system is designed from the ground up to be modular, easily configurable, a
|
|||
## ✨ Features
|
||||
|
||||
* **Automated Map Generation:** Leverages `minetestmapper` to create high-resolution PNG maps of your game worlds.
|
||||
* **Performant Image Processing:** Uses `vips`, a high-performance and memory-efficient library, to scale even huge maps (tested up to 64k x 64k pixels) for the web.
|
||||
* **Performant Image Processing:** Uses `vips` for all image processing tasks (resizing and metadata reading), providing a massive performance increase and support for huge maps. The project is now completely independent of ImageMagick.
|
||||
* **Tiled Map Generation:** Uses `gdal2tiles.py` to create performant, zoomable map tiles for a smooth user experience.
|
||||
* **Dynamic Map Viewer:** Implements an interactive map viewer using **OpenLayers**, powered by the generated map tiles, including digital zoom beyond the highest resolution.
|
||||
* **Live Player & Area Display:** Dynamically loads player positions and protected areas, displaying them as interactive overlays on the live map.
|
||||
* **Layer Control:** A menu on the map allows toggling the visibility of players, parent areas, and sub-areas (parcels).
|
||||
* **Live Data Overlays:** Dynamically fetches player positions and protected areas, displaying them as interactive overlays on the live map.
|
||||
* **Layer Control:** A menu on the map allows users to toggle the visibility of Players, Parent Areas, and Sub-Areas (Parcels).
|
||||
* **Map Archive:** Automatically saves a daily snapshot of the map and makes it available through a toggle on the world detail page.
|
||||
* **Template-Driven Site Generation:** Builds all static HTML pages from simple, customizable templates.
|
||||
* **Flexible Configuration:** Configuration is easy with a central global `config.sh` and a `web.conf` file for every one of your worlds.
|
||||
* **Flexible Configuration:** Configuration is easy with a central global `config.sh` and a `web.conf`-file for every of your worlds.
|
||||
* **Automation-Ready:** Designed for unattended execution via scheduling tools like `cron`.
|
||||
|
||||
## 🔧 Prerequisites
|
||||
|
|
@ -24,8 +24,6 @@ To run this system, the following software packages must be installed on your se
|
|||
* **bash:** The scripting language used for the entire project.
|
||||
* **vips:** A high-performance image processing library.
|
||||
* *Debian/Ubuntu Install:* `sudo apt-get install libvips-tools`
|
||||
* **ImageMagick:** Currently still required for the `identify` command (to read image dimensions).
|
||||
* *Debian/Ubuntu Install:* `sudo apt-get install imagemagick`
|
||||
* **GDAL/OGR:** Provides the `gdal2tiles.py` script for tile generation.
|
||||
* *Debian/Ubuntu Install:* `sudo apt-get install gdal-bin python3-gdal`
|
||||
* **SQLite3:** The command-line tool to query the game databases (`players.sqlite`, `auth.sqlite`).
|
||||
|
|
@ -34,7 +32,7 @@ To run this system, the following software packages must be installed on your se
|
|||
* *Debian/Ubuntu Install:* `sudo apt-get install bc`
|
||||
* **jq:** A command-line JSON processor, used by `sync_areas.sh`.
|
||||
* *Debian/Ubuntu Install:* `sudo apt-get install jq`
|
||||
* **minetestmapper:** The executable used to render maps from world data. Must be placed within the project directory.
|
||||
* **minetestmapper:** The executable used to render maps from world data. This must be placed within the project directory.
|
||||
* **iproute2:** Provides the `ss` command for `check_server_status.sh` (usually pre-installed on most systems).
|
||||
* **Web Server:** A web server like Nginx or Apache is needed to serve the generated static files.
|
||||
|
||||
|
|
@ -51,7 +49,7 @@ OR
|
|||
Clone the Git repository to a base directory.
|
||||
|
||||
```bash
|
||||
git clone [https://git.geigernet.eu/rainer/luanti-web.git](https://git.geigernet.eu/rainer/luanti-web.git) /opt/luweb
|
||||
git clone https://git.geigernet.eu/rainer/luanti-web.git /opt/luweb
|
||||
cd /opt/luweb
|
||||
# Make all scripts executable
|
||||
chmod +x generate_map.sh generate_site.sh check_server_status.sh check_dependencies.sh sync_players.sh sync_areas.sh
|
||||
|
|
@ -79,8 +77,7 @@ The system is designed so that **only worlds with a `web.conf` file** will be di
|
|||
├── minetestmapper (executable)
|
||||
├── site_generator/
|
||||
│ ├── functions/
|
||||
│ │ └── generators/
|
||||
│ │ └── ...
|
||||
│ │ └── ...
|
||||
│ ├── templates/
|
||||
│ └── examples/
|
||||
├── web_content/
|
||||
|
|
@ -92,17 +89,20 @@ The system is designed so that **only worlds with a `web.conf` file** will be di
|
|||
|
||||
## 🚀 Usage & Automation (Cronjob)
|
||||
|
||||
The scripts are designed for automated execution. Set them up using `crontab -e`.
|
||||
The scripts are designed for automated execution. If run without a `<world_key>` argument, they will process all worlds that contain a `web.conf` file.
|
||||
|
||||
**Example for `crontab -e`:**
|
||||
```bash
|
||||
# (Frequently) Update player and server status
|
||||
# Update player data for all worlds every minute
|
||||
* * * * * cd /opt/luweb && ./sync_players.sh >> /var/log/luweb/cron.log 2>&1
|
||||
|
||||
# Check server online status every 5 minutes
|
||||
*/5 * * * * cd /opt/luweb && ./check_server_status.sh >> /var/log/luweb/cron.log 2>&1
|
||||
|
||||
# (Hourly) Generate the base map and tiles
|
||||
# Generate map and tiles for all worlds once per hour
|
||||
0 * * * * cd /opt/luweb && ./generate_map.sh >> /var/log/luweb/cron.log 2>&1
|
||||
|
||||
# (Infrequently) Sync area data and rebuild the static site
|
||||
# Sync area data and rebuild the static site twice a day
|
||||
45 */12 * * * cd /opt/luweb && ./sync_areas.sh >> /var/log/luweb/cron.log 2>&1
|
||||
30 */12 * * * cd /opt/luweb && ./generate_site.sh >> /var/log/luweb/cron.log 2>&1
|
||||
```
|
||||
|
|
@ -111,7 +111,23 @@ The scripts are designed for automated execution. Set them up using `crontab -e`
|
|||
**MIT License**
|
||||
Copyright (c) 2025 Rage87 - rage87@geigernet.eu
|
||||
|
||||
(License text as before)
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
## 👤 Authors
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
## 👤 Autoren
|
||||
* **Rage87** (Main-Developer)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ else
|
|||
fi
|
||||
|
||||
# Checks für andere Programme, die im PATH sein sollten
|
||||
# HINZUGEFÜGT: sqlite3 und jq
|
||||
declare -a runtime_deps_in_path=("gdal2tiles.py" "vips" "ss" "bc" "sqlite3" "jq")
|
||||
# KORREKTUR: 'identify' entfernt, 'vipsheader' hinzugefügt.
|
||||
declare -a runtime_deps_in_path=("gdal2tiles.py" "vips" "vipsheader" "ss" "bc" "sqlite3" "jq")
|
||||
for dep in "${runtime_deps_in_path[@]}"; do
|
||||
if ! command -v "$dep" &> /dev/null; then
|
||||
echo "[-] FEHLER: Das benötigte Programm '$dep' wurde nicht im System-Pfad gefunden."
|
||||
|
|
@ -55,7 +55,6 @@ if ! command -v "dpkg" &> /dev/null; then
|
|||
echo "[!] WARNUNG: 'dpkg' Kommando nicht gefunden. Kann Build-Abhängigkeiten nicht prüfen. (Dies ist normal auf nicht-Debian-Systemen)"
|
||||
else
|
||||
for pkg in "${build_deps[@]}"; do
|
||||
# dpkg -s gibt einen Fehlercode zurück, wenn das Paket nicht installiert ist
|
||||
if ! dpkg -s "$pkg" &> /dev/null; then
|
||||
echo "[-] FEHLER: Das benötigte Build-Paket '$pkg' scheint nicht installiert zu sein."
|
||||
missing_count=$((missing_count + 1))
|
||||
|
|
@ -77,7 +76,7 @@ if [ "$missing_count" -gt 0 ]; then
|
|||
echo " Laufzeit-Programme:"
|
||||
echo " - minetestmapper: Muss manuell kompiliert oder aus einer anderen Quelle bezogen werden."
|
||||
echo " - gdal-bin: Enthält 'gdal2tiles.py'."
|
||||
echo " - libvips-tools: Enthält 'vips' für die Bildbearbeitung."
|
||||
echo " - libvips-tools: Enthält 'vips' und 'vipsheader' für die Bildbearbeitung."
|
||||
echo " - iproute2: Enthält 'ss' (meist vorinstalliert)."
|
||||
echo " - bc: Für mathematische Operationen in Skripten."
|
||||
echo " - sqlite3: Zur Abfrage der Spieldatenbanken."
|
||||
|
|
|
|||
1930
colors.txt
1930
colors.txt
File diff suppressed because it is too large
Load diff
|
|
@ -29,7 +29,6 @@ if [ -z "$1" ]; then
|
|||
if [ -f "${world_dir}web.conf" ]; then
|
||||
world_key_to_process=$(basename "$world_dir")
|
||||
log_message "main" "--- Starte Durchlauf für '${world_key_to_process}' ---"
|
||||
# Rufe das Skript für die gefundene Welt rekursiv auf
|
||||
bash "$0" "$world_key_to_process"
|
||||
fi
|
||||
done
|
||||
|
|
@ -38,17 +37,14 @@ if [ -z "$1" ]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
# #############################################################################
|
||||
# Ab hier beginnt die Logik für eine EINZELNE Welt
|
||||
# #############################################################################
|
||||
|
||||
# Prüfe Abhängigkeiten, bevor irgendetwas anderes passiert
|
||||
/opt/luweb/check_dependencies.sh || exit 1
|
||||
|
||||
WORLD_KEY=$1
|
||||
|
||||
# Pfad zum Verzeichnis der aktuellen Welt
|
||||
CURRENT_MINETEST_WORLD_DATA_PATH="${MINETESTMAPPER_WORLD_DATA_BASE_PATH}${WORLD_KEY}/"
|
||||
|
||||
if [ ! -d "$CURRENT_MINETEST_WORLD_DATA_PATH" ]; then
|
||||
|
|
@ -94,105 +90,97 @@ ARCHIVE_BASE_WEB_PATH="${WEB_CURRENT_WORLD_DIR}/${ARCHIVE_SUBDIR_NAME}"
|
|||
|
||||
# === Funktion zur Archivbereinigung ===
|
||||
prune_archives() {
|
||||
log_message "${WORLD_KEY}" "Starte Archivbereinigung für Welt '${WORLD_KEY}' im Pfad '${ARCHIVE_BASE_WEB_PATH}'..."
|
||||
if [ ! -d "$ARCHIVE_BASE_WEB_PATH" ]; then log_message "${WORLD_KEY}" "Archiv-Basispfad ${ARCHIVE_BASE_WEB_PATH} nicht gefunden."; return; fi
|
||||
local today_seconds=$(date +%s); local cutoff_date_14_days=$(date -d "today - 14 days" +%Y-%m-%d)
|
||||
local cutoff_seconds_14_days=$(date -d "$cutoff_date_14_days" +%s)
|
||||
log_message "${WORLD_KEY}" "Archivbereinigung: Behalte tägliche Bilder bis einschl. ${cutoff_date_14_days}. Ältere nur Montage."
|
||||
local images_processed=0; local images_deleted=0
|
||||
log_message "${WORLD_KEY}" "Starte Archivbereinigung..."
|
||||
if [ ! -d "$ARCHIVE_BASE_WEB_PATH" ]; then return; fi
|
||||
local cutoff_date_14_days=$(date -d "today - 14 days" +%Y-%m-%d)
|
||||
find "$ARCHIVE_BASE_WEB_PATH" -type f -name "*.png" | while IFS= read -r archive_file_path; do
|
||||
((images_processed++))
|
||||
if [[ "$archive_file_path" =~ /([0-9]{4})/([0-9]{2})/([0-9]{2})\.png$ ]]; then
|
||||
local year="${BASH_REMATCH[1]}"; local month="${BASH_REMATCH[2]}"; local day="${BASH_REMATCH[3]}"
|
||||
local img_date_str="${year}-${month}-${day}"; local img_date_seconds; local day_of_week
|
||||
if ! date -d "$img_date_str" "+%s" >/dev/null 2>&1; then log_message "${WORLD_KEY}" "WARNUNG: Ungültiges Datum: '${img_date_str}' ('${archive_file_path}')."; continue; fi
|
||||
img_date_seconds=$(date -d "$img_date_str" +%s)
|
||||
if [ "$img_date_seconds" -ge "$cutoff_seconds_14_days" ]; then log_message "${WORLD_KEY}" "BEHALTE (<=14 Tage): ${archive_file_path}"
|
||||
else day_of_week=$(date -d "$img_date_str" +%u); if [ "$day_of_week" -eq 1 ]; then log_message "${WORLD_KEY}" "BEHALTE (>14 Tage, Montag): ${archive_file_path}"; else log_message "${WORLD_KEY}" "LÖSCHE (>14 Tage, kein Montag): ${archive_file_path}"; if rm -f "$archive_file_path"; then ((images_deleted++)); else log_message "${WORLD_KEY}" "FEHLER Löschen: ${archive_file_path}"; fi; fi; fi
|
||||
else log_message "${WORLD_KEY}" "WARNUNG: Pfad '${archive_file_path}' passt nicht zu JJJJ/MM/TT.png."; fi
|
||||
local img_date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}"
|
||||
if ! date -d "$img_date_str" "+%s" >/dev/null 2>&1; then continue; fi
|
||||
if [ "$(date -d "$img_date_str" +%s)" -ge "$(date -d "$cutoff_date_14_days" +%s)" ]; then continue; fi
|
||||
if [ "$(date -d "$img_date_str" +%u)" -ne 1 ]; then
|
||||
log_message "${WORLD_KEY}" "LÖSCHE (>14 Tage, kein Montag): ${archive_file_path}"
|
||||
rm -f "$archive_file_path"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
log_message "${WORLD_KEY}" "Archivbereinigung: ${images_processed} geprüft, ${images_deleted} gelöscht."
|
||||
log_message "${WORLD_KEY}" "Räume leere Archiv-Unterverzeichnisse auf..."; find "$ARCHIVE_BASE_WEB_PATH" -mindepth 1 -type d -empty -print -delete >> "$LOG_FILE" 2>&1
|
||||
find "$ARCHIVE_BASE_WEB_PATH" -mindepth 1 -type d -empty -delete
|
||||
}
|
||||
|
||||
# === Hauptlogik für eine einzelne Welt ===
|
||||
exec 200>"$LOCK_FILE"
|
||||
flock -n 200 || { log_message "${WORLD_KEY}" "Script ${SCRIPT_BASENAME}.sh ist bereits für diese Welt aktiv (Lock: ${LOCK_FILE}). Beende."; exit 1; }
|
||||
flock -n 200 || { log_message "${WORLD_KEY}" "Script bereits aktiv. Beende."; exit 1; }
|
||||
trap 'rm -f "$LOCK_FILE"; log_message "${WORLD_KEY}" "Skript beendet."' EXIT
|
||||
log_message "${WORLD_KEY}" "Skript gestartet."
|
||||
|
||||
mkdir -p "${RAW_MAP_OUTPUT_DIR_ABSOLUTE}"
|
||||
|
||||
# 1. Generiere die map.png
|
||||
log_message "${WORLD_KEY}" "Starte minetestmapper zur Kartengenerierung..."
|
||||
MM_ALL_OPTIONS_STR="--zoom ${MM_OPT_ZOOM_LEVEL}"; if [ "${MM_CFG_DRAWALPHA}" = "true" ]; then MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --drawalpha"; fi
|
||||
if [ "${MM_CFG_DRAWORIGIN}" = "true" ]; then MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --draworigin"; fi
|
||||
log_message "${WORLD_KEY}" "Starte minetestmapper..."
|
||||
MM_ALL_OPTIONS_STR="--zoom ${MM_OPT_ZOOM_LEVEL} --min-y ${MM_OPT_MIN_Y}"
|
||||
if [ "${MM_CFG_DRAWALPHA}" = "true" ]; then MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --drawalpha"; fi
|
||||
if [ "${MM_CFG_DRAWPLAYERS}" = "true" ]; then MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --drawplayers"; fi
|
||||
if [ "${MM_CFG_DRAWSCALE}" = "true" ]; then MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --drawscale"; fi
|
||||
MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --origincolor '${MM_OPT_ORIGINCOLOR}' --playercolor '${MM_OPT_PLAYERCOLOR}'"
|
||||
MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --scalecolor '${MM_OPT_SCALECOLOR}' --bgcolor '${MM_OPT_BGCOLOR}'"
|
||||
MM_ALL_OPTIONS_STR="${MM_ALL_OPTIONS_STR} --min-y ${MM_OPT_MIN_Y}"
|
||||
|
||||
MAP_GENERATION_COMMAND="'${MINETESTMAPPER_PATH}' -i '${CURRENT_MINETEST_WORLD_DATA_PATH}' -o '${RAW_MAP_ABSOLUTE_PATH}' ${MM_ALL_OPTIONS_STR}"
|
||||
MAPPER_RUN_OUTPUT_CAPTURE_FILE=$(mktemp)
|
||||
(set -o pipefail; eval "${MAP_GENERATION_COMMAND}" 2>&1 | tee -a "$LOG_FILE" > "$MAPPER_RUN_OUTPUT_CAPTURE_FILE"); MAPPER_EXIT_STATUS=$?
|
||||
if [ ${MAPPER_EXIT_STATUS} -ne 0 ]; then log_message "${WORLD_KEY}" "FEHLER: minetestmapper (Status: ${MAPPER_EXIT_STATUS})."; rm -f "$MAPPER_RUN_OUTPUT_CAPTURE_FILE"; exit 1; fi
|
||||
if [ ! -f "$RAW_MAP_ABSOLUTE_PATH" ]; then log_message "${WORLD_KEY}" "FEHLER: ${RAW_MAP_ABSOLUTE_PATH} nicht gefunden."; rm -f "$MAPPER_RUN_OUTPUT_CAPTURE_FILE"; exit 1; fi
|
||||
if [ ${MAPPER_EXIT_STATUS} -ne 0 ] || [ ! -f "$RAW_MAP_ABSOLUTE_PATH" ]; then
|
||||
log_message "${WORLD_KEY}" "FEHLER: minetestmapper (Status: ${MAPPER_EXIT_STATUS})."
|
||||
rm -f "$MAPPER_RUN_OUTPUT_CAPTURE_FILE"; exit 1;
|
||||
fi
|
||||
log_message "${WORLD_KEY}" "map.png erfolgreich generiert."
|
||||
|
||||
# 2. Erstelle map_info.txt
|
||||
log_message "${WORLD_KEY}" "Erstelle map_info.txt..."
|
||||
MAP_DIMENSIONS=$(identify -format "%wx%h" "$RAW_MAP_ABSOLUTE_PATH" 2>/dev/null)
|
||||
# KORREKTUR: Die komplizierte Fallback-Logik wird durch den einfachen, funktionierenden vipsheader-Befehl ersetzt.
|
||||
IMG_WIDTH=$(vipsheader -f width "$RAW_MAP_ABSOLUTE_PATH" 2>/dev/null)
|
||||
IMG_HEIGHT=$(vipsheader -f height "$RAW_MAP_ABSOLUTE_PATH" 2>/dev/null)
|
||||
|
||||
EXTENT_COMMAND="'${MINETESTMAPPER_PATH}' -i '${CURRENT_MINETEST_WORLD_DATA_PATH}' --extent ${MM_ALL_OPTIONS_STR}"
|
||||
MAP_EXTENT=$(eval "${EXTENT_COMMAND}" 2>/dev/null | sed 's/Map extent: //')
|
||||
if [ -n "$MAP_DIMENSIONS" ] && [ -n "$MAP_EXTENT" ]; then
|
||||
|
||||
if [[ "$IMG_WIDTH" =~ ^[0-9]+$ ]] && [ "$IMG_WIDTH" -gt 0 ] && [ -n "$MAP_EXTENT" ]; then
|
||||
MAP_DIMENSIONS="${IMG_WIDTH}x${IMG_HEIGHT}"
|
||||
{ echo "map_dimension=${MAP_DIMENSIONS}"; echo "map_extent=${MAP_EXTENT}"; } > "$MAP_INFO_FILE_ABSOLUTE_PATH"
|
||||
log_message "${WORLD_KEY}" "map_info.txt erstellt: Dim=${MAP_DIMENSIONS}, Extent=${MAP_EXTENT}"
|
||||
else
|
||||
log_message "${WORLD_KEY}" "FEHLER: map_info.txt konnte nicht erstellt werden."
|
||||
log_message "${WORLD_KEY}" "-> Ermittelte Bild-Breite: '${IMG_WIDTH}'"
|
||||
log_message "${WORLD_KEY}" "-> Ermittelter Karten-Extent: '${MAP_EXTENT}'"
|
||||
fi
|
||||
|
||||
# 3. Verarbeite unknown_nodes.txt
|
||||
awk ' /Unknown nodes:/ {b=1;next} b&&NF==0 {b=0} b&&!/^[ \t]/ {b=0} b{n=$0;sub(/^[ \t]+/,"",n);sub(/[ \t]+$/,"",n);if(n~/:/&&n!="")print n} ' "$MAPPER_RUN_OUTPUT_CAPTURE_FILE" > "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}.new"
|
||||
if [ -s "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}.new" ]; then
|
||||
log_message "${WORLD_KEY}" "Neue 'Unknown nodes' gefunden. Füge zu bestehender Datei hinzu."
|
||||
cat "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}.new" >> "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}"
|
||||
sort -u "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}" -o "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}"
|
||||
fi
|
||||
rm -f "${UNKNOWN_NODES_FILE_ABSOLUTE_PATH}.new" "$MAPPER_RUN_OUTPUT_CAPTURE_FILE"
|
||||
|
||||
# 4. Erzeuge Web-Vorschaukarte mit vips
|
||||
log_message "${WORLD_KEY}" "Erzeuge Web-Version von map.png (max ${RESIZED_MAX_DIMENSION}px) mit 'vips'..."
|
||||
mkdir -p "$(dirname "$WEB_MAP_PNG_FULL_PATH")"
|
||||
(set -o pipefail; vips thumbnail "$RAW_MAP_ABSOLUTE_PATH" "$WEB_MAP_PNG_FULL_PATH" "${RESIZED_MAX_DIMENSION}" --height "${RESIZED_MAX_DIMENSION}" --size "down" 2>&1 | tee -a "$LOG_FILE")
|
||||
if [ $? -ne 0 ]; then log_message "${WORLD_KEY}" "FEHLER: Skalierung mit 'vips' fehlgeschlagen."; else log_message "${WORLD_KEY}" "Verkleinerte Web-map.png erstellt."; fi
|
||||
|
||||
# 5. Generiere Kacheln
|
||||
log_message "${WORLD_KEY}" "Generiere Kacheln (Zoom: ${GDAL2TILES_ZOOM_LEVELS})..."
|
||||
TEMP_TILES_DIR="${TILES_FULL_OUTPUT_PATH}_temp_$(date +%s)"; rm -rf "$TEMP_TILES_DIR";
|
||||
TEMP_TILES_DIR="${TILES_FULL_OUTPUT_PATH}_temp_$(date +%s)";
|
||||
(set -o pipefail; gdal2tiles.py --profile=raster --xyz --zoom="${GDAL2TILES_ZOOM_LEVELS}" -r near "${RAW_MAP_ABSOLUTE_PATH}" "${TEMP_TILES_DIR}" 2>&1 | tee -a "$LOG_FILE")
|
||||
if [ $? -ne 0 ]; then log_message "${WORLD_KEY}" "FEHLER: gdal2tiles.py fehlgeschlagen."; rm -rf "$TEMP_TILES_DIR"; exit 1; fi
|
||||
rm -rf "$TILES_FULL_OUTPUT_PATH"
|
||||
if ! mv "$TEMP_TILES_DIR" "$TILES_FULL_OUTPUT_PATH"; then log_message "${WORLD_KEY}" "FEHLER: Verschieben der Kacheln fehlgeschlagen."; exit 1; fi
|
||||
mv "$TEMP_TILES_DIR" "$TILES_FULL_OUTPUT_PATH"
|
||||
log_message "${WORLD_KEY}" "Kacheln erfolgreich generiert."
|
||||
|
||||
# 6. Archiv-Management
|
||||
prune_archives
|
||||
ARCHIVE_YEAR=$(date '+%Y'); ARCHIVE_MONTH=$(date '+%m'); ARCHIVE_DAY=$(date '+%d')
|
||||
ARCHIVE_DAILY_TARGET_DIR="${ARCHIVE_BASE_WEB_PATH}/${ARCHIVE_YEAR}/${ARCHIVE_MONTH}"
|
||||
ARCHIVE_DAILY_FILE_PATH="${ARCHIVE_DAILY_TARGET_DIR}/${ARCHIVE_DAY}.png"
|
||||
if [ ! -f "$ARCHIVE_DAILY_FILE_PATH" ]; then
|
||||
log_message "${WORLD_KEY}" "Erzeuge Archivbild für ${ARCHIVE_DAILY_FILE_PATH}..."
|
||||
mkdir -p "$ARCHIVE_DAILY_TARGET_DIR"
|
||||
(set -o pipefail; vips thumbnail "$RAW_MAP_ABSOLUTE_PATH" "$ARCHIVE_DAILY_FILE_PATH" "${RESIZED_MAX_DIMENSION}" --height "${RESIZED_MAX_DIMENSION}" --size "down" 2>&1 | tee -a "$LOG_FILE")
|
||||
if [ $? -eq 0 ]; then log_message "${WORLD_KEY}" "Verkleinertes Archivbild erstellt."; else log_message "${WORLD_KEY}" "FEHLER: Archivbild nicht erstellt.";fi
|
||||
vips thumbnail "$RAW_MAP_ABSOLUTE_PATH" "$ARCHIVE_DAILY_FILE_PATH" "${RESIZED_MAX_DIMENSION}" --height "${RESIZED_MAX_DIMENSION}" --size "down"
|
||||
fi
|
||||
|
||||
# 7. Finale Info-Dateien kopieren
|
||||
log_message "${WORLD_KEY}" "Kopiere finale Info-Dateien ins Web-Verzeichnis..."
|
||||
mkdir -p "$WEB_CURRENT_WORLD_DIR"
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S %Z')" > "${WEB_CURRENT_WORLD_DIR}/last_update.txt"
|
||||
cp "$UNKNOWN_NODES_FILE_ABSOLUTE_PATH" "${WEB_CURRENT_WORLD_DIR}/unknown_nodes.txt"
|
||||
cp "$MAP_INFO_FILE_ABSOLUTE_PATH" "${WEB_CURRENT_WORLD_DIR}/map_info.txt"
|
||||
[ -f "$UNKNOWN_NODES_FILE_ABSOLUTE_PATH" ] && cp "$UNKNOWN_NODES_FILE_ABSOLUTE_PATH" "${WEB_CURRENT_WORLD_DIR}/unknown_nodes.txt"
|
||||
[ -f "$MAP_INFO_FILE_ABSOLUTE_PATH" ] && cp "$MAP_INFO_FILE_ABSOLUTE_PATH" "${WEB_CURRENT_WORLD_DIR}/map_info.txt"
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -89,9 +89,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
const extent = [0, -mapData.mapHeight, mapData.mapWidth, 0];
|
||||
const sourceResolutions = %%RESOLUTIONS_JS_ARRAY%%;
|
||||
|
||||
// HINZUGEFÜGT: Logik für digitalen Zoom wiederhergestellt
|
||||
const maxNativeZoom = sourceResolutions.length -1;
|
||||
|
||||
const viewResolutions = [...sourceResolutions];
|
||||
const digitalZoomLevels = 3;
|
||||
let lastResolution = viewResolutions[viewResolutions.length - 1];
|
||||
|
|
@ -124,11 +123,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
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 playerLayer = new ol.layer.Vector({ source: window.playerMarkerSource_%%current_world_key%%, style: f => f.get('style'), title: 'Spieler' });
|
||||
const parentAreaLayer = new ol.layer.Vector({ source: window.parentAreaLayerSource_%%current_world_key%%, style: parentAreaStyle, title: 'Grundstücke' });
|
||||
const subAreaLayer = new ol.layer.Vector({ source: window.subAreaLayerSource_%%current_world_key%%, style: subAreaStyle, title: 'Parzellen', visible: false });
|
||||
const playerLayer = new ol.layer.Vector({ source: window.playerMarkerSource_%%current_world_key%%, style: f => f.get('style'), title: 'Spielerpositionen' });
|
||||
// KORREKTUR: Die 'title'-Eigenschaft wurde von diesem Layer entfernt
|
||||
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, playerLayer] });
|
||||
|
||||
const view = new ol.View({ projection: projection, center: ol.extent.getCenter(extent), resolutions: viewResolutions });
|
||||
|
||||
const popupContainer = document.getElementById('popup-%%current_world_key%%');
|
||||
|
|
@ -159,18 +161,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
}
|
||||
}
|
||||
|
||||
// KORREKTUR: Klick-Handler mit korrekter Priorisierung
|
||||
map.on('click', function(evt) {
|
||||
// Reset
|
||||
overlay.setPosition(undefined);
|
||||
popupCloser.blur();
|
||||
|
||||
// Priorität 1: Spieler-Marker
|
||||
const playerFeature = map.forEachFeatureAtPixel(evt.pixel, (f, l) => l === playerLayer ? f : undefined);
|
||||
if (playerFeature) {
|
||||
window.subAreaLayerSource_%%current_world_key%%.clear(); // Sub-Areas ausblenden, wenn ein Spieler geklickt wird
|
||||
window.subAreaLayerSource_%%current_world_key%%.clear();
|
||||
subAreaLayer.setVisible(false);
|
||||
|
||||
const playerData = playerFeature.get('playerData');
|
||||
if (playerData) {
|
||||
const statusDotClass = playerFeature.get('statusDotClass');
|
||||
|
|
@ -196,11 +194,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
popupContent.innerHTML = popupHTML;
|
||||
overlay.setPosition(playerFeature.getGeometry().getCoordinates());
|
||||
}
|
||||
return; // Verarbeitung hier beenden
|
||||
return;
|
||||
}
|
||||
|
||||
// Priorität 2: Hauptgrundstück (nur wenn kein Spieler geklickt wurde)
|
||||
window.subAreaLayerSource_%%current_world_key%%.clear(); // Alte Sub-Areas immer entfernen
|
||||
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');
|
||||
|
|
@ -215,7 +212,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
} else {
|
||||
subAreaLayer.setVisible(false);
|
||||
}
|
||||
|
||||
let popupHTML = `<strong>${areaData.name}</strong><br>Besitzer: ${areaData.owner}`;
|
||||
if (areaData.sub_areas && areaData.sub_areas.length > 0) {
|
||||
popupHTML += `<hr class="privilege-separator"><p><strong>Parzellen:</strong></p><ul>`;
|
||||
|
|
@ -225,7 +221,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
popupContent.innerHTML = popupHTML;
|
||||
overlay.setPosition(evt.coordinate);
|
||||
} else {
|
||||
// Wenn nichts geklickt wurde, auch die sub-areas ausblenden
|
||||
subAreaLayer.setVisible(false);
|
||||
}
|
||||
});
|
||||
|
|
@ -239,14 +234,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
console.error("Fehler beim Initialisieren der Karte oder der Grundstücksdaten:", 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) {
|
||||
liveBtn.addEventListener('click', () => { 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', () => { archiveContainer.style.display = 'block'; liveContainer.style.display = 'none'; });
|
||||
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>
|
||||
|
|
|
|||
252
site_generator/templates/world_detail_radar.template.bak.0
Normal file
252
site_generator/templates/world_detail_radar.template.bak.0
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
<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.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];
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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(); })
|
||||
]).then(([mapInfoText, areaData]) => {
|
||||
|
||||
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%%;
|
||||
|
||||
// HINZUGEFÜGT: Logik für digitalen Zoom wiederhergestellt
|
||||
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 parentAreaLayer = new ol.layer.Vector({ source: window.parentAreaLayerSource_%%current_world_key%%, style: parentAreaStyle, title: 'Grundstücke' });
|
||||
const subAreaLayer = new ol.layer.Vector({ source: window.subAreaLayerSource_%%current_world_key%%, style: subAreaStyle, title: 'Parzellen', visible: false });
|
||||
const playerLayer = new ol.layer.Vector({ source: window.playerMarkerSource_%%current_world_key%%, style: f => f.get('style'), title: 'Spielerpositionen' });
|
||||
|
||||
const overlayLayers = new ol.layer.Group({ title: 'Overlays', layers: [subAreaLayer, parentAreaLayer, playerLayer] });
|
||||
const view = new ol.View({ projection: projection, center: ol.extent.getCenter(extent), resolutions: viewResolutions });
|
||||
|
||||
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 mapControls = [ new ol.control.Zoom(), new ol.control.Rotate(), new ol.control.Attribution({ collapsible: false }), new ol.control.FullScreen(), new ol.control.LayerSwitcher() ];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// KORREKTUR: Klick-Handler mit korrekter Priorisierung
|
||||
map.on('click', function(evt) {
|
||||
// Reset
|
||||
overlay.setPosition(undefined);
|
||||
popupCloser.blur();
|
||||
|
||||
// Priorität 1: Spieler-Marker
|
||||
const playerFeature = map.forEachFeatureAtPixel(evt.pixel, (f, l) => l === playerLayer ? f : undefined);
|
||||
if (playerFeature) {
|
||||
window.subAreaLayerSource_%%current_world_key%%.clear(); // Sub-Areas ausblenden, wenn ein Spieler geklickt wird
|
||||
subAreaLayer.setVisible(false);
|
||||
|
||||
const playerData = playerFeature.get('playerData');
|
||||
if (playerData) {
|
||||
const statusDotClass = playerFeature.get('statusDotClass');
|
||||
const lastLoginFormatted = playerFeature.get('lastLoginFormatted');
|
||||
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; // Verarbeitung hier beenden
|
||||
}
|
||||
|
||||
// Priorität 2: Hauptgrundstück (nur wenn kein Spieler geklickt wurde)
|
||||
window.subAreaLayerSource_%%current_world_key%%.clear(); // Alte Sub-Areas immer entfernen
|
||||
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 popupHTML = `<strong>${areaData.name}</strong><br>Besitzer: ${areaData.owner}`;
|
||||
if (areaData.sub_areas && areaData.sub_areas.length > 0) {
|
||||
popupHTML += `<hr class="privilege-separator"><p><strong>Parzellen:</strong></p><ul>`;
|
||||
areaData.sub_areas.forEach(sub => { popupHTML += `<li>${sub.name} (${sub.owner})</li>`; });
|
||||
popupHTML += `</ul>`;
|
||||
}
|
||||
popupContent.innerHTML = popupHTML;
|
||||
overlay.setPosition(evt.coordinate);
|
||||
} else {
|
||||
// Wenn nichts geklickt wurde, auch die sub-areas ausblenden
|
||||
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 Karte oder der Grundstücksdaten:", 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) {
|
||||
liveBtn.addEventListener('click', () => { 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', () => { archiveContainer.style.display = 'block'; liveContainer.style.display = 'none'; });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue