- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
240 lines
18 KiB
Markdown
240 lines
18 KiB
Markdown
# combat.attack_combo_damage
|
||
|
||
## Scope
|
||
|
||
审计普攻、连击输入窗口、攻击动作状态包、`.msa` 命中窗口、碰撞命中、伤害包、击退/硬直、弓箭 FLY 帧和换图清理。
|
||
|
||
## Reference call chain
|
||
|
||
- `CPythonPlayer::NEW_Attack`
|
||
- 检查 private shop、`__CanAttack`、死亡/晕眩/施法/坐骑等级/弓箭条件。
|
||
- 按目标/方向调用 `CInstanceBase::NEW_Attack` 或 `NEW_AttackToDestInstanceDirection`。
|
||
- `CInstanceBase::InputComboAttack` / `__ComboProcess` / `__RunNextCombo`
|
||
- 根据职业、武器、坐骑、连击技能等级和 `ComboInputData` 推进段号;动作结束时清理 combo。
|
||
- `CPythonPlayerEventHandler::OnAttack`
|
||
- 每个动作段发送 `FUNC_COMBO + motion index` 状态包。
|
||
- `CActorInstance::AttackProcess` / `__NormalAttackProcess`
|
||
- 在 `.msa` 的 hit window 内用武器扫掠球和目标 `.msm` 防御球检测命中。
|
||
- `CPythonPlayerEventHandler::OnHit`
|
||
- 设置目标并发送 `CG_ATTACK`;根据受击方 owner、巨型/霸体和外力执行击退及同步位置。
|
||
- `CNormalBowAttack_FlyEventHandler_AutoClear`
|
||
- 起手 `OnSetFlyTarget` 发送目标,`.msa FLY` 帧 `OnShoot` 发送 `CG_SHOOT`;事件处理器随旧 Actor/动作生命周期失效。
|
||
|
||
## Current call chain
|
||
|
||
- `NetPlay._on_pick` / `_update_auto_attack` / `_reserve_process_click_actor`
|
||
- 目标选择、攻击距离、自动攻击和预约追击。
|
||
- `NetPlay._do_attack_swing` / `_run_next_combo` / `_combo_process`
|
||
- 播放动作、发送 `FUNC_COMBO`、读取 `.msa` combo/hit windows 和攻击速度。
|
||
- `NetPlay._attack_process` / `_normal_attack_process`
|
||
- 读取攻击者采样点、目标 defending spheres,执行 Z-cylinder 几何判定和 hit dedup。
|
||
- `NetPlay._process_attack_success` / `_on_hit`
|
||
- 受击动画、特效、硬直、击退、目标更新和 `client.attack`。
|
||
- `GameScene._queue_shot` / `_on_local_motion_event(type=FLY)`
|
||
- 将弓箭技能先排队,等 `.msa FLY` 帧再发送 `CG_SHOOT`;`_on_world_reset` 现在清理旧队列。
|
||
|
||
## Branch matrix
|
||
|
||
| 场景 | 40250 | 当前端 | 结论 |
|
||
|---|---|---|---|
|
||
| 普攻输入 | `NEW_Attack` 由方向/目标/按键分派,并受 CanAttack gates 限制 | `_on_pick`、SPACE/auto attack 和 `_do_attack_swing` 已接入 | PARTIAL:鼠标 press/release owner 不完全相同 |
|
||
| 连击段 | `ComboInputData` 的 start/next/end 时间和动作尾清理段号 | `.msa` 数据、combo 表和 motion_bound 清理已实现 | MAPPED + tests |
|
||
| 状态包 | 每段 `FUNC_COMBO + motion index`,技能使用 `FUNC_SKILL|motion` | `_send_state` 发送对应 func/arg/位置 | MAPPED |
|
||
| `.msa` 命中窗 | AttackProcess 每帧扫掠真实武器球与 `.msm` 防御球 | 仅在有效 `AttackingData/HitDataContainer` 和真实 defending sphere/bone 存在时判定 | FIXED + regression;WeaponTrace 视觉副作用仍未等价 |
|
||
| 命中限制 | `m_HitDataMap`、`fInvisibleTime`、hit limit、combo 每窗去重 | `_hit_dedup`、invisible、limit 和 combo 分支已实现 | MAPPED + tests |
|
||
| 受击后果 | `OnHit` 发 CG_ATTACK,按 owner/巨型/霸体决定击退和 sync position | `_process_attack_success` / `_on_hit` 已映射 | PARTIAL:服务端确认/死亡包实际闭环未 fixture 化 |
|
||
| 弓箭 | 起手 target,FLY 帧才 shoot;旧 handler 清除 | `_pending_shots` 等 FLY 帧发送;本轮已在 world reset 清空 | MAPPED + regression |
|
||
| 换图/断线 | 旧 Actor handler 和动作状态失效,不带旧攻击事件 | reset 已清 shot queue,其他攻击/命中窗状态仍分散 | PARTIAL |
|
||
|
||
## Equivalence matrix
|
||
|
||
| 项目 | 状态 | 证据 |
|
||
|---|---|---|
|
||
| Preconditions | MAPPED | `_can_attack`、safe/dead/stun/skill/mount/arrow gates 已定位 |
|
||
| Branch structure | PARTIAL | 鼠标 press/release、方向攻击、自动攻击、弓箭和坐骑完整排列不足 |
|
||
| Algorithms/formulas | PARTIAL | combo/hit geometry 已映射;缺失资源 fallback 已移除,WeaponTrace 和服务端伤害仍不等价 |
|
||
| State transition order | PARTIAL | 动作绑定、hit window、CG_ATTACK、击退和 sync position 顺序需包级证明 |
|
||
| Constants/units | PARTIAL | 攻击距离、speed ratio、hit limit、stiffen/invisible 等需从资源和 fixture 双证 |
|
||
| Timing/event sources | PARTIAL | 参考由 Actor motion event 驱动,当前由 Godot `_process` 和 signal 驱动 |
|
||
| Resource/data sources | PARTIAL | `.msa/.msm` 资源读取和缺失资源拒绝分支已接入;真实资源覆盖与 WeaponTrace 数据源仍未完全证明 |
|
||
| Protocol side effects | PARTIAL | FUNC_COMBO/CG_ATTACK/CG_SHOOT 已映射,真实服务端回包闭环未验证 |
|
||
| Interruption/failure/cleanup | PARTIAL | 本轮补了 pending shot;死亡、换图、断线、动作缺失和重复命中仍需测试 |
|
||
|
||
## Regression evidence
|
||
|
||
- `project/combat_fx_test.gd`:攻击锁定、连击段、hit window、命中去重、击退、抗击倒、弓箭 target/shoot 时序。
|
||
- `project/combat_parity_test.gd`:攻击锁、伤害数字和受击反馈。
|
||
- `project/gamescene_test.gd`:world reset 清理旧弓箭 FLY/CG_SHOOT 队列。
|
||
- `extension/tests/net_state_queue_test.cpp`:远端 `FUNC_ATTACK`、`FUNC_COMBO`、`FUNC_SKILL` 到达/动作状态。
|
||
|
||
### Active GameLib weapon-trace review
|
||
|
||
- `GameLib/WeaponTrace.cpp`/`.h` 使用动态池,按武器骨骼矩阵采样时间点,构建连续刀光顶点带,并分别支持纹理/alpha、生命周期、采样间隔、reach scale、TurnOn/TurnOff 和清理;它是 `ActorInstance` 攻击动作的渲染副作用,不是一次性的命中特效。
|
||
- 当前战斗链有 `skill_fx`、hit effect 和动作事件,但没有证明每个武器类型的骨骼采样、刀光顶点、alpha/texture 模式和跨动作清理与 `WeaponTrace` 相同;现有 `combat_fx_test` 只覆盖战斗桩和效果入口。
|
||
- 本轮完成 `WeaponTrace` 活跃源码静态核对,攻击/连击合同继续为 `PARTIAL`。
|
||
|
||
## Status
|
||
|
||
`PARTIAL`。普攻/连击主体已经有较完整的本地模拟;当前已删除缺少有效动作攻击数据时的本地 CG_ATTACK 合成路径,但 WeaponTrace、服务端实际伤害结果和完整输入/生命周期矩阵仍未达到 40250 完全等价。
|
||
|
||
## Audit round 2026-09-20T21:12Z
|
||
|
||
本轮沿 `CActorInstance::AttackingProcess -> __NormalAttackProcess -> OnHit` 重新核对当前攻击实现,并重跑 `combat_fx_test`、`combat_parity_test`、`gamescene_test` 和 23 项 native CTest:
|
||
|
||
- 状态包顺序、命中窗建立、`fInvisibleTime`/hit-limit 去重、受击硬直/击退、弓箭 FLY 帧以及换图清理已有可执行实现和回归证据。
|
||
- 明确保留差异:当前 `_emit_swing` 在缺少 `.msa` 命中窗时立即退化发送 `CG_ATTACK`,`_normal_attack_process` 在空 samples 时使用正前方/近距离兜底;40250 在没有有效 `m_pkCurRaceMotionData`/`HitDataContainer` 时不会合成这条本地命中路径。
|
||
- `WeaponTrace` 仍只有参考端源码证据,当前没有动态武器骨骼采样、刀光顶点带、alpha/texture 模式和动作中断回收的等价实现。
|
||
- native CTest 首次串行回归出现一次 `net.classic_encstream` 的加密头断言失败,单测复跑和全量复跑均通过;暂记为测试环境/时序抖动,不作为本轮攻击实现通过依据。
|
||
|
||
结论:缺失资源 fallback 已按 40250 的无有效攻击数据分支修复;WeaponTrace 和真实服务端伤害/死亡/击退包序进入下一轮修复候选,合同保持 `PARTIAL`。
|
||
|
||
## Audit and implementation round 2026-09-21
|
||
|
||
沿 `ActorInstanceBattle.cpp::ComboAttack`、`ActorInstanceMotion.cpp::__SetMotion` 和
|
||
`PythonPlayerEventHandler::OnAttack` 重新核对事件顺序,确认参考端是:先绑定
|
||
`InterceptOnceMotion`(同时绑定攻击数据、清理命中表,并触发弓箭 `OnSetFlyTarget`),再由
|
||
`__OnAttack` 发送 `FUNC_COMBO + motion index`。当前端原先在 `_emit_swing` 中先发状态包、再绑定
|
||
Godot 动作,存在服务端状态已发出但本地命中窗口尚未建立的顺序差异。
|
||
|
||
本轮已调整为“绑定动作/建立命中窗口 → 弓箭 SetFlyTarget → 发送 FUNC_COMBO”,并新增
|
||
`combat_fx_test.gd` 的跨对象顺序断言;`combat_fx_test.gd`、`combat_parity_test.gd`、
|
||
`gamescene_test.gd` 均通过。
|
||
|
||
仍保持 `PARTIAL`:当前没有 `WeaponTrace` 的骨骼采样/刀光生命周期实现,也没有真实服务器对
|
||
`CG_ATTACK`、`CG_SYNC_POSITION`、死亡/伤害/击退包的端到端 fixture;弓箭和攻击状态在换图、
|
||
断线、动作中断后的全部清理仍需要继续核对。
|
||
|
||
## Implementation fix round 2026-09-20T21:18Z
|
||
|
||
按 40250 `CActorInstance::isValidAttacking` 与
|
||
`ActorInstanceCollisionDetection.cpp::__NormalAttackProcess` 修复攻击命中源:
|
||
|
||
- `NetPlay::_emit_swing` 不再在缺少当前动作 `AttackingData/HitDataContainer` 时直接发送 `CG_ATTACK`;仍只发送动作对应的 `FUNC_COMBO` 状态包,真正的 `CG_ATTACK` 只能由命中处理链触发。
|
||
- `NetPlay::_attack_process` 现在要求当前动作存在有效 `AttackingData` 和命中窗;空命中窗不命中。
|
||
- `_defending_spheres` 不再为缺少 `.msm` 防御球的节点合成占位胶囊;空 `m_DefendingPointInstanceList` 直接无命中。
|
||
- `combat_fx_test.gd`、`netplay_test.gd` 增加缺少动作攻击数据和缺少防御球的反例,`combat_parity_test.gd`、`playable_combat_test.gd` 同步回归通过。
|
||
|
||
本轮修复了“缺失资源仍产生本地命中”的结构差异;`WeaponTrace` 骨骼采样/刀光生命周期、
|
||
真实服务端伤害确认和死亡/击退包序仍保持 `PARTIAL`。
|
||
|
||
## Audit round 2026-09-20T21:24Z — WeaponTrace call-chain review
|
||
|
||
本轮只做差异审计,没有把现有 `has_trail` 配置字段或一次性 `.mse` 特效误判为
|
||
`WeaponTrace` 已实现。沿 40250 的完整调用链核对结果如下:
|
||
|
||
- `ActorInstanceAttach.cpp::AttachWeapon` 仅对非 bell/fan/bow 武器创建
|
||
`CWeaponTrace`,并绑定武器模型实例;当前 `equip_model.gd` 只设置
|
||
`weapon_gr2`/`shield_gr2` 与强化 `.mse`,没有按武器类型创建轨迹对象。
|
||
- `ActorInstance::INSTANCEBASE_Deform` 每帧执行 `TraceProcess`;当前
|
||
`PlayerView`/`Metin2Model` 没有对应的每帧武器骨骼采样出口。
|
||
- `CWeaponTrace::Update` 同时维护 short/long 两条世界坐标时间队列,使用默认
|
||
`lifeTime=0.18s`、`samplingTime=0.003s`、武器 bound-box 长度和
|
||
`__GetReachScale()` 生成刀根/刀尖采样;当前端没有时间队列、reach scale、采样间隔
|
||
或真实武器骨骼矩阵等价物。
|
||
- `CWeaponTrace::BuildVertex/Render` 用 cubic spline 生成 triangle strip,分别提供
|
||
alpha/texture 渲染模式;当前端没有 WeaponTrace mesh、顶点带、材质模式或独立
|
||
`RenderTrace` 阶段。`socket_glow_fx_system.gd` 的 `has_trail` 只是强化配置结果,
|
||
没有消费方。
|
||
- `ActorInstanceMotion.cpp::__SetMotion` 在绑定新动作前统一 `__HideWeaponTrace`,
|
||
成功绑定可攻击动作后 `__ShowWeaponTrace`;`Destroy/Clear` 删除全部 trace,
|
||
`TurnOff` 保留旧队列以便自然淡出。当前 `_bind`、播放结束、换装、换图和销毁路径
|
||
都没有这一组开关/淡出/清理语义。
|
||
|
||
现有证据:`player_motion_test.gd`、`combat_fx_test.gd`、`combat_parity_test.gd` 和
|
||
`playable_combat_test.gd` 通过,但它们只能证明动作/战斗门和命中链,不能证明刀光。
|
||
`weapon_attach_test.gd` 当前还存在 race 0 的 03150 双手动作左手贴合断言失败并伴随
|
||
exit 139;该失败属于武器动作绑定的独立问题,也说明不能用当前武器挂点测试替代
|
||
WeaponTrace 证据。
|
||
|
||
结论:`combat.attack_combo_damage` 继续为 `PARTIAL`。下一次实现应新增独立
|
||
`WeaponTrace` 适配层,并在真实武器模型上验证“创建过滤 → 动作开关 → 双轨迹采样 →
|
||
刀光网格/alpha → 动作中断自然淡出 → 换装/换图销毁”的全链路;在此之前不得声称
|
||
40250 的武器刀光已经复刻。
|
||
|
||
## Implementation round 2026-09-20 — WeaponTrace adapter
|
||
|
||
已新增 `project/ui/weapon_trace.gd`,并将它接入
|
||
`project/ui/player_view.gd` 与 `project/ui/equip_model.gd`,对应 40250 的以下调用边界:
|
||
|
||
- `EquipModel._weapon_trace_enabled()` 对 bell/fan/bow 不创建刀光,普通武器才允许创建;换装和空武器路径执行 detach/clear/free。
|
||
- `PlayerView._refresh_weapon_trace()` 在附着的 `Weapon` 模型上建立 trace;`_bind()` 在绑定新动作前 `TurnOff`,攻击/连击动作成功绑定后 `TurnOn`,播放结束保留旧点并自然淡出。
|
||
- `WeaponTrace._process()` 每帧维护 short/long 世界坐标时间队列;默认 `0.18s` 生命周期、`0.003s` spline 采样步长,并复刻参考端三对角 cubic spline 与 triangle strip 顶点交错。
|
||
- `StandardMaterial3D` 提供双面、透明、无光照、不写深度的 alpha 路径,并保留 texture/alpha 两种模式;独立 `weapon_trace_test.gd` 覆盖附着、采样、网格、TurnOff 尾迹、过期、重挂和 PlayerView 攻击开关。
|
||
|
||
当前仍有明确适配差异,不能标记为完全一致:Godot 端点由 attached weapon 的 mesh AABB 推导,尚未由 native bone matrix 直接提供
|
||
`m_fLength * __GetReachScale()` 的同一坐标语义;远端/怪物渲染覆盖、`lot_ade10-2.tga`
|
||
真实视觉资源和服务端动作资源驱动的截图证据仍缺;`weapon_attach_test.gd` 的 race 0 /
|
||
03150 双手动作左手贴合断言仍失败并 exit 139。`socket_glow_fx_system.gd::has_trail`
|
||
仍是独立强化配置,不能当作 WeaponTrace 消费方。
|
||
|
||
结论:本轮把 WeaponTrace 从“生产链缺失”修复为“生产链已接入、核心生命周期有证据、坐标端点和覆盖范围仍 PARTIAL”。
|
||
|
||
## Implementation fix round 2026-09-21T03:42Z — MainInstance damage billboard
|
||
|
||
沿 40250 `PythonNetworkStreamPhaseGame::RecvDamageInfoPacket` →
|
||
`CInstanceBase::AddDamageEffect` → `CInstanceBase::ProcessDamage` 复核本地伤害显示:
|
||
参考端只要 `CharacterManager` 找到目标实例,且目标就是 `MainInstance`,仍会按
|
||
`bSelf` 入伤害队列并在本地主角上显示 `damage_*.dds`/MISS。
|
||
|
||
当前端 GameScene 为避免把本地主角重复挂入 `_by_vid`,将其单独保存为 `_local_node`。
|
||
修复前 `NetWorld._process_damage_queue()` 只从 `_by_vid` 取节点,导致本地主角自己的
|
||
伤害包已经入队,却在渲染前被静默丢弃。现在对本地 VID 使用 `_local_node` 回退,保持
|
||
普通远端 actor 与 MainInstance 的同一 `ProcessDamage` 分类和渲染路径。
|
||
|
||
新增 `project/damage_local_main_parity_test.gd`,修复前专项回归失败,修复后通过;
|
||
合同仍为 `PARTIAL`,因为服务端真实伤害/死亡包、WeaponTrace 坐标端点、远端/怪物覆盖和
|
||
攻击生命周期清理尚未达到 40250 完全等价。
|
||
|
||
## Implementation fix round 2026-09-21T03:46Z — multi-part WeaponTrace
|
||
|
||
沿 `ActorInstanceAttach.cpp::__IsRightHandWeapon`、`__IsLeftHandWeapon` 和
|
||
`CActorInstance::AttachWeapon` 继续核对:参考端不是每个角色只有一个刀光。
|
||
匕首会同时挂 `PART_WEAPON` 与 `PART_WEAPON_LEFT`,骑马扇也可能有双部件;每个可追踪
|
||
部件都独立创建一个 `CWeaponTrace`,并由 `m_WeaponTraceVector` 统一执行开关、纹理和
|
||
清理操作。
|
||
|
||
修复前当前 `PlayerView._refresh_weapon_trace()` 只查找 `Weapon` 并维护一个对象,
|
||
因此第二个部件的刀光不会出现。现在按 `Weapon`、`Shield` 部件顺序维护多条 trace,
|
||
逐部件执行 `attach/TurnOn/TurnOff/detach`,并补齐对应的纹理、Texture/Alpha 模式
|
||
控制入口。新增 `project/weapon_trace_multi_part_parity_test.gd`,修复前回归失败,
|
||
修复后与 `weapon_trace_test.gd`、`equip_model_test.gd`、`weapon_attach_test.gd`、
|
||
`player_motion_test.gd` 和 `combat_fx_test.gd` 一起通过。
|
||
|
||
仍为 `PARTIAL`:当前刀根/刀尖端点仍由 Godot mesh AABB 推导,不是参考端骨骼矩阵加
|
||
`__GetReachScale()`;远端/怪物 trace 覆盖、真实刀光纹理视觉和服务端攻击闭环仍未证明。
|
||
|
||
## Implementation fix round 2026-09-21T03:55Z — WeaponTrace geometry and reach scale
|
||
|
||
本轮继续沿 40250 的 `GameLib/WeaponTrace.cpp`、
|
||
`InstanceBaseEffect.cpp::__Warrior_SetGeomgyeongAffect` 和
|
||
`ActorInstanceCollisionDetection.cpp::__NormalAttackProcess` 核对端点与攻击扫掠:
|
||
|
||
- 参考端在 `SetWeaponInstance` 中从武器 bound box 到骨骼原点计算
|
||
`m_fLength`,`Update` 再沿武器局部 Z 轴使用 `m_fLength * fReachScale` 生成刀尖;
|
||
当前端不再把任意 AABB 角点当作方向,而是取 attached mesh AABB 的最大半径作为长度,
|
||
沿局部 Z 轴延伸,并提供可验证的 `reach_scale`。
|
||
- 参考端 `AFFECT_GEOMGYEONG` 开启时把 reach scale 设为 `1.5`,关闭时恢复 `1.0`;
|
||
当前 `PlayerView` 将该状态广播到所有 Weapon/Shield trace,并在重新附着时保留当前值。
|
||
- 参考端普通攻击把 `dsiSrc.v3Position - dsiSrc.v3LastPosition` 乘以
|
||
`__GetReachScale()` 后再参与碰撞变换;当前 `NetPlay._normal_attack_process` 通过
|
||
`_attack_sample_positions` 对同一段位移应用该缩放,因此视觉刀光和命中扫掠共用同一
|
||
reach scale 来源。
|
||
|
||
修复前专项回归分别复现了端点方向/长度错误及缺少 reach scale API、Geomgyeong 未改变
|
||
reach scale、攻击采样没有暴露 reach-scaled endpoint;修复后以下测试通过:
|
||
`weapon_trace_endpoint_parity_test.gd`、`weapon_trace_reach_affect_parity_test.gd`、
|
||
`weapon_reach_scale_attack_parity_test.gd`、`weapon_trace_multi_part_parity_test.gd`、
|
||
`weapon_trace_test.gd`、`combat_fx_test.gd`、`combat_parity_test.gd`、
|
||
`playable_combat_test.gd`、`test_combat_status_effect_parity.gd`、`netplay_test.gd`。
|
||
|
||
合同仍为 `PARTIAL`:当前端点的长度输入来自 Godot attached mesh AABB,尚未直接消费
|
||
native `CActorInstance` 的同一骨骼 composite matrix、旋转和绑定坐标语义;远端/怪物
|
||
覆盖、真实刀光纹理视觉、服务端真实攻击/死亡包级 fixture 和跨动作/断线清理仍未闭合。
|
||
|
||
## Audit evidence reconciliation 2026-09-21T04:00Z
|
||
|
||
重新运行当前工作树的 `weapon_attach_test.gd`,加载 `00010.gr2` 和 `03150.gr2` 均正常,
|
||
进程退出码为 0;此前记录的 race 0 / 03150 左手贴合断言失败不再是当前待办项,已从
|
||
manifest 的 `remaining` 移除。该校正只更新证据状态,不改变 WeaponTrace native
|
||
骨骼 composite matrix、远端/怪物覆盖和服务端攻击闭环仍未证明的结论。
|