extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_skill_tree.gd # The mothership's research tree (Forgejo issue #4). It gates which # structures may be built at all, and carries permanent upgrades that have # no building of their own. # # Verifies: # 1. from a fresh start only the mothership is buildable - the others are # greyed, and pressing one explains which research opens it rather than # silently failing # 2. a locked option cannot be built even if the confirm is forced # 3. research costs rocks, needs its prerequisite, and unlocks its building # 4. the mothership's action button opens the tree (it used to have no # action at all) # 5. fleet capacity upgrades raise the cap, and the extra vessel can # actually be crafted afterwards var scene: Node var game_state: Node var controller: Node var fleet: Node var field: Node var popup: Control var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") game_state.rocks = 500 game_state.buildings.clear() game_state.extractors.clear() game_state.reset_progression() game_state.reset_formation() scene = load("res://Main.tscn").instantiate() root.add_child(scene) controller = scene.get_node("BuildController") fleet = scene.get_node("Fleet") field = scene.get_node("BuildingField") popup = scene.get_node("UI/BuildMenu/InfoPopup") func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) field.place(GameState.BUILDING_MOTHERSHIP, Vector2(400.0, 0.0)) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.2: return false # --- everything but the mothership starts locked -------------- var mothership_free: bool = game_state.is_building_unlocked(GameState.BUILDING_MOTHERSHIP) var crafter_locked: bool = not game_state.is_building_unlocked(GameState.BUILDING_SHIP_CRAFTER) print("fresh start: mothership buildable=%s crafter locked=%s" % [ mothership_free, crafter_locked ]) if not (mothership_free and crafter_locked): print("TEST_SKILL_TREE: FAIL (wrong things buildable at the start)") quit(1) return true controller._on_build_pressed() var greyed: bool = not scene.get_node("UI/BuildMenu/CrafterButton").enabled controller._on_place_crafter() var explains: bool = popup.visible \ and "research" in popup.get_node("Panel/MessageLabel").text.to_lower() var offered: bool = popup.get_node("Panel/BuildButton").visible print("locked crafter: greyed=%s explains=%s confirm_offered=%s" % [ greyed, explains, offered ]) if not (greyed and explains) or offered: print("TEST_SKILL_TREE: FAIL (locked option did not explain itself)") quit(1) return true # Forcing the confirm must still build nothing. var before: int = game_state.buildings.size() controller.pending_build_type = GameState.BUILDING_SHIP_CRAFTER controller._confirm_build() if game_state.buildings.size() != before: print("TEST_SKILL_TREE: FAIL (built a locked structure)") quit(1) return true controller._close_info() # --- the mothership opens the tree ---------------------------- var mothership: Node2D = null for b in field.spawned: if is_instance_valid(b) and b.building_type == GameState.BUILDING_MOTHERSHIP: mothership = b controller.current_building = mothership controller._on_action_pressed() var tree_open: bool = scene.get_node("UI/SkillMenu").visible print("mothership action opens research: %s" % tree_open) if not tree_open: print("TEST_SKILL_TREE: FAIL (mothership has no research action)") quit(1) return true # --- prerequisites -------------------------------------------- if game_state.can_unlock("wing"): print("TEST_SKILL_TREE: FAIL (deep node unlockable with no prerequisite)") quit(1) return true # --- research costs rocks and unlocks its building ------------ var rocks_before: int = game_state.rocks var cost: int = int(game_state.skill_node("shipyards").cost) var ok: bool = game_state.unlock("shipyards") var charged: bool = game_state.rocks == rocks_before - cost var now_buildable: bool = game_state.is_building_unlocked(GameState.BUILDING_SHIP_CRAFTER) print("research shipyards: ok=%s rocks %d->%d charged=%s crafter unlocked=%s" % [ ok, rocks_before, game_state.rocks, charged, now_buildable ]) if not (ok and charged and now_buildable): print("TEST_SKILL_TREE: FAIL (research did not unlock its building)") quit(1) return true # The menu must show it as buildable now, without reopening. controller._on_build_pressed() if not scene.get_node("UI/BuildMenu/CrafterButton").enabled: print("TEST_SKILL_TREE: FAIL (build menu still greys an unlocked option)") quit(1) return true controller._close_info() scene.get_node("UI/BuildMenu").visible = false # --- fleet capacity ------------------------------------------- var base_cap: int = game_state.max_fleet_size() game_state.unlock("coordination") var wing_ok: bool = game_state.unlock("wing") var raised_cap: int = game_state.max_fleet_size() print("fleet cap %d -> %d after Wing Doctrine (unlocked=%s)" % [ base_cap, raised_cap, wing_ok ]) if not (wing_ok and raised_cap == base_cap + 1): print("TEST_SKILL_TREE: FAIL (capacity upgrade did not raise the cap)") quit(1) return true # And the extra vessel must actually be craftable. fleet.spawn_ships(base_cap, Vector2.ZERO) var added: bool = fleet.add_ship() print("fleet %d ships, add one more -> %s (now %d)" % [ base_cap, added, fleet.ships.size() ]) if not (added and fleet.ships.size() == base_cap + 1): print("TEST_SKILL_TREE: FAIL (could not crew the extra slot)") quit(1) return true if fleet.add_ship(): print("TEST_SKILL_TREE: FAIL (cap not enforced beyond the upgrade)") quit(1) return true print("TEST_SKILL_TREE: PASS") quit(0) return true return false