feature updates

This commit is contained in:
2026-02-02 16:28:53 -05:00
parent bf9aa3fa23
commit 44c06530ac
83 changed files with 282641 additions and 11251 deletions

494
scripts/ui/ProfileScreen.gd Normal file
View File

@@ -0,0 +1,494 @@
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()