fix(combat): 战斗逐项对齐 40250,修复移动 / 技能释放与武器挂点
- 连击类型由 combo_changed 驱动(SetComboSkillFlag,技能 122 等级) - 命中判定改为 .msa 刀尖 Z 圆柱 vs .msm 防御球(hit_collision.gd 逐行移植) - _register_hit 改为 map::insert 语义,修正命中上限 - 攻速同时缩放 GetAttackingElapsedTime 与攻击动作速率;按武器动作模式目录绑定攻击段 - 去掉本地连击超时,motion_bound 清命中表 / 连击段号 - __ProcessDataAttackSuccess:InsertDelay 硬直、physics_push.gd 击退 + GetBlendingPosition 同步、 命中特效、__HitGood / __HitGreate / __HitStone 受击动作链与抖动(ui/hit_reaction.gd) - ClassicSession::send_use_skill 不再夹带 CG_FLY_TARGETING - mac 前进 / 后退方向与技能释放修复;武器按职业骨骼挂到手上 - 新增 hit_collision / physics_push / hit_view / net_world_push / weapon_attach 测试; gpu_pose_bounds / race_motion_assembly 适配 damage 随机变体 - 文档:CLIENT-GAP.md、CLIENT-GAP-FIX.md §3.5 / §3.7 与 C.5 增量 130 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
This commit is contained in:
+150
-7
@@ -355,8 +355,10 @@
|
||||
普攻 `_swing_skill=0`),被击退目标记 `_victim_flush`。`_emit_swing()` 改为**只有拿不到
|
||||
命中窗**(headless / 缺资源)才在挥击时立即发 `CG_ATTACK`。`_flush_victim_list()`(对齐
|
||||
`PythonPlayerEventHandler.cpp:194`)帧末把 `_victim_flush`(≤16)打包 `client.sync_positions()`。
|
||||
几何判定为近似:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面 `±60°`、
|
||||
`(reach+20+45) cm` 弧内;真实动态圆柱扫掠需骨骼矩阵 + defending sphere(随 §3.1 留桩)。
|
||||
~~几何判定为近似:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面 `±60°`、
|
||||
`(reach+20+45) cm` 弧内;真实动态圆柱扫掠需骨骼矩阵 + defending sphere(随 §3.1 留桩)。~~
|
||||
**增量 130 已替换**为 `.msa` 刀尖扫掠 Z 圆柱 vs `.msm` 防御球的 `detect_z_cylinder` 判定(见 §3.5 修改 4 /
|
||||
C.5 增量 130)。
|
||||
`combat_fx_test.gd` 加 `FakeNetWorld` + 6 段断言(`CG_ATTACK` 延后 / 窗内命中 / `CG_SYNC_POSITION` /
|
||||
COMBO 单次 / 正面弧 / 越窗收起)。C++ 21/21 + 7 个 GDScript 回归全绿。留给增量 84:武器种类 →
|
||||
`combo_motion_mode` 映射。
|
||||
@@ -700,7 +702,7 @@
|
||||
新增 `_attack_ctx()` 从 `client.get_duel()` / `get_pvp_relations()` / `get_guild_wars()` /
|
||||
`get_party()` 装配 ctx(observer / main_vid / pk_mode / duel_mode / duel_opponents /
|
||||
pvp_pairs / gvg_pairs / party_vids)。5 处调用点(含 `__ReserveProcess_ClickActor` 第 3 步、
|
||||
`_can_shot`、`_hit_geometry`)无签名变化。
|
||||
`_can_shot`、`_hit_geometry`;增量 130 起后者并入 `_normal_attack_process`)无签名变化。
|
||||
④ 自检 `project/entity_rules_test.gd`(第 15 个 canonical,25 组断言);`combat_fx_test` /
|
||||
`netplay_test` / `fly_test` 的怪物夹具补 `ch_type: 2`(此前无 `ch_type` → `_entity_kind` 归 PC,
|
||||
桩按 PC 也「可打」;1:1 规则下和平同帝国 PC 不可打 → 夹具需明确怪类别,与真实服务器一致)。
|
||||
@@ -2352,9 +2354,23 @@ OnHit(uSkill, victimActor, isSendPacket):
|
||||
**只有拿不到命中窗数据**(headless / 缺资源)才在挥击时立即发 `CG_ATTACK`,有窗时交给
|
||||
`_attack_process`。`_flush_victim_list()`(对齐 `PythonPlayerEventHandler.cpp:194`)在
|
||||
`_process` 帧末把 `_victim_flush`(≤ `SYNC_POSITION_LIMIT` 16)打包 `client.sync_positions()`。
|
||||
几何判定是 **近似**:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面
|
||||
~~几何判定是 **近似**:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面
|
||||
`±60°`、`(reach + 20 + 45) cm` 弧内;真实的动态圆柱-圆柱扫掠需要骨骼矩阵 + defending
|
||||
sphere 数据(同 §3.1 actor 碰撞一起留桩)。`combat_fx_test.gd` 加 `FakeNetWorld` +
|
||||
sphere 数据(同 §3.1 actor 碰撞一起留桩)。~~
|
||||
**增量 130 升级为逐行判定**:删除 `_hit_deduped` / `_hit_geometry`,新增 `_normal_attack_process()`
|
||||
对齐 `__NormalAttackProcess`:
|
||||
- 距离门 `v3Distance = (dX, dZ, dZ)`:出货代码平面 Y 不参与、高度差算两次,原样保留;上限 300 cm,
|
||||
`IS_HUGE_RACE` 为 500 cm。
|
||||
- 命中窗 `[start,end]` 与 `[t-dt, t]` 相交后查 `m_HitDataMap`:COMBO 同窗不再判,其余等 `fInvisibleTime`。
|
||||
- 窗内每个采样的刀尖 `last_pos → pos` 按攻击者朝向转到世界 cm,作为半径 20 的动态 Z 圆柱,
|
||||
对受击方每个防御球做 `HitCollision.detect_z_cylinder()`(逐行移植
|
||||
`DetectCollisionDynamicZCylinderVSDynamicZCylinder` + `IntersectLineSegments`)。
|
||||
- 碰上后 `_register_hit()` → `_process_attack_success()`。
|
||||
- `_register_hit` 修正为 `map::insert` 语义:已有目标不覆盖冷却,插入后超上限才返回 FALSE。
|
||||
- 防御球取 `.msm` 的 `CollisionType 3`(`HitCollision.parse_msm_defending`):PC 读
|
||||
`root/msm/<class>_<m|w>.msm`,怪物读模型目录 `.msm`。上一帧球心缓存在 `_def_last`。
|
||||
- 判定时间改为 `_attacking_elapsed() = (_local_time - _motion_start_t) * _atk_speed_factor`
|
||||
(`GetAttackingElapsedTime`);`_local_time` 由 `_advance_local_time()` 先扣 `m_fDelay` 再推进。`combat_fx_test.gd` 加 `FakeNetWorld` +
|
||||
命中窗几何判定 / `CG_ATTACK` 延后 / `CG_SYNC_POSITION` / COMBO 单次 / 正面弧 / 越窗收起 6 段断言。
|
||||
5. `OnAttack` 事件(动作真正起手时)发 `CG_CHARACTER_MOVE{FUNC_COMBO, wMotionIndex}`,
|
||||
与 `CG_ATTACK` 是**两个不同时刻**的两个包,不能合并。**[已在增量 78 `_do_attack_swing` 落地]**
|
||||
@@ -2395,8 +2411,10 @@ OnHit(uSkill, victimActor, isSendPacket):
|
||||
修改 6 完成(增量 84:`motion_mode_for` + `_refresh_motion_mode` 按装备武器 subtype / 骑乘 / 变身
|
||||
切 `combo_motion_mode`,对齐 `RefreshState`);修改 7 完成(增量 85:`_clickable_distance()` 弓箭
|
||||
`__GetBowRange()` 分支 + `_bow_range_cm()` + `POINT_BOW_DISTANCE` 加成)。C++ 21/21 + 7 GDScript
|
||||
全绿。仍待:几何判定升级为真实动态圆柱扫掠(需骨骼矩阵 + defending sphere,随 §3.1 actor 碰撞)、
|
||||
服务端合法性校准。§3.6 箭矢 `MOTION_EVENT_TYPE_FLY` 事件已在增量 86 落地(见 §3.6)。
|
||||
全绿。~~仍待:几何判定升级为真实动态圆柱扫掠(需骨骼矩阵 + defending sphere,随 §3.1 actor 碰撞)、
|
||||
服务端合法性校准。~~ —— 增量 130 已落地:Z 圆柱 vs `.msm` 防御球,以及 `__ProcessDataAttackSuccess`
|
||||
的完整效果(见修改 4 / §3.7 / C.5 增量 130)。仍待:服务端合法性校准;防御球当前是模型本地近似,
|
||||
未跟骨骼矩阵走。§3.6 箭矢 `MOTION_EVENT_TYPE_FLY` 事件已在增量 86 落地(见 §3.6)。
|
||||
|
||||
---
|
||||
|
||||
@@ -2512,6 +2530,35 @@ handler 由 `__OnPressActor` 通过
|
||||
`netplay_test` / `netbridge_test` / `gamescene_test` / `player_motion_test`
|
||||
全绿。
|
||||
|
||||
**增量 130(受击方反应 1:1,2026-09-12)**
|
||||
|
||||
- 新增 `project/ui/hit_reaction.gd`,由 PlayerView / MobView 共用。`good()` / `greate()` / `stone()`
|
||||
逐分支对齐 `__HitGood` / `__HitGreate` / `__HitStone`:
|
||||
- 击倒中不再换受击动作,Greate 在起身中也不换。
|
||||
- 晕眩时:Good → `Die`(死亡动作由服务端驱动,seam);Greate 只播 `DAMAGE_FLYING(_BACK)`,
|
||||
并置 `m_isRealDead`,不起身。
|
||||
- `isLock`(普攻 / 连击 / 技能 / 表情 / 钓鱼等)只抖动不换动作;Greate 在 `IsUsingSkill` 时也只抖。
|
||||
- `scalar = dot(攻击方朝向, 受击方朝向)`:小于 0 走正面 `DAMAGE` / `DAMAGE_FLYING → STAND_UP`;
|
||||
否则先试 `_BACK` 版,缺资源退回正面版。
|
||||
- 动作链 = `InterceptOnceMotion` + `PushOnceMotion` + 链尾 `PushLoopMotion`,回到受击前的
|
||||
wait / walk / run。
|
||||
- `__Shake(100)`:100 ms 内模型位置每帧偏移 ±`rand()%10` cm,结束复位。
|
||||
- `InsertDelay(fStiffenTime)`:`time_scale = 0` 冻结,到期恢复该动作的 `fSpeedRatio`;冻结期间新绑定的
|
||||
动作同样保持冻结。
|
||||
- PC 受击文件按 `playersettingmodule` 注册表取,同名多份等权随机:
|
||||
- `damage` / `damage_1`;BACK 版 `damage_2` / `damage_3`
|
||||
- `damage_flying` / `back_damage_flying`
|
||||
- `falling_stand` / `back_falling_stand`
|
||||
- 怪物在 `STATE_MOTIONS` 新增 `BACK_DAMAGE` / `FRONT_KNOCKDOWN` / `BACK_KNOCKDOWN` / `FRONT_STANDUP` /
|
||||
`BACK_STANDUP`。
|
||||
- `is_in_hit_reaction()` 改为返回 `_hit.active`:整条链播完才清 `net_play._knock_down`。原来的
|
||||
`_state == "damage"` 分支并入链尾处理。
|
||||
- 击退:`net_world.push_victim()` 用 `physics_push.gd`(逐行移植 `CPhysicsObject` +
|
||||
`CEaseOutInterpolation`),每帧叠加位移。
|
||||
- `blending_position()` = `GetBlendingPosition`,供 `OnHit` 的 `CG_SYNC_POSITION` 使用。
|
||||
- 推动期间服务端位置变化:以新位置为基准继续叠加。
|
||||
- 推动结束后服务端位置一变,即丢弃偏移。
|
||||
|
||||
---
|
||||
|
||||
### 3.8 技能缺少统一合法性检查
|
||||
@@ -10145,3 +10192,99 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
seam ⑨ 名字与气泡合并为单尾标链表;完整 Quest / fishing / dungeon / mount / observer 玩法与
|
||||
复杂 UI 分支;真实服务器、真机、纯 pack、双架构和正式签名验收。
|
||||
```
|
||||
|
||||
### C.5 批次记录 · 增量 130(W2 / §3.5 · §3.7 战斗与 40250 对齐:命中判定、命中效果、受击反应、攻速、连击类型、技能包)
|
||||
|
||||
```text
|
||||
批次:增量 130 —— 逐项修正与 40250 参考端战斗实现的差异(2026-09-12)。每项先写失败断言再改实现。
|
||||
|
||||
1. 连击类型:
|
||||
net_play.gd 监听 client.combo_changed,由 _on_combo_changed() 处理,对齐
|
||||
CPythonPlayer::SetComboSkillFlag:
|
||||
· 查连击技能 122 的等级;缺槽或等级 <= 0 直接返回,不改 combo type。
|
||||
· 否则 _combo_type = enabled ? MIN(level, 2) : 0。
|
||||
· 不在本地伪造技能等级。
|
||||
|
||||
2. 命中判定:
|
||||
删除 _hit_deduped / _hit_geometry(reach + 正面 ±60° 近似)。
|
||||
· 新增 project/hit_collision.gd,逐行移植:
|
||||
IntersectLineSegments / FindNearestPointOnLineSegment / FindNearestPointOfParallelLineSegments /
|
||||
AdjustNearestPoints / DetectCollisionDynamicZCylinderVSDynamicZCylinder;
|
||||
另含 .msm CollisionType 3 防御球解析与 IS_HUGE_RACE。
|
||||
· net_play._normal_attack_process() 流程:
|
||||
(dX, dZ, dZ) 距离门 300 / 500 cm → 命中窗相交 → m_HitDataMap →
|
||||
刀尖采样半径 20 的 Z 圆柱 vs 受击方防御球(上一帧球心存 _def_last)。
|
||||
· PlayerView 取 root/msm/<class>_<m|w>.msm,MobView 取模型目录 .msm,都经 get_defending_spheres() 提供。
|
||||
· 没有 .msm 的占位节点退化为单球 HIT_FALLBACK_SPHERE(seam)。
|
||||
|
||||
3. _register_hit:
|
||||
改为 map::insert 语义:已登记的目标不刷新冷却,插入后超上限才返回 FALSE。
|
||||
|
||||
4. 攻速:
|
||||
_atk_speed_factor(bAttackSpeed/100,>1100 → 0)同时用于两处:
|
||||
· _attacking_elapsed() = (_local_time - _motion_start_t) * fSpeedRatio
|
||||
· PlayerView.play_attack_motion(mode, index, speed_ratio) 的 time_scale
|
||||
play_attack_motion 按 MOTION_MODE 目录绑定 attack(_1) / combo_0N,模式目录缺该段时回退 general。
|
||||
|
||||
5. 连击清零:
|
||||
删除本地连击超时清空。PlayerView / MobView 新增 motion_bound(state) 信号(__SetMotion 尾),
|
||||
由 net_play._on_motion_bound() 处理:
|
||||
· 新动作带 AttackingData → 清 m_HitDataMap。
|
||||
· 连击中换到无 ComboInputData 的动作 → _combo_index = 0。
|
||||
|
||||
6. __ProcessDataAttackSuccess(_process_attack_success):
|
||||
· InsertDelay(fStiffenTime):攻击者写 _delay(_advance_local_time 先扣),视图 time_scale 冻结;
|
||||
受击方同样 insert_delay,并记 m_fInvisibleTime(_victim_invisible_until,期间 AttackingProcess 跳过它)。
|
||||
· 击退门 __CanPushDestActor(_can_push):
|
||||
建筑 / 门 / 石头 / NPC / 巨型不推;晕眩必推;否则要求 owner 为主角且 owner 时间 <= 3 s。
|
||||
通过后 net_world.push_victim() 推动(physics_push.gd 逐行移植 CPhysicsObject + CEaseOutInterpolation)。
|
||||
OnHit 时目标仍在推动中 → GetBlendingPosition 进 CG_SYNC_POSITION 列表。
|
||||
· 命中特效 blow_1_low.mse:
|
||||
建筑 / 门放在攻击者身前 30 cm、不转向;巨型放在命中点;其余放在受击方。
|
||||
game_scene 装配 net_play.fx = fx、fx_parent = Entities。
|
||||
· 受击反应:石头 / 门 → hit_stone;HIT_TYPE_GOOD → hit_good;HIT_TYPE_GREAT → hit_greate;
|
||||
scalar = cos(攻击方 yaw − 受击方 yaw)。状态机 ui/hit_reaction.gd 见 §3.7 增量 130。
|
||||
· 最后 _on_hit:SetTarget + CG_ATTACK。
|
||||
|
||||
7. 技能包:
|
||||
ClassicSession::send_use_skill 删除夹带的 CG_FLY_TARGETING,只发 TPacketCGUseSkill(对齐
|
||||
SendUseSkillPacket)。飞行目标仍由施法方的 CG_ADD_FLY_TARGETING / OnSetFlyTarget 发送。
|
||||
net_classic_session_test 新增 "use skill wire: no extra fly targeting" 断言。
|
||||
|
||||
8. 既有测试随行为调整:
|
||||
受击 damage 按 playersettingmodule 注册为 damage / damage_1 等权随机。
|
||||
· gpu_pose_bounds_test:CPU / GPU 两个视图绑定前用同一 seed,保证选中同一段。
|
||||
· race_motion_assembly_test:damage 允许任一注册变体。
|
||||
这两个测试在本批次改动后曾失败(CPU / GPU 选到不同段、断言写死 damage.msa),调整后通过。
|
||||
|
||||
回归(godot --headless --path project --script <test>.gd):
|
||||
新增 PASS:hit_collision_test、physics_push_test、hit_view_test、net_world_push_test。
|
||||
PASS:combat_fx_test、gamescene_test、netplay_test、skill_test、player_move_test、input_key_test、
|
||||
weapon_attach_test、mob_view_test、player_motion_test、fly_test、remote_player_test、
|
||||
net_world_vis_test、forest_mob_render_test、p2b_test、playable_harness_test、playable_combat_test、
|
||||
gpu_pose_bounds_test、race_motion_assembly_test。
|
||||
已知失败(与本批次无关):equip_model_test "armor 11000 -> values[3]=0 回退 =vnum"(夹具期望过期)。
|
||||
forest_map_render_test 是 extends Node 的 MT_TEST_MODE 入口,不能用 --script 直跑,本批次未运行。
|
||||
ctest --test-dir build:22/23 PASS。net.classic_session 仍是既有 25 条失败
|
||||
(quest / duel / messenger / exchange / guild / safebox / mall / sync / ground item / despawn),
|
||||
都与 use_skill 无关;新增的 use skill 断言通过。
|
||||
|
||||
验收边界:
|
||||
已验证(离线):Z 圆柱几何、.msm 防御球解析、命中表 / 上限、攻速缩放、连击类型与清零、
|
||||
硬直 / 击退 / 同步位置 / 特效挂载、受击动作链 / 抖动 / 冻结、技能包线格式。
|
||||
未验证:真实服务器下的命中与击退手感、真机视觉。
|
||||
seam:
|
||||
· 击退没有地形 / 角色碰撞,也无 AdjustCollisionWithOtherObjects、溅射、IsResistFallen。
|
||||
· owner 时间取客户端观测时刻。
|
||||
· 防御球按模型本地坐标、不跟骨骼矩阵;占位节点退化为单球。
|
||||
· 远端角色不跑 AttackProcess;晕眩 → Die 不在本地播。
|
||||
· 攻击 / 受击动作变体等权随机;受击动作固定取 general 目录。
|
||||
· 抖动用模型位置偏移,InsertDelay 用 time_scale 冻结。
|
||||
· 受击链期间的循环动作请求延后到链尾。
|
||||
· 推动结束后服务端位置一变即丢弃偏移。
|
||||
· 本地 GC 伤害包仍直接走 set_anim_state("damage")。
|
||||
改动 C++ 后已重建 mtgodot 扩展;脚本改动需要重新导出 PCK 才进包。
|
||||
|
||||
下一批次:
|
||||
击退世界碰撞与 IsResistFallen;防御球跟骨骼矩阵;远端 AttackProcess;真实服务器战斗验收。
|
||||
```
|
||||
|
||||
+22
-5
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user