1119 lines
33 KiB
C#
1119 lines
33 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
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();
|
|
|
|
for (int i = 0; i < nCount; i++)
|
|
{
|
|
EditBoxItemBase pItem = it.Current.Value;
|
|
|
|
if (pItem.GetType() == EditboxItemType.enumEIEmotion)
|
|
{
|
|
int nSet = 0;
|
|
int nIndex = 0;
|
|
|
|
UnmarshalEmotionInfo(pItem.GetInfo(), ref nSet, ref nIndex);
|
|
|
|
pItem.SetInfo(MarshalEmotionInfo(nEmotionSet, nIndex));
|
|
}
|
|
|
|
it.MoveNext();
|
|
}
|
|
|
|
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))
|
|
{
|
|
if (info.UseSpriteName && !string.IsNullOrEmpty(info.SpriteName))
|
|
sb.AppendFormat("<sprite name=\"{0}\">", info.SpriteName);
|
|
else if (info.IsAnimated)
|
|
sb.AppendFormat("<sprite anim=\"{0},{1},{2}\">",
|
|
info.SpriteIndex, info.AnimEndFrame, info.AnimFPS);
|
|
else
|
|
sb.AppendFormat("<sprite index={0}>", info.SpriteIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append(ch);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append(ch);
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// [TMP] 将坐标item替换为TMP可点击link标签 — Replace coord items with TMP link tags.
|
|
/// Output: <link="coord:info"><color=#RRGGBB>name</color></link>
|
|
/// </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: <link="item:info"><color=#RRGGBB><u>name</u></color></link>
|
|
/// </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
|
|
{
|
|
const char AUICOMMON_ITEM_CODE_START = '\u0001';
|
|
const char AUICOMMON_ITEM_CODE_END = '\u0010';
|
|
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 ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
|
|
}
|
|
|
|
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_ITEM_CODE_END)
|
|
return 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;
|
|
}
|
|
|
|
public virtual string Serialize()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
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>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 <sprite name="SpriteName"> (sprite phải có trong Sprite Asset gán trên TMP).
|
|
/// If true: emit <sprite name="SpriteName"> (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);
|
|
}
|