mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
fix: Support hex RGB colors (#RRGGBB) in chat messages (fixes #2054)
Minecraft 1.16+ servers can send custom hex colors in JSON text
components ("color": "#RRGGBB"). MCC's ChatParser.Color2tag() only
recognized the 16 named colors, silently dropping hex values and
leaving gradient/custom-colored chat as uncolored plain text.
- ChatParser: recognize hex color values and emit internal §#rrggbb
encoding; extend ColorCodeRegex to match the new format
- ClassicConsoleBackend: resolve §#rrggbb to ANSI escape codes via
ColorHelper before passing to ConsoleInteractive (adapts to the
configured ConsoleColorMode: 24-bit, 8-bit, 4-bit, or disable)
- McColorParser (TUI): parse §#rrggbb into exact SolidColorBrush
for full RGB fidelity in Avalonia
- ChatBot.GetVerbatim(): skip the full 8-char §#rrggbb sequence
instead of only 2 chars, preventing hex digits from leaking into
stripped text
Made-with: Cursor
This commit is contained in:
parent
914c56fc6e
commit
7fd70ccf38
4 changed files with 80 additions and 6 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using static MinecraftClient.Settings.ConsoleConfigHealper.ConsoleConfig;
|
||||||
|
|
||||||
namespace MinecraftClient
|
namespace MinecraftClient
|
||||||
{
|
{
|
||||||
|
|
@ -26,11 +28,29 @@ namespace MinecraftClient
|
||||||
ConsoleInteractive.ConsoleWriter.WriteLine(text);
|
ConsoleInteractive.ConsoleWriter.WriteLine(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Regex HexColorRegex = new(@"§#([0-9a-fA-F]{6})", RegexOptions.Compiled);
|
||||||
|
|
||||||
public void WriteLineFormatted(string text)
|
public void WriteLineFormatted(string text)
|
||||||
{
|
{
|
||||||
|
bool hasHex = text.Contains("§#");
|
||||||
|
if (hasHex)
|
||||||
|
text = ResolveHexColors(text);
|
||||||
ConsoleInteractive.ConsoleWriter.WriteLineFormatted(text);
|
ConsoleInteractive.ConsoleWriter.WriteLineFormatted(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolveHexColors(string text)
|
||||||
|
{
|
||||||
|
var mode = Settings.Config.Console.General.ConsoleColorMode;
|
||||||
|
return HexColorRegex.Replace(text, match =>
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> hex = match.Groups[1].ValueSpan;
|
||||||
|
byte r = Convert.ToByte(hex[..2].ToString(), 16);
|
||||||
|
byte g = Convert.ToByte(hex[2..4].ToString(), 16);
|
||||||
|
byte b = Convert.ToByte(hex[4..6].ToString(), 16);
|
||||||
|
return ColorHelper.GetColorEscapeCode(r, g, b, foreground: true, mode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void BeginReadThread()
|
public void BeginReadThread()
|
||||||
{
|
{
|
||||||
ConsoleInteractive.ConsoleReader.MessageReceived += ForwardMessage;
|
ConsoleInteractive.ConsoleReader.MessageReceived += ForwardMessage;
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,12 @@ namespace MinecraftClient.Protocol.Message
|
||||||
/// <returns>Color code</returns>
|
/// <returns>Color code</returns>
|
||||||
private static string Color2tag(string colorname)
|
private static string Color2tag(string colorname)
|
||||||
{
|
{
|
||||||
return colorname.ToLower() switch
|
string lower = colorname.ToLower();
|
||||||
|
|
||||||
|
if (lower.Length == 7 && lower[0] == '#' && IsHexColor(lower))
|
||||||
|
return "§" + lower;
|
||||||
|
|
||||||
|
return lower switch
|
||||||
{
|
{
|
||||||
#pragma warning disable format // @formatter:off
|
#pragma warning disable format // @formatter:off
|
||||||
|
|
||||||
|
|
@ -227,6 +232,17 @@ namespace MinecraftClient.Protocol.Message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsHexColor(string s)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < s.Length; i++)
|
||||||
|
{
|
||||||
|
char c = s[i];
|
||||||
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specify whether translation rules have been loaded
|
/// Specify whether translation rules have been loaded
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -443,8 +459,8 @@ namespace MinecraftClient.Protocol.Message
|
||||||
{ "italic", "o" },
|
{ "italic", "o" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Matches a single color code (§0–§9, §a–§f). Used to strip color when replacing.</summary>
|
/// <summary>Matches a single color code (§0-§9, §a-§f) or a hex color (§#rrggbb). Used to strip color when replacing.</summary>
|
||||||
private static readonly Regex ColorCodeRegex = new(@"§[0-9a-f]", RegexOptions.Compiled);
|
private static readonly Regex ColorCodeRegex = new(@"§(?:[0-9a-f]|#[0-9a-f]{6})", RegexOptions.Compiled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use a JSON Object to build the corresponding string
|
/// Use a JSON Object to build the corresponding string
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -612,7 +612,7 @@ namespace MinecraftClient.Scripting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove color codes ("§c") from a text message received from the server
|
/// Remove color codes ("§c" and "§#rrggbb") from a text message received from the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetVerbatim(string? text)
|
public static string GetVerbatim(string? text)
|
||||||
{
|
{
|
||||||
|
|
@ -623,10 +623,20 @@ namespace MinecraftClient.Scripting
|
||||||
var data = new char[text.Length];
|
var data = new char[text.Length];
|
||||||
|
|
||||||
for (int i = 0; i < text.Length; i++)
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
if (text[i] != '§')
|
if (text[i] != '§')
|
||||||
|
{
|
||||||
data[idx++] = text[i];
|
data[idx++] = text[i];
|
||||||
|
}
|
||||||
|
else if (i + 1 < text.Length && text[i + 1] == '#' && i + 7 < text.Length)
|
||||||
|
{
|
||||||
|
i += 7; // skip §#rrggbb (8 chars total, loop increments once)
|
||||||
|
}
|
||||||
else
|
else
|
||||||
i++;
|
{
|
||||||
|
i++; // skip §x (2 chars total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new string(data, 0, idx);
|
return new string(data, 0, idx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Documents;
|
using Avalonia.Controls.Documents;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
|
@ -63,6 +64,19 @@ namespace MinecraftClient.Tui
|
||||||
if (i > start)
|
if (i > start)
|
||||||
AddRun(tb, text[start..i], currentColor, bold, italic, underline, strikethrough);
|
AddRun(tb, text[start..i], currentColor, bold, italic, underline, strikethrough);
|
||||||
|
|
||||||
|
if (text[i + 1] == '#' && i + 8 <= text.Length
|
||||||
|
&& TryParseHexColor(text.AsSpan(i + 2, 6), out var hexBrush))
|
||||||
|
{
|
||||||
|
currentColor = hexBrush;
|
||||||
|
bold = false;
|
||||||
|
italic = false;
|
||||||
|
underline = false;
|
||||||
|
strikethrough = false;
|
||||||
|
i += 7;
|
||||||
|
start = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
char code = char.ToLower(text[i + 1]);
|
char code = char.ToLower(text[i + 1]);
|
||||||
|
|
||||||
if (ColorMap.TryGetValue(code, out var brush))
|
if (ColorMap.TryGetValue(code, out var brush))
|
||||||
|
|
@ -108,6 +122,20 @@ namespace MinecraftClient.Tui
|
||||||
return tb;
|
return tb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryParseHexColor(ReadOnlySpan<char> hex, out IBrush brush)
|
||||||
|
{
|
||||||
|
if (hex.Length == 6
|
||||||
|
&& byte.TryParse(hex[..2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out byte r)
|
||||||
|
&& byte.TryParse(hex[2..4], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out byte g)
|
||||||
|
&& byte.TryParse(hex[4..6], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out byte b))
|
||||||
|
{
|
||||||
|
brush = new SolidColorBrush(Color.FromRgb(r, g, b));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
brush = Brushes.White;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddRun(TextBlock tb, string text, IBrush color,
|
private static void AddRun(TextBlock tb, string text, IBrush color,
|
||||||
bool bold, bool italic, bool underline, bool strikethrough)
|
bool bold, bool italic, bool underline, bool strikethrough)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue