143 lines
4.9 KiB
Go
143 lines
4.9 KiB
Go
package catalogs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
var _ = json.RawMessage{}
|
|
var _ = sync.Mutex{}
|
|
var _ = os.ReadFile
|
|
var _ = filepath.Join
|
|
|
|
// One published message instance: the row itself plus the linear follow-up, both bare ids.
|
|
type NpcChatNode4229938 struct {
|
|
Id uint32 `json:"id"`
|
|
NextId uint32 `json:"nextId"`
|
|
}
|
|
|
|
//
|
|
// One conversation thread inside an owner's NpcChatListDict.
|
|
// Key is the dictionary key the login frame must use — see NpcChatCatalog4229938
|
|
// for why it mirrors NPCChatConfig.AsNpcCultivation. Type is the head row's
|
|
// ChatType (dump.cs:1326930 Normal=1 / Invite=2 / Dialog=3 / Fake=4) and is what lets a build
|
|
// decide whether story-triggered threads ride along in the login frame.
|
|
//
|
|
type NpcChatThread4229938 struct {
|
|
Key uint32 `json:"key"`
|
|
Type uint32 `json:"type"`
|
|
Items []NpcChatNode4229938 `json:"items"`
|
|
}
|
|
|
|
// One entry of either NpcChats (single NPC) or NpcGroupChats (group chat).
|
|
type NpcChatOwner4229938 struct {
|
|
Id uint32 `json:"id"`
|
|
Members []uint32 `json:"members"`
|
|
Threads []NpcChatThread4229938 `json:"threads"`
|
|
}
|
|
|
|
//
|
|
// Reads Configs/NpcChats.json — a projection of the client's own NPCChatConfig (6215 rows)
|
|
// and NPCChatGroupConfig (45 rows), built by _gen_npc_chats.py. This is what fills the 手机
|
|
// app BBChat (AppId 27 "Message" / SystemId 146).
|
|
// Why the login frame: there is no "pull the list" RPC for it. PlayerInfoMinorNpcCultivationData.lua:55
|
|
// copies InfoMinor.InfoNpcCultivation.NpcChats / .NpcGroupChats straight into the player
|
|
// data, NpcChatManager_Story.lua:28 converts them into m_npcChatsDict, and every list the
|
|
// app shows is a projection of that table. Nothing else ever refills it, so leaving those two lists
|
|
// empty is why the app had no conversations at all.
|
|
// The server publishes only the instance — which row is unlocked, on what thread, in what order. Text,
|
|
// speaker side, avatars and option labels come from the client's tables:
|
|
// NpcChatUtils.NewFromNpcChatItem resolves everything from LTConfig.NPCChatConfig.GetConfig(chatItem.ChatId),
|
|
// so an id that is not in NPCChatConfig is dropped with a "NPCChat表没有查到" error.
|
|
// Nodes keeps the whole NextMessage map (every row, not just the published ones) so the
|
|
// interact handlers can continue a conversation on rows the player reached themselves and that are
|
|
// therefore not part of any published thread.
|
|
// A missing or malformed file leaves the catalogue empty and LoadError explains why.
|
|
//
|
|
var (
|
|
NpcChatCatalog4229938_cacheLock sync.RWMutex
|
|
NpcChatCatalog4229938_cached = false
|
|
NpcChatCatalog4229938_entries []any
|
|
)
|
|
|
|
// LoadNpcChatCatalog4229938 loads catalog records from client config directory
|
|
func LoadNpcChatCatalog4229938(configDir string) ([]any, error) {
|
|
NpcChatCatalog4229938_cacheLock.RLock()
|
|
if NpcChatCatalog4229938_cached {
|
|
defer NpcChatCatalog4229938_cacheLock.RUnlock()
|
|
return NpcChatCatalog4229938_entries, nil
|
|
}
|
|
NpcChatCatalog4229938_cacheLock.RUnlock()
|
|
|
|
NpcChatCatalog4229938_cacheLock.Lock()
|
|
defer NpcChatCatalog4229938_cacheLock.Unlock()
|
|
if NpcChatCatalog4229938_cached { return NpcChatCatalog4229938_entries, nil }
|
|
|
|
filePath := filepath.Join(configDir, "NpcChatCatalog.json.json")
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var raw struct {
|
|
Records []json.RawMessage `json:"records"`
|
|
}
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result []any
|
|
for _, item := range raw.Records {
|
|
var m map[string]any
|
|
if err := json.Unmarshal(item, &m); err == nil {
|
|
result = append(result, m)
|
|
}
|
|
}
|
|
|
|
NpcChatCatalog4229938_entries = result
|
|
NpcChatCatalog4229938_cached = true
|
|
return result, nil
|
|
}
|
|
|
|
//
|
|
// Linear follow-up of any row, or 0 at the end of a conversation. NPCChatConfig.NextMessage
|
|
// can branch, index 0 is the continuation the client also uses for "点一下继续".
|
|
//
|
|
func NpcChatCatalog4229938_NextOf(chatId uint32) uint32 {
|
|
// Catalog helper NpcChatCatalog4229938_NextOf
|
|
return 0
|
|
}
|
|
|
|
func NpcChatCatalog4229938_Knows(chatId uint32) bool {
|
|
// Catalog helper NpcChatCatalog4229938_Knows
|
|
return false
|
|
}
|
|
|
|
//
|
|
// Every member id of a group, taken from NPCChatGroupConfig.GroupMember. Used only to give
|
|
// ClientNpcGroupChatData.Members something real — the client already knows who is in a group
|
|
// from its own table, so this is belt and braces for a hand-edited catalogue.
|
|
//
|
|
func NpcChatCatalog4229938_MembersOf(groupId uint32) []uint32 {
|
|
// Catalog helper NpcChatCatalog4229938_MembersOf
|
|
return nil
|
|
}
|
|
|
|
func NpcChatCatalog4229938_ThreadCountOf(npcs []NpcChatOwner4229938, groups []NpcChatOwner4229938) int32 {
|
|
// Catalog helper NpcChatCatalog4229938_ThreadCountOf
|
|
return 0
|
|
}
|
|
|
|
func NpcChatCatalog4229938_Owners(row any, nodes map[uint32][]uint32) *NpcChatOwner4229938 {
|
|
// Catalog helper NpcChatCatalog4229938_Owners
|
|
return nil
|
|
}
|
|
|
|
func NpcChatCatalog4229938_U32(source any, name string) uint32 {
|
|
// Catalog helper NpcChatCatalog4229938_U32
|
|
return 0
|
|
}
|
|
|