208 lines
7.5 KiB
GDScript3
208 lines
7.5 KiB
GDScript3
|
|
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
|