mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Add chat formatting code propagation (bold, italic, underline, strikethrough, obfuscated)
Re-implements PR #2851 against the current System.Text.Json.Nodes codebase. Changes: - ChatParser.cs: Add FormattingCodes dict, update JSONData2String to propagate formatting codes alongside colors, update NbtToString for the NBT chat path (used in MC 1.20.4+), fix root-string shortcut to preserve formatting prefix - DiscordBridge.cs: Add GetDiscordText() converting § codes to Discord Markdown, handle unclosed formatting codes with end-of-string matching End-to-end tested against Minecraft 1.21.11 (MC 26.1) offline server. All 8 formatting types verified: bold(§l), italic(§o), strikethrough(§m), underline(§n), combined, nested, reset-via-false, obfuscated(§k). Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/65b0d230-287c-4ed8-9b3c-a5ea25411804
This commit is contained in:
parent
16a3ad2b69
commit
3fb7625b39
2 changed files with 123 additions and 78 deletions
|
|
@ -429,14 +429,31 @@ namespace MinecraftClient.Protocol.Message
|
|||
else return "[" + rulename + "] " + string.Join(" ", using_data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mapping from JSON/NBT property names to Minecraft formatting codes (without §).
|
||||
/// Both "underlined" (canonical Minecraft name) and "underline" (alias) are supported.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string> FormattingCodes = new()
|
||||
{
|
||||
{ "obfuscated", "k" },
|
||||
{ "bold", "l" },
|
||||
{ "strikethrough", "m" },
|
||||
{ "underlined", "n" },
|
||||
{ "underline", "n" },
|
||||
{ "italic", "o" },
|
||||
};
|
||||
|
||||
/// <summary>Matches a single color code (§0–§9, §a–§f). Used to strip color when replacing.</summary>
|
||||
private static readonly Regex ColorCodeRegex = new(@"§[0-9a-f]", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Use a JSON Object to build the corresponding string
|
||||
/// </summary>
|
||||
/// <param name="data">JSON object to convert</param>
|
||||
/// <param name="colorcode">Allow parent color code to affect child elements (set to "" for function init)</param>
|
||||
/// <param name="formatting">Inherited formatting codes from parent elements (set to "" for function init)</param>
|
||||
/// <param name="links">Container for links from JSON serialized text</param>
|
||||
/// <returns>returns the Minecraft-formatted string</returns>
|
||||
private static string JSONData2String(System.Text.Json.Nodes.JsonNode? data, string colorcode, List<string>? links)
|
||||
private static string JSONData2String(System.Text.Json.Nodes.JsonNode? data, string formatting, List<string>? links)
|
||||
{
|
||||
string extra_result = "";
|
||||
switch (data)
|
||||
|
|
@ -444,7 +461,20 @@ namespace MinecraftClient.Protocol.Message
|
|||
case System.Text.Json.Nodes.JsonObject obj:
|
||||
if (obj.ContainsKey("color"))
|
||||
{
|
||||
colorcode = Color2tag(JSONData2String(obj["color"], "", links));
|
||||
formatting = ColorCodeRegex.Replace(formatting, "");
|
||||
formatting += Color2tag(JSONData2String(obj["color"], "", links));
|
||||
}
|
||||
|
||||
foreach (var (key, code) in FormattingCodes)
|
||||
{
|
||||
if (obj.ContainsKey(key))
|
||||
{
|
||||
string val = obj[key]!.GetStringValue();
|
||||
if (val == "true")
|
||||
formatting += "§" + code;
|
||||
else if (val == "false")
|
||||
formatting = formatting.Replace("§" + code, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.ContainsKey("clickEvent") && links is not null)
|
||||
|
|
@ -462,12 +492,16 @@ namespace MinecraftClient.Protocol.Message
|
|||
if (obj.ContainsKey("extra"))
|
||||
{
|
||||
foreach (var item in obj["extra"]!.AsArray())
|
||||
extra_result = extra_result + JSONData2String(item, colorcode, links) + "§r";
|
||||
extra_result += JSONData2String(item, "§r" + formatting, links);
|
||||
}
|
||||
|
||||
// Strip any formatting codes that appear before the last §r, since §r resets all
|
||||
// prior formatting. The greedy .* matches up to the last §r in the string.
|
||||
formatting = Regex.Replace(formatting, ".*(§r.*)", "$1");
|
||||
|
||||
if (obj.ContainsKey("text"))
|
||||
{
|
||||
return colorcode + JSONData2String(obj["text"], colorcode, links) + extra_result;
|
||||
return formatting + JSONData2String(obj["text"], formatting, links) + extra_result;
|
||||
}
|
||||
else if (obj.ContainsKey("translate"))
|
||||
{
|
||||
|
|
@ -478,11 +512,11 @@ namespace MinecraftClient.Protocol.Message
|
|||
{
|
||||
foreach (var item in obj["with"]!.AsArray())
|
||||
{
|
||||
using_data.Add(JSONData2String(item, colorcode, links));
|
||||
using_data.Add(JSONData2String(item, formatting, links));
|
||||
}
|
||||
}
|
||||
|
||||
return colorcode +
|
||||
return formatting +
|
||||
TranslateString(JSONData2String(obj["translate"], "", links), using_data) +
|
||||
extra_result;
|
||||
}
|
||||
|
|
@ -492,98 +526,93 @@ namespace MinecraftClient.Protocol.Message
|
|||
string result = "";
|
||||
foreach (var item in arr)
|
||||
{
|
||||
result += JSONData2String(item, colorcode, links);
|
||||
result += JSONData2String(item, formatting, links);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
default:
|
||||
return colorcode + data.GetStringValue();
|
||||
return formatting + data.GetStringValue();
|
||||
}
|
||||
}
|
||||
|
||||
private static string NbtToString(Dictionary<string, object> nbt)
|
||||
private static string NbtToString(Dictionary<string, object> nbt, string formatting = "")
|
||||
{
|
||||
if (nbt.Count == 1 && nbt.TryGetValue("", out object? rootMessage))
|
||||
{
|
||||
return rootMessage?.ToString() ?? string.Empty;
|
||||
return formatting + (rootMessage?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
string message = string.Empty;
|
||||
string colorCode = string.Empty;
|
||||
StringBuilder extraBuilder = new();
|
||||
foreach (var kvp in nbt)
|
||||
|
||||
// Build formatting from color and formatting flags first
|
||||
if (nbt.TryGetValue("color", out object? color))
|
||||
{
|
||||
string key = kvp.Key;
|
||||
object value = kvp.Value;
|
||||
formatting = ColorCodeRegex.Replace(formatting, "");
|
||||
formatting += Color2tag((string)color);
|
||||
}
|
||||
|
||||
switch (key)
|
||||
foreach (var (key, code) in FormattingCodes)
|
||||
{
|
||||
if (nbt.TryGetValue(key, out object? flagValue))
|
||||
{
|
||||
case "text":
|
||||
bool isActive = flagValue switch
|
||||
{
|
||||
message = value?.ToString() ?? string.Empty;
|
||||
}
|
||||
break;
|
||||
case "extra":
|
||||
{
|
||||
object[] extras = (object[])value;
|
||||
for (var i = 0; i < extras.Length; i++)
|
||||
{
|
||||
var extraDict = extras[i] switch
|
||||
{
|
||||
int => new Dictionary<string, object> { { "text", $"{extras[i]}" } },
|
||||
string => new Dictionary<string, object>
|
||||
{
|
||||
{ "text", (string)extras[i] }
|
||||
},
|
||||
_ => (Dictionary<string, object>)extras[i]
|
||||
};
|
||||
|
||||
extraBuilder.Append(NbtToString(extraDict) + "§r");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "translate":
|
||||
{
|
||||
if (nbt.TryGetValue("translate", out object? translate))
|
||||
{
|
||||
var translateKey = (string)translate;
|
||||
List<string> translateString = new();
|
||||
if (nbt.TryGetValue("with", out object? withComponent))
|
||||
{
|
||||
var withs = (object[])withComponent;
|
||||
for (var i = 0; i < withs.Length; i++)
|
||||
{
|
||||
var withDict = withs[i] switch
|
||||
{
|
||||
int => new Dictionary<string, object> { { "text", $"{withs[i]}" } },
|
||||
string => new Dictionary<string, object>
|
||||
{
|
||||
{ "text", (string)withs[i] }
|
||||
},
|
||||
_ => (Dictionary<string, object>)withs[i]
|
||||
};
|
||||
|
||||
translateString.Add(NbtToString(withDict));
|
||||
}
|
||||
}
|
||||
|
||||
message = TranslateString(translateKey, translateString);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "color":
|
||||
{
|
||||
if (nbt.TryGetValue("color", out object? color))
|
||||
{
|
||||
colorCode = Color2tag((string)color);
|
||||
}
|
||||
}
|
||||
break;
|
||||
byte b => b > 0,
|
||||
bool b => b,
|
||||
_ => flagValue?.ToString()?.ToLower() == "true"
|
||||
};
|
||||
if (isActive)
|
||||
formatting += "§" + code;
|
||||
else
|
||||
formatting = formatting.Replace("§" + code, "");
|
||||
}
|
||||
}
|
||||
|
||||
return colorCode + message + extraBuilder.ToString();
|
||||
// Process text
|
||||
if (nbt.TryGetValue("text", out object? textValue))
|
||||
message = textValue?.ToString() ?? string.Empty;
|
||||
|
||||
// Process translate
|
||||
if (nbt.TryGetValue("translate", out object? translate))
|
||||
{
|
||||
var translateKey = (string)translate;
|
||||
List<string> translateString = new();
|
||||
if (nbt.TryGetValue("with", out object? withComponent))
|
||||
{
|
||||
var withs = (object[])withComponent;
|
||||
for (var i = 0; i < withs.Length; i++)
|
||||
{
|
||||
var withDict = withs[i] switch
|
||||
{
|
||||
int => new Dictionary<string, object> { { "text", $"{withs[i]}" } },
|
||||
string => new Dictionary<string, object> { { "text", (string)withs[i] } },
|
||||
_ => (Dictionary<string, object>)withs[i]
|
||||
};
|
||||
translateString.Add(NbtToString(withDict, formatting));
|
||||
}
|
||||
}
|
||||
message = TranslateString(translateKey, translateString);
|
||||
}
|
||||
|
||||
// Process extras, each starting with a reset then inheriting the current formatting
|
||||
if (nbt.TryGetValue("extra", out object? extraValue))
|
||||
{
|
||||
object[] extras = (object[])extraValue;
|
||||
for (var i = 0; i < extras.Length; i++)
|
||||
{
|
||||
var extraDict = extras[i] switch
|
||||
{
|
||||
int => new Dictionary<string, object> { { "text", $"{extras[i]}" } },
|
||||
string => new Dictionary<string, object> { { "text", (string)extras[i] } },
|
||||
_ => (Dictionary<string, object>)extras[i]
|
||||
};
|
||||
extraBuilder.Append(NbtToString(extraDict, "§r" + formatting));
|
||||
}
|
||||
}
|
||||
|
||||
return formatting + message + extraBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue