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:
+56
-12
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user