extends SceneTree # Forgejo #227: a resumed run comes back at its own mothership. # # Loading a save used to fall through to the brand-new-game branch in # PlanetAtmosphereController, which spawns STARTING_SHIP_COUNT interceptors # at the world origin. So a resume stranded the player in empty space with # the wrong ships, while their base sat wherever they had left it - which on # a charted map is thousands of units away. # # Covers: # A. resuming puts the fleet at the mothership, not at the origin # B. and crews it with the ships the save actually had # C. with no mothership standing, it falls back to where the save was made # D. a NEW game is untouched - still the starting fleet at the origin const HOME: Vector2 = Vector2(7400.0, -3100.0) const AWAY: Vector2 = Vector2(-5200.0, 900.0) var game_state: Node var scene: Node var elapsed: float = 0.0 var stage: int = 0 var expected_crew: int = 0 func _initialize() -> void: game_state = root.get_node("GameState") SaveGame.clear() func _fresh_state() -> void: game_state.buildings.clear() game_state.extractors.clear() game_state.reset_progression() game_state.reset_new_game() game_state.reset_quests() game_state.intro_active = false func _load_scene() -> void: if scene != null and is_instance_valid(scene): scene.free() scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene func _process(delta: float) -> bool: elapsed += delta match stage: 0: if elapsed < 0.3: return false # A run with a base at HOME and the fleet parked far from it. _fresh_state() game_state.add_building(GameState.BUILDING_MOTHERSHIP, HOME) # Capacity for the crew below. Since #219 the fleet holds # BASE_FLEET_SIZE (1) plus one berth per supplied fleet office, # so a save with three ships flying is a save that had two # offices standing - without them spawn_ships clamps to 1 and # this would assert nothing. game_state.add_building(GameState.BUILDING_FLEET_OFFICE, HOME + Vector2(400.0, 0.0)) game_state.add_building(GameState.BUILDING_FLEET_OFFICE, HOME + Vector2(800.0, 0.0)) game_state.fleet_vessel_types = PackedStringArray([ GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR]) game_state.fleet_position = AWAY expected_crew = game_state.fleet_vessel_types.size() if game_state.max_fleet_size() < expected_crew: return _fail("the fleet cannot hold %d, so a resumed crew would prove nothing (cap %d)" \ % [expected_crew, game_state.max_fleet_size()]) if not SaveGame.save(game_state): return _fail("could not write the save this test resumes from") _fresh_state() var result: Dictionary = SaveGame.load_into(game_state) if not bool(result.get("ok", false)): return _fail("the save would not load: %s" % result.get("reason", "")) game_state.resumed_from_save = true _load_scene() elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.6: return false var fleet: Node2D = scene.get_node("Fleet") # --- A. at the mothership, not the origin ------------------- var at: Vector2 = fleet.get_centroid() if at.distance_to(HOME) > 400.0: return _fail("resumed at %s, but the mothership is at %s" % [at, HOME]) if at.distance_to(Vector2.ZERO) < 400.0: return _fail("resumed at the world origin, which is the new-game behaviour") print("resumed at the mothership (%.0f units from it)" % at.distance_to(HOME)) # --- B. with the crew the save had -------------------------- if fleet.ships.size() != expected_crew: return _fail("resumed with %d ships, the save had %d" \ % [fleet.ships.size(), expected_crew]) print("resumed with the saved crew of %d" % fleet.ships.size()) if game_state.resumed_from_save: return _fail("the resume flag was left raised, so the next scene would re-use it") # --- C. no mothership: fall back to where the save was made -- _fresh_state() game_state.fleet_vessel_types = PackedStringArray([GameState.VESSEL_INTERCEPTOR]) game_state.fleet_position = AWAY game_state.resumed_from_save = true _load_scene() elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.6: return false var fleet2: Node2D = scene.get_node("Fleet") var at2: Vector2 = fleet2.get_centroid() if at2.distance_to(AWAY) > 400.0: return _fail("with no mothership, resumed at %s rather than the saved %s" % [at2, AWAY]) print("with no mothership, resumed where the save was written") # --- D. a new game is untouched ----------------------------- _fresh_state() game_state.resumed_from_save = false _load_scene() elapsed = 0.0 stage = 3 return false 3: if elapsed < 0.6: return false var fleet3: Node2D = scene.get_node("Fleet") if fleet3.get_centroid().distance_to(Vector2.ZERO) > 400.0: return _fail("a new game no longer starts at the origin (%s)" % fleet3.get_centroid()) if fleet3.ships.size() != GameState.STARTING_SHIP_COUNT: return _fail("a new game starts with %d ships, expected %d" \ % [fleet3.ships.size(), GameState.STARTING_SHIP_COUNT]) print("a new game still starts with %d ships at the origin" % fleet3.ships.size()) SaveGame.clear() print("TEST_RESUME_SPAWN: PASS") quit(0) return true return false func _fail(message: String) -> bool: print("TEST_RESUME_SPAWN: FAIL (%s)" % message) quit(1) return true