// .msa —— Metin2 动作描述(文本,ScriptType MotionData)。M2 T1。 #pragma once #include #include namespace fmt { struct MotionEvent { int type = 0; // MotionEventType(10=effect, 4/5=sound, ...) float start_time = 0; // StartingTime std::string effect_file; // EffectFileName(type 10) std::string sound_file; // SoundFileName std::string attaching_bone; bool attaching = false; bool following = false; bool independent = false; bool fishing_effect = false; float position[3] = {0, 0, 0}; }; struct Msa { std::string motion_gr2; // MotionFileName(原样,含 d:\... 前缀) float duration = 0; // MotionDuration float accumulation[3] = {0,0,0};// Accumulation(root motion 位移) std::vector events; bool has_loop_data = false; // LoopData 是片段循环,不等同于整段播放模式 int motion_loop_count = 0; // -1 无限;原客户端仅在 >1 或 -1 时回绕片段 bool loop_cancel_enable = false; float loop_start_time = 0; float loop_end_time = 0; // Group ComboInputData —— 连击输入时间窗(EterGrnLib CGrannyMotion::LoadMotionData → // CRaceMotionData::TComboInputData)。攻击节奏来自这里,不是硬编码间隔。 // PreInputTime → fInputStartTime 连击输入窗开始(可提前缓存下一击) // DirectInputTime→ fNextComboTime 直接输入阈值 / 下一段连击起点(= 普攻节奏) // InputLimitTime → fInputEndTime 连击输入窗关闭(超时归零) // LinkTime → fComboLinkTime 段间衔接时间 bool has_combo_input = false; float combo_pre_input_time = 0; float combo_direct_input_time = 0; float combo_input_limit_time = 0; float combo_link_time = 0; // Group AttackingData —— 命中判定窗(GameLib GameType.cpp NRaceData::TMotionAttackData)。 // 两种写法:老式(组内直接 AttackingStartTime/…/List HitPosition,无 HitDataCount)= // 单个命中窗;新式(AttackType + MotionType + HitDataCount N + Group HitData00..0N)= // N 个命中窗(多段挥击 / 旋风斩)。命中帧 / 硬直 / 无敌帧由动作数据驱动。 bool has_attacking_data = false; int attacking_type = 0; // AttackType / AttackingType(SAttackData::iAttackType) int motion_type = 0; // MotionType(缺省回退到 attacking_type) int hitting_type = 0; // HittingType float stiffen_time = 0; // StiffenTime(受击硬直,喂给 §3.7 / 受击方) float invisible_time = 0; // InvisibleTime(攻击者无敌帧) float external_force = 0; // ExternalForce(击退力度) int hit_limit_count = 0; // HitLimitCount(0 = 不限) // HitPosition 单个采样:time + 上一帧位置(xyz) + 当前帧位置(xyz),用于挥击扫掠球判定。 struct HitSample { float time = 0; float last_pos[3] = {0, 0, 0}; float pos[3] = {0, 0, 0}; }; // 一个命中窗(NRaceData::THitData)。 struct HitWindow { float start_time = 0; // AttackingStartTime float end_time = 0; // AttackingEndTime std::string bone_name; // AttackingBone float weapon_length = 0; // WeaponLength std::vector samples; // List HitPosition }; std::vector hit_windows; // = THitDataContainer;老式写法长度为 1 // 兼容旧调用方:第一个命中窗的起止时间(无命中窗时为 0)。 float attack_start_time = 0; float attack_end_time = 0; }; bool parse_msa(const std::string& text, Msa& out, std::string* err); bool parse_msa_file(const std::string& path, Msa& out, std::string* err); } // namespace fmt