extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_dialog_typing.gd # Forgejo #149: "les dialogues doivent arriver petit a petit, avec un VFX # associe a des dialogues de science fiction" - dialogs should arrive bit by # bit, with an effect that reads as science fiction. # # The failure worth guarding against is not "it does not type" - it is the # one that comes with typing: a player clicking at their usual pace skips # the second line entirely, having never seen a word of it. #127 exists to # stop exactly that, so a press on a line still arriving has to finish it # rather than dismiss it. # # Covers: # A. a line arrives a character at a time rather than all at once # B. the text itself is set whole and REVEALED - growing the string would # re-wrap the line every frame and make words jump between rows # C. a press on a line still arriving finishes it and does not dismiss it # D. a press on a finished line dismisses it, as it always did # E. it types while the game is stopped, being the one node still running # F. the sci-fi part: the panel is drawn differently while decoding, and # settles when the line is complete var game_state: Node var scene: Node var narration: Control var stage: int = 0 var elapsed: float = 0.0 var first_line: String = "" var seen: Array = [] func _initialize() -> void: # GameState is an autoload: a bare GameState. does not # compile in a SceneTree script, so the node is fetched by name instead. game_state = root.get_node("GameState") game_state.reset_new_game() game_state.intro_active = false game_state.reset_quests() func _fail(message: String) -> bool: print("TEST_DIALOG_TYPING: FAIL (%s)" % message) quit(1) return true func _click() -> void: var press: InputEventMouseButton = InputEventMouseButton.new() press.button_index = MOUSE_BUTTON_LEFT press.pressed = true narration._unhandled_input(press) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene narration = scene.get_node("UI/NarrationBanner") elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false first_line = "Convoy Kestrel, this is a long enough line that decoding it takes a moment." narration.say(first_line, "TEST") narration.say("And a second one behind it.", "TEST") # --- A and B. it arrives, and the text is whole ---------- if not narration.is_typing(): return _fail("the line was complete the instant it was said") if narration.label.text != first_line: return _fail("the label holds a partial string - the line will re-wrap as it types") if narration.label.visible_characters < 0: return _fail("the whole line is visible already") seen.append(narration.label.visible_characters) # Hand-ticked, so the reveal is measured rather than raced. The # banner processes while the tree is stopped (#128), which is E. if not paused: return _fail("a dialog did not stop the game") for i in range(12): narration._process(1.0 / 60.0) seen.append(narration.label.visible_characters) var rising: int = 0 for i in range(1, seen.size()): if int(seen[i]) > int(seen[i - 1]): rising += 1 if rising < 6: return _fail("the reveal advanced on only %d of %d frames" % [rising, seen.size() - 1]) if int(seen[-1]) >= first_line.length(): return _fail("the whole line decoded within 12 frames - that is not arriving") print("typed while the game was stopped: %d -> %d of %d characters over 12 frames" \ % [seen[0], seen[-1], first_line.length()]) # --- F. and it looks like it is working ------------------ # is_typing() is the flag _draw() branches on for the sweep, the # write-head caret and the frame's charge, so its transition IS # the effect changing. Checked rather than calling _draw() by # hand, which Godot refuses outside a paint. if not narration.is_typing(): return _fail("the test's own ticking finished the line") narration.queue_redraw() narration.finish_typing() if narration.is_typing(): return _fail("finish_typing() left the line still arriving") if narration.label.visible_characters != -1: return _fail("a finished line is still hiding characters") if narration.SWEEP_SECONDS <= 0.0 or narration.CHARS_PER_SECOND <= 0.0: return _fail("the decode has no rate to run at") print("decoding while arriving, settled when it lands (%.0f chars/s, %.1fs sweep)" \ % [narration.CHARS_PER_SECOND, narration.SWEEP_SECONDS]) # --- C. a press finishes rather than dismisses ----------- narration.say(first_line, "TEST") # say() queues behind the current line, so start it cleanly. narration.clear() narration.say(first_line, "TEST") narration.say("Second.", "TEST") if not narration.is_typing(): return _fail("the fresh line was not arriving") _click() if narration.is_typing(): return _fail("a press did not finish a line that was still arriving") if narration.current != first_line: return _fail("a press on an arriving line dismissed it - the next line would be skipped unread") print("a press on an arriving line finishes it, and does not move on") # --- D. and the next press does move on ------------------ _click() if narration.current == first_line: return _fail("a press on a finished line did not dismiss it") if narration.current != "Second.": return _fail("dismissing landed on \"%s\"" % narration.current) print("the press after that moves on to \"%s\"" % narration.current) print("TEST_DIALOG_TYPING: PASS") quit(0) return true return false