Files
test/Documentation/Chatsystem/ChatSystem_Flow.md
T
2026-03-27 11:39:31 +07:00

8.8 KiB

Luồng Hoạt Động Của Hệ Thống Chat (C#)

Tài liệu này tổng hợp luồng xử lý của hệ thống Chat S2C và C2S trong client, dựa trên hai hàm cốt lõi SendChatDataOnPrtcChatMessage thuộc lớp GameSession.cs.


1. Luồng Gửi Tin Nhắn (C2S) - Hàm SendChatData

Hàm này được gọi khi người chơi nhập tin nhắn ở Local và muốn gửi lên Server (hoặc sử dụng vật phẩm liên quan đến Chat). Đặc biệt, Server sẽ KHÔNG tự động gửi lại (echo) tin nhắn cho người gửi, do đó client phải chủ động tự hiển thị tin nhắn của chính mình lên UI.

Các bước xử lý:

  1. Khởi tạo và đóng gói dữ liệu:

    • Tạo gói tin C2S publicchat và thiết lập các thông số cơ bản (Channel, Roleid của người gửi).
  2. Xử lý đính kèm vật phẩm (nếu có):

    • Vật phẩm thẻ (GENERALCARD_PACK): Nếu tin nhắn được gửi cùng một thẻ (card), hệ thống sẽ tìm trong túi đồ client (Client Pack) theo slot (iSlot), lấy ra cardId và chèn byte data lệnh CHAT_GENERALCARD_COLLECTION vào giao thức.
    • Vật phẩm trang bị thông thường: Nếu iPackiSlot hợp lệ, đóng gói định dạng CHAT_EQUIP_ITEM gồm vị trí túi where và vị trí slot index chèn vào gói tin.
  3. Gửi gói tin lên Server:

    • Tin nhắn chuỗi (szMsg) được mã hóa sang UTF-16 LE (Unicode trong C#) và lưu vào Msg (kiểu Octets).
    • Gọi SendProtocol(p) để đẩy dữ liệu xuống tầng Network và gửi đi.
  4. Hiển thị tin nhắn Local ngay lập tức:

    • Xác định nEmotionSet (nếu có), ví dụ kênh Super Far Cry sẽ có hiệu ứng bong bóng đầu đặc biệt (trim 8 character cuối của text).
    • Bong bóng đầu (Head Bubble): Áp dụng nếu kênh thuộc dạng lân cận (GP_CHAT_LOCAL, GP_CHAT_FARCRY, GP_CHAT_SUPERFARCRY, GP_CHAT_BATTLE, GP_CHAT_COUNTRY).
    • Lấy tên Host Player, ghép chuỗi với định dạng text của hệ thống (sử dụng cố định FIXMSG_CHAT).
    • Gọi EC_Game.GetGameRun().AddChatMessage(...): Hàm này sẽ đẩy dữ liệu lên UI Chat Box và tự động xử lý bong bóng trên đầu nhân vật.

2. Luồng Nhận Tin Nhắn (S2C) - Hàm OnPrtcChatMessage

Hàm này nhận giao thức chatmessage từ Server và xử lý việc hiển thị lên kênh chat, bong bóng đầu player/NPC. Vì hàm OnPrtcChatMessage đang quá lớn nên tôi chia ra thêm 1 class Chat_GameSession để xử lý các logic nhỏ. Các bước xử lý:

  1. Tiền xử lý và Lọc (Filter):

    • Kiểm tra cấp độ người gửi qua Chat_GameSession.ShouldBlockByLevel(). Nếu bị chặn, hàm ngắt ngang và bỏ qua tin nhắn.
    • Tạo EC_IvtrItem (Chat Item) từ dải byte đính kèm (nếu có vật phẩm).
    • Lọc các tag mã hóa không hợp lệ thông qua AUICommon.FilterInvalidTags.
    • Chạy qua các policy chặn lọc hệ thống (Chat_GameSession.PolicyResolver()) để ra chuỗi tinh chỉnh cuối cùng (szMsg).
  2. Phân loại Channel và RoleID để xử lý:

    Hệ thống phân chia theo 3 nhóm luồng chính:

    Nhóm A: Broadcast, System, hoặc System Role (Srcroleid == 0)

    • Nếu ChannelGP_CHAT_SYSTEM và có Srcroleid > 0, tin nhắn thuộc các thông báo riêng biệt của cục bộ tính năng, được chia nhánh theo Srcroleid (Hardcode ID):
      • ID = 1,2,3,4,6,7: Bản tin Chiến trường (Battle Message).
      • ID = 18..22: Bản tin Đấu giá (Auction Message). Gọi ChatMessageEvent đưa lên kênh system.
      • ID = 24: Bản tin Nhiệm vụ (Task Message).
      • ID = 29..45: Bản tin Pháo đài, Lãnh thổ (Fortress Message).
      • ID = 46..49: Bản tin Quốc chiến (Country Battle).
      • ID = 50..59: Bản tin Vua (King Chat).
      • ID = 60..64: Bản tin PVP Bang hội (Faction PVP). (Lưu ý: Một số hàm của luồng C++ đang bị ẩn/ẩn implementation, nếu cần data pending chưa có sẽ hoãn và chờ lần gọi lại (bCalledagain).)
    • Nếu không thuộc các ID đặc biệt trên, hệ thống đơn giản là phát ra ChatMessageEvent đẩy thẳng tin nhắn lên luồng EventBus.

    Nhóm B: Instance Channel (GP_CHAT_INSTANCE & Srcroleid == 1)

    • Tin nhắn riêng của Phó bản, hiện tại được thiết kế để ném vào luồng AddHeartBeatHint ở UIMan.

    Nhóm C: Dành cho người chơi khác (Player) hoặc NPC

    Sử dụng chuỗi định dạng lấy từ FixedMsg kết hợp tên người / NPC.

    • Nếu là người chơi (ISPLAYERID):

      • Request tên người chơi GetPlayerName.
      • Thiếu Data: Nếu không tìm thấy tên player trong cache, tin nhắn yêu cầu Server lấy tên (AddChatPlayerID) và treo giao thức chat này vào Pending Protoco (AddElemForPendingProtocols). Giao thức này sẽ tự kích hoạt lại ở Tick sau khi Server trả về tên.
      • Có Data: Xây dựng câu (kiểu [Kênh] Player: Message), gọi AddChatMessage(...) đẩy vào hộp thoại Chat và bắn bong bóng đầu tự động.
    • Nếu là NPC (ISNPCID):

      • Tìm kiếm CECNPC từ danh sách NPCMan.
      • Dựng chuỗi bằng format FIXMSG_CHAT2.
      • Gọi AddChatMessage() cho chat panel và EventBus.Publish(ChatMessageEvent) cho UI liên quan.
      • Lọc riêng Name Flag trên đầu NPC, rồi gán nội dung bằng hàm pNPC.SetLastSaidWords(szMsg) để hiện Bubble Text trên mô hình 3D NPC.

3. Phân Tích Khác Biệt Giữa C++ và C# (Triển khai OnPrtcChatMessage)

Trong quá trình chuyển đổi (port) hệ thống Chat từ C++ sang C# (Unity), kiến trúc đã được thay đổi từ mô hình liên kết chặt chẽ (Tight Coupling) sang mô hình hướng sự kiện (Event-Driven) nhằm tách biệt hoàn toàn tầng Network và tầng UI/Logic Game.

Dưới đây là các điểm khác biệt chính:

Tiêu chí C++ (EC_GameSession.cpp) C# Unity (GameSession.cs & UIPlayer.cs)
Kiến trúc liên kết (Giao tiếp với mô hình 3D) Tầng Network gọi trực tiếp vào hệ thống quản lý Game World. Code chủ động tìm kiếm player: CECPlayer *pPlayer = GetWorld()->GetPlayerMan()->GetPlayer(...) rồi gọi trực tiếp hàm pPlayer->SetLastSaidWords(strTemp, p->emotion, pItem);. Gọi thông qua kiến trúc kênh Pub/Sub: EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg));. Tầng Network không cần biết đối tượng 3D có tồn tại hay không.
Bong bóng Chat trên đầu (Head Bubble) Đối tượng CECPlayer tự tính toán, lưu trữ Text và dựa vào Tick update trung tâm C++ để hiển thị/tắt text sau một khoảng thời gian. Giao diện hiển thị UIPlayer.cs (gắn trên Prefab nhân vật) tự đăng ký sự kiện (EventBus.SubscribeChannel). Khi nhận event, tự đổi chuỗi TextMeshProUGUI, hiện Text và gọi một UniTask (HideChatAsync) độc lập để tự động tắt sau 5 giây.
Xử lý Thread/Đa luồng Toàn bộ Network và Logic xử lý trên chung một luồng chính đồng bộ. Tầng Network (nhận Packet) và tầng UI hiển thị Unity có thể bất đồng bộ. Vì vậy C# dùng ChatThreadDispatcher.Instance.Post(...) trong UIPlayer.SetChatMessage để đồng bộ an toàn việc gán giá trị UI lên luồng chính của Unity.
Bảo trì / Mở rộng Gắn chặt UI với Data Network. Nếu UI thay đổi, sửa Network code. Decoupled (Giảm kết dính). Việc hiển thị UI chat (khung Chat Panel lẫn Head Bubble trên đầu nhân vật) hoạt động độc lập và chỉ "lắng nghe" dữ liệu.

Ví Dụ Chi Tiết (Luồng Head Bubble Player):

  • C++: Trong OnPrtcChatMessage, code thực hiện: pPlayer->SetLastSaidWords(strTemp, p->emotion, pItem); Tức là bắt ép đối tượng thực hiện cập nhật UI.
  • C#: Trong GameSession.cs (hoặc bên trong AddChatMessage), hệ thống gọi: EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg)); Tương ứng bên kia, trong UIPlayer.cs chạy ở hàm Start(): EventBus.SubscribeChannel<EventChatMessageOnTopPlayer>(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.