完善客户端功能并同步差距文档
This commit is contained in:
@@ -30,5 +30,202 @@ Group LoopData
|
||||
std::fprintf(stderr, "LoopData fields were not preserved\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A motion with no ComboInputData / AttackingData leaves the flags clear.
|
||||
if (msa.has_combo_input || msa.has_attacking_data) {
|
||||
std::fprintf(stderr, "combat groups reported present when absent\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// CLIENT-GAP §3.3 / §3.5 —— real onehand_sword/combo_01.msa field values.
|
||||
const std::string combo = R"(
|
||||
ScriptType MotionData
|
||||
MotionFileName "d:/ymir work/pc/warrior/onehand_sword/combo_01.gr2"
|
||||
MotionDuration 1.000000
|
||||
Accumulation 0.00 -131.76 0.00
|
||||
Group ComboInputData
|
||||
{
|
||||
PreInputTime 0.167094
|
||||
DirectInputTime 0.533333
|
||||
InputLimitTime 0.602564
|
||||
LinkTime 0.058889
|
||||
}
|
||||
Group AttackingData
|
||||
{
|
||||
AttackingType 2
|
||||
HittingType 2
|
||||
StiffenTime 0.0
|
||||
InvisibleTime 0.100000
|
||||
ExternalForce 8.000000
|
||||
AttackingStartTime 0.192308
|
||||
AttackingEndTime 0.315385
|
||||
}
|
||||
)";
|
||||
fmt::Msa cm;
|
||||
if (!fmt::parse_msa(combo, cm, &err)) {
|
||||
std::fprintf(stderr, "combo parse failed: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
const bool combo_ok = cm.has_combo_input &&
|
||||
std::fabs(cm.combo_pre_input_time - 0.167094f) < 1e-5f &&
|
||||
std::fabs(cm.combo_direct_input_time - 0.533333f) < 1e-5f &&
|
||||
std::fabs(cm.combo_input_limit_time - 0.602564f) < 1e-5f &&
|
||||
std::fabs(cm.combo_link_time - 0.058889f) < 1e-5f;
|
||||
const bool atk_ok = cm.has_attacking_data && cm.attacking_type == 2 && cm.hitting_type == 2 &&
|
||||
std::fabs(cm.attack_start_time - 0.192308f) < 1e-5f &&
|
||||
std::fabs(cm.attack_end_time - 0.315385f) < 1e-5f &&
|
||||
std::fabs(cm.invisible_time - 0.1f) < 1e-5f &&
|
||||
std::fabs(cm.external_force - 8.0f) < 1e-5f;
|
||||
if (!combo_ok || !atk_ok) {
|
||||
std::fprintf(stderr, "ComboInputData / AttackingData fields were not preserved\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// §3.5 修改 1 —— 老式内联写法(无 HitDataCount):单个命中窗,含 List HitPosition 采样。
|
||||
const std::string inl = R"(
|
||||
ScriptType MotionData
|
||||
MotionFileName "d:/ymir work/pc/warrior/general/attack.gr2"
|
||||
MotionDuration 1.0
|
||||
Group AttackingData
|
||||
{
|
||||
AttackingType 1
|
||||
HittingType 2
|
||||
StiffenTime 0.0
|
||||
InvisibleTime 0.5
|
||||
ExternalForce 0.0
|
||||
AttackingStartTime 0.456410
|
||||
AttackingEndTime 0.597436
|
||||
AttackingBone "equip_right_hand"
|
||||
WeaponLength 0.0
|
||||
List HitPosition
|
||||
{
|
||||
0.456410 1.0 2.0 3.0 4.0 5.0 6.0
|
||||
0.464744 7.0 8.0 9.0 10.0 11.0 12.0
|
||||
}
|
||||
}
|
||||
)";
|
||||
fmt::Msa im;
|
||||
if (!fmt::parse_msa(inl, im, &err)) {
|
||||
std::fprintf(stderr, "inline attacking parse failed: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (im.hit_windows.size() != 1 || im.hit_windows[0].samples.size() != 2 ||
|
||||
im.hit_windows[0].bone_name != "equip_right_hand" ||
|
||||
std::fabs(im.hit_windows[0].start_time - 0.456410f) > 1e-5f ||
|
||||
std::fabs(im.hit_windows[0].end_time - 0.597436f) > 1e-5f ||
|
||||
std::fabs(im.attack_start_time - 0.456410f) > 1e-5f ||
|
||||
std::fabs(im.hit_windows[0].samples[1].time - 0.464744f) > 1e-5f ||
|
||||
std::fabs(im.hit_windows[0].samples[1].last_pos[0] - 7.0f) > 1e-5f ||
|
||||
std::fabs(im.hit_windows[0].samples[1].pos[2] - 12.0f) > 1e-5f) {
|
||||
std::fprintf(stderr, "inline HitData window / HitPosition samples not preserved\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// §3.5 修改 1 —— 新式写法:AttackType + MotionType + HitDataCount N + Group HitData0N。
|
||||
const std::string multi = R"(
|
||||
ScriptType MotionData
|
||||
MotionFileName "d:/ymir work/pc/warrior/twohand_sword/combo_05.gr2"
|
||||
MotionDuration 1.6
|
||||
Group AttackingData
|
||||
{
|
||||
AttackType 0
|
||||
HittingType 2
|
||||
StiffenTime 0.0
|
||||
InvisibleTime 0.2
|
||||
ExternalForce 5.0
|
||||
HitLimitCount 3
|
||||
MotionType 2
|
||||
HitDataCount 2
|
||||
Group HitData00
|
||||
{
|
||||
AttackingStartTime 0.455475
|
||||
AttackingEndTime 0.759124
|
||||
AttackingBone "equip_right_hand"
|
||||
WeaponLength 150.0
|
||||
List HitPosition
|
||||
{
|
||||
0.455475 54.5 -30.5 71.1 89.9 105.7 19.4
|
||||
}
|
||||
}
|
||||
Group HitData01
|
||||
{
|
||||
AttackingStartTime 0.9
|
||||
AttackingEndTime 1.1
|
||||
AttackingBone "equip_left_hand"
|
||||
WeaponLength 120.0
|
||||
}
|
||||
}
|
||||
)";
|
||||
fmt::Msa mm;
|
||||
if (!fmt::parse_msa(multi, mm, &err)) {
|
||||
std::fprintf(stderr, "multi HitData parse failed: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (mm.attacking_type != 0 || mm.motion_type != 2 || mm.hit_limit_count != 3 ||
|
||||
mm.hit_windows.size() != 2 ||
|
||||
std::fabs(mm.hit_windows[0].start_time - 0.455475f) > 1e-5f ||
|
||||
mm.hit_windows[0].samples.size() != 1 ||
|
||||
mm.hit_windows[1].bone_name != "equip_left_hand" ||
|
||||
std::fabs(mm.hit_windows[1].end_time - 1.1f) > 1e-5f ||
|
||||
mm.hit_windows[1].samples.size() != 0 ||
|
||||
std::fabs(mm.attack_start_time - 0.455475f) > 1e-5f) {
|
||||
std::fprintf(stderr, "multi HitData windows not preserved\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- 可选:真实 PC .msa(设了 M2_ASSETS 才跑)----------------------------
|
||||
if (const char* root = std::getenv("M2_ASSETS")) {
|
||||
const std::string base = std::string(root) + "/PC/ymir work/pc/warrior/";
|
||||
fmt::Msa a1;
|
||||
if (fmt::parse_msa_file(base + "general/attack.msa", a1, &err)) {
|
||||
if (a1.hit_windows.size() != 1 || a1.hit_windows[0].bone_name != "equip_right_hand" ||
|
||||
std::fabs(a1.hit_windows[0].start_time - 0.456410f) > 1e-4f ||
|
||||
std::fabs(a1.hit_windows[0].end_time - 0.597436f) > 1e-4f ||
|
||||
a1.hit_windows[0].samples.size() != 17) {
|
||||
std::fprintf(stderr, "live general/attack.msa window/samples wrong "
|
||||
"(windows=%zu samples=%zu)\n", a1.hit_windows.size(),
|
||||
a1.hit_windows.empty() ? 0 : a1.hit_windows[0].samples.size());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
fmt::Msa c5;
|
||||
if (fmt::parse_msa_file(base + "twohand_sword/combo_05.msa", c5, &err)) {
|
||||
if (c5.hit_windows.size() != 1 || c5.motion_type != 2 ||
|
||||
std::fabs(c5.hit_windows[0].weapon_length - 150.0f) > 1e-3f ||
|
||||
std::fabs(c5.hit_windows[0].start_time - 0.455475f) > 1e-4f ||
|
||||
c5.hit_windows[0].samples.size() != 37) {
|
||||
std::fprintf(stderr, "live twohand_sword/combo_05.msa wrong "
|
||||
"(windows=%zu samples=%zu)\n", c5.hit_windows.size(),
|
||||
c5.hit_windows.empty() ? 0 : c5.hit_windows[0].samples.size());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// 真正的双持双命中窗(右手窗 + 左手窗)。
|
||||
const std::string adh = std::string(root) + "/PC/ymir work/pc/assassin/dualhand_sword/combo_01.msa";
|
||||
fmt::Msa dh;
|
||||
if (fmt::parse_msa_file(adh, dh, &err)) {
|
||||
if (dh.hit_windows.size() != 2 ||
|
||||
dh.hit_windows[0].bone_name != "Bip01 R Hand" ||
|
||||
dh.hit_windows[1].bone_name != "Bip01 L Hand" ||
|
||||
dh.hit_windows[0].samples.size() != 13 ||
|
||||
dh.hit_windows[1].samples.size() != 13 ||
|
||||
std::fabs(dh.hit_windows[1].start_time - 0.605128f) > 1e-4f) {
|
||||
std::fprintf(stderr, "live assassin dualhand combo_01.msa two-window wrong "
|
||||
"(windows=%zu)\n", dh.hit_windows.size());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
fmt::Msa pb;
|
||||
if (fmt::parse_msa_file(base + "skill/palbang.msa", pb, &err)) {
|
||||
// AttackingData 后还有 Group MotionEventData —— List 修复后二者都要解析到。
|
||||
if (pb.hit_windows.size() != 1 || pb.hit_limit_count != 18 ||
|
||||
std::fabs(pb.hit_windows[0].weapon_length - 130.0f) > 1e-3f ||
|
||||
pb.events.size() != 3) {
|
||||
std::fprintf(stderr, "live skill/palbang.msa wrong "
|
||||
"(windows=%zu events=%zu)\n", pb.hit_windows.size(), pb.events.size());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user