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:
@@ -27,6 +27,7 @@ const PickShow = preload("res://pick_show.gd")
|
||||
const NameTailLayout = preload("res://name_tail_layout.gd")
|
||||
const TextMetrics = preload("res://text_metrics.gd")
|
||||
const ScreenHpBar = preload("res://screen_hp_bar.gd")
|
||||
const PhysicsPush = preload("res://physics_push.gd")
|
||||
|
||||
signal entity_added(node: Node3D, vid: int)
|
||||
signal entity_removed(vid: int)
|
||||
@@ -93,6 +94,8 @@ var follow_lerp := 14.0
|
||||
var snap_dist := 6.0
|
||||
|
||||
var _by_vid := {} # vid:int -> Node3D
|
||||
# §3.7 受击击退:vid -> {obj: PhysicsPush(CPhysicsObject), off: Vector2 累计位移 cm, base: 推开时的 e.pos}
|
||||
var _push := {}
|
||||
var _main_vid := 0
|
||||
var _local_vid := 0 # 由 net_play 设:这个 vid 由本地 player 代表,不生成节点
|
||||
var _local_node: Node3D # 本地玩家的真模型(不挂在 _mount 下)
|
||||
@@ -836,6 +839,7 @@ func _on_despawn(vid: int) -> void:
|
||||
_duel_opponents.erase(vid)
|
||||
_fading.erase(vid)
|
||||
_chat_tails.erase(vid)
|
||||
_push.erase(vid)
|
||||
_remove_hp_bar(vid)
|
||||
var n: Node3D = _by_vid.get(vid, null)
|
||||
if n:
|
||||
@@ -1279,6 +1283,8 @@ func _process(dt: float) -> void:
|
||||
# SNetworkActorData::UpdatePosition 式线性插值),这里只跟随 e.pos,不再
|
||||
# 叠第二层 follow_lerp——否则会把 C++ 的到达时机拖慢、和动作事件脱节。
|
||||
var want := _grounded(e.get("pos", n.position))
|
||||
if _push.has(vid):
|
||||
want = _pushed_position(vid, e, dt, want)
|
||||
n.position = want
|
||||
# 朝向:Metin2 heading(度) -> Godot yaw。经验换算,真机再校准。
|
||||
var yaw := MapCoord.heading_to_yaw(float(e.get("angle_deg", 0.0)))
|
||||
@@ -1318,10 +1324,61 @@ func _apply_anim(n: Node3D, f: int, moving: bool, walk_mode := 1, dead := false)
|
||||
func _grounded(p: Variant) -> Vector3:
|
||||
# p 是 M2Client 的网络帧 pos —— 先转到 Metin2World 本地帧,再贴地。
|
||||
var v: Vector3 = MapCoord.to_world(p) if p is Vector3 else Vector3.ZERO
|
||||
return _ground_world(v)
|
||||
|
||||
func _ground_world(v: Vector3) -> Vector3:
|
||||
if world and world.has_method("sample_height"):
|
||||
v.y = float(world.call("sample_height", v.x, v.z))
|
||||
return v
|
||||
|
||||
# §3.7 __PushCircle + IncreaseExternalForce(ActorInstanceBattle.cpp / PhysicsObject.cpp):
|
||||
# dir = normalize(受击方 - 攻击方),Metin2 actor 平面帧(cm)。seam:无 IPhysicsWorld 地形碰撞回调。
|
||||
func push_victim(vid: int, dir: Vector2, force: float) -> void:
|
||||
if client == null or force <= 0.0 or not _by_vid.has(vid):
|
||||
return
|
||||
var e: Dictionary = client.get_entity(vid)
|
||||
if e.is_empty():
|
||||
return
|
||||
var pe: Dictionary = _push.get(vid, {})
|
||||
if pe.is_empty():
|
||||
pe = {"obj": PhysicsPush.new(), "off": Vector2.ZERO, "base": e.get("pos")}
|
||||
_push[vid] = pe
|
||||
pe.obj.set_direction(dir)
|
||||
pe.obj.increase_external_force(force)
|
||||
|
||||
# CActorInstance::IsPushing -> m_PhysicsObject.isBlending()
|
||||
func is_pushing(vid: int) -> bool:
|
||||
return _push.has(vid) and bool(_push[vid].obj.is_blending())
|
||||
|
||||
# CActorInstance::GetBlendingPosition:blending 中 = 当前位置 + LastPosition(击退终点),否则当前位置。
|
||||
func blending_position(vid: int) -> Vector3:
|
||||
var n: Node3D = _by_vid.get(vid, null)
|
||||
if n == null:
|
||||
return Vector3.ZERO
|
||||
if not is_pushing(vid):
|
||||
return n.global_position
|
||||
var last: Vector2 = _push[vid].obj.get_last_position()
|
||||
return n.global_position + Vector3(last.x / 100.0, 0.0, -last.y / 100.0)
|
||||
|
||||
# CActorInstance::PhysicsProcess:每帧 m_PhysicsObject.Update + AddMovement(GetX/YMovement)。
|
||||
# 位移叠在服务器位置上;blend 结束后服务器位置一变,以服务器为准丢弃本地偏移(seam)。
|
||||
func _pushed_position(vid: int, e: Dictionary, dt: float, want: Vector3) -> Vector3:
|
||||
var pe: Dictionary = _push[vid]
|
||||
var obj = pe.obj
|
||||
var pos: Variant = e.get("pos")
|
||||
if not (pos is Vector3):
|
||||
return want
|
||||
if pos != pe.base:
|
||||
if not obj.is_blending():
|
||||
_push.erase(vid)
|
||||
return want
|
||||
pe.base = pos
|
||||
if obj.is_blending():
|
||||
obj.update(dt)
|
||||
pe.off += obj.get_movement()
|
||||
var off: Vector2 = pe.off
|
||||
return _ground_world(MapCoord.to_world(pos) + Vector3(off.x / 100.0, 0.0, -off.y / 100.0))
|
||||
|
||||
func _make_placeholder(d: Dictionary) -> Node3D:
|
||||
var root := Node3D.new()
|
||||
var mesh := MeshInstance3D.new()
|
||||
|
||||
Reference in New Issue
Block a user