Update README.md, colors.txt, config.sh, and 10 more files for alpha v0.3
This commit is contained in:
parent
a08a86dc40
commit
6d9651b4ff
13 changed files with 449 additions and 109 deletions
107
README.md
107
README.md
|
|
@ -7,47 +7,40 @@ This system is designed from the ground up to be modular, easily configurable, a
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
* **Automated Map Generation:** Leverages `minetestmapper` to create high-resolution PNG maps of your game worlds.
|
* **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.
|
|
||||||
* **Tiled Map Generation:** Uses `gdal2tiles.py` to create performant, zoomable map tiles for a smooth user experience.
|
* **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.
|
* **Dynamic Map Viewer:** Implements an interactive map viewer using [Leaflet.js](https://leafletjs.com/), powered by the generated map tiles.
|
||||||
* **Live Player Tracking:** Dynamically fetches and displays player locations as markers on the live map, including custom-styled popups.
|
* **Live Player Tracking:** Dynamically fetches and displays player locations as markers on the live map.
|
||||||
* **Map Archive:** Automatically saves a daily snapshot of the map and makes it available through a dropdown viewer on the world detail page.
|
* **Map Archive:** Automatically saves a daily snapshot of the map and makes it available through a dropdown viewer on the world detail page.
|
||||||
* **Template-Driven Site Generation:** Builds all static HTML pages from simple, customizable templates.
|
* **Template-Driven Site Generation:** Builds all static HTML pages from simple, customizable templates.
|
||||||
* **Flexible Configuration:** Configuration is easy with a central global config and a `web.conf` file for every one of your worlds.
|
* **Flexible Configuration:** Configuration is easy with a central global config and a sub-config file for every of your worlds.
|
||||||
* **Automation-Ready:** Designed for unattended execution via scheduling tools like `cron`.
|
* **Automation-Ready:** Designed for unattended execution via scheduling tools like cron.
|
||||||
|
|
||||||
## 🔧 Prerequisites
|
## 🔧 Prerequisites
|
||||||
|
|
||||||
To run this system, the following software packages must be installed on your server:
|
To run this system, the following software packages must be installed on your server:
|
||||||
|
|
||||||
* **bash:** The scripting language used for the entire project.
|
* **bash:** The scripting language used for the entire project.
|
||||||
* **vips:** A high-performance image processing library that replaces `convert` (ImageMagick).
|
* **ImageMagick:** Required for `identify` (to read image dimensions) and `convert` (to resize images).
|
||||||
* *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`
|
* *Debian/Ubuntu Install:* `sudo apt-get install imagemagick`
|
||||||
* **GDAL/OGR:** Provides the `gdal2tiles.py` script for tile generation.
|
* **GDAL/OGR:** Provides the `gdal2tiles.py` script for tile generation.
|
||||||
* *Debian/Ubuntu Install:* `sudo apt-get install gdal-bin python3-gdal`
|
* *Debian/Ubuntu Install:* `sudo apt-get install gdal-bin python3-gdal`
|
||||||
* **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.
|
* **Web Server:** A web server like Nginx or Apache is needed to serve the generated static files.
|
||||||
|
|
||||||
An included script (`check_dependencies.sh`) can automatically verify all important dependencies.
|
|
||||||
|
|
||||||
## ⚙️ Installation & Setup
|
## ⚙️ Installation & Setup
|
||||||
|
|
||||||
### 1. Download/Clone Project Files
|
### 1. Clone the Repository
|
||||||
|
|
||||||
Download the **latest build** from the [Releases-Page](https://git.geigernet.eu/rainer/luanti-web/releases) and extract it to a base directory on your server, such as `/opt/luweb/`.
|
Download the **latest build** from the [Releases-Page](https://git.geigernet.eu/rainer/luanti-web/releases) and save it to your server's base directory, such as `/opt/luweb/`.
|
||||||
|
|
||||||
OR
|
OR
|
||||||
|
|
||||||
Clone the Git repository to a base directory.
|
Clone all project files to a base directory on your server.
|
||||||
|
|
||||||
```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
|
cd /opt/luweb
|
||||||
# Make all scripts executable
|
chmod +x generate_map.sh generate_site.sh
|
||||||
chmod +x generate_map.sh generate_site.sh check_server_status.sh check_dependencies.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Global Configuration
|
### 2. Global Configuration
|
||||||
|
|
@ -57,45 +50,53 @@ The main configuration file is `config.sh`. You must edit this file to match you
|
||||||
**Key variables in `config.sh`:**
|
**Key variables in `config.sh`:**
|
||||||
|
|
||||||
* `BASE_SCRIPT_DIR`: The root directory of the project (e.g., `/opt/luweb`).
|
* `BASE_SCRIPT_DIR`: The root directory of the project (e.g., `/opt/luweb`).
|
||||||
* `MINETESTMAPPER_WORLD_DATA_BASE_PATH`: The path to your Minetest/Luanti worlds' data directory.
|
* `MINETESTMAPPER_WORLD_DATA_BASE_PATH`: The path to your Minetest/Luanti worlds' data directory (e.g., `<server-path>/worlds/`), docker compatible.
|
||||||
* `WEB_ROOT_PATH`: The document root of your website where the generated files will be placed (e.g., `/var/www/your-domain.com/web`).
|
* `WEB_ROOT_PATH`: The document root of your website where the generated files will be placed (e.g., `/var/www/your-domain.com/web`).
|
||||||
* `LOG_DIR_BASE`: The directory where log files will be written (e.g., `/var/log/luweb`).
|
* `LOG_DIR_BASE`: The directory where log files will be written (e.g., `/var/log/luweb`).
|
||||||
|
|
||||||
### 3. Per-World Configuration
|
### 3. Per-World Configuration
|
||||||
|
|
||||||
The system is designed so that **only worlds with a `web.conf` file** will be displayed in the web frontend. This gives you full control over which worlds are publicly visible.
|
For each world you want to feature on the website, a `web.conf` file must exist within that world's data directory (e.g., `<server-path>/worlds/my_world/web.conf`). This file allows you to override global defaults with world-specific settings.
|
||||||
|
|
||||||
To add a world, copy the template `site_generator/examples/web.conf.template` into the data directory of the respective world (e.g., `<server-path>/worlds/my_world/web.conf`) and adjust the values.
|
A minimal `web.conf` could look like this and is automatically created for every detected world in your `MINETESTMAPPER_WORLD_DATA_BASE_PATH`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Display name for the world
|
||||||
|
WORLD_DISPLAY_NAME="My Creative World"
|
||||||
|
|
||||||
|
# Server connection details
|
||||||
|
SERVER_ADDRESS="your-server.com"
|
||||||
|
SERVER_PORT="30001"
|
||||||
|
|
||||||
|
# A short description for the world overview page
|
||||||
|
WORLD_SHORT_DESCRIPTION="A brief, catchy description of this world."
|
||||||
|
|
||||||
|
# A detailed HTML description for the world's detail page
|
||||||
|
WORLD_LONG_DESCRIPTION="<p>A longer description with <b>HTML</b> support.</p>"
|
||||||
|
```
|
||||||
|
|
||||||
## 📂 Directory Structure
|
## 📂 Directory Structure
|
||||||
|
|
||||||
The system now uses a modular structure to improve maintainability:
|
The system expects the following directory structure:
|
||||||
```md
|
```md
|
||||||
/opt/luweb/
|
/opt/luweb/
|
||||||
├── config.sh
|
├── config.sh
|
||||||
├── generate_map.sh
|
├── generate_map.sh
|
||||||
├── generate_site.sh
|
├── generate_site.sh
|
||||||
├── check_server_status.sh
|
|
||||||
├── check_dependencies.sh
|
|
||||||
├── minetestmapper (executable)
|
├── minetestmapper (executable)
|
||||||
├── site_generator/
|
├── site_generator/
|
||||||
│ ├── functions/
|
|
||||||
│ │ ├── 01_utils.sh
|
|
||||||
│ │ ├── 02_init.sh
|
|
||||||
│ │ ├── 03_html_helpers.sh
|
|
||||||
│ │ └── generators/
|
|
||||||
│ │ ├── css_generator.sh
|
|
||||||
│ │ ├── static_pages_generator.sh
|
|
||||||
│ │ ├── world_detail_generator.sh
|
|
||||||
│ │ └── world_overview_generator.sh
|
|
||||||
│ ├── templates/
|
│ ├── templates/
|
||||||
│ │ ├── world_detail_page.template
|
│ │ ├── world_detail_page.template
|
||||||
|
│ │ ├── world_detail_archive.template
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ └── examples/
|
│ └── examples/
|
||||||
│ └── web.conf.template
|
│ └── web.conf.template
|
||||||
├── web_content/
|
├── web_content/
|
||||||
│ ├── images/
|
│ ├── images/
|
||||||
|
│ │ └── players/
|
||||||
│ └── static/
|
│ └── static/
|
||||||
|
│ ├── startseite_content.html
|
||||||
|
│ └── ...
|
||||||
└── worldmaps_output/
|
└── worldmaps_output/
|
||||||
└── <world_name>/
|
└── <world_name>/
|
||||||
├── map.png
|
├── map.png
|
||||||
|
|
@ -104,44 +105,38 @@ The system now uses a modular structure to improve maintainability:
|
||||||
|
|
||||||
## 🚀 Usage
|
## 🚀 Usage
|
||||||
|
|
||||||
### 1. Map and Data Generation
|
The scripts are designed to be run from the command line, either manually or via automated jobs.
|
||||||
|
|
||||||
|
### 1. Map and Tile Generation
|
||||||
|
|
||||||
|
The `generate_map.sh` script creates the map, tiles, and archive images for a specific world. It must be called with the "world key" (the name of the world's directory) as an argument.
|
||||||
|
|
||||||
The `generate_map.sh` script creates the map, tiles, and metadata for a specific world.
|
|
||||||
```bash
|
```bash
|
||||||
# Generate assets for the world in the 'world' directory
|
# Generate map assets for the world located in the 'world' directory
|
||||||
./generate_map.sh world
|
./generate_map.sh world
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Website Generation
|
### 2. Website Generation
|
||||||
|
|
||||||
The `generate_site.sh` script builds the entire website for all worlds that have a `web.conf`.
|
The `generate_site.sh` script builds the entire website (overview, detail pages, etc.). It scans all configured world directories and creates a detail page for each one.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate the complete website
|
# Generate the complete website
|
||||||
./generate_site.sh
|
./generate_site.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Live Status Check
|
### 3. Automation (Cronjob)
|
||||||
|
|
||||||
The `check_server_status.sh` script checks the online status of all configured worlds. It should be run very frequently.
|
For fully automatic operation, setting up cronjobs is recommended.
|
||||||
```bash
|
|
||||||
# Check the server status
|
|
||||||
./check_server_status.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Automation (Cronjob)
|
|
||||||
|
|
||||||
Setting up cronjobs is recommended for fully automatic operation.
|
|
||||||
|
|
||||||
**Example for `crontab -e`:**
|
**Example for `crontab -e`:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate map assets for 'world' once per hour
|
# Generate map assets for the 'world' every 30 minutes
|
||||||
0 * * * * /opt/luweb/generate_map.sh world >> /var/log/luweb/cron.log 2>&1
|
*/30 * * * * /opt/luweb/generate_map.sh world >> /var/log/luweb/cron.log 2>&1
|
||||||
|
|
||||||
# Check the server status every 2 minutes
|
# Re-build the website every 12 hours
|
||||||
*/2 * * * * /opt/luweb/check_server_status.sh >> /var/log/luweb/cron.log 2>&1
|
* */12 * * * /opt/luweb/generate_site.sh >> /var/log/luweb/cron.log 2>&1
|
||||||
|
|
||||||
# Re-build the website (e.g., for new admins, changed descriptions) twice a day
|
|
||||||
0 */12 * * * /opt/luweb/generate_site.sh >> /var/log/luweb/cron.log 2>&1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
@ -167,4 +162,4 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
## 👤 Authors
|
## 👤 Authors
|
||||||
* **Rage87** (Main-Developer)
|
* **Rage87** (Main-Developer)
|
||||||
167
colors.txt
167
colors.txt
|
|
@ -1235,3 +1235,170 @@ xpanes:pane 249 249 249 64 16
|
||||||
xpanes:pane_flat 249 249 249 64 16
|
xpanes:pane_flat 249 249 249 64 16
|
||||||
xpanes:trapdoor_steel_bar 127 127 127 64 16
|
xpanes:trapdoor_steel_bar 127 127 127 64 16
|
||||||
xpanes:trapdoor_steel_bar_open 77 77 77 64 16
|
xpanes:trapdoor_steel_bar_open 77 77 77 64 16
|
||||||
|
|
||||||
|
# === NEUE EINTRÄGE VOM 21.06.2025 ===
|
||||||
|
|
||||||
|
# default
|
||||||
|
default:fence_aspen_wood_end 210 199 170 # Entspricht default:aspen_wood
|
||||||
|
|
||||||
|
# fake_fire
|
||||||
|
fake_fire:chimney_sandstone 198 193 143 # Entspricht default:sandstone
|
||||||
|
|
||||||
|
# farming
|
||||||
|
farming:barley_8 218 190 70 # Reifes Getreide, gold-gelb
|
||||||
|
farming:cabbage_5 135 240 80 # Reifender Kohl, hellgrün
|
||||||
|
farming:carrot_8 255 140 0 # Reife Karotte, kräftiges Orange
|
||||||
|
farming:cocoa_1 100 150 50 # Junge Kakaopflanze, grün
|
||||||
|
farming:corn_8 255 215 0 # Reifer Mais, goldgelb
|
||||||
|
farming:eggplant_4 75 0 130 # Reife Aubergine, tiefes Lila
|
||||||
|
farming:grapes_5 100 0 100 # Reifende Weintraube, lila
|
||||||
|
farming:grapes_6 100 0 100 # Reifende Weintraube, lila
|
||||||
|
farming:grapes_7 100 0 100 # Reifende Weintraube, lila
|
||||||
|
farming:grapes_8 100 0 100 # Reife Weintraube, lila
|
||||||
|
farming:hemp_8 60 100 50 # Reife Hanfpflanze, dunkles Grün
|
||||||
|
farming:onion_3 220 230 200 # Junge Zwiebel, hellgrün/weiß
|
||||||
|
farming:onion_4 245 245 230 # Reifende Zwiebel, fast weiß
|
||||||
|
farming:pea_1 100 160 80 # Erbsenpflanze, junges Grün
|
||||||
|
farming:pea_2 90 150 70 # Erbsenpflanze, mittleres Grün
|
||||||
|
farming:pea_3 80 140 60 # Erbsenpflanze, sattes Grün
|
||||||
|
farming:pepper_5 220 100 30 # Reifende Paprika, orange-rot
|
||||||
|
farming:pepper_7 200 30 30 # Reife Paprika, rot
|
||||||
|
farming:rhubarb_1 80 140 70 # Rhabarber, jung, grünlich
|
||||||
|
farming:rhubarb_2 150 80 80 # Rhabarber, reifend, rötlich
|
||||||
|
farming:rice_8 230 220 180 # Reifer Reis, helles Stroh
|
||||||
|
farming:rye_7 200 170 80 # Reifender Roggen
|
||||||
|
farming:rye_8 210 180 75 # Reifer Roggen, goldbraun
|
||||||
|
farming:sunflower_4 80 150 40 # Sonnenblume, mittlere Wachstumsphase
|
||||||
|
|
||||||
|
# irrigation
|
||||||
|
irrigation:water_barrel 131 102 57 # Holzfass, entspricht default:wood
|
||||||
|
|
||||||
|
# markers
|
||||||
|
markers:mark 128 128 128 # Generischer Marker, neutralgrau
|
||||||
|
|
||||||
|
# mesecons_lightstone
|
||||||
|
mesecons_lightstone:lightstone_blue_on 80 80 255 # Leuchtendes Blau
|
||||||
|
mesecons_lightstone:lightstone_green_off 0 80 0 # Dunkles Grün (aus)
|
||||||
|
mesecons_lightstone:lightstone_green_on 0 255 0 # Leuchtendes Grün
|
||||||
|
mesecons_lightstone:lightstone_red_off 80 0 0 # Dunkles Rot (aus)
|
||||||
|
mesecons_lightstone:lightstone_red_on 255 0 0 # Leuchtendes Rot
|
||||||
|
mesecons_lightstone:lightstone_violet_off 80 0 80 # Dunkles Violett (aus)
|
||||||
|
mesecons_lightstone:lightstone_violet_on 200 0 200 # Leuchtendes Violett
|
||||||
|
mesecons_lightstone:lightstone_yellow_on 255 255 0 # Leuchtendes Gelb
|
||||||
|
|
||||||
|
# mesecons_switch
|
||||||
|
mesecons_switch:mesecon_switch_off 110 110 110 # Schaltergehäuse, steingrau
|
||||||
|
|
||||||
|
# mesecons
|
||||||
|
mesecons:wire_00000000_off 139 50 50 # Mesecon-Draht im Aus-Zustand (dunkles Kupfer)
|
||||||
|
mesecons:wire_00010000_off 139 50 50
|
||||||
|
mesecons:wire_00010000_on 255 200 0 # Mesecon-Draht im An-Zustand (leuchtend)
|
||||||
|
mesecons:wire_00010001_off 139 50 50
|
||||||
|
mesecons:wire_00100000_off 139 50 50
|
||||||
|
mesecons:wire_00100000_on 255 200 0
|
||||||
|
mesecons:wire_00100010_off 139 50 50
|
||||||
|
mesecons:wire_00100010_on 255 200 0
|
||||||
|
mesecons:wire_00110000_off 139 50 50
|
||||||
|
mesecons:wire_00110000_on 255 200 0
|
||||||
|
mesecons:wire_00110001_off 139 50 50
|
||||||
|
mesecons:wire_00110001_on 255 200 0
|
||||||
|
mesecons:wire_00110011_off 139 50 50
|
||||||
|
mesecons:wire_00110011_on 255 200 0
|
||||||
|
mesecons:wire_01000000_on 255 200 0
|
||||||
|
mesecons:wire_01000100_on 255 200 0
|
||||||
|
mesecons:wire_01010000_off 139 50 50
|
||||||
|
mesecons:wire_01010000_on 255 200 0
|
||||||
|
mesecons:wire_01010001_on 255 200 0
|
||||||
|
mesecons:wire_01010100_on 255 200 0
|
||||||
|
mesecons:wire_01010101_off 139 50 50
|
||||||
|
mesecons:wire_01010101_on 255 200 0
|
||||||
|
mesecons:wire_01100000_off 139 50 50
|
||||||
|
mesecons:wire_01100000_on 255 200 0
|
||||||
|
mesecons:wire_01100100_off 139 50 50
|
||||||
|
mesecons:wire_01100100_on 255 200 0
|
||||||
|
mesecons:wire_01100110_off 139 50 50
|
||||||
|
mesecons:wire_01100110_on 255 200 0
|
||||||
|
mesecons:wire_01110000_off 139 50 50
|
||||||
|
mesecons:wire_01110000_on 255 200 0
|
||||||
|
mesecons:wire_01110001_off 139 50 50
|
||||||
|
mesecons:wire_01110011_on 255 200 0
|
||||||
|
mesecons:wire_01110100_off 139 50 50
|
||||||
|
mesecons:wire_01110100_on 255 200 0
|
||||||
|
mesecons:wire_01110101_off 139 50 50
|
||||||
|
mesecons:wire_01110101_on 255 200 0
|
||||||
|
mesecons:wire_01110110_off 139 50 50
|
||||||
|
mesecons:wire_01110111_off 139 50 50
|
||||||
|
mesecons:wire_01110111_on 255 200 0
|
||||||
|
mesecons:wire_10000000_off 139 50 50
|
||||||
|
mesecons:wire_10000000_on 255 200 0
|
||||||
|
mesecons:wire_10001000_on 255 200 0
|
||||||
|
mesecons:wire_10010000_off 139 50 50
|
||||||
|
mesecons:wire_10010000_on 255 200 0
|
||||||
|
mesecons:wire_10011000_on 255 200 0
|
||||||
|
mesecons:wire_10011001_off 139 50 50
|
||||||
|
mesecons:wire_10011001_on 255 200 0
|
||||||
|
mesecons:wire_10100000_on 255 200 0
|
||||||
|
mesecons:wire_10100010_off 139 50 50
|
||||||
|
mesecons:wire_10100010_on 255 200 0
|
||||||
|
mesecons:wire_10101000_on 255 200 0
|
||||||
|
mesecons:wire_10101010_off 139 50 50
|
||||||
|
mesecons:wire_10101010_on 255 200 0
|
||||||
|
mesecons:wire_10110010_on 255 200 0
|
||||||
|
mesecons:wire_10111000_on 255 200 0
|
||||||
|
mesecons:wire_10111001_off 139 50 50
|
||||||
|
mesecons:wire_10111011_off 139 50 50
|
||||||
|
mesecons:wire_10111011_on 255 200 0
|
||||||
|
mesecons:wire_11000000_off 139 50 50
|
||||||
|
mesecons:wire_11001100_off 139 50 50
|
||||||
|
mesecons:wire_11001100_on 255 200 0
|
||||||
|
mesecons:wire_11010000_on 255 200 0
|
||||||
|
mesecons:wire_11010100_off 139 50 50
|
||||||
|
mesecons:wire_11011000_off 139 50 50
|
||||||
|
mesecons:wire_11011001_off 139 50 50
|
||||||
|
mesecons:wire_11011001_on 255 200 0
|
||||||
|
mesecons:wire_11011100_off 139 50 50
|
||||||
|
mesecons:wire_11011100_on 255 200 0
|
||||||
|
mesecons:wire_11011101_on 255 200 0
|
||||||
|
mesecons:wire_11100000_off 139 50 50
|
||||||
|
mesecons:wire_11100000_on 255 200 0
|
||||||
|
mesecons:wire_11100010_off 139 50 50
|
||||||
|
mesecons:wire_11100110_on 255 200 0
|
||||||
|
mesecons:wire_11101000_on 255 200 0
|
||||||
|
mesecons:wire_11101010_off 139 50 50
|
||||||
|
mesecons:wire_11101010_on 255 200 0
|
||||||
|
mesecons:wire_11101100_on 255 200 0
|
||||||
|
mesecons:wire_11101110_on 255 200 0
|
||||||
|
mesecons:wire_11110000_off 139 50 50
|
||||||
|
mesecons:wire_11110000_on 255 200 0
|
||||||
|
mesecons:wire_11110010_off 139 50 50
|
||||||
|
mesecons:wire_11110011_on 255 200 0
|
||||||
|
mesecons:wire_11110100_off 139 50 50
|
||||||
|
mesecons:wire_11110101_off 139 50 50
|
||||||
|
mesecons:wire_11110101_on 255 200 0
|
||||||
|
mesecons:wire_11110110_on 255 200 0
|
||||||
|
mesecons:wire_11110111_on 255 200 0
|
||||||
|
mesecons:wire_11111000_off 139 50 50
|
||||||
|
mesecons:wire_11111000_on 255 200 0
|
||||||
|
mesecons:wire_11111001_off 139 50 50
|
||||||
|
mesecons:wire_11111011_on 255 200 0
|
||||||
|
mesecons:wire_11111100_off 139 50 50
|
||||||
|
mesecons:wire_11111100_on 255 200 0
|
||||||
|
mesecons:wire_11111101_on 255 200 0
|
||||||
|
mesecons:wire_11111110_on 255 200 0
|
||||||
|
mesecons:wire_11111111_off 139 50 50
|
||||||
|
mesecons:wire_11111111_on 255 200 0
|
||||||
|
|
||||||
|
# moreblocks
|
||||||
|
moreblocks:slab_coal_stone_quarter 60 60 60 # Entspricht moreblocks:coal_stone_bricks
|
||||||
|
moreblocks:slab_stone_block 100 97 96 # Entspricht default:stone_block
|
||||||
|
|
||||||
|
# morelights_vintage
|
||||||
|
morelights_vintage:lantern_f 70 70 70 # Laternengehäuse, dunkles Metall
|
||||||
|
|
||||||
|
# mystreets
|
||||||
|
mystreets:ramp_asphalt_center_solid_long 55 55 60 # Entspricht mystreets:asphalt
|
||||||
|
mystreets:ramp_asphalt_long 55 55 60 # Entspricht mystreets:asphalt
|
||||||
|
mystreets:ramp_asphalt_side_solid_left_long 55 55 60 # Entspricht mystreets:asphalt
|
||||||
|
mystreets:ramp_asphalt_side_solid_right_long 55 55 60 # Entspricht mystreets:asphalt
|
||||||
|
mystreets:ramp_sidewalk_long 150 150 150 # Entspricht mystreets:sidewalk
|
||||||
|
mystreets:stop_sign 200 0 0 # Stoppschild-Rot
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ MINETESTMAPPER_WORLD_DATA_BASE_PATH="/opt/luanti/data/worlds/"
|
||||||
DEFAULT_MM_OPT_ZOOM_LEVEL="2"; DEFAULT_MM_OPT_MIN_Y="-25"
|
DEFAULT_MM_OPT_ZOOM_LEVEL="2"; DEFAULT_MM_OPT_MIN_Y="-25"
|
||||||
DEFAULT_MM_OPT_ORIGINCOLOR="#ff0000"; DEFAULT_MM_OPT_PLAYERCOLOR="#ff0000"
|
DEFAULT_MM_OPT_ORIGINCOLOR="#ff0000"; DEFAULT_MM_OPT_PLAYERCOLOR="#ff0000"
|
||||||
DEFAULT_MM_OPT_SCALECOLOR="#ff0000"; DEFAULT_MM_OPT_BGCOLOR="#dddddd"
|
DEFAULT_MM_OPT_SCALECOLOR="#ff0000"; DEFAULT_MM_OPT_BGCOLOR="#dddddd"
|
||||||
DEFAULT_MM_CFG_DRAWALPHA="true"; DEFAULT_MM_CFG_DRAWORIGIN="true"
|
DEFAULT_MM_CFG_DRAWALPHA="true"; DEFAULT_MM_CFG_DRAWORIGIN="false"
|
||||||
DEFAULT_MM_CFG_DRAWPLAYERS="true"; DEFAULT_MM_CFG_DRAWSCALE="true"
|
DEFAULT_MM_CFG_DRAWPLAYERS="false"; DEFAULT_MM_CFG_DRAWSCALE="false"
|
||||||
|
|
||||||
# --- Dateinamen und relative Pfade (innerhalb BASE_SCRIPT_DIR) ---
|
# --- Dateinamen und relative Pfade (innerhalb BASE_SCRIPT_DIR) ---
|
||||||
RAW_MAP_BASE_SUBDIR="worldmaps_output"
|
RAW_MAP_BASE_SUBDIR="worldmaps_output"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ generate_world_detail_page() {
|
||||||
local tiles_subdir_name; tiles_subdir_name=$(get_config_value_from_file "$web_conf_file" "TILES_SUBDIR_NAME" "$DEFAULT_TILES_SUBDIR_NAME")
|
local tiles_subdir_name; tiles_subdir_name=$(get_config_value_from_file "$web_conf_file" "TILES_SUBDIR_NAME" "$DEFAULT_TILES_SUBDIR_NAME")
|
||||||
local web_tiles_rel_path="${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/${tiles_subdir_name}"
|
local web_tiles_rel_path="${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/${tiles_subdir_name}"
|
||||||
|
|
||||||
# HINZUGEFÜGT: Hintergrundfarbe auslesen
|
|
||||||
local map_background_color; map_background_color=$(get_config_value_from_file "$web_conf_file" "MM_OPT_BGCOLOR" "$DEFAULT_MM_OPT_BGCOLOR")
|
local map_background_color; map_background_color=$(get_config_value_from_file "$web_conf_file" "MM_OPT_BGCOLOR" "$DEFAULT_MM_OPT_BGCOLOR")
|
||||||
|
|
||||||
local WORLD_DISPLAY_NAME_PAGE; WORLD_DISPLAY_NAME_PAGE=$(get_config_value_from_file "$web_conf_file" "WORLD_DISPLAY_NAME")
|
local WORLD_DISPLAY_NAME_PAGE; WORLD_DISPLAY_NAME_PAGE=$(get_config_value_from_file "$web_conf_file" "WORLD_DISPLAY_NAME")
|
||||||
|
|
@ -173,7 +172,8 @@ generate_world_detail_page() {
|
||||||
"RESOLUTIONS_JS_ARRAY" "$resolutions_array" \
|
"RESOLUTIONS_JS_ARRAY" "$resolutions_array" \
|
||||||
"web_tiles_rel_path" "$web_tiles_rel_path" \
|
"web_tiles_rel_path" "$web_tiles_rel_path" \
|
||||||
"web_map_info_rel_path" "${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/map_info.txt" \
|
"web_map_info_rel_path" "${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/map_info.txt" \
|
||||||
"map_background_color" "$map_background_color"
|
"map_background_color" "$map_background_color" \
|
||||||
|
"DEFAULT_PLAYER_SKIN_URL" "$DEFAULT_PLAYER_SKIN_URL"
|
||||||
local WORLD_RADAR_HTML; WORLD_RADAR_HTML=$(<"$temp_radar_file")
|
local WORLD_RADAR_HTML; WORLD_RADAR_HTML=$(<"$temp_radar_file")
|
||||||
rm "$temp_radar_file"
|
rm "$temp_radar_file"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,9 @@ generate_worlds_overview() {
|
||||||
local detail_page_filename="world_${current_world_key}.html"
|
local detail_page_filename="world_${current_world_key}.html"
|
||||||
local preview_img_rel_path="${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/${current_map_png_filename_for_preview_ov}"
|
local preview_img_rel_path="${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/${current_map_png_filename_for_preview_ov}"
|
||||||
local preview_img_abs_path="${WEB_ROOT_PATH}/${preview_img_rel_path}"
|
local preview_img_abs_path="${WEB_ROOT_PATH}/${preview_img_rel_path}"
|
||||||
local online_status_text="offline"; local online_status_class="offline"
|
|
||||||
local status_file_for_overview="${WEB_ROOT_PATH}/${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/online_status.txt"
|
# KORREKTUR: Logik zur Status-Prüfung entfernt. Stattdessen wird nur der Pfad zur Status-Datei erstellt.
|
||||||
if [ -f "$status_file_for_overview" ]; then
|
local status_url_path="/${WEB_MAPS_BASE_SUBDIR}/${current_world_key}/online_status.txt"
|
||||||
local status_line_overview
|
|
||||||
status_line_overview=$(cat "$status_file_for_overview")
|
|
||||||
if [[ "$status_line_overview" == "online"* ]]; then online_status_text="online"; online_status_class="online"; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local preview_img_html
|
local preview_img_html
|
||||||
if [ -f "$preview_img_abs_path" ]; then
|
if [ -f "$preview_img_abs_path" ]; then
|
||||||
|
|
@ -70,8 +66,7 @@ generate_worlds_overview() {
|
||||||
"detail_page_filename" "$detail_page_filename" \
|
"detail_page_filename" "$detail_page_filename" \
|
||||||
"preview_img_html" "$preview_img_html" \
|
"preview_img_html" "$preview_img_html" \
|
||||||
"world_display_name_ov" "$world_display_name_ov" \
|
"world_display_name_ov" "$world_display_name_ov" \
|
||||||
"online_status_class" "$online_status_class" \
|
"status_url" "$status_url_path" \
|
||||||
"online_status_text" "$online_status_text" \
|
|
||||||
"admin_name_ov" "$admin_name_ov" \
|
"admin_name_ov" "$admin_name_ov" \
|
||||||
"world_short_desc_ov" "$world_short_desc_ov"
|
"world_short_desc_ov" "$world_short_desc_ov"
|
||||||
cat "$temp_overview_entry_file" >> "$overview_file"
|
cat "$temp_overview_entry_file" >> "$overview_file"
|
||||||
|
|
|
||||||
|
|
@ -94,13 +94,14 @@ a.world-preview:hover {
|
||||||
.world-preview-text p { color: #ccc !important; margin-top: 0; text-decoration: none !important; }
|
.world-preview-text p { color: #ccc !important; margin-top: 0; text-decoration: none !important; }
|
||||||
.world-preview-footer { font-size: 0.85em; color: #aaa; border-top: 1px solid #666; margin-top: 10px; padding-top: 10px; }
|
.world-preview-footer { font-size: 0.85em; color: #aaa; border-top: 1px solid #666; margin-top: 10px; padding-top: 10px; }
|
||||||
.online-status-badge { font-size: 0.8em; padding: 3px 8px; border-radius: 10px; color: white !important; margin-left: 10px; }
|
.online-status-badge { font-size: 0.8em; padding: 3px 8px; border-radius: 10px; color: white !important; margin-left: 10px; }
|
||||||
.online-status-badge.online { background-color: #3E8E41; } .online-status-badge.offline { background-color: #C0392B; }
|
.online-status-badge.online { background-color: #3E8E41; }
|
||||||
|
.online-status-badge.offline { background-color: #C0392B; }
|
||||||
|
.online-status-badge.unknown { background-color: #808080; } /* HINZUGEFÜGT */
|
||||||
|
|
||||||
/* Welt-Detailseite */
|
/* Welt-Detailseite */
|
||||||
.page-nav-buttons { text-align: right; margin-top: 10px; margin-bottom: 15px; }
|
.page-nav-buttons { text-align: right; margin-top: 10px; margin-bottom: 15px; }
|
||||||
.page-nav-buttons .button { background-color: #555; color: #FFA500; border: 1px solid #666; padding: 5px 10px; margin-left: 5px; margin-bottom: 5px; border-radius: 3px; text-decoration: none; font-size: 0.9em; display: inline-block; }
|
.page-nav-buttons .button { background-color: #555; color: #FFA500; border: 1px solid #666; padding: 5px 10px; margin-left: 5px; margin-bottom: 5px; border-radius: 3px; text-decoration: none; font-size: 0.9em; display: inline-block; }
|
||||||
.page-nav-buttons .button:hover { background-color: #666; color: #FFC500; }
|
.page-nav-buttons .button:hover { background-color: #666; color: #FFC500; }
|
||||||
/* KORREKTUR: Klasse umbenannt von .leaflet-map zu .map-view-container */
|
|
||||||
.map-view-container {
|
.map-view-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
|
|
@ -467,10 +468,9 @@ a.read-more-link {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
.player-box { width: calc((100% - 15px) / 2); } /* 2 Spalten auf Tablets */
|
.player-box { width: calc((100% - 15px) / 2); }
|
||||||
.page-nav-buttons .button { margin-top: 5px;}
|
.page-nav-buttons .button { margin-top: 5px;}
|
||||||
|
|
||||||
/* Responsive Stile für Welt-Vorschau */
|
|
||||||
.world-preview .world-preview-content {
|
.world-preview .world-preview-content {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
@ -481,15 +481,15 @@ a.read-more-link {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
.map-view-container { /* KORREKTUR: Klasse umbenannt */
|
.map-view-container {
|
||||||
height: auto;
|
height: auto;
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 4 / 3;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.player-box { width: 100%; } /* 1 Spalte auf Handys */
|
.player-box { width: 100%; }
|
||||||
.admin-box { flex-basis: 100%; } /* 1 Spalte auf Handys */
|
.admin-box { flex-basis: 100%; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stile für OpenLayers Popups (Leaflet-Look-and-Feel) */
|
/* Stile für OpenLayers Popups (Leaflet-Look-and-Feel) */
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,41 @@
|
||||||
|
|
||||||
<a href="#" id="scrollToTopBtn" class="scroll-to-top-btn" title="Nach oben scrollen">▲</a>
|
<a href="#" id="scrollToTopBtn" class="scroll-to-top-btn" title="Nach oben scrollen">▲</a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const statusBadges = document.querySelectorAll('.online-status-badge[data-status-url]');
|
||||||
|
|
||||||
|
if (statusBadges.length > 0) {
|
||||||
|
statusBadges.forEach(badge => {
|
||||||
|
const url = badge.dataset.statusUrl + '?v=' + new Date().getTime();
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok for ' + url);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
badge.classList.remove('unknown');
|
||||||
|
if (text.trim().startsWith('online')) {
|
||||||
|
badge.textContent = 'online';
|
||||||
|
badge.classList.add('online');
|
||||||
|
} else {
|
||||||
|
badge.textContent = 'offline';
|
||||||
|
badge.classList.add('offline');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Abrufen des Welt-Status:', error);
|
||||||
|
badge.textContent = 'n.a.'; // Nicht verfügbar
|
||||||
|
badge.classList.remove('unknown');
|
||||||
|
badge.classList.add('offline'); // Bei Fehler als offline markieren
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ function fetchWorldStatus_%%current_world_key%%() {
|
||||||
|
|
||||||
function fetchDataForElement(elementId, filePath, isRawText, prefixText, suffixText, isJsonPlayerList) {
|
function fetchDataForElement(elementId, filePath, isRawText, prefixText, suffixText, isJsonPlayerList) {
|
||||||
const el = document.getElementById(elementId);
|
const el = document.getElementById(elementId);
|
||||||
if (!el && !isJsonPlayerList) return;
|
if (!el && !isJsonPlayerList) return;
|
||||||
|
|
||||||
fetch('/' + filePath + '?v=%%CACHE_BUSTER%%&t=' + new Date().getTime())
|
fetch('/' + filePath + '?v=%%CACHE_BUSTER%%&t=' + new Date().getTime())
|
||||||
.then(r => { if (!r.ok) throw new Error('Datei ' + filePath + ' nicht erreichbar (' + r.status + ')'); return r.text(); })
|
.then(r => { if (!r.ok) throw new Error('Datei ' + filePath + ' nicht erreichbar (' + r.status + ')'); return r.text(); })
|
||||||
.then(t => {
|
.then(t => {
|
||||||
|
|
@ -111,9 +112,10 @@ function fetchDataForElement(elementId, filePath, isRawText, prefixText, suffixT
|
||||||
if (t.trim() === "") {
|
if (t.trim() === "") {
|
||||||
content = (elementId.startsWith("map-last-update") ? "<em>unbekannt</em>" : "<em>Keine Daten verfügbar.</em>");
|
content = (elementId.startsWith("map-last-update") ? "<em>unbekannt</em>" : "<em>Keine Daten verfügbar.</em>");
|
||||||
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
||||||
window.playerListLogic_%%current_world_key%%.masterPlayerData = {};
|
const logic = window.playerListLogic_%%current_world_key%%;
|
||||||
window.playerListLogic_%%current_world_key%%.updatePlayerMarkers({});
|
logic.masterPlayerData = {};
|
||||||
window.playerListLogic_%%current_world_key%%.applyPlayerFiltersAndRender();
|
logic.updatePlayerMarkers({});
|
||||||
|
logic.applyPlayerFiltersAndRender();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
||||||
|
|
@ -125,7 +127,11 @@ function fetchDataForElement(elementId, filePath, isRawText, prefixText, suffixT
|
||||||
logic.applyPlayerFiltersAndRender();
|
logic.applyPlayerFiltersAndRender();
|
||||||
} catch (e) { console.error("Fehler beim Parsen der Spielerdaten:", e); }
|
} catch (e) { console.error("Fehler beim Parsen der Spielerdaten:", e); }
|
||||||
return;
|
return;
|
||||||
} else { content = isRawText ? t.replace(/\n/g, '<br>') : t; }
|
} else {
|
||||||
|
const dv = document.createElement('div');
|
||||||
|
dv.textContent = t;
|
||||||
|
content = dv.innerHTML.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(el) { el.innerHTML = (prefixText || '') + content + (suffixText || ''); }
|
if(el) { el.innerHTML = (prefixText || '') + content + (suffixText || ''); }
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
|
@ -168,6 +174,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Periodische Updates
|
// Periodische Updates
|
||||||
setInterval(fetchWorldStatus_%%current_world_key%%, 60000);
|
setInterval(fetchWorldStatus_%%current_world_key%%, 60000);
|
||||||
setInterval(() => fetchDataForElement('map-last-update-text-%%current_world_key%%', '%%web_last_update_rel_path%%', true, '', ''), 60000);
|
setInterval(() => fetchDataForElement('map-last-update-text-%%current_world_key%%', '%%web_last_update_rel_path%%', true, '', ''), 60000);
|
||||||
setInterval(() => fetchDataForElement('player-info-%%current_world_key%%', '%%web_players_txt_rel_path%%', false, '', '', true), 70000);
|
// KORREKTUR: Intervall von 70000ms auf 30000ms (30 Sekunden) geändert
|
||||||
|
setInterval(() => fetchDataForElement('player-info-%%current_world_key%%', '%%web_players_txt_rel_path%%', false, '', '', true), 30000);
|
||||||
setInterval(() => fetchDataForElement('weather-info-%%current_world_key%%', '%%web_weather_txt_rel_path%%', true, '<p><strong>Wetter:</strong> ', '</p>'), 300000);
|
setInterval(() => fetchDataForElement('weather-info-%%current_world_key%%', '%%web_weather_txt_rel_path%%', true, '<p><strong>Wetter:</strong> ', '</p>'), 300000);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,6 @@ if (!window.playerListLogic_%%current_world_key%%) {
|
||||||
|
|
||||||
source.clear(); // Entferne alle alten Marker
|
source.clear(); // Entferne alle alten Marker
|
||||||
|
|
||||||
// KORREKTUR: Marker-Stile auf Kreise umgestellt
|
|
||||||
const adminIconStyle = new ol.style.Style({
|
|
||||||
image: new ol.style.Circle({
|
|
||||||
radius: 7,
|
|
||||||
fill: new ol.style.Fill({ color: 'rgba(255, 255, 0, 0.9)' }), // Gelb
|
|
||||||
stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const playerIconStyle = new ol.style.Style({
|
|
||||||
image: new ol.style.Circle({
|
|
||||||
radius: 6,
|
|
||||||
fill: new ol.style.Fill({ color: 'rgba(255, 0, 0, 0.9)' }), // Rot
|
|
||||||
stroke: new ol.style.Stroke({ color: '#fff', width: 1 })
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const features = [];
|
const features = [];
|
||||||
const now_epoch = Math.floor(Date.now() / 1000);
|
const now_epoch = Math.floor(Date.now() / 1000);
|
||||||
const twentyFourHoursInSeconds = 24 * 60 * 60;
|
const twentyFourHoursInSeconds = 24 * 60 * 60;
|
||||||
|
|
@ -72,13 +56,35 @@ if (!window.playerListLogic_%%current_world_key%%) {
|
||||||
lastLoginFormatted = formatTimestampForDisplay(p.last_login);
|
lastLoginFormatted = formatTimestampForDisplay(p.last_login);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HINZUGEFÜGT: Erstelle den kombinierten Stil aus Kreis und Text
|
||||||
|
const circleStyle = new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: isAdmin ? 7 : 6,
|
||||||
|
fill: new ol.style.Fill({ color: isAdmin ? 'rgba(255, 190, 0, 0.9)' : 'rgba(255, 40, 40, 0.9)' }),
|
||||||
|
stroke: new ol.style.Stroke({ color: '#ffffff', width: isAdmin ? 2 : 1 })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const textStyle = new ol.style.Style({
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: p.name,
|
||||||
|
font: 'bold 11px "Helvetica Neue", Arial, sans-serif',
|
||||||
|
fill: new ol.style.Fill({ color: '#ffffff' }),
|
||||||
|
stroke: new ol.style.Stroke({ color: 'rgba(0, 0, 0, 0.9)', width: 3 }),
|
||||||
|
offsetY: -20, // Positioniert den Text über dem Kreismittelpunkt
|
||||||
|
textAlign: 'center'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const feature = new ol.Feature({
|
const feature = new ol.Feature({
|
||||||
geometry: new ol.geom.Point(coords),
|
geometry: new ol.geom.Point(coords),
|
||||||
playerData: p,
|
playerData: p,
|
||||||
statusDotClass: statusDotClass,
|
statusDotClass: statusDotClass,
|
||||||
lastLoginFormatted: lastLoginFormatted
|
lastLoginFormatted: lastLoginFormatted
|
||||||
});
|
});
|
||||||
feature.set('style', isAdmin ? adminIconStyle : playerIconStyle);
|
|
||||||
|
// Setze den kombinierten Stil (Kreis und Text) für das Feature
|
||||||
|
feature.set('style', [circleStyle, textStyle]);
|
||||||
features.push(feature);
|
features.push(feature);
|
||||||
}
|
}
|
||||||
source.addFeatures(features);
|
source.addFeatures(features);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div id="live-map-container-%%current_world_key%%">
|
<div id="live-map-container-%%current_world_key%%">
|
||||||
<div id="ol-map-container-%%current_world_key%%" class="map-view-container"></div>
|
<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">
|
<div id="popup-%%current_world_key%%" class="ol-popup">
|
||||||
<a href="#" id="popup-closer-%%current_world_key%%" class="ol-popup-closer"></a>
|
<a href="#" id="popup-closer-%%current_world_key%%" class="ol-popup-closer"></a>
|
||||||
|
|
@ -78,7 +78,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
mapContainer.innerHTML = '';
|
mapContainer.innerHTML = '';
|
||||||
|
|
||||||
const extent = [0, -mapData.mapHeight, mapData.mapWidth, 0];
|
const extent = [0, -mapData.mapHeight, mapData.mapWidth, 0];
|
||||||
const resolutions = %%RESOLUTIONS_JS_ARRAY%%;
|
|
||||||
|
// KORREKTUR: Wir trennen die echten von den virtuellen Auflösungen
|
||||||
|
const sourceResolutions = %%RESOLUTIONS_JS_ARRAY%%;
|
||||||
|
|
||||||
|
const viewResolutions = [...sourceResolutions]; // Kopie für die Ansicht
|
||||||
|
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({
|
const projection = new ol.proj.Projection({
|
||||||
code: 'pixel-map-%%current_world_key%%',
|
code: 'pixel-map-%%current_world_key%%',
|
||||||
|
|
@ -88,7 +98,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
const tileGrid = new ol.tilegrid.TileGrid({
|
const tileGrid = new ol.tilegrid.TileGrid({
|
||||||
origin: ol.extent.getTopLeft(extent),
|
origin: ol.extent.getTopLeft(extent),
|
||||||
resolutions: resolutions,
|
resolutions: sourceResolutions, // Das TileGrid kennt NUR die echten Auflösungen
|
||||||
});
|
});
|
||||||
|
|
||||||
const tileSource = new ol.source.TileImage({
|
const tileSource = new ol.source.TileImage({
|
||||||
|
|
@ -96,6 +106,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
tileGrid: tileGrid,
|
tileGrid: tileGrid,
|
||||||
tileUrlFunction: function(tileCoord) {
|
tileUrlFunction: function(tileCoord) {
|
||||||
if (!tileCoord) return '';
|
if (!tileCoord) return '';
|
||||||
|
// OpenLayers wählt jetzt selbst die beste verfügbare Kachel aus.
|
||||||
|
// Die URL-Funktion kann wieder einfach sein.
|
||||||
return ('/%%web_tiles_rel_path%%/' + tileCoord[0] + '/' + tileCoord[1] + '/' + tileCoord[2] + '.png?v=%%CACHE_BUSTER%%');
|
return ('/%%web_tiles_rel_path%%/' + tileCoord[0] + '/' + tileCoord[1] + '/' + tileCoord[2] + '.png?v=%%CACHE_BUSTER%%');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -107,8 +119,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const view = new ol.View({
|
const view = new ol.View({
|
||||||
projection: projection,
|
projection: projection,
|
||||||
center: ol.extent.getCenter(extent),
|
center: ol.extent.getCenter(extent),
|
||||||
resolutions: resolutions,
|
resolutions: viewResolutions, // Die View kennt ALLE Auflösungen (echte + digitale)
|
||||||
maxZoom: resolutions.length - 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const popupContainer = document.getElementById('popup-%%current_world_key%%');
|
const popupContainer = document.getElementById('popup-%%current_world_key%%');
|
||||||
|
|
@ -129,10 +140,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const map = window.olMap_%%current_world_key%%;
|
const map = window.olMap_%%current_world_key%%;
|
||||||
|
|
||||||
// KORREKTUR: Hintergrundfarbe wird hier per JavaScript gesetzt
|
|
||||||
map.getViewport().style.backgroundColor = '%%map_background_color%%';
|
|
||||||
|
|
||||||
map.getView().fit(extent, { size: map.getSize() });
|
map.getView().fit(extent, { size: map.getSize() });
|
||||||
|
|
||||||
map.on('click', function(evt) {
|
map.on('click', function(evt) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<a href='%%detail_page_filename%%' class='world-preview'>
|
<a href='%%detail_page_filename%%' class='world-preview'>
|
||||||
<div class='world-preview-header'>
|
<div class='world-preview-header'>
|
||||||
<h3>%%world_display_name_ov%%</h3>
|
<h3>%%world_display_name_ov%%</h3>
|
||||||
<span class='online-status-badge %%online_status_class%%'>%%online_status_text%%</span>
|
<span class='online-status-badge unknown' data-status-url="%%status_url%%">lädt...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class='world-preview-content'>
|
<div class='world-preview-content'>
|
||||||
%%preview_img_html%%
|
%%preview_img_html%%
|
||||||
|
|
|
||||||
127
sync_players.sh
Executable file
127
sync_players.sh
Executable file
|
|
@ -0,0 +1,127 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Lade globale Konfiguration
|
||||||
|
CONFIG_FILE_PATH="$(dirname "$0")/config.sh"
|
||||||
|
if [ -f "$CONFIG_FILE_PATH" ]; then
|
||||||
|
source "$CONFIG_FILE_PATH"
|
||||||
|
else
|
||||||
|
echo "FEHLER: Globale config.sh nicht unter ${CONFIG_FILE_PATH} gefunden!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfe Abhängigkeiten, bevor irgendetwas anderes passiert
|
||||||
|
/opt/luweb/check_dependencies.sh || exit 1
|
||||||
|
|
||||||
|
# Welt-Schlüssel (Verzeichnisname) aus Argument oder Standardwert
|
||||||
|
WORLD_KEY="${1:-$DEFAULT_WORLD_NAME_KEY}"
|
||||||
|
CURRENT_MINETEST_WORLD_DATA_PATH="${MINETESTMAPPER_WORLD_DATA_BASE_PATH}${WORLD_KEY}/"
|
||||||
|
|
||||||
|
# === Abgeleitete Variablen ===
|
||||||
|
SCRIPT_BASENAME=$(basename "$0" .sh)
|
||||||
|
LOG_FILE="${LOG_DIR_BASE}/${SCRIPT_BASENAME}_${WORLD_KEY}.log"
|
||||||
|
LOCK_FILE="${LOCK_FILE_BASE_DIR}/${SCRIPT_BASENAME}_${WORLD_KEY}.lock"
|
||||||
|
|
||||||
|
# Pfade zu den Datenbanken und der Zieldatei
|
||||||
|
PLAYERS_DB_PATH="${CURRENT_MINETEST_WORLD_DATA_PATH}players.sqlite"
|
||||||
|
AUTH_DB_PATH="${CURRENT_MINETEST_WORLD_DATA_PATH}auth.sqlite"
|
||||||
|
PLAYERS_JSON_TARGET_DIR="${WEB_ROOT_PATH}/${WEB_MAPS_BASE_SUBDIR}/${WORLD_KEY}"
|
||||||
|
PLAYERS_JSON_FILE_PATH="${PLAYERS_JSON_TARGET_DIR}/players.txt"
|
||||||
|
PLAYERS_JSON_TMP_FILE_PATH="${PLAYERS_JSON_FILE_PATH}.tmp"
|
||||||
|
|
||||||
|
# SQLite-Befehl
|
||||||
|
SQLITE_CMD="sqlite3 -readonly"
|
||||||
|
|
||||||
|
# === Logging Funktion ===
|
||||||
|
log_message() {
|
||||||
|
local message_to_log; message_to_log="$(date '+%Y-%m-%d %H:%M:%S') - [${WORLD_KEY}] - $1"
|
||||||
|
echo "${message_to_log}" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# === Hauptlogik ===
|
||||||
|
exec 200>"$LOCK_FILE"
|
||||||
|
flock -n 200 || { log_message "Script ${SCRIPT_BASENAME}.sh ist bereits aktiv (Lock: ${LOCK_FILE}). Beende."; exit 1; }
|
||||||
|
trap 'rm -f "$LOCK_FILE"; log_message "Skript beendet."' EXIT
|
||||||
|
|
||||||
|
mkdir -p "$LOG_DIR_BASE"
|
||||||
|
log_message "Script gestartet für Welt: ${WORLD_KEY}"
|
||||||
|
|
||||||
|
# Prüfen, ob die Datenbanken existieren
|
||||||
|
if [ ! -f "$PLAYERS_DB_PATH" ] || [ ! -f "$AUTH_DB_PATH" ]; then
|
||||||
|
log_message "FEHLER: players.sqlite oder auth.sqlite nicht gefunden. Pfade prüfen:"
|
||||||
|
log_message "-> ${PLAYERS_DB_PATH}"
|
||||||
|
log_message "-> ${AUTH_DB_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Beginne mit dem Schreiben der temporären JSON-Datei
|
||||||
|
echo "{" > "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
|
||||||
|
first_entry=true
|
||||||
|
|
||||||
|
# Lese alle Spieler aus der auth-Datenbank (ID, Name, Letzter Login)
|
||||||
|
player_list_query="SELECT id, name, last_login FROM auth;"
|
||||||
|
$SQLITE_CMD "$AUTH_DB_PATH" "$player_list_query" | while IFS='|' read -r player_id name last_login; do
|
||||||
|
|
||||||
|
log_message "Verarbeite Spieler: ${name} (ID: ${player_id})"
|
||||||
|
|
||||||
|
player_data_query="SELECT posX, posY, posZ, hp, breath, pitch, yaw, creation_date FROM player WHERE name = '${name}';"
|
||||||
|
player_data=$($SQLITE_CMD "$PLAYERS_DB_PATH" "$player_data_query")
|
||||||
|
|
||||||
|
if [ -z "$player_data" ]; then
|
||||||
|
log_message "WARNUNG: Spieler '${name}' hat keine Positionsdaten in players.sqlite. Wird übersprungen."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='|' read -r posX posY posZ hp breath pitch yaw creation_date <<< "$player_data"
|
||||||
|
|
||||||
|
posX_rounded=$(printf "%.0f" "$(echo "$posX / 10" | bc)")
|
||||||
|
posY_rounded=$(printf "%.0f" "$(echo "$posY / 10" | bc)")
|
||||||
|
posZ_rounded=$(printf "%.0f" "$(echo "$posZ / 10" | bc)")
|
||||||
|
pitch_rounded=$(printf "%.0f" "$pitch")
|
||||||
|
yaw_rounded=$(printf "%.0f" "$yaw")
|
||||||
|
|
||||||
|
stamina_query="SELECT value FROM player_metadata WHERE player = '${name}' AND metadata = 'hunger_ng:hunger_value';"
|
||||||
|
stamina_val=$($SQLITE_CMD "$PLAYERS_DB_PATH" "$stamina_query")
|
||||||
|
stamina_rounded=$(printf "%.0f" "${stamina_val:-0}")
|
||||||
|
|
||||||
|
privs_query="SELECT privilege FROM user_privileges WHERE id = ${player_id};"
|
||||||
|
privs_string=$($SQLITE_CMD "$AUTH_DB_PATH" "$privs_query" | tr '\n' ',' | sed 's/,$//')
|
||||||
|
|
||||||
|
# Komma vor dem Eintrag hinzufügen, außer beim ersten
|
||||||
|
if [ "$first_entry" = true ]; then
|
||||||
|
first_entry=false
|
||||||
|
else
|
||||||
|
echo "," >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JSON-Objekt für den Spieler erstellen und in die Datei schreiben
|
||||||
|
printf ' "%s": {\n' "$player_id" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "name": "%s",\n' "$name" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "pitch": %d,\n' "$pitch_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "yaw": %d,\n' "$yaw_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "posX": %d,\n' "$posX_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "posY": %d,\n' "$posY_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "posZ": %d,\n' "$posZ_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "hp": %d,\n' "$hp" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "breath": %d,\n' "$breath" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "stamina": %d,\n' "$stamina_rounded" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "creation_date": "%s",\n' "$(echo "$creation_date" | sed 's/ /T/')Z" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' "last_login": %s,\n' "$last_login" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
# KORREKTUR: Kein Komma nach dem letzten Element
|
||||||
|
printf ' "privilege": "%s"\n' "$privs_string" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
printf ' }' >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# JSON-Datei abschließen
|
||||||
|
# KORREKTUR: Die überflüssige schließende Klammer wird hier entfernt
|
||||||
|
echo "" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
echo "}" >> "$PLAYERS_JSON_TMP_FILE_PATH"
|
||||||
|
|
||||||
|
# Temporäre Datei an den Zielort verschieben
|
||||||
|
mkdir -p "$PLAYERS_JSON_TARGET_DIR"
|
||||||
|
mv "$PLAYERS_JSON_TMP_FILE_PATH" "$PLAYERS_JSON_FILE_PATH"
|
||||||
|
|
||||||
|
log_message "players.txt erfolgreich synchronisiert nach ${PLAYERS_JSON_FILE_PATH}"
|
||||||
|
|
||||||
|
exit 0
|
||||||
BIN
web_content/images/players/Rage87.png
Executable file
BIN
web_content/images/players/Rage87.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 860 B |
Loading…
Add table
Add a link
Reference in a new issue