完善客户端功能并加入40250一致性审计
This commit is contained in:
+37
-18
@@ -319,6 +319,18 @@ func setup(m2client: Node, player_ctl: Node, nw: Node, hud_node: Node = null, au
|
||||
_observer_mode = bool(client.is_observer_mode())
|
||||
if pc and "skip_actor_collision" in pc:
|
||||
pc.skip_actor_collision = _observer_mode
|
||||
# GameScene 的资源加载会让首个 entity_main_set 早于 NetPlay.setup 到达;补读当前
|
||||
# 主角,和 NetWorld.catch_up 一样避免丢失进场状态(动作模式、本地主角 VID、目标)。
|
||||
if client.has_method("get_main_vid"):
|
||||
var current_main := int(client.get_main_vid())
|
||||
if current_main > 0:
|
||||
_on_main_set(current_main)
|
||||
# setup 通常发生在网络已完成首轮 POINTS 同步之后。先建立经验/等级基线,
|
||||
# 否则进入游戏后的第一次击杀只会把 _last_exp 从 -1 初始化,表现为没有经验反馈。
|
||||
if client.has_method("get_points"):
|
||||
var initial_points: Dictionary = client.get_points()
|
||||
if not initial_points.is_empty():
|
||||
_on_points(initial_points)
|
||||
|
||||
func _on_observer_mode(enabled: bool) -> void:
|
||||
_observer_mode = enabled
|
||||
@@ -610,11 +622,23 @@ func _combo_vec() -> PackedInt32Array:
|
||||
return cls.get(key, PackedInt32Array())
|
||||
|
||||
func _motion_data() -> Dictionary:
|
||||
var pv_anim: Object = player_view.get("anim") if player_view else null
|
||||
var view := _combat_view()
|
||||
var pv_anim: Object = view.get("anim") if view and "anim" in view else null
|
||||
if pv_anim and pv_anim.has_method("get_motion_data"):
|
||||
return pv_anim.get_motion_data()
|
||||
return {}
|
||||
|
||||
func _combat_view() -> Node:
|
||||
if is_instance_valid(player_view):
|
||||
return player_view
|
||||
# GameScene 会在占位角色替换成 PlayerView 后注入 player_view;替换同帧发生攻击时,
|
||||
# pc.player 已经是权威本地模型,因此用它兜底,避免只发包却没有攻击动作。
|
||||
if pc and "player" in pc and is_instance_valid(pc.player):
|
||||
var candidate: Node = pc.player
|
||||
if candidate.has_method("play_attack_motion") or candidate.has_method("set_anim_state"):
|
||||
return candidate
|
||||
return null
|
||||
|
||||
# --- helpers --------------------------------------------------------------
|
||||
|
||||
func _vid_of(node: Object) -> int:
|
||||
@@ -908,20 +932,11 @@ func _on_points(p: Dictionary) -> void:
|
||||
func _trigger_exp_gain(delta_exp: int, _p: Dictionary) -> void:
|
||||
print("[经验] 击杀怪物获得 %d 点经验值。" % delta_exp)
|
||||
|
||||
# 1. 40250 官方经验聚能光球飞行与吸收
|
||||
if is_instance_valid(net_world) and net_world.has_method("spawn_exp_fly"):
|
||||
var spawn_pos := _last_dead_mob_pos
|
||||
var player_node: Node3D = pc.player if (pc and is_instance_valid(pc.player)) else null
|
||||
if spawn_pos == Vector3.INF and is_instance_valid(player_node):
|
||||
# 若无死亡怪物坐标记录,则在玩家正前方 2.5 米处生成光球回吸
|
||||
var p_pos := player_node.global_position if player_node.is_inside_tree() else player_node.position
|
||||
var forward := -player_node.global_transform.basis.z if player_node.is_inside_tree() else Vector3(0, 0, -1)
|
||||
spawn_pos = p_pos + forward * 2.5
|
||||
if spawn_pos != Vector3.INF:
|
||||
net_world.spawn_exp_fly(spawn_pos, player_node, 3)
|
||||
_last_dead_mob_pos = Vector3.INF
|
||||
# 40250 的经验球由服务端 `from->CreateFly(FLY_EXP, to)` 下发 GC_CREATE_FLY,
|
||||
# NetWorld._on_fly 负责唯一一次播放。这里不能再合成一组,否则正常收包时会出现双份。
|
||||
_last_dead_mob_pos = Vector3.INF
|
||||
|
||||
# 2. 40250 吸收音效与任务栏反馈
|
||||
# 经验数值反馈音效;飞行和吸收闪光由权威 GC_CREATE_FLY 驱动。
|
||||
if audio and audio.has_method("play_ui"):
|
||||
audio.play_ui("money.wav")
|
||||
|
||||
@@ -2004,10 +2019,14 @@ func _emit_swing(motion_index: int, is_combo: bool) -> void:
|
||||
_send_state(FUNC_COMBO, motion_index, pc.player.position)
|
||||
# __SetMotion(SSetMotionData{ dwMotKey(mode, index), fSpeedRatio = m_fAtkSpd }):先绑定该段
|
||||
# 动作,命中窗 / 连击时间才读得到这一段的 .msa。
|
||||
if player_view and player_view.has_method("play_attack_motion"):
|
||||
player_view.play_attack_motion(combo_motion_mode, motion_index, _atk_speed_factor)
|
||||
elif player_view and player_view.has_method("set_anim_state"):
|
||||
player_view.set_anim_state("combo" if is_combo else "attack")
|
||||
var view := _combat_view()
|
||||
var motion_played := false
|
||||
if view and view.has_method("play_attack_motion"):
|
||||
# 旧视图/测试桩的方法没有返回值;只有新版 PlayerView 明确返回 true 时才认为
|
||||
# 已成功绑定 .msa,其余情况继续走 set_anim_state 兼容兜底。
|
||||
motion_played = view.play_attack_motion(combo_motion_mode, motion_index, _atk_speed_factor) == true
|
||||
if not motion_played and view and view.has_method("set_anim_state"):
|
||||
view.set_anim_state("combo" if is_combo else "attack")
|
||||
_begin_hit_windows()
|
||||
if _hit_windows.is_empty():
|
||||
# CG_ATTACK 的 bType 是技能号,普攻恒 0,不是连击段号
|
||||
|
||||
Reference in New Issue
Block a user