Files
mtgodot-poc/project/hit_collision.gd
T
shenleiandClaude Opus 5 1933a5ceda 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
2026-09-12 13:15:28 +09:00

161 lines
6.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HitCollision —— CLIENT-GAP §3.5 修改 2`CActorInstance::__NormalAttackProcess` 用到的碰撞几何,
# 逐行移植(单位 = Metin2 cm,Z 为高度):
# IntersectLineSegments / FindNearestPointOnLineSegment / FindNearestPointOfParallelLineSegments /
# AdjustNearestPointsEterLib/lineintersect_utils.cppMY_EPSILON 0.1
# DetectCollisionDynamicZCylinderVSDynamicZCylinderGameLib/GameUtil.cpp
# .msm AttachingData CollisionType 3 = m_DefendingPointInstanceList 的防御球
extends RefCounted
const MY_EPSILON := 0.1
const COLLISION_TYPE_DEFENDING := 3 # NRaceData::COLLISION_TYPE_DEFENDING
const HUGE_RACE_VNUM := 2493 # IS_HUGE_RACEInstanceBase.cpp
static var _msm_cache := {}
# FindNearestPointOnLineSegment|L|² < ε² 时 Nearest = A1parameter 保持调用方原值。
# 返回 [Nearest, parameter]。
static func _nearest_on_segment(a1: Vector3, l: Vector3, b: Vector3, parameter: float) -> Array:
var d := l.length_squared()
if d < MY_EPSILON * MY_EPSILON:
return [a1, parameter]
var p := clampf((b - a1).dot(l) / d, 0.0, 1.0)
return [a1 + p * l, p]
static func _out_of_range(a: float) -> bool:
return a < 0.0 or a > 1.0
# FindNearestPointOfParallelLineSegmentss[] 已被 FindNearestPointOnLineSegment 夹到 [0,1]
# 所以前两个分支在出货代码里实际走不到;照抄保留)。
static func _parallel(a1: Vector3, a2: Vector3, la: Vector3, b1: Vector3, b2: Vector3, lb: Vector3) -> Array:
var r0 := _nearest_on_segment(a1, la, b1, 0.0)
var out_a: Vector3 = r0[0]
var s0: float = r0[1]
var s1: float = _nearest_on_segment(a1, la, b2, 0.0)[1]
var out_b := Vector3.ZERO
if s0 < 0.0 and s1 < 0.0:
out_a = a1
out_b = b2 if s0 < s1 else b1
elif s0 > 1.0 and s1 > 1.0:
out_a = a2
out_b = b1 if s0 < s1 else b2
else:
var temp := 0.5 * (clampf(s0, 0.0, 1.0) + clampf(s1, 0.0, 1.0))
out_a = a1 + temp * la
out_b = _nearest_on_segment(b1, lb, out_a, temp)[0]
return [out_a, out_b]
static func _adjust(a1: Vector3, la: Vector3, b1: Vector3, lb: Vector3, s: float, t: float) -> Array:
var out_a := a1 + s * la
var out_b := b1 + t * lb
var r: Array
if _out_of_range(s) and _out_of_range(t):
s = clampf(s, 0.0, 1.0)
out_a = a1 + s * la
r = _nearest_on_segment(b1, lb, out_a, t)
out_b = r[0]
t = r[1]
if _out_of_range(t):
t = clampf(t, 0.0, 1.0)
out_b = b1 + t * lb
r = _nearest_on_segment(a1, la, out_b, s)
out_a = r[0]
s = r[1]
r = _nearest_on_segment(b1, lb, out_a, t)
out_b = r[0]
elif _out_of_range(s):
s = clampf(s, 0.0, 1.0)
out_a = a1 + s * la
out_b = _nearest_on_segment(b1, lb, out_a, t)[0]
elif _out_of_range(t):
t = clampf(t, 0.0, 1.0)
out_b = b1 + t * lb
out_a = _nearest_on_segment(a1, la, out_b, s)[0]
return [out_a, out_b]
# 返回 [OutA, OutB]:两段上的最近点对。
static func intersect_line_segments(a1: Vector3, a2: Vector3, b1: Vector3, b2: Vector3) -> Array:
var la := a2 - a1
var lb := b2 - b1
var l11 := la.length_squared()
var l22 := lb.length_squared()
var eps2 := MY_EPSILON * MY_EPSILON
if l11 < eps2:
return [a1, _nearest_on_segment(b1, lb, a1, 0.0)[0]]
elif l22 < eps2:
return [_nearest_on_segment(a1, la, b1, 0.0)[0], b1]
var ab := b1 - a1
var l12 := -la.dot(lb)
var det_l := l11 * l22 - l12 * l12
if absf(det_l) < MY_EPSILON:
return _parallel(a1, a2, la, b1, b2, lb)
var ra := la.dot(ab)
# 出货代码是 +dot(Lb, AB)(旁注写的是负号)——两段都在动时 t 会反号,1:1 保留。
var rb := lb.dot(ab)
var t := (l11 * rb - ra * l12) / det_l
var s := (ra - l12 * t) / l11
if _out_of_range(s) or _out_of_range(t):
return _adjust(a1, la, b1, lb, s, t)
return [a1 + s * la, b1 + t * lb]
# DetectCollisionDynamicZCylinderVSDynamicZCylinder:两个扫掠球都压到 z=0(高度不参与),
# 各自 AABB(按自身半径外扩)不相交直接 false,否则比较两段最近点距离与 r1+r2。
static func detect_z_cylinder(c1_last: Vector3, c1_pos: Vector3, r1: float,
c2_last: Vector3, c2_pos: Vector3, r2: float) -> bool:
c1_last.z = 0.0
c1_pos.z = 0.0
c2_last.z = 0.0
c2_pos.z = 0.0
var r := r1 + r2
var mi1 := Vector3(minf(c1_last.x, c1_pos.x), minf(c1_last.y, c1_pos.y), minf(c1_last.z, c1_pos.z)) - Vector3.ONE * r1
var mi2 := Vector3(maxf(c1_last.x, c1_pos.x), maxf(c1_last.y, c1_pos.y), maxf(c1_last.z, c1_pos.z)) + Vector3.ONE * r1
var mi3 := Vector3(minf(c2_last.x, c2_pos.x), minf(c2_last.y, c2_pos.y), minf(c2_last.z, c2_pos.z)) - Vector3.ONE * r2
var mi4 := Vector3(maxf(c2_last.x, c2_pos.x), maxf(c2_last.y, c2_pos.y), maxf(c2_last.z, c2_pos.z)) + Vector3.ONE * r2
if mi4.x < mi1.x or mi2.x < mi3.x:
return false
if mi4.y < mi1.y or mi2.y < mi3.y:
return false
if mi4.z < mi1.z or mi2.z < mi3.z:
return false
var o := intersect_line_segments(c1_last, c1_pos, c2_last, c2_pos)
return (Vector3(o[0]) - Vector3(o[1])).length_squared() <= r * r
# IS_HUGE_RACEInstanceBase.cpp):出货表只有 2493。
static func is_huge_race(race: int) -> bool:
return race == HUGE_RACE_VNUM
# 解析 .msm 的防御球:[{radius, pos: Vector3(cm, 模型本地), bone}]。bone 非空 = isAttaching
# 挂骨骼(本端按模型本地坐标近似,seam)。缺文件 -> []。按路径缓存。
static func parse_msm_defending(path: String) -> Array:
if _msm_cache.has(path):
return _msm_cache[path]
var out := []
if FileAccess.file_exists(path):
var f := FileAccess.open(path, FileAccess.READ)
var ctype := -1
var bone := ""
var attaching := false
var radius := 0.0
while f and not f.eof_reached():
var parts := f.get_line().strip_edges().replace("\t", " ").split(" ", false)
if parts.is_empty():
continue
var key := String(parts[0]).to_lower()
if key == "group" and parts.size() > 1 and String(parts[1]).to_lower().begins_with("attachingdata"):
ctype = -1
bone = ""
attaching = false
elif key == "collisiontype" and parts.size() > 1:
ctype = int(parts[1])
elif key == "isattaching" and parts.size() > 1:
attaching = int(parts[1]) != 0
elif key == "attachingbonename" and parts.size() > 1:
bone = String(parts[1]).trim_prefix("\"").trim_suffix("\"")
elif key == "radius" and parts.size() > 1:
radius = float(parts[1])
elif key == "position" and parts.size() > 3 and ctype == COLLISION_TYPE_DEFENDING:
out.append({"radius": radius,
"pos": Vector3(float(parts[1]), float(parts[2]), float(parts[3])),
"bone": bone if attaching else ""})
_msm_cache[path] = out
return out