diff --git a/static/script.js b/static/script.js index a9730d6d..2a8246c9 100644 --- a/static/script.js +++ b/static/script.js @@ -3586,21 +3586,85 @@ function openPlayerDashboard() { const radarWindows = {}; // character_name -> window element const dungeonMapCache = {}; // landblock hex string -> dungeon map data -let dungeonTileImages = null; // env_id -> Image, loaded once +let dungeonTileCanvases = null; // env_id -> processed offscreen canvas + +// UB default source colors (ARGB as signed int32 → R,G,B) +// These are the colors embedded in the tile images that get remapped +const UB_TILE_COLORS = { + walls: { r: 0, g: 0, b: 255 }, // -16777089 → #0000FF + innerWalls: { r: 127, g: 127, b: 255 }, // -8404993 → #7F7FFF + rampedWalls: { r: 77, g: 255, b: 255 }, // -11622657 → #4DFFFF (approx) + floors: { r: 0, g: 127, b: 255 }, // -16744513 → #007FFF + stairs: { r: 0, g: 63, b: 255 }, // -16760961 → #003FFF +}; + +// Target display colors (UB-like appearance on dark background) +const DUNGEON_DISPLAY_COLORS = { + walls: { r: 140, g: 140, b: 180 }, // light gray-blue walls + innerWalls: { r: 100, g: 100, b: 140 }, // darker inner walls + rampedWalls: { r: 120, g: 160, b: 120 }, // greenish ramps + floors: { r: 60, g: 80, b: 60 }, // dark green floors + stairs: { r: 180, g: 160, b: 80 }, // yellowish stairs +}; + +// Process a tile image: make white transparent, remap UB colors +function processTileImage(img) { + const c = document.createElement('canvas'); + c.width = 10; + c.height = 10; + const ctx = c.getContext('2d'); + ctx.drawImage(img, 0, 0, 10, 10); + const imageData = ctx.getImageData(0, 0, 10, 10); + const d = imageData.data; + + for (let i = 0; i < d.length; i += 4) { + const r = d[i], g = d[i+1], b = d[i+2]; + + // Make white (and near-white) transparent + if (r > 240 && g > 240 && b > 240) { + d[i+3] = 0; // alpha = 0 + continue; + } + + // Color remap: match source colors and replace with display colors + let matched = false; + for (const [key, src] of Object.entries(UB_TILE_COLORS)) { + if (Math.abs(r - src.r) < 15 && Math.abs(g - src.g) < 15 && Math.abs(b - src.b) < 15) { + const dst = DUNGEON_DISPLAY_COLORS[key]; + d[i] = dst.r; d[i+1] = dst.g; d[i+2] = dst.b; + matched = true; + break; + } + } + + // Make black semi-transparent (outlines) + if (!matched && r < 15 && g < 15 && b < 15) { + d[i+3] = 100; + } + } + + ctx.putImageData(imageData, 0, 0); + return c; +} // Load dungeon tile textures (614 tiles, ~287KB JSON) function loadDungeonTiles() { - if (dungeonTileImages) return; // already loading/loaded - dungeonTileImages = {}; + if (dungeonTileCanvases) return; // already loading/loaded + dungeonTileCanvases = {}; fetch('dungeon_tiles.json') .then(r => r.json()) .then(data => { + let loaded = 0; + const total = Object.keys(data).length; Object.entries(data).forEach(([envId, dataUrl]) => { const img = new Image(); + img.onload = () => { + dungeonTileCanvases[envId] = processTileImage(img); + loaded++; + if (loaded === total) console.log(`Processed ${total} dungeon tiles`); + }; img.src = dataUrl; - dungeonTileImages[envId] = img; }); - console.log(`Loaded ${Object.keys(dungeonTileImages).length} dungeon tile textures`); }) .catch(err => console.warn('Failed to load dungeon tiles:', err)); } @@ -3791,7 +3855,7 @@ function updateRadarWindow(msg) { ctx.translate(cx, cy); ctx.rotate(-playerHeading * Math.PI / 180); // heading-up rotation const cellSize = 10 * scale; // each cell is 10 game units - const hasTiles = dungeonTileImages && Object.keys(dungeonTileImages).length > 0; + const hasTiles = dungeonTileCanvases && Object.keys(dungeonTileCanvases).length > 0; // Normalize rotation value to radians (same as UB's DungeonCell.cs) function cellRotation(rot) { @@ -3801,21 +3865,28 @@ function updateRadarWindow(msg) { return 0; } - (dmap.z_levels || []).forEach(level => { + // Draw non-current floors first (dimmed), then current floor on top + const sortedLevels = (dmap.z_levels || []).slice().sort((a, b) => { + const aCur = a.z === playerRoundedZ ? 1 : 0; + const bCur = b.z === playerRoundedZ ? 1 : 0; + return aCur - bCur; // current floor drawn last (on top) + }); + + sortedLevels.forEach(level => { const isCurrentFloor = (level.z === playerRoundedZ); - ctx.globalAlpha = isCurrentFloor ? 0.7 : 0.15; + ctx.globalAlpha = isCurrentFloor ? 0.85 : 0.12; (level.cells || []).forEach(cell => { const dx = (cell.x - playerX) * scale; const dy = -(cell.y - playerY) * scale; // Y flipped - const tileImg = hasTiles ? dungeonTileImages[String(cell.env_id)] : null; + const tileCanvas = hasTiles ? dungeonTileCanvases[String(cell.env_id)] : null; - if (tileImg && tileImg.complete && tileImg.naturalWidth) { - // Draw textured tile with rotation + if (tileCanvas) { + // Draw processed tile with rotation ctx.save(); ctx.translate(dx, dy); ctx.rotate(cellRotation(cell.rotation)); - ctx.drawImage(tileImg, -cellSize / 2, -cellSize / 2, cellSize, cellSize); + ctx.drawImage(tileCanvas, -cellSize / 2, -cellSize / 2, cellSize, cellSize); ctx.restore(); } else { // Fallback: colored rectangle