package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // 「派对 NPC 穿什么」的四张官方配表 join(2026-09-26 第 53 轮)。 // WHY THIS FILE EXISTS // -------------------- // 用户现场:「派对功能可以自订邀请角色的外观服装,但是选择后开始露营显示的还是默认服装」。 // 全部结论都有实证,不是推断: // ① 客户端在**露营**这条路上送不出玩家选的服装。 // `PartyManager.lua:594-621` 弹 `MessageConfig.PartyChangeCloth` // (= 75109995「即将开始派对,是否要进行换装?」,按钮 ["使用默认套装","自行搭配"]): // · 「使用默认套装」= 第 1 个按钮 → 第 2 个实参 // → RequestStartSingleParty(partyId, npcIdList, nil, true) // → fashionsInfo = { UseDefaultFashion = true, Rents = {} } // · 「自行搭配」= 第 2 个按钮 → 第 3 个实参 → gDressManager:EnterChangeFashion(...) // (rentCb = function(rentMap) RequestStartSingleParty(partyId, npcIdList, rentMap) end) // ⛔ 而 PartyConfig#13030000(新启露营)的 FashionsCanRent = [](空), // 舞会 #13030003 才是 [11199901, 11199900]。 // `ChangeFashionPanelStore_RentMode.lua:555 BuildRentSuitList` 只从 // partyCfg.FashionsCanRent 取租借套装 ⇒ **露营的租借列表恒空** // ⇒ rentMap 恒空 ⇒ `elseif rentMap and next(rentMap)` 不成立 ⇒ fashionsInfo = nil。 // 实测三连(`packets-20260926-201341.log:15642` / `:49032`、`packets-latest.log:44434`) // —— 30 字节,三次逐字节相同: // // 70 D2 C6 00 | FF 06 | 1E 50 6D 05 24 50 6D 05 27 50 6D 05 2E 50 6D 05 23 50 6D 05 | FF FF 01 01 // partyId npcs(5) 91050014 91050020 91050023 91050030 91050019 Rents={} UseDefaultFashion=true // // ⇒ **露营这条路上,服务端只能自己按配表决定每个人穿什么**。 // ② 服务端此前把这件事整个丢掉了:`StartSingleParty4229938` 只裸读前 4 字节拿 partyId, // 第三个参数从未解析;且 `SpawnPartyNpcsAsync` 恒传 SuitId = 0 // ⇒ `GameRouter.Civilians.cs:585` / `DonorNpc.cs:248` 的 // FashionSuitId = dress is { SuitId: not 0 } ? dress.SuitId : 11190001 // 把所有角色都落成 11190001「Graffiti Set」= Bansy 的涂鸦套装。 // 实测日志 dress=uid=100 spirit=15020992 suit=0 pieces= hidden=0(5 人全 suit=0) // ⇒ 这就是用户看到的「默认服装」。 // ③ 「这个角色该穿哪套」官方配表里有,而且**按角色 + 按玩法标签**给: // FashionSpiritDressCodeConfig(33 条 / 18 个角色)= SpiritId · SuitId · Name · // RecommendPriority(1 = 原皮,5/10 = 推荐搭配)· TagList; // 与 PartyConfig.SuitTag 对撞(露营 #13030000 = 1,舞会 #13030003 = 2)。 // 本次露营实际邀请的 5 人逐条命中(`gym_tools\_r77_partyfashion_probe.py` 打印原值): // // 15020992 塔菲 → 11190003 Grind Knight prio 1 tags [0,1] 「塔菲原皮」 // 15021021 里希 → 11190013 NCCA Set · Modified prio 1 tags [0,1] 「里希原皮」 // 15021022 林檎 → 11190122 suit林檎 prio 1 tags [0,1] 「林檎原皮」 // 15021029 mimiko → 11190018 Top Dog Gamer prio 1 tags [0,1] 「mimiko原皮」 // 15021017 梅卡 → 11190011 Mecha's Workwear prio 1 tags [0,1] 「梅卡原皮」 // // ⚠ tag 匹配要放宽:18 个角色里只有 15021040 有 TagList ∋ 2 的条目, // 舞会(SuitTag = 2)必须能退到「任意 tag 里优先级最小的那条」,不能因为查不到就不穿。 // ④ 真选到租赁套装时(目前只有舞会配了 FashionsCanRent),客户端发的是 // Rents[spiritId] = rentCfgId,而 rentCfgId 是 FashionRentConfig.Id // (`ChangeFashionPanelStore_RentMode.lua:704/722-728`,反查同源 // `GetRentConfigBySuitId`)⇒ 还要再跳一层 // FashionRentConfig[rentCfgId].FashionSuitId 才是套装 id。 // ⚠ 与 `PartySpotCatalog4229938` 同一条设计:**本表允许缺失**。缺文件 / 坏 JSON ⇒ 空表, // 调用方回落 SuitId = 0(= 旧行为 11190001),**派对功能不会因此挂掉**。 // var ( PartyFashionCatalog4229938_cacheLock sync.RWMutex PartyFashionCatalog4229938_cached = false PartyFashionCatalog4229938_entries []any ) // LoadPartyFashionCatalog4229938 loads catalog records from client config directory func LoadPartyFashionCatalog4229938(configDir string) ([]any, error) { PartyFashionCatalog4229938_cacheLock.RLock() if PartyFashionCatalog4229938_cached { defer PartyFashionCatalog4229938_cacheLock.RUnlock() return PartyFashionCatalog4229938_entries, nil } PartyFashionCatalog4229938_cacheLock.RUnlock() PartyFashionCatalog4229938_cacheLock.Lock() defer PartyFashionCatalog4229938_cacheLock.Unlock() if PartyFashionCatalog4229938_cached { return PartyFashionCatalog4229938_entries, nil } filePath := filepath.Join(configDir, "PartyFashionCatalog.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) } } PartyFashionCatalog4229938_entries = result PartyFashionCatalog4229938_cached = true return result, nil } // 配表给的 SuitTag;查不到给 0(调用方会跳过 tag 精确匹配)。 func PartyFashionCatalog4229938_SuitTagOf(partyId uint32) uint32 { // Catalog helper PartyFashionCatalog4229938_SuitTagOf return 0 } // 客户端发来的 rentCfgId → 真正的套装 id。 func PartyFashionCatalog4229938_TryRentSuit(rentCfgId uint32, suitId uint32) bool { // Catalog helper PartyFashionCatalog4229938_TryRentSuit return false } // // 该角色在「本玩法标签」下的默认套装。先按 精确匹配; // = 0 或查不到时退到「任意 tag 里优先级最小的那条」。 // func PartyFashionCatalog4229938_TryDressCodeSuit(spiritId uint32, tag uint32, suitId uint32) bool { // Catalog helper PartyFashionCatalog4229938_TryDressCodeSuit return false } // 套装名(日志用)。查不到返回空串。 func PartyFashionCatalog4229938_SuitNameOf(suitId uint32) string { // Catalog helper PartyFashionCatalog4229938_SuitNameOf return "" } func PartyFashionCatalog4229938_Resolve(fileName string) string { // Catalog helper PartyFashionCatalog4229938_Resolve return "" } // 读一张 `{"records":[{"Id":..,"<field>":u32}, ...]}` 形状的表成 Id → u32。 func PartyFashionCatalog4229938_LoadIdMap(fileName string, valueField string) map[uint32]uint32 { // Catalog helper PartyFashionCatalog4229938_LoadIdMap return nil } func PartyFashionCatalog4229938_LoadSuitName() map[uint32]string { // Catalog helper PartyFashionCatalog4229938_LoadSuitName return nil } func PartyFashionCatalog4229938_LoadDressCode() any { // Catalog helper PartyFashionCatalog4229938_LoadDressCode var zero any; return zero } func PartyFashionCatalog4229938_U32(row any, name string) uint32 { // Catalog helper PartyFashionCatalog4229938_U32 return 0 }