# p10_test —— P10:serverinfo + loading 遮罩 + 断线重连 + 串场状态机 headless 自检。 # godot --headless --path project --script p10_test.gd extends SceneTree const ServerInfoRes = preload("res://net/serverinfo.gd") const LoadingScreen = preload("res://ui/loading_screen.gd") const ReconnectUI = preload("res://ui/reconnect_ui.gd") const AppFlow = preload("res://app_flow.gd") class FakeClient extends Node: signal phase_changed(phase: String) signal char_list(characters: Array) signal entered_game() signal disconnected(reason: String) signal login_failed(reason: String) var reconnects := 0 var connects := [] var selects := [] func connect_to_server(ah, ap, gh, gp, id, pw) -> void: connects.append([ah, ap, gh, gp, id, pw]) func select_character(i) -> bool: selects.append(i); return true func reconnect() -> bool: reconnects += 1; 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: p10_test (serverinfo + loading + reconnect + app flow)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: # --- serverinfo --- var si: RefCounted = ServerInfoRes.new() _ck(si.count() == 1 and si.server(0)["auth_host"] == "192.168.21.203", "serverinfo: built-in default") var addr: Dictionary = si.address(0, 3) _ck(addr["game_port"] == 13022, "serverinfo: 内置 CH3 -> 13022 (step 10)") var tmp := "user://p10_servers.txt" var f := FileAccess.open(tmp, FileAccess.WRITE) f.store_line("# test") f.store_line("欧服\teu.example.com\t13000\teu.example.com\t13011\t1,2\t10") f.close() _ck(si.load_file(tmp) and si.count() == 1 and si.server(0)["name"] == "欧服", "serverinfo: parse file") _ck(si.address(0, 2)["game_port"] == 13021, "serverinfo: file port_step 10 覆盖 -> CH2 = 13021") # --- loading screen --- var ls: Node = LoadingScreen.new() get_root().add_child(ls) ls.setup(null) _ck(not ls.is_showing(), "loading: hidden initially") ls._on_phase("loading") _ck(ls.is_showing() and ls._label.text.contains("地图"), "loading: shows on phase=loading") ls.set_progress(0.5) _ck(ls._bar.visible and ls._bar.value == 50.0, "loading: progress bar") ls._on_phase("select") _ck(not ls.is_showing(), "loading: hides on phase=select (选人界面要露出来)") ls._on_phase("loading") ls._on_phase("game") _ck(not ls.is_showing(), "loading: hides on phase=game") ls._on_phase("login") _ck(not ls.is_showing(), "loading: hides on phase=login") # --- reconnect --- var fc := FakeClient.new() get_root().add_child(fc) var rc: Node = ReconnectUI.new() get_root().add_child(rc) rc.setup(fc) _ck(not rc.is_showing(), "reconnect: hidden initially") fc.disconnected.emit("peer closed") _ck(rc.is_showing() and rc._msg.text.contains("peer closed"), "reconnect: shows on disconnect") # 倒计时耗尽 -> reconnect() rc._left = 0.05 rc._process(0.1) _ck(fc.reconnects == 1, "reconnect: auto-retry fired") # 手动 rc._retry_now() _ck(fc.reconnects == 2, "reconnect: manual retry") # entered_game -> 收起 fc.entered_game.emit() _ck(not rc.is_showing(), "reconnect: dismissed on entered_game") # --- app flow 状态机 --- var fc2 := FakeClient.new() var af: Node = AppFlow.new() af.build_game_scene = false get_root().add_child(af) af.start("", fc2) _ck(af.state() == AppFlow.LOGIN, "flow: starts at LOGIN") # 检测频道按钮存在 + 探测不可达端口快速返回 false var has_probe := false for b in af._ui.find_children("*", "Button", true, false): if String(b.text) == "检测频道": has_probe = true _ck(has_probe, "flow: 检测频道 button present") var reachable: bool = await af._probe_tcp("127.0.0.1", 1, 0.3) _ck(reachable == false, "flow: _probe_tcp unreachable -> false") af._do_connect() _ck(fc2.connects.size() == 1 and fc2.connects[0][3] == 13002, "flow: connect uses serverinfo addr") if af._loading and af._loading.has_method("show_for"): af._loading.show_for("载入角色…") # 模拟 phase=select 时遮罩已亮 fc2.char_list.emit([{"index": 0, "name": "Hero", "level": 42}]) _ck(af.state() == AppFlow.SELECT, "flow: char_list -> SELECT") _ck(af._loading == null or not af._loading.is_showing(), "flow: _goto_select 收起读取遮罩(选人界面不被盖)") # 新版选人页:默认已选 slot 0,点“开始”进游戏 var picked := false for b in af._ui.find_children("*", "Button", true, false): if String(b.text) == "开始": b.pressed.emit() picked = true _ck(picked and fc2.selects == [0], "flow: pick char -> select_character(0)") fc2.entered_game.emit() _ck(af.state() == AppFlow.GAME, "flow: entered_game -> GAME") # 在 SELECT 时断线 -> 回 LOGIN fc2.char_list.emit([{"index": 0, "name": "Hero", "level": 42}]) _ck(af.state() == AppFlow.SELECT, "flow: back to SELECT") fc2.disconnected.emit("dropped") _ck(af.state() == AppFlow.LOGIN, "flow: disconnect in SELECT -> LOGIN")