Files
2026-04-13 11:25:55 +07:00

199 lines
8.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Text;
using System.Text.RegularExpressions;
using CSNetwork;
namespace BrewMonster.Scripts.Chat
{
/// <summary>
/// Chat 输入/协议:wireMarshalEditBoxText)↔ TMP &lt;sprite&gt; 显示。
/// Chat input/protocol: wire (MarshalEditBoxText) ↔ TMP &lt;sprite&gt; display.
/// </summary>
public static class ChatWireTmpCodec
{
/// <summary>
/// Thẻ &lt;sprite …&gt; nội bộ (TMP cho phép &lt;sprite=&quot;asset&quot; …&gt; không có khoảng sau &quot;sprite&quot;).
/// English: Inner &lt;sprite …&gt; tag (TMP allows &lt;sprite=&quot;asset&quot; …&gt; with no space after &quot;sprite&quot;).
/// </summary>
private static readonly Regex InnerSpriteTagRegex = new Regex(@"<sprite[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Một emoji hiển thị trong input = &lt;size&gt; + sprite + &lt;/size&gt; (EmotionTMPTagBuilder).
/// English: One in-game emoji in input = size wrapper + sprite + closing size tag.
/// </summary>
private static readonly Regex EmotionSizedBlockRegex =
new Regex(@"<size=\d+%>\s*<sprite[^>]*>\s*</size>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex OrphanSpriteFragmentRegex = new Regex(@"^\s*sprite\s[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Tạo một đoạn wire marshal cho một emotion (set:index) — gửi server đúng protocol.
/// Build one marshaled wire segment for a single emotion (set:index) — correct server protocol.
/// </summary>
public static string BuildMarshaledEmotionWire(int emotionSet, int emotionIndex)
{
var item = new EditBoxItemBase(AUICommon.EditboxItemType.enumEIEmotion);
item.SetName("W");
item.SetInfo(AUICommon.MarshalEmotionInfo(emotionSet, emotionIndex));
var items = new EditBoxItemsSet();
char c = items.AppendItem(item);
if (c == '\0')
return "";
string display = c.ToString();
return AUICommon.MarshalEditBoxText(display, items);
}
/// <summary>
/// TMP 正文(无频道前缀)→ wire marshal(用于发送)。
/// TMP body text (no channel prefix) → marshaled wire (for sending).
/// </summary>
public static string TmpBodyToWire(string tmpBody, IEmotionSpriteMap map)
{
if (string.IsNullOrEmpty(tmpBody))
return "";
if (map == null)
return tmpBody;
var sb = new StringBuilder(tmpBody.Length);
int last = 0;
foreach (Match block in EmotionSizedBlockRegex.Matches(tmpBody))
{
AppendTmpSegmentToWire(sb, tmpBody, last, block.Index - last, map);
last = block.Index + block.Length;
Match inner = InnerSpriteTagRegex.Match(block.Value);
if (inner.Success && TryMatchSpriteTagToEmotion(map, inner.Value, out int es, out int ei))
sb.Append(BuildMarshaledEmotionWire(es, ei));
else
sb.Append(block.Value);
}
AppendTmpSegmentToWire(sb, tmpBody, last, tmpBody.Length - last, map);
return sb.ToString();
}
/// <summary>
/// Đoạn TMP (không gồm khối &lt;size&gt;…&lt;/size&gt; đã xử lý ở ngoài): chữ thường + &lt;sprite&gt; rời.
/// English: TMP segment: plain text plus bare sprite tags (outside size-wrapped emotion blocks).
/// </summary>
private static void AppendTmpSegmentToWire(StringBuilder sb, string tmpBody, int start, int length, IEmotionSpriteMap map)
{
if (length <= 0)
return;
string gap = tmpBody.Substring(start, length);
int last = 0;
foreach (Match m in InnerSpriteTagRegex.Matches(gap))
{
AppendSanitizedPlainText(sb, gap, last, m.Index - last);
string tag = m.Value;
if (TryMatchSpriteTagToEmotion(map, tag, out int es, out int ei))
sb.Append(BuildMarshaledEmotionWire(es, ei));
else
sb.Append(tag);
last = m.Index + m.Length;
var orphan = OrphanSpriteFragmentRegex.Match(gap, last);
if (orphan.Success)
last += orphan.Length;
}
AppendSanitizedPlainText(sb, gap, last, gap.Length - last);
}
private static void AppendSanitizedPlainText(StringBuilder sb, string source, int start, int length)
{
if (length <= 0)
return;
int end = start + length;
for (int i = start; i < end; i++)
{
char ch = source[i];
if (!AUICommon.IsEditboxItemCode(ch))
sb.Append(ch);
}
}
/// <summary>
/// Nếu charIndex nằm trong một emoji TMP (khối &lt;size&gt;… hoặc thẻ &lt;sprite&gt; rời), trả khoảng xóa [start, end).
/// English: If charIndex is inside one TMP emoji unit, returns delete span [start, end).
/// </summary>
public static bool TryGetSpriteTagRangeContainingCharacterIndex(string text, int charIndex, out int tagStart, out int tagEndExclusive)
{
tagStart = 0;
tagEndExclusive = 0;
if (string.IsNullOrEmpty(text) || charIndex < 0 || charIndex >= text.Length)
return false;
foreach (Match m in EmotionSizedBlockRegex.Matches(text))
{
int end = m.Index + m.Length;
if (charIndex >= m.Index && charIndex < end)
{
tagStart = m.Index;
tagEndExclusive = end;
return true;
}
}
foreach (Match m in InnerSpriteTagRegex.Matches(text))
{
int end = m.Index + m.Length;
if (charIndex >= m.Index && charIndex < end)
{
tagStart = m.Index;
tagEndExclusive = end;
return true;
}
}
return false;
}
/// <summary>
/// Khớp tag &lt;sprite…&gt; với EmotionTMPTagBuilder — so sánh phần sprite bên trong &lt;size&gt;…&lt;/size&gt;.
/// English: Match inner &lt;sprite…&gt; to EmotionTMPTagBuilder output (full tag includes size wrapper).
/// </summary>
public static bool TryMatchSpriteTagToEmotion(IEmotionSpriteMap map, string spriteTag, out int emotionSet, out int emotionIndex)
{
emotionSet = 0;
emotionIndex = 0;
if (map == null || string.IsNullOrEmpty(spriteTag))
return false;
string normalized = spriteTag.Trim();
for (int s = 0; s < AUICommon.AUIMANAGER_MAX_EMOTIONGROUPS; s++)
{
for (int e = 0; e < 512; e++)
{
if (!EmotionTMPTagBuilder.TryBuildEmotionTag(map, s, e, out string built))
continue;
Match builtSprite = InnerSpriteTagRegex.Match(built);
if (!builtSprite.Success)
continue;
if (string.Equals(builtSprite.Value, normalized, StringComparison.OrdinalIgnoreCase))
{
emotionSet = s;
emotionIndex = e;
return true;
}
}
}
return false;
}
/// <summary>
/// Wire → TMP 富文本(FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp)。
/// Wire → TMP rich text (FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp).
/// </summary>
public static string WireBodyToTmpForDisplay(string wireBody, IEmotionSpriteMap map, int cEmotion)
{
return ChatEmotionDisplayPipeline.ConvertWireBodyToTmpDisplay(wireBody, map, cEmotion);
}
}
}