extends "res://ui/menus/ModalMenu.gd" signal building_selected(type: String) signal build_confirmed const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd") const MENU_LAYOUT = preload("res://ui/menus/MenuLayout.gd") const BUTTON_HALF: float = 64.0 const BUTTON_HEIGHT: float = 100.0 const COST_HALF: float = 74.0 const COST_HEIGHT: float = 86.0 const LABEL_OFFSET: float = 106.0 const LABEL_GAP: float = 4.0 const NATURAL_SPACING: float = 280.0 const MIN_SPACING: float = BUTTON_HALF * 2.0 + 12.0 const LABEL_SHARE: float = 0.46 const LABEL_HEIGHT: float = 52.0 const LABEL_PADDING: float = 10.0 const COST_PADDING: float = 12.0 const SIDE_MARGIN: float = 24.0 const MAX_COLUMNS: int = 6 @onready var status: Label = $StatusLabel @onready var info_popup: Control = $InfoPopup @onready var info_title: Label = $InfoPopup/Panel/TitleLabel @onready var info_message: Label = $InfoPopup/Panel/MessageLabel @onready var info_cost: Control = $InfoPopup/Panel/CostNumber @onready var info_build_button: Control = $InfoPopup/Panel/BuildButton @onready var info_close_button: Control = $InfoPopup/Panel/CloseButton var entries: Array = [] func _ready() -> void: super._ready() _configure_entries() info_close_button.pressed.connect(close_info) info_build_button.pressed.connect(func(): build_confirmed.emit()) info_popup.visible = false func _configure_entries() -> void: entries.clear() for definition in BUILDING_DEFINITIONS.all(): if definition.menu_button_name == "": continue var button: Control = get_node(String(definition.menu_button_name)) var label: Label = get_node(String(definition.menu_label_name)) var cost_tag: Control = get_node(String(definition.menu_cost_name)) button.icon = definition.menu_icon button.pressed.connect( func(): building_selected.emit(String(definition.definition_id))) label.text = definition.display_name cost_tag.show_price(GameState.building_costs(String(definition.definition_id))) entries.append({"definition": definition, "button": button, "label": label, "cost": cost_tag}) func open_menu() -> void: super.open_menu() close_info() func close_menu() -> void: close_info() super.close_menu() func close_info() -> void: info_popup.visible = false func show_info(title: String, message: String, costs: Dictionary, can_build: bool) -> void: info_title.text = title info_message.text = message info_cost.show_price(costs) info_build_button.visible = can_build info_popup.visible = true func set_status(message: String) -> void: status.text = message func refresh(planet: Node2D) -> void: _layout_entries() refresh_option_states(planet) if planet == null: status.text = "" elif not GameState.is_planet_conquered(planet.planet_id): status.text = "Over %s - capture it before building an extractor" % planet.planet_name else: status.text = "Over %s - an extractor can be built here" % planet.planet_name func refresh_option_states(planet: Node2D) -> void: for entry in entries: var definition: Resource = entry.definition entry.cost.show_price(GameState.building_costs(String(definition.definition_id))) var enabled: bool = GameState.is_building_unlocked(definition.definition_id) if definition.placement_mode == "planet": enabled = enabled and planet != null \ and GameState.is_planet_conquered(planet.planet_id) entry.button.enabled = enabled entry.button.queue_redraw() func menu_band() -> Vector2: return MENU_LAYOUT.grid_band(status.offset_bottom, size.y) func menu_column_spacing(columns: int) -> float: var room: float = size.x - SIDE_MARGIN * 2.0 if room <= 0.0: room = 672.0 return maxf(MIN_SPACING, minf(NATURAL_SPACING, room / float(columns))) func menu_label_half(columns: int) -> float: return menu_column_spacing(columns) * LABEL_SHARE func measured_label_height(label: Label, text: String, columns: int) -> float: var font: Font = label.get_theme_font("font") var font_size: int = label.get_theme_font_size("font_size") if font == null or font_size <= 0 or text == "": return LABEL_HEIGHT var height: float = font.get_multiline_string_size(text, HORIZONTAL_ALIGNMENT_CENTER, menu_label_half(columns) * 2.0, font_size).y return maxf(LABEL_HEIGHT, height + LABEL_PADDING) func menu_label_height(columns: int) -> float: var tallest: float = LABEL_HEIGHT for entry in entries: if entry.label.visible: tallest = maxf(tallest, measured_label_height(entry.label, entry.label.text, columns)) return tallest func menu_cost_height() -> float: var tallest: float = COST_HEIGHT for entry in entries: if entry.cost.visible and entry.cost.has_method("wanted_height"): tallest = maxf(tallest, float(entry.cost.wanted_height()) + COST_PADDING) return tallest func menu_cost_offset(columns: int) -> float: return LABEL_OFFSET + menu_label_height(columns) + LABEL_GAP func menu_row_height(columns: int) -> float: return menu_cost_offset(columns) + menu_cost_height() func menu_columns(count: int) -> int: var widest: int = maxi(mini(MAX_COLUMNS, int(floor((size.x - SIDE_MARGIN * 2.0) / MIN_SPACING))), 2) for columns in range(2, widest + 1): if _rows_fit(int(ceil(float(count) / float(columns))), columns): return columns return widest func menu_rows(count: int) -> int: var columns: int = menu_columns(count) return maxi(int(ceil(float(count) / float(columns))), 1) func _rows_fit(rows: int, columns: int) -> bool: var height: float = menu_row_height(columns) var band: Vector2 = menu_band() if rows <= 1: return band.y - band.x >= height return (band.y - band.x - height) / float(rows - 1) >= height func menu_row_top(row: int, rows: int, columns: int) -> float: return MENU_LAYOUT.grid_row_top(row, rows, menu_row_height(columns), menu_band(), NATURAL_SPACING) func _layout_entries() -> void: var shown: Array = [] for entry in entries: var definition: Resource = entry.definition var entry_visible: bool = GameState.is_building_unlocked(definition.definition_id) entry.button.visible = entry_visible entry.label.visible = entry_visible entry.cost.visible = entry_visible if entry_visible: entry.cost.show_price(GameState.building_costs(String(definition.definition_id))) shown.append(entry) var columns: int = menu_columns(shown.size()) var rows: int = menu_rows(shown.size()) var spacing: float = menu_column_spacing(columns) var label_half: float = menu_label_half(columns) var label_height: float = menu_label_height(columns) var cost_offset: float = menu_cost_offset(columns) var cost_height: float = menu_cost_height() for i in range(shown.size()): var row: int = i / columns var in_row: int = mini(shown.size() - row * columns, columns) var column: int = i % columns var cx: float = (float(column) - (float(in_row) - 1.0) * 0.5) * spacing var top: float = menu_row_top(row, rows, columns) MENU_LAYOUT.place_entry(shown[i].button, cx, top, BUTTON_HALF, BUTTON_HEIGHT) MENU_LAYOUT.place_entry(shown[i].label, cx, top + LABEL_OFFSET, label_half, label_height) MENU_LAYOUT.place_entry(shown[i].cost, cx, top + cost_offset, COST_HALF, cost_height)