using System.Text.RegularExpressions;
using CSNetwork;
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
///
/// Chat 表情与内联富文本 — Emotion set + TMP conversion for chat (ported from CECGameUIMan::AddChatMessage).
/// Keeps channel emotion filtering and Unmarshal/Convert-to-TMP in one place.
///
public sealed class ChatEmotionDisplayPipeline
{
///
/// Khi thiếu ký tự PUA trước payload, Unmarshal không chạy nhưng vẫn còn <0><set:index> (Serialize).
/// When PUA prefix is missing, Unmarshal skips but <0><set:index> (Serialize) may remain.
///
private static readonly Regex LooseMarshaledEmotionRegex = new Regex(
@"<0><(\d+):(\d+)>",
RegexOptions.Compiled);
private IEmotionSpriteMap _spriteMap = new StubEmotionSpriteMap();
///
/// Wire 正文(服务器 / 输入缓冲)→ TMP 显示用富文本 — 与 CECGameUIMan.AddChatMessage 内转换一致(不含 FilterBadWords)。
/// Wire body (server / input buffer) → TMP rich text for display — same as CECGameUIMan.AddChatMessage (without FilterBadWords).
///
public static string ConvertWireBodyToTmpDisplay(string wireBody, IEmotionSpriteMap map, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return "";
if (map == null)
return wireBody;
var p = new ChatEmotionDisplayPipeline(map);
string filtered = p.ApplyChannelEmotionFilter(wireBody, cEmotion);
return p.ConvertInlineItemsToTmp(filtered);
}
public ChatEmotionDisplayPipeline(IEmotionSpriteMap spriteMap = null)
{
if (spriteMap != null)
_spriteMap = spriteMap;
else
BMLogger.LogWarning("[ChatEmotionDisplayPipeline] spriteMap is null — using StubEmotionSpriteMap. Call SetSpriteMap() or assign EmotionLibrarySpriteMap SO on CECUIManager.");
}
public void SetSpriteMap(IEmotionSpriteMap spriteMap)
{
if (spriteMap == null)
BMLogger.LogWarning("[ChatEmotionDisplayPipeline] SetSpriteMap() called with null — EmotionLibrarySpriteMap SO chưa được gán trên CECUIManager Inspector.");
_spriteMap = spriteMap ?? new StubEmotionSpriteMap();
}
/// C++: FilterEmotionSet — map inline emotion codes to the correct set for this channel.
public string ApplyChannelEmotionFilter(string pszMsg, int cEmotion)
{
return AUICommon.FilterEmotionSet(pszMsg, cEmotion);
}
///
/// TMP: Unmarshal edit-box item codes and convert emotion / coord / inventory links to TMP rich text.
/// Call after when the message is from a player channel.
///
public string ConvertInlineItemsToTmp(string pszMsgAfterBadWordFilter)
{
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
string displayText = AUICommon.UnmarshalEditBoxText(pszMsgAfterBadWordFilter, itemsSet);
string tmpText;
if (itemsSet.GetItemCount() <= 0)
tmpText = pszMsgAfterBadWordFilter;
else
{
tmpText = displayText;
tmpText = AUICommon.ConvertEmotionsToTMP(tmpText, itemsSet, _spriteMap);
tmpText = AUICommon.ConvertCoordsToTMP(tmpText, itemsSet);
tmpText = AUICommon.ConvertIvtrItemsToTMP(tmpText, itemsSet);
tmpText = AUICommon.StripRemainingItemCodes(tmpText);
}
return ReplaceLooseMarshaledEmotionTags(tmpText);
}
///
/// Fallback: wire emotion chỉ còn dạng Serialize <0><set:index> (không qua PUA) → TMP <sprite>.
/// Fallback: emotion wire as Serialize-only <0><set:index> (no PUA) → TMP <sprite>.
///
private string ReplaceLooseMarshaledEmotionTags(string text)
{
if (string.IsNullOrEmpty(text) || _spriteMap == null)
return text;
return LooseMarshaledEmotionRegex.Replace(text, match =>
{
int set = int.Parse(match.Groups[1].Value);
int index = int.Parse(match.Groups[2].Value);
if (_spriteMap.TryGetSprite(set, index, out EmotionSpriteInfo info))
return EmotionTMPTagBuilder.BuildSpriteTag(info);
return match.Value;
});
}
}
}