commit c0beeeff92e460e0dc409933d1e2c3f0f04d401f Author: Daniel Date: Sat Aug 30 22:02:13 2025 +0200 Init Initialer Load vom alten Projekt. diff --git a/hexgrid.png b/hexgrid.png new file mode 100644 index 0000000..112d727 Binary files /dev/null and b/hexgrid.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..1e1ac5b --- /dev/null +++ b/index.html @@ -0,0 +1,108 @@ + + + + + MapViewer – Spielleiter + + + + + + + + + +
+ +
+ +
+ + + +
+
+ +
+
+ + +
+ Battle Maps: +
+ + + + + + + + + + + + + + diff --git a/master.js b/master.js new file mode 100644 index 0000000..66a44e8 --- /dev/null +++ b/master.js @@ -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", "*"); + } +} diff --git a/pause.png b/pause.png new file mode 100644 index 0000000..c8eda82 Binary files /dev/null and b/pause.png differ diff --git a/server_launcher.py b/server_launcher.py new file mode 100644 index 0000000..8a03732 --- /dev/null +++ b/server_launcher.py @@ -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() diff --git a/slave.js b/slave.js new file mode 100644 index 0000000..e3372b9 --- /dev/null +++ b/slave.js @@ -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; + } +} + diff --git a/spieler.html b/spieler.html new file mode 100644 index 0000000..855cd51 --- /dev/null +++ b/spieler.html @@ -0,0 +1,44 @@ + + + + + MapViewer - Spieler + + + + +
Pause
+ + + + + + + + + diff --git a/standard_maps/cave.png b/standard_maps/cave.png new file mode 100644 index 0000000..d1cd949 Binary files /dev/null and b/standard_maps/cave.png differ diff --git a/standard_maps/city streets.png b/standard_maps/city streets.png new file mode 100644 index 0000000..fcdceed Binary files /dev/null and b/standard_maps/city streets.png differ diff --git a/standard_maps/farm field.jpg b/standard_maps/farm field.jpg new file mode 100644 index 0000000..2edd8d9 Binary files /dev/null and b/standard_maps/farm field.jpg differ diff --git a/standard_maps/harbor.png b/standard_maps/harbor.png new file mode 100644 index 0000000..40d6a42 Binary files /dev/null and b/standard_maps/harbor.png differ diff --git a/standard_maps/mansion garden.jpg b/standard_maps/mansion garden.jpg new file mode 100644 index 0000000..a44f20c Binary files /dev/null and b/standard_maps/mansion garden.jpg differ diff --git a/standard_maps/roadside camp.jpg b/standard_maps/roadside camp.jpg new file mode 100644 index 0000000..3c61510 Binary files /dev/null and b/standard_maps/roadside camp.jpg differ diff --git a/standard_maps/street.png b/standard_maps/street.png new file mode 100644 index 0000000..d50b419 Binary files /dev/null and b/standard_maps/street.png differ diff --git a/standard_maps/tavern.jpg b/standard_maps/tavern.jpg new file mode 100644 index 0000000..0faefa9 Binary files /dev/null and b/standard_maps/tavern.jpg differ diff --git a/starte_viewer.bat b/starte_viewer.bat new file mode 100644 index 0000000..1e48c9e --- /dev/null +++ b/starte_viewer.bat @@ -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 diff --git a/style.css b/style.css new file mode 100644 index 0000000..17a65e5 --- /dev/null +++ b/style.css @@ -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; +}