# quest_test —— P7:GC_SCRIPT 渲染 + quest_confirm + quest_log + NPC 点击 headless 自检。 # godot --headless --path project --script quest_test.gd extends SceneTree const QuestDialog = preload("res://ui/quest_dialog.gd") const QuestLog = preload("res://ui/quest_log.gd") class FakeClient extends Node: signal script_dialog(skin: int, text: String) signal quest_confirm_ask(msg: String, timeout: int, request_pid: int) signal quest_info(index: int) var answers := [] var cancels := 0 var inputs := [] var confirms := [] var clicks := [] var quests := [] func is_in_game() -> bool: return true func get_quests() -> Array: return quests func script_answer(a) -> bool: answers.append(a); return true func quest_cancel() -> bool: cancels += 1; return true func quest_input(text) -> bool: inputs.append(text); return true func quest_confirm(yes, pid) -> bool: confirms.append([yes, pid]); return true func click_npc(vid) -> bool: clicks.append(vid); return true func get_entity(_v) -> Dictionary: return {} 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: quest_test (script parse + dialog + confirm + log)") quit(0) else: printerr("%d check(s) failed" % _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 qd: Node = QuestDialog.new() get_root().add_child(qd) qd.setup(fc, canvas) var p: Dictionary = qd.parse_script("Greetings, hero.[ENTER]The village needs you.[ENTER][QUESTION arg(\"I accept\") arg(\"Not now\")]") _ck(p.body.contains("Greetings, hero.") and p.body.contains("village needs"), "plain text kept") _ck(p.body.split("\n").size() == 2, "[ENTER] -> line break (%d lines)" % p.body.split("\n").size()) _ck(p.choices == ["I accept", "Not now"], "[QUESTION] -> 2 choices") var p2: Dictionary = qd.parse_script("Bye.[DONE]") _ck(p2.has_next and p2.choices.is_empty(), "[DONE] -> continue button, no choices") # --- 对话弹窗 + 选择 -> script_answer --- fc.script_dialog.emit(1, "Pick one.[QUESTION arg(\"A\") arg(\"B\") arg(\"C\")]") await process_frame _ck(qd.is_open(), "dialog shown on script_dialog") _ck(qd._btnrow.get_child_count() == 3, "3 choice buttons") (qd._btnrow.get_child(1) as Button).pressed.emit() # 选 B (index 1) _ck(fc.answers == [1], "choice B -> script_answer(1)") _ck(not qd.is_open(), "dialog closes after choice") # 无选项 -> 「继续」-> script_answer(255) fc.script_dialog.emit(0, "Just text.[NEXT]") await process_frame _ck(qd._btnrow.get_child_count() == 1, "1 continue button") (qd._btnrow.get_child(0) as Button).pressed.emit() _ck(fc.answers == [1, 255], "continue -> script_answer(255)") # ESC dismisses the active script through the legacy QUEST_CANCEL packet. fc.script_dialog.emit(0, "Cancelable quest.[NEXT]") await process_frame var esc := InputEventKey.new() esc.pressed = true esc.keycode = KEY_ESCAPE qd._unhandled_input(esc) _ck(fc.cancels == 1, "ESC -> quest_cancel()") _ck(not qd.is_open(), "ESC closes quest dialog") # [INPUT] renders a LineEdit and sends the entered text through QUEST_INPUT_STRING. var pi: Dictionary = qd.parse_script("Tell me your name.[INPUT]") _ck(pi.has_input, "[INPUT] parsed") fc.script_dialog.emit(0, "Tell me your name.[INPUT]") await process_frame var input := qd._btnrow.get_child(0) as LineEdit input.text = "Alice" (qd._btnrow.get_child(1) as Button).pressed.emit() _ck(fc.inputs == ["Alice"], "[INPUT] submit -> quest_input('Alice')") _ck(not qd.is_open(), "input submit closes quest dialog") # --- quest_confirm --- fc.quest_confirm_ask.emit("Join the party?", 30, 999) await process_frame _ck(qd.is_open(), "confirm shown") # 找「接受」按钮 var acc: Button = null for n in qd._btnrow.find_children("*", "Button", true, false): if n.text == "接受": acc = n _ck(acc != null, "accept button present") if acc: acc.pressed.emit() _ck(fc.confirms == [[true, 999]], "accept -> quest_confirm(true, 999)") # --- quest_log --- var ql: Node = QuestLog.new() get_root().add_child(ql) ql.setup(fc, canvas) fc.quests = [ {"index": 1, "title": "Slay 3 wolves", "counter_name": "Wolves", "counter_value": 2, "clock_name": "", "clock_value": 0}, {"index": 2, "title": "", "counter_name": "", "counter_value": 0, "clock_name": "", "clock_value": 0}, # 完成 -> 不显示 ] ql.toggle() _ck(ql.is_open(), "quest log opened") await process_frame # 只 1 条有标题的 var titles := 0 for box in ql._list.get_children(): for lbl in box.find_children("*", "Label", true, false) if box is VBoxContainer else []: if String(lbl.text).begins_with("◆"): titles += 1 _ck(titles == 1, "quest log shows 1 titled quest (got %d)" % titles) fc.quest_info.emit(1) # 刷新不炸 await process_frame _ck(true, "quest_info refresh ok")