Merge pull request 'feature/chat_01' (#318) from feature/chat_01 into develop

Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/318
This commit is contained in:
cuongnv
2026-04-09 07:57:14 +00:00
52 changed files with 67392 additions and 1059 deletions
+133 -6
View File
@@ -1522,7 +1522,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: -187.8, y: -47.6}
m_AnchoredPosition: {x: -187.8, y: -47.600098}
m_SizeDelta: {x: 191, y: 60}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6965363531247653623
@@ -4825,6 +4825,7 @@ RectTransform:
m_Children:
- {fileID: 8741686998992894603}
- {fileID: 3488899534283412697}
- {fileID: 4668526399359599056}
- {fileID: 9056141770234008732}
- {fileID: 6541409353547558602}
- {fileID: 2907261990866691440}
@@ -6262,6 +6263,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
inputField: {fileID: 9217902013627304316}
chatSystem: {fileID: 0}
_spriteMap: {fileID: 0}
channelButtons: []
--- !u!1 &3544484534608324905
GameObject:
@@ -8700,8 +8702,8 @@ MonoBehaviour:
- {fileID: 2971821658315981769}
- {fileID: 452969679978752531}
- {fileID: 3647934876571221831}
HpItemButton: {fileID: 7382895648940793749}
MpItemButton: {fileID: 7614763976671640739}
HpItemButton: {fileID: 0}
MpItemButton: {fileID: 0}
m_nCurPanel1: 1
m_nCurPanel2: 1
m_bShowAll1: 0
@@ -10710,7 +10712,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0.5}
m_AnchorMax: {x: 0, y: 0.5}
m_AnchoredPosition: {x: 60.559204, y: -126.849976}
m_AnchoredPosition: {x: 60.559204, y: -126.8501}
m_SizeDelta: {x: 101.855, y: 57.3622}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3863452661654338700
@@ -11648,7 +11650,6 @@ MonoBehaviour:
_btnTaskTrace: {fileID: 3253955040536933532}
_taskTraceParent: {fileID: 2578159539417438268}
_btnTeamList: {fileID: 540188344648694736}
_lockviewList: {fileID: 0}
--- !u!1 &7352847439676120744
GameObject:
m_ObjectHideFlags: 0
@@ -18712,7 +18713,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 8435310359341866937, guid: b5a4a3ed5bf0e5a49ba0f89d26e1f36e, type: 3}
propertyPath: m_AnchoredPosition.y
value: -25.335571
value: -25.33545
objectReference: {fileID: 0}
- target: {fileID: 8579427623307909814, guid: b5a4a3ed5bf0e5a49ba0f89d26e1f36e, type: 3}
propertyPath: m_AnchorMax.y
@@ -18948,6 +18949,14 @@ PrefabInstance:
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2621697629504226575, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
propertyPath: miniChatContent
value:
objectReference: {fileID: 3171192075315926561}
- target: {fileID: 2621697629504226575, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
propertyPath: onOpenChatPanelButton
value:
objectReference: {fileID: 1884013949825403952}
- target: {fileID: 2818704151482351807, guid: a531b4b63ab8355419f297fe10d32abe, type: 3}
propertyPath: m_AnchorMax.y
value: 0
@@ -20714,6 +20723,124 @@ RectTransform:
m_CorrespondingSourceObject: {fileID: 4475312012745311020, guid: c82978c3789dad44da354dc354c782b2, type: 3}
m_PrefabInstance: {fileID: 8244659259478137406}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &8343482093857271211
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 3233441867675090637}
m_Modifications:
- target: {fileID: 843061721837273778, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_Name
value: prefab_MiniChat
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchorMax.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
--- !u!114 &1884013949825403952 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 7633421558195200411, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
m_PrefabInstance: {fileID: 8343482093857271211}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &3171192075315926561 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 6901861700741151626, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
m_PrefabInstance: {fileID: 8343482093857271211}
m_PrefabAsset: {fileID: 0}
--- !u!224 &4668526399359599056 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 3676046446549609595, guid: 05206a01b3910384cb9a17c74225d554, type: 3}
m_PrefabInstance: {fileID: 8343482093857271211}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &8508566894086459119
PrefabInstance:
m_ObjectHideFlags: 0
@@ -12,7 +12,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: af2fa24fb63c4aa45bb99a711c857114, type: 3}
m_Name: EmotionLibrarySpriteMap
m_EditorClassIdentifier:
Library: {fileID: 11400000, guid: 3011939e4e9c0ce4e83bc03a748fcf96, type: 2}
TmpSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, type: 2}
Library: {fileID: 11400000, guid: e4fbe1473e80a9c4794327d8bb11a4ab, type: 2}
TmpSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
PreferSpriteNameTag: 1
DefaultAnimFps: 10
@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using CSNetwork;
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
@@ -8,16 +10,44 @@ namespace BrewMonster.Scripts.Chat
/// </summary>
public sealed class ChatEmotionDisplayPipeline
{
/// <summary>
/// Khi thiếu ký tự PUA trước payload, Unmarshal không chạy nhưng vẫn còn &lt;0&gt;&lt;set:index&gt; (Serialize).
/// When PUA prefix is missing, Unmarshal skips but &lt;0&gt;&lt;set:index&gt; (Serialize) may remain.
/// </summary>
private static readonly Regex LooseMarshaledEmotionRegex = new Regex(
@"<0><(\d+):(\d+)>",
RegexOptions.Compiled);
private IEmotionSpriteMap _spriteMap = new StubEmotionSpriteMap();
/// <summary>
/// Wire 正文(服务器 / 输入缓冲)→ TMP 显示用富文本 — 与 CECGameUIMan.AddChatMessage 内转换一致(不含 FilterBadWords)。
/// Wire body (server / input buffer) → TMP rich text for display — same as CECGameUIMan.AddChatMessage (without FilterBadWords).
/// </summary>
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();
}
@@ -35,15 +65,38 @@ namespace BrewMonster.Scripts.Chat
{
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
string displayText = AUICommon.UnmarshalEditBoxText(pszMsgAfterBadWordFilter, itemsSet);
string tmpText;
if (itemsSet.GetItemCount() <= 0)
return pszMsgAfterBadWordFilter;
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);
}
string tmpText = displayText;
tmpText = AUICommon.ConvertEmotionsToTMP(tmpText, itemsSet, _spriteMap);
tmpText = AUICommon.ConvertCoordsToTMP(tmpText, itemsSet);
tmpText = AUICommon.ConvertIvtrItemsToTMP(tmpText, itemsSet);
tmpText = AUICommon.StripRemainingItemCodes(tmpText);
return tmpText;
return ReplaceLooseMarshaledEmotionTags(tmpText);
}
/// <summary>
/// Fallback: wire emotion chỉ còn dạng Serialize &lt;0&gt;&lt;set:index&gt; (không qua PUA) → TMP &lt;sprite&gt;.
/// Fallback: emotion wire as Serialize-only &lt;0&gt;&lt;set:index&gt; (no PUA) → TMP &lt;sprite&gt;.
/// </summary>
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;
});
}
}
}
@@ -0,0 +1,100 @@
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
{
private static readonly Regex SpriteTagRegex = new Regex(@"<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 m in SpriteTagRegex.Matches(tmpBody))
{
sb.Append(tmpBody, 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;
}
sb.Append(tmpBody, last, tmpBody.Length - last);
return sb.ToString();
}
/// <summary>
/// Khớp tag với EmotionTMPTagBuilder — duyệt (set,index) đủ nhỏ.
/// Match tag to EmotionTMPTagBuilder output — brute-force over (set,index) within reasonable bounds.
/// </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;
if (built == normalized)
{
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);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9a9d1336ec810654dae6b1bb4076ae3a
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
namespace BrewMonster.Scripts.Chat.EmotionData
@@ -16,6 +17,11 @@ namespace BrewMonster.Scripts.Chat.EmotionData
public int CellHeight = 32;
public Texture2D SourceAtlas;
public string SourceTxtAssetPath = "";
[Tooltip("TMP_SpriteAsset tương ứng với atlas này (cell_0000…). Bắt buộc cho emoji động (nhiều frame). " +
"TMP_SpriteAsset matching this atlas (cell_0000…). Required for animated emoji (multi-frame).")]
public TMP_SpriteAsset TmpSpriteAsset;
public List<EmotionEntryData> Entries = new List<EmotionEntryData>();
}
@@ -1,120 +1,134 @@
using System;
using TMPro;
using UnityEngine;
namespace BrewMonster.Scripts.Chat.EmotionData
{
/// <summary>
/// Ánh xạ (emotionSet, emotionIndex) từ <see cref="EmotionLibrarySO"/> sang tag TMP.
/// Gán asset này vào <see cref="CECUIManager"/> (field emotion) — <see cref="BrewMonster.UI.CECGameUIMan"/> không phải MonoBehaviour nên không kéo SO trên Inspector được.
/// Maps (emotionSet, emotionIndex) from EmotionLibrarySO to TMP rich-text tag.
///
/// Atlas được cắt trái→phải, trên→dưới, sub-sprite đặt tên cell_XXXX.
/// Số XXXX trong tên chính là index tuyến tính → không cần tra spriteCharacterTable.
/// Atlas is sliced left-to-right, top-to-bottom, named cell_XXXX.
/// The number XXXX in the name IS the linear index → no spriteCharacterTable lookup needed.
///
/// Cách dùng:
/// 1) Tạo <see cref="EmotionLibrarySO"/> bằng Emotion Atlas Converter.
/// 2) Tạo <see cref="TMP_SpriteAsset"/> từ cùng atlas (Window → TextMeshPro → Sprite Importer),
/// đảm bảo tên sub-sprite khớp (vd. cell_0000) với atlas Multiple từ tool.
/// 3) Gán TMP_SpriteAsset vào ô chat <see cref="TMPro.TextMeshProUGUI"/> (Sprite Asset / Additional).
/// 4) Kéo SO này vào field trên GameObject có <see cref="CECUIManager"/> (Awake gọi SetEmotionSpriteMap).
/// 1) Tạo EmotionLibrarySO bằng Emotion Atlas Converter.
/// 2) Gán TMP_SpriteAsset cho từng set vào EmotionSetSnapshot.TmpSpriteAsset trong Library
/// (chỉ cần cho emoji động nhiều frame hoặc khi PreferSpriteNameTag = false).
/// 3) Kéo SO này vào field trên GameObject có CECUIManager (Awake gọi SetEmotionSpriteMap).
/// </summary>
[CreateAssetMenu(fileName = "EmotionLibrarySpriteMap", menuName = "Perfect World/Chat/Emotion Library Sprite Map", order = 2)]
public class EmotionLibrarySpriteMap : ScriptableObject, IEmotionSpriteMap
{
[Tooltip("Dữ liệu emotion đã convert (Entries + FrameSprites).")]
[Tooltip("Dữ liệu emotion đã convert. Emotion data converted by the atlas tool.")]
public EmotionLibrarySO Library;
[Tooltip("Sprite Asset dùng cho chat TMP — cùng tên sub-sprite với atlas (cell_XXXX). Bắt buộc cho emoji động (anim) hoặc khi không dùng name tag.")]
public TMP_SpriteAsset TmpSpriteAsset;
[Tooltip("Nếu true và chỉ 1 frame: thử <sprite name=\"...\"> (không cần tra index). Động (nhiều frame) vẫn cần TmpSpriteAsset để tra index.")]
[Tooltip("Nếu true và emoji chỉ 1 frame: dùng <sprite name=\"cell_XXXX\"> thay vì <sprite index=N>. " +
"If true and single-frame: use <sprite name=\"cell_XXXX\"> instead of <sprite index=N>.")]
public bool PreferSpriteNameTag = true;
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks.")]
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks. " +
"Default FPS for <sprite anim> when it cannot be derived from FrameTicks.")]
public int DefaultAnimFps = 10;
// ─────────────────────────────────────────────────────────────────────
// IEmotionSpriteMap
// ─────────────────────────────────────────────────────────────────────
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
{
info = default;
if (Library == null)
{
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Library == null. Gán EmotionLibrarySO vào field Library.");
return false;
}
EmotionSetSnapshot set = Library.GetSetOrNull(emotionSet);
if (set == null || emotionIndex < 0 || emotionIndex >= set.Entries.Count)
if (set == null)
{
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Không tìm thấy set index={emotionSet}. " +
$"Sets hiện có: [{string.Join(", ", Library.Sets?.ConvertAll(s => s?.EmotionSetIndex.ToString() ?? "null") ?? new System.Collections.Generic.List<string>())}]");
return false;
}
if (emotionIndex < 0 || emotionIndex >= set.Entries.Count)
{
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: emotionIndex={emotionIndex} ngoài range [0, {set.Entries.Count - 1}].");
return false;
}
EmotionEntryData entry = set.Entries[emotionIndex];
Sprite[] frames = entry.FrameSprites;
if (frames == null || frames.Length == 0)
return false;
if (frames.Length == 1)
{
Sprite s0 = frames[0];
if (s0 == null)
return false;
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: entry.FrameSprites null/empty. Chạy lại Emotion Atlas Converter để populate FrameSprites.");
return false;
}
if (PreferSpriteNameTag && !string.IsNullOrEmpty(s0.name))
// Dùng StartPos/NumFrames từ txt (khớp EmotionAtlasConverterCore: cellIndex = StartPos + f).
// Use StartPos/NumFrames from txt (matches EmotionAtlasConverterCore: cellIndex = StartPos + f).
// Không đọc Sprite.name — GetName() chỉ được gọi trên main thread (vd. gói chat từ network thread).
// Do not read Sprite.name — GetName() is main-thread-only (e.g. chat packets on network thread).
int startCell = entry.StartPos;
int frameCount = frames.Length;
if (startCell < 0 || frameCount < 1)
{
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: StartPos={startCell} hoặc không có frame.");
return false;
}
// ── Single frame ──────────────────────────────────────────────────
if (frameCount == 1)
{
if (frames[0] == null)
{
info = new EmotionSpriteInfo
{
UseSpriteName = true,
SpriteName = s0.name,
IsAnimated = false
};
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: frames[0] == null.");
return false;
}
if (PreferSpriteNameTag)
{
// <sprite name="cell_XXXX"> — TMP tra tên trong SpriteAsset được gán trên component.
// <sprite name="cell_XXXX"> — TMP looks up the name in the SpriteAsset on the component.
info = new EmotionSpriteInfo { UseSpriteName = true, SpriteName = FormatCellSpriteName(startCell), IsAnimated = false };
return true;
}
int idx = FindSpriteIndexInTmpAsset(s0.name);
if (idx < 0)
return false;
info = new EmotionSpriteInfo { SpriteIndex = idx, IsAnimated = false };
info = new EmotionSpriteInfo { SpriteIndex = startCell, IsAnimated = false };
return true;
}
if (TmpSpriteAsset == null)
return false;
// ── Multi-frame (animated) ────────────────────────────────────────
int endCell = startCell + frameCount - 1;
int start = FindSpriteIndexInTmpAsset(frames[0]?.name);
int end = FindSpriteIndexInTmpAsset(frames[frames.Length - 1]?.name);
if (start < 0 || end < 0)
return false;
int fps = EstimateFps(entry.FrameTicks, frames.Length);
int fps = EstimateFps(entry.FrameTicks, frameCount);
info = new EmotionSpriteInfo
{
SpriteIndex = start,
IsAnimated = true,
AnimEndFrame = end,
AnimFPS = fps > 0 ? fps : DefaultAnimFps
SpriteIndex = startCell,
IsAnimated = true,
AnimEndFrame = endCell,
AnimFPS = fps > 0 ? fps : DefaultAnimFps
};
return true;
}
private int FindSpriteIndexInTmpAsset(string spriteName)
{
if (TmpSpriteAsset == null || string.IsNullOrEmpty(spriteName))
return -1;
// ─────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────
if (TmpSpriteAsset.spriteInfoList != null)
{
for (int i = 0; i < TmpSpriteAsset.spriteInfoList.Count; i++)
{
var s = TmpSpriteAsset.spriteInfoList[i];
if (s != null && s.name == spriteName)
return i;
}
}
return -1;
}
/// <summary>
/// Tên ô giống tool atlas: "cell_XXXX" (4 chữ số). Không đọc Unity <c>Sprite.name</c>.
/// Same cell naming as atlas tool: "cell_XXXX" (4 digits). Does not read Unity Sprite.name.
/// </summary>
private static string FormatCellSpriteName(int cellIndex) => $"cell_{cellIndex:D4}";
private static int EstimateFps(int[] frameTicks, int numFrames)
{
if (frameTicks == null || numFrames < 2 || frameTicks.Length < numFrames)
return 0;
int last = frameTicks[numFrames - 1];
int first = frameTicks[0];
int span = last - first;
int span = frameTicks[numFrames - 1] - frameTicks[0];
if (span <= 0)
return 0;
return Mathf.Max(1, Mathf.RoundToInt((numFrames - 1) * 60f / span));
@@ -0,0 +1,156 @@
using TMPro;
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
/// <summary>
/// Helper tĩnh: chuyển EmotionSpriteInfo → TMP rich-text tag và insert vào TMP_InputField.
/// Static helper: converts EmotionSpriteInfo → TMP rich-text tag and inserts it into a TMP_InputField.
///
/// Cách dùng điển hình / Typical usage:
/// <code>
/// // Khi người dùng bấm chọn emoji từ picker:
/// // When user taps an emoji in the picker:
/// EmotionTMPTagBuilder.InsertEmotionTag(_chatInputField, _spriteMap, emotionSet, emotionIndex);
///
/// // Hoặc chỉ lấy tag string rồi tự xử lý:
/// // Or just get the tag string and handle it yourself:
/// if (EmotionTMPTagBuilder.TryBuildEmotionTag(_spriteMap, set, idx, out string tag))
/// Debug.Log(tag); // → "<sprite name="cell_0005">" hoặc "<sprite anim="4,7,10">"
/// </code>
/// </summary>
public static class EmotionTMPTagBuilder
{
// ─────────────────────────────────────────────────────────────────────
// Tag builders
// ─────────────────────────────────────────────────────────────────────
/// <summary>
/// Tạo TMP rich-text tag từ EmotionSpriteInfo đã resolve.
/// Build TMP rich-text tag from an already-resolved EmotionSpriteInfo.
///
/// Thứ tự ưu tiên / Priority:
/// 1. UseSpriteName → &lt;sprite name="…"&gt;
/// 2. IsAnimated → &lt;sprite anim="start,end,fps"&gt;
/// 3. (fallback) → &lt;sprite index=N&gt;
/// </summary>
public static string BuildSpriteTag(EmotionSpriteInfo info)
{
if (info.UseSpriteName && !string.IsNullOrEmpty(info.SpriteName))
return $"<sprite name=\"{info.SpriteName}\">";
if (info.IsAnimated)
return $"<sprite anim=\"{info.SpriteIndex},{info.AnimEndFrame},{info.AnimFPS}\">";
return $"<sprite index={info.SpriteIndex}>";
}
/// <summary>
/// Tra bảng spriteMap rồi tạo TMP tag.
/// Lookup spriteMap then build the TMP tag.
/// </summary>
/// <param name="spriteMap">Bảng ánh xạ emotion → sprite. Emotion-to-sprite mapping.</param>
/// <param name="emotionSet">Chỉ số bộ (N trong Emotions{N}). Emotion set index.</param>
/// <param name="emotionIndex">Chỉ số emotion trong bộ (dòng trong .txt). Emotion index within set.</param>
/// <param name="tag">TMP tag được tạo ra nếu thành công. Built TMP tag on success.</param>
/// <returns>true nếu tạo được tag, false nếu không tìm thấy emotion. true if tag was built.</returns>
public static bool TryBuildEmotionTag(IEmotionSpriteMap spriteMap, int emotionSet, int emotionIndex, out string tag)
{
tag = string.Empty;
if (spriteMap == null)
{
Debug.LogWarning("[Cuong] TryBuildEmotionTag spriteMap is null.");
return false;
}
if (!spriteMap.TryGetSprite(emotionSet, emotionIndex, out EmotionSpriteInfo info))
{
Debug.LogWarning("[Cuong] TryBuildEmotionTag.");
return false;
}
tag = BuildSpriteTag(info);
return true;
}
// ─────────────────────────────────────────────────────────────────────
// TMP_InputField helpers
// ─────────────────────────────────────────────────────────────────────
/// <summary>
/// Insert TMP emotion tag vào TMP_InputField tại vị trí caret hiện tại.
/// Insert the TMP emotion tag into a TMP_InputField at the current caret position.
/// </summary>
/// <param name="inputField">InputField đang nhập chat. The active chat input field.</param>
/// <param name="spriteMap">Bảng ánh xạ. Sprite map.</param>
/// <param name="emotionSet">Chỉ số bộ. Emotion set index.</param>
/// <param name="emotionIndex">Chỉ số emotion. Emotion index within set.</param>
/// <returns>true nếu tag đã được insert. true if the tag was inserted.</returns>
public static bool InsertEmotionTag(TMP_InputField inputField, IEmotionSpriteMap spriteMap, int emotionSet, int emotionIndex)
{
if (inputField == null)
{
BMLogger.LogWarning("[Cuong] InsertEmotionTag inputField is null.");
return false;
}
if (!TryBuildEmotionTag(spriteMap, emotionSet, emotionIndex, out string tag))
{
BMLogger.LogWarning($"[Cuong] TryBuildEmotionTag false {emotionSet} {emotionIndex} {tag}");
return false;
}
InsertTagAtCaret(inputField, tag);
return true;
}
/// <summary>
/// Insert tag tại vị trí caret, cập nhật caret sau khi insert.
/// Insert tag at caret position, advance caret after insertion.
/// </summary>
public static void InsertTagAtCaret(TMP_InputField inputField, string tag)
{
if (inputField == null || string.IsNullOrEmpty(tag))
return;
string text = inputField.text ?? string.Empty;
int caret = Mathf.Clamp(inputField.caretPosition, 0, text.Length);
inputField.text = text.Insert(caret, tag);
// Đặt caret sau tag vừa insert — Move caret to end of inserted tag
inputField.caretPosition = caret + tag.Length;
inputField.ForceLabelUpdate();
}
// ─────────────────────────────────────────────────────────────────────
// Diagnostics
// ─────────────────────────────────────────────────────────────────────
/// <summary>
/// Log toàn bộ emotion set/index có trong spriteMap ra Console — dùng debug.
/// Log all emotion set/index entries in spriteMap to Console — for debugging.
/// </summary>
public static void DebugLogAllEntries(IEmotionSpriteMap spriteMap, int maxSets = 10, int maxEmotionsPerSet = 50)
{
if (spriteMap == null)
{
Debug.LogWarning("[Cuong] DebugLogAllEntries spriteMap is null.");
return;
}
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("[EmotionTMPTagBuilder] All entries:");
for (int s = 0; s < maxSets; s++)
{
for (int e = 0; e < maxEmotionsPerSet; e++)
{
if (spriteMap.TryGetSprite(s, e, out EmotionSpriteInfo info))
sb.AppendLine($" set={s} idx={e} → {BuildSpriteTag(info)}");
}
}
BMLogger.Log(sb.ToString());
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 200b07ddd8d07064b9e6896fda84dafe
@@ -1,3 +1,5 @@
using BrewMonster;
/// <summary>
/// Stub实现 — 在没有真实TMP_SpriteAsset(emotion atlas)时使用的占位映射。
/// Stub implementation used when no real TMP_SpriteAsset (emotion atlas) is available yet.
@@ -15,7 +17,7 @@ public class StubEmotionSpriteMap : IEmotionSpriteMap
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
{
int startFrame = (emotionSet * EMOTIONS_PER_SET + emotionIndex) * FRAMES_PER_EMOTION;
BMLogger.Log("[Cuong] TryGetSprite");
info = new EmotionSpriteInfo
{
SpriteIndex = startFrame,
@@ -17,21 +17,14 @@ namespace BrewMonster.Scripts.ChatUI
public class ChatSystemlUI : MonoBehaviour
{
[Header("MiniChat")]
public Button onOpenChatPanelButton;
[Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")]
public RectTransform miniChatContent;
[Tooltip("Null = dùng messagePrefab.")]
public ChatMessageView miniMessagePrefab;
[SerializeField] int miniChatPreviewLines = 5;
[Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")]
[SerializeField] bool miniChatPassThroughClicks = true;
[Header("ChatPanelUI")] public ScrollRect scrollRect;
[Tooltip("Nền/fullscreen block — bật/tắt cùng lúc với chatPanelUIGO (có thể để null).")]
public GameObject BgGameObject;
public GameObject chatPanelUIGO;
public RectTransform content;
public ChatMessageView messagePrefab;
public Button closeChatPanelButton;
public Button closeBGChatPanelButton;
public Button emojiButton;
public Button sendButton;
@@ -49,9 +42,6 @@ namespace BrewMonster.Scripts.ChatUI
private List<ChatMessageData> _messages = new();
private List<ChatMessageView> _visibleViews = new();
private List<ChatMessageData> _filteredMessagesCache = new();
private readonly List<ChatMessageData> _miniFilterBuffer = new();
private readonly List<ChatMessageView> _miniChatViews = new();
private ObjectPool<ChatMessageView> _pool;
private bool _userAtBottom = true;
@@ -74,6 +64,7 @@ namespace BrewMonster.Scripts.ChatUI
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
EventBus.Subscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
_pool = new ObjectPool<ChatMessageView>(
CreateItem,
OnGetItem,
@@ -86,13 +77,20 @@ namespace BrewMonster.Scripts.ChatUI
scrollRect.onValueChanged.AddListener(OnScrollChanged);
SetChatPanelAndBgVisible(false);
}
/// <summary>Bật/tắt panel chat và BG cùng trạng thái (2 GO tách nhưng luồng giống nhau).</summary>
void SetChatPanelAndBgVisible(bool visible)
{
if (chatPanelUIGO != null)
chatPanelUIGO.SetActive(false);
chatPanelUIGO.SetActive(visible);
if (BgGameObject != null)
BgGameObject.SetActive(visible);
}
void OnEnable()
{
RefreshMiniChat();
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf || _pool == null || content == null)
return;
@@ -107,10 +105,10 @@ namespace BrewMonster.Scripts.ChatUI
void WireUiButtons()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.AddListener(OpenChatPanel);
if (closeChatPanelButton != null)
closeChatPanelButton.onClick.AddListener(CloseChatPanel);
if (closeBGChatPanelButton != null)
closeBGChatPanelButton.onClick.AddListener(CloseChatPanel);
if (emojiButton != null)
emojiButton.onClick.AddListener(ToggleEmojiPanel);
if (closeEmojiPanelButton != null)
@@ -121,10 +119,10 @@ namespace BrewMonster.Scripts.ChatUI
private void OnDestroy()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.RemoveListener(OpenChatPanel);
if (closeChatPanelButton != null)
closeChatPanelButton.onClick.RemoveListener(CloseChatPanel);
if (closeBGChatPanelButton != null)
closeBGChatPanelButton.onClick.RemoveListener(CloseChatPanel);
if (emojiButton != null)
emojiButton.onClick.RemoveListener(ToggleEmojiPanel);
if (closeEmojiPanelButton != null)
@@ -135,25 +133,29 @@ namespace BrewMonster.Scripts.ChatUI
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
EventBus.Unsubscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
if (_pool != null)
{
_pool.Clear();
_pool.Dispose();
}
ClearMiniChatViews();
}
private void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
{
if (this == null) return;
_currentFilterChannel = e.channel;
RefreshMiniChat();
if (chatPanelUIGO != null && chatPanelUIGO.activeSelf)
RefreshVisible();
}
void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _)
{
if (this == null) return;
OpenChatPanel();
}
private bool ShouldShowMessage(ChatMessageData data)
{
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
@@ -226,8 +228,6 @@ namespace BrewMonster.Scripts.ChatUI
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
RefreshMiniChat();
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf)
return;
@@ -309,68 +309,6 @@ namespace BrewMonster.Scripts.ChatUI
ScrollToBottom();
}
void RefreshMiniChat()
{
if (miniChatContent == null) return;
_miniFilterBuffer.Clear();
foreach (var msg in _messages)
{
if (ShouldShowMessage(msg))
_miniFilterBuffer.Add(msg);
}
int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count);
int startIdx = _miniFilterBuffer.Count - take;
while (_miniChatViews.Count < take)
{
var v = CreateMiniChatItem();
if (v == null) return;
_miniChatViews.Add(v);
}
while (_miniChatViews.Count > take)
{
var last = _miniChatViews[_miniChatViews.Count - 1];
_miniChatViews.RemoveAt(_miniChatViews.Count - 1);
if (last != null)
Destroy(last.gameObject);
}
for (int i = 0; i < take; i++)
{
var data = _miniFilterBuffer[startIdx + i];
var view = _miniChatViews[i];
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
view.gameObject.SetActive(true);
view.transform.SetSiblingIndex(i);
if (miniChatPassThroughClicks)
DisableGraphicsRaycastUnder(view.transform);
view.Bind(icon, data.message);
}
Canvas.ForceUpdateCanvases();
}
/// <summary>
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
/// </summary>
static void DisableGraphicsRaycastUnder(Transform root)
{
if (root == null) return;
foreach (var g in root.GetComponentsInChildren<Graphic>(true))
g.raycastTarget = false;
}
ChatMessageView CreateMiniChatItem()
{
var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab;
if (prefab == null || miniChatContent == null) return null;
var item = Instantiate(prefab, miniChatContent, false);
return item;
}
public void ScrollToBottom()
{
Canvas.ForceUpdateCanvases();
@@ -387,24 +325,13 @@ namespace BrewMonster.Scripts.ChatUI
_visibleViews.Clear();
_messages.Clear();
ClearMiniChatViews();
}
void ClearMiniChatViews()
{
foreach (var v in _miniChatViews)
{
if (v != null)
Destroy(v.gameObject);
}
_miniChatViews.Clear();
}
public void OnHandlerChatButton()
{
if (chatPanelUIGO == null) return;
bool open = !chatPanelUIGO.activeSelf;
chatPanelUIGO.SetActive(open);
SetChatPanelAndBgVisible(open);
if (open)
RefreshVisible();
@@ -415,7 +342,7 @@ namespace BrewMonster.Scripts.ChatUI
public void OpenChatPanel()
{
if (chatPanelUIGO == null) return;
chatPanelUIGO.SetActive(true);
SetChatPanelAndBgVisible(true);
RefreshVisible();
_chatInput ??= GetComponent<ChatInputHandler>();
@@ -425,8 +352,7 @@ namespace BrewMonster.Scripts.ChatUI
public void CloseChatPanel()
{
if (chatPanelUIGO == null) return;
chatPanelUIGO.SetActive(false);
SetChatPanelAndBgVisible(false);
SetEmojiPanelVisible(false);
}
@@ -454,5 +380,10 @@ namespace BrewMonster.Scripts.ChatUI
}
}
public struct OnEventClearChat{}
public struct OnEventClearChat { }
/// <summary>
/// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; <see cref="ChatSystemlUI"/> subscribe.
/// </summary>
public struct OpenChatPanelRequestedEvent { }
}
@@ -0,0 +1,70 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Một ô emoji trong bảng chọn emoji — One emoji cell in the emoji picker grid.
/// Gán lên prefab Button có Image + (tuỳ chọn) TMP_Text cho tooltip.
/// </summary>
[RequireComponent(typeof(Button))]
public class EmojiButtonCell : MonoBehaviour
{
[SerializeField] Image _icon;
[Tooltip("Tuỳ chọn: hiện hint text (tên emoji). Optional: show hint/tooltip text.")]
public event Action<int, int> OnClicked; // (emotionSet, emotionIndex)
private int _emotionSet;
private int _emotionIndex;
private Button _button;
private void Awake()
{
_button = GetComponent<Button>();
if (_icon == null)
_icon = GetComponentInChildren<Image>(true);
}
private void OnEnable()
{
if (_button != null)
_button.onClick.AddListener(HandleClick);
}
private void OnDisable()
{
if (_button != null)
_button.onClick.RemoveListener(HandleClick);
}
/// <summary>
/// Gán dữ liệu cho ô — Bind emotion data to this cell.
/// </summary>
/// <param name="emotionSet">Chỉ số bộ (N trong Emotions{N}.txt).</param>
/// <param name="emotionIndex">Chỉ số emotion trong bộ (dòng trong .txt).</param>
/// <param name="icon">Sprite frame đầu tiên để hiển thị (có thể null). First frame sprite.</param>
/// <param name="hint">Tên / tooltip. Hint / tooltip text.</param>
public void Bind(int emotionSet, int emotionIndex, Sprite icon, string hint)
{
_emotionSet = emotionSet;
_emotionIndex = emotionIndex;
if (_icon != null)
{
_icon.enabled = icon != null;
_icon.sprite = icon;
}
if (_button != null)
_button.interactable = icon != null;
}
private void HandleClick()
{
OnClicked?.Invoke(_emotionSet, _emotionIndex);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ad6cc9a7d46da8949bae44dfcf3fe03d
@@ -0,0 +1,187 @@
using System.Collections.Generic;
using System.Linq;
using BrewMonster.Scripts.Chat;
using BrewMonster.Scripts.Chat.EmotionData;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// MonoBehaviour gắn trên EmojiPanel — quản lý lưới emoji để người chơi chọn.
/// MonoBehaviour placed on EmojiPanel — manages the emoji grid for player selection.
///
/// Cách dùng trên Inspector:
/// 1) Kéo component này lên EmojiPanel GameObject trong prefab.
/// 2) Gán EmotionLibrarySpriteMap SO (đã build từ Emotion Atlas Converter).
/// 3) Gán GridContent = child "Context" của EmojiPanel.
/// 4) Tạo prefab EmojiButtonCell → gán vào CellPrefab.
/// 5) Gán ChatInputHandler và ChatSystemlUI.
/// </summary>
public class EmojiPickerUI : MonoBehaviour
{
[Header("Emotion Data")]
[Tooltip("SO chứa Library + TMP Sprite Asset. Tạo qua Perfect World → Chat → Emotion Atlas Converter.")]
[SerializeField] EmotionLibrarySpriteMap _emotionSpriteMap;
[Header("Grid")]
[Tooltip("RectTransform chứa các ô emoji (child 'Context' của EmojiPanel). GridLayoutGroup được thêm tự động nếu chưa có.")]
[SerializeField] RectTransform _gridContent;
[Tooltip("Prefab một ô emoji (Button + Image + EmojiButtonCell). Cell prefab.")]
[SerializeField] EmojiButtonCell _cellPrefab;
[Tooltip("Kích thước mỗi ô (pixel). Cell size in pixels.")]
[SerializeField] Vector2 _cellSize = new Vector2(64f, 64f);
[Tooltip("Khoảng cách giữa các ô. Spacing between cells.")]
[SerializeField] Vector2 _cellSpacing = new Vector2(4f, 4f);
[Tooltip("Số cột cố định. Fixed column count.")]
[SerializeField] int _columnCount = 8;
[Header("Wiring")]
[Tooltip("ChatInputHandler để nhét emoji code vào ô nhập liệu.")]
[SerializeField] ChatInputHandler _chatInput;
[Tooltip("ChatSystemlUI để đóng EmojiPanel sau khi chọn.")]
[SerializeField] ChatSystemlUI _chatSystemUI;
private readonly List<EmojiButtonCell> _cells = new();
private bool _built;
private void Awake()
{
TryAutoWireIfNeeded();
}
private void Start()
{
BuildGrid();
}
private void OnEnable()
{
if (!_built)
BuildGrid();
}
/// <summary>
/// Xây dựng lưới emoji từ EmotionLibrarySpriteMap.
/// Build emoji grid from EmotionLibrarySpriteMap.
/// </summary>
public void BuildGrid()
{
ClearGrid();
TryAutoWireIfNeeded();
if (_gridContent == null || _cellPrefab == null)
{
Debug.LogWarning("[Cuong] GridContent hoặc CellPrefab chưa được gán.", this);
return;
}
if (_emotionSpriteMap == null || _emotionSpriteMap.Library == null)
{
Debug.LogWarning("[Cuong] EmotionLibrarySpriteMap chưa được gán hoặc chưa có Library.", this);
return;
}
EnsureGridLayout();
_gridContent.gameObject.SetActive(true);
// Keep UI stable: always render in set index order.
foreach (var set in _emotionSpriteMap.Library.Sets.OrderBy(x => x?.EmotionSetIndex ?? int.MaxValue))
{
if (set == null) continue;
if (set.Entries == null || set.Entries.Count == 0) continue;
for (int i = 0; i < set.Entries.Count; i++)
{
var entry = set.Entries[i];
if (entry == null) continue;
Sprite icon = (entry.FrameSprites != null && entry.FrameSprites.Length > 0)
? entry.FrameSprites[0]
: null;
var cell = Instantiate(_cellPrefab, _gridContent, false);
cell.Bind(set.EmotionSetIndex, i, icon, entry.Hint);
cell.OnClicked += HandleEmojiClicked;
_cells.Add(cell);
}
}
_built = true;
}
private void ClearGrid()
{
foreach (var c in _cells)
{
if (c == null) continue;
c.OnClicked -= HandleEmojiClicked;
Destroy(c.gameObject);
}
_cells.Clear();
_built = false;
}
private void TryAutoWireIfNeeded()
{
_chatInput ??= GetComponentInParent<ChatInputHandler>();
_chatSystemUI ??= GetComponentInParent<ChatSystemlUI>();
}
private void EnsureGridLayout()
{
var layout = _gridContent.GetComponent<GridLayoutGroup>();
if (layout == null)
layout = _gridContent.gameObject.AddComponent<GridLayoutGroup>();
layout.cellSize = _cellSize;
layout.spacing = _cellSpacing;
layout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
layout.constraintCount = _columnCount;
layout.startCorner = GridLayoutGroup.Corner.UpperLeft;
layout.startAxis = GridLayoutGroup.Axis.Horizontal;
layout.childAlignment = TextAnchor.UpperLeft;
var fitter = _gridContent.GetComponent<ContentSizeFitter>();
if (fitter == null)
fitter = _gridContent.gameObject.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
/// <summary>
/// Gọi khi người chơi click một ô emoji — Called when player clicks an emoji cell.
/// Chèn mã emoji vào input field và đóng panel.
/// Inserts the emotion code into the chat input and closes the panel.
/// </summary>
private void HandleEmojiClicked(int emotionSet, int emotionIndex)
{
TryAutoWireIfNeeded();
if (_chatInput != null)
{
EmotionTMPTagBuilder.InsertEmotionTag(_chatInput.inputField, _emotionSpriteMap, emotionSet,
emotionIndex);
Debug.Log("[Cuong] HandleEmojiClicked.");
}
else
{
Debug.LogWarning("[Cuong] Không tìm thấy ChatInputHandler để insert emoji.", this);
}
/*if (_chatSystemUI != null)
{
_chatSystemUI.CloseEmojiPanel();
}*/
}
private void OnDestroy()
{
ClearGrid();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3a53a5ea5bc85bc449775b64714b421f
@@ -0,0 +1,201 @@
using System.Collections.Generic;
using CSNetwork;
using CSNetwork.GPDataType;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Vùng xem trước chat nhỏ; đồng bộ qua EventBus với tin nhắn và bộ lọc kênh.
/// Mở panel chat đầy đủ qua <see cref="OpenChatPanelRequestedEvent"/>.
/// </summary>
public class MiniChatUI : MonoBehaviour
{
[Header("MiniChat")]
public Button onOpenChatPanelButton;
[Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")]
public RectTransform miniChatContent;
[Tooltip("Null = dùng messagePrefab (fallback).")]
public ChatMessageView miniMessagePrefab;
[Tooltip("Prefab dòng tin khi miniMessagePrefab null (giống messagePrefab panel chính).")]
public ChatMessageView messagePrefab;
[SerializeField] int miniChatPreviewLines = 5;
[Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")]
[SerializeField] bool miniChatPassThroughClicks = true;
[SerializeField] int maxStoredMessages = 2000;
[Header("Chat System Data")]
public ChatSystemSO chatSystemSO;
readonly List<ChatMessageData> _messages = new();
readonly List<ChatMessageData> _miniFilterBuffer = new();
readonly List<ChatMessageView> _miniChatViews = new();
Dictionary<byte, Sprite> _iconCache;
ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL;
void Awake()
{
_iconCache = new Dictionary<byte, Sprite>();
if (chatSystemSO != null && chatSystemSO.channelIcons != null)
{
foreach (var mapping in chatSystemSO.channelIcons)
_iconCache[(byte)mapping.channel] = mapping.icon;
}
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
}
void OnEnable()
{
RefreshMiniChat();
}
void Start()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.AddListener(OnOpenChatPanelButtonClicked);
}
void OnDestroy()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.RemoveListener(OnOpenChatPanelButtonClicked);
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
ClearMiniChatViews();
}
void OnOpenChatPanelButtonClicked()
{
EventBus.Publish(new OpenChatPanelRequestedEvent());
}
void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
{
if (this == null) return;
_currentFilterChannel = e.channel;
RefreshMiniChat();
}
bool ShouldShowMessage(ChatMessageData data)
{
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
if (data.channel == (byte)ChatChannel.GP_CHAT_MISC) return true;
return data.channel == (byte)_currentFilterChannel;
}
void OnChatMessageReceived(GameSession.ChatMessageEvent x)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
AddMessage(x.context, x.channel);
});
}
void OnChatMessageClear(OnEventClearChat obj)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
ClearChat();
});
}
void AddMessage(string msg, byte channel)
{
if (this == null) return;
var data = new ChatMessageData { message = msg, channel = channel };
_messages.Add(data);
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
RefreshMiniChat();
}
void RefreshMiniChat()
{
if (miniChatContent == null) return;
_miniFilterBuffer.Clear();
foreach (var msg in _messages)
{
if (ShouldShowMessage(msg))
_miniFilterBuffer.Add(msg);
}
int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count);
int startIdx = _miniFilterBuffer.Count - take;
while (_miniChatViews.Count < take)
{
var v = CreateMiniChatItem();
if (v == null) return;
_miniChatViews.Add(v);
}
while (_miniChatViews.Count > take)
{
var last = _miniChatViews[_miniChatViews.Count - 1];
_miniChatViews.RemoveAt(_miniChatViews.Count - 1);
if (last != null)
Destroy(last.gameObject);
}
for (int i = 0; i < take; i++)
{
var data = _miniFilterBuffer[startIdx + i];
var view = _miniChatViews[i];
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
view.gameObject.SetActive(true);
view.transform.SetSiblingIndex(i);
if (miniChatPassThroughClicks)
DisableGraphicsRaycastUnder(view.transform);
view.Bind(icon, data.message);
}
Canvas.ForceUpdateCanvases();
}
/// <summary>
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
/// </summary>
static void DisableGraphicsRaycastUnder(Transform root)
{
if (root == null) return;
foreach (var g in root.GetComponentsInChildren<Graphic>(true))
g.raycastTarget = false;
}
ChatMessageView CreateMiniChatItem()
{
var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab;
if (prefab == null || miniChatContent == null) return null;
var item = Instantiate(prefab, miniChatContent, false);
return item;
}
void ClearChat()
{
_messages.Clear();
ClearMiniChatViews();
}
void ClearMiniChatViews()
{
foreach (var v in _miniChatViews)
{
if (v != null)
Destroy(v.gameObject);
}
_miniChatViews.Clear();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0c3012f1ec3cb3d4a9dbf4bab742e81c
@@ -2,8 +2,10 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using BrewMonster.Scripts.Chat.EmotionData;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.TextCore;
namespace BrewMonster.Scripts.Editor
{
@@ -331,6 +333,175 @@ namespace BrewMonster.Scripts.Editor
return null;
}
}
// ─────────────────────────────────────────────────────────────────────
// TMP Sprite Asset Generator
// ─────────────────────────────────────────────────────────────────────
/// <summary>
/// Tạo TMP_SpriteAsset từ snapshot đã convert (atlas phải ở chế độ Sprite Multiple).
/// Create a TMP_SpriteAsset from a converted snapshot (atlas must already be Sprite Multiple).
/// </summary>
/// <param name="snapshot">Snapshot bộ emotion đã convert — Converted emotion set snapshot.</param>
/// <param name="outputAssetPath">Đường dẫn lưu .asset (VD: Assets/TextMesh Pro/Resources/Sprite Assets/Emotions 0.asset).</param>
/// <param name="cellSizeInPoints">
/// Kích thước ô tính theo pt TMP (chiều cao glyph). Nếu = 0 thì lấy cellHeight từ snapshot.
/// Cell size in TMP points (glyph height). 0 = use snapshot.CellHeight.
/// </param>
/// <param name="result">TMP_SpriteAsset vừa tạo.</param>
/// <param name="error">Thông báo lỗi nếu thất bại.</param>
public static bool CreateTMPSpriteAsset(
EmotionSetSnapshot snapshot,
string outputAssetPath,
float cellSizeInPoints,
out TMP_SpriteAsset result,
out string error)
{
result = null;
error = null;
if (snapshot?.SourceAtlas == null)
{
error = "Snapshot hoặc SourceAtlas null. Hãy chạy Convert trước.";
return false;
}
string atlasPath = AssetDatabase.GetAssetPath(snapshot.SourceAtlas);
if (string.IsNullOrEmpty(atlasPath))
{
error = "SourceAtlas không phải asset trong project.";
return false;
}
var importer = AssetImporter.GetAtPath(atlasPath) as TextureImporter;
if (importer == null || importer.spriteImportMode != SpriteImportMode.Multiple)
{
error = $"Atlas tại {atlasPath} chưa ở chế độ Sprite Multiple. Hãy chạy Convert trước.";
return false;
}
var atlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(atlasPath);
if (atlasTexture == null)
{
error = $"Không load được Texture2D: {atlasPath}";
return false;
}
var spritesByName = LoadSpritesByName(atlasPath);
if (spritesByName.Count == 0)
{
error = "Không tìm thấy sprite nào trong atlas. Kiểm tra import settings.";
return false;
}
int texW = atlasTexture.width;
int texH = atlasTexture.height;
int cellW = snapshot.CellWidth;
int cellH = snapshot.CellHeight;
if (cellW <= 0 || cellH <= 0)
{
error = "CellWidth/CellHeight trong snapshot phải > 0.";
return false;
}
int nNumX = texW / cellW;
int nNumY = texH / cellH;
int totalCells = nNumX * nNumY;
// Kích thước ô dùng cho TMP metrics (point scale) — Cell size for TMP glyph metrics
float glyphH = cellSizeInPoints > 0f ? cellSizeInPoints : cellH;
float glyphW = cellSizeInPoints > 0f ? cellSizeInPoints * ((float)cellW / cellH) : cellW;
// Dùng TMP 3.x API: TMP_SpriteGlyph + TMP_SpriteCharacter (spriteInfoList đã deprecated)
// Use TMP 3.x API: TMP_SpriteGlyph + TMP_SpriteCharacter (spriteInfoList is deprecated)
var spriteGlyphTable = new List<TMP_SpriteGlyph>(totalCells);
var spriteCharTable = new List<TMP_SpriteCharacter>(totalCells);
for (int i = 0; i < totalCells; i++)
{
string cellName = CellSpriteName(i);
if (!spritesByName.TryGetValue(cellName, out Sprite spr) || spr == null)
continue;
Rect r = spr.textureRect; // pixel rect, origin bottom-left
var glyph = new TMP_SpriteGlyph
{
index = (uint)spriteGlyphTable.Count,
sprite = spr,
glyphRect = new GlyphRect((int)r.x, (int)r.y, (int)r.width, (int)r.height),
metrics = new GlyphMetrics(glyphW, glyphH, 0f, glyphH, glyphW),
scale = 1f,
atlasIndex = 0,
};
spriteGlyphTable.Add(glyph);
var character = new TMP_SpriteCharacter(0, glyph)
{
name = cellName,
scale = 1f,
};
spriteCharTable.Add(character);
}
if (spriteCharTable.Count == 0)
{
error = "Không build được bảng character. Kiểm tra tên sprite dạng cell_XXXX.";
return false;
}
// Tìm TMP Sprite shader — Find TMP Sprite shader
Shader shader = Shader.Find("TextMeshPro/Sprite");
if (shader == null)
{
error = "Không tìm thấy shader 'TextMeshPro/Sprite'. Đảm bảo TMP package đã import đầy đủ.";
return false;
}
string outputDir = Path.GetDirectoryName(outputAssetPath)?.Replace('\\', '/') ?? "Assets";
EnsureFolder(outputDir);
// Material — tên khớp với TMP convention: "<AssetName> Material"
string assetNameNoExt = Path.GetFileNameWithoutExtension(outputAssetPath);
string matPath = $"{outputDir}/{assetNameNoExt} Material.mat";
// Xóa asset cũ nếu tồn tại — Delete existing assets if present
if (AssetDatabase.LoadAssetAtPath<Material>(matPath) != null)
AssetDatabase.DeleteAsset(matPath);
if (AssetDatabase.LoadAssetAtPath<TMP_SpriteAsset>(outputAssetPath) != null)
AssetDatabase.DeleteAsset(outputAssetPath);
var mat = new Material(shader) { name = $"{assetNameNoExt} Material" };
mat.mainTexture = atlasTexture;
AssetDatabase.CreateAsset(mat, matPath);
var spriteAsset = ScriptableObject.CreateInstance<TMP_SpriteAsset>();
spriteAsset.name = assetNameNoExt;
spriteAsset.spriteSheet = atlasTexture;
spriteAsset.material = mat;
//spriteAsset.spriteGlyphTable = spriteGlyphTable;
//spriteAsset.spriteCharacterTable = spriteCharTable;
// Rebuild lookup tables để <sprite name="..."> hoạt động — Rebuild lookup tables for name-based lookup
spriteAsset.UpdateLookupTables();
AssetDatabase.CreateAsset(spriteAsset, outputAssetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
result = spriteAsset;
return true;
}
/// <summary>
/// Tính đường dẫn mặc định của TMP_SpriteAsset cho một bộ emotion.
/// Returns the default TMP_SpriteAsset output path for an emotion set.
/// </summary>
public static string DefaultTMPSpriteAssetPath(string tmpSpriteFolder, int emotionSetIndex)
{
return $"{tmpSpriteFolder.TrimEnd('/')}/Emotions {emotionSetIndex}.asset";
}
}
/// <summary>
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using BrewMonster.Scripts.Chat.EmotionData;
using TMPro;
using UnityEditor;
using UnityEngine;
@@ -23,6 +24,15 @@ namespace BrewMonster.Scripts.Editor
private string _libraryAssetPath = "Assets/PerfectWorld/UI/Chat/GeneratedEmotions/EmotionLibrary.asset";
// ── TMP Sprite Asset ──────────────────────────────────────────────────
private bool _genTmpOnConvert = true;
private string _tmpSpriteFolder = "Assets/TextMesh Pro/Resources/Sprite Assets";
private float _tmpCellSizePt = 0f; // 0 = lấy từ cellH
private bool _showTmpSection = true;
// Snapshot cuối cùng được convert (cho nút gen TMP standalone)
// Last converted snapshot (for standalone TMP gen button)
private EmotionSetSnapshot _lastSnapshot;
[MenuItem("Tools/Perfect World/ChatSystem/Emotion Atlas Converter…")]
public static void Open()
{
@@ -120,6 +130,9 @@ namespace BrewMonster.Scripts.Editor
RunConvertLibrary();
}
EditorGUILayout.Space(12f);
DrawTMPSection();
EditorGUILayout.Space(8f);
EditorGUILayout.HelpBox(
"Mỗi bộ: một texture **Sprite Multiple**, tên sub-sprite `cell_0000` … theo chỉ số ô (giống C++).\n" +
@@ -127,6 +140,53 @@ namespace BrewMonster.Scripts.Editor
MessageType.Info);
}
// ─────────────────────────────────────────────────────────────────────
// TMP Sprite Asset section
// ─────────────────────────────────────────────────────────────────────
private void DrawTMPSection()
{
_showTmpSection = EditorGUILayout.Foldout(_showTmpSection, "TMP Sprite Asset", true, EditorStyles.foldoutHeader);
if (!_showTmpSection) return;
EditorGUI.indentLevel++;
_tmpSpriteFolder = EditorGUILayout.TextField(
new GUIContent("Sprite Assets folder",
"Thư mục lưu TMP_SpriteAsset (.asset). Mặc định: Assets/TextMesh Pro/Resources/Sprite Assets\n" +
"Folder for TMP_SpriteAsset files. Default: Assets/TextMesh Pro/Resources/Sprite Assets"),
_tmpSpriteFolder);
_tmpCellSizePt = EditorGUILayout.FloatField(
new GUIContent("Cell size (pt)",
"Chiều cao glyph theo đơn vị TMP point. 0 = dùng Cell height (pixel).\n" +
"Glyph height in TMP points. 0 = use Cell height in pixels."),
_tmpCellSizePt);
_genTmpOnConvert = EditorGUILayout.ToggleLeft(
"Tự động gen TMP Sprite Asset sau mỗi lần Convert — Auto-generate TMP Sprite Asset after Convert",
_genTmpOnConvert);
EditorGUILayout.HelpBox(
"Output: <Sprite Assets folder>/Emotions {N}.asset + Emotions {N} Material.mat\n" +
"Tên sub-sprite khớp với atlas: cell_0000 … (dùng cho <sprite name=\"cell_XXXX\"> trong TMP).\n" +
"Sub-sprite names match atlas: cell_0000 … (use <sprite name=\"cell_XXXX\"> in TMP).",
MessageType.Info);
EditorGUILayout.Space(4f);
// Nút gen standalone từ snapshot cuối — Standalone gen button from last snapshot
EditorGUI.BeginDisabledGroup(_lastSnapshot == null);
if (GUILayout.Button("Gen TMP Sprite Asset từ lần Convert cuối — from last Convert"))
RunGenTMPSingle(_lastSnapshot);
EditorGUI.EndDisabledGroup();
if (_lastSnapshot == null)
EditorGUILayout.HelpBox("Chạy Convert single ít nhất một lần để kích hoạt nút này.\nRun Convert (single) once to enable this button.", MessageType.None);
EditorGUI.indentLevel--;
}
private int NextSuggestedSetIndex()
{
int max = -1;
@@ -149,6 +209,8 @@ namespace BrewMonster.Scripts.Editor
return;
}
_lastSnapshot = snapshot;
var so = ScriptableObject.CreateInstance<EmotionSetDataSO>();
EmotionAtlasConverterCore.ApplySnapshotToSetSO(so, snapshot);
string setFolder = $"{_outputFolder}/Emotions{_emotionSetIndex}";
@@ -156,10 +218,37 @@ namespace BrewMonster.Scripts.Editor
AssetDatabase.CreateAsset(so, soPath);
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("Done", $"Đã tạo:\n{soPath}\nAtlas (Multiple) trong thư mục set (hoặc đã slice tại nguồn).", "OK");
string msg = $"Đã tạo:\n{soPath}\nAtlas (Multiple) trong thư mục set.";
if (_genTmpOnConvert)
{
TMP_SpriteAsset tmpAsset = RunGenTMPSingle(snapshot);
if (tmpAsset != null)
msg += $"\nTMP Sprite Asset: {AssetDatabase.GetAssetPath(tmpAsset)}";
}
EditorUtility.DisplayDialog("Done", msg, "OK");
EditorGUIUtility.PingObject(so);
}
/// <summary>
/// Gen TMP_SpriteAsset cho một snapshot. Trả về asset nếu thành công, null nếu thất bại.
/// Generate TMP_SpriteAsset for one snapshot. Returns asset on success, null on failure.
/// </summary>
private TMP_SpriteAsset RunGenTMPSingle(EmotionSetSnapshot snapshot)
{
if (snapshot == null) return null;
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snapshot.EmotionSetIndex);
if (!EmotionAtlasConverterCore.CreateTMPSpriteAsset(snapshot, outPath, _tmpCellSizePt, out var tmpAsset, out string err))
{
EditorUtility.DisplayDialog("TMP Gen Error", $"Set {snapshot.EmotionSetIndex}: {err}", "OK");
return null;
}
EditorGUIUtility.PingObject(tmpAsset);
return tmpAsset;
}
private void RunConvertLibrary()
{
if (!EmotionAtlasConverterCore.TryValidateBatchIndices(_batchSlots, out string dupErr))
@@ -220,9 +309,28 @@ namespace BrewMonster.Scripts.Editor
AssetDatabase.CreateAsset(library, _libraryAssetPath);
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog("Done",
$"EmotionLibrary: {ok} bộ.\n{_libraryAssetPath}",
"OK");
int tmpOk = 0;
if (_genTmpOnConvert)
{
for (int i = 0; i < library.Sets.Count; i++)
{
var snap = library.Sets[i];
EditorUtility.DisplayProgressBar("TMP Sprite Assets", $"Set {snap.EmotionSetIndex}…", (float)i / Mathf.Max(1, library.Sets.Count));
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snap.EmotionSetIndex);
if (EmotionAtlasConverterCore.CreateTMPSpriteAsset(snap, outPath, _tmpCellSizePt, out _, out string tmpErr))
tmpOk++;
else
Debug.LogWarning($"[EmotionConverter] TMP gen Set {snap.EmotionSetIndex}: {tmpErr}");
}
EditorUtility.ClearProgressBar();
}
string doneMsg = $"EmotionLibrary: {ok} bộ.\n{_libraryAssetPath}";
if (_genTmpOnConvert)
doneMsg += $"\nTMP Sprite Assets: {tmpOk}/{ok} bộ → {_tmpSpriteFolder}";
EditorUtility.DisplayDialog("Done", doneMsg, "OK");
EditorGUIUtility.PingObject(library);
}
}
@@ -84,9 +84,10 @@ namespace CSNetwork
var it = itemsSet.GetItemIterator();
for (int i = 0; i < nCount; i++)
while (it.MoveNext())
{
EditBoxItemBase pItem = it.Current.Value;
if (pItem == null) continue;
if (pItem.GetType() == EditboxItemType.enumEIEmotion)
{
@@ -97,8 +98,6 @@ namespace CSNetwork
pItem.SetInfo(MarshalEmotionInfo(nEmotionSet, nIndex));
}
it.MoveNext();
}
return MarshalEditBoxText(strOrgText, itemsSet);
@@ -591,8 +590,6 @@ public class AUI_UNMARSH_UNDERLINE_INFO
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;
@@ -653,7 +650,7 @@ public class EditBoxItemsSet
public bool IsEditboxItemCode(char ch)
{
return ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
return AUICommon.IsEditboxItemCode(ch);
}
public int GetItemCountByType(AUICommon.EditboxItemType type)
@@ -700,8 +697,8 @@ public class EditBoxItemsSet
public char EditboxGetNextChar(char cur)
{
if (cur >= AUICOMMON_ITEM_CODE_END)
return AUICOMMON_ITEM_CODE_START;
if (cur >= AUICommon.AUICOMMON_ITEM_CODE_END)
return AUICommon.AUICOMMON_ITEM_CODE_START;
else
return (char)(cur + 1);
}
@@ -967,9 +964,24 @@ public class EditBoxItemBase
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()
{
return "";
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)
@@ -1962,7 +1962,7 @@ namespace CSNetwork
string szName = pHost.GetName();
char[] szText = new char[80];
AUICommon.AUI_ConvertChatString(ref szName, ref szText, false);
Debug.Log($"[Cuong] {szText}");
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
string str;
try {
@@ -2044,6 +2044,18 @@ namespace CSNetwork
m_iCharID = iCharID;
}
/// <summary>
/// Wire 正文(表情等)→ TMP — 供仅走 EventBus 的系统消息使用;玩家消息由 CECGameUIMan.AddChatMessage 处理。
/// Wire body (emotions, etc.) → TMP — for system messages that only use EventBus; player messages use CECGameUIMan.AddChatMessage.
/// </summary>
private string ConvertWireBodyForChatPanel(string wireBody, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return wireBody;
var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan() as CECGameUIMan;
return ui != null ? ui.ConvertWireChatBodyForDisplay(wireBody, cEmotion) : wireBody;
}
private bool OnPrtcWorldChat(Protocol pProtocol, bool bCalledagain)
{
worldchat p = (worldchat)pProtocol;
@@ -2059,6 +2071,7 @@ namespace CSNetwork
string strMsg = Encoding.Unicode.GetString(p.Msg.ToArray());
string strSrcName = Encoding.Unicode.GetString(p.Name.ToArray());
Debug.Log($"[Cuong] {strMsg}");
// Wire → TMP 在 AddChatMessage / ChatEmotionDisplayPipeline 内完成 — English: wire → TMP inside AddChatMessage.
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
string formatted;
try {
@@ -2070,11 +2083,16 @@ namespace CSNetwork
pGameUI.AddChatMessage(formatted, (ChatChannel)p.Channel, p.Roleid, strSrcName,
0, p.Emotion, null, strMsg);
// Bubble chat on head
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Roleid);
if (pSrcPlayer != null)
// Kênh không thuộc showsAboveHead trong AddChatMessage 时 vẫn cần bong bóng — English: extra bubble for channels AddChatMessage does not publish.
if (!CECGameUIMan.ChannelShowsChatBubbleAboveHead((ChatChannel)p.Channel))
{
EventBus.PublishChannel(p.Roleid, new EventChatMessageOnTopPlayer(p.Roleid, strMsg));
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Roleid);
if (pSrcPlayer != null)
{
string bubbleTmp = pGameUI.ConvertWireBodyForHeadBubble(
strMsg, (ChatChannel)p.Channel, p.Roleid, p.Emotion);
EventBus.PublishChannel(p.Roleid, new EventChatMessageOnTopPlayer(p.Roleid, bubbleTmp));
}
}
return true;
@@ -2115,8 +2133,6 @@ namespace CSNetwork
private bool OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain)
{
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
chatmessage p = (chatmessage)pProtocol;
//var channel = (ChatChannel)p.Channel;
@@ -2158,7 +2174,7 @@ namespace CSNetwork
case 18: case 19: case 20: case 21: case 22: // Auction Message
// pGameUI.AddSysAuctionMessage(...)
Debug.Log("[Cuong] Auction " + strTemp);
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
EventBus.Publish(new ChatMessageEvent(ConvertWireBodyForChatPanel(strTemp, p.Emotion), p.Channel));
break;
case 24: // Task Message
// OnTaskChatMessage(p.Data.RawBuffer, p.Data.Size);
@@ -2182,7 +2198,7 @@ namespace CSNetwork
}
else
{
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
EventBus.Publish(new ChatMessageEvent(ConvertWireBodyForChatPanel(strTemp, p.Emotion), p.Channel));
}
}else if (p.Channel == (byte)ChatChannel.GP_CHAT_INSTANCE && p.Srcroleid == 1)
{
@@ -2213,7 +2229,7 @@ namespace CSNetwork
// Do not pass szText into FormatPrintf — it is a scratch buffer from AUI_ConvertChatString, not a format arg.
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
string str = string.Format(fmt, szName, szMsg);
// [Port] Gọi AddChatMessage để hiển thị lên UI Chat Box.
// [Port] Gọi AddChatMessage để hiển thị lên UI Chat Box — wire→TMP 在 AddChatMessage 内(ChatEmotionDisplayPipeline)。
// AddChatMessage bên trong đã tự publish ChatMessageEvent (cho ChatPanelUI)
// VÀ EventChatMessageOnTopPlayer (cho Head Bubble) nếu channel thuộc nhóm
// showsAboveHead (LOCAL, TEAM, FARCRY, SUPERFARCRY, BATTLE, COUNTRY).
@@ -2245,7 +2261,8 @@ namespace CSNetwork
0,
p.Emotion
);
EventBus.Publish(new ChatMessageEvent(message));
// Không publish thêm ChatMessageEvent(message): message vẫn là wire trong thân,
// AddChatMessage đã publish bản đã wire→TMP — English: no duplicate; AddChatMessage already publishes converted text.
CECUIHelper.RemoveNameFlagFromNPCChat(strTemp, out szMsg);
@@ -2269,6 +2286,7 @@ namespace CSNetwork
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
CECStringTab pStrTab = EC_Game.GetFixedMsgs();
// Private chat wire body → TMP 在 AddChatMessage — English: wire → TMP inside AddChatMessage.
if (p.Channel == 0 /* CHANNEL_NORMAL */ || p.Channel == 1 /* CHANNEL_NORMALRE */)
{
// Format: "[Name] whispers to [You]: [Message]"
@@ -2290,11 +2308,14 @@ namespace CSNetwork
p.Channel, p.Emotion, null, strMsg);
}
// Set player's last said words for head bubble
// 私聊头顶气泡:AddChatMessage 对 GP_CHAT_WHISPER 不发布 EventChatMessageOnTopPlayer,在此单独发 TMP 正文。
// Whisper head bubble: AddChatMessage does not publish EventChatMessageOnTopPlayer for whisper — publish TMP body here.
CECPlayer pSrcPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Srcroleid);
if (pSrcPlayer != null)
{
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg));
string bubbleTmp = pGameUI.ConvertWireBodyForHeadBubble(
strMsg, ChatChannel.GP_CHAT_WHISPER, p.Srcroleid, p.Emotion);
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, bubbleTmp));
}
}
@@ -46,6 +46,57 @@ namespace BrewMonster.UI
_chatEmotionPipeline.SetSpriteMap(map);
}
/// <summary>
/// 服务器下发的 wire 正文 → TMP(表情/坐标/物品等内联)— 供 GameSession 等不经过 AddChatMessage 的 UI 路径。
/// Server wire body → TMP (inline emotion/coord/item) — for UI paths that do not go through AddChatMessage.
/// </summary>
public string ConvertWireChatBodyForDisplay(string wireBody, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return wireBody;
string f = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
return _chatEmotionPipeline.ConvertInlineItemsToTmp(f);
}
/// <summary>
/// 仅正文 wire(无 printf 包装)→ 头顶气泡 TMP — 与 AddChatMessage 内 FilterBadWords + 内联转 TMP 一致。
/// Wire body only (no printf wrapper) → head-bubble TMP — same as AddChatMessage FilterBadWords + inline to TMP.
/// </summary>
public string ConvertWireBodyForHeadBubble(string wireBody, ChatChannel cChannel, int idPlayer, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return wireBody;
string s = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
if (string.IsNullOrEmpty(s))
return s;
bool isPlayerChannel = cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_FACTION
|| cChannel == ChatChannel.GP_CHAT_WHISPER
|| cChannel == ChatChannel.GP_CHAT_TRADE
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
if (isPlayerChannel && idPlayer > 0)
CECUIManager.Instance.FilterBadWords(ref s);
return _chatEmotionPipeline.ConvertInlineItemsToTmp(s);
}
/// <summary>
/// Kênh có bong bóng trên đầu trong AddChatMessage — dùng để tránh double-publish với PROTOCOL_WORLDCHAT.
/// Channels that get head bubble from AddChatMessage — avoids double-publish with PROTOCOL_WORLDCHAT.
/// </summary>
public static bool ChannelShowsChatBubbleAboveHead(ChatChannel cChannel)
{
return cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
}
// Layout/settings flags for GetUserLayout (saved to server)
// 布局/设置标志,用于 GetUserLayout(保存到服务器)
private bool m_bAutoReply;
@@ -601,18 +652,15 @@ namespace BrewMonster.UI
// C++: AddChatMessage also handles head bubble via pPlayer->SetLastSaidWords
// Unity equivalent: Publish EventChatMessageOnTopPlayer
bool showsAboveHead = cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
bool showsAboveHead = ChannelShowsChatBubbleAboveHead(cChannel);
if (showsAboveHead && idPlayer > 0)
{
// C++: pPlayer->SetLastSaidWords(strTemp, ...) — strTemp là nội dung chat thuần,
// KHÔNG bao gồm tên người chơi. Dùng pszMsgOrigion thay vì pszMsg.
string bubbleText = !string.IsNullOrEmpty(pszMsgOrigion) ? pszMsgOrigion : pszMsg;
// C++: pPlayer->SetLastSaidWords(strTemp, ...) — chỉ thân tin (không tên); bản wire pszMsgOrigion phải → TMP giống khung chat.
// C++: SetLastSaidWords — body only (no name); wire pszMsgOrigion must become TMP like chat panel.
string bubbleText = !string.IsNullOrEmpty(pszMsgOrigion)
? ConvertWireBodyForHeadBubble(pszMsgOrigion, cChannel, idPlayer, cEmotion)
: pszMsg;
EventBus.PublishChannel(idPlayer, new EventChatMessageOnTopPlayer(idPlayer, bubbleText));
}
@@ -18,6 +18,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 865978f275731044a9b84fe9dbfe4210, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions0.txt
TmpSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
Entries:
- StartPos: 0
NumFrames: 4
@@ -436,6 +437,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 9e83ff623b8332a48a31f02c32a417bf, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions1.txt
TmpSpriteAsset: {fileID: 11400000, guid: 6f37606e7f8ba924fb4bb97cd0e6c734, type: 2}
Entries:
- StartPos: 0
NumFrames: 4
@@ -867,6 +869,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 6414ed03b9b595c4893bf27641a26a92, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions2.txt
TmpSpriteAsset: {fileID: 11400000, guid: c0d482604d9e30848afc3d65f2576f77, type: 2}
Entries:
- StartPos: 0
NumFrames: 6
@@ -1560,6 +1563,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: cdad8f497ffd51e4caeecc2115c9c5bf, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions3.txt
TmpSpriteAsset: {fileID: 11400000, guid: 3a5ecb4a05fb4de42a06037cf133bc69, type: 2}
Entries:
- StartPos: 0
NumFrames: 14
@@ -2158,6 +2162,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 3f3a694e7f4186645b1c0e2e4fd9d477, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions4.txt
TmpSpriteAsset: {fileID: 11400000, guid: ba5f1b4119a9bd84987d0c718b44f183, type: 2}
Entries:
- StartPos: 0
NumFrames: 4
@@ -2615,6 +2620,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: f545545a96251f8429c50ca2d2b415e0, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions5.txt
TmpSpriteAsset: {fileID: 11400000, guid: c664bed7f27d3bf4d88ffaa5b81a30c5, type: 2}
Entries:
- StartPos: 0
NumFrames: 4
@@ -3093,6 +3099,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 7857a986ab79aee4a9e881d288a4b8c8, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions6.txt
TmpSpriteAsset: {fileID: 11400000, guid: 966ad31186c6a8642b429169350b685b, type: 2}
Entries:
- StartPos: 0
NumFrames: 2
@@ -3675,6 +3682,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 6e5f5a0f5610bd849a8ee5a9e995da18, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions7.txt
TmpSpriteAsset: {fileID: 11400000, guid: 146a3d3c57ca61845890281300f22db2, type: 2}
Entries:
- StartPos: 0
NumFrames: 6
@@ -4363,6 +4371,7 @@ MonoBehaviour:
CellHeight: 32
SourceAtlas: {fileID: 2800000, guid: 84e9c440264236f45adfb529204b91f9, type: 3}
SourceTxtAssetPath: Assets/PerfectWorld/UI/Chat/emotions8.txt
TmpSpriteAsset: {fileID: 11400000, guid: 516b8c277c9eb5f4dbf5af1a8bf42e89, type: 2}
Entries:
- StartPos: 0
NumFrames: 9
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3011939e4e9c0ce4e83bc03a748fcf96
guid: e4fbe1473e80a9c4794327d8bb11a4ab
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -11416,7 +11416,7 @@ TextureImporter:
customData:
physicsShape: []
bones: []
spriteID: 3097da9ef9188074aafd00a6cf38eaf4
spriteID:
internalID: 0
vertices: []
indices:
@@ -87,6 +87,7 @@ GameObject:
- component: {fileID: 7228077960814023056}
- component: {fileID: 5305392080666511277}
- component: {fileID: 7604324431011831295}
- component: {fileID: 8883197050699466089}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
@@ -236,6 +237,26 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!114 &8883197050699466089
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6240941777052618231}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 291.7
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &6627717456258223658
GameObject:
m_ObjectHideFlags: 0
@@ -0,0 +1,746 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &718172578440456188
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8823934296412569056}
m_Layer: 5
m_Name: Sliding Area
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8823934296412569056
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 718172578440456188}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4092718722133982449}
m_Father: {fileID: 3822843052799882238}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -20, y: -20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &843061721837273778
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3676046446549609595}
- component: {fileID: 482462868452457425}
- component: {fileID: 5438734168994104898}
- component: {fileID: 1062586482629190498}
m_Layer: 5
m_Name: prefab_MiniChatUI
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3676046446549609595
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 843061721837273778}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1026350322123039068}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &482462868452457425
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 843061721837273778}
m_CullTransparentMesh: 0
--- !u!114 &5438734168994104898
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 843061721837273778}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.392}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &1062586482629190498
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 843061721837273778}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0c3012f1ec3cb3d4a9dbf4bab742e81c, type: 3}
m_Name:
m_EditorClassIdentifier:
onOpenChatPanelButton: {fileID: 7633421558195200411}
miniChatContent: {fileID: 6901861700741151626}
miniMessagePrefab: {fileID: 1976417251556044024, guid: af3b510e838bc934d97c31d0c665ede6, type: 3}
messagePrefab: {fileID: 1976417251556044024, guid: af3b510e838bc934d97c31d0c665ede6, type: 3}
miniChatPreviewLines: 5
miniChatPassThroughClicks: 1
maxStoredMessages: 10
chatSystemSO: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2}
--- !u!1 &3073746784354305239
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3822843052799882238}
- component: {fileID: 3308938358456462458}
- component: {fileID: 4982766943607529008}
- component: {fileID: 883384003667195735}
m_Layer: 5
m_Name: Scrollbar Vertical
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &3822843052799882238
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3073746784354305239}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8823934296412569056}
m_Father: {fileID: 4845475585563704660}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 10, y: -17}
m_Pivot: {x: 1, y: 1}
--- !u!222 &3308938358456462458
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3073746784354305239}
m_CullTransparentMesh: 1
--- !u!114 &4982766943607529008
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3073746784354305239}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &883384003667195735
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3073746784354305239}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 7407862892964548378}
m_HandleRect: {fileID: 4092718722133982449}
m_Direction: 2
m_Value: 0
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
--- !u!1 &3298050727520355041
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4092718722133982449}
- component: {fileID: 1090130158241239070}
- component: {fileID: 7407862892964548378}
m_Layer: 5
m_Name: Handle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4092718722133982449
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3298050727520355041}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8823934296412569056}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1090130158241239070
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3298050727520355041}
m_CullTransparentMesh: 1
--- !u!114 &7407862892964548378
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3298050727520355041}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &3329353435299617291
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1026350322123039068}
- component: {fileID: 5093099212117339368}
- component: {fileID: 7552365743554843694}
- component: {fileID: 7633421558195200411}
m_Layer: 5
m_Name: ButtonOpenChat
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1026350322123039068
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3329353435299617291}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4845475585563704660}
m_Father: {fileID: 3676046446549609595}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -175.64499, y: 151.8}
m_SizeDelta: {x: -1148.71, y: 227}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5093099212117339368
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3329353435299617291}
m_CullTransparentMesh: 1
--- !u!114 &7552365743554843694
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3329353435299617291}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: dcaed5f7f4ac2564caa797987cad6e40, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7633421558195200411
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3329353435299617291}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 0
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 7552365743554843694}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &3415728744842130225
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6901861700741151626}
- component: {fileID: 8221922843074448897}
- component: {fileID: 7620348421717998225}
- component: {fileID: 1308636600769309511}
m_Layer: 5
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6901861700741151626
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3415728744842130225}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4775615495870991432}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 10, y: 0}
m_SizeDelta: {x: -30, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!114 &8221922843074448897
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3415728744842130225}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier:
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!114 &7620348421717998225
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3415728744842130225}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 0
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &1308636600769309511
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3415728744842130225}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4cf21a97aa5c9445c9859afa14de01ad, type: 3}
m_Name:
m_EditorClassIdentifier:
_rectTransform: {fileID: 6901861700741151626}
--- !u!1 &4130028666636203937
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4775615495870991432}
- component: {fileID: 6058492214783487750}
- component: {fileID: 6128544922052315148}
- component: {fileID: 8978003769100856221}
m_Layer: 5
m_Name: Viewport
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4775615495870991432
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4130028666636203937}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6901861700741151626}
m_Father: {fileID: 4845475585563704660}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6058492214783487750
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4130028666636203937}
m_CullTransparentMesh: 1
--- !u!114 &6128544922052315148
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4130028666636203937}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &8978003769100856221
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4130028666636203937}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ShowMaskGraphic: 0
--- !u!1 &6874262700332502098
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4845475585563704660}
- component: {fileID: 6639134328005110591}
- component: {fileID: 566607841426224557}
- component: {fileID: 8391397933666603359}
m_Layer: 5
m_Name: Scroll View
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4845475585563704660
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6874262700332502098}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4775615495870991432}
- {fileID: 3822843052799882238}
m_Father: {fileID: 1026350322123039068}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6639134328005110591
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6874262700332502098}
m_CullTransparentMesh: 1
--- !u!114 &566607841426224557
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6874262700332502098}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: efd7d18860367bf48b7d93df7b0d8b1f, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &8391397933666603359
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6874262700332502098}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Content: {fileID: 6901861700741151626}
m_Horizontal: 0
m_Vertical: 1
m_MovementType: 2
m_Elasticity: 0.1
m_Inertia: 1
m_DecelerationRate: 0.135
m_ScrollSensitivity: 1
m_Viewport: {fileID: 4775615495870991432}
m_HorizontalScrollbar: {fileID: 0}
m_VerticalScrollbar: {fileID: 0}
m_HorizontalScrollbarVisibility: 2
m_VerticalScrollbarVisibility: 1
m_HorizontalScrollbarSpacing: -3
m_VerticalScrollbarSpacing: -3
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05206a01b3910384cb9a17c74225d554
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7588729112411274048
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6105556955705595552}
- component: {fileID: 3541366497170239759}
- component: {fileID: 9017642443478747476}
- component: {fileID: -8647101344485271738}
- component: {fileID: 4685469653980001717}
m_Layer: 5
m_Name: prefab_EmojiCell
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6105556955705595552
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7588729112411274048}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3541366497170239759
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7588729112411274048}
m_CullTransparentMesh: 1
--- !u!114 &9017642443478747476
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7588729112411274048}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &-8647101344485271738
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7588729112411274048}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 0
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 9017642443478747476}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!114 &4685469653980001717
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7588729112411274048}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ad6cc9a7d46da8949bae44dfcf3fe03d, type: 3}
m_Name:
m_EditorClassIdentifier:
_icon: {fileID: 9017642443478747476}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 616f563fe32927844be2a17dbb408331
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+148 -18
View File
@@ -5,6 +5,8 @@ using CSNetwork.GPDataType;
using System.Collections.Generic;
using BrewMonster;
using BrewMonster.Network;
using BrewMonster.Scripts.Chat;
using BrewMonster.Scripts.Chat.EmotionData;
using BrewMonster.Scripts.Managers;
using BrewMonster.UI;
using CSNetwork;
@@ -29,6 +31,18 @@ namespace BrewMonster.Scripts.ChatUI
public TMP_InputField inputField;
public ChatSystemSO chatSystem;
[Header("Emoji")]
[Tooltip("SO ánh xạ emotion → TMP sprite tag. Gán EmotionLibrarySpriteMap đã build từ Emotion Atlas Converter.")]
[SerializeField] EmotionLibrarySpriteMap _spriteMap;
/// <summary>
/// Nội dung tin nhắn theo protocol wire (MarshalEditBoxText) — gửi server; không phải TMP.
/// Message body in wire protocol (MarshalEditBoxText) — sent to server; not TMP.
/// </summary>
private string _chatWireBody = "";
private bool _syncingWireToDisplay;
[Serializable]
public struct ChannelButtonMapping
{
@@ -73,7 +87,10 @@ namespace BrewMonster.Scripts.ChatUI
{
EventBus.Unsubscribe<WhisperPlayerEvent>(OnWhisperPlayerEvent);
if (inputField != null)
{
inputField.onSubmit.RemoveListener(OnSubmit);
inputField.onValueChanged.RemoveListener(OnInputValueChanged);
}
}
private void OnWhisperPlayerEvent(WhisperPlayerEvent e)
@@ -87,6 +104,9 @@ namespace BrewMonster.Scripts.ChatUI
if (inputField != null && Application.platform != RuntimePlatform.Android)
inputField.onSubmit.AddListener(OnSubmit);
if (inputField != null)
inputField.onValueChanged.AddListener(OnInputValueChanged);
foreach (var mapping in channelButtons)
{
if (mapping.button != null)
@@ -168,6 +188,8 @@ namespace BrewMonster.Scripts.ChatUI
inputField.MoveTextEnd(false);
}
SyncChatWireBodyFromInput();
EventBus.Publish(new ChatChannelFilterChangedEvent(m_currentChannel));
}
@@ -219,7 +241,10 @@ namespace BrewMonster.Scripts.ChatUI
private void OnCommand_speak(string text)
{
string strText = text.Trim();
SyncChatWireBodyFromInput();
string routingLine = (text ?? "").Trim();
string strText = _chatWireBody?.Trim() ?? "";
if (strText.Length <= 0)
{
@@ -232,26 +257,27 @@ namespace BrewMonster.Scripts.ChatUI
int nSlot = -1;
FilterBadWords(ref strText);
_chatWireBody = strText;
if (HandleDebugCommand(strText))
if (HandleDebugCommand(routingLine))
return;
float now = Time.time;
if (!CheckFarCryRequirement(strText, now))
if (!CheckFarCryRequirement(routingLine, now))
return;
if (!CheckSuperFarCryRequirement(strText, now))
if (!CheckSuperFarCryRequirement(routingLine, now))
return;
if (!CheckSpamProtection(strText, now))
return;
ChatChannel resolvedChannel = ParseAndSendMessage(strText, nPack, nSlot);
ChatChannel resolvedChannel = ParseAndSendMessage(routingLine, nPack, nSlot);
if (resolvedChannel == ChatChannel.GP_CHAT_FARCRY && strText.StartsWith("!@"))
if (resolvedChannel == ChatChannel.GP_CHAT_FARCRY && routingLine.StartsWith("!@"))
m_dwTickFarCry = now;
if (resolvedChannel == ChatChannel.GP_CHAT_SUPERFARCRY && strText.StartsWith("!#"))
if (resolvedChannel == ChatChannel.GP_CHAT_SUPERFARCRY && routingLine.StartsWith("!#"))
m_dwTickFarCry2 = now;
SaveHistory(resolvedChannel, strText, nPack, nSlot, now);
@@ -365,32 +391,27 @@ namespace BrewMonster.Scripts.ChatUI
private ChatChannel ParseAndSendMessage(string text, int nPack, int nSlot)
{
ChatChannel channel = m_currentChannel;
string pszMsg = text;
string pszMsg = _chatWireBody;
if (text.StartsWith("!!"))
{
channel = ChatChannel.GP_CHAT_TEAM;
pszMsg = text.Substring(2);
}
else if (text.StartsWith("!~"))
{
channel = ChatChannel.GP_CHAT_FACTION;
pszMsg = text.Substring(2);
}
else if (text.StartsWith("!@"))
{
channel = ChatChannel.GP_CHAT_FARCRY;
pszMsg = text.Substring(2);
}
else if (text.StartsWith("!#"))
{
channel = ChatChannel.GP_CHAT_SUPERFARCRY;
pszMsg = text.Substring(2);
}
else if (text.StartsWith("$"))
{
channel = ChatChannel.GP_CHAT_TRADE;
pszMsg = text.Substring(1);
}
else if (text.StartsWith("/"))
{
@@ -433,11 +454,10 @@ namespace BrewMonster.Scripts.ChatUI
}
string player = cmd.Substring(0, spaceIndex);
string msg = cmd.Substring(spaceIndex + 1);
m_whisperTarget = player;
SendPrivateChat(player, msg, nPack, nSlot);
SendPrivateChat(player, _chatWireBody, nPack, nSlot);
}
// =====================================================
@@ -474,12 +494,12 @@ namespace BrewMonster.Scripts.ChatUI
string localMsg;
try
{
localMsg = string.Format(fmt, target, msg);
localMsg = string.Format(fmt, target, _chatWireBody);
}
catch
{
// Fallback: dùng &target& để AddChatMessage tạo link — format không có & thì mình thêm
localMsg = $"&{target}&: {msg}";
localMsg = $"&{target}&: {_chatWireBody}";
}
CECGameUIMan pGameUI = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
@@ -487,7 +507,7 @@ namespace BrewMonster.Scripts.ChatUI
{
// idTarget là ID người NHẬN (hoặc -1 nếu không tìm thấy trong friend list)
// AddChatMessage sẽ convert &target& thành TMP link tag có dạng idTarget|target
pGameUI.AddChatMessage(localMsg, ChatChannel.GP_CHAT_WHISPER, idTarget, null, 0, 0, null, msg);
pGameUI.AddChatMessage(localMsg, ChatChannel.GP_CHAT_WHISPER, idTarget, null, 0, 0, null, _chatWireBody);
}
}
@@ -608,6 +628,7 @@ namespace BrewMonster.Scripts.ChatUI
private void ClearTextInInputField()
{
_chatWireBody = "";
if (m_currentChannel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
{
inputField.text = "/" + m_whisperTarget + " ";
@@ -617,6 +638,87 @@ namespace BrewMonster.Scripts.ChatUI
inputField.text = "";
}
}
private int GetEmotionSetForCurrentChannel()
{
return m_currentChannel == ChatChannel.GP_CHAT_SUPERFARCRY ? GameSession.SUPER_FAR_CRY_EMOTION_SET : 0;
}
private string BuildVisualPrefixForCurrentChannel()
{
if (chatSystem == null)
return "";
if (m_currentChannel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
return "/" + m_whisperTarget + " ";
var cfg = chatSystem.channelIcons.Find(c => c.channel == m_currentChannel);
return cfg.prefix ?? "";
}
/// <summary>
/// Lấy phần thân (TMP) sau prefix kênh / whisper — English: TMP body after channel or whisper prefix.
/// </summary>
private string ExtractMessageBodyFromVisual(string full)
{
if (string.IsNullOrEmpty(full))
return "";
if (full.StartsWith("/"))
{
int sp = full.IndexOf(' ');
if (sp > 0 && sp + 1 < full.Length)
return full.Substring(sp + 1);
return "";
}
string t = RemoveKnownPrefix(full);
if (chatSystem != null)
{
var cfg = chatSystem.channelIcons.Find(c => c.channel == m_currentChannel);
if (cfg.prefix != null && cfg.prefix.Length > 0 && t.StartsWith(cfg.prefix))
t = t.Substring(cfg.prefix.Length);
}
return t;
}
private void SyncChatWireBodyFromInput()
{
if (inputField == null)
return;
string tmpBody = ExtractMessageBodyFromVisual(inputField.text ?? "");
if (_spriteMap == null)
{
_chatWireBody = tmpBody;
return;
}
_chatWireBody = ChatWireTmpCodec.TmpBodyToWire(tmpBody, _spriteMap);
}
private void OnInputValueChanged(string _)
{
if (_syncingWireToDisplay)
return;
SyncChatWireBodyFromInput();
}
private void RefreshInputDisplayFromWire()
{
if (inputField == null)
return;
_syncingWireToDisplay = true;
try
{
string prefix = BuildVisualPrefixForCurrentChannel();
string tmpBody = ChatWireTmpCodec.WireBodyToTmpForDisplay(_chatWireBody, _spriteMap, GetEmotionSetForCurrentChannel());
inputField.text = prefix + tmpBody;
inputField.MoveTextEnd(false);
}
finally
{
_syncingWireToDisplay = false;
}
}
private void ChangeFocus()
{
inputField.ActivateInputField();
@@ -636,5 +738,33 @@ namespace BrewMonster.Scripts.ChatUI
{
return EC_Game.GetGameRun()?.GetHostPlayer()?.GetBasicProps().iLevel ?? 0;
}
// =====================================================
// EMOJI INSERT
// =====================================================
/// <summary>
/// Thêm emotion vào buffer wire rồi refresh TMP — gửi server đúng protocol.
/// Append emotion to wire buffer then refresh TMP — server protocol preserved.
/// </summary>
public void AppendEmotionWire(int emotionSet, int emotionIndex)
{
string segment = ChatWireTmpCodec.BuildMarshaledEmotionWire(emotionSet, emotionIndex);
if (string.IsNullOrEmpty(segment))
return;
_chatWireBody += segment;
RefreshInputDisplayFromWire();
}
/// <summary>
/// Chèn TMP emotion tag vào inputField tại vị trí caret hiện tại.
/// Inserts the TMP emotion tag into the inputField at the current caret position.
/// Gọi từ EmojiPickerUI khi người chơi bấm chọn emoji.
/// Called from EmojiPickerUI when the player selects an emoji.
/// </summary>
public void InsertEmoji(int emotionSet, int emotionIndex)
{
AppendEmotionWire(emotionSet, emotionIndex);
}
}
}
@@ -25,7 +25,7 @@ Material:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 2800000, guid: e1a057e1756274235a7c676d3d0a761a, type: 3}
m_Texture: {fileID: 2800000, guid: dffef66376be4fa480fb02b19edbe903, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
@@ -79,7 +79,7 @@ MonoBehaviour:
m_StrikethroughThickness: 0
m_TabWidth: 0
m_Material: {fileID: 2103686}
spriteSheet: {fileID: 2800000, guid: e1a057e1756274235a7c676d3d0a761a, type: 3}
spriteSheet: {fileID: 2800000, guid: dffef66376be4fa480fb02b19edbe903, type: 3}
m_SpriteCharacterTable:
- m_ElementType: 2
m_Unicode: 128522
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee97404ddf0b24345809ec3b36f78e02
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f37606e7f8ba924fb4bb97cd0e6c734
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c0d482604d9e30848afc3d65f2576f77
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a5ecb4a05fb4de42a06037cf133bc69
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ba5f1b4119a9bd84987d0c718b44f183
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c664bed7f27d3bf4d88ffaa5b81a30c5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 966ad31186c6a8642b429169350b685b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 146a3d3c57ca61845890281300f22db2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 516b8c277c9eb5f4dbf5af1a8bf42e89
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -36,11 +36,20 @@ MonoBehaviour:
m_fallbackFontAssets: []
m_matchMaterialPreset: 1
m_HideSubTextObjects: 0
m_defaultSpriteAsset: {fileID: 11400000, guid: c41005c129ba4d66911b75229fd70b45, type: 2}
m_defaultSpriteAsset: {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
m_defaultSpriteAssetPath: Sprite Assets/
m_enableEmojiSupport: 1
m_MissingCharacterSpriteUnicode: 0
m_EmojiFallbackTextAssets: []
m_EmojiFallbackTextAssets:
- {fileID: 11400000, guid: ee97404ddf0b24345809ec3b36f78e02, type: 2}
- {fileID: 11400000, guid: 6f37606e7f8ba924fb4bb97cd0e6c734, type: 2}
- {fileID: 11400000, guid: c0d482604d9e30848afc3d65f2576f77, type: 2}
- {fileID: 11400000, guid: 3a5ecb4a05fb4de42a06037cf133bc69, type: 2}
- {fileID: 11400000, guid: ba5f1b4119a9bd84987d0c718b44f183, type: 2}
- {fileID: 11400000, guid: c664bed7f27d3bf4d88ffaa5b81a30c5, type: 2}
- {fileID: 11400000, guid: 966ad31186c6a8642b429169350b685b, type: 2}
- {fileID: 11400000, guid: 146a3d3c57ca61845890281300f22db2, type: 2}
- {fileID: 11400000, guid: 516b8c277c9eb5f4dbf5af1a8bf42e89, type: 2}
m_defaultColorGradientPresetsPath: Color Gradient Presets/
m_defaultStyleSheet: {fileID: 11400000, guid: f952c082cb03451daed3ee968ac6c63e, type: 2}
m_StyleSheetsResourcePath: