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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
shen
2026-09-19 08:51:25 -07:00
parent 1db9e9a129
commit 66d217b313
650 changed files with 53046 additions and 1586 deletions
+56 -12
View File
@@ -33,9 +33,16 @@ var _last_mobile_area := Rect2(-1, -1, 0, 0)
var _drag_win: Control = null
var _drag_from := Vector2.ZERO
func _ready() -> void:
func _init() -> void:
layer = 10
_ensure_root()
func _ensure_root() -> void:
if _root != null:
return
layer = 10
_root = Control.new()
_root.name = "UiRoot"
_root.set_anchors_preset(Control.PRESET_FULL_RECT)
_root.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_root)
@@ -51,6 +58,9 @@ func _ready() -> void:
_mobile_blocker.mouse_filter = Control.MOUSE_FILTER_STOP
_mobile_blocker.visible = false
_root.add_child(_mobile_blocker)
func _ready() -> void:
_ensure_root()
set_process_input(true)
set_process_unhandled_input(true)
set_process(true)
@@ -121,16 +131,40 @@ func build_script(path: String, assets_root: String) -> Dictionary:
return {"root": Control.new(), "nodes": {}}
return UiBuild.build(spec, assets_root)
## 1:1 对齐 40250 ui.py: SetCenterPosition(x = 0, y = 0)
## 将窗口居中于当前屏幕 / 视口
func center_window(win: Control, offset := Vector2.ZERO) -> void:
if win == null or not is_instance_valid(win):
return
var vp_sz := Vector2(1920, 1080)
var vp := win.get_viewport()
if vp:
var vrect := vp.get_visible_rect().size
if vrect.x > 300 and vrect.y > 300:
vp_sz = vrect
elif _root and _root.size.x > 300 and _root.size.y > 300:
vp_sz = _root.size
elif screen.x > 0 and screen.y > 0:
vp_sz = Vector2(screen)
var win_sz := win.size
if win_sz.x <= 0 or win_sz.y <= 0:
win_sz = win.custom_minimum_size
win.position = ((vp_sz - win_sz) / 2.0 + offset).round()
# 解析 + 构建 + 打开,返回 { root, nodes }
func open_script(path: String, assets_root: String, modal := false) -> Dictionary:
func open_script(path: String, assets_root: String, modal := false, center := false) -> Dictionary:
var r := build_script(path, assets_root)
open(r.root, modal)
if center and r.root is Control:
center_window(r.root)
return r
func open(win: Control, modal := false) -> void:
_ensure_root()
if win.get_parent() == null:
_root.add_child(win)
win.set_meta("modal", modal)
_wire_close_buttons(win)
if mobile_mode:
MobileWindowHost.install(win, _mobile_area(), _window_title(win),
Callable(self, "_close_hosted_window").bind(win))
@@ -141,6 +175,13 @@ func open(win: Control, modal := false) -> void:
_restack()
window_opened.emit(win)
func _wire_close_buttons(win: Control) -> void:
for btn in win.find_children("CloseButton", "BaseButton", true, false):
var b := btn as BaseButton
if not b.has_meta("_close_wired"):
b.set_meta("_close_wired", true)
b.pressed.connect(func(): close(win))
func close(win: Control) -> void:
if win == null:
return
@@ -383,19 +424,22 @@ func _close_mobile_popup() -> bool:
# --- 标题栏拖动 --------------------------------------------------------
func _wire_drag(win: Control) -> void:
var handle := _find_drag_handle(win)
if handle == null or handle.has_meta("_drag_wired"):
return
handle.set_meta("_drag_wired", true)
handle.mouse_filter = Control.MOUSE_FILTER_STOP
handle.gui_input.connect(func(ev: InputEvent): _on_handle_input(win, ev))
var handles := _find_drag_handles(win)
for handle in handles:
if handle == null or handle.has_meta("_drag_wired"):
continue
handle.set_meta("_drag_wired", true)
handle.mouse_filter = Control.MOUSE_FILTER_STOP
handle.gui_input.connect(func(ev: InputEvent): _on_handle_input(win, ev))
func _find_drag_handle(win: Control) -> Control:
# 优先 titlebar;否则 board_with_titlebar 的顶部条;否则整个 window 根
func _find_drag_handles(win: Control) -> Array[Control]:
var list: Array[Control] = []
for n in win.find_children("*", "", true, false):
if n is Control and (n.get_meta("is_titlebar", false) or n.get_meta("titlebar_h", 0) > 0):
return n
return win
list.append(n as Control)
if list.is_empty():
list.append(win)
return list
func _on_handle_input(win: Control, ev: InputEvent) -> void:
if ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT: