- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
140 lines
4.5 KiB
GDScript
140 lines
4.5 KiB
GDScript
# death_ui.gd —— 40250 死亡结算与复活面板(RestartDialog)1:1 实现
|
||
# 严格对照:
|
||
# root/uirestart.py (RestartDialog)
|
||
# uiscript/restartdialog.py (200x88 ThinBoard)
|
||
# metin2/src/server/game/src/cmd_general.cpp:478 (10s restart_here cooldown)
|
||
extends Node
|
||
|
||
const RESTART_HERE_COOLDOWN := 10.0 # 40250 官方 10 秒原地复活保护冷却
|
||
const BOARD_WIDTH := 200.0
|
||
const BOARD_HEIGHT := 88.0
|
||
|
||
var client: Node
|
||
var _root: Control
|
||
var _board: Panel
|
||
var _btn_restart_here: Button
|
||
var _btn_restart_town: Button
|
||
var _shown := false
|
||
var _here_cooldown_remaining := 0.0
|
||
|
||
func setup(m2client: Node, parent: Node) -> void:
|
||
client = m2client
|
||
_build(parent)
|
||
if client.has_signal("entity_dead"):
|
||
client.entity_dead.connect(_on_dead)
|
||
if client.has_signal("phase_changed"):
|
||
client.phase_changed.connect(func(p): if p == "dead": show_dialog())
|
||
if client.has_signal("vitals_changed"):
|
||
client.vitals_changed.connect(_on_vitals)
|
||
|
||
func _process(delta: float) -> void:
|
||
if not _shown:
|
||
return
|
||
if _here_cooldown_remaining > 0.0:
|
||
_here_cooldown_remaining -= delta
|
||
if _here_cooldown_remaining <= 0.0:
|
||
_here_cooldown_remaining = 0.0
|
||
_btn_restart_here.disabled = false
|
||
_btn_restart_here.text = "原地复活"
|
||
else:
|
||
_btn_restart_here.disabled = true
|
||
_btn_restart_here.text = "原地复活 (%d 秒)" % int(ceilf(_here_cooldown_remaining))
|
||
|
||
func _on_dead(vid: int) -> void:
|
||
if client.has_method("get_main_vid") and vid == client.get_main_vid():
|
||
show_dialog()
|
||
|
||
func _on_vitals(vid: int) -> void:
|
||
if not _shown:
|
||
return
|
||
if client.has_method("get_main_vid") and vid == client.get_main_vid():
|
||
var e: Dictionary = client.get_entity(vid)
|
||
if int(e.get("hp", 0)) > 0 and not e.get("dead", false):
|
||
hide_dialog()
|
||
|
||
func show_dialog() -> void:
|
||
if _shown:
|
||
return
|
||
_shown = true
|
||
_here_cooldown_remaining = RESTART_HERE_COOLDOWN
|
||
_btn_restart_here.disabled = true
|
||
_btn_restart_here.text = "原地复活 (10 秒)"
|
||
_root.visible = true
|
||
|
||
func hide_dialog() -> void:
|
||
_shown = false
|
||
_here_cooldown_remaining = 0.0
|
||
_root.visible = false
|
||
|
||
func is_open() -> bool:
|
||
return _shown and _root != null and _root.visible
|
||
|
||
func _build(parent: Node) -> void:
|
||
_root = Control.new()
|
||
_root.name = "RestartDialog"
|
||
_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
_root.visible = false
|
||
_root.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
parent.add_child(_root)
|
||
|
||
# 全屏暗红暗角
|
||
var dim := ColorRect.new()
|
||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
dim.color = Color(0.12, 0.0, 0.0, 0.6)
|
||
_root.add_child(dim)
|
||
|
||
# 40250 uiscript/restartdialog.py 1:1 尺寸 200x88 ThinBoard
|
||
_board = Panel.new()
|
||
_board.name = "Board"
|
||
_board.set_anchors_preset(Control.PRESET_CENTER)
|
||
_board.position = Vector2(-BOARD_WIDTH / 2.0, -BOARD_HEIGHT / 2.0)
|
||
_board.size = Vector2(BOARD_WIDTH, BOARD_HEIGHT)
|
||
var sb := StyleBoxFlat.new()
|
||
sb.bg_color = Color(0.3333, 0.2941, 0.2588, 0.95)
|
||
sb.border_color = Color(0.45, 0.38, 0.22, 0.95)
|
||
sb.set_border_width_all(1)
|
||
sb.set_corner_radius_all(2)
|
||
_board.add_theme_stylebox_override("panel", sb)
|
||
_root.add_child(_board)
|
||
|
||
# 40250 restart_here_button (10, 17), 180x26
|
||
_btn_restart_here = Button.new()
|
||
_btn_restart_here.name = "RestartHereButton"
|
||
_btn_restart_here.position = Vector2(10, 17)
|
||
_btn_restart_here.size = Vector2(180, 26)
|
||
_btn_restart_here.text = "原地复活 (10 秒)"
|
||
_btn_restart_here.disabled = true
|
||
_btn_restart_here.add_theme_font_size_override("font_size", 11)
|
||
_btn_restart_here.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
_btn_restart_here.pressed.connect(_on_restart_here_pressed)
|
||
_board.add_child(_btn_restart_here)
|
||
|
||
# 40250 restart_town_button (10, 47), 180x26
|
||
_btn_restart_town = Button.new()
|
||
_btn_restart_town.name = "RestartTownButton"
|
||
_btn_restart_town.position = Vector2(10, 47)
|
||
_btn_restart_town.size = Vector2(180, 26)
|
||
_btn_restart_town.text = "在村庄复活"
|
||
_btn_restart_town.add_theme_font_size_override("font_size", 11)
|
||
_btn_restart_town.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
_btn_restart_town.pressed.connect(_on_restart_town_pressed)
|
||
_board.add_child(_btn_restart_town)
|
||
|
||
# 40250 uirestart.py: OnPressEscapeKey / OnPressExitKey returns True (禁止 ESC 关闭)
|
||
_root.gui_input.connect(func(event: InputEvent):
|
||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||
_root.accept_event()
|
||
)
|
||
|
||
func _on_restart_here_pressed() -> void:
|
||
if _here_cooldown_remaining > 0.0:
|
||
return
|
||
_restart("/restart_here")
|
||
|
||
func _on_restart_town_pressed() -> void:
|
||
_restart("/restart_town")
|
||
|
||
func _restart(cmd: String) -> void:
|
||
if client and client.has_method("say"):
|
||
client.say(0, cmd)
|