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

154 lines
6.1 KiB
GDScript

# test_taskbar_quickslot_parity.gd —— 验证方向 3:底部主任务栏、4 珍珠经验球与鼠标控制器 40250 1:1 对齐
extends SceneTree
const Quickbar = preload("res://ui/quickbar.gd")
class DummySkillTable extends RefCounted:
func motion_idx_of(_id: int) -> int: return 1
func is_fan_range(_id: int) -> bool: return false
func is_circle_range(_id: int) -> bool: return false
func target_range(_id: int) -> float: return 500.0
func target_count(_id: int, _lvl: int) -> int: return 1
func fly_shape(_id: int) -> int: return 0
func skill_name(id: int) -> String:
if id == 1: return "三连斩"
if id == 2: return "剑气"
return "技能 #%d" % id
class DummyClient extends Node:
signal quickslots_changed
signal skills_changed
signal points_changed(pts: Dictionary)
var _qs: Array = []
func get_quickslots() -> Array: return _qs
func get_points() -> Dictionary: return {"hp": 1000, "max_hp": 1000, "sp": 500, "max_sp": 500, "exp": 0, "next_exp": 1000}
func get_main_vid() -> int: return 1
func get_entity(_vid: int) -> Dictionary: return {"race": 0, "dead": false}
func get_skills() -> Array: return []
func get_skill_group() -> int: return 1
func quickslot_add(pos: int, type: int, ref: int) -> bool:
_qs.append({"pos": pos, "type": type, "ref": ref})
quickslots_changed.emit()
return true
var _passed := 0
var _failed := 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_passed += 1
print(" [PASS] %s" % msg)
else:
_failed += 1
printerr(" [FAIL] %s" % msg)
func _init() -> void:
print("\n=== Running test_taskbar_quickslot_parity (Direction 3: Taskbar & Quickslot 1:1) ===")
var root := Control.new()
root.size = Vector2(800, 600)
var client := DummyClient.new()
var table := DummySkillTable.new()
var qb := Quickbar.new()
qb.setup(client, table, root, func(): return null, "metin2-client/assets")
test_exp_four_pearls_parity(qb)
test_page_flip_parity(qb)
test_mouse_mode_controllers(qb)
print("\n=== SUMMARY: %d passed, %d failed ===" % [_passed, _failed])
if _failed > 0:
printerr("TEST FAILED!")
quit(1)
else:
print("ALL TESTS PASSED!")
quit(0)
func test_exp_four_pearls_parity(qb: Node) -> void:
print("\n--- Test 1: 40250 uitaskbar.py 4-Pearl EXP Gauge Calculation ---")
# Case 1: 0% EXP
qb.set_exp(0, 1000)
var exp_board: Control = qb.get("_exp_board")
_assert(exp_board != null, "EXP gauge board exists")
_assert(exp_board.tooltip_text == "EXP : 0.00%", "0% EXP tooltip formatted 1:1")
var clips: Array = qb.get("_exp_clips")
_assert(clips.size() == 4, "4 pearl clip controls present")
_assert(not clips[0].visible and not clips[1].visible and not clips[2].visible and not clips[3].visible,
"At 0% EXP, all 4 pearls are hidden/empty")
# Case 2: 25% EXP (1st quarter full)
qb.set_exp(250, 1000)
_assert(exp_board.tooltip_text == "EXP : 25.00%", "25% EXP tooltip matches")
_assert(clips[0].visible, "Pearl 1 is fully filled and visible")
_assert(clips[0].size == Vector2(19, 19), "Pearl 1 is full 19x19 dimensions")
_assert(not clips[1].visible and not clips[2].visible and not clips[3].visible,
"Pearls 2..4 remain empty at 25%")
# Case 3: 62.5% EXP (2 quarters full, 3rd quarter 50% filled)
# 625 / 1000 = quarter_pt 250, full_count = 2 (pearls 0 and 1 full). Pearl 2 fill_h = round(19 * 125/250) = 10 px
qb.set_exp(625, 1000)
_assert(exp_board.tooltip_text == "EXP : 62.50%", "62.5% EXP tooltip matches")
_assert(clips[0].visible and clips[0].size.y == 19, "Pearl 1 is full (19px)")
_assert(clips[1].visible and clips[1].size.y == 19, "Pearl 2 is full (19px)")
_assert(clips[2].visible and clips[2].size.y == 10, "Pearl 3 is partially filled (10px)")
_assert(not clips[3].visible, "Pearl 4 is empty")
# Case 4: 100% EXP (All 4 pearls full)
qb.set_exp(1000, 1000)
_assert(exp_board.tooltip_text == "EXP : 100.00%", "100% EXP tooltip matches")
_assert(clips[0].visible and clips[1].visible and clips[2].visible and clips[3].visible,
"All 4 pearls are visible and full at 100% EXP")
func test_page_flip_parity(qb: Node) -> void:
print("\n--- Test 2: Quickslot Multi-Page Flipping (Shift+1..4) ---")
_assert(qb.get_page() == 0, "Initial page is 0 (Page 1)")
qb.set_page(1)
_assert(qb.get_page() == 1, "Flipped to page 1 (Page 2)")
qb.set_page(2)
_assert(qb.get_page() == 2, "Flipped to page 2 (Page 3)")
qb.set_page(3)
_assert(qb.get_page() == 3, "Flipped to page 3 (Page 4)")
# Out of bounds ignored
qb.set_page(4)
_assert(qb.get_page() == 3, "Out of bounds page 4 rejected")
qb.set_page(-1)
_assert(qb.get_page() == 3, "Out of bounds page -1 rejected")
# Flipped back to page 0
qb.set_page(0)
_assert(qb.get_page() == 0, "Flipped back to page 0")
func test_mouse_mode_controllers(qb: Node) -> void:
print("\n--- Test 3: Mouse Left/Right Button Mode Controllers ---")
var btn_left: Control = qb.get_button("mouse_left")
var btn_right: Control = qb.get_button("mouse_right")
_assert(btn_left != null, "mouse_left button exists")
_assert(btn_right != null, "mouse_right button exists")
# Left mouse mode cycle: 0 -> 1 -> 2 -> 0
_assert(qb.get_left_mouse_mode() == 0, "Initial left mouse mode is 0 (Move & Attack)")
qb.set_left_mouse_mode(1)
_assert(qb.get_left_mouse_mode() == 1, "Left mouse mode changed to 1 (Auto Attack)")
_assert("自动攻击" in btn_left.tooltip_text, "Left mouse button tooltip updated to Auto Attack")
qb.set_left_mouse_mode(2)
_assert(qb.get_left_mouse_mode() == 2, "Left mouse mode changed to 2 (Camera)")
_assert("镜头旋转" in btn_left.tooltip_text, "Left mouse button tooltip updated to Camera")
# Right mouse mode & skill binding
qb.set_right_mouse_mode(0)
_assert(qb.get_right_mouse_mode() == 0, "Right mouse mode changed to 0 (Move & Attack)")
qb.set_right_mouse_mode(1)
_assert(qb.get_right_mouse_mode() == 1, "Right mouse mode changed to 1 (Camera)")
qb.set_right_mouse_mode(2)
_assert(qb.get_right_mouse_mode() == 2, "Right mouse mode changed to 2 (Skill)")
qb.set_right_mouse_skill(1)
_assert(qb.get_right_mouse_skill() == 1, "Right mouse skill set to 1 (三连斩)")
_assert("三连斩" in btn_right.tooltip_text, "Right mouse button tooltip reflects selected skill name")