# HitCollision —— CLIENT-GAP §3.5 修改 2:`CActorInstance::__NormalAttackProcess` 用到的碰撞几何, # 逐行移植(单位 = Metin2 cm,Z 为高度): # IntersectLineSegments / FindNearestPointOnLineSegment / FindNearestPointOfParallelLineSegments / # AdjustNearestPoints(EterLib/lineintersect_utils.cpp,MY_EPSILON 0.1) # DetectCollisionDynamicZCylinderVSDynamicZCylinder(GameLib/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_RACE(InstanceBase.cpp) static var _msm_cache := {} # FindNearestPointOnLineSegment:|L|² < ε² 时 Nearest = A1,parameter 保持调用方原值。 # 返回 [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 # FindNearestPointOfParallelLineSegments(s[] 已被 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_RACE(InstanceBase.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