Files
mtgodot-poc/project/popup_windows_parity_test.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

178 lines
6.8 KiB
GDScript

# popup_windows_parity_test.gd —— 校验弹窗(人物属性面板、仓库、背包等)
# 严格按照 40250 客户端的 UI 规范与关闭行为。
# godot --headless --path project --script popup_windows_parity_test.gd
extends SceneTree
const UiManager = preload("res://ui/ui_manager.gd")
const CharStatusUI = preload("res://ui/char_status_ui.gd")
const SafeboxUI = preload("res://ui/safebox_ui.gd")
const InventoryUI = preload("res://ui/inventory_ui.gd")
const AssetRoot = preload("res://asset_root.gd")
class FakeClient extends Node:
signal points_changed(points: Dictionary)
signal entity_main_set(vid: int)
signal safebox_opened(size: int, gold: int)
signal safebox_closed()
signal safebox_item_set(cell: int, vnum: int, count: int, flags: int, anti_flags: int, sockets: Array, attrs: Array)
signal inventory_changed(window: int, cell: int)
var pts: Array = []
var _main_vid := 7
func _init() -> void:
pts.resize(255)
pts.fill(0)
pts[1] = 42
pts[5] = 850
pts[6] = 900
pts[7] = 120
pts[8] = 150
func get_main_vid() -> int: return _main_vid
func get_points() -> Dictionary:
return {
"points": pts, "hp": pts[5], "max_hp": pts[6], "sp": pts[7], "max_sp": pts[8],
"level": pts[1], "exp": 0, "next_exp": 1000, "gold": 10000,
}
func get_entity(vid: int) -> Dictionary:
return {"name": "TestHero", "guild": 1, "race": 0}
func get_guild_name(gid: int) -> String:
return "HeroGuild" if gid == 1 else ""
func get_inventory() -> Array: return []
func get_equipment() -> Array: return []
func get_belt_inventory() -> Array: return []
func get_safebox_items() -> Array: return []
func get_safebox_gold() -> int: return 5000
func get_safebox_size() -> int: return 45
func safebox_close() -> void: pass
func say(type: int, text: String) -> bool: return true
var _fail := 0
func _ck(c: bool, m: String) -> void:
if not c:
_fail += 1
printerr("FAIL: " + m)
func _init() -> void:
_run()
if _fail == 0:
print("PASS: popup_windows_parity_test (CharacterWindow, SafeboxWindow, InventoryWindow layout + 40250 close behaviors)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
var assets := AssetRoot.path()
if not FileAccess.file_exists(assets.path_join("uiscript/uiscript/characterwindow.py")):
print(" (skip: missing uiscript assets)")
return
var ui: CanvasLayer = UiManager.new()
get_root().add_child(ui)
ui._ready()
var client := FakeClient.new()
get_root().add_child(client)
# =========================================================================
# 1. CharacterWindow
# =========================================================================
var char_ui := CharStatusUI.new()
get_root().add_child(char_ui)
char_ui.setup(ui, client, assets)
char_ui.open("STATUS")
_ck(char_ui.is_open(), "CharacterWindow is_open() after open('STATUS')")
var char_root: Control = char_ui._win.get("root")
_ck(char_root != null, "CharacterWindow root node exists")
if char_root:
_ck(char_root.position.x >= 0.0, "CharacterWindow position.x (%f) >= 0" % char_root.position.x)
_ck(char_root.position.y >= 0.0, "CharacterWindow position.y (%f) >= 0" % char_root.position.y)
# Face slot / Guild slot auto-deduction of dimensions
var face_slot: Control = char_ui._node("Face_Slot")
if face_slot:
_ck(face_slot.size.x > 0 and face_slot.size.y > 0,
"Face_Slot has non-zero size: %s" % str(face_slot.size))
var guild_slot: Control = char_ui._node("Guild_Name_Slot")
if guild_slot:
_ck(guild_slot.size.x > 0 and guild_slot.size.y > 0,
"Guild_Name_Slot has non-zero size: %s" % str(guild_slot.size))
# Tab button type check: must be Button with toggle_mode, NOT CheckBox
var tab1: Control = char_ui._node("Tab_Button_01")
if tab1:
_ck(tab1 is Button and not (tab1 is CheckBox),
"Tab_Button_01 is Button (not CheckBox)")
_ck((tab1 as Button).toggle_mode, "Tab_Button_01 has toggle_mode = true")
# Close button inside TitleBar
var close_btn: BaseButton = char_root.find_child("CloseButton", true, false)
_ck(close_btn != null, "CharacterWindow has CloseButton in titlebar")
if close_btn:
close_btn.pressed.emit()
_ck(not char_ui.is_open(), "CharacterWindow closes when CloseButton is pressed")
# =========================================================================
# 2. SafeboxWindow
# =========================================================================
var safe_ui := SafeboxUI.new()
get_root().add_child(safe_ui)
safe_ui.setup(client, ui, null, assets)
safe_ui.open()
_ck(safe_ui.is_open(), "SafeboxWindow is_open() after open()")
var safe_root: Control = safe_ui._root
_ck(safe_root != null, "SafeboxWindow _root node exists")
if safe_root:
_ck(safe_root.position.x >= 0.0, "SafeboxWindow position.x (%f) >= 0 (not negative off-screen)" % safe_root.position.x)
_ck(safe_root.position.y >= 0.0, "SafeboxWindow position.y (%f) >= 0 (not negative off-screen)" % safe_root.position.y)
# Page buttons check: page radio buttons are Buttons with toggle_mode, not CheckBoxes
var page1: Control = safe_root.find_child("PAGE1_BUTTON", true, false)
if page1:
_ck(page1 is Button and not (page1 is CheckBox),
"PAGE1_BUTTON is Button (not CheckBox)")
# CloseButton on TitleBar
var safe_close_btn: BaseButton = safe_root.find_child("CloseButton", true, false)
_ck(safe_close_btn != null, "SafeboxWindow has CloseButton in titlebar")
if safe_close_btn:
safe_close_btn.pressed.emit()
_ck(not safe_ui.is_open(), "SafeboxWindow closes when CloseButton is pressed")
# Re-open and test ExitButton ("关闭" button at bottom)
safe_ui.open()
_ck(safe_ui.is_open(), "SafeboxWindow re-opened")
if safe_ui.is_open():
var exit_btn: BaseButton = safe_ui._root.find_child("ExitButton", true, false)
_ck(exit_btn != null, "SafeboxWindow has ExitButton at bottom")
if exit_btn:
exit_btn.pressed.emit()
_ck(not safe_ui.is_open(), "SafeboxWindow closes when ExitButton is pressed")
# =========================================================================
# 3. InventoryWindow
# =========================================================================
var inv_ui := InventoryUI.new()
get_root().add_child(inv_ui)
inv_ui.setup(ui, client, null, assets)
inv_ui.open()
_ck(inv_ui.is_open(), "InventoryWindow is_open() after open()")
var inv_root: Control = inv_ui._win.get("root")
_ck(inv_root != null, "InventoryWindow root node exists")
if inv_root:
_ck(inv_root.position.x >= 0.0, "InventoryWindow position.x (%f) >= 0" % inv_root.position.x)
_ck(inv_root.position.y >= 0.0, "InventoryWindow position.y (%f) >= 0" % inv_root.position.y)
var inv_close_btn: BaseButton = inv_root.find_child("CloseButton", true, false)
_ck(inv_close_btn != null, "InventoryWindow has CloseButton in TitleBar")
if inv_close_btn:
inv_close_btn.pressed.emit()
_ck(not inv_ui.is_open(), "InventoryWindow closes when CloseButton is pressed")