144 lines
7.3 KiB
Go
144 lines
7.3 KiB
Go
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<byte>()`)
|
||
// ⇒ 客户端 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
|
||
}
|
||
|