forked from wmantly/mc-bot-town
140 lines
4.7 KiB
JavaScript
140 lines
4.7 KiB
JavaScript
'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 };
|