class_name ProfileScreen extends CanvasLayer ## ProfileScreen - Displays player stats and match history signal back_pressed # Window dimensions const WINDOW_SIZE := Vector2i(600, 700) # Pagination const MATCHES_PER_PAGE = 10 # UI Components var main_container: VBoxContainer var back_button: Button var username_label: Label var stats_container: HBoxContainer var elo_value: Label var wins_value: Label var losses_value: Label var winrate_value: Label var games_value: Label var history_section: PanelContainer var history_list: VBoxContainer var pagination_container: HBoxContainer var prev_button: Button var page_label: Label var next_button: Button var loading_label: Label var error_label: Label # State var current_page: int = 0 var total_matches: int = 0 var is_loading: bool = false var matches_cache: Array = [] # Styling var custom_font: Font = preload("res://JimNightshade-Regular.ttf") const BG_COLOR := Color(0.12, 0.11, 0.15, 1.0) const PANEL_COLOR := Color(0.18, 0.16, 0.22, 1.0) const ACCENT_COLOR := Color(0.4, 0.35, 0.55, 1.0) const TEXT_COLOR := Color(0.9, 0.88, 0.82, 1.0) const MUTED_COLOR := Color(0.6, 0.58, 0.52, 1.0) const WIN_COLOR := Color(0.4, 0.8, 0.4, 1.0) const LOSS_COLOR := Color(0.8, 0.4, 0.4, 1.0) func _ready() -> void: _create_ui() _load_profile() func _create_ui() -> void: # Background var bg = ColorRect.new() bg.color = BG_COLOR bg.set_anchors_preset(Control.PRESET_FULL_RECT) add_child(bg) # Main container main_container = VBoxContainer.new() main_container.set_anchors_preset(Control.PRESET_FULL_RECT) main_container.add_theme_constant_override("separation", 16) add_child(main_container) var margin = MarginContainer.new() margin.add_theme_constant_override("margin_left", 24) margin.add_theme_constant_override("margin_right", 24) margin.add_theme_constant_override("margin_top", 16) margin.add_theme_constant_override("margin_bottom", 16) margin.set_anchors_preset(Control.PRESET_FULL_RECT) main_container.add_child(margin) var content = VBoxContainer.new() content.add_theme_constant_override("separation", 16) margin.add_child(content) # Back button back_button = _create_button("< Back", false) back_button.custom_minimum_size = Vector2(80, 32) back_button.pressed.connect(_on_back_pressed) content.add_child(back_button) # Title var title = Label.new() title.add_theme_font_override("font", custom_font) title.add_theme_font_size_override("font_size", 28) title.add_theme_color_override("font_color", TEXT_COLOR) title.text = "PLAYER PROFILE" title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER content.add_child(title) # Username username_label = Label.new() username_label.add_theme_font_override("font", custom_font) username_label.add_theme_font_size_override("font_size", 22) username_label.add_theme_color_override("font_color", ACCENT_COLOR) username_label.text = "Loading..." username_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER content.add_child(username_label) # Stats section _create_stats_section(content) # Match history section _create_history_section(content) # Error label error_label = Label.new() error_label.add_theme_font_override("font", custom_font) error_label.add_theme_font_size_override("font_size", 14) error_label.add_theme_color_override("font_color", Color(0.9, 0.3, 0.3)) error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER error_label.autowrap_mode = TextServer.AUTOWRAP_WORD error_label.visible = false content.add_child(error_label) func _create_stats_section(parent: VBoxContainer) -> void: var stats_panel = _create_panel_section("STATISTICS") parent.add_child(stats_panel) stats_container = HBoxContainer.new() stats_container.add_theme_constant_override("separation", 24) stats_panel.get_child(0).add_child(stats_container) # Create stat boxes var elo_box = _create_stat_box("ELO RATING") elo_value = elo_box.get_child(1) stats_container.add_child(elo_box) var wins_box = _create_stat_box("WINS") wins_value = wins_box.get_child(1) stats_container.add_child(wins_box) var losses_box = _create_stat_box("LOSSES") losses_value = losses_box.get_child(1) stats_container.add_child(losses_box) var winrate_box = _create_stat_box("WIN RATE") winrate_value = winrate_box.get_child(1) stats_container.add_child(winrate_box) var games_box = _create_stat_box("GAMES") games_value = games_box.get_child(1) stats_container.add_child(games_box) func _create_stat_box(title: String) -> VBoxContainer: var box = VBoxContainer.new() box.add_theme_constant_override("separation", 4) box.size_flags_horizontal = Control.SIZE_EXPAND_FILL var title_label = Label.new() title_label.add_theme_font_override("font", custom_font) title_label.add_theme_font_size_override("font_size", 12) title_label.add_theme_color_override("font_color", MUTED_COLOR) title_label.text = title title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(title_label) var value_label = Label.new() value_label.add_theme_font_override("font", custom_font) value_label.add_theme_font_size_override("font_size", 24) value_label.add_theme_color_override("font_color", TEXT_COLOR) value_label.text = "-" value_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(value_label) return box func _create_history_section(parent: VBoxContainer) -> void: history_section = _create_panel_section("MATCH HISTORY") history_section.size_flags_vertical = Control.SIZE_EXPAND_FILL parent.add_child(history_section) var content = history_section.get_child(0) as VBoxContainer # Loading indicator loading_label = Label.new() loading_label.add_theme_font_override("font", custom_font) loading_label.add_theme_font_size_override("font_size", 14) loading_label.add_theme_color_override("font_color", MUTED_COLOR) loading_label.text = "Loading..." loading_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER content.add_child(loading_label) # Match list history_list = VBoxContainer.new() history_list.add_theme_constant_override("separation", 8) history_list.size_flags_vertical = Control.SIZE_EXPAND_FILL content.add_child(history_list) # Pagination pagination_container = HBoxContainer.new() pagination_container.add_theme_constant_override("separation", 16) pagination_container.alignment = BoxContainer.ALIGNMENT_CENTER content.add_child(pagination_container) prev_button = _create_button("< Prev", false) prev_button.custom_minimum_size = Vector2(80, 32) prev_button.pressed.connect(_on_prev_page) pagination_container.add_child(prev_button) page_label = Label.new() page_label.add_theme_font_override("font", custom_font) page_label.add_theme_font_size_override("font_size", 14) page_label.add_theme_color_override("font_color", TEXT_COLOR) page_label.text = "Page 1" pagination_container.add_child(page_label) next_button = _create_button("Next >", false) next_button.custom_minimum_size = Vector2(80, 32) next_button.pressed.connect(_on_next_page) pagination_container.add_child(next_button) func _create_panel_section(title: String) -> PanelContainer: var panel = PanelContainer.new() var style = StyleBoxFlat.new() style.bg_color = PANEL_COLOR style.set_corner_radius_all(8) style.set_content_margin_all(16) panel.add_theme_stylebox_override("panel", style) var vbox = VBoxContainer.new() vbox.add_theme_constant_override("separation", 12) panel.add_child(vbox) var title_label = Label.new() title_label.add_theme_font_override("font", custom_font) title_label.add_theme_font_size_override("font_size", 18) title_label.add_theme_color_override("font_color", TEXT_COLOR) title_label.text = title title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER vbox.add_child(title_label) return panel func _create_button(text: String, primary: bool) -> Button: var button = Button.new() button.text = text button.add_theme_font_override("font", custom_font) button.add_theme_font_size_override("font_size", 16) button.custom_minimum_size = Vector2(0, 40) var normal = StyleBoxFlat.new() var hover = StyleBoxFlat.new() var pressed = StyleBoxFlat.new() var disabled = StyleBoxFlat.new() if primary: normal.bg_color = ACCENT_COLOR hover.bg_color = ACCENT_COLOR.lightened(0.15) pressed.bg_color = ACCENT_COLOR.darkened(0.15) button.add_theme_color_override("font_color", Color.WHITE) button.add_theme_color_override("font_hover_color", Color.WHITE) else: normal.bg_color = Color(0.25, 0.23, 0.3) hover.bg_color = Color(0.3, 0.28, 0.35) pressed.bg_color = Color(0.2, 0.18, 0.25) button.add_theme_color_override("font_color", TEXT_COLOR) button.add_theme_color_override("font_hover_color", TEXT_COLOR) disabled.bg_color = Color(0.2, 0.18, 0.22) button.add_theme_color_override("font_disabled_color", MUTED_COLOR) for style in [normal, hover, pressed, disabled]: style.set_corner_radius_all(6) style.set_content_margin_all(8) button.add_theme_stylebox_override("normal", normal) button.add_theme_stylebox_override("hover", hover) button.add_theme_stylebox_override("pressed", pressed) button.add_theme_stylebox_override("disabled", disabled) return button func _load_profile() -> void: if not NetworkManager or not NetworkManager.is_authenticated: _show_error("Not logged in") return is_loading = true loading_label.visible = true var result = await NetworkManager.get_profile() if result.success: _update_profile_display(result.user) _load_match_history() else: _show_error(result.message) is_loading = false func _update_profile_display(user: Dictionary) -> void: username_label.text = user.get("username", "Unknown") var stats = user.get("stats", {}) elo_value.text = str(stats.get("eloRating", 1000)) wins_value.text = str(stats.get("wins", 0)) losses_value.text = str(stats.get("losses", 0)) games_value.text = str(stats.get("gamesPlayed", 0)) # Calculate win rate var games_played = stats.get("gamesPlayed", 0) if games_played > 0: var win_rate = float(stats.get("wins", 0)) / float(games_played) * 100.0 winrate_value.text = "%.1f%%" % win_rate else: winrate_value.text = "N/A" func _load_match_history() -> void: loading_label.visible = true _clear_history_list() var offset = current_page * MATCHES_PER_PAGE var result = await NetworkManager.get_match_history(MATCHES_PER_PAGE, offset) loading_label.visible = false if result.success: var matches = result.get("matches", []) matches_cache = matches _display_matches(matches) _update_pagination() else: _show_error(result.message) func _display_matches(matches: Array) -> void: _clear_history_list() if matches.is_empty(): var empty_label = Label.new() empty_label.add_theme_font_override("font", custom_font) empty_label.add_theme_font_size_override("font_size", 14) empty_label.add_theme_color_override("font_color", MUTED_COLOR) empty_label.text = "No matches found" empty_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER history_list.add_child(empty_label) return for match_data in matches: var match_row = _create_match_row(match_data) history_list.add_child(match_row) func _create_match_row(match_data: Dictionary) -> HBoxContainer: var row = HBoxContainer.new() row.add_theme_constant_override("separation", 12) # Win/Loss indicator var result_label = Label.new() result_label.add_theme_font_override("font", custom_font) result_label.add_theme_font_size_override("font_size", 14) result_label.custom_minimum_size.x = 50 var is_win = match_data.get("isWin", false) if is_win: result_label.text = "WIN" result_label.add_theme_color_override("font_color", WIN_COLOR) else: result_label.text = "LOSS" result_label.add_theme_color_override("font_color", LOSS_COLOR) row.add_child(result_label) # Opponent var vs_label = Label.new() vs_label.add_theme_font_override("font", custom_font) vs_label.add_theme_font_size_override("font_size", 14) vs_label.add_theme_color_override("font_color", TEXT_COLOR) vs_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL var opponent = match_data.get("player1", "") var current_username = NetworkManager.current_user.get("username", "") if opponent == current_username: opponent = match_data.get("player2", "Unknown") vs_label.text = "vs %s" % opponent row.add_child(vs_label) # Result type var reason_label = Label.new() reason_label.add_theme_font_override("font", custom_font) reason_label.add_theme_font_size_override("font_size", 12) reason_label.add_theme_color_override("font_color", MUTED_COLOR) reason_label.custom_minimum_size.x = 80 var result_reason = match_data.get("result", "") match result_reason: "damage": reason_label.text = "Damage" "deck_out": reason_label.text = "Deck Out" "concede": reason_label.text = "Concede" "timeout": reason_label.text = "Timeout" "disconnect": reason_label.text = "Disconnect" _: reason_label.text = result_reason.capitalize() row.add_child(reason_label) # ELO change var elo_label = Label.new() elo_label.add_theme_font_override("font", custom_font) elo_label.add_theme_font_size_override("font_size", 14) elo_label.custom_minimum_size.x = 60 elo_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT var elo_change = match_data.get("eloChange", 0) if is_win: elo_label.text = "+%d" % elo_change elo_label.add_theme_color_override("font_color", WIN_COLOR) else: elo_label.text = "-%d" % elo_change elo_label.add_theme_color_override("font_color", LOSS_COLOR) row.add_child(elo_label) # Date var date_label = Label.new() date_label.add_theme_font_override("font", custom_font) date_label.add_theme_font_size_override("font_size", 12) date_label.add_theme_color_override("font_color", MUTED_COLOR) date_label.custom_minimum_size.x = 80 date_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT var played_at = match_data.get("playedAt", "") if played_at != "": # Parse ISO date and format nicely date_label.text = _format_date(played_at) row.add_child(date_label) return row func _format_date(iso_date: String) -> String: # Simple date formatting - extracts date portion if iso_date.contains("T"): var parts = iso_date.split("T") var date_part = parts[0] var date_components = date_part.split("-") if date_components.size() >= 3: return "%s/%s" % [date_components[1], date_components[2]] return iso_date.left(10) func _clear_history_list() -> void: for child in history_list.get_children(): child.queue_free() func _update_pagination() -> void: page_label.text = "Page %d" % (current_page + 1) prev_button.disabled = current_page == 0 # Disable next if we got fewer results than requested (end of data) next_button.disabled = matches_cache.size() < MATCHES_PER_PAGE func _show_error(message: String) -> void: error_label.text = message error_label.visible = true await get_tree().create_timer(5.0).timeout if is_instance_valid(error_label): error_label.visible = false func _on_back_pressed() -> void: back_pressed.emit() func _on_prev_page() -> void: if current_page > 0: current_page -= 1 _load_match_history() func _on_next_page() -> void: current_page += 1 _load_match_history() func refresh() -> void: current_page = 0 _load_profile()