diff --git a/MinecraftClient/ChatBots/Map.cs b/MinecraftClient/ChatBots/Map.cs
index 930e057e..ff5d1830 100644
--- a/MinecraftClient/ChatBots/Map.cs
+++ b/MinecraftClient/ChatBots/Map.cs
@@ -447,109 +447,62 @@ namespace MinecraftClient.ChatBots
public DateTime LastUpdated { get; set; }
}
- internal class MapColors
+ ///
+ /// Map packet base color palette. Colors are loaded from the embedded
+ /// MinimapBlockColors.json resource (map_palette section) generated by
+ /// tools/gen_block_color_map.py, which parses MapColor.java.
+ ///
+ internal static class MapColors
{
- // When colors are updated in a new update, you can get them using the game code: net\minecraft\world\level\material\MaterialColor.java
- public static Dictionary Colors = new()
+ private static readonly Dictionary Colors;
+
+ private static readonly byte[] ShadeMultipliers = [180, 220, 255, 135];
+
+ static MapColors()
{
- //Color ID R G B
- {0, new byte[]{0, 0, 0}},
- {1, new byte[]{127, 178, 56}},
- {2, new byte[]{247, 233, 163}},
- {3, new byte[]{199, 199, 199}},
- {4, new byte[]{255, 0, 0}},
- {5, new byte[]{160, 160, 255}},
- {6, new byte[]{167, 167, 167}},
- {7, new byte[]{0, 124, 0}},
- {8, new byte[]{255, 255, 255}},
- {9, new byte[]{164, 168, 184}},
- {10, new byte[]{151, 109, 77}},
- {11, new byte[]{112, 112, 112}},
- {12, new byte[]{64, 64, 255}},
- {13, new byte[]{143, 119, 72}},
- {14, new byte[]{255, 252, 245}},
- {15, new byte[]{216, 127, 51}},
- {16, new byte[]{178, 76, 216}},
- {17, new byte[]{102, 153, 216}},
- {18, new byte[]{229, 229, 51}},
- {19, new byte[]{127, 204, 25}},
- {20, new byte[]{242, 127, 165}},
- {21, new byte[]{76, 76, 76}},
- {22, new byte[]{153, 153, 153}},
- {23, new byte[]{76, 127, 153}},
- {24, new byte[]{127, 63, 178}},
- {25, new byte[]{51, 76, 178}},
- {26, new byte[]{102, 76, 51}},
- {27, new byte[]{102, 127, 51}},
- {28, new byte[]{153, 51, 51}},
- {29, new byte[]{25, 25, 25}},
- {30, new byte[]{250, 238, 77}},
- {31, new byte[]{92, 219, 213}},
- {32, new byte[]{74, 128, 255}},
- {33, new byte[]{0, 217, 58}},
- {34, new byte[]{129, 86, 49}},
- {35, new byte[]{112, 2, 0}},
- {36, new byte[]{209, 177, 161}},
- {37, new byte[]{159, 82, 36}},
- {38, new byte[]{149, 87, 108}},
- {39, new byte[]{112, 108, 138}},
- {40, new byte[]{186, 133, 36}},
- {41, new byte[]{103, 117, 53}},
- {42, new byte[]{160, 77, 78}},
- {43, new byte[]{57, 41, 35}},
- {44, new byte[]{135, 107, 98}},
- {45, new byte[]{87, 92, 92}},
- {46, new byte[]{122, 73, 88}},
- {47, new byte[]{76, 62, 92}},
- {48, new byte[]{76, 50, 35}},
- {49, new byte[]{76, 82, 42}},
- {50, new byte[]{142, 60, 46}},
- {51, new byte[]{37, 22, 16}},
- {52, new byte[]{189, 48, 49}},
- {53, new byte[]{148, 63, 97}},
- {54, new byte[]{92, 25, 29}},
- {55, new byte[]{22, 126, 134}},
- {56, new byte[]{58, 142, 140}},
- {57, new byte[]{86, 44, 62}},
- {58, new byte[]{20, 180, 133}},
- {59, new byte[]{100, 100, 100}},
- {60, new byte[]{216, 175, 147}},
- {61, new byte[]{127, 167, 150}}
- };
+ Colors = new Dictionary();
+ try
+ {
+ using var stream = System.Reflection.Assembly.GetExecutingAssembly()
+ .GetManifestResourceStream("MinimapBlockColors.json");
+ if (stream is not null)
+ {
+ using var doc = System.Text.Json.JsonDocument.Parse(stream);
+ if (doc.RootElement.TryGetProperty("map_palette", out var palette))
+ {
+ foreach (var prop in palette.EnumerateObject())
+ {
+ if (!byte.TryParse(prop.Name, out byte id))
+ continue;
+ var arr = prop.Value;
+ Colors[id] = [
+ arr[0].GetByte(),
+ arr[1].GetByte(),
+ arr[2].GetByte()
+ ];
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ ConsoleIO.WriteLogLine($"[Map] Failed to load map palette: {ex.Message}");
+ }
+ }
public static ColorRGBA ColorByteToRGBA(byte receivedColorId)
{
- // Divide received color id by 4 to get the base color id
- // Much thanks to DevBobcorn
byte baseColorId = (byte)(receivedColorId >> 2);
- // Any new colors that we haven't added will be purple like in the missing CS: Source Texture
- if (!Colors.ContainsKey(baseColorId))
+ if (!Colors.TryGetValue(baseColorId, out byte[]? rgb))
return new(248, 0, 248, 255, true);
- byte shadeId = (byte)(receivedColorId % 4);
- byte shadeMultiplier = 255;
-
- switch (shadeId)
- {
- case 0:
- shadeMultiplier = 180;
- break;
-
- case 1:
- shadeMultiplier = 220;
- break;
-
- case 3:
- // NOTE: If we ever add map support below 1.8, this needs to be 220 before 1.8
- shadeMultiplier = 135;
- break;
- }
+ byte multiplier = ShadeMultipliers[receivedColorId & 3];
return new(
- r: (byte)((Colors[baseColorId][0] * shadeMultiplier) / 255),
- g: (byte)((Colors[baseColorId][1] * shadeMultiplier) / 255),
- b: (byte)((Colors[baseColorId][2] * shadeMultiplier) / 255),
+ r: (byte)(rgb[0] * multiplier / 255),
+ g: (byte)(rgb[1] * multiplier / 255),
+ b: (byte)(rgb[2] * multiplier / 255),
a: 255
);
}
diff --git a/MinecraftClient/Tui/MinimapBlockColors.json b/MinecraftClient/Tui/MinimapBlockColors.json
index af7d726c..aa833f29 100644
--- a/MinecraftClient/Tui/MinimapBlockColors.json
+++ b/MinecraftClient/Tui/MinimapBlockColors.json
@@ -3548,5 +3548,317 @@
"PackedIce",
"BlueIce",
"FrostedIce"
- ]
+ ],
+ "map_palette": {
+ "0": [
+ 0,
+ 0,
+ 0
+ ],
+ "1": [
+ 127,
+ 178,
+ 56
+ ],
+ "2": [
+ 247,
+ 233,
+ 163
+ ],
+ "3": [
+ 199,
+ 199,
+ 199
+ ],
+ "4": [
+ 255,
+ 0,
+ 0
+ ],
+ "5": [
+ 160,
+ 160,
+ 255
+ ],
+ "6": [
+ 167,
+ 167,
+ 167
+ ],
+ "7": [
+ 0,
+ 124,
+ 0
+ ],
+ "8": [
+ 255,
+ 255,
+ 255
+ ],
+ "9": [
+ 164,
+ 168,
+ 184
+ ],
+ "10": [
+ 151,
+ 109,
+ 77
+ ],
+ "11": [
+ 112,
+ 112,
+ 112
+ ],
+ "12": [
+ 64,
+ 64,
+ 255
+ ],
+ "13": [
+ 143,
+ 119,
+ 72
+ ],
+ "14": [
+ 255,
+ 252,
+ 245
+ ],
+ "15": [
+ 216,
+ 127,
+ 51
+ ],
+ "16": [
+ 178,
+ 76,
+ 216
+ ],
+ "17": [
+ 102,
+ 153,
+ 216
+ ],
+ "18": [
+ 229,
+ 229,
+ 51
+ ],
+ "19": [
+ 127,
+ 204,
+ 25
+ ],
+ "20": [
+ 242,
+ 127,
+ 165
+ ],
+ "21": [
+ 76,
+ 76,
+ 76
+ ],
+ "22": [
+ 153,
+ 153,
+ 153
+ ],
+ "23": [
+ 76,
+ 127,
+ 153
+ ],
+ "24": [
+ 127,
+ 63,
+ 178
+ ],
+ "25": [
+ 51,
+ 76,
+ 178
+ ],
+ "26": [
+ 102,
+ 76,
+ 51
+ ],
+ "27": [
+ 102,
+ 127,
+ 51
+ ],
+ "28": [
+ 153,
+ 51,
+ 51
+ ],
+ "29": [
+ 25,
+ 25,
+ 25
+ ],
+ "30": [
+ 250,
+ 238,
+ 77
+ ],
+ "31": [
+ 92,
+ 219,
+ 213
+ ],
+ "32": [
+ 74,
+ 128,
+ 255
+ ],
+ "33": [
+ 0,
+ 217,
+ 58
+ ],
+ "34": [
+ 129,
+ 86,
+ 49
+ ],
+ "35": [
+ 112,
+ 2,
+ 0
+ ],
+ "36": [
+ 209,
+ 177,
+ 161
+ ],
+ "37": [
+ 159,
+ 82,
+ 36
+ ],
+ "38": [
+ 149,
+ 87,
+ 108
+ ],
+ "39": [
+ 112,
+ 108,
+ 138
+ ],
+ "40": [
+ 186,
+ 133,
+ 36
+ ],
+ "41": [
+ 103,
+ 117,
+ 53
+ ],
+ "42": [
+ 160,
+ 77,
+ 78
+ ],
+ "43": [
+ 57,
+ 41,
+ 35
+ ],
+ "44": [
+ 135,
+ 107,
+ 98
+ ],
+ "45": [
+ 87,
+ 92,
+ 92
+ ],
+ "46": [
+ 122,
+ 73,
+ 88
+ ],
+ "47": [
+ 76,
+ 62,
+ 92
+ ],
+ "48": [
+ 76,
+ 50,
+ 35
+ ],
+ "49": [
+ 76,
+ 82,
+ 42
+ ],
+ "50": [
+ 142,
+ 60,
+ 46
+ ],
+ "51": [
+ 37,
+ 22,
+ 16
+ ],
+ "52": [
+ 189,
+ 48,
+ 49
+ ],
+ "53": [
+ 148,
+ 63,
+ 97
+ ],
+ "54": [
+ 92,
+ 25,
+ 29
+ ],
+ "55": [
+ 22,
+ 126,
+ 134
+ ],
+ "56": [
+ 58,
+ 142,
+ 140
+ ],
+ "57": [
+ 86,
+ 44,
+ 62
+ ],
+ "58": [
+ 20,
+ 180,
+ 133
+ ],
+ "59": [
+ 100,
+ 100,
+ 100
+ ],
+ "60": [
+ 216,
+ 175,
+ 147
+ ],
+ "61": [
+ 127,
+ 167,
+ 150
+ ]
+ }
}
\ No newline at end of file
diff --git a/tools/gen_block_color_map.py b/tools/gen_block_color_map.py
index 69cbb502..8bea845f 100644
--- a/tools/gen_block_color_map.py
+++ b/tools/gen_block_color_map.py
@@ -205,6 +205,28 @@ WATER_BLOCKS = ["Water"]
ICE_BLOCKS = ["Ice", "PackedIce", "BlueIce", "FrostedIce"]
+def build_map_palette(map_color_java: Path) -> dict[str, list[int]]:
+ """Build MapColor ID -> [R, G, B] palette for the Map bot (map packet rendering).
+
+ Returns a dict keyed by string IDs ("0", "1", ...) to keep JSON simple.
+ """
+ text = map_color_java.read_text()
+ palette: dict[str, list[int]] = {}
+
+ pattern = re.compile(
+ r'new\s+MapColor\(\s*(\d+)\s*,\s*(\d+)\s*\)')
+ for m in pattern.finditer(text):
+ cid = int(m.group(1))
+ raw = int(m.group(2))
+ r = (raw >> 16) & 0xFF
+ g = (raw >> 8) & 0xFF
+ b = raw & 0xFF
+ palette[str(cid)] = [r, g, b]
+
+ print(f" Built map_palette with {len(palette)} base color entries")
+ return dict(sorted(palette.items(), key=lambda x: int(x[0])))
+
+
def main():
if len(sys.argv) != 2:
print(__doc__)
@@ -249,12 +271,15 @@ def main():
block_colors = matched
print(f" {len(block_colors)} blocks matched to Material.cs entries")
+ map_palette = build_map_palette(map_color_java)
+
output = {
"version": root.name.replace("-decompiled", "").replace("-client", ""),
"colors": {k: list(v) for k, v in sorted(block_colors.items())},
"transparent": sorted(TRANSPARENT_BLOCKS),
"water": WATER_BLOCKS,
"ice": ICE_BLOCKS,
+ "map_palette": map_palette,
}
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)