- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
179 lines
6.7 KiB
GDScript
179 lines
6.7 KiB
GDScript
# test_refine_parity.gd —— 40250 强化系统 1:1 纯净无头自检套件
|
|
# 验证:
|
|
# 1. RefineDialogNew 动态几何尺寸计算与 Tooltip
|
|
# 2. 材料槽位与 ThinBoard,背包存量白字/红字对比
|
|
# 3. 金币费用与不足警示
|
|
# 4. 100% 成功率与 type==5 免弹窗直接发包
|
|
# 5. type==3 (铁匠石)、type==2 (祝福卷轴)、type==0 (普通铁匠) 对应专属 QuestionDialog2 文案
|
|
# 6. 二次确认弹窗 "否" 拦截发包、"是" 触发发包
|
|
# 7. 40250 取消规范:点击取消/ESC/关闭标题栏发送 refine(255, 255)
|
|
# 8. 10.0m 超距自动取消发包
|
|
# 9. 强化成功与失败音画回显
|
|
extends SceneTree
|
|
|
|
const RefineUI = preload("res://ui/refine_ui.gd")
|
|
|
|
class FakeAudio extends Node:
|
|
var calls: Array[String] = []
|
|
func play_ui(name: String) -> void:
|
|
calls.append(name)
|
|
|
|
class FakeClient extends Node:
|
|
signal refine_ask(info: Dictionary)
|
|
signal refine_result(ok: bool)
|
|
|
|
var calls: Array = []
|
|
var gold := 1000000
|
|
var inventory: Array = [
|
|
{"vnum": 30001, "count": 5, "cell": 0, "window": 1},
|
|
{"vnum": 30002, "count": 1, "cell": 1, "window": 1},
|
|
]
|
|
var main_pos := Vector3(100.0, 10.0, 100.0)
|
|
|
|
func is_in_game() -> bool: return true
|
|
func get_gold() -> int: return gold
|
|
func get_inventory() -> Array: return inventory
|
|
func get_main_vid() -> int: return 1001
|
|
func get_entity(vid: int) -> Dictionary:
|
|
if vid == 1001:
|
|
return {"pos": main_pos}
|
|
return {}
|
|
func refine(pos: int, type: int) -> bool:
|
|
calls.append(["refine", pos, type])
|
|
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: test_refine_parity (40250 RefineDialogNew 1:1 parity)")
|
|
quit(0)
|
|
else:
|
|
printerr("%d check(s) failed in test_refine_parity" % _fail)
|
|
quit(1)
|
|
|
|
func _run() -> void:
|
|
var canvas := CanvasLayer.new()
|
|
get_root().add_child(canvas)
|
|
var fc := FakeClient.new()
|
|
get_root().add_child(fc)
|
|
var fa := FakeAudio.new()
|
|
get_root().add_child(fa)
|
|
|
|
var ru: Node = RefineUI.new()
|
|
get_root().add_child(ru)
|
|
ru.setup(fc, canvas, null, fa)
|
|
|
|
_ck(not ru.is_open(), "1. 初始状态关闭")
|
|
|
|
# --- Case 1: 普通强化 (type 0, prob 60 < 100, 材料足) ---
|
|
fc.refine_ask.emit({
|
|
"type": 0, "pos": 5, "src_vnum": 10, "result_vnum": 11,
|
|
"cost": 50000, "prob": 60,
|
|
"materials": [{"vnum": 30001, "count": 2}],
|
|
})
|
|
_ck(ru.is_open(), "2. 收到 refine_ask 打开窗口")
|
|
_ck(ru._prob_label.text == "成功率: 60%", "3. 成功率文本准确")
|
|
_ck(ru._cost_label.text == "费用: 50000 金", "4. 费用文本准确")
|
|
_ck(ru._material_container.get_child_count() == 1, "5. 材质行生成")
|
|
var mat_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0)
|
|
_ck(mat_lbl.modulate.r < 0.95 and mat_lbl.modulate.g > 0.8, "6. 材料足 (5>=2) 显示白字")
|
|
|
|
# 点击精炼 -> 应当弹出 QuestionDialog2,而不是直接发包
|
|
ru._refine_button.pressed.emit()
|
|
_ck(ru._question_dialog.visible, "7. prob 60 < 100 必须弹出 QuestionDialog2")
|
|
_ck(ru._qd_message1.text.contains("销毁"), "8. type 0 对应彻底销毁文案")
|
|
_ck(fc.calls.is_empty(), "9. 弹窗期间未发包")
|
|
|
|
# 点击 "否" -> 取消确认,精炼窗口不关,不发包
|
|
ru._qd_cancel_btn.pressed.emit()
|
|
_ck(not ru._question_dialog.visible, "10. 点否关闭确认弹窗")
|
|
_ck(ru.is_open(), "11. 主强化窗依然保持开启")
|
|
_ck(fc.calls.is_empty(), "12. 依然未发包")
|
|
|
|
# 再次点击精炼,并点 "是" -> 确认发包
|
|
ru._refine_button.pressed.emit()
|
|
ru._qd_accept_btn.pressed.emit()
|
|
_ck(fc.calls == [["refine", 5, 0]], "13. 确认后发出 client.refine(pos=5, type=0)")
|
|
_ck(not ru.is_open(), "14. 确认后主窗关闭")
|
|
fc.calls.clear()
|
|
|
|
# --- Case 2: 100% 成功率免弹窗 ---
|
|
fc.refine_ask.emit({
|
|
"type": 0, "pos": 2, "src_vnum": 1, "result_vnum": 2,
|
|
"cost": 1000, "prob": 100, "materials": [],
|
|
})
|
|
ru._refine_button.pressed.emit()
|
|
_ck(not ru._question_dialog.visible, "15. 100% 成功率不弹 QuestionDialog2")
|
|
_ck(fc.calls == [["refine", 2, 0]], "16. 100% 成功率直接发出 refine(2, 0)")
|
|
_ck(not ru.is_open(), "17. 发包后关闭")
|
|
fc.calls.clear()
|
|
|
|
# --- Case 3: 祝福卷轴 (type 2) 降级文案 ---
|
|
fc.refine_ask.emit({
|
|
"type": 2, "pos": 10, "src_vnum": 15, "result_vnum": 16,
|
|
"cost": 10000, "prob": 30, "materials": [],
|
|
})
|
|
ru._refine_button.pressed.emit()
|
|
_ck(ru._question_dialog.visible, "18. type 2 弹确认框")
|
|
_ck(ru._qd_message1.text.contains("降低1级"), "19. type 2 对应降级文案")
|
|
ru._qd_cancel_btn.pressed.emit()
|
|
|
|
# 点击取消按钮 -> 40250 发送 refine(255, 255)
|
|
ru._cancel_button.pressed.emit()
|
|
_ck(not ru.is_open(), "20. 取消后关闭")
|
|
_ck(fc.calls == [["refine", 255, 255]], "21. 取消发包 refine(255, 255)")
|
|
fc.calls.clear()
|
|
|
|
# --- Case 4: 铁匠石 (type 3) 加成文案 ---
|
|
fc.refine_ask.emit({
|
|
"type": 3, "pos": 12, "src_vnum": 18, "result_vnum": 19,
|
|
"cost": 20000, "prob": 50, "materials": [],
|
|
})
|
|
ru._refine_button.pressed.emit()
|
|
_ck(ru._qd_message1.text.contains("加成") or ru._qd_message1.text.contains("提升"), "22. type 3 对应概率提升文案")
|
|
# ESC 取消 -> 发送 refine(255, 255)
|
|
ru.cancel_refine()
|
|
_ck(not ru.is_open() and fc.calls == [["refine", 255, 255]], "23. cancel_refine 发送 (255, 255)")
|
|
fc.calls.clear()
|
|
|
|
# --- Case 5: 材料不足红字标红 ---
|
|
fc.refine_ask.emit({
|
|
"type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2,
|
|
"cost": 1000, "prob": 80,
|
|
"materials": [{"vnum": 30002, "count": 5}], # 背包只有 1 个
|
|
})
|
|
var shortage_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0)
|
|
_ck(shortage_lbl.modulate.r > 0.9 and shortage_lbl.modulate.g < 0.4, "24. 材料不足 (1<5) 显示红字")
|
|
ru.close()
|
|
|
|
# --- Case 6: 10 米超距自动发包取消 ---
|
|
fc.refine_ask.emit({
|
|
"type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2,
|
|
"cost": 1000, "prob": 80, "materials": [],
|
|
})
|
|
_ck(ru.is_open(), "25. 再次打开")
|
|
# 玩家移动 5 米 (仍在 10 米内)
|
|
fc.main_pos = Vector3(105.0, 10.0, 100.0)
|
|
ru._process(0.1)
|
|
_ck(ru.is_open(), "26. 移动 5 米不关闭")
|
|
# 玩家移动 15 米 (超出 10 米)
|
|
fc.main_pos = Vector3(120.0, 10.0, 100.0)
|
|
ru._process(0.1)
|
|
_ck(not ru.is_open(), "27. 移动 15 米超出限制自动关闭")
|
|
_ck(fc.calls == [["refine", 255, 255]], "28. 超距自动发送取消包 refine(255, 255)")
|
|
fc.calls.clear()
|
|
|
|
# --- Case 7: 结果音效与状态回显 ---
|
|
fc.refine_result.emit(true)
|
|
_ck(ru.is_open() and ru._prob_label.text.contains("成功"), "29. 成功结果回显")
|
|
_ck(fa.calls.has("refine_success.wav"), "30. 播放 refine_success.wav")
|
|
fc.refine_result.emit(false)
|
|
_ck(ru._prob_label.text.contains("失败"), "31. 失败结果回显")
|
|
_ck(fa.calls.has("refine_fail.wav"), "32. 播放 refine_fail.wav")
|