Initialer Load vom alten Projekt.
This commit is contained in:
Daniel Nagel 2025-08-30 22:02:13 +02:00
commit c0beeeff92
17 changed files with 618 additions and 0 deletions

BIN
hexgrid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

108
index.html Normal file
View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>MapViewer Spielleiter</title>
<link rel="stylesheet" href="style.css" />
<style>
body.paused {
outline: 8px solid rgba(0, 150, 255, 0.75);
outline-offset: -8px;
}
</style>
</head>
<!-- Weltkarte Button unten links -->
<button id="mapToggleButton" title="Weltkarte anzeigen"
style="
position: fixed;
bottom: 15px;
left: 15px;
width: 100px;
height: 100px;
font-size: 60px;
background-color: #444;
color: green;
border: none;
border-radius: 10px;
cursor: pointer;
z-index: 9999;">
🗺
</button>
<body>
<div id="controls">
<label><input type="checkbox" id="toggleFog"></label>
<button id="fogMode">Modus: Radierer</button><br>
<button id="fogClear">Alles aufdecken</button>
<button id="fogFull">Alles vernebeln</button><br>
<label>Pinselgröße: <input type="range" id="brushSize" min="1" max="5" value="1" /></label>
<div style="background:#ccc; padding:10px; margin-top:10px;">
<label><input type="checkbox" id="toggleHexgrid" checked> Hexgrid anzeigen</label><br>
<label>Grid-Skalierung: <input type="range" id="gridScaleSlider" min="0.1" max="4.0" step="0.1" value="1"></label>
</div>
</div>
<!-- Battlemap-Auswahl Buttons -->
<div id="battlemap-buttons" style="position: absolute; top: 175px; left: 10px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 10px; z-index: 9999;">
<strong>Battle Maps:</strong>
</div>
<canvas id="mapCanvas"></canvas>
<iframe id="worldmap-iframe"
src="https://map.arenos.danielnagel.at"
style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: calc(100% - 130px); z-index: 8000; border: none;">
</iframe>
<script src="master.js">
</script>
<script>
let paused = false;
document.addEventListener("keydown", (e) => {
if (e.code === "Space") {
paused = !paused;
document.body.classList.toggle("paused", paused);
localStorage.setItem("pauseActive", paused);
}
});
// === Dynamische Standardmaps laden ===
fetch('/list-maps')
.then(res => res.json())
.then(data => {
const controlDiv = document.getElementById('battlemap-buttons');
const container = document.createElement('div');
container.style.marginTop = '5px';
container.style.background = '#ccc';
container.style.padding = '5px';
data.forEach(file => {
const button = document.createElement('button');
button.textContent = file;
button.style.display = 'block';
button.style.marginBottom = '5px';
button.addEventListener('click', () => {
LoadImageMap(`standard_maps/${file}`);
});
container.appendChild(button);
});
controlDiv.appendChild(container);
})
.catch(err => console.error('Fehler beim Laden der Battlemap-Liste:', err));
</script>
</body>
</html>

287
master.js Normal file
View File

@ -0,0 +1,287 @@
const canvas = document.getElementById("mapCanvas");
const fogCanvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const fogCtx = fogCanvas.getContext("2d");
const iframe = document.getElementById("worldmap-iframe");
let image = null;
let offsetX = 0, offsetY = 0;
let scale = 1;
let isDragging = false;
let startX, startY;
let fogEnabled = false;
let fogMode = "erase";
let brushSize = 50;
let mouseX = 0, mouseY = 0;
const channel = new BroadcastChannel("mapsync");
let MapX = 0;
let MapY = 0;
let MapZoom = 2;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
// === Fog Controls ===
document.getElementById("toggleFog").addEventListener("change", (e) => {
fogEnabled = e.target.checked;
channel.postMessage({ type: "fogEnabled", enabled: fogEnabled });
draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
});
document.getElementById("fogMode").addEventListener("click", () => {
fogMode = fogMode === "erase" ? "paint" : "erase";
document.getElementById("fogMode").innerText = fogMode === "erase" ? "Modus: Radierer" : "Modus: Nebel";
});
document.getElementById("fogClear").addEventListener("click", () => {
const btn = document.getElementById("fogClear");
btn.disabled = true;
setTimeout(() => btn.disabled = false, 1000);
fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height);
syncFog(); draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
});
document.getElementById("fogFull").addEventListener("click", () => {
const btn = document.getElementById("fogFull");
btn.disabled = true;
setTimeout(() => btn.disabled = false, 1000);
fogCtx.fillStyle = "rgba(50, 50, 50, 1)";
fogCtx.fillRect(0, 0, fogCanvas.width, fogCanvas.height);
syncFog(); draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
});
document.getElementById("brushSize").addEventListener("input", (e) => {
brushSize = 50 * parseInt(e.target.value);
});
// === Mouse Interaktion ===
canvas.addEventListener("dragover", e => e.preventDefault());
canvas.addEventListener("drop", e => {
e.preventDefault();
const file = e.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = evt => {
const img = new Image();
img.onload = () => {
image = img;
//Fog auf Größe des Geladenen Bildes anpassen.
fogCanvas.width = img.width;
fogCanvas.height = img.height;
fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height);
draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
channel.postMessage({ type: "mapData", data: evt.target.result });
channel.postMessage({ type: "fogEnabled", enabled: fogEnabled });
syncFog();
};
img.src = evt.target.result;
};
reader.readAsDataURL(file);
});
function LoadImageMap(i) {
const img = new Image();
img.onload = () => {
image = img;
fogCanvas.width = img.width;
fogCanvas.height = img.height;
fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height);
draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
channel.postMessage({ type: "mapData", data: i });
channel.postMessage({ type: "fogEnabled", enabled: fogEnabled });
syncFog();
};
img.src = i;
}
window.LoadImageMap = LoadImageMap;
canvas.addEventListener("mousedown", e => {
if (e.button === 1) {
isDragging = true;
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
}
});
canvas.addEventListener("mouseup", () => { isDragging = false; });
canvas.addEventListener("mouseout", () => { isDragging = false; });
canvas.addEventListener("mousemove", e => {
mouseX = e.clientX;
mouseY = e.clientY;
if (fogEnabled) draw();
if (isDragging) {
offsetX = e.clientX - startX;
offsetY = e.clientY - startY;
syncView();
draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
} else if (fogEnabled && e.buttons === 1) {
const x = (mouseX - offsetX) / scale;
const y = (mouseY - offsetY) / scale;
fogCtx.globalCompositeOperation = fogMode === "erase" ? "destination-out" : "source-over";
fogCtx.fillStyle = "rgba(50, 50, 50, 0.9)";
fogCtx.beginPath();
fogCtx.arc(x, y, brushSize, 0, 2 * Math.PI);
fogCtx.fill();
syncFog(); draw();
drawBrushPreview(mouseX, mouseY);
}
});
canvas.addEventListener("wheel", e => {
e.preventDefault();
const worldX = (e.clientX - offsetX) / scale;
const worldY = (e.clientY - offsetY) / scale;
const zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
scale *= zoomFactor;
offsetX = e.clientX - worldX * scale;
offsetY = e.clientY - worldY * scale;
syncView(); draw();
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
});
function syncView() {
channel.postMessage({ type: "viewData", offsetX, offsetY, scale });
}
function syncFog() {
const fogURL = fogCanvas.toDataURL("image/png");
channel.postMessage({ type: "fogData", data: fogURL });
}
function draw() {
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
ctx.clearRect(-offsetX / scale, -offsetY / scale, canvas.width / scale, canvas.height / scale);
if (image) ctx.drawImage(image, 0, 0);
if (fogEnabled && fogCanvas.width && fogCanvas.height) {
ctx.globalAlpha = 0.4;
ctx.drawImage(fogCanvas, 0, 0);
ctx.globalAlpha = 1;
}
if (fogEnabled) drawBrushPreview(mouseX, mouseY);
}
function drawBrushPreview(mouseX, mouseY) {
const worldX = (mouseX - offsetX) / scale;
const worldY = (mouseY - offsetY) / scale;
ctx.save();
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
ctx.strokeStyle = "rgba(255,255,255,0.8)";
ctx.lineWidth = 2 / scale;
ctx.beginPath();
ctx.arc(worldX, worldY, brushSize, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
window.addEventListener("keydown", (e) => {
if (e.key.toLowerCase() === "f") {
fogEnabled = !fogEnabled;
document.getElementById("toggleFog").checked = fogEnabled;
channel.postMessage({ type: "fogEnabled", enabled: fogEnabled });
draw();
}
});
document.getElementById("toggleHexgrid").addEventListener("change", (e) => {
channel.postMessage({ type: "hexgrid", enabled: e.target.checked });
});
document.getElementById("gridScaleSlider").addEventListener("input", (e) => {
const scale = parseFloat(e.target.value);
channel.postMessage({ type: "hexgridScale", scale });
});
// === Weltkarte Funktionen ===
let worldMapMode = false;
let previousImageSrc = null;
// === Leaflet View Update Intervall ===
setInterval(() => {
const iframe = document.getElementById("worldmap-iframe");
if (iframe?.contentWindow && worldMapMode) {
iframe.contentWindow.postMessage("getView", "*");
}
}, 500);
window.addEventListener("message", (event) => {
const data = event.data;
if (data?.type === "viewData") {
MapX = data.center.lng;
MapY = data.center.lat;
MapZoom = data.zoom;
channel.postMessage({ type: "MapCoord", MapX, MapY, MapZoom}); //Dem Slave die Karten Werte schicken.
}
});
const mapButton = document.getElementById("mapToggleButton");
if (mapButton) {
mapButton.addEventListener("click", () => {
if (!worldMapMode) {
previousImageSrc = image?.src || "pause.png";
loadMap();
channel.postMessage({ type: "mapMode", enabled: true });
mapButton.textContent = "↩️";
worldMapMode = true;
syncWorldMapView();
} else {
restoreImage(previousImageSrc);
channel.postMessage({ type: "mapMode", enabled: false });
mapButton.textContent = "🗺";
worldMapMode = false;
}
});
}
//Laden der iframe WorldMap
function loadMap() {
const iframe = document.getElementById("worldmap-iframe");
if (!iframe) return;
iframe.style.display = "block";
if (iframe.contentWindow) {
iframe.contentWindow.postMessage({ action: "syncRequest" }, "*");
} else {
iframe.onload = () => {
iframe.contentWindow.postMessage({ action: "syncRequest" }, "*");
};
}
}
//Nach dem schließen der iframe WorldMap, wieder die letzte Map herstellen.
function restoreImage(src) {
const iframe = document.getElementById("worldmap-iframe");
const canvas = document.getElementById("mapCanvas");
if (iframe) iframe.style.display = "none";
if (canvas) canvas.style.display = "block";
if (!src) return;
const img = new Image();
img.onload = () => {
image = img;
fogCanvas.width = img.width;
fogCanvas.height = img.height;
fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height);
draw();
};
img.src = src;
}
function syncWorldMapView() {
const iframe = document.getElementById("worldmap-iframe");
if (worldMapMode && iframe?.contentWindow) {
iframe.contentWindow.postMessage("getView", "*");
}
}

BIN
pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

26
server_launcher.py Normal file
View File

@ -0,0 +1,26 @@
import http.server
import socketserver
import os
import json
PORT = 8000
os.chdir(os.path.dirname(__file__))
class CustomHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/list-maps':
try:
files = os.listdir('standard_maps')
image_files = [f for f in files if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(image_files).encode('utf-8'))
except Exception as e:
self.send_error(500, f"Fehler: {str(e)}")
else:
super().do_GET()
with socketserver.TCPServer(("", PORT), CustomHandler) as httpd:
print(f"Starte Server auf Port {PORT}...")
httpd.serve_forever()

126
slave.js Normal file
View File

@ -0,0 +1,126 @@
// slave.js synchronisiert Ansicht mit master.js (mit direkter URL-Übergabe an Leaflet)
const canvas = document.getElementById("mapCanvas");
const ctx = canvas.getContext("2d");
let image = null;
let fogImage = null;
let hexGrid = new Image();
hexGrid.src = "hexgrid.png";
let offsetX = 0, offsetY = 0;
let scale = 1;
let fogEnabled = false;
let hexgridEnabled = false;
let hexgridScale = 1;
let MapX = 0;
let MapY = 0;
let MapZoom = 2;
const channel = new BroadcastChannel("mapsync");
channel.onmessage = (event) => {
const msg = event.data;
if (msg.type === "mapData") {
const img = new Image();
img.onload = () => {
image = img;
draw();
};
img.src = msg.data;
} else if (msg.type === "hexgrid") {
hexgridEnabled = msg.enabled;
draw();
} else if (msg.type === "hexgridScale") {
hexgridScale = msg.scale;
draw();
} else if (msg.type === "viewData") {
offsetX = msg.offsetX;
offsetY = msg.offsetY;
scale = msg.scale;
draw();
} else if (msg.type === "fogData") {
const fog = new Image();
fog.onload = () => {
fogImage = fog;
draw();
};
fog.src = msg.data;
} else if (msg.type === "fogEnabled") {
fogEnabled = msg.enabled;
draw();
} else if (msg.type === "mapMode") {
const iframe = document.getElementById("worldmap-iframe");
const canvas = document.getElementById("mapCanvas");
if (msg.enabled) {
iframe.style.display = "block";
canvas.style.display = "none";
} else {
iframe.style.display = "none";
canvas.style.display = "block";
}
} else if (msg.type === "MapCoord") {
MapX = msg.MapX;
MapY = msg.MapY;
MapZoom = msg.MapZoom;
const iframe = document.getElementById("worldmap-iframe");
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({
type: "setView",
center: [MapY, MapX],
zoom: MapZoom
}, "*");
}
}
};
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw();
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
function draw() {
if (!image) return;
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
ctx.clearRect(-offsetX / scale, -offsetY / scale, canvas.width / scale, canvas.height / scale);
ctx.drawImage(image, 0, 0);
if (hexgridEnabled && hexGrid.complete) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 0.5;
const scaleMod = 0.2;
const gridWidth = hexGrid.width * (hexgridScale * scaleMod);
const gridHeight = hexGrid.height * (hexgridScale * scaleMod);
const dx = (canvas.width - gridWidth) / 2;
const dy = (canvas.height - gridHeight) / 2;
ctx.drawImage(hexGrid, dx, dy, gridWidth, gridHeight);
ctx.restore();
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
}
if (fogEnabled && fogImage) {
ctx.globalAlpha = 1.0;
ctx.drawImage(fogImage, 0, 0);
ctx.globalAlpha = 1.0;
}
}

44
spieler.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>MapViewer - Spieler</title>
<link rel="stylesheet" href="style.css" />
<style>
#pauseScreen {
position: fixed;
inset: 0;
background: #000;
z-index: 9999;
display: none;
}
#pauseScreen img {
object-fit: cover;
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body><div id="pauseScreen"><img src="pause.png" alt="Pause"></div>
<canvas id="mapCanvas"></canvas>
<script src="slave.js"></script>
<iframe id="worldmap-iframe"
src="https://map.arenos.danielnagel.at"
style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 9000; border: none;">
</iframe>
<script>
const pauseScreen = document.getElementById("pauseScreen");
function checkPauseState() {
const paused = localStorage.getItem("pauseActive") === "true";
pauseScreen.style.display = paused ? "block" : "none";
requestAnimationFrame(checkPauseState);
}
requestAnimationFrame(checkPauseState);
</script>
</body>
</html>

BIN
standard_maps/cave.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 MiB

BIN
standard_maps/harbor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

BIN
standard_maps/street.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
standard_maps/tavern.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

5
starte_viewer.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
start /min python server_launcher.py
timeout /t 2 >nul
start http://localhost:8000/index.html
start http://localhost:8000/spieler.html

22
style.css Normal file
View File

@ -0,0 +1,22 @@
body {
margin: 0;
overflow: hidden;
background: #000;
}
#mapCanvas {
display: block;
width: 100vw;
height: 100vh;
background-color: #111;
cursor: grab;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
background: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 8px;
font-family: sans-serif;
}