Files
mtgodot-poc/project/ui/system_menu_ui.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

258 lines
8.2 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.
# SystemMenuUI (P11) —— ESC 系统菜单(1:1 迁移 `assets/root/uisystem.py` `SystemDialog`
# 的 `__LoadSystemMenu_Default`;布局走真 `assets/uiscript/uiscript/systemdialog.py`)。
#
# var sm := preload("res://ui/system_menu_ui.gd").new()
# add_child(sm)
# sm.setup(ui_manager, m2client, assets_root, system_option_ui, game_option_ui)
# sm.toggle() # ESC(无其它窗打开时)
#
# 按钮绑定逐字对照 uisystem.py
# help_button → interface.OpenHelpWindowhelp_ui.gd,§8.3
# mall_button → net.SendChatPacket("/in_game_mall")
# system_option_button → uiSystemOption.OptionDialogsystem_option_ui
# game_option_button → uiGameOption.OptionDialoggame_option_ui
# change_button → net.ExitGame() = SendChatPacket("/phase_select")
# logout_button → net.LogOutGame() = SendChatPacket("/logout")
# exit_button → net.ExitApplication() = 退出进程
# cancel_button / 标题栏 → 关闭
extends Node
const HelpUI = preload("res://ui/help_ui.gd")
const LABELS := {
"help_button": "帮助", "mall_button": "商城", "system_option_button": "系统设置",
"game_option_button": "游戏设置", "change_button": "选择角色", "logout_button": "登出",
"exit_button": "退出游戏", "cancel_button": "取消",
}
var ui: CanvasLayer
var client: Node
var assets_root := ""
var uiscript_dir := ""
var system_option_ui: Node
var game_option_ui: Node
var help_ui: Node # HelpUI(§8.3)—— 未注入时 _ensure_help() 懒建
var toast: Callable = Callable() # 可选:func(msg: String) 显示提示
var _win: Dictionary = {}
var _mobile_mode := false
var _mobile_root: Control
var _mobile_buttons: Dictionary = {}
const MOBILE_SIZE := Vector2(700, 330)
func setup(ui_manager: CanvasLayer, m2client: Node, assets := "",
sys_opt: Node = null, game_opt: Node = null, help_win: Node = null) -> void:
ui = ui_manager
client = m2client
system_option_ui = sys_opt
game_option_ui = game_opt
help_ui = help_win
assets_root = assets
if assets_root == "" and ui and "assets_root" in ui:
assets_root = ui.assets_root
uiscript_dir = assets_root.path_join("uiscript/uiscript")
if not DirAccess.dir_exists_absolute(uiscript_dir):
uiscript_dir = assets_root.path_join("uiscript")
if ui and ui.has_signal("window_closed"):
ui.window_closed.connect(func(w):
if not _win.is_empty() and _win.get("root") == w:
_win = {}
)
func is_open() -> bool:
if _mobile_mode:
return _mobile_root != null and is_instance_valid(_mobile_root)
return not _win.is_empty() and is_instance_valid(_win.get("root"))
func set_mobile_mode(enabled: bool) -> void:
if enabled != _mobile_mode and is_open():
close()
_mobile_mode = enabled
if is_instance_valid(help_ui) and help_ui.has_method("set_mobile_mode"):
help_ui.set_mobile_mode(enabled)
func toggle() -> void:
if is_open(): close()
else: open()
func close() -> void:
if _mobile_mode:
var root := _mobile_root
_mobile_root = null
_mobile_buttons.clear()
if root and is_instance_valid(root) and ui:
ui.close(root)
return
var r: Control = _win.get("root", null)
_win = {}
if r and is_instance_valid(r) and ui:
ui.close(r)
func open() -> void:
if _mobile_mode:
_open_mobile()
return
if is_open():
return
var path := uiscript_dir.path_join("systemdialog.py")
if not FileAccess.file_exists(path):
push_warning("SystemMenuUI: no systemdialog.py at " + path)
return
_win = ui.open_script(path, assets_root, true) # modal
if not is_open():
return
_relabel()
_wire()
func _open_mobile() -> void:
if is_open():
return
_mobile_root = Control.new()
_mobile_root.name = "MobileSystemMenuWindow"
_mobile_root.size = MOBILE_SIZE
_mobile_root.set_meta("mobile_title", "系统菜单")
_mobile_root.mouse_filter = Control.MOUSE_FILTER_STOP
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
panel.add_theme_stylebox_override("panel", _mobile_panel_style())
_mobile_root.add_child(panel)
var hint := Label.new()
hint.text = "账户、设置和帮助"
hint.position = Vector2(22, 52)
hint.size = Vector2(640, 24)
hint.add_theme_font_size_override("font_size", 13)
hint.add_theme_color_override("font_color", Color(0.68, 0.78, 0.92))
_mobile_root.add_child(hint)
var grid := GridContainer.new()
grid.name = "MobileSystemActions"
grid.columns = 2
grid.position = Vector2(22, 82)
grid.size = Vector2(656, 174)
grid.add_theme_constant_override("h_separation", 12)
grid.add_theme_constant_override("v_separation", 8)
_mobile_root.add_child(grid)
for action in ["帮助", "商城", "系统设置", "游戏设置", "选择角色", "登出"]:
var button := Button.new()
button.name = "MobileAction_%s" % action
button.text = String(action)
button.custom_minimum_size = Vector2(322, 48)
button.add_theme_font_size_override("font_size", 14)
var selected := String(action)
button.pressed.connect(func(): _mobile_action(selected))
_mobile_buttons[selected] = button
grid.add_child(button)
var exit := Button.new()
exit.name = "MobileExitButton"
exit.text = "退出游戏"
exit.position = Vector2(22, 272)
exit.size = Vector2(154, 42)
exit.add_theme_font_size_override("font_size", 13)
exit.pressed.connect(func(): _mobile_action("退出游戏"))
_mobile_buttons["退出游戏"] = exit
_mobile_root.add_child(exit)
var cancel := Button.new()
cancel.name = "MobileCancelButton"
cancel.text = "取消"
cancel.position = Vector2(524, 272)
cancel.size = Vector2(154, 42)
cancel.add_theme_font_size_override("font_size", 13)
cancel.pressed.connect(close)
_mobile_buttons["取消"] = cancel
_mobile_root.add_child(cancel)
ui.open(_mobile_root, true)
func _mobile_action(action: String) -> void:
match action:
"帮助":
close()
open_help()
"商城":
_send_and_close("/in_game_mall")
"系统设置":
close()
if system_option_ui and system_option_ui.has_method("open"):
system_option_ui.open()
"游戏设置":
close()
if game_option_ui and game_option_ui.has_method("open"):
game_option_ui.open()
"选择角色": _send_and_close("/phase_select")
"登出": _send_and_close("/logout")
"退出游戏": get_tree().quit()
func _send_and_close(command: String) -> void:
if client and client.has_method("say"):
client.say(0, command)
close()
func _mobile_panel_style() -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = Color(0.04, 0.065, 0.1, 0.98)
style.border_color = Color(0.52, 0.68, 0.9, 0.86)
style.set_border_width_all(1)
style.set_corner_radius_all(10)
style.shadow_color = Color(0, 0, 0, 0.45)
style.shadow_size = 12
return style
func _node(nm: String) -> Control:
if _win.is_empty():
return null
var n = _win.get("nodes", {}).get(nm, null)
return n if n is Control else null
func _relabel() -> void:
for nm in LABELS:
var n := _node(nm)
if n and n.has_method("set_text"):
n.set_text(LABELS[nm])
func _btn(nm: String, cb: Callable) -> void:
var b := _node(nm)
if b is BaseButton:
b.pressed.connect(cb)
# uisystem.py: self.interface.OpenHelpWindow()。未注入 help_ui 时懒建一个,
# 复用本菜单的 ui / client / assets_root。
func _ensure_help() -> Node:
if not is_instance_valid(help_ui):
help_ui = HelpUI.new()
add_child(help_ui)
help_ui.setup(ui, client, assets_root)
if help_ui.has_method("set_mobile_mode"):
help_ui.set_mobile_mode(_mobile_mode)
return help_ui
func open_help() -> void:
_ensure_help().open()
func toggle_help() -> void:
_ensure_help().toggle()
func _wire() -> void:
_btn("help_button", func():
close()
open_help())
_btn("mall_button", func():
_send_and_close("/in_game_mall"))
_btn("system_option_button", func():
close()
if system_option_ui and system_option_ui.has_method("open"):
system_option_ui.open())
_btn("game_option_button", func():
close()
if game_option_ui and game_option_ui.has_method("open"):
game_option_ui.open())
_btn("change_button", func():
_send_and_close("/phase_select"))
_btn("logout_button", func():
_send_and_close("/logout"))
_btn("exit_button", func(): get_tree().quit())
_btn("cancel_button", close)
var board := _node("board")
if board:
for x in board.find_children("*", "BaseButton", true, false):
if x.get_meta("is_titlebar", false):
x.pressed.connect(close)