Files

151 lines
5.9 KiB
C++

// M2 T1 —— .msa 解析(照 EterGrnLib/Util.cpp CGrannyMotion::LoadMotionData 的字段)。
#include "msa.h"
#include "textscript.h"
#include <cstdlib>
namespace fmt {
static void vec3(const std::vector<std::string>* l, float* out) {
if (!l) return;
for (int k = 0; k < 3 && k + 1 < (int)l->size(); ++k) out[k] = float(std::atof((*l)[k + 1].c_str()));
}
static void loop_data(const Node& root, Msa& out) {
if (const Node* loop = root.group("LoopData")) {
out.has_loop_data = true;
out.motion_loop_count = loop->inum("MotionLoopCount", -1);
out.loop_cancel_enable = loop->inum("LoopCancelEnable", 0) != 0;
out.loop_start_time = loop->num("LoopStartTime");
out.loop_end_time = loop->num("LoopEndTime");
}
}
// `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;
out = Msa{};
out.motion_gr2 = root.str("MotionFileName");
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()));
(void)count;
for (const Node& ev : med->groups) {
MotionEvent e;
e.type = ev.inum("MotionEventType");
e.start_time = ev.num("StartingTime");
e.effect_file = ev.str("EffectFileName");
e.sound_file = ev.str("SoundFileName");
e.attaching_bone = ev.str("AttachingBoneName");
e.attaching = ev.inum("AttachingEnable") != 0;
e.following = ev.inum("FollowingEnable") != 0;
e.independent = ev.inum("IndependentFlag") != 0;
e.fishing_effect = ev.inum("FishingEffectFlag") != 0;
vec3(ev.find("EffectPosition"), e.position);
out.events.push_back(std::move(e));
}
}
if (out.motion_gr2.empty()) { if (err) *err = "msa: 缺 MotionFileName"; return false; }
return true;
}
bool parse_msa_file(const std::string& path, Msa& out, std::string* err) {
Node root;
if (!parse_textscript_file(path, root, err)) return false;
// 复用上面逻辑:重解析文本更简单,这里直接从 root 走一遍
out = Msa{};
out.motion_gr2 = root.str("MotionFileName");
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;
e.type = ev.inum("MotionEventType");
e.start_time = ev.num("StartingTime");
e.effect_file = ev.str("EffectFileName");
e.sound_file = ev.str("SoundFileName");
e.attaching_bone = ev.str("AttachingBoneName");
e.attaching = ev.inum("AttachingEnable") != 0;
e.following = ev.inum("FollowingEnable") != 0;
e.independent = ev.inum("IndependentFlag") != 0;
e.fishing_effect = ev.inum("FishingEffectFlag") != 0;
vec3(ev.find("EffectPosition"), e.position);
out.events.push_back(std::move(e));
}
if (out.motion_gr2.empty()) { if (err) *err = "msa: 缺 MotionFileName (" + path + ")"; return false; }
return true;
}
} // namespace fmt