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:
@@ -0,0 +1,160 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user