package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // BBChat — AppId 27 "Message" (SystemId 146), the phone's NPC chat app. It is NOT the player IM: // its red dot comes from gRedDotUtils.CheckNpcChatHasRedDot() (MainPhoneUtils.lua:688) and // its panels are the NpcChat* stores, i.e. everything here is gNpcChatManager. // Content half: the conversation list is published by the login frame // (InfoNpcCultivation.NpcChats / NpcGroupChats, see RuntimePayloadFactory.FillNpcChats // and NpcChatCatalog4229938). Nothing here refills it — there is no pull request for that list. // Interaction half: every request below carries exactly one u32 (chatid) and the client // declares no reply reader for any of them (no midToReader entry in RPCDeserializeAuto for // 63013781 / 63098840 / 63137850 / 63203620 / 63266674 / 63082065), so the answer must be the // empty OK — anything else desynchronises nothing the client reads but wastes bytes. What actually // moves a conversation is the SyncNpcChat push: the next row of NPCChatConfig. // ⛑ The server owns no chat text. A row is "which config id is unlocked next"; the client resolves // speaker, side, avatar and body itself via LTConfig.NPCChatConfig.GetConfig(chatItem.ChatId) // (NpcChatUtils.NewFromNpcChatItem), which drops any id it does not know with a // "NPCChat表没有查到" error — hence NextChatId always comes from the catalogue's own map. // // Leading u32 of every request body here. func GameRouter_NpcChat_NpcChatRequestId(ctx context.Context, msg any) uint32 { // Router implementation for GameRouter_NpcChat_NpcChatRequestId return 0 } // ================================================================================= // Advancing a conversation // // Three different requests do the same thing from three different entry points, and all of them // mean "take the given row's NextMessage[0] and hand it to the client": // InteractNpcChat(63098840) NpcChatChattingPanelStore.lua:1035/1055 — tap to continue // ChatToNpc(63013781) :1169 — the player picked an authored reply // InviteNpcChat(63137850) :1142 — the player accepted an invite row // The client arms a timeout before sending (StartChatTimeout) and clears it when the push lands, // so pushing nothing would leave the panel spinning for its timeout window; pushing the same row // back would be worse, hence the explicit unknown-row branch below. // ================================================================================= func GameRouter_NpcChat_InteractNpcChat(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_InteractNpcChat var zero any; return zero } func GameRouter_NpcChat_ChatToNpc(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_ChatToNpc var zero any; return zero } func GameRouter_NpcChat_InviteNpcChat(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_InviteNpcChat var zero any; return zero } func GameRouter_NpcChat_AdvanceNpcChatAsync(ctx context.Context, conn any, msg any, via string) any { // Router implementation for GameRouter_NpcChat_AdvanceNpcChatAsync var zero any; return zero } // // InteractNpcChatToEnd(63203620) — "skip to the end" button. The whole remaining tail of the // thread is pushed as one burst so the panel can render it as history. // Capped at NpcChatSkipBatch rows: the rows are pushed one SyncNpcChat at a time // because that is the only framing the client reads for this, and a 60-message thread would // otherwise hold the gesture hostage for a full row-by-row animation queue. // func GameRouter_NpcChat_InteractNpcChatToEnd(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_InteractNpcChatToEnd var zero any; return zero } // ================================================================================= // Bookkeeping requests — nothing persists, everything answers OK // ================================================================================= // AskMarkNpcChatRead(63266674) — NpcChatChattingPanelStore.lua:132/818, NpcChatN2NChattingPanel.lua:217. func GameRouter_NpcChat_AskMarkNpcChatRead(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_AskMarkNpcChatRead var zero any; return zero } // // RequestNpcChatList(63082065) — ChatNpcsPhoneManager.lua:229 / ChattingToNpcPanelStore.lua:375. // Used by the GM "pull this conversation" route; the player-facing list already arrived in the // login frame, so there is nothing to stream back — **but the reply still has a shape**: // ⛑⛑ 2026-09-29 修正:客户端 `midToReturnMessageReader[63082065]` 是 // `Base.ReadList7Bit(reader, …Complex[372])`,即 RET 是 **List<T> 而不是 void**。 // 原来 `ReturnEmptyOkAsync` 是 **0 字节载荷**(Connection.cs:32 `Array.Empty()`) // ⇒ 客户端 reader 读不到 presence 字节。实测 [NPCCHAT] 有触发(今日累计 2 次)。 // ⇒ 回**空列表** `FF 01`(0xFF=presence,0x01=Int7(0) 的 +1 偏置,硬规则"空列表 2B")。 // 数据本身仍为空(列表确实走登录包),但 wire 形状正确,客户端不会读空 buffer。 // ⛑ 回滚:把 return 换回 `conn.ReturnEmptyOkAsync(msg)`。 // func GameRouter_NpcChat_RequestNpcChatList(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_RequestNpcChatList var zero any; return zero } // AskCloseNpcChatWnd(67994672) — NpcChatChattingPanelStore.lua:635/1240, sent on the scene delegate. func GameRouter_NpcChat_AskCloseNpcChatWnd(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_AskCloseNpcChatWnd var zero any; return zero } // AskClearSpiritGroupChat(63527240) / AskClearNpcUncompletedInviteChat(63639364) — one-shot cleanups. func GameRouter_NpcChat_AskClearSpiritGroupChat(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_AskClearSpiritGroupChat var zero any; return zero } func GameRouter_NpcChat_AskClearNpcUncompletedInviteChat(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_NpcChat_AskClearNpcUncompletedInviteChat var zero any; return zero } // ================================================================================= // Row construction // ================================================================================= // // One pushed/downloaded history row. Field order and the complex root come from the client's own // reader — Auto.Reader[372] read via Base.ReadComplex, so the push starts with a // 0xFF marker and no other framing is involved. // func GameRouter_NpcChat_BuildNpcChatItem(ctx context.Context, chatId uint32, fromChatId uint32, read bool) any { // Router implementation for GameRouter_NpcChat_BuildNpcChatItem var zero any; return zero } // // Best-effort owner for a pushed row. The catalogue only stores owners for published threads, so // anything else falls back to the owner of the row the player came from — what really matters is // that it is non-zero and stable for a conversation, because it becomes the dictionary key. // func GameRouter_NpcChat_OwnerOf(ctx context.Context, chatId uint32, fromChatId uint32) uint32 { // Router implementation for GameRouter_NpcChat_OwnerOf return 0 }