// combo_table —— PC 连击段表(CRaceData::TComboAttackDataMap 的等价)。M2 §3.5。 // // 参考端的 PC 连击段表**不在** .msm 里(.msm 只有 BaseModelFileName + HairData)。 // 它由 playersettingmodule.py 的 chrmgr.ReserveComboAttackNew / RegisterComboAttackNew // 调用在 CRaceData 上建起来(UserInterface/PythonCharacterManagerModule.cpp → // CRaceData::ReserveComboAttack / RegisterComboAttack,GameLib/RaceData.cpp)。 // 本解析器就把那批调用读成同一张表。 // // NPC/怪物的连击段走另一条路(RaceManager.cpp __LoadRaceMotionList 读 motlist.txt, // 只在 MODE_GENERAL 下登记 NAME_COMBO_ATTACK_1/2/3,没有段号向量),不在这里。 #pragma once #include #include #include #include namespace fmt { // CRaceMotionData::EMode(chr.MOTION_MODE_*)。combo key 的高 16 位。 enum ComboMotionMode : uint16_t { COMBO_MODE_RESERVED = 0, COMBO_MODE_GENERAL = 1, COMBO_MODE_ONEHAND_SWORD = 2, COMBO_MODE_TWOHAND_SWORD = 3, COMBO_MODE_DUALHAND_SWORD = 4, COMBO_MODE_BOW = 5, COMBO_MODE_FAN = 6, COMBO_MODE_BELL = 7, COMBO_MODE_FISHING = 8, COMBO_MODE_HORSE = 9, COMBO_MODE_HORSE_ONEHAND_SWORD = 10, COMBO_MODE_HORSE_TWOHAND_SWORD = 11, COMBO_MODE_HORSE_DUALHAND_SWORD = 12, COMBO_MODE_HORSE_BOW = 13, COMBO_MODE_HORSE_FAN = 14, COMBO_MODE_HORSE_BELL = 15, COMBO_MODE_WEDDING_DRESS = 16, }; // CRaceMotionData::EName 的攻击段(chr.MOTION_NORMAL_ATTACK / chr.MOTION_COMBO_ATTACK_*)。 // ComboIndexVector 里存的就是这些段号。 enum : uint16_t { COMBO_NAME_NORMAL_ATTACK = 13, COMBO_NAME_COMBO_ATTACK_1 = 14, COMBO_NAME_COMBO_ATTACK_8 = 21, }; // RaceData.h: MAKE_COMBO_KEY(motion_mode, combo_type) inline uint32_t make_combo_key(uint16_t motion_mode, uint16_t combo_type) { return (static_cast(motion_mode) << 16) | static_cast(combo_type); } inline uint16_t combo_key_mode(uint32_t key) { return static_cast((key >> 16) & 0xFFFF); } inline uint16_t combo_key_type(uint32_t key) { return static_cast(key & 0xFFFF); } // playersettingmodule.py 按 __LoadGameEx(race, path) 分四块,男女同一函数体 // (连击段表与性别无关),所以这里按职业收,不按 race。 enum ComboClass : int { COMBO_CLASS_WARRIOR = 0, COMBO_CLASS_ASSASSIN = 1, COMBO_CLASS_SURA = 2, COMBO_CLASS_SHAMAN = 3, COMBO_CLASS_COUNT = 4, }; // CRaceData::TComboAttackDataMap 的等价:MAKE_COMBO_KEY -> ComboIndexVector。 struct ComboTable { // key = make_combo_key(mode, type);value = 有序的 COMBO_NAME_COMBO_ATTACK_* 段号, // 下标即 dwComboArrayIndex(m_dwcurComboIndex - 1)。 std::map> combos; // GetComboDataPointer:命中返回段号向量指针,否则 nullptr。 const std::vector* get(uint16_t motion_mode, uint16_t combo_type) const; bool empty() const { return combos.empty(); } size_t key_count() const { return combos.size(); } }; struct PlayerComboTables { ComboTable per_class[COMBO_CLASS_COUNT]; // 越界返回一张空表(不抛)。 const ComboTable& klass(int combo_class) const; }; // 解析 playersettingmodule.py 里的 chrmgr.ReserveComboAttackNew / RegisterComboAttackNew // 调用,按 def __LoadGameEx 分块填 per_class。照 CRaceData 语义: // Reserve -> combos[key] = vector(count, 0)(重复 key 不覆盖,同 std::map::insert) // Register -> combos[key][index] = motion(key 未 Reserve 或 index 越界则跳过) // 认得的符号:chr.MOTION_MODE_*、chr.MOTION_COMBO_ATTACK_N(=13+N)、 // chr.MOTION_NORMAL_ATTACK(=13)、COMBO_TYPE_N(=N-1)、COMBO_INDEX_N(=N-1)、裸整数。 bool parse_player_combo_tables(const std::string& py_text, PlayerComboTables& out, std::string* err); bool parse_player_combo_tables_file(const std::string& path, PlayerComboTables& out, std::string* err); } // namespace fmt