'use strict'; const { PNG } = require('pngjs'); // Minecraft map color palette (base colors × 4 shades each) // Source: https://minecraft.wiki/w/Map_item_format#Color_table // Index 0-3 = NONE (transparent), 4-7 = GRASS, 8-11 = SAND, etc. // Each base color has 4 multipliers: 0.71, 0.86, 1.0, 0.53 const BASE_COLORS = [ null, // 0: NONE [127, 178, 56], // 1: GRASS [247, 233, 163], // 2: SAND [199, 199, 199], // 3: WOOL [255, 0, 0], // 4: FIRE [160, 160, 255], // 5: ICE [167, 167, 167], // 6: METAL [0, 124, 0], // 7: PLANT [255, 255, 255], // 8: SNOW [164, 168, 184], // 9: CLAY [151, 109, 77], // 10: DIRT [112, 112, 112], // 11: STONE [64, 64, 255], // 12: WATER [143, 119, 72], // 13: WOOD [255, 252, 245], // 14: QUARTZ [216, 127, 51], // 15: COLOR_ORANGE [178, 76, 216], // 16: COLOR_MAGENTA [102, 153, 216], // 17: COLOR_LIGHT_BLUE [229, 229, 51], // 18: COLOR_YELLOW [127, 204, 25], // 19: COLOR_LIGHT_GREEN [242, 127, 165], // 20: COLOR_PINK [76, 76, 76], // 21: COLOR_GRAY [153, 153, 153], // 22: COLOR_LIGHT_GRAY [76, 127, 153], // 23: COLOR_CYAN [127, 63, 178], // 24: COLOR_PURPLE [51, 76, 178], // 25: COLOR_BLUE [102, 76, 51], // 26: COLOR_BROWN [102, 127, 51], // 27: COLOR_GREEN [153, 51, 51], // 28: COLOR_RED [25, 25, 25], // 29: COLOR_BLACK [250, 238, 77], // 30: GOLD [92, 219, 213], // 31: DIAMOND [74, 128, 255], // 32: LAPIS [0, 217, 58], // 33: EMERALD [129, 86, 49], // 34: PODZOL [112, 2, 0], // 35: NETHER [209, 177, 161], // 36: TERRACOTTA_WHITE [159, 82, 36], // 37: TERRACOTTA_ORANGE [149, 87, 108], // 38: TERRACOTTA_MAGENTA [112, 108, 138], // 39: TERRACOTTA_LIGHT_BLUE [186, 133, 36], // 40: TERRACOTTA_YELLOW [103, 117, 53], // 41: TERRACOTTA_LIGHT_GREEN [160, 77, 78], // 42: TERRACOTTA_PINK [57, 41, 35], // 43: TERRACOTTA_GRAY [135, 107, 98], // 44: TERRACOTTA_LIGHT_GRAY [87, 92, 92], // 45: TERRACOTTA_CYAN [122, 73, 88], // 46: TERRACOTTA_PURPLE [76, 62, 92], // 47: TERRACOTTA_BLUE [76, 50, 35], // 48: TERRACOTTA_BROWN [76, 82, 42], // 49: TERRACOTTA_GREEN [142, 60, 46], // 50: TERRACOTTA_RED [37, 22, 16], // 51: TERRACOTTA_BLACK [189, 48, 49], // 52: CRIMSON_NYLIUM [148, 63, 97], // 53: CRIMSON_STEM [92, 25, 29], // 54: CRIMSON_HYPHAE [22, 126, 134], // 55: WARPED_NYLIUM [58, 142, 140], // 56: WARPED_STEM [86, 44, 62], // 57: WARPED_HYPHAE [20, 180, 133], // 58: WARPED_WART_BLOCK [100, 100, 100], // 59: DEEPSLATE [216, 175, 147], // 60: RAW_IRON [127, 167, 150], // 61: GLOW_LICHEN ]; const SHADE_MULTIPLIERS = [180, 220, 255, 135]; // out of 255 // Build the full 256-entry lookup: MAP_COLORS[colorIndex] → [r, g, b] const MAP_COLORS = new Array(256).fill(null); for (let base = 0; base < BASE_COLORS.length; base++) { for (let shade = 0; shade < 4; shade++) { const idx = base * 4 + shade; if (!BASE_COLORS[base]) { MAP_COLORS[idx] = [0, 0, 0, 0]; // transparent } else { const [r, g, b] = BASE_COLORS[base]; const m = SHADE_MULTIPLIERS[shade]; MAP_COLORS[idx] = [ Math.floor(r * m / 255), Math.floor(g * m / 255), Math.floor(b * m / 255), 255 ]; } } } /** * Apply a partial map update from a protocol packet to a pixel buffer. * @param {Uint8Array} pixels - 128×128 color index buffer (mutated in place) * @param {Object} packet - Map protocol packet with columns, rows, x, y, data */ function applyMapUpdate(pixels, packet) { const { columns, rows, x, y, data } = packet; if (!columns || columns === 0 || !data) return; for (let col = 0; col < columns; col++) { for (let row = 0; row < rows; row++) { const srcIdx = col * rows + row; const dstX = x + col; const dstY = y + row; if (dstX < 128 && dstY < 128) { pixels[dstY * 128 + dstX] = data[srcIdx]; } } } } /** * Render a 128×128 color-index buffer to a base64 PNG string. * @param {Uint8Array} pixels - 128×128 array of Minecraft color indices * @returns {string} base64-encoded PNG */ function renderMapToPNG(pixels) { const png = new PNG({ width: 128, height: 128 }); for (let i = 0; i < 128 * 128; i++) { const colorIdx = pixels[i]; const color = MAP_COLORS[colorIdx] || [0, 0, 0, 0]; const offset = i * 4; png.data[offset] = color[0]; // R png.data[offset + 1] = color[1]; // G png.data[offset + 2] = color[2]; // B png.data[offset + 3] = color[3]; // A } const buffer = PNG.sync.write(png); return buffer.toString('base64'); } module.exports = { MAP_COLORS, applyMapUpdate, renderMapToPNG };