完善客户端功能并同步差距文档

This commit is contained in:
shenlei
2026-09-03 18:40:01 +09:00
parent f93a172296
commit 13ccb8d02d
135 changed files with 21881 additions and 1259 deletions
+66
View File
@@ -20,6 +20,70 @@ static void loop_data(const Node& root, Msa& out) {
}
}
// `List HitPosition { t lastXYZ XYZ t lastXYZ XYZ ... }` —— 7 个一组切成采样点。
static void hit_positions(const Node& win, std::vector<Msa::HitSample>& out) {
const Node* lst = win.group("HitPosition");
if (!lst || lst->lines.empty()) return;
const std::vector<std::string>& t = lst->lines[0];
for (size_t i = 0; i + 7 <= t.size(); i += 7) {
Msa::HitSample s;
s.time = float(std::atof(t[i].c_str()));
for (int k = 0; k < 3; ++k) s.last_pos[k] = float(std::atof(t[i + 1 + k].c_str()));
for (int k = 0; k < 3; ++k) s.pos[k] = float(std::atof(t[i + 4 + k].c_str()));
out.push_back(s);
}
}
// 一个命中窗(NRaceData::THitData)。老式写法里 win == AttackingData 组本身。
static Msa::HitWindow hit_window(const Node& win) {
Msa::HitWindow w;
w.start_time = win.num("AttackingStartTime");
w.end_time = win.num("AttackingEndTime");
w.bone_name = win.str("AttackingBone");
w.weapon_length = win.num("WeaponLength");
hit_positions(win, w.samples);
return w;
}
// Group ComboInputData / Group AttackingData —— 照 EterGrnLib CGrannyMotion::LoadMotionData
// 与 GameLib NRaceData::LoadMotionAttackData 的字段名读取;缺组时保持默认值。
static void combat_data(const Node& root, Msa& out) {
if (const Node* c = root.group("ComboInputData")) {
out.has_combo_input = true;
out.combo_pre_input_time = c->num("PreInputTime");
out.combo_direct_input_time = c->num("DirectInputTime");
out.combo_input_limit_time = c->num("InputLimitTime");
out.combo_link_time = c->num("LinkTime");
}
if (const Node* a = root.group("AttackingData")) {
out.has_attacking_data = true;
out.attacking_type = a->inum("AttackType", a->inum("AttackingType", 0));
out.motion_type = a->inum("MotionType", out.attacking_type);
out.hitting_type = a->inum("HittingType", 0);
out.stiffen_time = a->num("StiffenTime");
out.invisible_time = a->num("InvisibleTime");
out.external_force = a->num("ExternalForce");
out.hit_limit_count = a->inum("HitLimitCount", 0);
// 新式:HitDataCount N + Group HitData00..0N;老式:无 HitDataCount,组本身即窗。
bool has_children = false;
for (const Node& g : a->groups) {
const std::string& n = g.name;
if (n.size() >= 7 && (n.compare(0, 7, "HitData") == 0 || n.compare(0, 7, "hitdata") == 0)) {
out.hit_windows.push_back(hit_window(g));
has_children = true;
}
}
if (!has_children)
out.hit_windows.push_back(hit_window(*a));
if (!out.hit_windows.empty()) {
out.attack_start_time = out.hit_windows.front().start_time;
out.attack_end_time = out.hit_windows.front().end_time;
}
}
}
bool parse_msa(const std::string& text, Msa& out, std::string* err) {
Node root;
if (!parse_textscript(text, root, err)) return false;
@@ -29,6 +93,7 @@ bool parse_msa(const std::string& text, Msa& out, std::string* err) {
out.duration = root.num("MotionDuration");
vec3(root.find("Accumulation"), out.accumulation);
loop_data(root, out);
combat_data(root, out);
if (const Node* med = root.group("MotionEventData")) {
int count = med->inum("MotionEventDataCount", int(med->groups.size()));
@@ -57,6 +122,7 @@ bool parse_msa_file(const std::string& path, Msa& out, std::string* err) {
out.duration = root.num("MotionDuration");
vec3(root.find("Accumulation"), out.accumulation);
loop_data(root, out);
combat_data(root, out);
if (const Node* med = root.group("MotionEventData"))
for (const Node& ev : med->groups) {
MotionEvent e;