- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
20 KiB
movement.motion_resource_events
Scope
审计 40250 Windows 客户端的 .msa/.msm 动作资源加载、动作模式与 motion key、随机权重、MotionDuration/Accumulation、LoopData、连击输入窗口、攻击命中窗、MotionEventData、动作队列、速度倍率、音效/特效事件,以及当前端 Metin2AnimPlayer、PC/NPC/远端 PC 视图和 NetPlay 的对应实现。判断标准是两端是否按同一资源字段、分支、帧时序和动作队列推进;“能播放一个动作”不等于实现统一。
Reference call chain
GameLib/RaceData.h/GameLib/RaceData.cpp/GameLib/RaceManager.cppCRaceData按 motion mode 建立TMotionVectorMap,每个 motion index 可有多个带百分比的TMotion;GetMotionKey、motion mode parent、normal attack index 和 combo table 决定最终 motion key 与随机资源。NEW_RegisterMotion将.msa对应的CRaceMotionData与 GR2 资源绑定;缺失 motion key 时SetLoopMotion/InterceptMotion返回失败,不凭空替换成同类动作。
Client/Eternexus/root/playersettingmodule.py- PC 的
RegisterCacheMotionData注册顺序、默认 100 权重、SetMotionRandomWeight覆盖和各职业/武器/骑马模式目录是 RaceData 的实际数据来源;同一资源不能用当前端自行推导的等权列表替代。
- PC 的
GameLib/RaceMotionData.cpp/GameLib/RaceMotionData.h.msa读取MotionFileName、MotionDuration、三轴Accumulation、ComboInputData、AttackingData、LoopData和MotionEventData;事件的StartingTime按g_fGameFPS转为帧。CRaceMotionData还加载 motion sound script,保留攻击硬直、无敌、外力、命中次数、多个攻击采样窗和技能取消条件。
GameLib/ActorInstanceMotion.cpp/GameLib/ActorInstanceMotionEvent.cppSetLoopMotion、InterceptMotion、PushOnceMotion、PushLoopMotion将动作放入当前/预约队列,按GetMotionDuration()/fSpeedRatio计算结束时间;CurrentMotionProcess按 LoopData 的 start/end/count 回绕或回到 WAIT。__MotionEventProcess每个 actor 按当前帧调用MotionEventProcess/SoundEventProcess;事件分支包括独立特效、挂骨特效、目标特效、屏幕震动、特殊攻击碰撞窗、3D 声音、飞行物、显示/隐藏和 warp。
UserInterface/InstanceBaseMotion.cpp/InstanceBaseBattle.cppCInstanceBase的 motion mode、钓鱼/受击/情绪动作委托给 actor motion queue;普攻/连击/技能的ComboInputData和GetComboDataPointer决定输入窗口,动作资源本身决定速度与命中时序。
Current call chain
formats/msa.cpp解析MotionFileName、MotionDuration、Accumulation、LoopData、ComboInputData、AttackingData 和一组通用 MotionEvent 字段;formats/msm.cpp解析BaseModelFileName、MotionListFileName与HairData。extension/src/metin2_anim.cpp的Metin2AnimPlayer缓存/加载 MSA 与 GR2,进行 CPU/GPU skin、0.15 秒 crossfade、事件时间穿越派发、命中窗/连击窗口 getter、root accumulation getter 和 sound/event signal。project/ui/player_view.gd结合project/ui/motion_registry.gd读取发布的playersettingmodule.py,按注册的 mode/index/权重选择 PC motion;project/ui/mob_view.gd按选中.msm的MotionListFileName解析 NPC/怪物motlist,把别名归并到 40250EName动作索引并按权重选择;remote_player_view.gd复用 PlayerView 并刷新远端装备动作模式。project/net_play.gd消费get_motion_data(),推进普攻/连击输入窗和.msa命中窗;project/game_scene.gd将本地主角的motion_event_detailed接到特效、声音、屏幕震动、弓箭发射和 WARP,NetWorld将真实远端 Actor 的事件接到同一消费边界。project/ui/player_view.gd/mob_view.gd对受击动作使用本地HitReaction队列;循环/一次性动作由原生 LoopData 和 playback_finished 驱动,远端 NPC/PC 的真实 animator 事件由NetWorld连接到 GameScene 的统一分派。
Branch matrix
| 场景 | 40250 | 当前端 | 结论 |
|---|---|---|---|
| motion mode → motion key | CRaceData::GetMotionKey 查 mode/index/subindex、父 mode 和注册资源 |
PC 按注册表查具体目录;普通模式缺 key 回 GENERAL,HORSE_* 缺 key 只回 HORSE,父模式也缺失时保持失败;NPC 按 motlist 的 EName 索引解析 |
PARTIAL:PC 父模式顺序和 NPC 常用动作索引已统一,完整 RaceData fallback/失败边界仍不同 |
| 随机动作资源 | TMotion.byPercentage 按注册顺序、random()%100 和百分比选择 |
motion_registry.gd 保留 playersettingmodule.py 注册顺序、重复项、默认 100 及 SetMotionRandomWeight,PlayerView 按相同百分比选择;MobView 对 motlist 别名按 EName 归并并按同一百分比算法选择 |
PARTIAL:PC/NPC 常用权重已复刻,完整注册快照和缺失资源的原生失败语义仍未闭合 |
| MotionDuration | CRaceMotionData 的 .msa MotionDuration,结束时间除以速度倍率 |
Metin2AnimPlayer 对 .msa 现在以 msa_metadata.duration 为播放/动作结束权威,只有 raw .gr2 或缺失时回退 GR2 时长 |
PARTIAL:权威字段已统一;GR2 采样长度、速度倍率和所有资源边界仍需固定样本矩阵 |
| Accumulation | actor motion/root movement 按三轴累计量推进 | MSA 解析和 get_accumulation 存在;远端主要用 walk/run speed 交给 EntityStore,本地主角位置由 PlayerController/NetPlay 驱动 |
PARTIAL:远端移动有近似入口,本地 actor root motion 与三轴逐帧应用未统一证明 |
| LoopData | MotionLoopCount、LoopStartTime、LoopEndTime 控制片段回绕与次数 |
原生播放器按 LoopStartTime..LoopEndTime 回绕并递减 motion_loop_count,尾部再发 playback_finished;anim.loop 仍负责普通循环动作 |
PARTIAL:主循环语义已统一,跨大 delta、事件边界和动作队列仍需继续证明 |
| 动作队列 | Set/Intercept 清队列,Push once/loop 预约,结束时继续队列;死亡/晕倒有特殊分支 | Player/Mob 的 HitReaction 维护本地链;普通 one-shot 与循环动作以字符串状态和 playback_finished 驱动 | PARTIAL:常见攻击/受击路径可用,但预约队列、死/晕/站起、同动作重入和技能取消分支不完整 |
| 连击输入窗 | PreInputTime/DirectInputTime/InputLimitTime/LinkTime 来自当前 .msa,combo segment 来自 CRaceData |
parser/get_motion_data、NetPlay 窗口和 combo table 已接入 | PARTIAL:主要算法有;mode parent、资源权重、段动作队列和动画真实完成时刻未完全统一 |
| 普攻/技能命中窗 | AttackingData 的多个 THitData、sphere sweep、stiffen/invisible/external force/limit |
parser 暴露 hit_windows,NetPlay 使用 .msm 防御球与扫掠判定 |
PARTIAL:结构已映射,真实多事件 actor 时序、资源缺失和帧边界仍需 live 证据 |
| MotionEventData | 每帧遍历每个 actor,按类型执行 effect/target effect/special attack/sound/fly/show/hide/warp | Metin2AnimPlayer 以时间区间派发详细 signal;本地主角消费已有入口,type 6 按 .msa 的 FlyFileName/FlyPosition/AttachingBoneName 创建本地 FlyManager 实例并单独发送 CG_SHOOT,真实远端 Actor 经 NetWorld 接入,WARP 按 270cm/阻挡/朝向分支执行;有效动作绑定后按 40250 __SetMotion -> __ShowEvent 自动恢复隐藏状态 |
PARTIAL:type 4 splash 的所有 actor 消费、type 6 骨骼/飞行特效细节和事件状态清理仍不等价 |
| 事件时序 | StartingTime→60 FPS frame equality;动作速度与队列决定当前帧 |
以动作时间跨越区间派发;首帧/LoopData 回绕点使用包含起点的边界,整段循环按 duration 分段遍历 | PARTIAL:首帧、回绕起点和多周期大 delta 已统一,固定帧量化、事件资源消费和所有 actor live 时序仍需证明 |
| 音效脚本 | 每个 actor 的 SoundEventProcess 按帧/3D 距离频率规则播放 .mss |
Player/Mob 切动作后加载 .mss,按 floor(time*60) 更新;remote/非主角事件音效链不完整 |
PARTIAL |
.msm/模型动作绑定 |
RaceData 同时管理 base/LOD/attachment、motion mode、collision/attaching data | formats/msm.cpp 已解析 base/motion-list/hair;PC/NPC 其余动作和防御球由路径约定与独立解析器提供 |
PARTIAL:资源链可运行,但不是完整 RaceData 等价实现 |
| 资源加载失败 | GetMotionKey/LoadMotionData 失败即保留当前动作或返回失败;不混用别的动作 | Player/Mob 对缺失状态有别名/目录 fallback,攻击缺失回 general,另有 placeholder/测试 fallback | PARTIAL:兼容性更强但分支语义已不同,需要按 40250 逐资源决定是否允许 fallback |
Implementation-equivalence matrix
| 项目 | 状态 | 证据/限制 |
|---|---|---|
| Preconditions | PARTIAL | MSA/GR2 加载、model skeleton、motion mode、PC playersettingmodule registry 和部分 hit/skill gate 存在;NPC/完整 RaceData registration、motion key 失败语义和 per-actor event consumer 未闭合 |
| Branch structure | PARTIAL | loop/combo/attack/event 主字段和常用状态存在;LoopData、motion queue、事件类型分支及非主角消费不等价 |
| Algorithms/formulas | PARTIAL | PC motion random 已按注册顺序、random()%100 和百分比实现;Accumulation/Duration/Combo/HitWindow 基础公式有实现,NPC 权重、帧量化、三轴 root motion、LoopData 回绕和事件特效绑定仍是近似 |
| State transition order | PARTIAL | bind→motion data→NetPlay hit/combo 的顺序清晰,击倒/起身早退已补齐;参考端完整 Set/Intercept/Push queue、事件与动作帧、结束回 WAIT 的顺序未完全复现 |
| Constants/units | PARTIAL | 60 FPS、速度倍率、MSA accumulation、0.15 blend、motion mode 目录和 combo key 有证据;GR2 duration 覆盖 MSA duration、cm/m 转换与骨骼 offset 仍需固定样本矩阵 |
| Timing/event sources | PARTIAL | MSA MotionDuration、LoopData 播放和 Godot 跨越事件已有统一入口;大 delta、loop count、事件重复/跳过、远端 actor 事件尚未完全覆盖 |
| Resource/data sources | PARTIAL | PC playersettingmodule.py 的主要 mode/index/weight 已由 MotionRegistry 读取并有回归;NPC/GR2/MSA/MSS/MSM 全量注册、LOD/attachment/collision 数据与所有事件资源尚未通过 |
| Protocol side effects | PARTIAL | 动作状态包、攻击/技能/飞行入口与 local motion event 有;非主角 MotionEvent、动作失败保持当前、服务器拒绝和重连清理缺 live 证据 |
| Interruption/failure/cleanup | PARTIAL | loop/one-shot/skill/受击/死亡/切图/断线的组合动作队列和事件实例清理未完整证明;本轮击倒/起身早退已覆盖,但跨图/断线和全 actor 清理仍未闭合 |
Regression evidence
project/motion_registry_test.gd:通过,确认 Warrior 的 GENERAL wait 70/30、ONEHAND wait 50/50、HORSE wait 90/9/1、GENERAL attack 50/50 注册顺序和权重。project/player_motion_test.gd、project/hit_view_test.gd、project/keyboard_motion_timeline_test.gd、project/race_motion_assembly_test.gd、project/animation_blend_test.gd:通过,覆盖父模式回退、加权变体、击倒/起身早退、基础动作切换、键盘时间线、8 race 资源装配和 crossfade;PlayerMotion 的 HORSE WAIT 断言接受 40250 注册的wait/wait_1/wait_2加权变体,不再把随机结果写死;不证明完整 RaceData/NPC weighted motion/LoopData/事件链。project/test_combo_continuous_flow.gd、project/combat_fx_test.gd、project/hit_collision_test.gd、project/motion_effect_anchor_test.gd、project/target_effect_test.gd:通过,覆盖本端连击、命中窗/防御球、挂点和目标特效 helper;不证明所有 actor 的 40250 MotionEventProcess。project/mob_view_test.gd:通过;验证 NPC 真模型、MotionListFileName选择、motlist 别名/权重归并、SPAWN 缺省规则和动作缺失保持当前动作。formats/tests/msm_test.cpp:通过;验证.msm MotionListFileName与已有 BaseModel/HairData 字段一起解析。project/motion_event_timing_test.gd:通过;验证StartingTime=0首帧事件只触发一次,以及大时间步跨越多个整段循环时每个周期都派发事件。project/player_motion_test.gd:通过;验证 CHARACTER_HIDE 后再次请求相同动作或新动作时,按 40250__SetMotion语义自动恢复 actor 可见性。project/motion_event_fly_test.gd:通过;验证本地 type 6 按.msf创建一颗 FlyManager 弹道,同时只发送一份排队的CG_SHOOT,缺失资源不创建实例。project/animation_cache_test.gd:通过;测试显式切换已注册的 wait/run/attack 资源,并按两个 actor 实际抽到的 weighted wait 变体计算唯一资源数,避免把随机结果当固定路径。本轮运行得到hits=16, misses=4或hits=17, misses=3(msa_*同步),均证明 decoded clip 和 MSA metadata 在热切换中复用。project/eterngrn_polish_test.gd:动作事件分派断言未作为完整 live 资源证据;运行有未入树 Node3D 的 transform 错误、ObjectDB/RID leak warning,需后续隔离测试 fixture。build/extension/*的现有网络测试未覆盖 Metin2AnimPlayer 的 LoopData、weighted motion、每 actor MotionEventProcess、remote NPC/PC 事件和缓存复用边界。
Active GameLib actor-core review
GameLib/ActorInstance.h、ActorInstanceEvent.cpp、ActorInstancePosition.cpp、ActorInstanceRotation.cpp和ActorInstanceBlend.cpp把状态分成位置源/目标/当前快照、advancing rotation、0.3 秒LookAtblend、alpha blend、WAIT/MOVE/ATTACK/SKILL/HIT/WARP 事件回调;事件回调同时携带位置和朝向,不能只用一个字符串动画状态替代。ActorInstanceData.cpp的SetRace/SetShape/SetPart会从 RaceData/ItemData 注册 base、LOD、attachment、motion mode 和碰撞数据;当前PlayerView/RemotePlayerView的模型和动作绑定已覆盖常见路径,但没有完整ActorInstance级 motion queue、父 mode、LOD/碰撞传播。ActorInstanceEvent.cpp的OnUseSkill会把 moving-skill 标记编码进 loop 参数,OnAttack使用目标旋转,OnHit/affect 直接进入消费者;当前 GameScene 主要消费本地主角 motion signal,远端/NPC 的完整 actor event consumer 仍缺。
Status
PARTIAL。当前端已经具备 MSA 基础解析、以 MSA MotionDuration 为权威的 GR2 播放、crossfade、root speed、LoopData、连击/命中窗、本地主角事件入口,以及从 playersettingmodule.py 驱动的 PC mode/index/weighted motion 选择;但参考端的完整 RaceData motion key/权重/父模式、NPC/所有 actor 的帧事件处理、动作队列和事件实例清理尚未统一。本轮补齐了 PC 注册表权重、HORSE 父模式查找和击倒/起身早退,并补充对应回归,仍不能宣称完整动作资源链等价。
Implementation fix round 2026-09-21T05:25Z
按 40250 CRaceMotionData::GetMotionDuration() 的资源来源,将
Metin2AnimPlayer::reload() 的 .msa 播放时长改为 MotionDuration;仅 raw .gr2 或无有效
MSA 时长时使用 GR2 duration。这样动作结束、LoopData 边界、MotionEventData 时间区间和
get_motion_data().duration 使用同一权威字段。
同时修正 animation_cache_test.gd 的压力方式:原测试通过重复请求仍在播放的攻击动作,实际
被 40250 的动作不可打断门拦截,并没有真正覆盖热重载;现在显式在相同三个已注册资源之间切换,
确认 decoded clip 与 MSA metadata 各为 17 hits / 3 misses。资源时长、技能循环、玩家动作、
战斗特效、键盘动作和动作缓存回归均通过。
Implementation fix round 2026-09-21T05:37Z
按 40250 playersettingmodule.py → CRaceData::RegisterMotionData →
CActorInstance::GetRandomMotionKey 的完整 PC 动作资源链,新增
project/ui/motion_registry.gd:读取当前客户端随资源发布的 Python 注册表,保留 mode/index
注册顺序、重复资源、默认权重以及 SetMotionRandomWeight 修改,再由 PlayerView 按
random() % 100 选择 wait/受击/攻击资源。动作模式缺少 key 时补上 HORSE_* 到 HORSE 的
父模式查找,并保留普通模式回退路径;不再把 PC 变体当作等权文件名列表。
同时按 ActorInstanceBattle.cpp::__HitGood/__HitGreate 的
IsKnockDown/__IsStandUpMotion 早退语义,记录受击链当前步骤,防止击倒或起身期间因
_state 仍是 run/wait 而插入新的 DAMAGE;补充 motion_registry_test、父模式/权重动作回归,
并将已有动作测试改为验证注册变体集合而非依赖某次随机结果。PC 注册表和本轮动作回归通过,
完整 NPC/RaceData/事件消费者/队列清理仍保持 PARTIAL。
Implementation fix round 2026-09-21T05:55Z
按 RaceDataFile.cpp / RaceManager::__LoadRaceMotionList 补齐 NPC 动作资源链:
.msm 现在解析 MotionListFileName,MobView 从选中的动作表读取并保留 40250
的动作索引映射、WAIT 等后缀最多两字符的别名规则、注册顺序、百分比权重、未知类型跳过,
并按 random()%100 从同一 EName vector 选资源;缺少 SPAWN 时复用最后一条 DAMAGE
资源,动作 key 缺失时不再将 WALK/RUN、DAMAGE/WAIT 等无关动作互相顶替。
新增/更新 mob_view_test.gd 与 formats/tests/msm_test.cpp 覆盖 MotionListFileName、
NPC alias/weight、COMBO 索引、未知动作、SPAWN fallback 和严格失败语义。NPC 常用动作表
已与 40250 对齐,但完整 RaceData mode/parent、所有 NPC 技能索引、MotionEvent 消费、动作
预约队列和跨 actor 清理仍保持 PARTIAL。
Implementation fix round 2026-09-21T06:15Z
按 ActorInstanceMotion.cpp::__MotionEventProcess 的 frame-0 和逐帧语义修正
Metin2AnimPlayer::dispatch_events:非循环动作在首个动作帧包含 StartingTime=0,但普通
连续帧仍保持左开区间;LoopData 回绕后立即处理 LoopStartTime 点;整段循环按每个
duration 分段遍历,跨越多个周期时不再最多只派发一次事件。新增
project/motion_event_timing_test.gd,使用真实 .msa 资源验证首帧、去重和大 delta 多周期
事件数量。LoopData 的所有资源变体、60 FPS 离散帧与非主角事件消费者仍保持 PARTIAL。
Implementation fix round 2026-09-21T06:20Z
按 ActorInstanceMotion.cpp::__SetMotion 的顺序补齐隐藏状态恢复:40250 在成功解析动作 key
后调用 __ShowEvent(),因此 CHARACTER_HIDE 后的下一次有效 SetLoopMotion(包括同一循环
动作)会恢复显示。当前 PlayerView/MobView 现在在动作 key 成功并进入 _bind 时清除隐藏
状态,并放宽同状态提前返回条件;缺失动作仍保持当前动作和隐藏状态,不会伪造成功。
新增 PlayerView 真实资源回归覆盖同状态与新状态两条路径。type 4 splash、type 6 .fly
资源消费、固定帧量化、所有 actor 事件消费者和动作队列清理仍保持 PARTIAL。
Implementation fix round 2026-09-21T06:30Z
按 ActorInstanceMotionEvent.cpp::ProcessMotionEventFly 与
FlyingObjectManager::CreateFlyingInstanceFlyTarget 补齐本地主角的 type 6 事件链:有效
FlyFileName 现在经 FlyData.load_msf 进入已有 FlyManager,起点复用角色动作骨骼坐标
变换,目标沿用当前动作目标;GameScene 仍将 queued uSkill 只消费一次发送 CG_SHOOT。
服务器 GC_CREATE_FLY 的索引弹道未与这条本地路径合并,避免普通弓箭视觉重复。
motion_event_fly_test.gd 覆盖有效资源、缺失资源、弹道推进,以及“一个本地实例 + 一份
CG_SHOOT”的集成边界。远端 type 6 的资源化特效、type 4 全 actor 消费、固定 60 FPS
量化和完整事件清理仍保持 PARTIAL。