Files
mtgodot-poc/project/ui/hit_reaction.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

128 lines
4.6 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.
# HitReaction —— CLIENT-GAP §3.7 受击方视图(PlayerView / MobView)共用的受击状态机。
# __HitGood / __HitGreate / __HitStoneActorInstanceBattle.cpp)的分支判定、
# __Shake + ShakeProcess(世界矩阵平移 ±rand()%10 cm)、CGraphicThingInstance::InsertDelay、
# InterceptOnceMotion + PushOnceMotion + PushLoopMotion 的动作链。
# 视图只提供「按状态名绑定一段一次性动作」的 Callablebind(step) -> bool,资源缺失 = false
# 等同 GetMotionKey 失败时 InterceptOnceMotion / PushOnceMotion 返回 FALSE)。
extends RefCounted
const SHAKE_TIME := 0.1 # __Shake(100)m_dwShakeTime = now + 100 ms
const SHAKE_RAND_CM := 10 # ShakeProcessrand() % 10
const LOOP_STATES := ["wait", "walk", "run"]
const DAMAGE_STATES := ["damage", "damage_back"] # IsDamage 类
const KNOCKDOWN_STATES := ["knockdown", "knockdown_back"] # IsKnockDownDAMAGE_FLYING(_BACK)
const STANDUP_STATES := ["standup", "standup_back"] # __IsStandUpMotionSTAND_UP(_BACK)
# CActorInstance::isLockNORMAL_ATTACK / COMBO_ATTACK_* / SPECIAL_* / 钓鱼 / 表情跳舞等
const LOCK_STATES := ["attack", "combo", "skill", "__motion", "emotion", "dig",
"fishing", "fishing_react", "fishing_catch", "fishing_fail"]
const SKILL_STATES := ["skill"] # IsUsingSkillSKILL 段 / SPECIAL_1..6
var delay_left := 0.0
var speed := 1.0 # 当前动作的 fSpeedRatioInsertDelay 结束后恢复)
var shake_left := 0.0
var queue: Array = [] # 剩余的 PushOnceMotion 步
var resume := "wait" # 链尾 PushLoopMotion 的循环动作
var active := false # 受击动作链正在播
var real_dead := false # m_isRealDead:晕眩击倒后不起身
var _shaking := false
var _shake_base := Vector3.ZERO
static func is_reaction(state: String) -> bool:
return state in DAMAGE_STATES or state in KNOCKDOWN_STATES or state in STANDUP_STATES
# __HitGood:返回候选动作链(按顺序尝试,首步绑定成功的那条生效)。
func good(state: String, scalar: float, stunned: bool) -> Array:
if state in KNOCKDOWN_STATES:
return []
if stunned:
return [] # IsStun() -> Die():死亡动作由服务器驱动(seam)
shake()
if state in LOCK_STATES:
return []
if scalar < 0.0:
return [["damage"]]
return [["damage_back"], ["damage"]]
# __HitGreate
func greate(state: String, scalar: float, stunned: bool) -> Array:
if state in KNOCKDOWN_STATES or state in STANDUP_STATES:
return []
shake()
if state in SKILL_STATES:
return []
if stunned:
if scalar < 0.0:
return [["knockdown"]]
return [["knockdown_back"], ["knockdown"]]
if scalar < 0.0:
return [["knockdown", "standup"]]
return [["knockdown_back", "standup_back"], ["knockdown", "standup"]]
# __HitStone
func stone(stunned: bool) -> void:
if not stunned:
shake()
func shake() -> void:
shake_left = SHAKE_TIME
func insert_delay(d: float, anim: Object) -> void:
if d <= 0.0:
return
delay_left = d
if anim:
anim.set("time_scale", 0.0)
# 绑定新动作时的 time_scaleInsertDelay 期间保持冻结。
func scale_for(ratio: float) -> float:
speed = ratio
return 0.0 if delay_left > 0.0 else ratio
# 首步绑定成功 -> 记下链尾要回的循环动作,剩余步入队。
func start(chains: Array, current: String, bind: Callable, dead_after := false) -> bool:
for chain: Array in chains:
if chain.is_empty() or not bool(bind.call(String(chain[0]))):
continue
if not is_reaction(current):
resume = current if current in LOOP_STATES else "wait"
queue = chain.slice(1)
active = true
real_dead = dead_after
return true
return false
# 一段受击动作播完:绑下一步(缺资源的步跳过);返回 false = 链结束。
func advance(bind: Callable) -> bool:
while not queue.is_empty():
if bool(bind.call(String(queue.pop_front()))):
return true
active = false
return false
func cancel() -> void:
active = false
queue.clear()
real_dead = false
func tick(dt: float, anim: Object, model: Node3D) -> void:
if delay_left > 0.0:
delay_left -= dt
if delay_left <= 0.0 and anim:
anim.set("time_scale", speed)
if shake_left > 0.0:
shake_left -= dt
if model == null:
return
if shake_left > 0.0:
if not _shaking:
_shaking = true
_shake_base = model.position
model.position = _shake_base + Vector3(_rand_cm(), _rand_cm(), _rand_cm()) / 100.0
elif _shaking:
_shaking = false
model.position = _shake_base
static func _rand_cm() -> float:
var v := float(randi() % SHAKE_RAND_CM)
return v if randi() % 2 == 0 else -v