- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
16 KiB
skill.cast-effect-timing
Scope
比较技能等级、目标选择、施法前置、动作事件、伤害/状态结果以及粒子和命中特效的时序。
Reference call chain
UserInterface/PythonSkill.cppUserInterface/PythonPlayerSkill.cppUserInterface/PythonNetworkStreamPhaseGame.cppUserInterface/InstanceBaseEffect.cppEffectLib/EffectInstance.cppEffectLib/ParticleInstance.cppGameLib/ActorInstanceMotionEvent.cppUserInterface/InstanceBase.cpp
Current call chain
project/player_skill.gdproject/skill_table.gdproject/net_play.gdproject/net_world.gdproject/fx/skill_fx.gdproject/fx/effect_player.gdextension/src/net/entity_store.cppextension/tests/net_state_queue_test.cppproject/combat_fx_test.gdproject/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_SKILL 取 motion_idx 并调用 PlayerView.play_skill_motion(motion_name),没有把 40250 NEW_UseSkill/到达逻辑中的 uArg & 0x0f 动作循环次数和 (uArg >> 4) 移动技能标志传入视图;PlayerView.play_skill_motion 的 grade 也没有来自网络状态的真实来源。这样只能证明“技能状态会进入”,不能证明每个技能的动作循环、移动技能动作和结束时机与 40250 一致。
同时,当前 SkillFx/NetPlay 仍有固定施法锁定和特效时长路径;40250 的关键帧、挂点、颜色/混合和动作事件来自 MSA/MSE 与 ActorInstanceMotionEvent。这些资源驱动事件尚未建立逐技能时间线,因此本合同保持 PARTIAL。
Deep audit round 2026-09-20
本轮沿 CInstanceBase::NEW_UseSkill、CActorInstance::ProcessMotionEvent*、本地施法入口和远端 FUNC_SKILL 路径复核,并重新执行技能/战斗/特效回归:
- 40250
NEW_UseSkill明确接收uMotLoopCount和isMovingSkill,调用InterceptOnceMotion、__OnUseSkill,再设置动作循环次数;当前NetPlay.on_use_skill()只把motion_skill和arg上行,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_FLY、EFFECT_TO_TARGET、SPECIAL_ATTACKING和SOUND都由动作事件驱动,带有攻击窗口、挂点、跟随目标、碰撞球和一次性/循环生命周期;当前effect_playback_test只验证通用 MSE 播放/停止/重播,combat_fx_test只验证战斗门和桩,不是逐技能 MSA→MSE→命中结果时间线。 - 本轮
skill_test.gd、combat_fx_test.gd、effect_playback_test.gd和net_state_queue_test均退出码 0。它们证明现有表格、技能 UI、战斗状态机、通用特效播放和FUNC_SKILL队列不回归,但不能把动作循环、移动技能、grade、挂点/颜色/混合、飞行/命中事件和服务端结果判定视为 40250 等价。
本轮没有修改实现;技能基础校验和部分本地动作链可用,但正式技能特效接线、远端动作参数和资源驱动事件仍未闭合,合同保持 PARTIAL。
Active MSA motion ownership review
本轮继续对照 CInstanceBase::NEW_UseSkill、CActorInstance::SetMotionLoopCount 和
CActorInstance::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_test、test_no_auto_move_regression、netplay_test、skill_test、
combat_fx_test、effect_playback_test、test_skill_state_arg_parity、
net_world_remote_action_test、player_motion_test 及既有战斗/技能循环测试均通过。
Active GameLib skill-event review
GameLib/ActorInstanceEvent.cpp明确把OnAttack、OnUseSkill、OnHit和 affect 清理交给 actor event handler;OnUseSkill的uLoopCount与 moving-skill bit 组合后才传给上层,不能用固定施法延迟完全替代。GameLib/ActorInstance.h的 motion queue、IsUsingMovingSkill、CanCancelSkill、MotionEventProcess、ProcessMotionEventEffectToTargetEvent、ProcessMotionEventSpecialAttacking和ProcessMotionEventFly构成技能时序的消费者;当前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 现在从 .msk
的 MotionLoopCountFormula 按 SkillPoint 计算低 4 位,并把 MOVING_SKILL 属性编码到
第 4 位;快捷栏和本地 GameScene 都使用同一份值,M2Client.cast_skill 不再固定发送
零参数。
远端链路新增 Entity.func_arg,TCP 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_EMOTION
的 uArg 也会尝试进入实际 CRaceMotionData action ID,出生时携带动作状态的实体会立即绑定
初始动作。
net_state_queue_test、net_world_remote_action_test、net_world_vis_test、netplay_test、
player_motion_test、test_skill_state_arg_parity 和完整 ctest(23/23)通过。技能可取消
性的权威 MSA 数据、grade、逐技能关键帧/特效事件、双人表情目标和完整死亡/断线清理仍为
PARTIAL。
Implementation fix round 2026-09-22 — MSA LoopData / SetMotionLoopCount
按 40250 CInstanceBase::NEW_UseSkill、CActorInstance::SetMotionLoopCount 和
CurrentMotionProcess 的实际语义补齐技能动作循环:Metin2AnimPlayer 现在读取当前
.msa 的 LoopData,保留动作的 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::GetSkillMotionIndex、
SKILL_EFFECT_COUNT=4、SKILL_GRADEGAP=25 以及当前资源
assets/root/constinfo.py 的 USE_SKILL_EFFECT_UPGRADE_ENABLE=1 后完成修复:
SkillTable::motion_idx_for_grade按base + 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;公会技能仍按 40250UseGuildSkill使用基础索引。NetWorld::_apply_anim从远端 FUNC_SKILL 的等级动作索引反解 grade,再传给PlayerView.play_skill_motion;循环次数和 moving-skill bit 保持原有编码。
修复后通过:skill_test.gd、net_world_remote_action_test.gd、
test_skill_state_arg_parity.gd、skill_motion_loop_test.gd、skill_fx_test.gd、
combat_fx_test.gd。本轮只闭合了等级动作索引及其本地/远端映射,不宣称逐技能 MSA
事件、MSE 挂点/颜色/混合、飞行/特殊攻击、服务端结果和完整中断清理已完全等价,合同继续为
PARTIAL。
Implementation fix round 2026-09-21 — remote SPECIAL_ATTACKING consumer
本轮按 40250 ActorInstanceMotionEvent.cpp::ProcessMotionEventSpecialAttacking、
InstanceBaseBattle.cpp::AttackProcess 和
ActorInstanceCollisionDetection.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.gd、
net_world_motion_event_test.gd、motion_splash_test.gd、combat_fx_test.gd、
skill_test.gd 均通过。
本轮只闭合了远端 SPECIAL_ATTACKING 的本地消费者;真实服务端伤害/经验结果、完整技能
MSA→MSE 挂点/颜色/混合、远端资源矩阵、所有中断/断线清理仍保持 PARTIAL。