This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

167 lines
7.2 KiB
Go
Raw Permalink Normal View History

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 字节,三次逐字节相同:
// <code>
// 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
// </code>
// ⇒ **露营这条路上,服务端只能自己按配表决定每个人穿什么**。
// ② 服务端此前把这件事整个丢掉了:`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` 打印原值):
// <code>
// 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] 「梅卡原皮」
// </code>
// ⚠ 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":..,"&lt;field&gt;":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
}