Files
mtgodot-poc/audit/contracts/skill.cast-effect-timing.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

16 KiB
Raw Blame History

skill.cast-effect-timing

Scope

比较技能等级、目标选择、施法前置、动作事件、伤害/状态结果以及粒子和命中特效的时序。

Reference call chain

  • UserInterface/PythonSkill.cpp
  • UserInterface/PythonPlayerSkill.cpp
  • UserInterface/PythonNetworkStreamPhaseGame.cpp
  • UserInterface/InstanceBaseEffect.cpp
  • EffectLib/EffectInstance.cpp
  • EffectLib/ParticleInstance.cpp
  • GameLib/ActorInstanceMotionEvent.cpp
  • UserInterface/InstanceBase.cpp

Current call chain

  • project/player_skill.gd
  • project/skill_table.gd
  • project/net_play.gd
  • project/net_world.gd
  • project/fx/skill_fx.gd
  • project/fx/effect_player.gd
  • extension/src/net/entity_store.cpp
  • extension/tests/net_state_queue_test.cpp
  • project/combat_fx_test.gd
  • project/skill_test.gd

Initial finding

当前存在技能表、技能上下文和特效测试,但这些测试主要证明局部数据/材质行为,尚未证明完整 40250 施法链路。技能动作事件、挂点、目标范围、冷却、服务端结果和特效停止/重播仍需要逐分支对照。

Current finding

combat_fx_test.gd 的失败是测试选择器使用本地化文本造成的,并非技能业务失败;现已改为按 40250 的控制节点名定位并通过。更重要的是,实体状态机此前没有复刻 40250 的 eFunc & FUNC_SKILL 位判断,移动中的技能会在到达目标时回到 WAIT;现已补齐远近两种分支并加入 C++ 回归测试。

本轮继续追到视觉动作层后,发现还有一个具体的参数丢失:project/net_world.gd::_apply_anim 只从 FUNC_SKILLmotion_idx 并调用 PlayerView.play_skill_motion(motion_name),没有把 40250 NEW_UseSkill/到达逻辑中的 uArg & 0x0f 动作循环次数和 (uArg >> 4) 移动技能标志传入视图;PlayerView.play_skill_motiongrade 也没有来自网络状态的真实来源。这样只能证明“技能状态会进入”,不能证明每个技能的动作循环、移动技能动作和结束时机与 40250 一致。

同时,当前 SkillFx/NetPlay 仍有固定施法锁定和特效时长路径;40250 的关键帧、挂点、颜色/混合和动作事件来自 MSA/MSE 与 ActorInstanceMotionEvent。这些资源驱动事件尚未建立逐技能时间线,因此本合同保持 PARTIAL

Deep audit round 2026-09-20

本轮沿 CInstanceBase::NEW_UseSkillCActorInstance::ProcessMotionEvent*、本地施法入口和远端 FUNC_SKILL 路径复核,并重新执行技能/战斗/特效回归:

  • 40250 NEW_UseSkill 明确接收 uMotLoopCountisMovingSkill,调用 InterceptOnceMotion__OnUseSkill,再设置动作循环次数;当前 NetPlay.on_use_skill() 只把 motion_skillarg 上行,NetWorld._apply_anim() 只取 FUNC_SKILL 的 motion index,调用远端 play_skill_motion(mname) 时没有把 uArg & 0x0f 循环次数、uArg >> 4 移动技能标志或技能 grade 传入。
  • 当前 PlayerView.play_skill_motion() 支持按 grade 选择 .msa 后缀,但正式远端网络动作路径没有 grade 来源;本地 GameScene._on_skill_cast_started() 能传 grade 并从 MSA 读取 duration/cancel_enable,两个路径的输入并不统一。
  • 当前 SkillFx.spawn_skill() 只在 skill_fx_test.gd 中被直接调用,正式 GameScene/NetPlay 没有生产调用;正式本地技能特效主要依赖 MSA motion event 和 fx.spawn_motion,因此技能表中的主特效/子特效映射没有被证明与 40250 的技能触发链一致。
  • 40250 的 MOTION_EVENT_TYPE_FLYEFFECT_TO_TARGETSPECIAL_ATTACKINGSOUND 都由动作事件驱动,带有攻击窗口、挂点、跟随目标、碰撞球和一次性/循环生命周期;当前 effect_playback_test 只验证通用 MSE 播放/停止/重播,combat_fx_test 只验证战斗门和桩,不是逐技能 MSA→MSE→命中结果时间线。
  • 本轮 skill_test.gdcombat_fx_test.gdeffect_playback_test.gdnet_state_queue_test 均退出码 0。它们证明现有表格、技能 UI、战斗状态机、通用特效播放和 FUNC_SKILL 队列不回归,但不能把动作循环、移动技能、grade、挂点/颜色/混合、飞行/命中事件和服务端结果判定视为 40250 等价。

本轮没有修改实现;技能基础校验和部分本地动作链可用,但正式技能特效接线、远端动作参数和资源驱动事件仍未闭合,合同保持 PARTIAL

Active MSA motion ownership review

本轮继续对照 CInstanceBase::NEW_UseSkillCActorInstance::SetMotionLoopCountCActorInstance::MotionEventProcess 核对施法锁定的结束源。此前本地入口虽然已经绑定真实 MSA 并支持 LoopData,但 NetPlay 仍会无条件按 clip duration 清除 _using_skill,因此 当循环片段或动作尾部晚于名义 duration 时会提前解锁,和 40250 的动作所有权不一致。

现已修复:GameScene._on_skill_cast_started() 将“技能 MSA 是否成功绑定”传入 NetPlay.start_skill_cast();有效动作设置为 motion-driven,施法门只在 PlayerView 的动作尾部发出 motion_bound("wait") 时清除;缺失 MSA 或 headless 测试仍使用 duration 作为兼容兜底。is_lock()_process() 两条计时路径均遵守该门,死亡/重置/网络 状态失效也会清除 motion-driven 状态,避免下一次施法继承旧门。

新增回归覆盖“名义时长已过但 MSA 动作未结束仍保持锁定”和“动作尾事件后解锁”。 target_effect_testtest_no_auto_move_regressionnetplay_testskill_testcombat_fx_testeffect_playback_testtest_skill_state_arg_paritynet_world_remote_action_testplayer_motion_test 及既有战斗/技能循环测试均通过。

Active GameLib skill-event review

  • GameLib/ActorInstanceEvent.cpp 明确把 OnAttackOnUseSkillOnHit 和 affect 清理交给 actor event handlerOnUseSkilluLoopCount 与 moving-skill bit 组合后才传给上层,不能用固定施法延迟完全替代。
  • GameLib/ActorInstance.h 的 motion queue、IsUsingMovingSkillCanCancelSkillMotionEventProcessProcessMotionEventEffectToTargetEventProcessMotionEventSpecialAttackingProcessMotionEventFly 构成技能时序的消费者;当前 GameScene 主要处理本地主角 signal,远端/怪物的同一事件分支没有完整接线。
  • 本轮完成 GameLib actor skill/event 核对;现有 skill_test/effect_playback_test 仍只是局部资源与 helper 证据,技能合同继续为 PARTIAL

Equivalence matrix

项目 状态 备注
Preconditions PARTIAL 等级、职业、目标、冷却已有本地校验;真实服务端权限仍待验证
Branch structure PARTIAL 单体/范围/辅助及失败分支已有代码映射;中断仍需端到端记录
Algorithms/formulas PARTIAL 范围/目标选择已映射,技能公式仍由服务端权威
State transition order VERIFIED `FUNC_SKILL
Constants/units UNASSESSED 范围、持续时间、速度和坐标
Timing/event sources PARTIAL 有效本地 MSA 的施法门现在由 motion_bound("wait") 动作尾释放,缺失动作才使用兼容计时器;逐技能 MSA 关键帧和特效事件仍未完整接线
Resource/data sources UNASSESSED skilldesc、skilltable、MSA、MSE 和挂点
Protocol side effects PARTIAL 技能状态上行、动作状态接收已覆盖;实际结果包仍需服务端验证
Interruption/failure/cleanup PARTIAL 远端不可取消技能/表情会阻止后续 TCP 状态,动作尾、节点剔除和换图会清门;可取消技能、死亡中断、重复施法和特效回收仍未全闭合

Remaining

  • 逐技能类型建立 40250 → Godot 分支映射。
  • 用至少一个真实攻击技能和一个辅助技能完成时序测试。
  • 远端 FUNC_SKILL 的动作循环次数、移动技能标志和等级动作索引已完成传递;本地有效 MSA 的施法锁定已改为动作尾驱动;逐技能 MSA 事件到 MSE 的挂点、颜色/混合和服务端结果仍需继续验证。
  • 继续补齐未实现的颜色、混合、挂点和动作事件语义。

Active UserInterface skill-data review

  • PythonSkill.h 还负责从 skill table/desc 注册技能、计算 cooldown/SP/continuation/loop/target/duration 和 motion index;这些值是 PythonPlayerSkill、quickslot 与 ActorInstanceEvent 的输入,不能由固定 UI 延迟代替。
  • 本轮确认技能数据桥已被 active Visual Studio 工程引用,但当前端动作事件链仍没有证明使用同一套资源字段驱动循环次数、移动技能标志、grade、飞行/命中/特殊攻击挂点和 cleanup;合同保持 PARTIAL

Implementation fix round 2026-09-21T12:30Z

本轮按 40250 CActorInstance::__OnUseSkill 的确切打包规则闭合技能动作参数丢失: uArg = uLoopCount | (isMovingSkill ? 1 << 4 : 0)SkillTable 现在从 .mskMotionLoopCountFormulaSkillPoint 计算低 4 位,并把 MOVING_SKILL 属性编码到 第 4 位;快捷栏和本地 GameScene 都使用同一份值,M2Client.cast_skill 不再固定发送 零参数。

远端链路新增 Entity.func_argTCP state queue 在“走到目标后施法”与“近距离立即施法” 两条分支都保留该参数;NetWorld._apply_anim 再解出循环次数和移动技能标志传给 PlayerView.play_skill_motion。新增 test_skill_state_arg_parity.gd 与原生 net_state_queue_test 断言了位编码、近距离分支和远距离到达分支;技能表/快捷栏、场景装配、 战斗特效、通用播放及全量 23 项 CTest 均通过。

这一轮修复的是动作参数和状态链,不宣称已经实现 SetMotionLoopCount 的真实循环片段、 远端 grade 的权威来源,或完整 MSA→MSE→命中结果时间线;挂点、颜色/混合、飞行/特殊攻击 事件、服务端结果和中断清理继续保持 PARTIAL

Implementation fix round 2026-09-21 — remote motion gates

按 40250 __CanProcessNetworkStatePacket / __IsEnableTCPProcess 补齐远端动作期间的 网络状态门:不可取消技能、活动表情和 TCP 状态禁用会保持后续 GC_MOVE 在队列中;技能或 表情的真实一次性动作通过 motion_bound("wait") 尾事件释放,而不是使用固定计时器。FUNC_EMOTIONuArg 也会尝试进入实际 CRaceMotionData action ID,出生时携带动作状态的实体会立即绑定 初始动作。

net_state_queue_testnet_world_remote_action_testnet_world_vis_testnetplay_testplayer_motion_testtest_skill_state_arg_parity 和完整 ctest(23/23)通过。技能可取消 性的权威 MSA 数据、grade、逐技能关键帧/特效事件、双人表情目标和完整死亡/断线清理仍为 PARTIAL

Implementation fix round 2026-09-22 — MSA LoopData / SetMotionLoopCount

按 40250 CInstanceBase::NEW_UseSkillCActorInstance::SetMotionLoopCountCurrentMotionProcess 的实际语义补齐技能动作循环:Metin2AnimPlayer 现在读取当前 .msaLoopData,保留动作的 lead-in/tail,只在 LoopStartTime..LoopEndTime 片段 内回绕;正数 uMotLoopCount 在动作绑定后覆盖资源默认次数,0 使用资源中的 MotionLoopCount-1 保持无限片段循环。循环边界会重复派发该段动作事件,最终尾部才发出 playback_finished,因此 motion_bound("wait")/技能网络门不会提前释放。

PlayerView.play_skill_motion() 已把本地与远端统一传入的 loop count 交给原生播放器。 新增 skill_motion_loop_test.gd 覆盖 PlayerView 参数传递、真实 yeonsa.msa 的资源默认值、 覆盖为 2 时的一次片段回绕、计数递减、尾部结束和切换普通动作后的清理;技能表、状态参数、 远端动作、特效播放和 PlayerView 回归均通过。

该轮闭合了 SetMotionLoopCount/MSA 片段时序差异,但技能 grade 的权威来源、逐技能 MSA 事件到 MSE 的挂点/颜色/混合、飞行/特殊攻击、服务端结果和完整中断清理仍保持 PARTIAL

Implementation fix round 2026-09-21T03:46Z — skill grade motion index

本轮先在 project/skill_test.gd 加入等级动作回归,修复前复现 1 项失败:技能表没有 等级动作索引入口。对照 40250 PythonSkill.cpp::GetSkillMotionIndexSKILL_EFFECT_COUNT=4SKILL_GRADEGAP=25 以及当前资源 assets/root/constinfo.pyUSE_SKILL_EFFECT_UPGRADE_ENABLE=1 后完成修复:

  • SkillTable::motion_idx_for_gradebase + grade * 25 计算 M/G/P 动作索引, 超出该技能 grade_count 回退基础索引;新增 motion_name_by_idx / motion_grade_by_idx 让远端收到 26/51/76 等等级动作索引时能恢复同一技能和对应 _2/_3/_4.msa
  • Quickbar 普通技能快捷栏和技能窗口直施路径在 cast_skill 前读取当前 master grade 不再只发送基础 motion index;公会技能仍按 40250 UseGuildSkill 使用基础索引。
  • NetWorld::_apply_anim 从远端 FUNC_SKILL 的等级动作索引反解 grade,再传给 PlayerView.play_skill_motion;循环次数和 moving-skill bit 保持原有编码。

修复后通过:skill_test.gdnet_world_remote_action_test.gdtest_skill_state_arg_parity.gdskill_motion_loop_test.gdskill_fx_test.gdcombat_fx_test.gd。本轮只闭合了等级动作索引及其本地/远端映射,不宣称逐技能 MSA 事件、MSE 挂点/颜色/混合、飞行/特殊攻击、服务端结果和完整中断清理已完全等价,合同继续为 PARTIAL

Implementation fix round 2026-09-21 — remote SPECIAL_ATTACKING consumer

本轮按 40250 ActorInstanceMotionEvent.cpp::ProcessMotionEventSpecialAttackingInstanceBaseBattle.cpp::AttackProcessActorInstanceCollisionDetection.cpp::__SplashAttackProcess 继续核对技能事件消费者。 参考端的 SPECIAL_ATTACKING 不是只有本地主角才能创建的事件:每个可见 CActorInstance 都会建立自己的 m_kSplashArea,随后由 AttackProcess 遍历目标,执行 1000cm broad-phase、ATTACK_TYPE_SNIPE 目标限制、碰撞球检测、HittedObjectSet 去重和 iHitLimitCount 上限,最后调用 __ProcessDataAttackSuccess;只有本地事件处理器才把 isEnableHitProcess 转成 CG_ATTACK

修复前 GameScene._on_remote_motion_event() 只处理远端特效/飞行/隐藏事件,远端 type 4 事件没有消费者;NetPlay 只有本地主角的 start_motion_splash()_splash_attack_process()。现在:

  • NetWorld._apply_anim() 保存当前远端 FUNC_SKILL 的 motion index,供事件恢复 uSkill
  • GameScene._on_remote_motion_event() 将 type 4 送入 NetPlay.start_remote_motion_splash()
  • NetPlay._remote_splash_attack_process() 使用与本地相同的 MotionSplash 几何、持续时间、 去重、命中上限和攻击可达性判定;
  • 远端命中复用 __ProcessDataAttackSuccess 对应的硬直、无敌时间、受击动作、抖动和击退 分支,但把 InsertDelay 施加在远端攻击者,且禁止远端命中伪造本地 CG_ATTACK

新增 remote_motion_splash_test.gd 覆盖创建、命中反应、攻击者硬直、无上行攻击包、同 VID 去重和 DuringTime 到期清理。该回归与 net_world_remote_action_test.gdnet_world_motion_event_test.gdmotion_splash_test.gdcombat_fx_test.gdskill_test.gd 均通过。

本轮只闭合了远端 SPECIAL_ATTACKING 的本地消费者;真实服务端伤害/经验结果、完整技能 MSA→MSE 挂点/颜色/混合、远端资源矩阵、所有中断/断线清理仍保持 PARTIAL