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:
+94
-4
@@ -21,11 +21,25 @@ const STATE_MOTIONS := {
|
||||
"damage": ["FRONT_DAMAGE", "DAMAGE", "BACK_DAMAGE"],
|
||||
"dead": ["FRONT_DEAD", "DEAD", "BACK_DEAD"],
|
||||
"emotion": ["WAIT"],
|
||||
# §3.7 受击动作步(motlist 名 -> NAME_DAMAGE_BACK / DAMAGE_FLYING(_BACK) / STAND_UP(_BACK))。
|
||||
# 不加别名:缺哪段就让 InterceptOnceMotion / PushOnceMotion 失败,与参考端一致。
|
||||
"damage_back": ["BACK_DAMAGE"],
|
||||
"knockdown": ["FRONT_KNOCKDOWN"],
|
||||
"knockdown_back": ["BACK_KNOCKDOWN"],
|
||||
"standup": ["FRONT_STANDUP"],
|
||||
"standup_back": ["BACK_STANDUP"],
|
||||
}
|
||||
const HitReaction = preload("res://ui/hit_reaction.gd")
|
||||
const HitCollision = preload("res://hit_collision.gd")
|
||||
|
||||
# CActorInstance::__SetMotion 尾:动作绑定完成。
|
||||
signal motion_bound(state: String)
|
||||
|
||||
var model: Node # Metin2Model
|
||||
var anim: Node # Metin2AnimPlayer
|
||||
var _dir := ""
|
||||
var _mesh_stem := ""
|
||||
var _hit := HitReaction.new()
|
||||
var _motions := {} # MOTION_NAME(String) -> 绝对 .msa 路径
|
||||
var _state := ""
|
||||
var _last_resolution := {}
|
||||
@@ -89,6 +103,7 @@ func build(assets_root: String, proto: Node, race: int, pump := Callable()) -> b
|
||||
break
|
||||
if _dir == "":
|
||||
return false
|
||||
_mesh_stem = mesh_stem
|
||||
var gr2 := _dir.path_join(mesh_stem + ".gr2")
|
||||
if not FileAccess.file_exists(gr2):
|
||||
gr2 = _dir.path_join(_dir.get_file() + ".gr2") # 目录同名主网格
|
||||
@@ -114,6 +129,8 @@ func build(assets_root: String, proto: Node, race: int, pump := Callable()) -> b
|
||||
anim.set("model_path", NodePath("../Metin2Model"))
|
||||
anim.set("blend_time", 0.15)
|
||||
add_child(anim)
|
||||
if anim.has_signal("playback_finished"):
|
||||
anim.playback_finished.connect(_on_playback_finished)
|
||||
set_anim_state("wait")
|
||||
CharShadow.attach(self, 1.6) # 怪脚印大一点
|
||||
return true
|
||||
@@ -125,15 +142,87 @@ func set_audio(audio_node: Node) -> void:
|
||||
func set_anim_state(s: String) -> void:
|
||||
if s == _state or anim == null:
|
||||
return
|
||||
if HitReaction.is_reaction(s):
|
||||
_hit.start([[s]], _state, _bind_reaction)
|
||||
return
|
||||
# 受击动作链播放中:循环动作请求留到链尾 PushLoopMotion 再回(seam:参考端动作队列)
|
||||
if _hit.active and s in HitReaction.LOOP_STATES:
|
||||
_hit.resume = s
|
||||
return
|
||||
_hit.cancel()
|
||||
_state = s
|
||||
_last_resolution = resolve_motion(s)
|
||||
var msa := String(_last_resolution.path)
|
||||
if msa == "":
|
||||
# 旧客户端 GetMotionKey 失败时 SetLoopMotion/InterceptMotion 直接返回:保留当前动作。
|
||||
return
|
||||
anim.set("loop", s in ["wait", "walk", "run"])
|
||||
anim.set("anim_path", msa)
|
||||
_refresh_sound_script(msa)
|
||||
_bind(s, msa, s in HitReaction.LOOP_STATES, 1.0)
|
||||
|
||||
# CGraphicThingInstance::InsertDelay(fStiffenTime)
|
||||
func insert_delay(d: float) -> void:
|
||||
_hit.insert_delay(d, anim)
|
||||
|
||||
# CActorInstance::__HitGood / __HitGreate / __HitStone(ActorInstanceBattle.cpp)
|
||||
func hit_good(scalar: float, stunned: bool) -> void:
|
||||
_hit.start(_hit.good(_state, scalar, stunned), _state, _bind_reaction)
|
||||
|
||||
func hit_greate(scalar: float, stunned: bool) -> void:
|
||||
_hit.start(_hit.greate(_state, scalar, stunned), _state, _bind_reaction, stunned)
|
||||
|
||||
func hit_stone(stunned: bool) -> void:
|
||||
_hit.stone(stunned)
|
||||
|
||||
func is_in_hit_reaction() -> bool:
|
||||
return _hit.active
|
||||
|
||||
# 受击方防御球:怪目录的 .msm(<网格代号>.msm / <目录名>.msm / 目录里第一个 .msm)。
|
||||
func get_defending_spheres() -> Array:
|
||||
if _dir == "":
|
||||
return []
|
||||
var cands: Array = []
|
||||
if _mesh_stem != "":
|
||||
cands.append(_dir.path_join(_mesh_stem + ".msm"))
|
||||
cands.append(_dir.path_join(_dir.get_file() + ".msm"))
|
||||
for p: String in cands:
|
||||
if FileAccess.file_exists(p):
|
||||
return HitCollision.parse_msm_defending(p)
|
||||
var da := DirAccess.open(_dir)
|
||||
if da:
|
||||
for fn in da.get_files():
|
||||
if fn.to_lower().ends_with(".msm"):
|
||||
return HitCollision.parse_msm_defending(_dir.path_join(fn))
|
||||
return []
|
||||
|
||||
func _on_playback_finished() -> void:
|
||||
if not _hit.active:
|
||||
return
|
||||
if _hit.advance(_bind_reaction) or _hit.real_dead:
|
||||
return
|
||||
_state = ""
|
||||
set_anim_state(_hit.resume)
|
||||
|
||||
func _bind_reaction(step: String) -> bool:
|
||||
if anim == null:
|
||||
return false
|
||||
var r := resolve_motion(step)
|
||||
_last_resolution = r
|
||||
var msa := String(r.path)
|
||||
if msa == "":
|
||||
return false
|
||||
_bind(step, msa, false, 1.0)
|
||||
return true
|
||||
|
||||
func _bind(state: String, path: String, loop: bool, speed_ratio: float) -> void:
|
||||
if not HitReaction.is_reaction(state):
|
||||
_hit.cancel()
|
||||
_state = state
|
||||
anim.set("loop", loop)
|
||||
if not loop and String(anim.get("anim_path")) == path:
|
||||
anim.set("anim_path", "") # 同一段一次性动作再触发:先清,否则原生播放器视为无变化
|
||||
anim.set("anim_path", path)
|
||||
anim.set("time_scale", _hit.scale_for(speed_ratio))
|
||||
_refresh_sound_script(path)
|
||||
motion_bound.emit(state)
|
||||
|
||||
# Read-only motion resolver used by the forest acceptance harness. Returning
|
||||
# the selected .msa before playback lets the test distinguish a real action
|
||||
@@ -169,7 +258,8 @@ func resolve_motion(state: String) -> Dictionary:
|
||||
func last_motion_resolution() -> Dictionary:
|
||||
return _last_resolution.duplicate()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
func _process(delta: float) -> void:
|
||||
_hit.tick(delta, anim, model as Node3D) # InsertDelay + ShakeProcess
|
||||
if _audio == null or anim == null or _sound_instances.is_empty() or not anim.has_method("get_time") \
|
||||
or not bool(anim.get("playing")):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user