168 lines
7.2 KiB
Go
168 lines
7.2 KiB
Go
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":..,"<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
|
||
}
|
||
|