141 lines
5.3 KiB
C++
141 lines
5.3 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");
|
|
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");
|
|
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
|