Minecraft-Console-Client/MinecraftClient/Tui/MinimapColorMap.cs

169 lines
6.6 KiB
C#
Raw Normal View History

2026-03-29 18:47:40 +08:00
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using Avalonia.Media;
using MinecraftClient.Mapping;
namespace MinecraftClient.Tui
{
/// <summary>
/// Maps block Materials to minimap colors using data extracted from Minecraft's
/// official MapColor table. Colors are loaded from the embedded MinimapBlockColors.json
/// resource generated by tools/gen_block_color_map.py.
/// </summary>
public static class MinimapColorMap
{
public static readonly Color WaterColor = Color.FromRgb(64, 64, 255);
public static readonly Color IceColor = Color.FromRgb(160, 160, 255);
public static readonly Color LavaColor = Color.FromRgb(255, 100, 0);
public static readonly Color DefaultColor = Color.FromRgb(60, 60, 60);
public static readonly Color VoidColor = Color.FromRgb(0, 0, 0);
private static readonly FrozenDictionary<Material, Color> ColorTable;
private static readonly FrozenSet<Material> FullyTransparentMats;
private static readonly FrozenSet<Material> WaterMats;
private static readonly FrozenSet<Material> IceMats;
static MinimapColorMap()
{
var colors = new Dictionary<Material, Color>();
var transparent = new HashSet<Material>();
var water = new HashSet<Material>();
var ice = new HashSet<Material>();
try
{
using var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("MinimapBlockColors.json");
if (stream is not null)
{
using var doc = JsonDocument.Parse(stream);
var root = doc.RootElement;
if (root.TryGetProperty("colors", out var colorsEl))
{
foreach (var prop in colorsEl.EnumerateObject())
{
if (!Enum.TryParse<Material>(prop.Name, out var mat))
continue;
var arr = prop.Value;
if (arr.GetArrayLength() < 3) continue;
byte r = (byte)arr[0].GetInt32();
byte g = (byte)arr[1].GetInt32();
byte b = (byte)arr[2].GetInt32();
colors[mat] = Color.FromRgb(r, g, b);
}
}
if (root.TryGetProperty("transparent", out var transEl))
{
foreach (var item in transEl.EnumerateArray())
{
if (Enum.TryParse<Material>(item.GetString(), out var mat))
transparent.Add(mat);
}
}
if (root.TryGetProperty("water", out var waterEl))
{
foreach (var item in waterEl.EnumerateArray())
{
if (Enum.TryParse<Material>(item.GetString(), out var mat))
water.Add(mat);
}
}
if (root.TryGetProperty("ice", out var iceEl))
{
foreach (var item in iceEl.EnumerateArray())
{
if (Enum.TryParse<Material>(item.GetString(), out var mat))
ice.Add(mat);
}
}
}
}
catch (Exception ex)
{
ConsoleIO.WriteLineFormatted($"\u00a7e[Minimap] Failed to load color data: {ex.Message}");
}
if (transparent.Count == 0)
{
transparent.Add(Material.Air);
transparent.Add(Material.CaveAir);
transparent.Add(Material.VoidAir);
}
if (water.Count == 0)
water.Add(Material.Water);
if (ice.Count == 0)
{
ice.Add(Material.Ice);
ice.Add(Material.PackedIce);
ice.Add(Material.BlueIce);
ice.Add(Material.FrostedIce);
}
ColorTable = colors.ToFrozenDictionary();
FullyTransparentMats = transparent.ToFrozenSet();
WaterMats = water.ToFrozenSet();
IceMats = ice.ToFrozenSet();
}
public static bool IsFullyTransparent(Material m) => FullyTransparentMats.Contains(m);
public static bool IsWater(Material m) => WaterMats.Contains(m);
public static bool IsIce(Material m) => IceMats.Contains(m);
public static Color GetBaseColor(Material m)
{
if (m == Material.Lava)
return LavaColor;
return ColorTable.GetValueOrDefault(m, DefaultColor);
}
/// <summary>
/// Apply Minecraft-style height shading. The shade multiplier depends on
/// the height difference between the current block and the block to its north.
/// Vanilla maps use four brightness levels: LOW (180/255), NORMAL (220/255),
/// HIGH (255/255), and LOWEST (135/255). We use NORMAL as baseline and shift
/// up/down based on delta.
/// </summary>
public static Color ApplyHeightShade(Color baseColor, int heightDelta)
{
int multiplier = heightDelta switch
{
> 0 => 255, // higher than neighbor: brightest
0 => 220, // same height: normal
_ => 180, // lower than neighbor: darker
};
byte r = (byte)(baseColor.R * multiplier / 255);
byte g = (byte)(baseColor.G * multiplier / 255);
byte b = (byte)(baseColor.B * multiplier / 255);
return Color.FromRgb(r, g, b);
}
public static Color BlendWaterColor(Color bottomColor, int waterDepth)
{
double alpha = Math.Min(0.85, 0.35 + waterDepth * 0.08);
return Blend(WaterColor, bottomColor, alpha);
}
public static Color BlendIceColor(Color bottomColor)
{
return Blend(IceColor, bottomColor, 0.35);
}
private static Color Blend(Color top, Color bottom, double topAlpha)
{
byte r = (byte)(top.R * topAlpha + bottom.R * (1.0 - topAlpha));
byte g = (byte)(top.G * topAlpha + bottom.G * (1.0 - topAlpha));
byte b = (byte)(top.B * topAlpha + bottom.B * (1.0 - topAlpha));
return Color.FromRgb(r, g, b);
}
}
}