spaceship/tests/test_dialog_pause.gd

208 lines
7.5 KiB
GDScript3
Raw Normal View History

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.<instance member> 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