feat: UB-style tile processing — white transparent, color remapping
Process each tile through canvas pixel manipulation: - White pixels → transparent (same as UB's MakeTransparent) - Remap UB's 5 source colors (walls, inner walls, ramps, floors, stairs) to readable display colors on dark background - Black outlines made semi-transparent - Current floor at 85% opacity, other floors at 12% - Non-current floors drawn first, current floor on top Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
06f326cce0
commit
683d1cf337
1 changed files with 83 additions and 12 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue