From 1c68d2563c5fab03248dbe8e4a2fbb01ba64be01 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Fri, 10 Apr 2026 15:27:36 +0700 Subject: [PATCH] add doc chatsystem flow --- Documentation/Chatsystem/ChatSystem_Flow.md | 148 ++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/Documentation/Chatsystem/ChatSystem_Flow.md b/Documentation/Chatsystem/ChatSystem_Flow.md index d7c6673d59..4ad3a6266d 100644 --- a/Documentation/Chatsystem/ChatSystem_Flow.md +++ b/Documentation/Chatsystem/ChatSystem_Flow.md @@ -98,3 +98,151 @@ Dưới đây là các điểm khác biệt chính: Tương ứng bên kia, trong `UIPlayer.cs` chạy ở hàm `Start()`: `EventBus.SubscribeChannel(hostplayer.GetCharacterID(), SetChatMessage);` Khi event kích hoạt, hàm `SetChatMessage` sẽ nhận Context và gỡ nó ra gán vào Text Mesh UI một cách an toàn. + +--- + +## 4. UI nhập liệu: TMP_Dropdown kênh và MRU Whisper (`ChatInputHandler`) + +Luồng này bổ sung cho ô chat mobile: dropdown gắn với `ChatInputHandler` (`Assets/Scripts/ChatInputHandler.cs`). + +### 4.1 Cấu hình Inspector + +- **Component:** `ChatInputHandler` trên GameObject chat (có thể cùng `ChatSystemlUI`). +- **`recentWhisperDropdown`:** kéo **TMP_Dropdown** từ Hierarchy. +- **`maxRecentWhisperTargets`:** giới hạn MRU (mặc định **5**, tối thiểu áp dụng **1** qua property `MaxRecentWhisperTargets`). + +### 4.2 Hai chế độ nội dung dropdown + +**A. Không ở kênh Whisper** (`m_currentChannel != GP_CHAT_WHISPER`) + +Dropdown liệt kê **4 kênh** cố định theo thứ tự: + +| Thứ tự | `ChatChannel` | Nhãn hiển thị | +|--------|-------------------|---------------| +| 0 | `GP_CHAT_LOCAL` | Local | +| 1 | `GP_CHAT_TEAM` | Team | +| 2 | `GP_CHAT_FACTION` | Faction | +| 3 | `GP_CHAT_FARCRY` | World | + +- Chọn dòng → `OnCommand_speakmode(kênh)` (tương đương `channelButtons`). +- Đồng bộ selection: nếu kênh hiện tại **không** thuộc bốn kênh trên (ví dụ Trade), caption mặc định về **Local** (index 0) cho đến khi người chọn lại. + +**B. Kênh Whisper** (`GP_CHAT_WHISPER`) + +- Dòng đầu: ký tự **—** (placeholder), không ép chọn MRU / giữ target đang gõ. +- Các dòng sau: tên người **MRU** (mới chat gần nhất lên trên). +- MRU chỉ cập nhật sau khi gửi whisper thành công (`SendPrivateChat` → `RecordRecentWhisper`). +- Khi đang ở kênh khác, MRU vẫn lưu trong bộ nhớ; chuyển sang Whisper sẽ thấy danh sách trong dropdown. + +### 4.3 Tương tác và sự kiện + +- **Kênh System:** `inputField` không nhập; dropdown **`interactable = false`**. +- **Các kênh khác:** dropdown **interactable** (kể cả 4 kênh public hoặc Whisper). +- **`WhisperPlayerEvent` / `SetWhisperTarget`:** vẫn chuyển sang Whisper và đồng bộ dropdown (MRU). + +### 4.4 Code tham chiếu + +- `ChatDropdownChannelOrder`, `GetChannelDropdownLabel(ChatChannel)`, `RebuildChatDropdownOptions()`, `RecordRecentWhisper`. + +### 4.5 Lưu ý + +- MRU **không** persist (PlayerPrefs / file) — chỉ trong phiên chơi. +- Tên field SerializeField vẫn là `recentWhisperDropdown` để tương thích prefab cũ. + +--- + +## 5. Chat / Emotion: từ protocol PC đến TMP (tổng hợp handoff) + +Phần này tương ứng **mục 9** trong `claude.md` và các tóm tắt `Docs/chat-*-summary.md`: luồng rich text (emoji, item, coord), wire format PC, và hiển thị TextMeshPro. + +### 5.1 Tham chiếu C++ (chỉ đối chiếu) + +- **`FilterEmotionSet`:** `AUInterface2/AUICommon.cpp` — trong `UnmarshalEditBoxText`, với mỗi `enumEIEmotion` sửa info `"set:index"` rồi `MarshalEditBoxText`. +- **Atlas + txt:** `AUInterface2/AUIManager.cpp` (`Init`): `Surfaces\InGame\Emotions{l}.dds`, lưới **32×32**, `Surfaces\InGame\Emotions{l}.txt` — mỗi dòng: `nStartPos`, `nNumFrames`, hint, `nFrameTick[]` (tối đa 20). **`index` trong protocol** = thứ tự dòng trong `.txt`, không phải tọa độ tùy ý. +- File **`Emotions{N}.txt`** thường **không** có trong repo source — lấy từ client PC: `Surfaces\InGame\`. + +### 5.2 Luồng Unity: tin nhắn → TMP trên panel + +1. **`EC_GameUIMan.AddChatMessage`** (`Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs`): xử lý king bit (Country) → **`AUICommon.FilterEmotionSet`** → FilterBadWords → **`UnmarshalEditBoxText`** + **`ConvertEmotionsToTMP`** / **`ConvertCoordsToTMP`** / **`ConvertIvtrItemsToTMP`** / **`StripRemainingItemCodes`** → format màu/kênh → **`GameSession.ChatMessageEvent`** (hoặc luồng tương đương). +2. **`ChatPanelUI`** nhận event → **`ChatMessageView.Bind`** → TMP (`TextOutlet`). +3. **`AUICommon.cs` (`CSNetwork`):** logic rich-text item + `EmotionSpriteInfo` (hỗ trợ `UseSpriteName`, `SpriteName`, và tag có chỉ định asset qua `EmotionTMPTagBuilder`) + `IEmotionSpriteMap`. +4. **`CECGameUIMan`:** field **`_emotionLibrarySpriteMap`**. Trong **`Init()`**, nếu gán SO thì dùng map đó, không thì **`StubEmotionSpriteMap`**. + +### 5.3 Wire (raw) ↔ TMP: gửi và nhận + +**Mục tiêu:** Giữ **chuỗi wire** đúng chuẩn PC (PUA `\uE000`–`\uE3FF` + payload `MarshalEditBoxText` / `EditBoxItemBase.Serialize`) khi **gửi**; ô nhập hiển thị bản **TMP** (``). Tin từ server là wire; chuyển TMP ở pipeline hiển thị. + +**Các thành phần chính:** + +- **`EditBoxItemBase.Serialize()`** (`AUICommon.cs`): port từ C++ — emotion / coord / image; cần cho `MarshalEditBoxText` đầy đủ. +- **`EditBoxItemsSet`:** dùng range PUA `AUICommon` (`AUICOMMON_ITEM_CODE_START/END`), không dùng range `\u0001…\u0010` sai. +- **`ChatWireTmpCodec.cs`:** `BuildMarshaledEmotionWire`, `TmpBodyToWire` (parse `` khớp output `EmotionTMPTagBuilder`), `WireBodyToTmpForDisplay` (ủy quyền pipeline). +- **`ChatEmotionDisplayPipeline`:** `ConvertWireBodyToTmpDisplay`, cảnh báo khi thiếu `spriteMap`; **`ConvertInlineItemsToTmp`** có **fallback regex** `<0><(\d+):(\d+)>` (`LooseMarshaledEmotionRegex`) khi thiếu PUA nhưng còn literal serialize — tránh TMP in raw `<0>`. +- **`ChatInputHandler`:** **`_chatWireBody`** lưu thân tin wire gửi server; `onValueChanged` → `TmpBodyToWire`; gửi qua `SendChat` / `SendPrivateChat` dùng **`_chatWireBody`**; emoji: `AppendEmotionWire` / `InsertEmoji` + `RefreshInputDisplayFromWire()` (Super Far Cry dùng `GameSession.SUPER_FAR_CRY_EMOTION_SET`). +- **`CECGameUIMan.ConvertWireChatBodyForDisplay`:** tái sử dụng wire → TMP cho UI. +- **`GameSession.ConvertWireBodyForChatPanel`:** wire → TMP cho nhánh chỉ `EventBus.Publish(ChatMessageEvent)` (system/broadcast). Nhánh player qua **`AddChatMessage`** đã convert — **không** double-convert. Nhánh **NPC (`ISNPCID`):** đã bỏ publish `ChatMessageEvent` trùng sau `AddChatMessage` (tránh tin thứ hai còn wire). + +**Gán `EmotionLibrarySpriteMap` trên `ChatInputHandler`** nếu cần TMP ↔ wire khớp emoji; thiếu thì phần thân gần như text thường. + +### 5.4 Nhiều bộ emoji (9 set) và tag TMP + +- Mỗi **`EmotionSetSnapshot`** trong **`EmotionLibrarySO`** có **`TmpSpriteAsset`** riêng (một atlas / một TMP asset mỗi bộ). +- **`EmotionLibrarySpriteMap.TryGetSprite`** trả thông tin + **tên asset** (`SpriteAssetName`) để **`EmotionTMPTagBuilder`** tạo tag dạng **``** (hoặc `anim` / `index`) — tránh trường hợp nhiều asset cùng tên `cell_XXXX` khiến TMP luôn resolve set 1. +- Index sprite: **`ParseCellIndex`** từ tên `cell_XXXX` (thứ tự cắt atlas trái→phải, trên→dưới), giảm phụ thuộc thứ tự trong `spriteCharacterTable`. +- Converter atlas có thể đặt tên namespace **`s{N}_cell_XXXX`** khi gom nhiều pack; cần đồng bộ với TMP asset và tag builder. + +### 5.5 Dữ liệu và ScriptableObject + +| File / type | Vai trò | +|-------------|--------| +| `Chat/EmotionData/EmotionEntryData.cs` | Một emotion: StartPos, NumFrames, Hint, FrameTicks, FrameSprites | +| `Chat/EmotionData/EmotionSetDataSO.cs` | Một bộ `EmotionsN` | +| `Chat/EmotionData/EmotionLibrarySO.cs` | Nhiều bộ: `List` (+ `TmpSpriteAsset` trên snapshot) | +| `Chat/EmotionData/EmotionLibrarySpriteMap.cs` | SO implement `IEmotionSpriteMap`: Library + tùy chọn PreferSpriteNameTag, DefaultAnimFps | +| `Chat/StubEmotionSpriteMap.cs` | Fallback khi chưa gán map đầy đủ | +| `Chat/EmotionTMPTagBuilder.cs` | Build tag TMP thống nhất cho input và `ConvertEmotionsToTMP` | + +### 5.6 Tool Editor (atlas + txt) + +- Menu: **Perfect World → Chat → Emotion Atlas Converter…** +- **`EmotionAtlasConverterCore.cs`:** mỗi bộ xuất **`Emotions{N}_atlas.png`** (hoặc slice in-place), **Sprite Mode Multiple**, sub-sprite **`cell_0000` …** +- **`EmotionAtlasConverterWindow.cs`:** single set; batch theo slot (**Set N**, Atlas, TXT), trùng `N` báo lỗi; có thể tạo **`EmotionLibrary.asset`**. + +### 5.7 Checklist setup Unity (một lần) + +1. Import PNG + `Emotions{N}.txt` → chạy converter → **`EmotionLibrary.asset`** (và/hoặc từng `EmotionSetData_*.asset`). +2. Tạo **TMP Sprite Asset** khớp atlas; tên asset ổn định (ví dụ `Emotions 0`, `Emotions 1`, …) để tag `` resolve đúng. +3. Trong **Emotion Library**: mỗi snapshot gán **`Tmp Sprite Asset`** tương ứng. +4. Tạo asset **Create → Perfect World → Chat → Emotion Library Sprite Map**: gán Library + tùy chọn **Prefer Sprite Name Tag** (emoji 1 frame). +5. Gán **`EmotionLibrarySpriteMap`** vào **`CECGameUIMan`** (và `ChatInputHandler` / `CECUIManager` theo luồng wire–TMP). +6. Trên **TextMeshProUGUI** chat: gán **Sprite Asset** / fallback phù hợp để `` render. + +### 5.8 Lỗi đã xử lý (tham chiếu nhanh) + +- **`FilterEmotionSet` NRE:** enumerator `Dictionary` — đổi `while (it.MoveNext())`, check null item (`AUICommon.cs`). +- **`UnmarshalEditBoxText` không extract item:** `itemMask` → dùng `ITEMMASK_ALL = ~0`. +- **`EditBoxItemsSet` range sai:** đồng bộ PUA với `AUICommon`. +- **Hiển thị raw wire trên TMP:** luồng convert TMP ↔ wire; tag có chỉ định sprite asset theo set. +- **Literal `<0>` khi nhận:** fallback regex trong `ChatEmotionDisplayPipeline.ConvertInlineItemsToTmp`. +- **Duplicate `ChatMessageEvent` NPC:** bỏ publish thứ hai sau `AddChatMessage` (`GameSession.cs`). + +### 5.9 Việc còn mở / tùy chọn + +- Test end-to-end tin có emoji từ server. +- Link **`coord:`** / **`item:`** trong TMP: mở rộng `ChatMessageView.OnPointerClick` nếu khác logic whisper. +- Tinh chỉnh FPS animation từ `FrameTicks` cho sát PC. +- Test nhanh local: `Assets/Scripts/ChatEmojiQuickTest.cs` (mô phỏng TMP → wire → TMP). + +### 5.10 Đường dẫn file nhanh + +- `Assets/PerfectWorld/Scripts/Network/CSNetwork/AUICommon.cs` +- `Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs` +- `Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs` +- `Assets/PerfectWorld/Scripts/Chat/UI/ChatPanelUI.cs`, `ChatMessageView.cs` +- `Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs`, `ChatEmotionDisplayPipeline.cs`, `EmotionTMPTagBuilder.cs` +- `Assets/PerfectWorld/Scripts/Chat/EmotionData/*` +- `Assets/PerfectWorld/Scripts/Editor/EmotionAtlasConverter*.cs` +- `Assets/Scripts/ChatInputHandler.cs`, `Assets/Scripts/CECUIManager.cs` +- `Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs` + +*Tài liệu bổ sung: `Docs/chat-channel-whisper-dropdown-summary.md`, `Docs/chat-emoji-progress-summary.md`, `Docs/chat-emotion-refactor-summary.md`, `claude.md` §9.*