356 lines
12 KiB
Go
356 lines
12 KiB
Go
package catalogs
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"os"
|
|||
|
|
"path/filepath"
|
|||
|
|
"sync"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var _ = json.RawMessage{}
|
|||
|
|
var _ = sync.Mutex{}
|
|||
|
|
var _ = os.ReadFile
|
|||
|
|
var _ = filepath.Join
|
|||
|
|
|
|||
|
|
// 搜打撤物品。下发用的是 ConsumableId(8 位线上 id),不是配置行 id(9993xxxx)。
|
|||
|
|
type ExtractionShooterItemMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
ConsumableId uint32 `json:"consumableId"`
|
|||
|
|
RealSceneItemModelId uint32 `json:"realSceneItemModelId"`
|
|||
|
|
Type int32 `json:"type"`
|
|||
|
|
VolumeX int32 `json:"volumeX"`
|
|||
|
|
VolumeY int32 `json:"volumeY"`
|
|||
|
|
MaxStackNum int32 `json:"maxStackNum"`
|
|||
|
|
SearchTime int32 `json:"searchTime"`
|
|||
|
|
Quality int32 `json:"quality"`
|
|||
|
|
BasicValue int32 `json:"basicValue"`
|
|||
|
|
ReadInfoType int32 `json:"readInfoType"`
|
|||
|
|
CanTakeOn bool `json:"canTakeOn"`
|
|||
|
|
Durability int32 `json:"durability"`
|
|||
|
|
IfSafetyBox bool `json:"ifSafetyBox"`
|
|||
|
|
IfDiscard bool `json:"ifDiscard"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 容器(箱子 / 抽屉 / 子容器)。只有洗过规则的行才有刷物能力。
|
|||
|
|
type ExtractionShooterContainerMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
Type int32 `json:"type"`
|
|||
|
|
CapacityX int32 `json:"capacityX"`
|
|||
|
|
CapacityY int32 `json:"capacityY"`
|
|||
|
|
QuantityMin int32 `json:"quantityMin"`
|
|||
|
|
QuantityMax int32 `json:"quantityMax"`
|
|||
|
|
CertainItemList []uint32 `json:"certainItemList"`
|
|||
|
|
RandomItemGroups []any `json:"randomItemGroups"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 随机物品组。按 Weight 抽一件。
|
|||
|
|
type ExtractionShooterGroupMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Items []any `json:"items"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 背包。Type 决定它走 Bags 还是 SlotGroups
|
|||
|
|
// (0 背包 / 1 安全箱 / 2 仓库 → Bags;4 轮盘 / 5 战备 / 6 扩容 → SlotGroups)。
|
|||
|
|
//
|
|||
|
|
type ExtractionShooterBagMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
Type int32 `json:"type"`
|
|||
|
|
CapacityX int32 `json:"capacityX"`
|
|||
|
|
CapacityY int32 `json:"capacityY"`
|
|||
|
|
SlotInitial int32 `json:"slotInitial"`
|
|||
|
|
SlotMax int32 `json:"slotMax"`
|
|||
|
|
AllowedItemTypes []uint32 `json:"allowedItemTypes"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 扩容条目。Type=1 给 BelongBagId 加一行;Type=2 解锁 BagAddId 仓库。
|
|||
|
|
type ExtractionShooterExpansionMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Type int32 `json:"type"`
|
|||
|
|
BelongBagId uint32 `json:"belongBagId"`
|
|||
|
|
BagAddId uint32 `json:"bagAddId"`
|
|||
|
|
ExpandX int32 `json:"expandX"`
|
|||
|
|
ExpandY int32 `json:"expandY"`
|
|||
|
|
FrontExpansionIds []uint32 `json:"frontExpansionIds"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ExtractionShooterBirthPoint struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
X float32 `json:"x"`
|
|||
|
|
Y float32 `json:"y"`
|
|||
|
|
Z float32 `json:"z"`
|
|||
|
|
Weight int32 `json:"weight"`
|
|||
|
|
EvacuationPoints []uint32 `json:"evacuationPoints"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ExtractionShooterEvacuationPoint struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
X float32 `json:"x"`
|
|||
|
|
Y float32 `json:"y"`
|
|||
|
|
Z float32 `json:"z"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 图。BirthPointIds 指向 BirthPlaceConfig;表内没有 raid↔图外键。
|
|||
|
|
type ExtractionShooterMapMeta struct {
|
|||
|
|
Id uint32 `json:"id"`
|
|||
|
|
Name string `json:"name"`
|
|||
|
|
GlobalCountTime int32 `json:"globalCountTime"`
|
|||
|
|
BirthPointIds []uint32 `json:"birthPointIds"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 搜打撤配置表读取层。11 张表全部来自官方 ConfigDump(ClientData/4229938/Configs),
|
|||
|
|
// 与本服其它 catalog 一样用 Lazy 一次性解析,坏文件只记 LoadError 不抛。
|
|||
|
|
// 两张表是派生性质的,不在这里:
|
|||
|
|
// - ExtractionMonsterCatalog.json —— 捐赠端从 AgentConfig/RandomEnemyConfig 派生的自建表,
|
|||
|
|
// 官方 dump 里没有(我们这轮的 S1 不做铺怪,见硬规则:点位在关卡静态刷怪点)。
|
|||
|
|
// - DropConfig.ExtractionShooterItem —— 那是掉落表,不是容器刷物池,别拿它刷箱子。
|
|||
|
|
//
|
|||
|
|
const (
|
|||
|
|
// 轮盘 bag。物品进这里只是"存进格子",要让 Tab 能切还要走实体化(本轮不做)。
|
|||
|
|
ExtractionShooterCatalog4229938_WheelBagId uint32 = 99952004
|
|||
|
|
ExtractionShooterCatalog4229938_BackpackBagId uint32 = 99952001
|
|||
|
|
ExtractionShooterCatalog4229938_SafeBoxBagId uint32 = 99952002
|
|||
|
|
ExtractionShooterCatalog4229938_FirstStashBagId uint32 = 99952003
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
ExtractionShooterCatalog4229938_cacheLock sync.RWMutex
|
|||
|
|
ExtractionShooterCatalog4229938_cached = false
|
|||
|
|
ExtractionShooterCatalog4229938_entries []any
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// LoadExtractionShooterCatalog4229938 loads catalog records from client config directory
|
|||
|
|
func LoadExtractionShooterCatalog4229938(configDir string) ([]any, error) {
|
|||
|
|
ExtractionShooterCatalog4229938_cacheLock.RLock()
|
|||
|
|
if ExtractionShooterCatalog4229938_cached {
|
|||
|
|
defer ExtractionShooterCatalog4229938_cacheLock.RUnlock()
|
|||
|
|
return ExtractionShooterCatalog4229938_entries, nil
|
|||
|
|
}
|
|||
|
|
ExtractionShooterCatalog4229938_cacheLock.RUnlock()
|
|||
|
|
|
|||
|
|
ExtractionShooterCatalog4229938_cacheLock.Lock()
|
|||
|
|
defer ExtractionShooterCatalog4229938_cacheLock.Unlock()
|
|||
|
|
if ExtractionShooterCatalog4229938_cached { return ExtractionShooterCatalog4229938_entries, nil }
|
|||
|
|
|
|||
|
|
filePath := filepath.Join(configDir, "ExtractionShooterCatalog.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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ExtractionShooterCatalog4229938_entries = result
|
|||
|
|
ExtractionShooterCatalog4229938_cached = true
|
|||
|
|
return result, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 换局 / 重开时调用,让同一批容器实例 id 刷出新内容。
|
|||
|
|
func ExtractionShooterCatalog4229938_ReseedSessionSalt() {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_ReseedSessionSalt
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Item(configId uint32) *ExtractionShooterItemMeta {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Item
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 配置行 id → 下发给客户端的物品 id。3 条测试行没有 ConsumableId,退回配置行 id
|
|||
|
|
// (客户端那三条本来就换不出去,退成 0 会让整件物品显示成空白)。
|
|||
|
|
//
|
|||
|
|
func ExtractionShooterCatalog4229938_WireIdFor(configId uint32) uint32 {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_WireIdFor
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 线上物品 id(客户端发回来的那个)→ 配置行。是 WireIdFor 的反函数。
|
|||
|
|
// 客户端回传的一律是 ConsumableId(8 位),服务端内部记的是配置行 id(9993xxxx),
|
|||
|
|
// 所以「客户端说它把哪件东西放进了轮盘」必须走这里翻译,不能拿去查 Item。
|
|||
|
|
// 3 条测试行没有 ConsumableId ⇒ 退回配置行 id,与 WireIdFor 对称。
|
|||
|
|
//
|
|||
|
|
func ExtractionShooterCatalog4229938_ItemByWire(wireId uint32) *ExtractionShooterItemMeta {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_ItemByWire
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 物品类型是否带「详情读条」(ItemTypeConfig.ReadInfoType == 1)—— 实测就是武器与医疗道具。
|
|||
|
|
func ExtractionShooterCatalog4229938_CarriesInfo(meta ExtractionShooterItemMeta) bool {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_CarriesInfo
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 这件物品对应的军械武器实例 id(0 = 不是可实体化的武器)。
|
|||
|
|
// 链路:线上物品 id → 物品表 RealSceneItemModelId → SceneitemConfig 模板 →
|
|||
|
|
// 账号军械实例 id(CombatCatalogRepository.AccountInstanceId)。
|
|||
|
|
// 轮盘里的枪要靠它下发 WeaponDetail,牛奶 / 药剂在这一步返回 0 ⇒ 不实体化。
|
|||
|
|
//
|
|||
|
|
func ExtractionShooterCatalog4229938_WeaponInstanceOf(wireId uint32) uint64 {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_WeaponInstanceOf
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 该 bag 是否属于 SlotGroups 一侧。文档判据:Type ∈ {4,5,6} ⟺ SlotCapacity.max > 0。
|
|||
|
|
func ExtractionShooterCatalog4229938_IsSlotGroup(bag ExtractionShooterBagMeta) bool {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_IsSlotGroup
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 按容器实例 id 确定性选一个「有刷物规则」的容器。
|
|||
|
|
// 客户端搜容器只发 8 字节实例 id、不带 config id,所以选哪张表只能服务端自己定。
|
|||
|
|
// 用的是捐赠端那套 hash:mix = (id ^ (id>>32)) * 0x9E3779B97F4A7C15。
|
|||
|
|
// ⓿ **容量不加盐** —— 同一个箱子进图两次应该还是那个格子数。
|
|||
|
|
//
|
|||
|
|
func ExtractionShooterCatalog4229938_PickContainer(instanceId uint64) *ExtractionShooterContainerMeta {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_PickContainer
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 随机物品组。
|
|||
|
|
func ExtractionShooterCatalog4229938_Group(groupId uint32) *ExtractionShooterGroupMeta {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Group
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//
|
|||
|
|
// 玩家落点落在哪张图里。表里没有 raid↔图外键 ⇒ 只能用出生点的包围盒反查。
|
|||
|
|
//
|
|||
|
|
func ExtractionShooterCatalog4229938_MapAt(x float32, z float32) *ExtractionShooterMapMeta {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_MapAt
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_TryBirthPoint(id uint32, point ExtractionShooterBirthPoint) bool {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_TryBirthPoint
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_BasePath() string {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_BasePath
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Rows(tableName string) []any {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Rows
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ⛑️⛑️ JsonElement.TryGetInt32 / TryGetUInt32 **不是**「取不到就返回 false」——
|
|||
|
|
// 元素不是 Number 时它们直接**抛** InvalidOperationException
|
|||
|
|
// ("The requested operation requires an element of type 'Number', but the target element has type 'False'")。
|
|||
|
|
// 实测:TryGetInt32(False) / TryGetInt32(True) / TryGetUInt32(False) 全抛,只有 ValueKind 先判才安全。
|
|||
|
|
// 官方配置里布尔字段是**真 true/false**(不是 0/1):ItemTypeConfig.CanTakeOn 13 条里 5 条 false、
|
|||
|
|
// ItemConfig.CanBringOut 314 条里 113 条 false ⇒ 不加守卫的表一读到就整块 catch 掉。
|
|||
|
|
// 所以下面三个取值器一律先判 ValueKind 再 Try。
|
|||
|
|
func ExtractionShooterCatalog4229938_U32(e any, name string) uint32 {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_U32
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_I32(e any, name string) int32 {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_I32
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Bool(e any, name string) bool {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Bool
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Str(e any, name string) string {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Str
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_U32List(e any, name string) []uint32 {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_U32List
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Int2(e any, name string) any {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Int2
|
|||
|
|
var zero any; return zero
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ExtractionShooterCatalog4229938_Int2Initial(e any, name string) any {
|
|||
|
|
// Catalog helper ExtractionShooterCatalog4229938_Int2Initial
|
|||
|
|
var zero any; return zero
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- load
|
|||
|
|
var (
|
|||
|
|
Store_cacheLock sync.RWMutex
|
|||
|
|
Store_cached = false
|
|||
|
|
Store_entries []any
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// LoadStore loads catalog records from client config directory
|
|||
|
|
func LoadStore(configDir string) ([]any, error) {
|
|||
|
|
Store_cacheLock.RLock()
|
|||
|
|
if Store_cached {
|
|||
|
|
defer Store_cacheLock.RUnlock()
|
|||
|
|
return Store_entries, nil
|
|||
|
|
}
|
|||
|
|
Store_cacheLock.RUnlock()
|
|||
|
|
|
|||
|
|
Store_cacheLock.Lock()
|
|||
|
|
defer Store_cacheLock.Unlock()
|
|||
|
|
if Store_cached { return Store_entries, nil }
|
|||
|
|
|
|||
|
|
filePath := filepath.Join(configDir, "Store.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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Store_entries = result
|
|||
|
|
Store_cached = true
|
|||
|
|
return result, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func Store_NewSalt() {
|
|||
|
|
// Catalog helper Store_NewSalt
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func Store_TryBounds(mapId uint32, box any) bool {
|
|||
|
|
// Catalog helper Store_TryBounds
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|