Files
mtgodot-poc/project/ui/hit_reaction.gd
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

134 lines
4.8 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 current_step := "" # 当前 InterceptOnce/PushOnce 动作
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
current_step = String(chain[0])
return true
return false
# 一段受击动作播完:绑下一步(缺资源的步跳过);返回 false = 链结束。
func advance(bind: Callable) -> bool:
while not queue.is_empty():
var step := String(queue.pop_front())
if bool(bind.call(step)):
current_step = step
return true
active = false
current_step = ""
return false
func cancel() -> void:
active = false
queue.clear()
real_dead = false
current_step = ""
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