From a82337f61787cddb7dfafba972e5841da446ed27 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 1 Apr 2026 16:47:02 +0700 Subject: [PATCH] update skill for chat --- .agents/skills/chat_system_csharp/SKILL.md | 26 ++++++ .../CONVERSION_GUIDE_STRING_FORMAT.md | 83 +++++++++++++++++-- 2 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 .agents/skills/chat_system_csharp/SKILL.md diff --git a/.agents/skills/chat_system_csharp/SKILL.md b/.agents/skills/chat_system_csharp/SKILL.md new file mode 100644 index 0000000000..d8725dfd91 --- /dev/null +++ b/.agents/skills/chat_system_csharp/SKILL.md @@ -0,0 +1,26 @@ +--- +name: C# Chat System Formatting +description: Hướng dẫn viết code C# cho hệ thống chat dùng AUIDialog.FormatPrintf và pGameUI.GetStringFromTable. +--- + +# C# Chat System Formatting + +Skill này cung cấp hướng dẫn khi viết code C# cho hệ thống chat (ví dụ như khi chuyển đổi code từ C++ sang C# trong Unity). + +## Quy tắc lấy chuỗi và format + +Khi cần lấy một chuỗi từ bảng ngôn ngữ (Table) và format chuỗi đó với các tham số (thay thế cho %s, %d,...), hãy sử dụng kết hợp `pGameUI.GetStringFromTable` và `AUIDialog.FormatPrintf`. + +Trong phiên bản C++ gốc (ví dụ trong `EC_GameUIMan.cpp`), việc lấy chuỗi thường dùng `GetStringFromTable` và format bằng `swprintf` hoặc các hàm tương tự. Ở phiên bản C#, chúng ta thực hiện như sau: + +```csharp +// Ví dụ Table 9343: chuỗi dạng "Thời gian đăng nhập trước: %s" +// AUIDialog.FormatPrintf sẽ thực hiện kết hợp giữa pGameUI.GetStringFromTable(9343) và chuỗi timeStr +string textTime = AUIDialog.FormatPrintf(pGameUI.GetStringFromTable(9343), timeStr); +``` + +## Các bước thực hiện +1. Đảm bảo đã lấy được referent tới `pGameUI`, ví dụ qua `CECUIManager.Instance?.GetInGameUIMan()`. +2. Lấy chuỗi format gốc từ bảng ngôn ngữ: `pGameUI.GetStringFromTable(ID)`. +3. Dùng `AUIDialog.FormatPrintf` để thay thế các placeholder (như `%s`, `%d`) trong chuỗi lấy được bằng các giá trị thực tế. +4. Gửi chuỗi đã được format đến hệ thống chat, ví dụ qua `EventBus.Publish(new GameSession.ChatMessageEvent {...})`. diff --git a/Documentation/CONVERSION_GUIDE_STRING_FORMAT.md b/Documentation/CONVERSION_GUIDE_STRING_FORMAT.md index 7435b3639a..6db6b8bb73 100644 --- a/Documentation/CONVERSION_GUIDE_STRING_FORMAT.md +++ b/Documentation/CONVERSION_GUIDE_STRING_FORMAT.md @@ -66,16 +66,23 @@ ACString str; str.Format(GetStringFromTable(11326), needMoney, needSp); ``` -**C# (string)**: +**C#**: +**Method A: Using `AUIDialog.FormatPrintf` (Recommended for Legacy Strings)** +If the string from the table uses C++ format specifiers (`%d`, `%s`, `%f`), `AUIDialog.FormatPrintf` handles them natively without requiring string conversion. +```csharp +string confirmMessage = AUIDialog.FormatPrintf(GetStringFromTable(11326), needMoney, needSp); +``` + +**Method B: Using `string.Format` (Standard C#)** +If the string table has been updated to use C# format specifiers (`{0}`, `{1}`), you can use the built-in `string.Format`. ```csharp string confirmMessage = string.Format(GetStringFromTable(11326), needMoney, needSp); ``` **Notes**: -- C++'s `ACString::Format()` is a member function -- C#'s `string.Format()` is a static function -- Both use the same placeholder syntax: `%d` (C++) or `{0}`, `{1}` (C#) -- If your string table uses C++ format specifiers, you may need to convert them: +- C++'s `ACString::Format()` is a member function. +- C#'s `string.Format()` is a static function but only understands `{0}`, `{1}` syntax. +- If your string table still uses C++ format specifiers (`%d`, `%s`, `%f`), **you should use `AUIDialog.FormatPrintf`**. Otherwise, you would need to manually convert the placeholders in the string table: - `%d` → `{0}`, `{1}` for integers - `%s` → `{0}`, `{1}` for strings - `%f` → `{0}`, `{1}` for floats @@ -346,11 +353,66 @@ if (spOK) { } ``` +### Example 4: Clickable Text Links (TextMeshPro) + +**C++ (Rich Text and Actions)**: +In C++, player names or interactive elements (like items) in chat are often wrapped with custom delimiters like `&PlayerName&` to be parsed later for coloring and clicking. The click actions are handled by complex UI dialogue components. + +**C# (Unity TextMeshPro `` tag)**: +In Unity's TextMeshPro, we can use the standard `` tag to define an interactive region of text. By parsing the legacy `&PlayerName&` format using Regex, we can inject a `` tag that Unity's event system can interact with. + +1. **Format the string with Regex replacing delimiters (e.g., in `EC_GameUIMan.cs`)**: +```csharp +if (parsedMsg.Contains("&")) +{ + // Convert &PlayerName& into PlayerName + parsedMsg = System.Text.RegularExpressions.Regex.Replace( + parsedMsg, + @"&([^&]+)&", + $"$1" + ); +} +``` + +2. **Handle the Click Event via Unity EventSystems (e.g., in `ChatMessageView.cs`)**: +The UI View script attached to the TextMeshPro text must implement `IPointerClickHandler` to intercept pointer clicks on the `` markup. +```csharp +using UnityEngine.EventSystems; +using TMPro; + +public class ChatMessageView : MonoBehaviour, IPointerClickHandler +{ + public EC_UIUtility.TextOutlet messageText; + + public void OnPointerClick(PointerEventData eventData) + { + if (messageText == null || messageText.tmp == null) return; + + // Check if the pointer click intersects with any tag boundary + int linkIndex = TMP_TextUtilities.FindIntersectingLink(messageText.tmp, eventData.position, eventData.pressEventCamera); + if (linkIndex != -1) + { + // Retrieve the link ID (which we dynamically set to the player's name) + TMP_LinkInfo linkInfo = messageText.tmp.textInfo.linkInfo[linkIndex]; + string linkId = linkInfo.GetLinkID(); + + if (!string.IsNullOrEmpty(linkId)) + { + // Trigger logic, e.g., Whisper the player! + EventBus.Publish(new WhisperPlayerEvent(linkId)); + } + } + } +} +``` + +**Note for specific PW Dialogues**: Legacy classes (such as `DlgNameLink.cs`) may implement a customized command pattern (e.g., `LinkCommand`, `MoveToLinkCommand` alongside `StyledTaskTraceText`) to process complex hyperlink commands recursively. However, for completely rewritten or standalone UI systems like standard Chat or logging panels, utilizing TextMeshPro's native `TMP_TextUtilities.FindIntersectingLink` combined with `IPointerClickHandler` is significantly faster, lightweight, and standard for Unity development. + --- ## Common Pitfalls -### ❌ Wrong: Using C++ format specifiers in C# +### ❌ Wrong: Using C++ format specifiers with `string.Format` ```csharp // This won't work if the string uses C++ format specifiers @@ -358,8 +420,15 @@ string template = "Cost: %d gold"; // C++ style string message = string.Format(template, 1000); // Error! ``` -### ✅ Correct: Convert to C# format +### ✅ Correct: Use `AUIDialog.FormatPrintf` OR convert to C# format +**Option 1: Use AUIDialog.FormatPrintf for C++ styles:** +```csharp +string template = "Cost: %d gold"; // C++ style +string message = AUIDialog.FormatPrintf(template, 1000); // "Cost: 1000 gold" +``` + +**Option 2: Convert to C# format strings:** ```csharp string template = "Cost: {0} gold"; // C# style string message = string.Format(template, 1000); // "Cost: 1000 gold"