class_name ResourceBar extends Control # Compact resource table shared by crafting, research and the ship lab. # Rows are resources; columns are locations. Adding another material to # The resource-definition catalog automatically gives each material a row. var building_id: int = 0 # Header band and font sizes were all set for a 84px-tall box, which made the # table read as a dense strip of small numbers. The hosting menus now give it # 136px, so the rows have somewhere to breathe and the text can be legible at # arm's length on a phone. const HEADER_HEIGHT: float = 30.0 # Vertical gap between rows. Previously rows were flush against each other and # the only separation was colour. const ROW_GAP: float = 4.0 const HEADER_FONT_SIZE: int = 20 const NAME_FONT_SIZE: int = 23 const VALUE_FONT_SIZE: int = 24 const RULE_COLOR: Color = Color(0.25, 0.55, 0.7) const FLEET_COLOR: Color = Color(1.0, 0.78, 0.25) const LOCAL_COLOR: Color = Color(0.45, 0.9, 0.55) # #164: research points belong up here with everything else the player is # spending, rather than buried in a sentence at the bottom of whichever panel # happens to mention them. They are NOT a resource - no inventory, no glyph in # the catalog, nothing carries them - so they get a written mark and only the # banked column, exactly the treatment PriceTag already gives them. const POINT_COLOR: Color = Color(0.72, 0.62, 1.0) const POINT_NAME: String = "RESEARCH" var _header_labels: Array[Label] = [] var _rows: Dictionary = {} # The value columns, in the order they are drawn. LOCAL is dropped from this # list where it would only repeat another column - see _local_is_redundant(). const VALUE_KEYS: Array = ["bank", "fleet", "local"] var _local_shown: bool = true var _points_row: Dictionary = {} func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE for heading in ["", "BANKED", "FLEET", "LOCAL"]: var label := _make_label(heading, HEADER_FONT_SIZE, Color(0.62, 0.78, 0.86)) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER add_child(label) _header_labels.append(label) for resource_type in GameState.resource_types(): var glyph := ResourceGlyph.new() glyph.resource_type = resource_type add_child(glyph) var name_label := _make_label(GameState.resource_name(resource_type).to_upper(), NAME_FONT_SIZE, GameState.resource_color(resource_type)) add_child(name_label) var bank_label := _make_value_label(GameState.resource_color(resource_type)) var fleet_label := _make_value_label(FLEET_COLOR) var local_label := _make_value_label(LOCAL_COLOR) for label in [bank_label, fleet_label, local_label]: add_child(label) _rows[resource_type] = { "glyph": glyph, "name": name_label, "bank": bank_label, "fleet": fleet_label, "local": local_label, } var point_name := _make_label(POINT_NAME, NAME_FONT_SIZE, POINT_COLOR) add_child(point_name) var point_value := _make_value_label(POINT_COLOR) add_child(point_value) _points_row = {"name": point_name, "value": point_value} resized.connect(_layout) _layout() queue_redraw() func _make_label(text_value: String, font_size: int, colour: Color) -> Label: var label := Label.new() label.text = text_value label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.add_theme_font_size_override("font_size", font_size) label.add_theme_color_override("font_color", colour) label.add_theme_color_override("font_outline_color", Color(0.01, 0.01, 0.035, 0.95)) label.add_theme_constant_override("outline_size", 2) label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER return label func _make_value_label(colour: Color) -> Label: var label := _make_label("0", VALUE_FONT_SIZE, colour) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER return label # Whether a LOCAL column would say anything a reader could act on. Two ways # it would not: # # #95: at a building that IS the bank - the mothership carries both the "bank" # and the "research" capability, so the research menu is always opened from one # - building_inventory() redirects to bank_inventory itself, and LOCAL is not # merely equal to BANKED but the very same store read twice. Printing the # number again teaches nothing and costs a third of the table. # # #137: and at no building at all, which is how the build menu opens it - it # is reached from the fleet's own button rather than from a structure. There # is no "here" to report on, and a column of em-dashes is worth less than the # width it takes from the two columns that do say something. func _local_is_redundant() -> bool: if building_id == 0: return true return GameState.building_inventory(building_id) == GameState.bank_inventory func _visible_value_keys() -> Array: return VALUE_KEYS if _local_shown else VALUE_KEYS.slice(0, 2) func _layout() -> void: if _header_labels.is_empty(): return var keys: Array = _visible_value_keys() var resource_width: float = size.x * 0.25 var value_width: float = (size.x - resource_width) / float(keys.size()) var columns: Array = [0.0] var widths: Array = [resource_width] for i in range(keys.size()): columns.append(resource_width + value_width * float(i)) widths.append(value_width) for i in range(_header_labels.size()): var shown: bool = i < columns.size() _header_labels[i].visible = shown if not shown: continue _header_labels[i].position = Vector2(columns[i], 0.0) _header_labels[i].size = Vector2(widths[i], HEADER_HEIGHT) # One row per resource, plus the research-point line. var rows: float = maxf(1.0, float(_rows.size() + 1)) # The gap is taken OUT of the available height rather than added on top of # each row, so the table still ends exactly where its box does however many # resources the catalog grows to. var row_height: float = (size.y - HEADER_HEIGHT - ROW_GAP) / rows var row_index: int = 0 for resource_type in GameState.resource_types(): var row: Dictionary = _rows[resource_type] var y: float = HEADER_HEIGHT + float(row_index) * row_height var content_height: float = row_height - ROW_GAP row.glyph.position = Vector2(2.0, y + ROW_GAP * 0.5) row.glyph.size = Vector2(content_height, content_height) row.name.position = Vector2(content_height + 10.0, y) row.name.size = Vector2(resource_width - content_height - 10.0, row_height) for i in range(VALUE_KEYS.size()): var label: Label = row[VALUE_KEYS[i]] label.visible = i < keys.size() if not label.visible: continue label.position = Vector2(columns[i + 1], y) label.size = Vector2(widths[i + 1], row_height) row_index += 1 if not _points_row.is_empty(): var y: float = HEADER_HEIGHT + float(row_index) * row_height var content_height: float = row_height - ROW_GAP # Indented to where the resource NAMES start rather than where their # glyphs do, so the column of words stays a column and the missing # glyph reads as "not one of these" rather than as a gap. _points_row.name.position = Vector2(content_height + 10.0, y) _points_row.name.size = Vector2(resource_width - content_height - 10.0, row_height) _points_row.value.position = Vector2(columns[1], y) _points_row.value.size = Vector2(widths[1], row_height) queue_redraw() func _process(_delta: float) -> void: # building_id is assigned by whichever panel opens, so the column count can # change between frames; re-lay out only when it actually flips. var local_shown: bool = not _local_is_redundant() if local_shown != _local_shown: _local_shown = local_shown _layout() for resource_type in GameState.resource_types(): var row: Dictionary = _rows.get(resource_type, {}) if row.is_empty(): continue row.bank.text = str(GameState.banked_resource(resource_type)) row.fleet.text = str(GameState.fleet_resource(resource_type)) row.local.text = str(GameState.building_resource(building_id, resource_type)) \ if building_id != 0 else "—" if not _points_row.is_empty(): _points_row.value.text = str(GameState.research_points) static func natural_height() -> float: return 84.0 func _draw() -> void: NeonDraw.draw_glowing_line(self, Vector2(0.0, size.y), Vector2(size.x, size.y), RULE_COLOR, 0.55, 1.0)