mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
fix: decode modern chat type holders correctly
This commit is contained in:
parent
4a68205f02
commit
3ae1d746fa
7 changed files with 339 additions and 73 deletions
159
MinecraftClient.Tests/ChatTypeHolderTests.cs
Normal file
159
MinecraftClient.Tests/ChatTypeHolderTests.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using MinecraftClient.Protocol.Handlers;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Tests;
|
||||
|
||||
public sealed class ChatTypeHolderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReferenceHolderUsesOneBasedWireIdIn121AndNewer()
|
||||
{
|
||||
var dataTypes = new DataTypes(Protocol18Handler.MC_1_21_Version);
|
||||
var packetData = new Queue<byte>(DataTypes.GetVarInt(2));
|
||||
|
||||
int chatTypeId = ChatParser.ReadChatTypeHolder(
|
||||
dataTypes,
|
||||
packetData,
|
||||
Protocol18Handler.MC_1_21_Version,
|
||||
out var directDecoration);
|
||||
|
||||
Assert.Equal(1, chatTypeId);
|
||||
Assert.Null(directDecoration);
|
||||
Assert.Empty(packetData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegistryIdRemainsUnchangedBefore121()
|
||||
{
|
||||
var dataTypes = new DataTypes(Protocol18Handler.MC_1_20_6_Version);
|
||||
var packetData = new Queue<byte>(DataTypes.GetVarInt(2));
|
||||
|
||||
int chatTypeId = ChatParser.ReadChatTypeHolder(
|
||||
dataTypes,
|
||||
packetData,
|
||||
Protocol18Handler.MC_1_20_6_Version,
|
||||
out var directDecoration);
|
||||
|
||||
Assert.Equal(2, chatTypeId);
|
||||
Assert.Null(directDecoration);
|
||||
Assert.Empty(packetData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectHolderConsumesChatAndNarrationDecorations()
|
||||
{
|
||||
var dataTypes = new DataTypes(Protocol18Handler.MC_1_21_Version);
|
||||
var packetBytes = new List<byte>();
|
||||
packetBytes.AddRange(DataTypes.GetVarInt(0));
|
||||
AddDecoration(packetBytes, dataTypes, "chat.type.text", 0, 2);
|
||||
AddDecoration(packetBytes, dataTypes, "chat.type.text.narrate", 0, 2);
|
||||
var packetData = new Queue<byte>(packetBytes);
|
||||
|
||||
int chatTypeId = ChatParser.ReadChatTypeHolder(
|
||||
dataTypes,
|
||||
packetData,
|
||||
Protocol18Handler.MC_1_21_Version,
|
||||
out var directDecoration);
|
||||
|
||||
Assert.Equal(-1, chatTypeId);
|
||||
Assert.NotNull(directDecoration);
|
||||
Assert.Equal("chat.type.text", directDecoration.TranslationKey);
|
||||
Assert.Equal(
|
||||
[ChatParser.ChatTypeParameter.Sender, ChatParser.ChatTypeParameter.Content],
|
||||
directDecoration.Parameters);
|
||||
Assert.Empty(packetData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegistryDecorationControlsParameterSelectionAndOrdering()
|
||||
{
|
||||
Dictionary<int, ChatParser.MessageType>? originalChatTypes = ChatParser.ChatId2Type;
|
||||
try
|
||||
{
|
||||
ChatParser.ClearChatTypeDecorations();
|
||||
ChatParser.ChatId2Type = [];
|
||||
var chatTypeData = new Dictionary<string, object>
|
||||
{
|
||||
["chat"] = new Dictionary<string, object>
|
||||
{
|
||||
["translation_key"] = "commands.message.display.outgoing",
|
||||
["parameters"] = new object[] { "target", "content" }
|
||||
}
|
||||
};
|
||||
ChatParser.ReadChatType(42, "example:custom_chat", chatTypeData);
|
||||
var message = new ChatMessage(
|
||||
"hello",
|
||||
false,
|
||||
42,
|
||||
Guid.Empty,
|
||||
null,
|
||||
"Alice",
|
||||
"Bob",
|
||||
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
null,
|
||||
false);
|
||||
|
||||
string rendered = ChatParser.ParseSignedChat(message);
|
||||
|
||||
Assert.Equal("You whisper to Bob: hello", rendered);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ChatParser.ClearChatTypeDecorations();
|
||||
ChatParser.ChatId2Type = originalChatTypes;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownTranslationKeyIsUsedAsVanillaFormatPattern()
|
||||
{
|
||||
Dictionary<int, ChatParser.MessageType>? originalChatTypes = ChatParser.ChatId2Type;
|
||||
try
|
||||
{
|
||||
ChatParser.ClearChatTypeDecorations();
|
||||
ChatParser.ChatId2Type = [];
|
||||
var chatTypeData = new Dictionary<string, object>
|
||||
{
|
||||
["chat"] = new Dictionary<string, object>
|
||||
{
|
||||
["translation_key"] = "%s",
|
||||
["parameters"] = new object[] { "sender", "content" }
|
||||
}
|
||||
};
|
||||
ChatParser.ReadChatType(42, "ordinary:custom_chat", chatTypeData);
|
||||
var message = new ChatMessage(
|
||||
"hello",
|
||||
false,
|
||||
42,
|
||||
Guid.Empty,
|
||||
null,
|
||||
"Alice » hello",
|
||||
null,
|
||||
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
null,
|
||||
false);
|
||||
|
||||
string rendered = ChatParser.ParseSignedChat(message);
|
||||
|
||||
Assert.Equal("Alice » hello", rendered);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ChatParser.ClearChatTypeDecorations();
|
||||
ChatParser.ChatId2Type = originalChatTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddDecoration(
|
||||
List<byte> packetBytes,
|
||||
DataTypes dataTypes,
|
||||
string translationKey,
|
||||
params int[] parameters)
|
||||
{
|
||||
packetBytes.AddRange(dataTypes.GetString(translationKey));
|
||||
packetBytes.AddRange(DataTypes.GetVarInt(parameters.Length));
|
||||
foreach (int parameter in parameters)
|
||||
packetBytes.AddRange(DataTypes.GetVarInt(parameter));
|
||||
packetBytes.AddRange(dataTypes.GetNbtTag(new Dictionary<string, object>()));
|
||||
}
|
||||
}
|
||||
|
|
@ -3796,6 +3796,8 @@ namespace MinecraftClient
|
|||
{
|
||||
UpdateKeepAlive();
|
||||
|
||||
Log.Debug(string.Format(Translations.protocol_chat_raw_message, message.content));
|
||||
|
||||
List<string> links = new();
|
||||
string messageText;
|
||||
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
},
|
||||
_ => ChatParser.ChatId2Type
|
||||
};
|
||||
ChatParser.ClearChatTypeDecorations();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -576,7 +577,6 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var isEnchantment = registryId == "minecraft:enchantment";
|
||||
var isDialog = registryId == "minecraft:dialog";
|
||||
|
||||
var availableChats = isChat ? new Dictionary<int, string>() : null;
|
||||
var dimensionIdMap = isDimension ? new Dictionary<int, string>() : null;
|
||||
var attributeIdMap = isAttribute ? new Dictionary<int, string>() : null;
|
||||
var enchantmentIdMap = isEnchantment ? new Dictionary<int, string>() : null;
|
||||
|
|
@ -591,7 +591,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
nbtData = dataTypes.ReadNextNbt(packetData);
|
||||
|
||||
if (isChat)
|
||||
availableChats!.Add(i, entryId);
|
||||
ChatParser.ReadChatType(i, entryId, nbtData);
|
||||
else if (isDimension)
|
||||
{
|
||||
dimensionIdMap!.Add(i, entryId);
|
||||
|
|
@ -611,9 +611,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
handler.OnDialogRegistryData(i, entryId, dialogNbtParser.Parse(nbtData));
|
||||
}
|
||||
|
||||
if (isChat)
|
||||
ChatParser.ReadChatType(availableChats!);
|
||||
else if (isDimension)
|
||||
if (isDimension)
|
||||
{
|
||||
World.SetDimensionIdMap(dimensionIdMap!);
|
||||
if (!handler.GetTerrainEnabled() || !World.HasAnyDimension())
|
||||
|
|
@ -1232,7 +1230,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
// Network Target
|
||||
// net.minecraft.network.message.MessageType.Serialized#write
|
||||
var chatTypeId = dataTypes.ReadNextVarInt(packetData);
|
||||
var chatTypeId = ChatParser.ReadChatTypeHolder(
|
||||
dataTypes, packetData, protocolVersion, out var directChatTypeDecoration);
|
||||
var chatName = dataTypes.ReadNextChat(packetData);
|
||||
var targetName = dataTypes.ReadNextBool(packetData)
|
||||
? dataTypes.ReadNextChat(packetData)
|
||||
|
|
@ -1281,7 +1280,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
ChatMessage chat = new(message, false, chatTypeId, senderUuid, unsignedChatContent,
|
||||
senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult);
|
||||
senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult)
|
||||
{
|
||||
chatTypeDecoration = directChatTypeDecoration
|
||||
};
|
||||
lock (MessageSigningLock)
|
||||
Acknowledge(chat);
|
||||
handler.OnTextReceived(chat);
|
||||
|
|
@ -1345,14 +1347,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
break;
|
||||
case PacketTypesIn.ProfilelessChatMessage:
|
||||
var message_ = dataTypes.ReadNextChat(packetData);
|
||||
var messageType_ = dataTypes.ReadNextVarInt(packetData);
|
||||
var messageType_ = ChatParser.ReadChatTypeHolder(
|
||||
dataTypes, packetData, protocolVersion, out var directProfilelessChatTypeDecoration);
|
||||
var messageName = dataTypes.ReadNextChat(packetData);
|
||||
var targetName_ = dataTypes.ReadNextBool(packetData)
|
||||
? dataTypes.ReadNextChat(packetData)
|
||||
: null;
|
||||
ChatMessage profilelessChat = new(message_, targetName_ ?? messageName, false, messageType_,
|
||||
ChatMessage profilelessChat = new(message_, messageName, false, messageType_,
|
||||
Guid.Empty, true);
|
||||
profilelessChat.isSenderJson = false;
|
||||
profilelessChat.teamName = targetName_;
|
||||
profilelessChat.chatTypeDecoration = directProfilelessChatTypeDecoration;
|
||||
handler.OnTextReceived(profilelessChat);
|
||||
break;
|
||||
case PacketTypesIn.CombatEvent:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ namespace MinecraftClient.Protocol.Message
|
|||
|
||||
public bool? isSignatureLegal;
|
||||
|
||||
internal ChatParser.ChatTypeDecoration? chatTypeDecoration;
|
||||
|
||||
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, string? unsignedContent, string displayName, string? teamName, long timestamp, byte[]? signature, bool isSignatureLegal)
|
||||
{
|
||||
isSignedChat = true;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Text;
|
|||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Handlers;
|
||||
using Tomlet;
|
||||
using Tomlet.Models;
|
||||
using static MinecraftClient.Settings;
|
||||
|
|
@ -36,33 +37,49 @@ namespace MinecraftClient.Protocol.Message
|
|||
|
||||
public static Dictionary<int, MessageType>? ChatId2Type;
|
||||
|
||||
internal enum ChatTypeParameter
|
||||
{
|
||||
Sender,
|
||||
Target,
|
||||
Content
|
||||
}
|
||||
|
||||
internal sealed record ChatTypeDecoration(string TranslationKey, ChatTypeParameter[] Parameters);
|
||||
|
||||
private static readonly Dictionary<int, ChatTypeDecoration> ChatId2Decoration = new();
|
||||
|
||||
internal static void ClearChatTypeDecorations()
|
||||
{
|
||||
ChatId2Decoration.Clear();
|
||||
}
|
||||
|
||||
// Used to store Chat Types in 1.20.6+
|
||||
public static void ReadChatType(Dictionary<int, string> data)
|
||||
public static void ReadChatType(int chatId, string chatName, Dictionary<string, object>? chatTypeData)
|
||||
{
|
||||
var chatTypeDictionary = ChatId2Type ?? new Dictionary<int, MessageType>();
|
||||
|
||||
foreach (var (chatId, chatName) in data)
|
||||
chatTypeDictionary[chatId] = chatName switch
|
||||
{
|
||||
chatTypeDictionary[chatId] = chatName switch
|
||||
{
|
||||
"minecraft:chat" => MessageType.CHAT,
|
||||
"minecraft:emote_command" => MessageType.EMOTE_COMMAND,
|
||||
"minecraft:msg_command_incoming" => MessageType.MSG_COMMAND_INCOMING,
|
||||
"minecraft:msg_command_outgoing" => MessageType.MSG_COMMAND_OUTGOING,
|
||||
"minecraft:say_command" => MessageType.SAY_COMMAND,
|
||||
"minecraft:team_msg_command_incoming" => MessageType.TEAM_MSG_COMMAND_INCOMING,
|
||||
"minecraft:team_msg_command_outgoing" => MessageType.TEAM_MSG_COMMAND_OUTGOING,
|
||||
_ => MessageType.CHAT,
|
||||
};
|
||||
}
|
||||
"minecraft:chat" => MessageType.CHAT,
|
||||
"minecraft:emote_command" => MessageType.EMOTE_COMMAND,
|
||||
"minecraft:msg_command_incoming" => MessageType.MSG_COMMAND_INCOMING,
|
||||
"minecraft:msg_command_outgoing" => MessageType.MSG_COMMAND_OUTGOING,
|
||||
"minecraft:say_command" => MessageType.SAY_COMMAND,
|
||||
"minecraft:team_msg_command_incoming" => MessageType.TEAM_MSG_COMMAND_INCOMING,
|
||||
"minecraft:team_msg_command_outgoing" => MessageType.TEAM_MSG_COMMAND_OUTGOING,
|
||||
_ => MessageType.CHAT,
|
||||
};
|
||||
|
||||
ChatId2Type = chatTypeDictionary;
|
||||
|
||||
if (TryReadChatTypeDecoration(chatTypeData, out var decoration))
|
||||
ChatId2Decoration[chatId] = decoration;
|
||||
else
|
||||
ChatId2Decoration.Remove(chatId);
|
||||
}
|
||||
|
||||
public static void ReadChatType(Dictionary<string, object> registryCodec)
|
||||
{
|
||||
Dictionary<int, MessageType> chatTypeDictionary = ChatId2Type ?? new();
|
||||
|
||||
// Check if the chat type registry is in the correct format
|
||||
if (!registryCodec.ContainsKey("minecraft:chat_type"))
|
||||
{
|
||||
|
|
@ -84,25 +101,80 @@ namespace MinecraftClient.Protocol.Message
|
|||
}
|
||||
|
||||
var chatTypeListNbt = (object[])(((Dictionary<string, object>)registryCodec["minecraft:chat_type"])["value"]);
|
||||
foreach (var (chatName, chatId) in from Dictionary<string, object> chatTypeNbt in chatTypeListNbt
|
||||
let chatName = (string)chatTypeNbt["name"]
|
||||
let chatId = (int)chatTypeNbt["id"]
|
||||
select (chatName, chatId))
|
||||
foreach (Dictionary<string, object> chatTypeNbt in chatTypeListNbt)
|
||||
{
|
||||
chatTypeDictionary[chatId] = chatName switch
|
||||
string chatName = (string)chatTypeNbt["name"];
|
||||
int chatId = (int)chatTypeNbt["id"];
|
||||
var chatTypeData = chatTypeNbt.TryGetValue("element", out var element)
|
||||
? element as Dictionary<string, object>
|
||||
: null;
|
||||
ReadChatType(chatId, chatName, chatTypeData);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int ReadChatTypeHolder(
|
||||
DataTypes dataTypes,
|
||||
Queue<byte> packetData,
|
||||
int protocolVersion,
|
||||
out ChatTypeDecoration? directDecoration)
|
||||
{
|
||||
int encodedId = dataTypes.ReadNextVarInt(packetData);
|
||||
directDecoration = null;
|
||||
|
||||
if (protocolVersion < Protocol18Handler.MC_1_21_Version)
|
||||
return encodedId;
|
||||
|
||||
if (encodedId > 0)
|
||||
return encodedId - 1;
|
||||
|
||||
directDecoration = ReadNetworkChatTypeDecoration(dataTypes, packetData);
|
||||
_ = ReadNetworkChatTypeDecoration(dataTypes, packetData); // Narration decoration
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static ChatTypeDecoration ReadNetworkChatTypeDecoration(
|
||||
DataTypes dataTypes,
|
||||
Queue<byte> packetData)
|
||||
{
|
||||
string translationKey = dataTypes.ReadNextString(packetData);
|
||||
int parameterCount = dataTypes.ReadNextVarInt(packetData);
|
||||
var parameters = new ChatTypeParameter[parameterCount];
|
||||
|
||||
for (int i = 0; i < parameterCount; i++)
|
||||
parameters[i] = (ChatTypeParameter)dataTypes.ReadNextVarInt(packetData);
|
||||
|
||||
_ = dataTypes.ReadNextNbtTag(packetData); // Style
|
||||
return new ChatTypeDecoration(translationKey, parameters);
|
||||
}
|
||||
|
||||
private static bool TryReadChatTypeDecoration(
|
||||
Dictionary<string, object>? chatTypeData,
|
||||
[NotNullWhen(true)] out ChatTypeDecoration? decoration)
|
||||
{
|
||||
decoration = null;
|
||||
if (chatTypeData is null
|
||||
|| !chatTypeData.TryGetValue("chat", out var chat)
|
||||
|| chat is not Dictionary<string, object> chatDecoration
|
||||
|| !chatDecoration.TryGetValue("translation_key", out var translationKey)
|
||||
|| translationKey is not string translationKeyText
|
||||
|| !chatDecoration.TryGetValue("parameters", out var parameters)
|
||||
|| parameters is not object[] parameterList)
|
||||
return false;
|
||||
|
||||
var parsedParameters = new ChatTypeParameter[parameterList.Length];
|
||||
for (int i = 0; i < parameterList.Length; i++)
|
||||
{
|
||||
parsedParameters[i] = parameterList[i] switch
|
||||
{
|
||||
"minecraft:chat" => MessageType.CHAT,
|
||||
"minecraft:emote_command" => MessageType.EMOTE_COMMAND,
|
||||
"minecraft:msg_command_incoming" => MessageType.MSG_COMMAND_INCOMING,
|
||||
"minecraft:msg_command_outgoing" => MessageType.MSG_COMMAND_OUTGOING,
|
||||
"minecraft:say_command" => MessageType.SAY_COMMAND,
|
||||
"minecraft:team_msg_command_incoming" => MessageType.TEAM_MSG_COMMAND_INCOMING,
|
||||
"minecraft:team_msg_command_outgoing" => MessageType.TEAM_MSG_COMMAND_OUTGOING,
|
||||
_ => MessageType.CHAT,
|
||||
"sender" => ChatTypeParameter.Sender,
|
||||
"target" => ChatTypeParameter.Target,
|
||||
"content" => ChatTypeParameter.Content,
|
||||
_ => ChatTypeParameter.Sender
|
||||
};
|
||||
}
|
||||
|
||||
ChatId2Type = chatTypeDictionary;
|
||||
decoration = new ChatTypeDecoration(translationKeyText, parsedParameters);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -147,6 +219,26 @@ namespace MinecraftClient.Protocol.Message
|
|||
string text;
|
||||
List<string> usingData = new();
|
||||
|
||||
ChatTypeDecoration? decoration = message.chatTypeDecoration;
|
||||
if (decoration is null)
|
||||
ChatId2Decoration.TryGetValue(message.chatTypeId, out decoration);
|
||||
|
||||
if (decoration is not null)
|
||||
{
|
||||
foreach (var parameter in decoration.Parameters)
|
||||
{
|
||||
usingData.Add(parameter switch
|
||||
{
|
||||
ChatTypeParameter.Sender => sender,
|
||||
ChatTypeParameter.Target => message.teamName ?? string.Empty,
|
||||
ChatTypeParameter.Content => content,
|
||||
_ => string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
return TranslateString(decoration.TranslationKey, usingData);
|
||||
}
|
||||
|
||||
MessageType chatType;
|
||||
if (message.chatTypeId == -1)
|
||||
chatType = MessageType.RAW_MSG;
|
||||
|
|
@ -557,48 +649,47 @@ namespace MinecraftClient.Protocol.Message
|
|||
RulesInitialized = true;
|
||||
}
|
||||
|
||||
if (TryGetTranslationRule(rulename, out string? rule))
|
||||
{
|
||||
int using_idx = 0;
|
||||
StringBuilder result = new();
|
||||
for (int i = 0; i < rule.Length; i++)
|
||||
{
|
||||
if (rule[i] == '%' && i + 1 < rule.Length)
|
||||
{
|
||||
//Using string or int with %s or %d
|
||||
if (rule[i + 1] == 's' || rule[i + 1] == 'd')
|
||||
{
|
||||
if (using_data.Count > using_idx)
|
||||
{
|
||||
result.Append(using_data[using_idx]);
|
||||
using_idx++;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!TryGetTranslationRule(rulename, out string? rule))
|
||||
rule = rulename;
|
||||
|
||||
//Using specified string or int with %1$s, %2$s...
|
||||
else if (char.IsDigit(rule[i + 1])
|
||||
&& i + 3 < rule.Length && rule[i + 2] == '$'
|
||||
&& (rule[i + 3] == 's' || rule[i + 3] == 'd'))
|
||||
int using_idx = 0;
|
||||
StringBuilder result = new();
|
||||
for (int i = 0; i < rule.Length; i++)
|
||||
{
|
||||
if (rule[i] == '%' && i + 1 < rule.Length)
|
||||
{
|
||||
//Using string or int with %s or %d
|
||||
if (rule[i + 1] == 's' || rule[i + 1] == 'd')
|
||||
{
|
||||
if (using_data.Count > using_idx)
|
||||
{
|
||||
int specified_idx = rule[i + 1] - '1';
|
||||
if (using_data.Count > specified_idx)
|
||||
{
|
||||
result.Append(using_data[specified_idx]);
|
||||
using_idx++;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
result.Append(using_data[using_idx]);
|
||||
using_idx++;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result.Append(rule[i]);
|
||||
//Using specified string or int with %1$s, %2$s...
|
||||
else if (char.IsDigit(rule[i + 1])
|
||||
&& i + 3 < rule.Length && rule[i + 2] == '$'
|
||||
&& (rule[i + 3] == 's' || rule[i + 3] == 'd'))
|
||||
{
|
||||
int specified_idx = rule[i + 1] - '1';
|
||||
if (using_data.Count > specified_idx)
|
||||
{
|
||||
result.Append(using_data[specified_idx]);
|
||||
using_idx++;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
result.Append(rule[i]);
|
||||
}
|
||||
else return "[" + rulename + "] " + string.Join(" ", using_data);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static bool TryGetTranslationRule(string rulename, [NotNullWhen(true)] out string? result)
|
||||
|
|
|
|||
|
|
@ -8296,5 +8296,9 @@ namespace MinecraftClient {
|
|||
get { return ResourceManager.GetString("dialog.render.help_hint", resourceCulture); }
|
||||
}
|
||||
|
||||
internal static string protocol_chat_raw_message {
|
||||
get { return ResourceManager.GetString("protocol.chat.raw_message", resourceCulture); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3127,4 +3127,7 @@ see item details.</value>
|
|||
<data name="dialog.render.help_hint" xml:space="preserve">
|
||||
<value>Use /dialog help for a list of commands.</value>
|
||||
</data>
|
||||
<data name="protocol.chat.raw_message" xml:space="preserve">
|
||||
<value>Raw server chat message: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue