542 lines
24 KiB
GDScript
542 lines
24 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless test: run with
|
|
# godot --headless -s res://tests/test_intro_scene.gd
|
|
# Forgejo #108 (#122, #123, #124): the prologue.
|
|
#
|
|
# The director drives the REAL scene rather than being a scene of its own, so
|
|
# everything below is checked against the real fleet, the real enemies and
|
|
# the real asteroid field.
|
|
#
|
|
# Covers:
|
|
# A. it runs on a NEW game and not on a resumed one
|
|
# B. "no action button available, only movements" - every pressable
|
|
# affordance is gone, and the joystick is not
|
|
# C. the convoy: bigger than the player's own ceiling, and mostly holds
|
|
# D. the guide speaks, and the banner never shows a line nobody set
|
|
# E. the ambush is easy: chasers only, and clearing them advances the beat
|
|
# F. the belt is dense, and it is across the lane rather than beside it
|
|
# G. the breach takes the wheel: player input stops moving the fleet
|
|
# H. the vortex leaves exactly one vessel, and it is the default interceptor
|
|
# I. the handover: the flag clears, the buttons come back, the spawner and
|
|
# the asteroid field are running again, and the fleet is a new game's
|
|
# J. skipping lands in the same state as playing it through
|
|
# K. #125 - no beat ends before its own floor, and a beat whose point is
|
|
# the guide talking does not end mid-sentence
|
|
# L. #125 - the convoy is spread out, with its holds surrounded by escorts
|
|
# M. #125 - the breach is a growing black hole with a lens over it, not a
|
|
# few lines turning around a point
|
|
# N. #127 - a dialog stays up until it is dismissed: no timer closes it,
|
|
# a click does, and the click is eaten rather than also steering
|
|
# O. #127 - every dialog names its speaker
|
|
# P. #128 - the prologue opens STOPPED behind its first line, and
|
|
# acknowledging it is what starts the world
|
|
# Q. #133 - the handover runs straight into the first quest
|
|
# R. #139 - no belt rock is laid down inside the visible screen
|
|
# S. #170 - the prologue opens on the PICTURE: the convoy is flying and the
|
|
# game is running for a few seconds before the guide says anything
|
|
# T. #183 - absorption whites out the full screen, teleports only once the
|
|
# cover is opaque, then reveals the fleet at the normal spawn
|
|
|
|
var game_state: Node
|
|
var scene: Node
|
|
var director: Node
|
|
var fleet: Node
|
|
var narration: Control
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
var convoy_size: int = 0
|
|
var skipped_props: Array = []
|
|
var whiteout_mid_checked: bool = false
|
|
|
|
|
|
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()
|
|
game_state.rocks = 0
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_INTRO_SCENE: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|
|
|
|
|
|
func _load_scene() -> void:
|
|
scene = load("res://world/Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
current_scene = scene
|
|
fleet = scene.get_node("Fleet")
|
|
director = scene.get_node_or_null("IntroDirector")
|
|
narration = scene.get_node("UI/NarrationBanner")
|
|
|
|
|
|
func _drop_scene() -> void:
|
|
scene.queue_free()
|
|
scene = null
|
|
|
|
|
|
func _hidden(path: String) -> bool:
|
|
var node: Control = scene.get_node_or_null(path)
|
|
return node == null or not node.visible
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
# --- A. a new game gets one ------------------------------
|
|
if not game_state.intro_active:
|
|
return _fail("reset_new_game() did not arm the prologue")
|
|
_load_scene()
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
1:
|
|
if elapsed < 0.4:
|
|
return false
|
|
if director == null:
|
|
return _fail("no IntroDirector in the scene")
|
|
if not director.running:
|
|
return _fail("the director did not start on a new game")
|
|
|
|
# --- S. #170: it opens on the picture, not the caption ---
|
|
# The scene is a third of a second old here, well inside the
|
|
# silence, so the convoy should be flying and nothing should be
|
|
# on the banner.
|
|
if narration.is_speaking():
|
|
return _fail("the guide was already talking %.2fs in - #170 asks for a few seconds first" \
|
|
% director.beat_time)
|
|
if paused:
|
|
return _fail("the prologue opened stopped, with nothing on the banner to stop it")
|
|
# Hand-ticked from here so the silence is MEASURED rather than
|
|
# waited out, and so the tree's own clock cannot race it.
|
|
director.set_process(false)
|
|
var ticks: int = 0
|
|
while not narration.is_speaking() and ticks < 1200:
|
|
director._process(1.0 / 60.0)
|
|
ticks += 1
|
|
if narration.is_speaking():
|
|
break
|
|
if director.beat_time > director.OPENING_SILENCE + 0.05:
|
|
return _fail("the clock passed the %.1fs silence at %.2fs and the guide still had not spoken" \
|
|
% [director.OPENING_SILENCE, director.beat_time])
|
|
if not narration.is_speaking():
|
|
return _fail("the guide never spoke at all")
|
|
if director.beat_time < director.OPENING_SILENCE:
|
|
return _fail("the guide spoke at %.2fs, inside the %.1fs silence" \
|
|
% [director.beat_time, director.OPENING_SILENCE])
|
|
print("the prologue flies for %.1fs before the guide calls in" % director.beat_time)
|
|
director.set_process(true)
|
|
|
|
# --- N. a dialog waits for the reader --------------------
|
|
var first_line: String = narration.label.text
|
|
if first_line.strip_edges() == "":
|
|
return _fail("the guide spoke with an empty banner")
|
|
# The beat clock as the line went up. Nothing may move it on while
|
|
# the line is unread (#128) - the tree is stopped, and this test
|
|
# stops hand-ticking the director for exactly that reason.
|
|
var clock_at_line: float = director.beat_time
|
|
# Ten seconds of clock, with nothing pressed. A timer-driven
|
|
# banner would have moved on several times over.
|
|
for i in range(600):
|
|
narration._process(1.0 / 60.0)
|
|
if narration.label.text != first_line:
|
|
return _fail("the line changed on its own after 10s - #127 asks for a click")
|
|
if not narration.visible:
|
|
return _fail("the banner closed itself")
|
|
# --- P. and the game is STOPPED behind it (#128) ---------
|
|
if not paused:
|
|
return _fail("the prologue opened on a line with the game still running")
|
|
if absf(director.beat_time - clock_at_line) > 0.01:
|
|
return _fail("the director's clock ran on %.2fs behind an unread line" \
|
|
% absf(director.beat_time - clock_at_line))
|
|
print("the line stopped the game: beat clock held at %.2fs" % director.beat_time)
|
|
|
|
var click: InputEventMouseButton = InputEventMouseButton.new()
|
|
click.button_index = MOUSE_BUTTON_LEFT
|
|
click.pressed = true
|
|
narration._unhandled_input(click)
|
|
if narration.label.text == first_line and narration.is_speaking():
|
|
return _fail("a click did not move the dialog on")
|
|
print("line held for 10s untouched, then a click moved it on")
|
|
|
|
# --- O. and it says who is talking -----------------------
|
|
if narration.speaker.strip_edges() == "":
|
|
return _fail("the dialog names no speaker")
|
|
if narration.speaker_label.text != narration.speaker:
|
|
return _fail("the speaker label does not show the speaker")
|
|
# It sits on the top border, at the left - above the text, and
|
|
# left of centre.
|
|
if narration.speaker_label.position.y >= narration.label.position.y:
|
|
return _fail("the speaker sits below the text, not on the top border")
|
|
if narration.speaker_label.position.x > narration.size.x * 0.5:
|
|
return _fail("the speaker is not at the left of the border")
|
|
print("speaker \"%s\" on the top-left border" % narration.speaker)
|
|
|
|
# --- K. nothing ends before its floor --------------------
|
|
# The floors are what #125 bought: every beat had a condition
|
|
# that could be satisfied while the guide was still talking.
|
|
for b in range(director.Beat.AFTERMATH + 1):
|
|
if director._floor_time() <= 0.0 and b != director.Beat.RUN:
|
|
return _fail("beat %d has no minimum length" % b)
|
|
if director.beat != director.Beat.BRIEF:
|
|
return _fail("the opening beat was over before the first line finished")
|
|
if not narration.is_speaking():
|
|
return _fail("the opening beat is silent, so waiting for it proves nothing")
|
|
# Every beat has room to run past its floor before its deadline,
|
|
# so a player who will not finish a beat's task is carried past
|
|
# it. Since #128 that deadline is game time and a dialog stops
|
|
# the clock, so it is a way past an unfinished BEAT, never past
|
|
# an unacknowledged line.
|
|
if director._deadline() <= director._floor_time():
|
|
return _fail("the opening beat has no deadline past its floor - a stalled player would be stuck")
|
|
print("opening beat still running at %.1fs, guide still talking" % director.beat_time)
|
|
|
|
# --- C. the convoy --------------------------------------
|
|
convoy_size = fleet.ships.size()
|
|
if convoy_size <= GameState.BASE_FLEET_SIZE:
|
|
return _fail("the convoy is %d vessels, no more than a new game's %d slots" \
|
|
% [convoy_size, GameState.BASE_FLEET_SIZE])
|
|
var holds: int = 0
|
|
var escorts: int = 0
|
|
for ship in fleet.ships:
|
|
if String(ship.vessel_type) == GameState.VESSEL_INTERCEPTOR:
|
|
escorts += 1
|
|
else:
|
|
holds += 1
|
|
if holds <= escorts:
|
|
return _fail("the convoy is %d holds to %d escorts - it should be mostly cargo" \
|
|
% [holds, escorts])
|
|
if escorts <= 0:
|
|
return _fail("the convoy has no escorts at all")
|
|
print("convoy: %d vessels, %d holds and %d escorts" % [convoy_size, holds, escorts])
|
|
|
|
# --- L. spread out, and the holds are on the inside -------
|
|
var spread: float = 0.0
|
|
var closest: float = 1.0e9
|
|
for a in fleet.ships:
|
|
for b in fleet.ships:
|
|
if a == b:
|
|
continue
|
|
spread = maxf(spread, a.position.distance_to(b.position))
|
|
closest = minf(closest, a.position.distance_to(b.position))
|
|
if spread < 400.0:
|
|
return _fail("the whole convoy fits in %.0fpx - that is a wedge, not a convoy" % spread)
|
|
if closest < 80.0:
|
|
return _fail("two convoy vessels are %.0fpx apart" % closest)
|
|
# Every hold must be further in than the furthest-out escort is,
|
|
# which is what "surrounded by the default vessels" means.
|
|
var escort_reach: float = 0.0
|
|
var hold_reach: float = 0.0
|
|
for ship in fleet.ships:
|
|
var from_centre: float = ship.position.length()
|
|
if String(ship.vessel_type) == GameState.VESSEL_INTERCEPTOR:
|
|
escort_reach = maxf(escort_reach, from_centre)
|
|
else:
|
|
hold_reach = maxf(hold_reach, from_centre)
|
|
if hold_reach >= escort_reach:
|
|
return _fail("a hold sits %.0fpx out, past the escorts at %.0f - they are not screened" \
|
|
% [hold_reach, escort_reach])
|
|
print("convoy %.0fpx across, holds within %.0f and escorts out at %.0f" \
|
|
% [spread, hold_reach, escort_reach])
|
|
|
|
# --- B. movement and nothing else -----------------------
|
|
for pressable in ["UI/BuildButton", "UI/MapButton", "UI/BuildActionButton",
|
|
"UI/BuildingActionButton", "UI/EnterAtmosphereButton",
|
|
"UI/TransferButton", "UI/HangarButton", "UI/LaserButton",
|
|
"UI/GasBombButton"]:
|
|
if not _hidden(pressable):
|
|
return _fail("%s is offered during the prologue" % pressable)
|
|
var joystick: Control = scene.get_node("UI/Joystick")
|
|
if not joystick.visible:
|
|
return _fail("the joystick is hidden - the player cannot even fly")
|
|
print("every action button is away; the joystick is not")
|
|
|
|
# --- D. the guide speaks --------------------------------
|
|
if not narration.is_speaking():
|
|
return _fail("the prologue opened without a word from the guide")
|
|
if narration.label.text.strip_edges() == "":
|
|
return _fail("the banner is up with nothing on it")
|
|
print("guide: \"%s\"" % narration.label.text)
|
|
|
|
# --- E. the ambush --------------------------------------
|
|
director._begin(director.Beat.AMBUSH)
|
|
if director.ambush.is_empty():
|
|
return _fail("the ambush beat spawned nothing")
|
|
for enemy in director.ambush:
|
|
if String(enemy.enemy_id) != "chaser":
|
|
return _fail("the ambush includes a %s - it is meant to be easy" % enemy.enemy_id)
|
|
if director._beat_complete():
|
|
return _fail("the ambush counts as cleared while its raiders are alive")
|
|
for enemy in director.ambush:
|
|
enemy.queue_free()
|
|
# #128: the beat announced itself, and an unacknowledged line
|
|
# stops the tree - including the director, whose advance is what
|
|
# stage 2 is waiting for. Acknowledged here, the way a player
|
|
# would, so the clock below is running.
|
|
narration.clear()
|
|
if paused:
|
|
return _fail("clearing the last line left the game stopped")
|
|
elapsed = 0.0
|
|
stage = 2
|
|
return false
|
|
2:
|
|
# Past the ambush beat's own floor (#125): clearing it is what
|
|
# ENDS it, but no beat may end before its floor, so watching for
|
|
# the advance means waiting that long.
|
|
if elapsed < director._floor_time() + 0.5:
|
|
return false
|
|
# The director advances itself, so the evidence that clearing the
|
|
# ambush ended the beat is that it has MOVED ON - asking
|
|
# _beat_complete() again would be asking about a beat it left.
|
|
if director.beat <= director.Beat.AMBUSH:
|
|
return _fail("killing every raider did not end the ambush beat")
|
|
print("%d raiders, all chasers, and clearing them ends the beat" % director.AMBUSH_COUNT)
|
|
|
|
# --- F. the belt ----------------------------------------
|
|
director._begin(director.Beat.GAUNTLET)
|
|
# Only the rocks still standing: the convoy flies into them, and
|
|
# a rock it has already broken is not evidence about the belt.
|
|
var standing: Array = []
|
|
for rock in director.rocks:
|
|
if is_instance_valid(rock) and not rock.is_queued_for_deletion():
|
|
standing.append(rock)
|
|
if standing.size() < 40:
|
|
return _fail("the \"pretty dense\" belt is %d rocks" % standing.size())
|
|
var ahead: int = 0
|
|
for rock in standing:
|
|
if rock.global_position.y < fleet.get_centroid().y:
|
|
ahead += 1
|
|
if ahead < standing.size() / 2:
|
|
return _fail("only %d of %d rocks are on the lane ahead" \
|
|
% [ahead, standing.size()])
|
|
print("belt: %d rocks, %d of them across the lane ahead" % [standing.size(), ahead])
|
|
|
|
# --- R. #139: and none of it appears on screen ------------
|
|
# The belt used to be laid out with its own arithmetic, which put
|
|
# its near edge within a screen of the fleet - so rocks popped
|
|
# into existence in view. It goes through the asteroid field's
|
|
# own keep-out now, the same one #126 gave the rest of the game.
|
|
var field: Node2D = scene.get_node("AsteroidField")
|
|
var keep_out: Rect2 = field.keep_out_rect(fleet.get_centroid())
|
|
var visible_rect: Rect2 = ViewUtils.get_visible_world_rect(field)
|
|
if visible_rect.size.x > 1.0 and not keep_out.encloses(visible_rect):
|
|
return _fail("the belt's keep-out %s does not cover the screen %s" \
|
|
% [keep_out, visible_rect])
|
|
var nearest: float = 1.0e9
|
|
for rock in standing:
|
|
if keep_out.has_point(rock.global_position):
|
|
return _fail("a belt rock was laid down at %s, inside the view %s" \
|
|
% [rock.global_position, keep_out])
|
|
nearest = minf(nearest, absf(rock.global_position.y - fleet.get_centroid().y))
|
|
print("no belt rock inside the keep-out %.0fx%.0f; nearest is %.0f ahead" \
|
|
% [keep_out.size.x, keep_out.size.y, nearest])
|
|
|
|
# --- G. the breach takes the wheel ----------------------
|
|
director._begin(director.Beat.BREACH)
|
|
if not game_state.intro_locks_movement:
|
|
return _fail("the breach beat left the player in control")
|
|
# GameController.gd is the script on Main itself, not a child.
|
|
var before: Vector2 = fleet.fleet_target
|
|
scene._process(0.1)
|
|
if fleet.fleet_target != before:
|
|
return _fail("player steering still moved the fleet during the breach")
|
|
print("the breach has the wheel: steering input moves nothing")
|
|
|
|
# --- M. the breach is a black hole ----------------------
|
|
var hole: Node2D = director.breach
|
|
if hole == null or not is_instance_valid(hole):
|
|
return _fail("the breach beat opened no black hole")
|
|
if hole.horizon_radius() > 1.0:
|
|
return _fail("the hole was already open the moment the beat began")
|
|
director._drive_breach(0.5)
|
|
var half_open: float = hole.horizon_radius()
|
|
director._drive_breach(1.0)
|
|
if hole.horizon_radius() <= half_open:
|
|
return _fail("the hole does not grow - %.1f at half, %.1f at full" \
|
|
% [half_open, hole.horizon_radius()])
|
|
if hole.disc_radius() <= hole.horizon_radius():
|
|
return _fail("the accretion disc is inside the horizon")
|
|
var lens: ColorRect = scene.get_node("UI/BreachOverlay")
|
|
if not lens.visible:
|
|
return _fail("the screen-space lens never came up")
|
|
if lens.material == null or lens.material.shader == null:
|
|
return _fail("the lens has no shader on it")
|
|
print("breach: horizon %.0f -> %.0f, disc %.0f, lens up" \
|
|
% [half_open, hole.horizon_radius(), hole.disc_radius()])
|
|
|
|
# --- H. the vortex --------------------------------------
|
|
director._begin(director.Beat.VORTEX)
|
|
var fade: ColorRect = scene.get_node("FadeOverlay/FadeRect")
|
|
var viewport_size: Vector2 = scene.get_viewport().get_visible_rect().size
|
|
if fade.size.distance_to(viewport_size) > 1.0:
|
|
return _fail("whiteout is %s, viewport is %s" % [fade.size, viewport_size])
|
|
if fade.color.r < 0.99 or fade.color.g < 0.99 or fade.color.b < 0.99:
|
|
return _fail("the vortex transition is not white")
|
|
if fade.color.a > 0.01:
|
|
return _fail("the whiteout began fully opaque instead of fading in")
|
|
elapsed = 0.0
|
|
stage = 3
|
|
return false
|
|
3:
|
|
# Left to the director's own clock rather than ticked by hand:
|
|
# the vortex is a timed animation and driving it twice would not
|
|
# be the thing that ships.
|
|
if not whiteout_mid_checked and elapsed >= director.WHITEOUT_IN_SECONDS * 0.5:
|
|
whiteout_mid_checked = true
|
|
var mid_fade: ColorRect = scene.get_node("FadeOverlay/FadeRect")
|
|
if mid_fade.color.a <= 0.05 or mid_fade.color.a >= 0.95:
|
|
return _fail("whiteout did not gradually fade in (alpha %.2f)" % mid_fade.color.a)
|
|
if director.whiteout_teleported:
|
|
return _fail("fleet teleported before the screen was fully white")
|
|
if fleet.get_formation_origin().distance_to(director.DEFAULT_SPAWN) <= 1.0:
|
|
return _fail("fleet reached default spawn before full white")
|
|
if elapsed < 4.2:
|
|
return false
|
|
var alive: int = 0
|
|
for ship in fleet.ships:
|
|
if is_instance_valid(ship) and not ship.is_queued_for_deletion():
|
|
alive += 1
|
|
if alive != 1:
|
|
return _fail("the vortex left %d vessels, expected 1" % alive)
|
|
if String(fleet.ships[0].vessel_type) != GameState.VESSEL_INTERCEPTOR:
|
|
return _fail("the survivor is a %s, not the default interceptor" \
|
|
% fleet.ships[0].vessel_type)
|
|
print("the vortex took %d vessels and left one interceptor" % (convoy_size - 1))
|
|
if not director.whiteout_teleported:
|
|
return _fail("the screen reached white without teleporting the fleet")
|
|
if fleet.get_formation_origin().distance_to(director.DEFAULT_SPAWN) > 1.0:
|
|
return _fail("whiteout revealed the fleet at %s instead of default spawn %s" \
|
|
% [fleet.get_formation_origin(), director.DEFAULT_SPAWN])
|
|
var revealing: ColorRect = scene.get_node("FadeOverlay/FadeRect")
|
|
if revealing.color.a >= 0.99:
|
|
return _fail("screen stayed fully white after teleport")
|
|
print("whiteout hid the teleport to default spawn, then began revealing it")
|
|
|
|
# --- I. the handover ------------------------------------
|
|
director.finish()
|
|
elapsed = 0.0
|
|
stage = 4
|
|
return false
|
|
4:
|
|
if elapsed < 0.3:
|
|
return false
|
|
if game_state.intro_active or game_state.intro_locks_movement:
|
|
return _fail("the prologue finished without clearing its own flags")
|
|
if game_state.intro_fleet_size != 0:
|
|
return _fail("the convoy's fleet-size override outlived the prologue")
|
|
if game_state.max_fleet_size() != GameState.BASE_FLEET_SIZE:
|
|
return _fail("the fleet still holds %d slots after the prologue" \
|
|
% game_state.max_fleet_size())
|
|
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
|
|
return _fail("the game starts with %d vessels, expected %d" \
|
|
% [fleet.ships.size(), GameState.STARTING_SHIP_COUNT])
|
|
if not scene.get_node("EnemySpawner").is_processing():
|
|
return _fail("waves are still switched off after the prologue")
|
|
if not scene.get_node("AsteroidField").is_processing():
|
|
return _fail("the asteroid field is still switched off after the prologue")
|
|
scene.get_node("BuildController")._process(0.1)
|
|
if _hidden("UI/BuildButton"):
|
|
return _fail("the build button never came back")
|
|
# #133: the guide has nothing more to say - but the quest chain
|
|
# starts exactly here, so what is on the banner now is the first
|
|
# objective rather than any leftover of the prologue.
|
|
var quest: Dictionary = game_state.active_quest()
|
|
if quest.is_empty():
|
|
return _fail("the handover left no first quest")
|
|
if narration.is_speaking() and narration.current != String(quest.brief):
|
|
return _fail("the banner is still on \"%s\" after the handover" \
|
|
% narration.current)
|
|
print("the prologue hands over to the first quest: \"%s\"" % quest.title)
|
|
if scene.get_node("UI/BreachOverlay").visible:
|
|
return _fail("the breach lens is still over the screen after the handover")
|
|
if is_instance_valid(director) and director.breach != null:
|
|
return _fail("the black hole outlived the prologue")
|
|
var finished_fade: ColorRect = scene.get_node("FadeOverlay/FadeRect")
|
|
if finished_fade.color.a > 0.001 or finished_fade.color.r > 0.001:
|
|
return _fail("intro left the shared fade overlay tinted white")
|
|
print("handover: flags clear, 1 interceptor, waves and rocks running, buttons back")
|
|
_drop_scene()
|
|
elapsed = 0.0
|
|
stage = 5
|
|
return false
|
|
5:
|
|
if elapsed < 0.3:
|
|
return false
|
|
# --- A (second half). a resumed game gets no prologue ----
|
|
game_state.intro_active = false
|
|
_load_scene()
|
|
elapsed = 0.0
|
|
stage = 6
|
|
return false
|
|
6:
|
|
if elapsed < 0.4:
|
|
return false
|
|
if director != null and is_instance_valid(director) and director.running:
|
|
return _fail("the prologue ran again on a game that is past it")
|
|
if fleet.ships.size() > GameState.BASE_FLEET_SIZE:
|
|
return _fail("a resumed game opened with a convoy")
|
|
print("a game already past the prologue does not replay it")
|
|
_drop_scene()
|
|
elapsed = 0.0
|
|
stage = 7
|
|
return false
|
|
7:
|
|
if elapsed < 0.3:
|
|
return false
|
|
# --- J. skipping lands where playing it through does -----
|
|
game_state.reset_new_game()
|
|
_load_scene()
|
|
elapsed = 0.0
|
|
stage = 8
|
|
return false
|
|
8:
|
|
if elapsed < 0.4:
|
|
return false
|
|
# Skipped mid-prologue, with props on the board, so the check
|
|
# below is about clearing them rather than about an empty stage.
|
|
director._begin(director.Beat.AMBUSH)
|
|
director._begin(director.Beat.GAUNTLET)
|
|
skipped_props = director.ambush + director.rocks
|
|
if skipped_props.size() < 40:
|
|
return _fail("nothing was on the board to skip past")
|
|
director.skip()
|
|
elapsed = 0.0
|
|
stage = 9
|
|
return false
|
|
9:
|
|
if elapsed < 0.3:
|
|
return false
|
|
if game_state.intro_active or game_state.intro_locks_movement:
|
|
return _fail("skipping left the prologue's flags set")
|
|
if game_state.max_fleet_size() != GameState.BASE_FLEET_SIZE:
|
|
return _fail("skipping left the convoy's fleet size behind")
|
|
if fleet.ships.size() != GameState.STARTING_SHIP_COUNT:
|
|
return _fail("skipping left %d vessels flying" % fleet.ships.size())
|
|
for prop in skipped_props:
|
|
if is_instance_valid(prop) and not prop.is_queued_for_deletion():
|
|
return _fail("skipping left the prologue's own props on the board")
|
|
print("skipping clears %d props and lands where playing it through does" \
|
|
% skipped_props.size())
|
|
|
|
# #161: the prologue widens the formation into a freight convoy
|
|
# by writing over game_state.formation. Finishing has to put the
|
|
# fleet's own wedge back, or the player flies the convoy's
|
|
# 600-unit spread for the rest of the game - a shape nobody chose,
|
|
# which reads as the fleet having drifted apart.
|
|
for i in range(mini(game_state.formation.size(),
|
|
game_state.DEFAULT_FORMATION.size())):
|
|
if not game_state.formation[i].is_equal_approx(game_state.DEFAULT_FORMATION[i]):
|
|
print("TEST_INTRO_SCENE: FAIL (slot %d is %s after the prologue, expected the default %s)" \
|
|
% [i, game_state.formation[i], game_state.DEFAULT_FORMATION[i]])
|
|
quit(1)
|
|
return true
|
|
print("formation restored to the fleet's own wedge")
|
|
|
|
print("TEST_INTRO_SCENE: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|