extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_dialog_pause.gd # Forgejo #128: "When a dialog appears, no command should work and the game # physics should be paused as long as the message is not acknowledged." # # Two claims, and they are checked separately because they can fail # separately: the world must STOP, and the controls must not ANSWER. Presses # are pushed through the viewport rather than handed to a handler directly - # calling a handler by hand would prove nothing about whether the engine # would have delivered the event at all, which is the whole question here. # # What a press does while a dialog is up is ACKNOWLEDGE it (#127) and # nothing else. So "no command works" is not "the press vanishes": it is # "the press reached the dialog and reached nothing else", and that is what # is asserted below. # # Covers: # A. a line goes up and the tree is paused # B. the world is frozen: the fleet holds still under a movement order # C. GameState's own clock is frozen too - the wave clock does not run # D. no command works: a press on a HUD button, delivered the way a finger # delivers one, acknowledges the dialog and does not fire the button # E. queued lines hold the pause until the LAST one is acknowledged # F. after the last one, the world runs and the same press does fire # G. leaving the tree with a line up does not leave the game stopped with # nothing left that could start it again var game_state: Node var scene: Node var fleet: Node2D var narration: Control var button: Control var build_menu: Control var presses: int = 0 var stage: int = 0 var elapsed: float = 0.0 var frozen_at: Vector2 = Vector2.ZERO var frozen_wave_timer: float = 0.0 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() # Not the prologue. The prologue has its own dialog coverage # (test_intro_scene); this is about dialogs in the ordinary game, where # there are commands to block in the first place. game_state.intro_active = false # And no quest chain. Quest briefings are dialogs too (#132), and this # test is about what a dialog DOES - it needs to be the one deciding # when one appears. game_state.reset_quests() game_state.rocks = 500 func _fail(message: String) -> bool: print("TEST_DIALOG_PAUSE: FAIL (%s)" % message) quit(1) return true func _on_pressed() -> void: presses += 1 # A press delivered the way the player delivers one: through the viewport, at # the button's own screen rect, so the engine's routing decides whether it # arrives rather than the test deciding for it. func _press_button() -> void: var at: Vector2 = button.get_global_rect().get_center() for is_down in [true, false]: var event: InputEventMouseButton = InputEventMouseButton.new() event.button_index = MOUSE_BUTTON_LEFT event.pressed = is_down event.position = at event.global_position = at # in_local_coords, deliberately. The other form runs the position # through the window's screen transform, and headless has no window - # the event then lands nowhere and the button looks broken. root.push_input(event, true) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene fleet = scene.get_node("Fleet") narration = scene.get_node("UI/NarrationBanner") button = scene.get_node("UI/BuildButton") build_menu = scene.get_node("UI/BuildMenu") button.pressed.connect(_on_pressed) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.4: return false if paused: return _fail("the game was already stopped before any dialog") # The button answers when nothing is in the way. Without this # baseline, D below would prove only that the button was # unreachable all along. presses = 0 _press_button() elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.2: return false if presses == 0: return _fail("the build button does not answer a press even with no dialog up") print("baseline: a press on the build button fires it") # It opened the build menu, which is modal and would block every # later press for a reason that has nothing to do with #128. build_menu.visible = false # --- A. a line stops the game ---------------------------- narration.say("Hold position.", "TEST") narration.say("And stand by.", "TEST") if not paused: return _fail("a dialog went up and the game kept running") print("dialog up: the tree is paused") frozen_at = fleet.get_centroid() frozen_wave_timer = game_state.wave_timer # Ordered somewhere it is nowhere near, so even a little drift # would show up. steer_direction persists, so this order stands # for the whole frozen second below. fleet.move_in_direction(Vector2.RIGHT) presses = 0 elapsed = 0.0 stage = 3 return false 3: # --- B and C. a whole second of frozen world ------------- if elapsed < 1.0: return false var moved: float = fleet.get_centroid().distance_to(frozen_at) if moved > 0.5: return _fail("the fleet drifted %.1fpx while a dialog was up" % moved) print("fleet held still for 1s under a movement order (%.2fpx)" % moved) var ran: float = absf(game_state.wave_timer - frozen_wave_timer) if ran > 0.001: return _fail("the wave clock ran on %.2fs while a dialog was up" % ran) print("the wave clock did not advance") # --- D. the press acknowledges, and does nothing else ---- _press_button() if presses != 0: return _fail("the build button fired while a dialog was up") if build_menu.visible: return _fail("the build menu opened from a press meant for the dialog") print("a press while the dialog was up did not reach the button") # --- E. the queued line keeps the game stopped ----------- if not narration.is_speaking(): return _fail("one press acknowledged both queued lines") if not paused: return _fail("the game restarted with a line still on screen") print("a second queued line keeps the game stopped") _press_button() if presses != 0: return _fail("the build button fired on the acknowledging press") if narration.is_speaking(): return _fail("the banner is still speaking after the last line") if paused: return _fail("the last line was acknowledged and the game is still stopped") print("last line acknowledged: the game is running again") frozen_at = fleet.get_centroid() presses = 0 elapsed = 0.0 stage = 4 return false 4: # --- F. and the world answers again ---------------------- if elapsed < 0.4: return false if fleet.get_centroid().distance_to(frozen_at) < 1.0: return _fail("the fleet is still frozen after the dialog closed") _press_button() if presses == 0: return _fail("the build button stayed dead after the dialog closed") build_menu.visible = false print("the world moves again: the fleet flies and the button fires") # --- G. torn down mid-line ------------------------------ narration.say("Interrupted.", "TEST") if not paused: return _fail("the dialog did not stop the game the second time") scene.free() scene = null if paused: return _fail("the scene was torn down mid-dialog and left the game stopped") print("a scene torn down mid-dialog leaves the game running") print("TEST_DIALOG_PAUSE: PASS") quit(0) return true return false