Files

187 lines
7.3 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// M2 §3.5 —— playersettingmodule.py 的 ComboAttackNew 调用 -> PC 连击段表。
#include "combo_table.h"
#include "m2_tokvec.h" // fmt::read_file
#include <cctype>
#include <cstdlib>
namespace fmt {
const std::vector<uint16_t>* ComboTable::get(uint16_t motion_mode, uint16_t combo_type) const {
auto it = combos.find(make_combo_key(motion_mode, combo_type));
return it == combos.end() ? nullptr : &it->second;
}
const ComboTable& PlayerComboTables::klass(int combo_class) const {
static const ComboTable kEmpty;
if (combo_class < 0 || combo_class >= COMBO_CLASS_COUNT) return kEmpty;
return per_class[combo_class];
}
namespace {
std::string trim(const std::string& s) {
size_t a = 0, b = s.size();
while (a < b && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
return s.substr(a, b - a);
}
bool starts_with(const std::string& s, const char* p) {
return s.rfind(p, 0) == 0;
}
// chr.MOTION_MODE_XXX -> CRaceMotionData::EMode 值。
bool resolve_motion_mode(const std::string& suffix, long& out) {
static const std::map<std::string, long> kMap = {
{"RESERVED", 0}, {"GENERAL", 1}, {"ONEHAND_SWORD", 2}, {"TWOHAND_SWORD", 3},
{"DUALHAND_SWORD", 4}, {"BOW", 5}, {"FAN", 6}, {"BELL", 7}, {"FISHING", 8},
{"HORSE", 9}, {"HORSE_ONEHAND_SWORD", 10}, {"HORSE_TWOHAND_SWORD", 11},
{"HORSE_DUALHAND_SWORD", 12}, {"HORSE_BOW", 13}, {"HORSE_FAN", 14},
{"HORSE_BELL", 15}, {"WEDDING_DRESS", 16},
};
auto it = kMap.find(suffix);
if (it == kMap.end()) return false;
out = it->second;
return true;
}
// COMBO_TYPE_N / COMBO_INDEX_N -> N-1(两者都是从 1 起的人读编号,值从 0 起)。
bool resolve_one_based(const std::string& tail, long& out) {
if (tail.empty()) return false;
char* end = nullptr;
long n = std::strtol(tail.c_str(), &end, 10);
if (end == tail.c_str() || *end != '\0' || n < 1) return false;
out = n - 1;
return true;
}
// 一个实参 token -> 整数。认符号常量,也认裸整数。
bool resolve_token(const std::string& tok_in, long& out) {
const std::string tok = trim(tok_in);
if (tok.empty()) return false;
if (starts_with(tok, "chr.MOTION_MODE_")) {
return resolve_motion_mode(tok.substr(sizeof("chr.MOTION_MODE_") - 1), out);
}
if (starts_with(tok, "chr.MOTION_COMBO_ATTACK_")) {
if (!resolve_one_based(tok.substr(sizeof("chr.MOTION_COMBO_ATTACK_") - 1), out)) return false;
out = COMBO_NAME_COMBO_ATTACK_1 + out; // 0-based n -> 段号 14 + n
return true;
}
if (tok == "chr.MOTION_NORMAL_ATTACK") { out = COMBO_NAME_NORMAL_ATTACK; return true; }
if (starts_with(tok, "COMBO_TYPE_")) { return resolve_one_based(tok.substr(sizeof("COMBO_TYPE_") - 1), out); }
if (starts_with(tok, "COMBO_INDEX_")) { return resolve_one_based(tok.substr(sizeof("COMBO_INDEX_") - 1), out); }
// 裸整数
char* end = nullptr;
long n = std::strtol(tok.c_str(), &end, 10);
if (end == tok.c_str() || *end != '\0') return false;
out = n;
return true;
}
// "a, b, c)"call 名后的部分)-> 解析出的整数实参。分号里没有嵌套括号。
bool parse_call_args(const std::string& after_paren, std::vector<long>& out) {
size_t close = after_paren.find(')');
if (close == std::string::npos) return false;
const std::string inner = after_paren.substr(0, close);
out.clear();
size_t start = 0;
while (start <= inner.size()) {
size_t comma = inner.find(',', start);
std::string tok = inner.substr(start, comma == std::string::npos ? std::string::npos : comma - start);
tok = trim(tok);
if (!tok.empty()) {
long v = 0;
if (!resolve_token(tok, v)) return false;
out.push_back(v);
}
if (comma == std::string::npos) break;
start = comma + 1;
}
return true;
}
int class_from_def_line(const std::string& line) {
if (line.find("def __LoadGameWarriorEx") != std::string::npos) return COMBO_CLASS_WARRIOR;
if (line.find("def __LoadGameAssassinEx") != std::string::npos) return COMBO_CLASS_ASSASSIN;
if (line.find("def __LoadGameSuraEx") != std::string::npos) return COMBO_CLASS_SURA;
if (line.find("def __LoadGameShamanEx") != std::string::npos) return COMBO_CLASS_SHAMAN;
return -1;
}
} // namespace
bool parse_player_combo_tables(const std::string& py_text, PlayerComboTables& out, std::string* err) {
out = PlayerComboTables{};
int cur_class = -1;
size_t pos = 0;
const size_t n = py_text.size();
while (pos < n) {
size_t eol = py_text.find('\n', pos);
std::string line = py_text.substr(pos, eol == std::string::npos ? std::string::npos : eol - pos);
pos = (eol == std::string::npos) ? n : eol + 1;
const std::string t = trim(line);
if (t.empty() || t[0] == '#') continue;
// 顶层 def 换块:只有四个 __LoadGame<Class>Ex 有连击段调用,其它 def 复位。
if (starts_with(line, "def ")) {
cur_class = class_from_def_line(line);
continue;
}
if (cur_class < 0) continue;
const char* kReserve = "chrmgr.ReserveComboAttackNew(";
const char* kRegister = "chrmgr.RegisterComboAttackNew(";
size_t r = t.find(kReserve);
if (r != std::string::npos) {
std::vector<long> args;
if (!parse_call_args(t.substr(r + std::string(kReserve).size()), args) || args.size() != 3) {
if (err) *err = "combo_table: 无法解析 ReserveComboAttackNew: " + t;
return false;
}
const uint32_t key = make_combo_key(static_cast<uint16_t>(args[0]), static_cast<uint16_t>(args[1]));
const long count = args[2] < 0 ? 0 : args[2];
// CRaceData::ReserveComboAttack 用 map::insert —— 已存在的 key 不覆盖。
out.per_class[cur_class].combos.emplace(
key, std::vector<uint16_t>(static_cast<size_t>(count), 0));
continue;
}
size_t g = t.find(kRegister);
if (g != std::string::npos) {
std::vector<long> args;
if (!parse_call_args(t.substr(g + std::string(kRegister).size()), args) || args.size() != 4) {
if (err) *err = "combo_table: 无法解析 RegisterComboAttackNew: " + t;
return false;
}
const uint32_t key = make_combo_key(static_cast<uint16_t>(args[0]), static_cast<uint16_t>(args[1]));
const long idx = args[2];
const uint16_t motion = static_cast<uint16_t>(args[3]);
auto it = out.per_class[cur_class].combos.find(key);
if (it == out.per_class[cur_class].combos.end()) continue; // 未 Reserve:跳过
if (idx < 0 || static_cast<size_t>(idx) >= it->second.size()) continue; // 越界:跳过
it->second[static_cast<size_t>(idx)] = motion;
continue;
}
}
if (err) err->clear();
return true;
}
bool parse_player_combo_tables_file(const std::string& path, PlayerComboTables& out, std::string* err) {
std::string s;
if (!read_file(path, s)) {
if (err) *err = "combo_table: 打不开 " + path;
return false;
}
return parse_player_combo_tables(s, out, err);
}
} // namespace fmt