Files
test/Assets/PerfectWorld/Scripts/Network/CSNetwork/AUICommon.cs
T
2026-04-10 14:50:40 +07:00

1130 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BrewMonster.Scripts.Chat;
using CSNetwork;
namespace CSNetwork
{
public static class AUICommon
{
public const char AUICOMMON_ITEM_CODE_START = (char)0xE000;
public const char AUICOMMON_ITEM_CODE_END = (char)0xE3FF;
public const int AUIMANAGER_MAX_EMOTIONGROUPS = 32;
public const int MAX_EDITBOX_ITEM_NUM =
AUICOMMON_ITEM_CODE_END - AUICOMMON_ITEM_CODE_START + 1;
public const int MAXNUM_CUSTOM_ITEM = 255;
public enum EditboxItemType
{
enumEIEmotion = 0,
enumEIIvtrlItem,
enumEICoord,
enumEIImage,
enumEIScriptItem,
enumEICustom,
enumEINum = enumEICustom + MAXNUM_CUSTOM_ITEM
}
public static void AUI_ConvertChatString(ref string pszChat, ref char[] pszConv, bool bName)
{
if (string.IsNullOrEmpty(pszChat) || pszConv == null)
return;
int nLen = 0;
for (int i = 0; i < pszChat.Length; i++)
{
char c = pszChat[i];
if (c == '^')
{
if (nLen + 1 < pszConv.Length)
{
pszConv[nLen] = '^';
pszConv[nLen + 1] = '^';
nLen += 2;
}
}
else if (c == '&')
{
if (nLen + 1 < pszConv.Length)
{
pszConv[nLen] = '^';
pszConv[nLen + 1] = '&';
nLen += 2;
}
}
else
{
if (nLen < pszConv.Length)
{
pszConv[nLen] = c;
nLen++;
}
}
}
// kết thúc chuỗi bằng ký tự null
if (nLen < pszConv.Length)
pszConv[nLen] = '\0';
}
public static string FilterEmotionSet(string szText, int nEmotionSet)
{
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
string strOrgText = UnmarshalEditBoxText(szText, itemsSet);
int nCount = itemsSet.GetItemCount();
if (nCount == 0)
return szText;
var it = itemsSet.GetItemIterator();
while (it.MoveNext())
{
EditBoxItemBase pItem = it.Current.Value;
if (pItem == null) continue;
if (pItem.GetType() == EditboxItemType.enumEIEmotion)
{
int nSet = 0;
int nIndex = 0;
UnmarshalEmotionInfo(pItem.GetInfo(), ref nSet, ref nIndex);
pItem.SetInfo(MarshalEmotionInfo(nEmotionSet, nIndex));
}
}
return MarshalEditBoxText(strOrgText, itemsSet);
}
public static string MarshalEmotionInfo(int nEmotionSet, int nIndex)
{
return nEmotionSet.ToString() + ":" + nIndex.ToString();
}
public static void UnmarshalEmotionInfo(string szText, ref int nEmotionSet, ref int nIndex)
{
if (string.IsNullOrEmpty(szText))
return;
var parts = szText.Split(':');
if (parts.Length >= 2)
{
int.TryParse(parts[0], out nEmotionSet);
int.TryParse(parts[1], out nIndex);
}
// a_ClampFloor(nEmotionSet, 0);
if (nEmotionSet < 0)
nEmotionSet = 0;
// a_ClampFloor(nIndex, 0);
if (nIndex < 0)
nIndex = 0;
if (nEmotionSet >= AUIMANAGER_MAX_EMOTIONGROUPS)
nEmotionSet = 0;
}
public static string MarshalEditBoxText(string text, EditBoxItemsSet itemsSet)
{
var sb = new StringBuilder(text.Length * 2);
int start = 0;
for (int i = 0; i < text.Length; i++)
{
char ch = text[i];
if (IsEditboxItemCode(ch))
{
sb.Append(text, start, i - start + 1);
var item = itemsSet.GetItemByChar(ch);
if (item != null)
sb.Append(item.Serialize());
start = i + 1;
}
}
if (start < text.Length)
sb.Append(text, start, text.Length - start);
return sb.ToString();
}
public static bool IsEditboxItemCode(char ch)
{
return ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
}
public static string UnmarshalEditBoxText(string szText, EditBoxItemsSet itemsSet)
{
return UnmarshalEditBoxText(
szText,
itemsSet,
0,
null,
0,
0,
null,
0,
false,
false,
0
);
}
public static string UnmarshalEditBoxText(
string? sztext,
EditBoxItemsSet itemsSet,
int msgIndex,
string ivtrItem,
uint clIvtrItem,
int itemMask,
EditboxScriptItem[]? scriptItems,
int scriptItemCount,
bool underLine,
bool sameColor,
uint clUnderLine)
{
if (sztext == null)
return "";
var scriptInfo = new AUI_UNMARSH_SCRIPTITEM_INFO
{
ScriptItems = scriptItems,
ScriptItemCount = scriptItemCount
};
var underlineInfo = new AUI_UNMARSH_UNDERLINE_INFO
{
UnderLine = underLine,
SameColor = sameColor,
UnderLineColor = clUnderLine
};
return UnmarshalEditBoxTextEx(
sztext,
itemsSet,
msgIndex,
ivtrItem,
clIvtrItem,
itemMask,
scriptInfo,
underlineInfo
);
}
public static string UnmarshalEditBoxTextEx(
string text,
EditBoxItemsSet itemsSet,
int msgIndex,
string ivtrItem,
uint clIvtrItem,
int itemMask,
AUI_UNMARSH_SCRIPTITEM_INFO? scriptInfo,
AUI_UNMARSH_UNDERLINE_INFO? underlineInfo)
{
if (text == null)
return "";
int start = 0;
int i = 0;
int curScriptIndex = 0;
var sb = new StringBuilder();
while (i < text.Length)
{
char ch = text[i];
if (IsEditboxItemCode(ch))
{
if (i > start)
sb.Append(text, start, i - start);
i++;
EditBoxItemBase? item = EditBoxItemBase.Unserialize(text, ref i);
start = i;
if (item != null)
{
if ((itemMask & (1 << (int)item.GetType())) != 0)
{
char newChar = itemsSet.AppendItem(item);
if (newChar != '\0')
{
sb.Append(newChar);
item.SetMsgIndex(msgIndex);
if (underlineInfo != null)
{
item.SetUnderLine(
underlineInfo.UnderLine,
underlineInfo.SameColor,
underlineInfo.UnderLineColor);
}
switch (item.GetType())
{
case EditboxItemType.enumEIIvtrlItem:
item.SetName(ivtrItem);
item.SetColor(clIvtrItem);
break;
case EditboxItemType.enumEIScriptItem:
if (scriptInfo != null &&
curScriptIndex < scriptInfo.ScriptItemCount)
{
var sItem = scriptInfo.ScriptItems![curScriptIndex];
item.SetName(sItem.Name);
item.SetColor(sItem.Color);
var data = sItem.Name;
if (data != null)
item.SetExtraData(sItem.Data, sItem.GetDataSize());
curScriptIndex++;
}
break;
}
}
}
}
}
else
{
i++;
}
}
if (i > start)
sb.Append(text, start, i - start);
return sb.ToString();
}
/// <summary>
/// [Port] CECGameUIMan::FilterInvalidTags (EC_GameUIMan.cpp:6424)
/// Lọc bỏ các tag đặc biệt không hợp lệ trong nội dung chat nhận từ server.
/// - Emotion (biểu cảm): luôn giữ lại.
/// - IvtrItem (link vật phẩm): giữ hoặc loại tùy bFilterItem.
/// - Tất cả loại khác (image, coord...): luôn bị loại bỏ (thay bằng text thuần).
/// </summary>
public static string FilterInvalidTags(string szText, bool bFilterItem)
{
return AUI_FilterEditboxItem(szText, (EditBoxItemBase pItem) =>
{
var type = pItem.GetType();
if (type == EditboxItemType.enumEIEmotion)
return false;
if (type == EditboxItemType.enumEIIvtrlItem)
{
pItem.SetInfo("");
return bFilterItem;
}
return true;
});
}
/// <summary>
/// [Port] CECGameUIMan::AUI_FilterEditboxItem (EC_GameUIMan.cpp:6477)
/// Duyệt qua các EditboxItem trong text, filter trả về true thì item bị loại
/// (thay bằng tên hiển thị dạng text thuần), false thì giữ nguyên.
/// </summary>
public static string AUI_FilterEditboxItem(string szText, Func<EditBoxItemBase, bool> filter)
{
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
string strText = UnmarshalEditBoxText(szText, itemsSet);
int nCount = itemsSet.GetItemCount();
if (nCount == 0)
return szText;
var it = itemsSet.GetItemIterator();
int i = 0;
var itemsToFilter = new List<KeyValuePair<char, string>>();
while (it.MoveNext() && i < nCount)
{
EditBoxItemBase pItem = it.Current.Value;
if (pItem != null && filter(pItem))
{
itemsToFilter.Add(new KeyValuePair<char, string>(it.Current.Key, pItem.GetName()));
}
i++;
}
foreach (var kv in itemsToFilter)
{
strText = AUI_ReplaceEditboxItem(strText, kv.Key, kv.Value);
}
return MarshalEditBoxText(strText, itemsSet);
}
/// <summary>
/// [Port] CECGameUIMan::AUI_ReplaceEditboxItem (EC_GameUIMan.cpp:6431)
/// Thay thế ký tự EditboxItem code trong chuỗi bằng một đoạn text thay thế.
/// </summary>
public static string AUI_ReplaceEditboxItem(string szText, char cItem, string szSubText)
{
if (string.IsNullOrEmpty(szText) || !IsEditboxItemCode(cItem))
return szText ?? "";
var sb = new StringBuilder(szText.Length);
for (int i = 0; i < szText.Length; i++)
{
if (szText[i] == cItem)
sb.Append(szSubText ?? "");
else
sb.Append(szText[i]);
}
return sb.ToString();
}
// =====================================================================
// TMP Rich Text Converters — 将内部item格式转换为TextMeshPro标签
// Pipeline: displayText + EditBoxItemsSet
// → ConvertEmotionsToTMP (表情 → <sprite>)
// → ConvertCoordsToTMP (坐标 → <link>)
// → ConvertIvtrItemsToTMP (物品 → <link>)
// → StripRemainingItemCodes(清理残留)
// =====================================================================
/// <summary>
/// [TMP] 将表情item替换为TMP sprite标签 — Replace emotion items with TMP animated sprite tags.
/// Input: displayText từ UnmarshalEditBoxText (chứa PUA placeholder chars).
/// Các item không phải Emotion được giữ nguyên cho converter tiếp theo xử lý.
/// </summary>
public static string ConvertEmotionsToTMP(string displayText, EditBoxItemsSet itemsSet, IEmotionSpriteMap spriteMap)
{
if (string.IsNullOrEmpty(displayText) || spriteMap == null)
return displayText ?? "";
var sb = new StringBuilder(displayText.Length);
for (int i = 0; i < displayText.Length; i++)
{
char ch = displayText[i];
if (IsEditboxItemCode(ch))
{
var item = itemsSet.GetItemByChar(ch);
if (item != null && item.GetType() == EditboxItemType.enumEIEmotion)
{
int nSet = 0, nIndex = 0;
UnmarshalEmotionInfo(item.GetInfo(), ref nSet, ref nIndex);
if (spriteMap.TryGetSprite(nSet, nIndex, out var info))
sb.Append(EmotionTMPTagBuilder.BuildSpriteTag(info));
}
else
{
sb.Append(ch);
}
}
else
{
sb.Append(ch);
}
}
return sb.ToString();
}
/// <summary>
/// [TMP] 将坐标item替换为TMP可点击link标签 — Replace coord items with TMP link tags.
/// Output: &lt;link="coord:info"&gt;&lt;color=#RRGGBB&gt;name&lt;/color&gt;&lt;/link&gt;
/// </summary>
public static string ConvertCoordsToTMP(string displayText, EditBoxItemsSet itemsSet)
{
if (string.IsNullOrEmpty(displayText))
return displayText ?? "";
var sb = new StringBuilder(displayText.Length);
for (int i = 0; i < displayText.Length; i++)
{
char ch = displayText[i];
if (IsEditboxItemCode(ch))
{
var item = itemsSet.GetItemByChar(ch);
if (item != null && item.GetType() == EditboxItemType.enumEICoord)
{
string colorHex = A3DColorToHex(item.GetColor());
string name = item.GetName() ?? "";
string info = item.GetInfo() ?? "";
sb.AppendFormat("<link=\"coord:{0}\"><color=#{1}>{2}</color></link>",
info, colorHex, name);
}
else
{
sb.Append(ch);
}
}
else
{
sb.Append(ch);
}
}
return sb.ToString();
}
/// <summary>
/// [TMP] 将物品item替换为TMP可点击link标签 — Replace inventory item links with TMP link tags.
/// Output: &lt;link="item:info"&gt;&lt;color=#RRGGBB&gt;&lt;u&gt;name&lt;/u&gt;&lt;/color&gt;&lt;/link&gt;
/// </summary>
public static string ConvertIvtrItemsToTMP(string displayText, EditBoxItemsSet itemsSet)
{
if (string.IsNullOrEmpty(displayText))
return displayText ?? "";
var sb = new StringBuilder(displayText.Length);
for (int i = 0; i < displayText.Length; i++)
{
char ch = displayText[i];
if (IsEditboxItemCode(ch))
{
var item = itemsSet.GetItemByChar(ch);
if (item != null && item.GetType() == EditboxItemType.enumEIIvtrlItem)
{
string colorHex = A3DColorToHex(item.GetColor());
string name = item.GetName() ?? "";
string info = item.GetInfo() ?? "";
sb.AppendFormat("<link=\"item:{0}\"><color=#{1}><u>{2}</u></color></link>",
info, colorHex, name);
}
else
{
sb.Append(ch);
}
}
else
{
sb.Append(ch);
}
}
return sb.ToString();
}
/// <summary>
/// [TMP] 移除剩余的PUA item code字符 — Safety net: strip any remaining PUA item codes
/// not handled by the above converters (e.g. enumEIScriptItem, enumEIImage, custom types).
/// </summary>
public static string StripRemainingItemCodes(string displayText)
{
if (string.IsNullOrEmpty(displayText))
return displayText ?? "";
var sb = new StringBuilder(displayText.Length);
for (int i = 0; i < displayText.Length; i++)
{
if (!IsEditboxItemCode(displayText[i]))
sb.Append(displayText[i]);
}
return sb.ToString();
}
/// <summary>
/// A3DCOLOR (uint, ARGB format) → "RRGGBB" hex string cho TMP color tag.
/// </summary>
public static string A3DColorToHex(uint color)
{
byte r = (byte)((color >> 16) & 0xFF);
byte g = (byte)((color >> 8) & 0xFF);
byte b = (byte)(color & 0xFF);
return $"{r:X2}{g:X2}{b:X2}";
}
}
}
public class AUI_UNMARSH_SCRIPTITEM_INFO
{
public EditboxScriptItem[]? ScriptItems;
public int ScriptItemCount;
}
public class AUI_UNMARSH_UNDERLINE_INFO
{
public bool UnderLine;
public bool SameColor;
public uint UnderLineColor;
}
public class EditBoxItemsSet
{
protected Dictionary<char, EditBoxItemBase> m_Items = new();
protected int[] m_ItemsCount = new int[(int)AUICommon.EditboxItemType.enumEINum];
protected char m_cNextItemChar;
public EditBoxItemsSet()
{
Array.Clear(m_ItemsCount, 0, m_ItemsCount.Length);
m_cNextItemChar = AUICommon.AUICOMMON_ITEM_CODE_START;
}
public EditBoxItemsSet(EditBoxItemsSet itemsset)
{
this.Assign(itemsset);
}
public void Assign(EditBoxItemsSet src)
{
m_Items.Clear();
foreach (var kv in src.m_Items)
{
m_Items[kv.Key] = new EditBoxItemBase(kv.Value);
}
Array.Copy(src.m_ItemsCount, m_ItemsCount, m_ItemsCount.Length);
m_cNextItemChar = src.m_cNextItemChar;
}
public void Release()
{
m_Items.Clear();
Array.Clear(m_ItemsCount, 0, m_ItemsCount.Length);
m_cNextItemChar = AUICommon.AUICOMMON_ITEM_CODE_START;
}
public int GetItemCount()
{
return m_Items.Count;
}
public Dictionary<char, EditBoxItemBase>.Enumerator GetItemIterator()
{
return m_Items.GetEnumerator();
}
public EditBoxItemBase? GetItemByChar(char ch)
{
if (!IsEditboxItemCode(ch))
throw new Exception("Invalid editbox item char");
if (m_Items.TryGetValue(ch, out var item))
return item;
return null;
}
public bool IsEditboxItemCode(char ch)
{
return AUICommon.IsEditboxItemCode(ch);
}
public int GetItemCountByType(AUICommon.EditboxItemType type)
{
return m_ItemsCount[(int)type];
}
public void DelItemByChar(char ch)
{
if (m_Items.TryGetValue(ch, out var item))
{
m_ItemsCount[(int)item.GetType()]--;
m_Items.Remove(ch);
}
}
public char AppendItem(EditBoxItemBase pItem)
{
if (m_Items.Count >= AUICommon.MAX_EDITBOX_ITEM_NUM)
return '\0';
char cur = m_cNextItemChar;
do
{
if (m_Items.ContainsKey(m_cNextItemChar))
{
m_cNextItemChar = EditboxGetNextChar(m_cNextItemChar);
}
else
{
m_Items[m_cNextItemChar] = pItem;
m_ItemsCount[(int)pItem.GetType()]++;
char ret = m_cNextItemChar;
m_cNextItemChar = EditboxGetNextChar(m_cNextItemChar);
return ret;
}
} while (m_cNextItemChar != cur);
return '\0';
}
public char EditboxGetNextChar(char cur)
{
if (cur >= AUICommon.AUICOMMON_ITEM_CODE_END)
return AUICommon.AUICOMMON_ITEM_CODE_START;
else
return (char)(cur + 1);
}
public char AppendItem(AUICommon.EditboxItemType type, uint cl, string szName, string szInfo)
{
// Implement theo logic C++ gốc
throw new NotImplementedException();
}
public int GetTotalExtraDataSize()
{
int sz = 0;
foreach (var item in m_Items.Values)
{
sz += item.GetExtraDataSize();
}
return sz;
}
}
public class EditBoxItemBase
{
protected AUICommon.EditboxItemType m_type;
protected uint m_dwColor;
protected string m_strName = "";
protected string m_strInfo = "";
protected int m_nMsgIndex;
protected int m_nImageIndex;
protected int m_nImageFrame;
protected float m_fImageScale;
protected byte[]? m_pExtraData;
protected int m_uExtraDataSize;
protected bool m_bUnderLine;
protected bool m_bSameColor;
protected uint m_dwUnderLineColor;
protected static EditBoxItemBase?[] m_mapCustomType = new EditBoxItemBase[AUICommon.MAXNUM_CUSTOM_ITEM];
public EditBoxItemBase(AUICommon.EditboxItemType type)
{
m_type = type;
m_dwColor = 0xffffffff;
m_nMsgIndex = 0;
m_nImageIndex = 0;
m_nImageFrame = 0;
m_fImageScale = 1.0f;
m_bUnderLine = false;
m_bSameColor = true;
m_dwUnderLineColor = 0;
RegisterCustomType(type);
}
public EditBoxItemBase(EditBoxItemBase src)
{
Assign(src);
}
public bool UnserializeContent(string text, ref int index)
{
int szNext;
int szEnd = text.IndexOf("<", index);
if (szEnd == -1)
return false;
if (m_type == AUICommon.EditboxItemType.enumEICoord)
{
szNext = szEnd + 1;
if (!TryParseInt(text, szNext, out int color))
return false;
SetColor((uint)color);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int underline))
return false;
m_bUnderLine = underline != 0;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int underlineColor))
return false;
m_dwUnderLineColor = (uint)underlineColor;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
m_bSameColor = (m_dwUnderLineColor == m_dwColor);
szNext = szEnd + 2;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
SetName(text.Substring(szNext, szEnd - szNext));
szEnd += 1;
}
else if (m_type == AUICommon.EditboxItemType.enumEIImage)
{
szNext = szEnd + 1;
if (!TryParseUInt(text, szNext, out uint color))
return false;
SetColor(color);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int imageIndex))
return false;
SetImageIndex(imageIndex);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int frame))
return false;
SetImageFrame(frame);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!float.TryParse(text.Substring(szNext), out float f))
return false;
SetImageScale(f);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szEnd += 1;
}
else if (m_type == AUICommon.EditboxItemType.enumEIEmotion)
{
SetName("W");
}
szNext = szEnd + 1;
szEnd = text.IndexOf('>', szNext);
if (szEnd == -1)
return false;
SetInfo(text.Substring(szNext, szEnd - szNext));
index = szEnd + 1;
return true;
}
private bool TryParseInt(string text, int start, out int value)
{
int end = start;
while (end < text.Length && char.IsDigit(text[end]))
end++;
return int.TryParse(text.Substring(start, end - start), out value);
}
private bool TryParseUInt(string text, int start, out uint value)
{
int end = start;
while (end < text.Length && char.IsDigit(text[end]))
end++;
return uint.TryParse(text.Substring(start, end - start), out value);
}
protected void RegisterCustomType(AUICommon.EditboxItemType type)
{
if (type >= AUICommon.EditboxItemType.enumEICustom &&
type < AUICommon.EditboxItemType.enumEINum)
{
int index = (int)type - (int)AUICommon.EditboxItemType.enumEICustom;
if (m_mapCustomType[index] == null)
m_mapCustomType[index] = this;
}
}
protected virtual EditBoxItemBase Create()
{
return new EditBoxItemBase(m_type);
}
protected static EditBoxItemBase? GetCustomItemFromType(AUICommon.EditboxItemType type)
{
if (type >= AUICommon.EditboxItemType.enumEICustom &&
type < AUICommon.EditboxItemType.enumEINum)
{
return m_mapCustomType[(int)type - (int)AUICommon.EditboxItemType.enumEICustom];
}
return null;
}
public uint GetColor() => m_dwColor;
public AUICommon.EditboxItemType GetType() => m_type;
public string GetName() => m_strName;
public string GetInfo() => m_strInfo;
public int GetMsgIndex() => m_nMsgIndex;
public int GetImageIndex() => m_nImageIndex;
public int GetImageFrame() => m_nImageFrame;
public float GetImageScale() => m_fImageScale;
public bool GetUnderLine() => m_bUnderLine;
public bool GetSameColor() => m_bSameColor;
public uint GetUnderLineColor() => m_dwUnderLineColor;
public void SetColor(uint cl) => m_dwColor = cl;
public void SetName(string name) => m_strName = name;
public void SetInfo(string info) => m_strInfo = info;
public void SetMsgIndex(int n) => m_nMsgIndex = n;
public void SetImageIndex(int n) => m_nImageIndex = n;
public void SetImageFrame(int n) => m_nImageFrame = n;
public void SetImageScale(float f) => m_fImageScale = f;
public byte[]? GetExtraData() => m_pExtraData;
public int GetExtraDataSize() => m_uExtraDataSize;
public void SetUnderLine(bool underline, bool sameColor = true, uint underlineColor = 0)
{
m_bUnderLine = underline;
m_bSameColor = sameColor;
m_dwUnderLineColor = underlineColor;
}
public void SetExtraData(byte[] data, int size)
{
m_pExtraData = new byte[size];
Array.Copy(data, 0, m_pExtraData, 0, size);
m_uExtraDataSize = size;
}
/// <summary>
/// [Port] EditBoxItemBase::Serialize — AUICommon.cpp — wire payload sau mỗi ký tự placeholder PUA.
/// [Port] EditBoxItemBase::Serialize — AUICommon.cpp — wire payload after each PUA placeholder character.
/// </summary>
public virtual string Serialize()
{
if (m_type == AUICommon.EditboxItemType.enumEICoord)
{
uint ul = m_bSameColor ? m_dwColor : m_dwUnderLineColor;
return $"<{(int)m_type}><{m_dwColor}><{(m_bUnderLine ? 1 : 0)}><{ul}><{m_strName}><{m_strInfo}>";
}
if (m_type == AUICommon.EditboxItemType.enumEIImage)
{
return $"<{(int)m_type}><{m_dwColor}><{m_nImageIndex}><{m_nImageFrame}><{m_fImageScale.ToString(System.Globalization.CultureInfo.InvariantCulture)}><{m_strInfo}>";
}
return $"<{(int)m_type}><{m_strInfo}>";
}
public static EditBoxItemBase Unserialize(string text, ref int index)
{
int start = text.IndexOf('<', index);
if (start == -1)
return null;
start++;
int endType = text.IndexOf('>', start);
if (endType == -1)
return null;
string typeStr = text.Substring(start, endType - start);
if (!int.TryParse(typeStr, out int type))
return null;
if (type < 0 || type >= (int)AUICommon.EditboxItemType.enumEINum)
return null;
index = endType + 1;
EditBoxItemBase pItem = EditBoxItemBase.GetCustomItemFromType((AUICommon.EditboxItemType)type);
EditBoxItemBase pItemNew;
if (pItem == null)
{
pItemNew = new EditBoxItemBase((AUICommon.EditboxItemType)type);
}
else
{
pItemNew = pItem.Create();
}
if (!pItemNew.UnserializeContent(text, ref index))
{
return null;
}
return pItemNew;
}
public void Assign(EditBoxItemBase src)
{
m_type = src.m_type;
m_dwColor = src.m_dwColor;
m_strName = src.m_strName;
m_strInfo = src.m_strInfo;
m_nMsgIndex = src.m_nMsgIndex;
m_nImageIndex = src.m_nImageIndex;
m_nImageFrame = src.m_nImageFrame;
m_fImageScale = src.m_fImageScale;
if (src.m_pExtraData != null)
{
SetExtraData(src.m_pExtraData, src.m_uExtraDataSize);
}
else
{
m_pExtraData = null;
m_uExtraDataSize = 0;
}
RegisterCustomType(m_type);
}
}
public class EditboxScriptItem
{
public string Name { get; set; } = "";
public uint Color { get; set; }
public byte[]? Data { get; private set; }
public void SetData(byte[] data)
{
Data = data.ToArray(); // deep copy
}
public int GetDataSize()
{
return Data?.Length ?? 0;
}
}
/// <summary>
/// 表情sprite信息 — Describes how one emotion maps to a TMP_SpriteAsset sprite (or animation).
/// </summary>
public struct EmotionSpriteInfo
{
/// <summary>
/// Tên TMP_SpriteAsset cần dùng cho emoji này. Rỗng/null = dùng Sprite Asset mặc định trên text component.
/// TMP_SpriteAsset name to target for this emoji. Empty/null = use text component default sprite asset.
/// </summary>
public string SpriteAssetName;
/// <summary>TMP sprite index (frame đầu tiên nếu có animation).</summary>
public int SpriteIndex;
/// <summary>True nếu emotion này là icon động (nhiều frame).</summary>
public bool IsAnimated;
/// <summary>Index frame cuối (inclusive). Chỉ dùng khi IsAnimated = true.</summary>
public int AnimEndFrame;
/// <summary>Frames per second. Chỉ dùng khi IsAnimated = true.</summary>
public int AnimFPS;
/// <summary>
/// Nếu true: dùng &lt;sprite name="SpriteName"&gt; (sprite phải có trong Sprite Asset gán trên TMP).
/// If true: emit &lt;sprite name="SpriteName"&gt; (sprite must exist on the TextMeshPro sprite asset).
/// </summary>
public bool UseSpriteName;
/// <summary>Tên sub-sprite Unity (vd. cell_0012) — khớp TMP Sprite Asset.</summary>
public string SpriteName;
}
/// <summary>
/// Interface ánh xạ (emotionSet, emotionIndex) → TMP sprite info.
/// Implement interface này khi đã có TMP_SpriteAsset (atlas emotion icons).
///
/// Ví dụ sử dụng:
/// <code>
/// public class MyEmotionMap : IEmotionSpriteMap
/// {
/// public bool TryGetSprite(int set, int index, out EmotionSpriteInfo info)
/// {
/// int spriteStart = set * 30 + index * 4; // 30 emotions/set, 4 frames mỗi emotion
/// info = new EmotionSpriteInfo
/// {
/// SpriteIndex = spriteStart,
/// IsAnimated = true,
/// AnimEndFrame = spriteStart + 3,
/// AnimFPS = 10
/// };
/// return true;
/// }
/// }
/// </code>
/// </summary>
public interface IEmotionSpriteMap
{
/// <summary>
/// Tra bảng (emotionSet, emotionIndex) → EmotionSpriteInfo.
/// Trả về false nếu không tìm thấy (emotion sẽ bị bỏ qua khi render).
/// </summary>
bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info);
}