extends SceneTree # Forgejo #230: the upkeep menu shows what the fleet costs IN TOTAL, laid out # as a table. # # It listed each berth and never added them up, so the bill had to be done in # the player's head. The rows were also one Label padded with %-16s, which # lines up only in a monospace font - the theme's is CarroisGothicSC, which # is proportional, so the "columns" never actually aligned. Worse, that Label # had autowrap on, so a long row folded and the table lost its shape. # # Covers: # A. a row per paying berth, in a real grid # B. a TOTAL row, and it equals the sum of the rows # C. the headline states both the per-charge bill and the per-minute rate # D. an unpaid berth is marked # E. with nothing to charge, the table gives way to an explanation # F. the sheet is not rebuilt on every frame - the menu refreshes at frame # rate and these numbers move every 25 seconds var game_state: Node var scene: Node var menu: Control var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") 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 scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene menu = scene.get_node("UI/FleetUpkeepMenu") func _rows() -> Array: # Grid cells come out in reading order; COLUMNS wide makes one row. var width: int = menu.sheet.columns var cells: Array = [] for child in menu.sheet.get_children(): cells.append(String((child as Label).text)) var rows: Array = [] for i in range(0, cells.size(), width): rows.append(cells.slice(i, i + width)) return rows func _process(delta: float) -> bool: elapsed += delta if elapsed < 0.4: return false # --- E. nothing to charge ------------------------------------------- menu.open_menu() menu.refresh() if not menu.empty_label.visible or menu.sheet.visible: return _fail("with no berths the table is shown instead of an explanation") if not menu.total_label.text.contains("NOTHING"): return _fail("with no berths the headline reads '%s'" % menu.total_label.text) print("with nothing to charge: '%s'" % menu.total_label.text) # Two offices, two crewed berths - the second costs more than the first. game_state.add_building(GameState.BUILDING_FLEET_OFFICE, Vector2.ZERO) game_state.add_building(GameState.BUILDING_FLEET_OFFICE, Vector2(600.0, 0.0)) game_state.fleet_vessel_types = PackedStringArray([ GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR]) menu.refresh() var breakdown: Dictionary = game_state.fleet_upkeep_breakdown() if breakdown.berths.size() < 2: return _fail("only %d berth(s) are being charged - that proves nothing about a total" \ % breakdown.berths.size()) # --- A. a row per berth, plus header and total ------------------------ var rows: Array = _rows() if not menu.sheet.visible or menu.empty_label.visible: return _fail("the table is hidden while berths are being charged") if rows.size() != breakdown.berths.size() + 2: return _fail("%d rows for %d berths - expected a header and a total too" \ % [rows.size(), breakdown.berths.size()]) if String(rows[0][0]) != "BERTH": return _fail("the first row is not a header: %s" % str(rows[0])) print("%d berths -> %d rows (header, %d berths, total)" \ % [breakdown.berths.size(), rows.size(), breakdown.berths.size()]) # --- B. and the total is the sum of the rows -------------------------- var total_row: Array = rows[rows.size() - 1] if String(total_row[0]) != "TOTAL": return _fail("the last row is '%s', not a total" % total_row[0]) var summed: int = 0 for berth in breakdown.berths: for resource_type in berth.costs: summed += int(berth.costs[resource_type]) var shown: int = 0 for word in String(total_row[2]).split(" "): if word.is_valid_int(): shown += int(word) if shown != summed: return _fail("the total row reads '%s' (%d), but the berths add to %d" \ % [total_row[2], shown, summed]) if summed <= 0: return _fail("the berths cost nothing, so a total proves nothing") print("total row '%s' matches the %d the berths add to" % [total_row[2], summed]) # --- C. the headline carries both figures ----------------------------- var headline: String = menu.total_label.text if not headline.contains("PER MINUTE") or not headline.contains("EVERY"): return _fail("the headline does not state both the bill and the rate: '%s'" % headline) print("headline: %s" % headline) # --- F. and it is not rebuilt every frame ----------------------------- var first: Array = menu.sheet.get_children() menu.refresh() menu.refresh() var again: Array = menu.sheet.get_children() if first.size() != again.size(): return _fail("the row count changed across a refresh with no change behind it") for i in range(first.size()): if first[i] != again[i]: return _fail("refreshing rebuilt the table though nothing changed") print("refreshing twice reuses the same %d cells" % again.size()) # --- D. an unpaid berth is called out --------------------------------- for building in game_state.buildings: if String(building.type) == GameState.BUILDING_FLEET_OFFICE: building["starved"] = true menu.refresh() var marked: bool = false for row in _rows(): if String(row[4]) == "UNPAID": marked = true if not marked: return _fail("a starved berth is not marked unpaid") print("a starved berth is marked UNPAID") print("TEST_FLEET_UPKEEP_SHEET: PASS") quit(0) return true func _fail(message: String) -> bool: print("TEST_FLEET_UPKEEP_SHEET: FAIL (%s)" % message) quit(1) return true