extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_outpost_building.gd # Forgejo #102 (#97d): "A new building should be available, pretty early in # the tech tree, to create outposts." # # Covers: # A. the outpost is in the building catalog, with its own visual, its own # icon and its own map colour - not a reused one # B. a skill node unlocks it, and it is EARLY: shallower than the deep # branches, and reachable for less than the mid-game structures cost # C. it is hidden from the build menu until that node is researched, and # appears afterwards, like every other gated structure # D. it can be placed, it stands up in the world, and it has enough health # to be worth attacking - it exists to be shot at const MAX_EARLY_DEPTH: int = 2 var game_state: Node var scene: Node var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: # GameState is an autoload: a bare GameState. does not # compile in a SceneTree script, so the node is fetched by name instead. game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.transporters.clear() game_state.reset_progression() game_state.reset_waves() game_state.rocks = 5000 func _fail(message: String) -> bool: print("TEST_OUTPOST_BUILDING: FAIL (%s)" % message) quit(1) return true func _catalog_cases() -> bool: var definitions: Script = load("res://buildings/BuildingDefinitions.gd") var outpost: Resource = definitions.get_definition(GameState.BUILDING_OUTPOST) if outpost == null: return _fail("no \"outpost\" definition in the catalog") if outpost.visual_scene == null: return _fail("the outpost has no visual - it would be an invisible structure") if outpost.menu_button_name == "": return _fail("the outpost declares no build-menu entry, so it could never be built") # --- A. its own look ------------------------------------------------- for other in definitions.all(): if other.definition_id == GameState.BUILDING_OUTPOST: continue if other.menu_icon == outpost.menu_icon: return _fail("the outpost shares its icon with the %s" % other.definition_id) if other.visual_scene == outpost.visual_scene: return _fail("the outpost shares its visual with the %s" % other.definition_id) if other.map_color.is_equal_approx(outpost.map_color): return _fail("the outpost shares its map colour with the %s" % other.definition_id) print("the outpost has its own icon, visual and map colour") # --- B. researched, and early ---------------------------------------- var node_name: String = game_state.research_for_building(GameState.BUILDING_OUTPOST) if node_name == "": return _fail("nothing in the skill tree unlocks the outpost") var node_id: String = "" for node in GameState.SKILL_TREE: if node.get("unlocks", "") == GameState.BUILDING_OUTPOST: node_id = String(node.id) var depth: int = game_state.skill_depth(node_id) if depth > MAX_EARLY_DEPTH: return _fail("\"%s\" sits %d prerequisites deep - that is not early" % [node_name, depth]) var cost: int = int(game_state.skill_node(node_id).cost) for node in GameState.SKILL_TREE: if String(node.get("unlocks", "")) in [GameState.BUILDING_TRANSPORT_HUB, GameState.BUILDING_SHIP_LAB, GameState.BUILDING_GAS_BURNER]: if int(node.cost) <= cost: return _fail("\"%s\" costs %d, no less than the mid-game %s at %d" \ % [node_name, cost, node.name, node.cost]) print("\"%s\" is %d deep and costs %d ore - earlier and cheaper than the mid-game structures" \ % [node_name, depth, cost]) # --- C. gated until researched --------------------------------------- if game_state.is_building_unlocked(GameState.BUILDING_OUTPOST): return _fail("the outpost is buildable before its node is researched") game_state.unlocked.append(node_id) if not game_state.is_building_unlocked(GameState.BUILDING_OUTPOST): return _fail("researching \"%s\" did not unlock the outpost" % node_name) print("hidden until \"%s\" is researched, then offered" % node_name) return false func _process(delta: float) -> bool: elapsed += delta match stage: 0: if _catalog_cases(): return true scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) # --- C (continued). the menu shows it ------------------------ var controller: Node = scene.get_node("BuildController") var button: Control = scene.get_node("UI/BuildMenu/OutpostButton") controller._layout_build_menu() if not button.visible: return _fail("the outpost is researched but its build-menu button is hidden") scene.get_node("BuildingField").place(GameState.BUILDING_OUTPOST, Vector2(500.0, 0.0)) elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.2: return false # --- D. it stands up, and can be shot at --------------------- var node: Node2D = null for b in scene.get_node("BuildingField").spawned: if is_instance_valid(b) and b.building_type == GameState.BUILDING_OUTPOST: node = b if node == null: return _fail("placing an outpost produced no node") if node.get_node_or_null("VisualHost").get_child_count() == 0: return _fail("the outpost node has no visual under it") if node.get_node_or_null("Hitbox") == null: return _fail("the outpost has no hitbox - a wave could not touch it") var hp: int = game_state.building_hp(node.building_id) var crafter_hp: int = game_state.building_max_hp(GameState.BUILDING_SHIP_CRAFTER) if hp < crafter_hp: return _fail("the outpost has %d hp, less than a crafter's %d - it is meant to take hits" \ % [hp, crafter_hp]) print("an outpost stands at %d hp, against a crafter's %d" % [hp, crafter_hp]) print("TEST_OUTPOST_BUILDING: PASS") quit(0) return true return false