353 lines
13 KiB
Go
353 lines
13 KiB
Go
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"`
|
||
}
|
||
|