extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_research_bar_columns.gd # Forgejo #95, both halves of it. # # The research menu's resource table printed BANKED, FLEET and LOCAL, and # its LOCAL column was always a copy of BANKED. That was not a coincidence: # the research action lives on the mothership, the mothership also carries # the "bank" capability, and GameState.building_inventory() redirects any # bank-capable building to bank_inventory itself. The two columns were the # same store read twice. # # The panel also wore a "Research" heading, which named a screen you can # only arrive at by pressing the research button, and charged the tree # canvas 128px for the privilege. # # Covers: # A. the research bar shows BANKED and FLEET and hides LOCAL # B. the two remaining columns tile the full width - dropping one must not # leave a third of the table blank # C. a building that is NOT the bank still gets its LOCAL column, so the # fix removes a duplicate rather than the feature # D. no heading above the table, and the tree canvas starts right under it const GAP_TOLERANCE: float = 48.0 var game_state: Node var scene: Node var controller: Node var skill_menu: Control var craft_menu: Control 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.reset_progression() game_state.reset_formation() game_state.rocks = 500 scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) controller = scene.get_node("BuildController") skill_menu = scene.get_node("UI/SkillMenu") craft_menu = scene.get_node("UI/CraftMenu") func _building(building_type: String) -> Node2D: for building in scene.get_node("BuildingField").spawned: if is_instance_valid(building) and building.building_type == building_type: return building return null func _shown_headers(bar: Control) -> Array: var shown: Array = [] for label in bar._header_labels: if label.visible and label.text != "": shown.append(label.text) return shown func _process(delta: float) -> bool: elapsed += delta if stage == 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) var field: Node = scene.get_node("BuildingField") field.place(GameState.BUILDING_MOTHERSHIP, Vector2(400.0, 0.0)) field.place(GameState.BUILDING_SHIP_CRAFTER, Vector2(-400.0, 0.0)) elapsed = 0.0 stage = 1 return false if stage != 1 or elapsed < 0.2: return false stage = 2 var mothership: Node2D = _building(GameState.BUILDING_MOTHERSHIP) if mothership == null: return _fail("no mothership was placed") controller.current_building = mothership controller._on_action_pressed() if not skill_menu.visible: return _fail("the mothership did not open the research menu") var bar: Control = scene.get_node("UI/SkillMenu/Bar") # One frame of _process is what tells the bar its building changed. bar._process(0.0) # --- A. LOCAL is gone from the research table ---------------------- var headers: Array = _shown_headers(bar) if headers != ["BANKED", "FLEET"]: return _fail("the research table shows %s, not just BANKED and FLEET" % str(headers)) for resource_type in game_state.resource_types(): var row: Dictionary = bar._rows[resource_type] if row.local.visible: return _fail("the LOCAL value for %s is still drawn" % resource_type) if not row.bank.visible or not row.fleet.visible: return _fail("dropping LOCAL also hid a column that is not redundant") print("the research table drops the LOCAL copy of BANKED") # --- B. the survivors share the freed width ------------------------- var first_row: Dictionary = bar._rows[game_state.resource_types()[0]] var covered: float = (first_row.fleet.position.x + first_row.fleet.size.x) \ - first_row.bank.position.x var expected: float = bar.size.x - bar.size.x * 0.25 if not is_equal_approx(covered, expected): return _fail("the two columns cover %.0fpx of %.0fpx - the dropped column left a hole" \ % [covered, expected]) print("BANKED and FLEET share the whole %.0fpx of value width" % covered) # --- C. a non-bank building keeps its LOCAL column ------------------ skill_menu.visible = false var crafter: Node2D = _building(GameState.BUILDING_SHIP_CRAFTER) if crafter == null: return _fail("no ship crafter was placed") controller.current_building = crafter controller._on_action_pressed() if not craft_menu.visible: return _fail("the crafter did not open the craft menu") var craft_bar: Control = scene.get_node("UI/CraftMenu/Bar") craft_bar._process(0.0) var craft_headers: Array = _shown_headers(craft_bar) if craft_headers != ["BANKED", "FLEET", "LOCAL"]: return _fail("the crafter table shows %s - its LOCAL stock is not the bank" \ % str(craft_headers)) print("a building that is not the bank keeps LOCAL") # --- D. no heading, and the tree starts under the table ------------- for child in skill_menu.get_children(): if child is Label and String(child.text).strip_edges() == "Research": return _fail("the \"Research\" heading is still in the panel") var tree_view: Control = scene.get_node("UI/SkillMenu/Tree") var bar_bottom: float = bar.position.y + bar.size.y var head_gap: float = tree_view.position.y - bar_bottom if bar.position.y > GAP_TOLERANCE: return _fail("%.0fpx of empty panel above the table - the heading's hole" % bar.position.y) if head_gap < 0.0: return _fail("the tree canvas overlaps the table above it") if head_gap > GAP_TOLERANCE: return _fail("%.0fpx of unused panel between the table and the tree" % head_gap) print("no heading: the table sits %.0fpx down and the tree is %.0fpx tall" \ % [bar.position.y, tree_view.size.y]) print("TEST_RESEARCH_BAR_COLUMNS: PASS") quit(0) return true func _fail(message: String) -> bool: print("TEST_RESEARCH_BAR_COLUMNS: FAIL (%s)" % message) quit(1) return true