package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // Build-4229938 combat catalog reconstructed from the unmodified extracted client tables. // It is the single source of truth for weapon slots, weapon-specific fight styles and skill bindings. // const ( // // FightSkillFightSkillTypeConfig 39020005 =「徒手」。武器 = Fists 80003412 // (FightSpiritConfig.FistSceneItem)。官方该类别只有 Street Brawler(39000005) 一个武学, // 它没有终结技;私服允许徒手借用角色自带武学,判据见 ResolveStyle。 // CombatCatalogRepository_BarehandFightSkillTypeId uint32 = 39020005 ) var ( CombatCatalogRepository_cacheLock sync.RWMutex CombatCatalogRepository_cached = false CombatCatalogRepository_entries []any ) // LoadCombatCatalogRepository loads catalog records from client config directory func LoadCombatCatalogRepository(configDir string) ([]any, error) { CombatCatalogRepository_cacheLock.RLock() if CombatCatalogRepository_cached { defer CombatCatalogRepository_cacheLock.RUnlock() return CombatCatalogRepository_entries, nil } CombatCatalogRepository_cacheLock.RUnlock() CombatCatalogRepository_cacheLock.Lock() defer CombatCatalogRepository_cacheLock.Unlock() if CombatCatalogRepository_cached { return CombatCatalogRepository_entries, nil } filePath := filepath.Join(configDir, "CombatCatalog.jsonRepository.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) } } CombatCatalogRepository_entries = result CombatCatalogRepository_cached = true return result, nil } func CombatCatalogRepository_Loadout(spiritTemplateId uint32) CombatLoadout { // Catalog helper CombatCatalogRepository_Loadout var zero CombatLoadout; return zero } // // Null instead of throwing when the catalog has no loadout for this template. Anything that walks // persisted data (player-progress.json) or the configured roster must use this: a spirit id that // the catalog does not know — a removed character, a stale save row — used to throw straight out // of LoginGame, so the client sat on a gateway timeout and fell back to "select a server" with the // account completely unable to log in. // func CombatCatalogRepository_TryLoadout(spiritTemplateId uint32) *CombatLoadout { // Catalog helper CombatCatalogRepository_TryLoadout return nil } func CombatCatalogRepository_Weapon(instanceId uint64) *CombatWeaponDefinition { // Catalog helper CombatCatalogRepository_Weapon return nil } func CombatCatalogRepository_Weapon_2(spiritTemplateId uint32, instanceId uint64) *CombatWeaponDefinition { // Catalog helper CombatCatalogRepository_Weapon_2 return nil } // // 按「账号军械实例 id + 当前角色」取武器定义,带归属校验。 // 与 Weapon(ulong) 的差别:Weapon(instanceId) 命中就返回,不检查这把武器 // 属于哪个角色;切过角色之后,同一个实例 id 若挂在当前角色名下之外,拿到的就是别人的 // 定义(FightSkillType / 流派 / 弹药全错,表现为「枪能切出来但技能不对」)。 // 这里的顺序:先取账号武器 → 若 spiritTemplateId == 0(不关心归属)或归属一致就用它 → // 否则改查 (角色, 实例) 表,让「这个角色确实拥有的那份定义」优先。 // 移植自捐赠端 v1.7(CombatCatalogRepository.WeaponByAccountInstance)。 // ⛑️ 未经实机判定:不确定 AccountWeaponsByInstance 是否跨角色撞 id。若切角色后轮盘的枪 // 反而取不到定义,回滚本文件到 .bak-20260926-v17-weaponowner。 // func CombatCatalogRepository_WeaponByAccountInstance(instanceId uint64, spiritTemplateId uint32) *CombatWeaponDefinition { // Catalog helper CombatCatalogRepository_WeaponByAccountInstance return nil } // // SceneitemConfig 模板 id → 账号军械库给它分配的实例 id(0 = 该模板不是军械武器)。 // 搜打撤的 bag 里只有线上物品 id(ConsumableId),要把一件枪实体化成 // WeaponDetail 还缺「SceneitemConfig 模板 + 稳定实例 id」,两个都从这里出。 // 之所以必须复用军械库那一份实例 id 而不是自造:客户端 WeaponManager.weaponDict // 与服务端 CombatCodec.Weapon(spirit, ActiveWeaponInstanceId) 都按它解析技能 / // 弹药 / 流派,自造 ⇒ 技能全部回落到默认武器。代价:同模板放两格时实例 id 相同(行为一致)。 // 牛奶 / 药剂这类道具也有 RealSceneItemModelId,但它们的 FightSkillType = 0、不在军械库里 // ⇒ 这里返回 0 ⇒ 不实体化(仍然正常显示为轮盘物品,只是不进 Tab 循环)。 // func CombatCatalogRepository_AccountInstanceId(templateId uint32) uint64 { // Catalog helper CombatCatalogRepository_AccountInstanceId return 0 } func CombatCatalogRepository_Style(styleId uint32) *CombatStyleDefinition { // Catalog helper CombatCatalogRepository_Style return nil } func CombatCatalogRepository_ResourceMaximum(resourceId uint32, fallback float32) float32 { // Catalog helper CombatCatalogRepository_ResourceMaximum return 0 } func CombatCatalogRepository_SkillCastTag(skillId uint32) uint32 { // Catalog helper CombatCatalogRepository_SkillCastTag return 0 } func CombatCatalogRepository_IsKnownSkill(skillId uint32) bool { // Catalog helper CombatCatalogRepository_IsKnownSkill return false } func CombatCatalogRepository_IsNativeShootSkill(skillId uint32) bool { // Catalog helper CombatCatalogRepository_IsNativeShootSkill return false } func CombatCatalogRepository_IsStyleCompatible(weapon CombatWeaponDefinition, style CombatStyleDefinition) bool { // Catalog helper CombatCatalogRepository_IsStyleCompatible return false } func CombatCatalogRepository_StyleMatchesType(styleId uint32, typeId uint32) bool { // Catalog helper CombatCatalogRepository_StyleMatchesType return false } func CombatCatalogRepository_ResolveStyle(weapon WeaponSource, effectiveFightSkillTypeId uint32, spiritId uint32, initialStyleIds []uint32, styles map[uint32]CombatStyleDefinition, defaultStyleByType map[uint32]uint32, config any) *CombatStyleDefinition { // Catalog helper CombatCatalogRepository_ResolveStyle return nil } func CombatCatalogRepository_ResolveAuthoredAccountStyle(weapon WeaponSource, effectiveFightSkillTypeId uint32, styles map[uint32]CombatStyleDefinition, defaultStyleByType map[uint32]uint32) *CombatStyleDefinition { // Catalog helper CombatCatalogRepository_ResolveAuthoredAccountStyle return nil } func CombatCatalogRepository_IsCompleteAbilityStyle(style *CombatStyleDefinition) bool { // Catalog helper CombatCatalogRepository_IsCompleteAbilityStyle return false } func CombatCatalogRepository_AbilityCompleteness(style CombatStyleDefinition) int32 { // Catalog helper CombatCatalogRepository_AbilityCompleteness return 0 } func CombatCatalogRepository_ParseStyle(value any, skills map[uint32]SkillMetadata, jumpTargets map[uint32]uint32, genericJumpTargets map[uint32][]uint32) *CombatStyleDefinition { // Catalog helper CombatCatalogRepository_ParseStyle return nil } func CombatCatalogRepository_ParseSkillMetadata(value any) *SkillMetadata { // Catalog helper CombatCatalogRepository_ParseSkillMetadata return nil } func CombatCatalogRepository_ParseWeaponSource(value any, shootById map[uint32]WeaponShootSource, legacyById map[uint32]LegacyWeaponSource) *WeaponSource { // Catalog helper CombatCatalogRepository_ParseWeaponSource return nil } func CombatCatalogRepository_ReadRecords(path string) []any { // Catalog helper CombatCatalogRepository_ReadRecords return nil } func CombatCatalogRepository_ReadUInt32Array(value any, name string) []uint32 { // Catalog helper CombatCatalogRepository_ReadUInt32Array return nil } func CombatCatalogRepository_ReadStringArray(value any, name string) []string { // Catalog helper CombatCatalogRepository_ReadStringArray return nil } func CombatCatalogRepository_TryUInt32(value any, name string, result uint32) bool { // Catalog helper CombatCatalogRepository_TryUInt32 return false } func CombatCatalogRepository_TryInt32(value any, name string, result int32) bool { // Catalog helper CombatCatalogRepository_TryInt32 return false } func CombatCatalogRepository_TrySingle(value any, name string, result float32) bool { // Catalog helper CombatCatalogRepository_TrySingle return false } func CombatCatalogRepository_TryString(value any, name string) *string { // Catalog helper CombatCatalogRepository_TryString return nil } func CombatCatalogRepository_TryBool(value any, name string) bool { // Catalog helper CombatCatalogRepository_TryBool return false } type CombatCatalog struct { Loadouts map[uint32]CombatLoadout `json:"loadouts"` AccountWeapons []CombatWeaponDefinition `json:"accountWeapons"` AccountWeaponsByInstance map[uint64]CombatWeaponDefinition `json:"accountWeaponsByInstance"` WeaponsBySpiritAndInstance map[any]CombatWeaponDefinition `json:"weaponsBySpiritAndInstance"` Styles map[uint32]CombatStyleDefinition `json:"styles"` DefaultStyleByType map[uint32]uint32 `json:"defaultStyleByType"` UnlockedStyleIds []uint32 `json:"unlockedStyleIds"` ResourceMaximums map[uint32]float32 `json:"resourceMaximums"` SkillCastTags map[uint32]uint32 `json:"skillCastTags"` AmmoTemplateIds []uint32 `json:"ammoTemplateIds"` } type WeaponSource struct { TemplateId uint32 `json:"templateId"` Name string `json:"name"` LegacyWeaponId uint32 `json:"legacyWeaponId"` OwnerSpiritId uint32 `json:"ownerSpiritId"` FightSkillTypeId uint32 `json:"fightSkillTypeId"` FixedFightStyleId uint32 `json:"fixedFightStyleId"` Durability int32 `json:"durability"` MagazineAmmo int32 `json:"magazineAmmo"` BulletIds []uint32 `json:"bulletIds"` IsShootWeapon bool `json:"isShootWeapon"` SeparateBullets bool `json:"separateBullets"` ReloadSkillId uint32 `json:"reloadSkillId"` WheelIconId uint32 `json:"wheelIconId"` NotEnterRoulette bool `json:"notEnterRoulette"` } type WeaponShootSource struct { IsShootWeapon bool `json:"isShootWeapon"` SeparateBullets bool `json:"separateBullets"` BulletIds []uint32 `json:"bulletIds"` ReloadSkillId uint32 `json:"reloadSkillId"` } type LegacyWeaponSource struct { OwnerSpiritId uint32 `json:"ownerSpiritId"` FightSkillTypeId uint32 `json:"fightSkillTypeId"` FixedFightStyleId uint32 `json:"fixedFightStyleId"` } type SpiritCombatSource struct { InitialStyleIds []uint32 `json:"initialStyleIds"` FistSceneItemId uint32 `json:"fistSceneItemId"` PrivateSceneItemId uint32 `json:"privateSceneItemId"` GeneralSceneItemIds []uint32 `json:"generalSceneItemIds"` WeaponSlotNames []string `json:"weaponSlotNames"` } type SkillMetadata struct { Id uint32 `json:"id"` Cooldown float32 `json:"cooldown"` MaximumCharges uint32 `json:"maximumCharges"` CastTypeTag uint32 `json:"castTypeTag"` TransitionIds []uint32 `json:"transitionIds"` RelatedSkills []uint32 `json:"relatedSkills"` } type CombatLoadout struct { SpiritTemplateId uint32 `json:"spiritTemplateId"` Weapons []CombatWeaponDefinition `json:"weapons"` Slots []*CombatWeaponDefinition `json:"slots"` DefaultWeapon CombatWeaponDefinition `json:"defaultWeapon"` StyleByType map[uint32]uint32 `json:"styleByType"` } type CombatWeaponDefinition struct { SpiritTemplateId uint32 `json:"spiritTemplateId"` TemplateId uint32 `json:"templateId"` InstanceId uint64 `json:"instanceId"` Name string `json:"name"` Durability int32 `json:"durability"` MagazineAmmo int32 `json:"magazineAmmo"` BulletId uint32 `json:"bulletId"` BulletIds []uint32 `json:"bulletIds"` IsShootWeapon bool `json:"isShootWeapon"` SeparateBullets bool `json:"separateBullets"` ReloadSkillId uint32 `json:"reloadSkillId"` IsPrivate bool `json:"isPrivate"` WeaponFightSkillTypeId uint32 `json:"weaponFightSkillTypeId"` Style CombatStyleDefinition `json:"style"` AbilityFallbackStyle CombatStyleDefinition `json:"abilityFallbackStyle"` InitialMagazineAmmo int32 `json:"initialMagazineAmmo"` // Separate-ammo guns use Durability as the loaded-ammo value in the shared Armory UI, // while ordinary guns use it as total remaining ammo (magazine + reserve). InitialClientDurability int32 `json:"initialClientDurability"` } type CombatStyleDefinition struct { Id uint32 `json:"id"` Name string `json:"name"` FightSkillTypeId uint32 `json:"fightSkillTypeId"` ActionType uint32 `json:"actionType"` SpiritIds any `json:"spiritIds"` ShowInArmory bool `json:"showInArmory"` CommonSkill uint32 `json:"commonSkill"` PressCommonSkill uint32 `json:"pressCommonSkill"` HeavyCommonSkill uint32 `json:"heavyCommonSkill"` ControlSkill uint32 `json:"controlSkill"` DodgeSkill uint32 `json:"dodgeSkill"` DodgeAttackSkill uint32 `json:"dodgeAttackSkill"` GrappleAttackSkill uint32 `json:"grappleAttackSkill"` ActiveSkill uint32 `json:"activeSkill"` UniqueSkill uint32 `json:"uniqueSkill"` SwitchSkill uint32 `json:"switchSkill"` AllowedSkillIds any `json:"allowedSkillIds"` SkillCooldowns map[uint32]float32 `json:"skillCooldowns"` SkillMaximumCharges map[uint32]uint32 `json:"skillMaximumCharges"` }