extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_input_after_dialog.gd # Forgejo #153: "after a dialog popup the godot input system is lost on # keypress. If I had a key held it keeps moving in the key direction." # # Nothing was lost. A dialog stops the tree (#128) and a paused node receives # no input, so a key RELEASED while the line was up never reached the # controller - keys_down kept it, and the fleet flew on in that direction # once the game restarted. The keyboard is the authority on what is held; # keys_down is only a record of what we were told, and it went stale. # # Covers: # A. holding a key steers, as it always did # B. a release that arrives normally is honoured # C. the bug: a key held when a dialog opens, released while it is up - # the release never arrives, and the fleet must still stop # D. and it is the pause LIFTING that does it, not the dismissal itself, # so anything else that stops the tree is covered too # # Forgejo #154 rides along here, being the same scene: a dialog has to be # drawn over every panel, and it was drawn under the skill tree. const KEY_UP: Key = KEY_W var game_state: Node var scene: Node var controller: Node var narration: Control var stage: int = 0 var elapsed: 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() game_state.intro_active = false game_state.reset_quests() func _fail(message: String) -> bool: print("TEST_INPUT_AFTER_DIALOG: FAIL (%s)" % message) quit(1) return true func _key(keycode: Key, pressed: bool) -> void: var event: InputEventKey = InputEventKey.new() event.keycode = keycode event.pressed = pressed controller._handle_key(event) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene controller = scene narration = scene.get_node("UI/NarrationBanner") elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false # --- A. holding a key steers ---------------------------- _key(KEY_UP, true) if controller.keyboard_direction() == Vector2.ZERO: return _fail("holding a key steers nowhere") print("held key steers %s" % controller.keyboard_direction()) # --- B. and a release that arrives is honoured ---------- _key(KEY_UP, false) if controller.keyboard_direction() != Vector2.ZERO: return _fail("a delivered release did not stop the steering") print("a delivered release stops it") # --- C. the bug ------------------------------------------ # Held, then a dialog opens. The player lets go while reading; # that release is never delivered, because this node is paused. _key(KEY_UP, true) narration.say("Let go while you read this.", "TEST") if not paused: return _fail("the dialog did not stop the game, so this proves nothing") if controller.keyboard_direction() == Vector2.ZERO: return _fail("the key was already forgotten before the dialog closed") # ...the release happens here, and reaches nobody. narration.finish_typing() if not narration.dismiss(): return _fail("the dialog would not close") if paused: return _fail("closing the dialog left the game stopped") # NOTIFICATION_UNPAUSED is delivered as part of lifting the # pause, so by now the stale key must be gone. if controller.keyboard_direction() != Vector2.ZERO: return _fail("the fleet is still steering %s after a release that was never delivered" \ % controller.keyboard_direction()) if controller.keys_down.has(KEY_UP): return _fail("the key is still recorded as held") print("a release swallowed by the pause no longer strands the fleet") # --- D. by the pause lifting, not by the dismissal ------- _key(KEY_UP, true) paused = true paused = false if controller.keyboard_direction() != Vector2.ZERO: return _fail("stopping and restarting the tree left a stale key held") print("any pause, not just a dialog, reconciles what is held") # --- #154. a dialog draws over every panel --------------- var ui: Node = scene.get_node("UI") var banner_at: int = narration.get_index() var panels: Array = [] for menu in get_nodes_in_group("modal_menus"): if ui.is_ancestor_of(menu): panels.append(menu) if panels.is_empty(): return _fail("no modal panels found to be drawn over") for menu in panels: if menu.get_index() > banner_at: return _fail("%s is a later child than the dialog, so it draws on top of it" \ % menu.name) if banner_at != ui.get_child_count() - 1: return _fail("the dialog is child %d of %d - something still draws over it" \ % [banner_at, ui.get_child_count()]) print("the dialog is the last child of the UI layer, over all %d panels" % panels.size()) print("TEST_INPUT_AFTER_DIALOG: PASS") quit(0) return true return false