class_name GameSetupMenu extends CanvasLayer ## GameSetupMenu - Setup screen for configuring game before starting ## Allows selection of game type and decks for each player signal back_pressed signal start_game_requested(p1_deck: Array, p2_deck: Array) const WINDOW_SIZE := Vector2(800, 600) # UI Components var background: PanelContainer var main_vbox: VBoxContainer var title_label: Label var game_type_container: HBoxContainer var game_type_dropdown: OptionButton var players_container: HBoxContainer var player1_panel: Control var player2_panel: Control var p1_deck_dropdown: OptionButton var p2_deck_dropdown: OptionButton var p1_preview: Control var p2_preview: Control var buttons_container: HBoxContainer var start_button: Button var back_button: Button # Deck data var saved_decks: Array[String] = [] var starter_decks: Array = [] # Array of StarterDeckData var p1_selected_deck: Array = [] # Card IDs var p2_selected_deck: Array = [] # Card IDs func _ready() -> void: # Set high layer to be on top of everything layer = 100 _load_deck_options() _create_ui() _select_random_decks() func _load_deck_options() -> void: # Load saved decks saved_decks = DeckManager.list_decks() # Load starter decks starter_decks = CardDatabase.get_starter_decks() func _create_ui() -> void: # Background panel - use the window size constant background = PanelContainer.new() add_child(background) background.position = Vector2.ZERO background.size = WINDOW_SIZE background.add_theme_stylebox_override("panel", _create_panel_style()) # Main vertical layout with padding var margin = MarginContainer.new() background.add_child(margin) margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) margin.add_theme_constant_override("margin_left", 25) margin.add_theme_constant_override("margin_right", 25) margin.add_theme_constant_override("margin_top", 15) margin.add_theme_constant_override("margin_bottom", 15) main_vbox = VBoxContainer.new() margin.add_child(main_vbox) main_vbox.add_theme_constant_override("separation", 8) # Title _create_title() # Game type selector _create_game_type_selector() # Player panels _create_player_panels() # Spacer var spacer = Control.new() spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL main_vbox.add_child(spacer) # Buttons _create_buttons() func _create_title() -> void: title_label = Label.new() title_label.text = "GAME SETUP" title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_label.add_theme_font_size_override("font_size", 32) title_label.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8)) main_vbox.add_child(title_label) # Separator var separator = HSeparator.new() separator.add_theme_stylebox_override("separator", _create_separator_style()) main_vbox.add_child(separator) func _create_game_type_selector() -> void: game_type_container = HBoxContainer.new() game_type_container.add_theme_constant_override("separation", 15) main_vbox.add_child(game_type_container) var label = Label.new() label.text = "Game Type:" label.add_theme_font_size_override("font_size", 18) label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) game_type_container.add_child(label) game_type_dropdown = OptionButton.new() game_type_dropdown.custom_minimum_size = Vector2(250, 36) game_type_dropdown.add_item("2-Player Local (Share Screen)") game_type_dropdown.add_item("vs AI (Coming Soon)") game_type_dropdown.set_item_disabled(1, true) game_type_dropdown.add_theme_font_size_override("font_size", 14) _style_dropdown(game_type_dropdown) game_type_container.add_child(game_type_dropdown) func _create_player_panels() -> void: players_container = HBoxContainer.new() players_container.add_theme_constant_override("separation", 20) players_container.alignment = BoxContainer.ALIGNMENT_CENTER main_vbox.add_child(players_container) player1_panel = _create_player_panel("PLAYER 1", 1) players_container.add_child(player1_panel) player2_panel = _create_player_panel("PLAYER 2", 2) players_container.add_child(player2_panel) func _create_player_panel(title: String, player_num: int) -> Control: var panel = PanelContainer.new() panel.custom_minimum_size = Vector2(320, 280) panel.add_theme_stylebox_override("panel", _create_player_panel_style()) var vbox = VBoxContainer.new() vbox.add_theme_constant_override("separation", 8) panel.add_child(vbox) var margin = MarginContainer.new() margin.add_theme_constant_override("margin_left", 12) margin.add_theme_constant_override("margin_right", 12) margin.add_theme_constant_override("margin_top", 8) margin.add_theme_constant_override("margin_bottom", 8) vbox.add_child(margin) var inner_vbox = VBoxContainer.new() inner_vbox.add_theme_constant_override("separation", 6) margin.add_child(inner_vbox) # Player title var title_label = Label.new() title_label.text = title title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_label.add_theme_font_size_override("font_size", 18) title_label.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8)) inner_vbox.add_child(title_label) # Deck dropdown var dropdown = OptionButton.new() dropdown.custom_minimum_size = Vector2(300, 32) dropdown.add_theme_font_size_override("font_size", 12) _style_dropdown(dropdown) _populate_deck_dropdown(dropdown) dropdown.item_selected.connect(_on_deck_selected.bind(player_num)) inner_vbox.add_child(dropdown) if player_num == 1: p1_deck_dropdown = dropdown else: p2_deck_dropdown = dropdown # Deck preview panel (with box art) var preview = _create_deck_preview(player_num) inner_vbox.add_child(preview) if player_num == 1: p1_preview = preview else: p2_preview = preview return panel func _create_deck_preview(player_num: int) -> Control: var panel = PanelContainer.new() panel.custom_minimum_size = Vector2(280, 170) var style = StyleBoxFlat.new() style.bg_color = Color(0.06, 0.06, 0.1, 0.8) style.border_color = Color(0.3, 0.3, 0.35) style.set_border_width_all(1) style.set_corner_radius_all(4) style.content_margin_left = 8 style.content_margin_right = 8 style.content_margin_top = 6 style.content_margin_bottom = 6 panel.add_theme_stylebox_override("panel", style) var vbox = VBoxContainer.new() vbox.name = "VBoxContainer" vbox.add_theme_constant_override("separation", 4) panel.add_child(vbox) # Box art container (centered) var art_container = CenterContainer.new() art_container.name = "ArtContainer" art_container.custom_minimum_size = Vector2(260, 90) vbox.add_child(art_container) # Box art image var box_art = TextureRect.new() box_art.name = "BoxArt" box_art.custom_minimum_size = Vector2(90, 85) box_art.expand_mode = TextureRect.EXPAND_FIT_HEIGHT_PROPORTIONAL box_art.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED art_container.add_child(box_art) # Placeholder when no art available var placeholder = ColorRect.new() placeholder.name = "Placeholder" placeholder.custom_minimum_size = Vector2(60, 85) placeholder.color = Color(0.15, 0.15, 0.2, 0.5) placeholder.visible = true art_container.add_child(placeholder) # Placeholder icon/text var placeholder_label = Label.new() placeholder_label.text = "?" placeholder_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER placeholder_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER placeholder_label.add_theme_font_size_override("font_size", 48) placeholder_label.add_theme_color_override("font_color", Color(0.3, 0.3, 0.35)) placeholder.add_child(placeholder_label) placeholder_label.set_anchors_preset(Control.PRESET_FULL_RECT) # Deck name var name_label = Label.new() name_label.name = "DeckName" name_label.text = "No deck selected" name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_label.add_theme_font_size_override("font_size", 14) name_label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) vbox.add_child(name_label) # Elements row (centered) var elements_center = CenterContainer.new() elements_center.name = "ElementsCenter" vbox.add_child(elements_center) var elements_hbox = HBoxContainer.new() elements_hbox.name = "ElementsRow" elements_hbox.add_theme_constant_override("separation", 6) elements_center.add_child(elements_hbox) # Info row (card count + description) var info_hbox = HBoxContainer.new() info_hbox.name = "InfoRow" info_hbox.alignment = BoxContainer.ALIGNMENT_CENTER info_hbox.add_theme_constant_override("separation", 10) vbox.add_child(info_hbox) # Card count var count_label = Label.new() count_label.name = "CardCount" count_label.text = "0 cards" count_label.add_theme_font_size_override("font_size", 11) count_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6)) info_hbox.add_child(count_label) # Separator var sep = Label.new() sep.text = "•" sep.add_theme_font_size_override("font_size", 11) sep.add_theme_color_override("font_color", Color(0.4, 0.4, 0.4)) info_hbox.add_child(sep) # Description var desc_label = Label.new() desc_label.name = "Description" desc_label.text = "" desc_label.add_theme_font_size_override("font_size", 11) desc_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.55)) info_hbox.add_child(desc_label) return panel func _populate_deck_dropdown(dropdown: OptionButton) -> void: dropdown.clear() # Add "My Decks" section if there are saved decks if not saved_decks.is_empty(): dropdown.add_separator("-- My Decks --") for deck_name in saved_decks: dropdown.add_item(deck_name) dropdown.set_item_metadata(dropdown.get_item_count() - 1, {"type": "saved", "name": deck_name}) # Add "Starter Decks" section dropdown.add_separator("-- Starter Decks --") for starter_deck in starter_decks: var display_name = "%s (%s)" % [starter_deck.name, starter_deck.opus] dropdown.add_item(display_name) dropdown.set_item_metadata(dropdown.get_item_count() - 1, {"type": "starter", "id": starter_deck.id}) func _select_random_decks() -> void: # Select random starter decks for both players if starter_decks.size() >= 2: var indices = range(starter_decks.size()) indices.shuffle() var p1_index = _find_dropdown_index_for_starter(p1_deck_dropdown, starter_decks[indices[0]].id) var p2_index = _find_dropdown_index_for_starter(p2_deck_dropdown, starter_decks[indices[1]].id) if p1_index >= 0: p1_deck_dropdown.select(p1_index) _on_deck_selected(p1_index, 1) if p2_index >= 0: p2_deck_dropdown.select(p2_index) _on_deck_selected(p2_index, 2) elif starter_decks.size() == 1: var index = _find_dropdown_index_for_starter(p1_deck_dropdown, starter_decks[0].id) if index >= 0: p1_deck_dropdown.select(index) _on_deck_selected(index, 1) p2_deck_dropdown.select(index) _on_deck_selected(index, 2) func _find_dropdown_index_for_starter(dropdown: OptionButton, starter_id: String) -> int: for i in range(dropdown.get_item_count()): var meta = dropdown.get_item_metadata(i) if meta is Dictionary and meta.get("type") == "starter" and meta.get("id") == starter_id: return i return -1 func _on_deck_selected(index: int, player_num: int) -> void: var dropdown = p1_deck_dropdown if player_num == 1 else p2_deck_dropdown var preview = p1_preview if player_num == 1 else p2_preview var meta = dropdown.get_item_metadata(index) if not meta is Dictionary: return var deck_cards: Array = [] var deck_name: String = "" var deck_elements: Array = [] var deck_description: String = "" var deck_texture: Texture2D = null if meta.get("type") == "saved": var deck = DeckManager.load_deck(meta.get("name")) if deck: deck_cards = deck.to_card_array() deck_name = deck.name deck_elements = _get_elements_from_deck(deck_cards) deck_description = "Custom deck" # No box art for custom decks elif meta.get("type") == "starter": var starter = CardDatabase.get_starter_deck(meta.get("id")) if starter: deck_cards = starter.cards.duplicate() deck_name = starter.name deck_elements = starter.elements deck_description = starter.description deck_texture = starter.get_texture() # Store selected deck if player_num == 1: p1_selected_deck = deck_cards else: p2_selected_deck = deck_cards # Update preview _update_preview(preview, deck_name, deck_elements, deck_cards.size(), deck_description, deck_texture) # Update start button state _update_start_button() func _get_elements_from_deck(card_ids: Array) -> Array: var elements: Dictionary = {} for card_id in card_ids: var card = CardDatabase.get_card(card_id) if card: for element in card.elements: var elem_name = Enums.element_to_string(element) elements[elem_name] = elements.get(elem_name, 0) + 1 # Sort by count and return top elements var sorted_elements: Array = [] for elem_name in elements.keys(): sorted_elements.append({"name": elem_name, "count": elements[elem_name]}) sorted_elements.sort_custom(func(a, b): return a.count > b.count) var result: Array = [] for i in range(mini(2, sorted_elements.size())): result.append(sorted_elements[i].name) return result func _update_preview(preview: Control, deck_name: String, elements: Array, card_count: int, description: String, texture: Texture2D = null) -> void: var box_art = preview.get_node_or_null("VBoxContainer/ArtContainer/BoxArt") as TextureRect var placeholder = preview.get_node_or_null("VBoxContainer/ArtContainer/Placeholder") as ColorRect var name_label = preview.get_node_or_null("VBoxContainer/DeckName") as Label var elements_row = preview.get_node_or_null("VBoxContainer/ElementsCenter/ElementsRow") as HBoxContainer var count_label = preview.get_node_or_null("VBoxContainer/InfoRow/CardCount") as Label var desc_label = preview.get_node_or_null("VBoxContainer/InfoRow/Description") as Label # Update box art if box_art and placeholder: if texture: box_art.texture = texture box_art.visible = true placeholder.visible = false else: box_art.texture = null box_art.visible = false placeholder.visible = true if name_label: name_label.text = deck_name if not deck_name.is_empty() else "No deck selected" if elements_row: # Clear existing elements for child in elements_row.get_children(): child.queue_free() # Add element indicators for elem_name in elements: var elem_container = HBoxContainer.new() elem_container.add_theme_constant_override("separation", 4) var color_rect = ColorRect.new() color_rect.custom_minimum_size = Vector2(12, 12) var element = Enums.element_from_string(elem_name) color_rect.color = Enums.element_to_color(element) elem_container.add_child(color_rect) var elem_label = Label.new() elem_label.text = elem_name elem_label.add_theme_font_size_override("font_size", 11) elem_label.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8)) elem_container.add_child(elem_label) elements_row.add_child(elem_container) if count_label: count_label.text = "%d cards" % card_count if desc_label: desc_label.text = description func _update_start_button() -> void: # Require at least 1 card in each deck to start (relaxed from 50 for testing with incomplete card databases) var can_start = p1_selected_deck.size() >= 1 and p2_selected_deck.size() >= 1 start_button.disabled = not can_start if can_start: start_button.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) else: start_button.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)) func _create_buttons() -> void: buttons_container = HBoxContainer.new() buttons_container.add_theme_constant_override("separation", 20) buttons_container.alignment = BoxContainer.ALIGNMENT_CENTER main_vbox.add_child(buttons_container) back_button = _create_styled_button("Back", Color(0.3, 0.25, 0.25)) back_button.pressed.connect(_on_back_pressed) buttons_container.add_child(back_button) start_button = _create_styled_button("Start Game", Color(0.2, 0.35, 0.25)) start_button.custom_minimum_size.x = 180 start_button.pressed.connect(_on_start_pressed) start_button.disabled = true buttons_container.add_child(start_button) func _create_styled_button(text: String, base_color: Color) -> Button: var button = Button.new() button.text = text button.custom_minimum_size = Vector2(140, 44) button.add_theme_font_size_override("font_size", 16) var style_normal = StyleBoxFlat.new() style_normal.bg_color = base_color style_normal.border_color = Color(0.5, 0.4, 0.2) style_normal.set_border_width_all(2) style_normal.set_corner_radius_all(6) button.add_theme_stylebox_override("normal", style_normal) var style_hover = StyleBoxFlat.new() style_hover.bg_color = base_color.lightened(0.15) style_hover.border_color = Color(0.7, 0.55, 0.3) style_hover.set_border_width_all(2) style_hover.set_corner_radius_all(6) button.add_theme_stylebox_override("hover", style_hover) var style_pressed = StyleBoxFlat.new() style_pressed.bg_color = base_color.darkened(0.1) style_pressed.border_color = Color(0.5, 0.4, 0.2) style_pressed.set_border_width_all(2) style_pressed.set_corner_radius_all(6) button.add_theme_stylebox_override("pressed", style_pressed) var style_disabled = StyleBoxFlat.new() style_disabled.bg_color = Color(0.15, 0.15, 0.18) style_disabled.border_color = Color(0.3, 0.3, 0.3) style_disabled.set_border_width_all(2) style_disabled.set_corner_radius_all(6) button.add_theme_stylebox_override("disabled", style_disabled) button.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) button.add_theme_color_override("font_hover_color", Color(1.0, 0.95, 0.8)) button.add_theme_color_override("font_pressed_color", Color(0.7, 0.65, 0.55)) button.add_theme_color_override("font_disabled_color", Color(0.4, 0.4, 0.4)) return button func _style_dropdown(dropdown: OptionButton) -> void: var style = StyleBoxFlat.new() style.bg_color = Color(0.12, 0.12, 0.16) style.border_color = Color(0.4, 0.35, 0.25) style.set_border_width_all(1) style.set_corner_radius_all(4) style.content_margin_left = 10 style.content_margin_right = 10 style.content_margin_top = 6 style.content_margin_bottom = 6 dropdown.add_theme_stylebox_override("normal", style) var hover_style = style.duplicate() hover_style.border_color = Color(0.6, 0.5, 0.3) dropdown.add_theme_stylebox_override("hover", hover_style) dropdown.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) dropdown.add_theme_color_override("font_hover_color", Color(1.0, 0.95, 0.8)) func _create_panel_style() -> StyleBoxFlat: var style = StyleBoxFlat.new() style.bg_color = Color(0.08, 0.08, 0.12, 1.0) # No border on the outer panel to avoid gaps at window edges style.set_border_width_all(0) style.set_corner_radius_all(0) return style func _create_player_panel_style() -> StyleBoxFlat: var style = StyleBoxFlat.new() style.bg_color = Color(0.1, 0.1, 0.14, 0.9) style.border_color = Color(0.4, 0.35, 0.25) style.set_border_width_all(2) style.set_corner_radius_all(6) return style func _create_separator_style() -> StyleBoxFlat: var style = StyleBoxFlat.new() style.bg_color = Color(0.5, 0.4, 0.2, 0.5) style.content_margin_top = 1 return style func _on_back_pressed() -> void: back_pressed.emit() func _on_start_pressed() -> void: if p1_selected_deck.size() >= 1 and p2_selected_deck.size() >= 1: start_game_requested.emit(p1_selected_deck, p2_selected_deck)