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)