- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
134 lines
4.8 KiB
GDScript
134 lines
4.8 KiB
GDScript
# HitReaction —— CLIENT-GAP §3.7 受击方视图(PlayerView / MobView)共用的受击状态机。
|
||
# __HitGood / __HitGreate / __HitStone(ActorInstanceBattle.cpp)的分支判定、
|
||
# __Shake + ShakeProcess(世界矩阵平移 ±rand()%10 cm)、CGraphicThingInstance::InsertDelay、
|
||
# InterceptOnceMotion + PushOnceMotion + PushLoopMotion 的动作链。
|
||
# 视图只提供「按状态名绑定一段一次性动作」的 Callable(bind(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 # ShakeProcess:rand() % 10
|
||
const LOOP_STATES := ["wait", "walk", "run"]
|
||
const DAMAGE_STATES := ["damage", "damage_back"] # IsDamage 类
|
||
const KNOCKDOWN_STATES := ["knockdown", "knockdown_back"] # IsKnockDown:DAMAGE_FLYING(_BACK)
|
||
const STANDUP_STATES := ["standup", "standup_back"] # __IsStandUpMotion:STAND_UP(_BACK)
|
||
# CActorInstance::isLock:NORMAL_ATTACK / COMBO_ATTACK_* / SPECIAL_* / 钓鱼 / 表情跳舞等
|
||
const LOCK_STATES := ["attack", "combo", "skill", "__motion", "emotion", "dig",
|
||
"fishing", "fishing_react", "fishing_catch", "fishing_fail"]
|
||
const SKILL_STATES := ["skill"] # IsUsingSkill:SKILL 段 / SPECIAL_1..6
|
||
|
||
var delay_left := 0.0
|
||
var speed := 1.0 # 当前动作的 fSpeedRatio(InsertDelay 结束后恢复)
|
||
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_scale:InsertDelay 期间保持冻结。
|
||
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
|