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:
shenlei
2026-09-12 13:15:28 +09:00
co-authored by Claude Opus 5
parent 96c29d2bb7
commit 1933a5ceda
30 changed files with 2353 additions and 209 deletions
+78
View File
@@ -0,0 +1,78 @@
# hit_collision_test —— §3.5__NormalAttackProcess 的碰撞几何 headless 自检。
# IntersectLineSegmentsEterLib/lineintersect_utils.cpp
# DetectCollisionDynamicZCylinderVSDynamicZCylinderGameLib/GameUtil.cpp
# .msm AttachingData CollisionType 3 = m_DefendingPointInstanceList
# godot --headless --path project --script hit_collision_test.gd
extends SceneTree
const HitCollision = preload("res://hit_collision.gd")
var _fail := 0
func _ck(c: bool, m: String) -> void:
if not c:
_fail += 1
printerr("FAIL: " + m)
func _init() -> void:
_run()
if _fail == 0:
print("PASS: hit_collision_test (segments / z-cylinder / msm defending)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _near(a: Vector3, b: Vector3) -> bool:
return a.distance_to(b) < 0.01
func _run() -> void:
# --- IntersectLineSegments ---
# 静止受击方(L22 < ε²):OutB = B1OutA = A 上离 B1 最近点。
var r: Array = HitCollision.intersect_line_segments(
Vector3(-100, 0, 0), Vector3(100, 0, 0), Vector3(0, 50, 0), Vector3(0, 50, 0))
_ck(_near(r[0], Vector3.ZERO) and _near(r[1], Vector3(0, 50, 0)), "stationary B -> nearest on A = (0,0,0)")
# 退化 AL11 < ε²)
r = HitCollision.intersect_line_segments(
Vector3(10, 10, 0), Vector3(10, 10, 0), Vector3(0, 0, 0), Vector3(100, 0, 0))
_ck(_near(r[0], Vector3(10, 10, 0)) and _near(r[1], Vector3(10, 0, 0)), "degenerate A -> OutB (10,0,0)")
# 平行(|DetL| < ε):两段 clamp 参数取中点
r = HitCollision.intersect_line_segments(
Vector3(0, 0, 0), Vector3(100, 0, 0), Vector3(0, 50, 0), Vector3(100, 50, 0))
_ck(_near(r[0], Vector3(50, 0, 0)) and _near(r[1], Vector3(50, 50, 0)), "parallel -> midpoints (50,0)/(50,50)")
# 一般情形,s/t 双越界 -> AdjustNearestPoints
r = HitCollision.intersect_line_segments(
Vector3(0, 0, 0), Vector3(100, 0, 0), Vector3(200, -50, 0), Vector3(200, 50, 0))
_ck(_near(r[0], Vector3(100, 0, 0)) and _near(r[1], Vector3(200, 0, 0)), "both out of range -> (100,0)/(200,0)")
# 参考端 rb = +dot(Lb, AB)(注释里是负号):两条运动段十字相交时 t 被算成 -0.5,
# 经 AdjustNearestPoints 夹到 B1。1:1 保留这个出货行为。
r = HitCollision.intersect_line_segments(
Vector3(-100, 0, 0), Vector3(100, 0, 0), Vector3(0, -100, 0), Vector3(0, 100, 0))
_ck(_near(r[0], Vector3.ZERO) and _near(r[1], Vector3(0, -100, 0)),
"shipped rb sign: crossing moving segments -> OutB clamps to B1, got %s / %s" % [r[0], r[1]])
# --- DetectCollisionDynamicZCylinderVSDynamicZCylinder ---
# 攻击球扫过静止受击球:只看扫掠段,不看端点;z(高度)清零。
_ck(HitCollision.detect_z_cylinder(Vector3(-200, 0, 500), Vector3(200, 0, 500), 20.0,
Vector3(0, 60, 0), Vector3(0, 60, 0), 70.0), "sweep passes 60cm from victim, r 20+70 -> hit (height ignored)")
_ck(not HitCollision.detect_z_cylinder(Vector3(-200, 0, 0), Vector3(200, 0, 0), 20.0,
Vector3(0, 100, 0), Vector3(0, 100, 0), 70.0), "sweep passes 100cm away -> no hit")
_ck(not HitCollision.detect_z_cylinder(Vector3(-200, 0, 0), Vector3(200, 0, 0), 20.0,
Vector3(400, 0, 0), Vector3(400, 0, 0), 70.0), "AABB reject -> no hit")
_ck(HitCollision.detect_z_cylinder(Vector3(0, 0, 0), Vector3(0, 0, 0), 20.0,
Vector3(80, 0, 900), Vector3(80, 0, 900), 70.0), "static spheres 80cm apart, r sum 90 -> hit")
# --- .msm 防御球(CollisionType 3),CollisionType 1 = body 不算 ---
var assets := AssetRoot.path()
var wolf := assets.path_join("Monster/ymir work/monster/wolf/wolf_blue.msm")
var ds: Array = HitCollision.parse_msm_defending(wolf)
_ck(ds.size() == 1, "wolf_blue.msm -> 1 defending sphere, got %d" % ds.size())
if ds.size() == 1:
_ck(is_equal_approx(float(ds[0].radius), 90.0) and _near(ds[0].pos, Vector3(0, -15, 80)),
"wolf defending sphere r90 (0,-15,80), got %s" % [ds[0]])
var war := assets.path_join("root/msm/warrior_m.msm")
ds = HitCollision.parse_msm_defending(war)
_ck(ds.size() >= 1 and is_equal_approx(float(ds[0].radius), 70.0) and _near(ds[0].pos, Vector3(0, 0, 100)),
"warrior_m.msm defending sphere r70 (0,0,100), got %s" % [ds])
_ck(HitCollision.parse_msm_defending(assets.path_join("no/such.msm")).is_empty(), "missing msm -> []")
# IS_HUGE_RACEInstanceBase.cpp
_ck(HitCollision.is_huge_race(2493) and not HitCollision.is_huge_race(101), "IS_HUGE_RACE: only 2493")