From f10d777c90b7f2c293633328aaf6256bb71c658f Mon Sep 17 00:00:00 2001 From: adtpdn Date: Wed, 15 Apr 2026 16:26:49 +0800 Subject: [PATCH] feat: overhaul UI main and profile --- .../enhanced_gridmap/meshlibrary/default.tres | 2 +- assets/themes/ui_theme.tres | 1 + modify_tscn.py | 63 -- move_ui.py | 49 -- scenes/lobby.gd | 143 +++- scenes/lobby.tscn | 541 +++++++++--- scenes/main.tscn | 18 +- scenes/player.gd | 20 + scenes/ui/login_screen.tscn | 2 + scenes/ui/profile_panel.tscn | 767 +++++++++++++----- scenes/ui/shop_panel.tscn | 93 +++ scripts/managers/user_profile_manager.gd | 70 +- scripts/ui/profile_panel.gd | 691 ++++++++++------ scripts/ui/shop_panel.gd | 91 +++ scripts/ui/shop_panel.gd.uid | 1 + server/nakama/tekton_admin.js | 46 ++ 16 files changed, 1888 insertions(+), 710 deletions(-) delete mode 100644 modify_tscn.py delete mode 100644 move_ui.py create mode 100644 scenes/ui/shop_panel.tscn create mode 100644 scripts/ui/shop_panel.gd create mode 100644 scripts/ui/shop_panel.gd.uid diff --git a/addons/enhanced_gridmap/meshlibrary/default.tres b/addons/enhanced_gridmap/meshlibrary/default.tres index 1f089fa..141a3c0 100644 --- a/addons/enhanced_gridmap/meshlibrary/default.tres +++ b/addons/enhanced_gridmap/meshlibrary/default.tres @@ -14,7 +14,7 @@ [ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"] [ext_resource type="BoxMesh" uid="uid://fy4bhoeii40c" path="res://addons/enhanced_gridmap/meshlibrary/tile_safe_zone.tres" id="10_uwjsj"] [ext_resource type="BoxMesh" uid="uid://b5cc3prem52r6" path="res://addons/enhanced_gridmap/meshlibrary/tile_freeze.tres" id="11_pgnbl"] -[ext_resource type="BoxMesh" uid="uid://dcjdwbffgtutt" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"] +[ext_resource type="BoxMesh" path="res://addons/enhanced_gridmap/meshlibrary/tile_non_walkable.tres" id="11_uwjsj"] [ext_resource type="Texture2D" uid="uid://cdnxwlysxnujd" path="res://assets/textures/tile_heart.png" id="12_heart_tex"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uxput"] diff --git a/assets/themes/ui_theme.tres b/assets/themes/ui_theme.tres index fc019e4..1b1dcc5 100644 --- a/assets/themes/ui_theme.tres +++ b/assets/themes/ui_theme.tres @@ -248,3 +248,4 @@ TabContainer/colors/font_unselected_color = Color(0.69, 0.529, 0.357, 1) TabContainer/styles/panel = SubResource("StyleBoxFlat_panel_bg") TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_tab_selected") TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_tab_unselected") +TextEdit/fonts/font = null diff --git a/modify_tscn.py b/modify_tscn.py deleted file mode 100644 index 181d713..0000000 --- a/modify_tscn.py +++ /dev/null @@ -1,63 +0,0 @@ -import sys -import re - -file_path = 'c:/Users/beng/Godot/Projects/tekton-enet/scenes/main.tscn' -with open(file_path, 'r', encoding='utf-8') as f: - lines = f.readlines() - -interaction_idx = -1 -for i, line in enumerate(lines): - if line.startswith('[node name="InteractionBtn" type="VBoxContainer"'): - interaction_idx = i - break - -if interaction_idx != -1: - for i in range(interaction_idx, interaction_idx + 10): - if lines[i].startswith('offset_bottom = '): - lines[i] = 'offset_bottom = 282.9999\n' - break - -power_btn_start = -1 -power_btn_end = -1 -for i, line in enumerate(lines): - if line.startswith('[node name="PowerUpBtn" type="Button" parent="TouchLayer/TouchControls/PowerUpInventoryUI"'): - power_btn_start = i - # Need to check original parent too in case the earlier modification didn't change this string perfectly, wait! - # Ah, PowerUpBtn parent was originally "PowerUpInventoryUI". I did change PowerUpInventoryUI's parent... but did I change PowerUpBtn's parent text in `.tscn`? NO! PowerUpBtn is still `parent="PowerUpInventoryUI"`. - if line.startswith('[node name="PowerUpBtn" type="Button" parent="PowerUpInventoryUI"'): - power_btn_start = i - -if power_btn_start != -1: - for i in range(power_btn_start+1, len(lines)): - if lines[i].startswith('[node '): - power_btn_end = i - break - if power_btn_end == -1: - power_btn_end = len(lines) - -insert_target_start = -1 -if interaction_idx != -1: - for i in range(interaction_idx + 1, len(lines)): - if lines[i].startswith('[node '): - if 'parent="TouchLayer/TouchControls/InteractionBtn"' in lines[i]: - pass - else: - insert_target_start = i - break - -if power_btn_start != -1 and insert_target_start != -1: - block = lines[power_btn_start:power_btn_end] - block[0] = block[0].replace('parent="PowerUpInventoryUI"', 'parent="TouchLayer/TouchControls/InteractionBtn"') - - del lines[power_btn_start:power_btn_end] - - if insert_target_start > power_btn_end: - insert_target_start -= (power_btn_end - power_btn_start) - - lines = lines[:insert_target_start] + block + lines[insert_target_start:] - - with open(file_path, 'w', encoding='utf-8') as f: - f.writelines(lines) - print('Successfully moved PowerUpBtn and resized InteractionBtn') -else: - print(f'Failed to find targets: btn_start={power_btn_start}, insert_target={insert_target_start}, interaction_idx={interaction_idx}') diff --git a/move_ui.py b/move_ui.py deleted file mode 100644 index 77918dd..0000000 --- a/move_ui.py +++ /dev/null @@ -1,49 +0,0 @@ -import sys - -file_path = 'c:/Users/beng/Godot/Projects/tekton-enet/scenes/main.tscn' -with open(file_path, 'r', encoding='utf-8') as f: - lines = f.readlines() - -power_up_ui_start = -1 -power_up_ui_end = -1 -touch_controls_idx = -1 - -for i, line in enumerate(lines): - if line.startswith('[node name="PowerUpInventoryUI"'): - power_up_ui_start = i - elif power_up_ui_start != -1 and line.startswith('[node ') and power_up_ui_end == -1: - if 'parent="PowerUpInventoryUI"' not in line: - power_up_ui_end = i - - if line.startswith('[node name="TouchControls" type="Control" parent="TouchLayer"'): - touch_controls_idx = i - -if power_up_ui_end == -1: - power_up_ui_end = len(lines) - -if power_up_ui_start != -1 and touch_controls_idx != -1: - # Extract the block - block = lines[power_up_ui_start:power_up_ui_end] - - # Modify the parent - block[0] = block[0].replace('parent="."', 'parent="TouchLayer/TouchControls"') - - # Remove from original location - del lines[power_up_ui_start:power_up_ui_end] - - # Adjust touch_controls_idx if it was after the removed block - if touch_controls_idx > power_up_ui_end: - touch_controls_idx -= (power_up_ui_end - power_up_ui_start) - - # Find end of TouchControls block/properties to insert - insert_idx = touch_controls_idx + 1 - while insert_idx < len(lines) and not lines[insert_idx].startswith('['): - insert_idx += 1 - - lines = lines[:insert_idx] + block + lines[insert_idx:] - - with open(file_path, 'w', encoding='utf-8') as f: - f.writelines(lines) - print('Successfully moved PowerUpInventoryUI to TouchLayer/TouchControls') -else: - print(f'Failed to find nodes: star={power_up_ui_start}, touch={touch_controls_idx}') diff --git a/scenes/lobby.gd b/scenes/lobby.gd index 73de880..207c497 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -2,22 +2,33 @@ extends Control # UI References - Main Menu @onready var main_menu_panel = $MainMenuPanel -@onready var main_title = $MainMenuPanel/HiddenLogic/Title -@onready var username_label = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer/Username -@onready var main_subtitle = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer/Subtitle -@onready var create_room_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/CreateRoomBtn -@onready var browse_rooms_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/BrowseRoomsBtn -@onready var tutorial_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/TutorialBtn -@onready var main_menu_profile_btn = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/ProfileBtn -@onready var lobby_settings_btn = $MainMenuPanel/MainContainer/MarginContainer/TopRightPanel/SettingsBtn -@onready var quit_btn = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/QuitBtn +@onready var main_title = %Title +@onready var username_label = %Username +@onready var main_subtitle = %Subtitle +@onready var create_room_btn = %CreateRoomBtn +@onready var browse_rooms_btn = %BrowseRoomsBtn +@onready var tutorial_btn = %TutorialBtn +@onready var main_menu_profile_btn = %MainProfileBtn +@onready var avatar_display = %AvatarDisplay +@onready var lobby_settings_btn = %SettingsBtn +@onready var quit_btn = %QuitBtn + +# Main Menu 3D Preview +@onready var character_root = %CharacterRoot +@onready var anim_player = %AnimationPlayer + +# UI References - Currencies +@onready var gold_label = %GoldLabel +@onready var star_label = %StarLabel # UI References - Server Selection -@onready var server_option = $MainMenuPanel/HiddenLogic/ServerOption -@onready var server_ip_input = $MainMenuPanel/HiddenLogic/ServerIPInput +@onready var server_option = %ServerOption +@onready var server_ip_input = %ServerIPInput # Leaderboard Reference -@onready var leaderboard_btn = $MainMenuPanel/MainContainer/MarginContainer/TopRightPanel/LeaderboardBtn +@onready var leaderboard_btn = %LeaderboardBtn +@onready var shop_btn = %CartBtn +@onready var top_right_profile_btn = %ProfileBtn # UI References - Room List @onready var room_list_panel = $RoomListPanel @@ -108,9 +119,9 @@ const GLOBAL_CHAT_ROOM := "global_lobby" var _chat_channel = null var _chat_messages: Array = [] -@onready var chat_display: RichTextLabel = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ChatPanel/MarginContainer/RichTextLabel -@onready var chat_input: LineEdit = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/ChatInput -@onready var chat_send_btn: Button = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/SendBtn +@onready var chat_display: RichTextLabel = %RichTextLabel +@onready var chat_input: LineEdit = %ChatInput +@onready var chat_send_btn: Button = %SendBtn # Server Selection Controls (Now in tscn) # var server_option: OptionButton @@ -142,8 +153,15 @@ func _ready(): tutorial_btn.pressed.connect(_on_tutorial_pressed) if main_menu_profile_btn: main_menu_profile_btn.pressed.connect(_on_profile_btn_pressed) + if top_right_profile_btn: + top_right_profile_btn.pressed.connect(_on_profile_btn_pressed) if lobby_settings_btn: lobby_settings_btn.pressed.connect(_on_settings_pressed) + + # Shop Button + if shop_btn: + shop_btn.pressed.connect(_on_shop_pressed) + if leaderboard_btn: leaderboard_btn.pressed.connect(_on_leaderboard_pressed) if quit_btn: @@ -252,6 +270,33 @@ func _ready(): # Setup # ============================================================================= +const CHAR_NODE_MAP: Dictionary = { + "Copper": "Oldpop", + "Dabro": "Masbro", + "Gatot": "Gatot", + "Pip": "Bob" +} + +func _setup_3d_preview() -> void: + """Swaps out the active character inside the 3D MainMenu SubViewport.""" + if not character_root: + return + var target_character = UserProfileManager.profile.get("loadout_character", "Copper") + var node_name = CHAR_NODE_MAP.get(target_character, "Bob") + + for child in character_root.get_children(): + if child is Node3D: + child.visible = (child.name == node_name) + + if anim_player: + var new_root: Node3D = character_root.get_node_or_null(node_name) as Node3D + if new_root: + anim_player.root_node = new_root.get_path() + if anim_player.has_animation("animation-pack/idle"): + anim_player.play("animation-pack/idle") + elif anim_player.get_animation_list().size() > 0: + anim_player.play(anim_player.get_animation_list()[0]) + func _load_character_textures() -> void: """Load character preview textures.""" var characters = { @@ -672,12 +717,19 @@ func _on_profile_btn_pressed() -> void: if not profile_panel_instance: var profile_panel_scene := load("res://scenes/ui/profile_panel.tscn") profile_panel_instance = profile_panel_scene.instantiate() - profile_panel_instance.closed.connect(func(): profile_panel_instance.hide()) + profile_panel_instance.closed.connect(func(): + profile_panel_instance.hide() + if main_menu_panel: + main_menu_panel.show() + ) # Full-screen overlay — fill the entire lobby viewport profile_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(profile_panel_instance) - profile_panel_instance.show_panel() + if profile_panel_instance: + if main_menu_panel: + main_menu_panel.hide() + profile_panel_instance.show_panel() func _on_logout_pressed() -> void: AuthManager.logout() @@ -696,26 +748,60 @@ func _on_settings_pressed(): settings_menu.name = "SettingsMenu" add_child(settings_menu) - # Connect close button + # Connect close button — restore whichever panel was visible before var close_btn = settings_menu.get_node_or_null("PanelContainer/VBoxContainer/Header/CloseButton") if close_btn: - # settings_menu.gd handles basic visibility, but we can override or add to it - pass + close_btn.pressed.connect(func(): + if lobby_panel and lobby_panel.visible: + pass # lobby_panel restored below via _restore_panel + _restore_after_settings() + ) if settings_menu: + # Remember what is currently active + settings_menu.set_meta("from_lobby", lobby_panel.visible if lobby_panel else false) + main_menu_panel.hide() + if lobby_panel: lobby_panel.hide() settings_menu.open() +func _restore_after_settings() -> void: + var settings_menu = get_node_or_null("SettingsMenu") + var from_lobby: bool = settings_menu.get_meta("from_lobby", false) if settings_menu else false + if from_lobby: + if lobby_panel: lobby_panel.show() + else: + if main_menu_panel: main_menu_panel.show() + +func _on_shop_pressed() -> void: + if not NakamaManager.session: + connection_status.text = "Must be logged in" + return + + var shop_scene = load("res://scenes/ui/shop_panel.tscn") + if shop_scene: + var shop = shop_scene.instantiate() + add_child(shop) + if main_menu_panel: main_menu_panel.hide() + shop.closed.connect(func(): if main_menu_panel: main_menu_panel.show()) + shop.show_panel() + func _on_leaderboard_pressed() -> void: if not leaderboard_panel_instance: var leaderboard_panel_scene := load("res://scenes/ui/leaderboard_panel.tscn") if leaderboard_panel_scene: leaderboard_panel_instance = leaderboard_panel_scene.instantiate() - leaderboard_panel_instance.closed.connect(func(): leaderboard_panel_instance.hide()) + leaderboard_panel_instance.closed.connect(func(): + leaderboard_panel_instance.hide() + if main_menu_panel: + main_menu_panel.show() + ) # Full-screen overlay leaderboard_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(leaderboard_panel_instance) if leaderboard_panel_instance: + if main_menu_panel: + main_menu_panel.hide() leaderboard_panel_instance.show_panel() func _go_to_login() -> void: @@ -873,15 +959,22 @@ func _on_profile_updated() -> void: if username_label: username_label.text = display_name - if main_menu_profile_btn: + if avatar_display: var avatar_url = UserProfileManager.get_avatar_url() if ResourceLoader.exists(avatar_url): - main_menu_profile_btn.icon = load(avatar_url) - main_menu_profile_btn.expand_icon = true - main_menu_profile_btn.text = "" + avatar_display.texture = load(avatar_url) + if main_menu_profile_btn: + main_menu_profile_btn.text = "" + + if gold_label and UserProfileManager.is_profile_loaded: + gold_label.text = str(UserProfileManager.wallet.get("gold", 0)) + if star_label and UserProfileManager.is_profile_loaded: + star_label.text = str(UserProfileManager.wallet.get("star", 0)) # Sync to LobbyManager LobbyManager.set_player_name(display_name) + + _setup_3d_preview() # ============================================================================= # Player Slot Updates diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index c0bf236..28a1d55 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -3,8 +3,21 @@ [ext_resource type="Script" uid="uid://b5q6yekyk0tld" path="res://scenes/lobby.gd" id="1_lp6xi"] [ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="2_theme"] [ext_resource type="Texture2D" uid="uid://2d1ks5pmblc7" path="res://assets/graphics/main_menu/bg_back.png" id="3_q60fs"] +[ext_resource type="PackedScene" uid="uid://ejeamn0pyey4" path="res://assets/characters/Bob.glb" id="4_bob"] +[ext_resource type="PackedScene" uid="uid://d4cul3w3wem5w" path="res://assets/characters/Gatot.glb" id="4_gatot"] +[ext_resource type="PackedScene" uid="uid://1vk0mjnwkngi" path="res://assets/characters/Masbro.glb" id="4_masbro"] [ext_resource type="Texture2D" uid="uid://dvp0as6yyudco" path="res://assets/graphics/main_menu/bg_illust.png" id="4_nqcc7"] +[ext_resource type="PackedScene" uid="uid://bmln7v6v5kvxg" path="res://assets/characters/Oldpop.glb" id="4_oldpop"] +[ext_resource type="AnimationLibrary" uid="uid://c3pyopnwibckj" path="res://assets/characters/animations/animation-pack.res" id="5_animlib"] [ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="5_pc087"] +[ext_resource type="Texture2D" uid="uid://brhn1dhp1gm13" path="res://assets/graphics/character_selection/sc_characters/sc_copper.png" id="10_dyhay"] + +[sub_resource type="Environment" id="Env_preview"] +background_mode = 1 +background_color = Color(0, 0, 0, 0) +ambient_light_source = 2 +ambient_light_color = Color(1, 1, 1, 1) +ambient_light_energy = 0.5 [node name="Lobby" type="Control" unique_id=1490767889] layout_mode = 3 @@ -16,7 +29,17 @@ grow_vertical = 2 theme = ExtResource("2_theme") script = ExtResource("1_lp6xi") +[node name="Background3" type="ColorRect" parent="." unique_id=245238082] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.0627451, 0.0745098, 0.101961, 1) + [node name="Background" type="TextureRect" parent="." unique_id=767675801] +modulate = Color(1, 1, 1, 0.28235295) layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -38,226 +61,480 @@ texture = ExtResource("4_nqcc7") expand_mode = 2 flip_h = true -[node name="MainMenuPanel" type="CanvasLayer" parent="." unique_id=296519838] +[node name="MainMenuPanel" type="CanvasLayer" parent="." unique_id=1628604448] -[node name="MainContainer" type="Control" parent="MainMenuPanel" unique_id=1358259397] +[node name="ViewportWrapper" type="Control" parent="MainMenuPanel" unique_id=9084] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +size_flags_vertical = 3 -[node name="MarginContainer" type="MarginContainer" parent="MainMenuPanel/MainContainer" unique_id=396129054] +[node name="SubViewportContainer" type="SubViewportContainer" parent="MainMenuPanel/ViewportWrapper" unique_id=90485] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +stretch = true + +[node name="PreviewViewport" type="SubViewport" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer" unique_id=904850] +unique_name_in_owner = true +own_world_3d = true +transparent_bg = true +handle_input_locally = false +size = Vector2i(1366, 720) +render_target_update_mode = 4 + +[node name="WorldEnvironment" type="WorldEnvironment" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport" unique_id=253593246] +environment = SubResource("Env_preview") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport" unique_id=129901731] +transform = Transform3D(0.866025, -0.25, 0.433013, 0, 0.866025, 0.5, -0.5, -0.433013, 0.75, 1, 4, 0) +light_energy = 1.5 + +[node name="FillLight" type="OmniLight3D" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport" unique_id=9048] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2, 2) +light_color = Color(0.4, 0.55, 1, 1) +light_energy = 0.6 +omni_range = 10.0 + +[node name="Camera3D" type="Camera3D" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport" unique_id=904] +transform = Transform3D(1, 0, 0, 0, 0.970728, 0.24018, 0, -0.24018, 0.970728, 0, 0.972868, 3.4) +current = true +fov = 38.0 + +[node name="CharacterRoot" type="Node3D" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport" unique_id=1661304167] +unique_name_in_owner = true + +[node name="Masbro" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport/CharacterRoot" unique_id=1689619097 instance=ExtResource("4_masbro")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) +visible = false + +[node name="Bob" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport/CharacterRoot" unique_id=753190814 instance=ExtResource("4_bob")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.484, 0) +visible = false + +[node name="Gatot" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport/CharacterRoot" unique_id=145617195 instance=ExtResource("4_gatot")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) +visible = false + +[node name="Oldpop" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport/CharacterRoot" unique_id=886481313 instance=ExtResource("4_oldpop")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) + +[node name="AnimationPlayer" type="AnimationPlayer" parent="MainMenuPanel/ViewportWrapper/SubViewportContainer/PreviewViewport/CharacterRoot" unique_id=790589799] +unique_name_in_owner = true +root_node = NodePath("../Oldpop") +libraries/animation-pack = ExtResource("5_animlib") +autoplay = &"animation-pack/idle" + +[node name="MainMargin" type="MarginContainer" parent="MainMenuPanel" unique_id=534985223] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 theme_override_constants/margin_left = 30 theme_override_constants/margin_top = 30 theme_override_constants/margin_right = 30 theme_override_constants/margin_bottom = 30 -[node name="LeftPanel" type="VBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer" unique_id=1627330537] +[node name="MainHBox" type="HBoxContainer" parent="MainMenuPanel/MainMargin" unique_id=892534882] layout_mode = 2 -size_flags_horizontal = 0 +theme_override_constants/separation = 16 -[node name="ProfileBox" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=2112406804] +[node name="LeftCol" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox" unique_id=53823485] +custom_minimum_size = Vector2(350, 0) layout_mode = 2 -theme_override_constants/separation = 10 +theme_override_constants/separation = 8 -[node name="ProfileBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox" unique_id=1711074288] -custom_minimum_size = Vector2(80, 80) +[node name="HBoxContainer2" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=473798164] layout_mode = 2 -text = "ICON" -[node name="VBoxContainer" type="VBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox" unique_id=1812465381] +[node name="ProfileCard" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2" unique_id=5938459] +clip_contents = true +custom_minimum_size = Vector2(0, 130) layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") + +[node name="HBox" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard" unique_id=58934509] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="MainProfileBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard/HBox" unique_id=89234859] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 100) +layout_mode = 2 +size_flags_vertical = 4 +flat = true +expand_icon = true + +[node name="AvatarDisplay" type="TextureRect" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard/HBox/MainProfileBtn" unique_id=593485890] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("10_dyhay") +expand_mode = 1 +stretch_mode = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard/HBox" unique_id=598345789] +layout_mode = 2 +size_flags_horizontal = 3 alignment = 1 -[node name="Username" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer" unique_id=499855830] +[node name="Username" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard/HBox/VBoxContainer" unique_id=50934858] +unique_name_in_owner = true layout_mode = 2 -theme_override_font_sizes/font_size = 24 +theme = ExtResource("2_theme") +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 text = "USERNAME" -[node name="Subtitle" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer" unique_id=2081013344] +[node name="Subtitle" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2/ProfileCard/HBox/VBoxContainer" unique_id=5948395] +unique_name_in_owner = true layout_mode = 2 -theme_override_colors/font_color = Color(0.71, 0.575, 0.035, 1) -theme_override_font_sizes/font_size = 18 +theme_override_colors/font_color = Color(0.7, 0.6, 0.2, 1) +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 14 text = "EU SERVER" -[node name="Spacer" type="Control" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=2013644806] -custom_minimum_size = Vector2(0, 20) +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2" unique_id=177944371] +custom_minimum_size = Vector2(80, 0) +layout_direction = 3 layout_mode = 2 -[node name="CurrenciesBox" type="VBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=1252464073] +[node name="SpacerProfile" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=58943589] +custom_minimum_size = Vector2(0, 8) layout_mode = 2 -theme_override_constants/separation = 10 -[node name="StarPanel" type="PanelContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox" unique_id=1482932751] -custom_minimum_size = Vector2(160, 40) +[node name="CurrencyRow" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=59384589] layout_mode = 2 -size_flags_horizontal = 0 +theme_override_constants/separation = 8 -[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/StarPanel" unique_id=1113231570] -layout_mode = 2 -theme_override_constants/separation = 10 -alignment = 1 - -[node name="Icon" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/StarPanel/HBoxContainer" unique_id=1267791073] -layout_mode = 2 -text = "⭐" - -[node name="Label" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/StarPanel/HBoxContainer" unique_id=2117232678] -layout_mode = 2 -text = "23891" - -[node name="GoldPanel" type="PanelContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox" unique_id=948555205] -custom_minimum_size = Vector2(160, 40) -layout_mode = 2 -size_flags_horizontal = 0 - -[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/GoldPanel" unique_id=788193079] -layout_mode = 2 -theme_override_constants/separation = 10 -alignment = 1 - -[node name="Icon" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/GoldPanel/HBoxContainer" unique_id=131964257] -layout_mode = 2 -text = "💰" - -[node name="Label" type="Label" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox/GoldPanel/HBoxContainer" unique_id=1973438143] -layout_mode = 2 -text = "3217" - -[node name="ChatSpacer" type="Control" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=1632908247] +[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=1807032398] layout_mode = 2 size_flags_vertical = 3 -[node name="ChatPanel" type="PanelContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=1582318454] -custom_minimum_size = Vector2(350, 200) -layout_mode = 2 - -[node name="MarginContainer" type="MarginContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ChatPanel" unique_id=1866798800] -layout_mode = 2 -theme_override_constants/margin_left = 15 -theme_override_constants/margin_top = 15 -theme_override_constants/margin_right = 15 -theme_override_constants/margin_bottom = 15 - -[node name="RichTextLabel" type="RichTextLabel" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ChatPanel/MarginContainer" unique_id=1563684167] -layout_mode = 2 -bbcode_enabled = true -scroll_following = true -text = "" - -[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=976080441] -layout_mode = 2 -theme_override_constants/separation = 10 - -[node name="QuitBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer" unique_id=1499547561] -custom_minimum_size = Vector2(40, 40) -layout_mode = 2 -text = "<]" - -[node name="ChatInput" type="LineEdit" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer" unique_id=1511957602] +[node name="StarPanel" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer" unique_id=509438] layout_mode = 2 size_flags_horizontal = 3 -placeholder_text = " type to chat" +theme = ExtResource("2_theme") -[node name="SendBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer" unique_id=1337641536] +[node name="Margin" type="MarginContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer/StarPanel" unique_id=609485] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 4 + +[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer/StarPanel/Margin" unique_id=69485094] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Icon" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer/StarPanel/Margin/HBoxContainer" unique_id=958438] +layout_mode = 2 +theme_override_colors/font_color = Color(0.9, 0.7, 0.3, 1) +theme_override_font_sizes/font_size = 18 +text = "✦" + +[node name="StarLabel" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer/StarPanel/Margin/HBoxContainer" unique_id=948509] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 +text = "0" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer" unique_id=255421565] +custom_minimum_size = Vector2(180, 0) +layout_direction = 3 +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=1399423386] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GoldPanel" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2" unique_id=9458940] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") + +[node name="Margin" type="MarginContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2/GoldPanel" unique_id=94589] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 4 + +[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2/GoldPanel/Margin" unique_id=9458094] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Icon" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2/GoldPanel/Margin/HBoxContainer" unique_id=9485] +layout_mode = 2 +theme_override_colors/font_color = Color(0.8, 0.6, 0.2, 1) +theme_override_font_sizes/font_size = 18 +text = "▤" + +[node name="GoldLabel" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2/GoldPanel/Margin/HBoxContainer" unique_id=94850] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 +text = "0" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer2" unique_id=320833893] +custom_minimum_size = Vector2(180, 0) +layout_direction = 3 +layout_mode = 2 + +[node name="SpacerMiddle" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=984509] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ChatPanel" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=948590] +custom_minimum_size = Vector2(360, 160) +layout_mode = 2 +theme = ExtResource("2_theme") + +[node name="MarginContainer" type="MarginContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/ChatPanel" unique_id=945890] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 8 + +[node name="RichTextLabel" type="RichTextLabel" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/ChatPanel/MarginContainer" unique_id=1730680016] +unique_name_in_owner = true +layout_mode = 2 +theme = ExtResource("2_theme") +theme_override_colors/default_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +bbcode_enabled = true +scroll_following = true + +[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=459038] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="QuitBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer" unique_id=1853395708] +unique_name_in_owner = true custom_minimum_size = Vector2(40, 40) layout_mode = 2 +theme = ExtResource("2_theme") +text = "←" + +[node name="ChatInput" type="LineEdit" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer" unique_id=1346390421] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 20 +placeholder_text = " type to chat" + +[node name="SendBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer" unique_id=1491048704] +unique_name_in_owner = true +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +theme = ExtResource("2_theme") text = ">" -[node name="TopRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer" unique_id=377986819] +[node name="CenterCol" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox" unique_id=7006870] +clip_contents = true layout_mode = 2 -size_flags_horizontal = 8 -size_flags_vertical = 0 -theme_override_constants/separation = 15 +size_flags_horizontal = 3 -[node name="CartBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=2015877617] -custom_minimum_size = Vector2(60, 60) +[node name="RightCol" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox" unique_id=1457624180] +custom_minimum_size = Vector2(280, 0) layout_mode = 2 -text = "🛒" -[node name="TicketBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=2142342229] -custom_minimum_size = Vector2(60, 60) +[node name="TopRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=20449742] +custom_minimum_size = Vector2(500, 0) layout_mode = 2 -text = "🎟" +theme = ExtResource("2_theme") +theme_override_constants/separation = 12 +alignment = 2 -[node name="SocialBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=23817931] -custom_minimum_size = Vector2(60, 60) +[node name="ProfileBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=752873406] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) layout_mode = 2 -text = "👥" +theme_override_fonts/font = ExtResource("5_pc087") +text = "PROFILE" -[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=1742815055] -custom_minimum_size = Vector2(60, 60) +[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=312524216] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) layout_mode = 2 -text = "🏆" +theme_override_fonts/font = ExtResource("5_pc087") +text = "LEADERBOARD" -[node name="SettingsBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=439499917] -custom_minimum_size = Vector2(60, 60) +[node name="CartBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=456149005] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) layout_mode = 2 -text = "⚙" +theme_override_fonts/font = ExtResource("5_pc087") +text = "SHOP" -[node name="RightPanel" type="VBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer" unique_id=1806183556] +[node name="TicketBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=64042310] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) layout_mode = 2 -size_flags_horizontal = 8 -size_flags_vertical = 4 -theme_override_constants/separation = 20 +theme_override_fonts/font = ExtResource("5_pc087") +disabled = true +text = "BATTLE PASS" -[node name="Banner1" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=1918689135] -custom_minimum_size = Vector2(250, 120) +[node name="SocialBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=82719328] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) layout_mode = 2 +theme_override_fonts/font = ExtResource("5_pc087") +disabled = true +text = "SOCIAL" + +[node name="SettingsBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=90] +unique_name_in_owner = true +custom_minimum_size = Vector2(44, 44) +layout_mode = 2 +theme_override_fonts/font = ExtResource("5_pc087") +text = "SETTINGS" + +[node name="Spacer1" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=9023] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Banners" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=1356766829] +layout_mode = 2 +theme_override_constants/separation = 16 +alignment = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners" unique_id=160134239] +layout_mode = 2 +alignment = 2 + +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer" unique_id=1128600858] +clip_contents = true +custom_minimum_size = Vector2(350, 0) +layout_direction = 3 +layout_mode = 2 + +[node name="Banner1" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer" unique_id=5676924] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 120) +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 30 +disabled = true text = "Banner Slot" -[node name="Banner2" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=336081993] -custom_minimum_size = Vector2(250, 120) +[node name="HBoxContainer2" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners" unique_id=1932828178] layout_mode = 2 +alignment = 2 + +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer2" unique_id=1409766358] +clip_contents = true +custom_minimum_size = Vector2(350, 0) +layout_direction = 3 +layout_mode = 2 + +[node name="Banner2" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer2" unique_id=695110150] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 120) +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 30 +disabled = true text = "Banner Slot" -[node name="Banner3" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=942094893] -custom_minimum_size = Vector2(250, 120) +[node name="HBoxContainer3" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners" unique_id=1502007727] layout_mode = 2 +alignment = 2 + +[node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer3" unique_id=249145750] +clip_contents = true +custom_minimum_size = Vector2(350, 0) +layout_direction = 3 +layout_mode = 2 + +[node name="Banner3" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners/HBoxContainer3" unique_id=945] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 120) +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 30 +disabled = true text = "Banner Slot" -[node name="BottomRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainContainer/MarginContainer" unique_id=923616751] +[node name="Spacer2" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=324] layout_mode = 2 -size_flags_horizontal = 8 -size_flags_vertical = 8 -theme_override_constants/separation = 15 +size_flags_vertical = 3 -[node name="TutorialBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1468068637] -custom_minimum_size = Vector2(160, 60) +[node name="BottomRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=987] layout_mode = 2 -theme_override_font_sizes/font_size = 22 +theme_override_constants/separation = 12 + +[node name="TutorialBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel" unique_id=951866518] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 text = "TUTORIAL" -[node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1069893052] -custom_minimum_size = Vector2(160, 60) +[node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel" unique_id=8] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 50) layout_mode = 2 -theme_override_font_sizes/font_size = 22 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 text = "HOST" -[node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1488596739] -custom_minimum_size = Vector2(160, 60) +[node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel" unique_id=4595] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 50) layout_mode = 2 -theme_override_font_sizes/font_size = 22 +size_flags_horizontal = 3 +theme = ExtResource("2_theme") +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 18 text = "BROWSE" -[node name="HiddenLogic" type="Control" parent="MainMenuPanel" unique_id=1708805146] +[node name="HiddenLogic" type="Control" parent="MainMenuPanel" unique_id=345] visible = false layout_mode = 3 anchors_preset = 0 -[node name="Title" type="Label" parent="MainMenuPanel/HiddenLogic" unique_id=586152975] +[node name="Title" type="Label" parent="MainMenuPanel/HiddenLogic" unique_id=534] +unique_name_in_owner = true layout_mode = 0 text = "TEKTON DASH" -[node name="ServerOption" type="OptionButton" parent="MainMenuPanel/HiddenLogic" unique_id=837593831] +[node name="ServerOption" type="OptionButton" parent="MainMenuPanel/HiddenLogic" unique_id=645] +unique_name_in_owner = true layout_mode = 0 selected = 3 item_count = 4 @@ -270,7 +547,8 @@ popup/item_2/id = 2 popup/item_3/text = "Tekton Dash EU" popup/item_3/id = 3 -[node name="ServerIPInput" type="LineEdit" parent="MainMenuPanel/HiddenLogic" unique_id=690927860] +[node name="ServerIPInput" type="LineEdit" parent="MainMenuPanel/HiddenLogic" unique_id=1616424715] +unique_name_in_owner = true layout_mode = 0 text = "127.0.0.1" @@ -346,6 +624,7 @@ layout_mode = 2 text = "BACK" [node name="ProfileBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=1473526002] +visible = false custom_minimum_size = Vector2(80, 44) layout_mode = 2 text = "PROFILE" @@ -405,6 +684,7 @@ layout_mode = 2 theme_override_constants/separation = 10 [node name="ProfileBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1726139031] +visible = false custom_minimum_size = Vector2(80, 32) layout_mode = 2 theme_override_fonts/font = ExtResource("5_pc087") @@ -412,6 +692,7 @@ theme_override_font_sizes/font_size = 11 text = "Profile" [node name="LogoutBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1221873519] +visible = false custom_minimum_size = Vector2(70, 32) layout_mode = 2 theme_override_fonts/font = ExtResource("5_pc087") diff --git a/scenes/main.tscn b/scenes/main.tscn index b7a5dc7..183b94e 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -35,6 +35,7 @@ [ext_resource type="Texture2D" uid="uid://bowng12514p53" path="res://assets/graphics/gui/rankings/3rd.png" id="29_pibwh"] [ext_resource type="Texture2D" uid="uid://biun2yvglxgij" path="res://assets/graphics/touch_control/grab_tekton.png" id="36_pibwh"] [ext_resource type="Texture2D" uid="uid://bucwm17lfd131" path="res://assets/graphics/gui/help icon.png" id="37_pibwh"] +[ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="38_5he1u"] [ext_resource type="Texture2D" uid="uid://da3yx87ar7kcj" path="res://assets/graphics/gui/setting_icon.png" id="38_c6pm6"] [ext_resource type="Script" uid="uid://86ikh0wuqk7v" path="res://scripts/ui/powerup_inventory_ui.gd" id="powerup_ui_script"] [ext_resource type="Script" uid="uid://b54tfa0n6kogi" path="res://scripts/managers/touch_controls.gd" id="touch_manager"] @@ -96,6 +97,10 @@ content_margin_left = 33.0 content_margin_bottom = 20.0 texture = SubResource("CompressedTexture2D_chjal") +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5poiv"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_2cjbq"] + [node name="Main" type="Node3D" unique_id=864552263] script = ExtResource("1_xcpe3") @@ -1746,10 +1751,12 @@ offset_right = 150.0 offset_bottom = 150.0 grow_horizontal = 2 grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_5poiv") [node name="NetworkPanel" type="Panel" parent="PauseMenu/Panel" unique_id=935918250] layout_mode = 2 -theme_override_styles/panel = ExtResource("5_dvx6y") +theme = ExtResource("38_5he1u") +theme_override_styles/panel = SubResource("StyleBoxEmpty_2cjbq") [node name="NetworkInfo" type="HBoxContainer" parent="PauseMenu/Panel/NetworkPanel" unique_id=1194782604] visible = false @@ -1778,11 +1785,13 @@ vertical_alignment = 1 [node name="VBox" type="VBoxContainer" parent="PauseMenu/Panel" unique_id=837757440] layout_mode = 2 +theme = ExtResource("38_5he1u") theme_override_constants/separation = 15 alignment = 1 [node name="Title" type="Label" parent="PauseMenu/Panel/VBox" unique_id=1740539610] layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") theme_override_font_sizes/font_size = 24 text = "Menu" horizontal_alignment = 1 @@ -1794,22 +1803,26 @@ layout_mode = 2 [node name="ResumeBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=79086881] custom_minimum_size = Vector2(200, 45) layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") text = "Resume" [node name="HowToPlayBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=987654321] visible = false custom_minimum_size = Vector2(200, 45) layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") text = "How to Play" [node name="SettingsBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=215865401] custom_minimum_size = Vector2(200, 45) layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") text = "Settings" [node name="QuitBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=1771850243] custom_minimum_size = Vector2(200, 45) layout_mode = 2 +theme_override_fonts/font = ExtResource("13_j8jky") text = "Quit Match" [node name="HowToPlayPanel" type="CanvasLayer" parent="." unique_id=123456789] @@ -1837,6 +1850,7 @@ offset_right = 300.0 offset_bottom = 250.0 grow_horizontal = 2 grow_vertical = 2 +theme = ExtResource("38_5he1u") [node name="VBox" type="VBoxContainer" parent="HowToPlayPanel/Panel" unique_id=123456792] layout_mode = 2 @@ -1845,6 +1859,7 @@ alignment = 1 [node name="Title" type="Label" parent="HowToPlayPanel/Panel/VBox" unique_id=123456793] layout_mode = 2 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) theme_override_font_sizes/font_size = 22 text = "How to Play" horizontal_alignment = 1 @@ -1864,6 +1879,7 @@ metadata/_tab_index = 0 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Free Mode" unique_id=123456796] layout_mode = 2 +theme_override_colors/default_color = Color(0.5686275, 0.36862746, 0.12941177, 1) bbcode_enabled = true text = "[b]Free Mode[/b] diff --git a/scenes/player.gd b/scenes/player.gd index 562b189..a47aae4 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -500,6 +500,10 @@ func set_character(character_name: String) -> void: # Apply outline shader to the active character if active_character: _apply_outline_recursive(active_character) + + # Apply cosmetic loadout to active model (local auth only) + if active_character and is_multiplayer_authority(): + apply_loadout(active_character) func _apply_outline_recursive(node: Node): if node is MeshInstance3D and node.mesh: @@ -517,6 +521,22 @@ func _apply_outline_recursive(node: Node): for child in node.get_children(): _apply_outline_recursive(child) +func apply_loadout(character_node: Node3D) -> void: + """Apply equipped cosmetics from UserProfileManager.loadout onto the active character model. + Items are expected as child nodes named matching the item ID (e.g. 'head_hat1', 'acc_glasses'). + All nodes in the cosmetic category groups are hidden first, then the equipped one is shown.""" + if not has_node("/root/UserProfileManager"): + return + + var loadout: Dictionary = UserProfileManager.loadout + + for category in ["head", "costume", "glove", "accessory"]: + var equipped: String = loadout.get(category, "") + for child in character_node.get_children(): + # Only manage nodes that start with the category prefix + if child.name.begins_with(category): + child.visible = (child.name == equipped) + @rpc("any_peer", "call_local", "reliable") func sync_character(character_name: String) -> void: """Sync character selection across all clients.""" diff --git a/scenes/ui/login_screen.tscn b/scenes/ui/login_screen.tscn index c067f36..b00c2cf 100644 --- a/scenes/ui/login_screen.tscn +++ b/scenes/ui/login_screen.tscn @@ -186,12 +186,14 @@ layout_mode = 2 text = "Sign In" [node name="SocialLabel" type="Label" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=840440943] +visible = false layout_mode = 2 theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1) text = "─────── or continue with ───────" horizontal_alignment = 1 [node name="SocialButtons" type="HBoxContainer" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=1899762021] +visible = false layout_mode = 2 theme_override_constants/separation = 12 alignment = 1 diff --git a/scenes/ui/profile_panel.tscn b/scenes/ui/profile_panel.tscn index 7fc994d..539b345 100644 --- a/scenes/ui/profile_panel.tscn +++ b/scenes/ui/profile_panel.tscn @@ -2,19 +2,21 @@ [ext_resource type="Script" uid="uid://y6wswtalyiho" path="res://scripts/ui/profile_panel.gd" id="1"] [ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="2"] -[ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="3_e13i6"] +[ext_resource type="Texture2D" uid="uid://2d1ks5pmblc7" path="res://assets/graphics/main_menu/bg_back.png" id="3_0vpw4"] +[ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="3_font"] [ext_resource type="PackedScene" uid="uid://ejeamn0pyey4" path="res://assets/characters/Bob.glb" id="4_bob"] [ext_resource type="PackedScene" uid="uid://d4cul3w3wem5w" path="res://assets/characters/Gatot.glb" id="4_gatot"] [ext_resource type="PackedScene" uid="uid://1vk0mjnwkngi" path="res://assets/characters/Masbro.glb" id="4_masbro"] [ext_resource type="PackedScene" uid="uid://bmln7v6v5kvxg" path="res://assets/characters/Oldpop.glb" id="4_oldpop"] +[ext_resource type="Texture2D" uid="uid://brhn1dhp1gm13" path="res://assets/graphics/character_selection/sc_characters/sc_copper.png" id="4_pti1t"] [ext_resource type="AnimationLibrary" uid="uid://c3pyopnwibckj" path="res://assets/characters/animations/animation-pack.res" id="5_animlib"] -[sub_resource type="Environment" id="Environment_preview"] +[sub_resource type="Environment" id="Env_preview"] background_mode = 1 -background_color = Color(0.08, 0.09, 0.12, 1) +background_color = Color(0.06, 0.07, 0.1, 1) ambient_light_source = 2 -ambient_light_color = Color(0.6, 0.65, 0.8, 1) -ambient_light_energy = 0.6 +ambient_light_color = Color(0.55, 0.65, 0.9, 1) +ambient_light_energy = 0.7 [node name="ProfilePanel" type="Control" unique_id=229091481] layout_mode = 3 @@ -26,367 +28,748 @@ grow_vertical = 2 theme = ExtResource("2") script = ExtResource("1") -[node name="Background" type="ColorRect" parent="." unique_id=650894265] +[node name="Background" type="ColorRect" parent="." unique_id=1526402211] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -color = Color(0.05, 0.06, 0.09, 0.97) +color = Color(0.0627451, 0.0745098, 0.101961, 1) -[node name="MainLayout" type="HBoxContainer" parent="." unique_id=730259035] +[node name="Background2" type="TextureRect" parent="." unique_id=1984021675] +modulate = Color(1, 1, 1, 0.28235295) layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_constants/separation = 0 +texture = ExtResource("3_0vpw4") +expand_mode = 2 -[node name="LeftPanel" type="PanelContainer" parent="MainLayout" unique_id=282453419] -custom_minimum_size = Vector2(420, 0) -layout_mode = 2 -size_flags_horizontal = 3 +[node name="MainMargin" type="MarginContainer" parent="." unique_id=949618423] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 30 +theme_override_constants/margin_bottom = 30 -[node name="LeftVBox" type="VBoxContainer" parent="MainLayout/LeftPanel" unique_id=322585038] +[node name="MainHBox" type="HBoxContainer" parent="MainMargin" unique_id=1978926863] layout_mode = 2 theme_override_constants/separation = 16 -[node name="Header" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1587238635] +[node name="LeftCol" type="VBoxContainer" parent="MainMargin/MainHBox" unique_id=843498295] +custom_minimum_size = Vector2(240, 0) +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="ProfileCard" type="PanelContainer" parent="MainMargin/MainHBox/LeftCol" unique_id=1335998802] layout_mode = 2 -[node name="BackBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=303437283] +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/LeftCol/ProfileCard" unique_id=854167006] +layout_mode = 2 + +[node name="HBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin" unique_id=2103985860] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="AvatarDisplay" type="TextureRect" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox" unique_id=2014103516] unique_name_in_owner = true -custom_minimum_size = Vector2(44, 44) -layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "← BACK" - -[node name="Title" type="Label" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=1630320969] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0.41568628, 0.6392157, 0.14117648, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -theme_override_font_sizes/font_size = 26 -text = "PROFILE & LOADOUT" - -[node name="Separator0" type="HSeparator" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1757294445] -layout_mode = 2 - -[node name="AvatarSection" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=579456932] -layout_mode = 2 -theme_override_constants/separation = 16 - -[node name="AvatarDisplay" type="TextureRect" parent="MainLayout/LeftPanel/LeftVBox/AvatarSection" unique_id=1604724524] -unique_name_in_owner = true -custom_minimum_size = Vector2(80, 80) +custom_minimum_size = Vector2(100, 100) layout_mode = 2 +texture = ExtResource("4_pti1t") expand_mode = 1 stretch_mode = 5 -[node name="AvatarButtons" type="VBoxContainer" parent="MainLayout/LeftPanel/LeftVBox/AvatarSection" unique_id=208948821] +[node name="VBox" type="VBoxContainer" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox" unique_id=1314733992] layout_mode = 2 size_flags_horizontal = 3 -theme_override_constants/separation = 8 +theme_override_constants/separation = 6 -[node name="AccountType" type="Label" parent="MainLayout/LeftPanel/LeftVBox/AvatarSection/AvatarButtons" unique_id=1334846162] +[node name="NameRow" type="HBoxContainer" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox/VBox" unique_id=286365590] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="DisplayNameInput" type="LineEdit" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox/VBox/NameRow" unique_id=918086953] unique_name_in_owner = true -layout_mode = 2 -theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Account: Guest" - -[node name="ChangeAvatarBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/AvatarSection/AvatarButtons" unique_id=215076479] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 40) -layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Change Avatar" - -[node name="DisplayNameSection" type="VBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1669216784] -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="Label" type="Label" parent="MainLayout/LeftPanel/LeftVBox/DisplayNameSection" unique_id=792928394] -layout_mode = 2 -theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Display Name" - -[node name="HBox" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox/DisplayNameSection" unique_id=1713677971] -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="DisplayNameInput" type="LineEdit" parent="MainLayout/LeftPanel/LeftVBox/DisplayNameSection/HBox" unique_id=1087692485] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) +custom_minimum_size = Vector2(100, 32) layout_mode = 2 size_flags_horizontal = 3 -theme_override_fonts/font = ExtResource("3_e13i6") -max_length = 50 +theme_override_fonts/font = ExtResource("3_font") +placeholder_text = "Username" +max_length = 6 -[node name="SaveNameBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/DisplayNameSection/HBox" unique_id=518731018] +[node name="SaveNameBtn" type="Button" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox/VBox/NameRow" unique_id=856176583] unique_name_in_owner = true -custom_minimum_size = Vector2(80, 44) +custom_minimum_size = Vector2(48, 32) layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") +theme_override_fonts/font = ExtResource("3_font") text = "Save" -[node name="Separator1" type="HSeparator" parent="MainLayout/LeftPanel/LeftVBox" unique_id=2142486834] +[node name="ChangeAvatarBtn" type="Button" parent="MainMargin/MainHBox/LeftCol/ProfileCard/Margin/HBox/VBox" unique_id=1415839292] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 28) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "Change Avatar" + +[node name="CurrencyRow" type="HBoxContainer" parent="MainMargin/MainHBox/LeftCol" unique_id=23456385] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="StarPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=1253486930] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/StarPanel" unique_id=1653822675] layout_mode = 2 -[node name="LoadoutSection" type="VBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=109456880] +[node name="HBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/StarPanel/Margin" unique_id=304844268] layout_mode = 2 -theme_override_constants/separation = 10 +theme_override_constants/separation = 8 -[node name="LoadoutTitle" type="Label" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection" unique_id=1249647867] +[node name="Icon" type="Label" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/StarPanel/Margin/HBox" unique_id=1733501] layout_mode = 2 -theme_override_colors/font_color = Color(0.41568628, 0.6392157, 0.14117648, 1) -theme_override_fonts/font = ExtResource("3_e13i6") +theme_override_colors/font_color = Color(0.9, 0.7, 0.3, 1) theme_override_font_sizes/font_size = 18 -text = "DEFAULT CHARACTER (LOADOUT)" +text = "✦" -[node name="LoadoutHint" type="Label" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection" unique_id=1773015369] +[node name="StarLabel" type="Label" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/StarPanel/Margin/HBox" unique_id=1536697694] +unique_name_in_owner = true layout_mode = 2 -theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1) -theme_override_font_sizes/font_size = 11 -text = "This character will be auto-selected when you join or create a room." -autowrap_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 18 +text = "0" +horizontal_alignment = 2 -[node name="CharacterSelector" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection" unique_id=1852188787] +[node name="GoldPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=289720528] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/GoldPanel" unique_id=1374150600] +layout_mode = 2 + +[node name="HBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/GoldPanel/Margin" unique_id=1558907810] layout_mode = 2 theme_override_constants/separation = 8 alignment = 1 -[node name="CharLeftBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection/CharacterSelector" unique_id=1225290843] -unique_name_in_owner = true -custom_minimum_size = Vector2(44, 44) +[node name="Icon" type="Label" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/GoldPanel/Margin/HBox" unique_id=351462654] layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "◀" +theme_override_colors/font_color = Color(0.8, 0.6, 0.2, 1) +theme_override_font_sizes/font_size = 18 +text = "▤" -[node name="LoadoutCharName" type="Label" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection/CharacterSelector" unique_id=1258961866] +[node name="GoldLabel" type="Label" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/GoldPanel/Margin/HBox" unique_id=1517793393] unique_name_in_owner = true -custom_minimum_size = Vector2(120, 44) layout_mode = 2 size_flags_horizontal = 3 -theme_override_colors/font_color = Color(1, 1, 1, 1) -theme_override_fonts/font = ExtResource("3_e13i6") +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("3_font") theme_override_font_sizes/font_size = 18 -text = "Copper" -horizontal_alignment = 1 -vertical_alignment = 1 +text = "0" +horizontal_alignment = 2 -[node name="CharRightBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection/CharacterSelector" unique_id=320910811] -unique_name_in_owner = true -custom_minimum_size = Vector2(44, 44) -layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "▶" - -[node name="SetDefaultBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection" unique_id=1404065032] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) -layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "✓ SET AS DEFAULT" - -[node name="Separator2" type="HSeparator" parent="MainLayout/LeftPanel/LeftVBox" unique_id=565201523] +[node name="StatsPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftCol" unique_id=1663081199] layout_mode = 2 -[node name="StatsSection" type="VBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1747237537] +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/LeftCol/StatsPanel" unique_id=1581044063] layout_mode = 2 -theme_override_constants/separation = 6 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 12 -[node name="StatsTitle" type="Label" parent="MainLayout/LeftPanel/LeftVBox/StatsSection" unique_id=1171799022] +[node name="VBox" type="VBoxContainer" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin" unique_id=73009603] layout_mode = 2 -theme_override_colors/font_color = Color(0.41568628, 0.6392157, 0.14117648, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -theme_override_font_sizes/font_size = 16 -text = "Statistics" +theme_override_constants/separation = 4 -[node name="GamesPlayed" type="Label" parent="MainLayout/LeftPanel/LeftVBox/StatsSection" unique_id=440203237] +[node name="AccountType" type="Label" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=1583438808] unique_name_in_owner = true layout_mode = 2 -theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Games Played: 0" +theme_override_colors/font_color = Color(0.7, 0.5, 0.3, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 13 +text = "Account : Email" -[node name="WinRate" type="Label" parent="MainLayout/LeftPanel/LeftVBox/StatsSection" unique_id=1940835558] +[node name="LinkAccountBtn" type="Button" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=779455472] +unique_name_in_owner = true +visible = false +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "Link Email" + +[node name="GamesPlayed" type="Label" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=279949601] unique_name_in_owner = true layout_mode = 2 -theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Win Rate: 0%" +theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 13 +text = "Games Played: 8" -[node name="HighScore" type="Label" parent="MainLayout/LeftPanel/LeftVBox/StatsSection" unique_id=12845342] +[node name="WinRate" type="Label" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=1698973042] unique_name_in_owner = true layout_mode = 2 -theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "High Score: 0" +theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 13 +text = "Win Rate: 62.5%" -[node name="AccountSection" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1267378790] -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="LinkAccountBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/AccountSection" unique_id=1126355767] +[node name="HighScore" type="Label" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=1418839301] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Link Email (Keep Progress)" +theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 13 +text = "High Score: 14050" -[node name="AdminPanelBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/AccountSection" unique_id=987654321] +[node name="Spacer" type="Control" parent="MainMargin/MainHBox/LeftCol" unique_id=899023204] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="AccSettingsBtn" type="Button" parent="MainMargin/MainHBox/LeftCol" unique_id=114904148] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "Account Setting" + +[node name="AdminPanelBtn" type="Button" parent="MainMargin/MainHBox/LeftCol" unique_id=1155262037] +unique_name_in_owner = true +visible = false layout_mode = 2 theme_override_colors/font_color = Color(1, 0.4, 0.4, 1) -theme_override_fonts/font = ExtResource("3_e13i6") -text = "Server Admin Panel" +theme_override_fonts/font = ExtResource("3_font") +text = "Server Admin" -[node name="LogoutBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/AccountSection" unique_id=1741557407] +[node name="LogoutBtn" type="Button" parent="MainMargin/MainHBox/LeftCol" unique_id=612591334] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 44) layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e13i6") +theme_override_fonts/font = ExtResource("3_font") text = "Logout" -[node name="StatusLabel" type="Label" parent="MainLayout/LeftPanel/LeftVBox" unique_id=188332146] +[node name="StatusLabel" type="Label" parent="MainMargin/MainHBox/LeftCol" unique_id=1531937007] unique_name_in_owner = true layout_mode = 2 -theme_override_font_sizes/font_size = 12 +theme_override_font_sizes/font_size = 11 horizontal_alignment = 1 -[node name="RightPanel" type="SubViewportContainer" parent="MainLayout" unique_id=2313053] -custom_minimum_size = Vector2(400, 0) +[node name="BackBtn" type="Button" parent="MainMargin/MainHBox/LeftCol" unique_id=2005161566] +unique_name_in_owner = true +custom_minimum_size = Vector2(56, 40) +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 24 +text = "←" + +[node name="CenterCol" type="VBoxContainer" parent="MainMargin/MainHBox" unique_id=1176098048] layout_mode = 2 size_flags_horizontal = 3 -size_flags_stretch_ratio = 1.2 +theme_override_constants/separation = 8 + +[node name="CategoryTabs" type="HBoxContainer" parent="MainMargin/MainHBox/CenterCol" unique_id=1050731335] +layout_mode = 2 +theme_override_constants/separation = 12 +alignment = 1 + +[node name="HeadTabBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/CategoryTabs" unique_id=767008262] +unique_name_in_owner = true +custom_minimum_size = Vector2(56, 56) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "🎩" + +[node name="CostumeTabBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/CategoryTabs" unique_id=73958290] +unique_name_in_owner = true +custom_minimum_size = Vector2(56, 56) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "👔" + +[node name="GloveTabBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/CategoryTabs" unique_id=204746295] +unique_name_in_owner = true +custom_minimum_size = Vector2(56, 56) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "🧤" + +[node name="AccTabBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/CategoryTabs" unique_id=1885872464] +unique_name_in_owner = true +custom_minimum_size = Vector2(56, 56) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "💎" + +[node name="ViewportWrapper" type="Control" parent="MainMargin/MainHBox/CenterCol" unique_id=1095002692] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ViewportContainer" type="SubViewportContainer" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper" unique_id=1062885163] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 stretch = true -[node name="PreviewViewport" type="SubViewport" parent="MainLayout/RightPanel" unique_id=2118325644] +[node name="PreviewViewport" type="SubViewport" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer" unique_id=724440223] unique_name_in_owner = true own_world_3d = true transparent_bg = true handle_input_locally = false -size = Vector2i(746, 720) +size = Vector2i(627, 487) render_target_update_mode = 4 -[node name="WorldEnvironment" type="WorldEnvironment" parent="MainLayout/RightPanel/PreviewViewport" unique_id=1213356390] -environment = SubResource("Environment_preview") +[node name="WorldEnvironment" type="WorldEnvironment" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1660814495] +environment = SubResource("Env_preview") -[node name="DirectionalLight3D" type="DirectionalLight3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=524977852] -transform = Transform3D(0.866025, -0.25, 0.433013, 0, 0.866025, 0.5, -0.5, -0.433013, 0.75, 0, 4, 0) -light_energy = 1.4 +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1722256175] +transform = Transform3D(0.866025, -0.25, 0.433013, 0, 0.866025, 0.5, -0.5, -0.433013, 0.75, 1, 4, 0) +light_energy = 1.5 -[node name="FillLight" type="OmniLight3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=1512907756] +[node name="FillLight" type="OmniLight3D" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1283982748] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2, 2) -light_color = Color(0.5, 0.6, 1, 1) -light_energy = 0.5 -omni_range = 8.0 +light_color = Color(0.4, 0.55, 1, 1) +light_energy = 0.6 +omni_range = 10.0 -[node name="Camera3D" type="Camera3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=1682800090] -transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 1.6, 3.2) +[node name="Camera3D" type="Camera3D" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1696846826] +transform = Transform3D(1, 0, 0, 0, 0.97072834, 0.2401802, 0, -0.2401802, 0.97072834, 0, 0.8262464, 2.6238613) current = true -fov = 40.0 +fov = 38.0 -[node name="CharacterRoot" type="Node3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=687534677] +[node name="CharacterRoot" type="Node3D" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1793550534] unique_name_in_owner = true -transform = Transform3D(0.6587157, 0, 0.75239193, 0, 1, 0, -0.75239193, 0, 0.6587157, 0, 0.7540468, 0) -[node name="Masbro" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=48482236 instance=ExtResource("4_masbro")] +[node name="Masbro" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport/CharacterRoot" unique_id=2118952077 instance=ExtResource("4_masbro")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) visible = false -[node name="Bob" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=1951694644 instance=ExtResource("4_bob")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.484627, 0) +[node name="Bob" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport/CharacterRoot" unique_id=1510408045 instance=ExtResource("4_bob")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.484, 0) visible = false -[node name="Gatot" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=1852101135 instance=ExtResource("4_gatot")] +[node name="Gatot" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport/CharacterRoot" unique_id=340292178 instance=ExtResource("4_gatot")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) visible = false -[node name="Oldpop" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=812849337 instance=ExtResource("4_oldpop")] +[node name="Oldpop" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport/CharacterRoot" unique_id=1591549997 instance=ExtResource("4_oldpop")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0) -[node name="AnimationPlayer" type="AnimationPlayer" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=4187871] +[node name="AnimationPlayer" type="AnimationPlayer" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport/CharacterRoot" unique_id=1330912730] +unique_name_in_owner = true root_node = NodePath("../Oldpop") libraries/animation-pack = ExtResource("5_animlib") autoplay = &"animation-pack/idle" -[node name="AvatarSelectionPopup" type="PopupPanel" parent="." unique_id=1186892858] +[node name="DragZone" type="Control" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper" unique_id=1365789190] unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="DragHint" type="Label" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper" unique_id=272858156] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -76.5 +offset_top = -38.0 +offset_right = 77.5 +offset_bottom = -14.0 +grow_horizontal = 2 +grow_vertical = 0 +theme_override_colors/font_color = Color(1, 1, 1, 0.2) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 16 +text = "← drag to rotate →" +horizontal_alignment = 1 + +[node name="BottomControls" type="VBoxContainer" parent="MainMargin/MainHBox/CenterCol" unique_id=1513522557] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_constants/separation = 4 + +[node name="CharSelectorRow" type="HBoxContainer" parent="MainMargin/MainHBox/CenterCol/BottomControls" unique_id=1642710720] +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="CharLeftBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/BottomControls/CharSelectorRow" unique_id=1911454313] +unique_name_in_owner = true +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "◀" + +[node name="LoadoutCharName" type="Label" parent="MainMargin/MainHBox/CenterCol/BottomControls/CharSelectorRow" unique_id=1417186267] +unique_name_in_owner = true +custom_minimum_size = Vector2(90, 36) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 18 +text = "Dabro" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="CharRightBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/BottomControls/CharSelectorRow" unique_id=254943219] +unique_name_in_owner = true +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "▶" + +[node name="SetDefaultBtn" type="Button" parent="MainMargin/MainHBox/CenterCol/BottomControls" unique_id=73462951] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 36) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "Set as Default" + +[node name="RightCol" type="VBoxContainer" parent="MainMargin/MainHBox" unique_id=857477144] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="ItemInfoCard" type="PanelContainer" parent="MainMargin/MainHBox/RightCol" unique_id=1009501358] +layout_mode = 2 + +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard" unique_id=663473404] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 12 + +[node name="HBox" type="HBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin" unique_id=749660081] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="PreviewBorder" type="PanelContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox" unique_id=501514249] +layout_mode = 2 + +[node name="ItemPreview" type="TextureRect" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/PreviewBorder" unique_id=1738821483] +unique_name_in_owner = true +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +expand_mode = 1 +stretch_mode = 5 + +[node name="VBox" type="VBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox" unique_id=953647763] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 6 + +[node name="ItemNameLabel" type="Label" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox" unique_id=1127187232] +unique_name_in_owner = true +layout_mode = 2 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 16 +text = "Cute Hat" + +[node name="RarityPriceRow" type="HBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox" unique_id=336105315] +layout_mode = 2 + +[node name="ItemRarityLabel" type="Label" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox/RarityPriceRow" unique_id=1170021429] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0.7, 0.3, 1, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 12 +text = "Epic" + +[node name="ItemPriceIcon" type="Label" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox/RarityPriceRow" unique_id=1881961667] +layout_mode = 2 +theme_override_colors/font_color = Color(0.9, 0.7, 0.3, 1) +theme_override_font_sizes/font_size = 12 +text = "✦" + +[node name="ItemPriceLabel" type="Label" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox/RarityPriceRow" unique_id=1837607820] +unique_name_in_owner = true +layout_mode = 2 +theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 12 +text = "1500" + +[node name="ActionsRow" type="HBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox" unique_id=1252309476] +layout_mode = 2 +theme_override_constants/separation = 6 + +[node name="EquipBtn" type="Button" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox/ActionsRow" unique_id=1276164880] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 14 +text = "Equip" + +[node name="DismantleBtn" type="Button" parent="MainMargin/MainHBox/RightCol/ItemInfoCard/Margin/HBox/VBox/ActionsRow" unique_id=1451515715] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(1, 0.4, 0.4, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 14 +text = "Dismantle" + +[node name="ItemGridCard" type="PanelContainer" parent="MainMargin/MainHBox/RightCol" unique_id=748947828] +layout_mode = 2 + +[node name="Margin" type="MarginContainer" parent="MainMargin/MainHBox/RightCol/ItemGridCard" unique_id=2047178434] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 12 + +[node name="VBox" type="VBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin" unique_id=553633431] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="ItemGrid" type="GridContainer" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox" unique_id=2005677714] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/h_separation = 8 +theme_override_constants/v_separation = 8 +columns = 3 + +[node name="Slot0" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=1964986286] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot1" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=1570438748] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot2" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=1730218687] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot3" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=533222646] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot4" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=1359133451] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot5" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=212812652] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot6" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=464526955] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot7" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=87751984] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Slot8" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/ItemGrid" unique_id=1866702419] +custom_minimum_size = Vector2(88, 88) +layout_mode = 2 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="Pagination" type="HBoxContainer" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox" unique_id=1821323993] +layout_mode = 2 +theme_override_constants/separation = 12 +alignment = 1 + +[node name="PrevPageBtn" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/Pagination" unique_id=78956964] +unique_name_in_owner = true +custom_minimum_size = Vector2(32, 32) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "◀" + +[node name="PageLabel" type="Label" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/Pagination" unique_id=1014922888] +unique_name_in_owner = true +custom_minimum_size = Vector2(80, 32) +layout_mode = 2 +theme_override_colors/font_color = Color(0.9, 0.7, 0.3, 1) +theme_override_fonts/font = ExtResource("3_font") +theme_override_font_sizes/font_size = 18 +text = "1" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="NextPageBtn" type="Button" parent="MainMargin/MainHBox/RightCol/ItemGridCard/Margin/VBox/Pagination" unique_id=1618583973] +unique_name_in_owner = true +custom_minimum_size = Vector2(32, 32) +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_font") +text = "▶" + +[node name="DismantleConfirmDialog" type="ConfirmationDialog" parent="." unique_id=234823811] +unique_name_in_owner = true +title = "Confirm Dismantle" +size = Vector2i(320, 120) + +[node name="ResetStatsConfirmDialog" type="ConfirmationDialog" parent="." unique_id=98723485] +unique_name_in_owner = true +size = Vector2i(320, 120) +dialog_text = "Wipe ALL stats to 0? This cannot be undone." + +[node name="LinkEmailDialog" type="AcceptDialog" parent="." unique_id=128913898] +unique_name_in_owner = true +title = "Link Email" +size = Vector2i(320, 160) +dialog_text = "Link this guest account to an email." + +[node name="VBox" type="VBoxContainer" parent="LinkEmailDialog" unique_id=3456345] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 312.0 +offset_bottom = 111.0 + +[node name="LinkEmailInput" type="LineEdit" parent="LinkEmailDialog/VBox" unique_id=8712361] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Email Address" + +[node name="LinkPassInput" type="LineEdit" parent="LinkEmailDialog/VBox" unique_id=64356434] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Password" +secret = true + +[node name="AvatarSelectionPopup" type="PopupPanel" parent="." unique_id=1466095185] +unique_name_in_owner = true +oversampling_override = 1.0 title = "Select Avatar" size = Vector2i(320, 220) -[node name="GridContainer" type="GridContainer" parent="AvatarSelectionPopup" unique_id=1871925487] +[node name="GridContainer" type="GridContainer" parent="AvatarSelectionPopup" unique_id=119483729] unique_name_in_owner = true +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 offset_left = 4.0 offset_top = 4.0 offset_right = 316.0 offset_bottom = 216.0 +grow_horizontal = 2 +grow_vertical = 2 columns = 3 -[node name="AccountSettingsDialog" type="AcceptDialog" parent="." unique_id=987000001] +[node name="AvatarBtn0" type="Button" parent="AvatarSelectionPopup/GridContainer" unique_id=45239452] +custom_minimum_size = Vector2(62, 62) +layout_mode = 2 +expand_icon = true + +[node name="AvatarBtn1" type="Button" parent="AvatarSelectionPopup/GridContainer" unique_id=12401825] +custom_minimum_size = Vector2(62, 62) +layout_mode = 2 +expand_icon = true + +[node name="AvatarBtn2" type="Button" parent="AvatarSelectionPopup/GridContainer" unique_id=98357285] +custom_minimum_size = Vector2(62, 62) +layout_mode = 2 +expand_icon = true + +[node name="AvatarBtn3" type="Button" parent="AvatarSelectionPopup/GridContainer" unique_id=18498453] +custom_minimum_size = Vector2(62, 62) +layout_mode = 2 +expand_icon = true + +[node name="AccountSettingsDialog" type="AcceptDialog" parent="." unique_id=1862558949] unique_name_in_owner = true title = "Account Settings" size = Vector2i(360, 480) -[node name="VBox" type="VBoxContainer" parent="AccountSettingsDialog" unique_id=987000002] +[node name="VBox" type="VBoxContainer" parent="AccountSettingsDialog" unique_id=608788631] offset_left = 8.0 offset_top = 8.0 offset_right = 352.0 offset_bottom = 431.0 theme_override_constants/separation = 12 -[node name="CredTitle" type="Label" parent="AccountSettingsDialog/VBox" unique_id=987000003] +[node name="CredTitle" type="Label" parent="AccountSettingsDialog/VBox" unique_id=947368044] layout_mode = 2 text = "Update Credentials" -[node name="OldPassInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=987000004] +[node name="OldPassInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=187554107] unique_name_in_owner = true layout_mode = 2 placeholder_text = "Current Password" secret = true -[node name="NewEmailInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=987000005] +[node name="NewEmailInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=656070098] unique_name_in_owner = true layout_mode = 2 placeholder_text = "New Email" -[node name="NewPassInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=987000006] +[node name="NewPassInput" type="LineEdit" parent="AccountSettingsDialog/VBox" unique_id=1102941873] unique_name_in_owner = true layout_mode = 2 placeholder_text = "New Password" secret = true -[node name="SubmitCredBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=987000007] +[node name="SubmitCredBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=214981590] unique_name_in_owner = true layout_mode = 2 text = "Submit Credentials" -[node name="HSeparator1" type="HSeparator" parent="AccountSettingsDialog/VBox" unique_id=987000008] +[node name="HSeparator1" type="HSeparator" parent="AccountSettingsDialog/VBox" unique_id=2011703728] layout_mode = 2 -[node name="TzLabel" type="Label" parent="AccountSettingsDialog/VBox" unique_id=987000009] +[node name="TzLabel" type="Label" parent="AccountSettingsDialog/VBox" unique_id=1789076555] layout_mode = 2 text = "Timezone" -[node name="TzDropdown" type="OptionButton" parent="AccountSettingsDialog/VBox" unique_id=987000010] +[node name="TzDropdown" type="OptionButton" parent="AccountSettingsDialog/VBox" unique_id=2029336897] unique_name_in_owner = true layout_mode = 2 -[node name="SaveTzBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=987000011] +[node name="SaveTzBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=821911571] unique_name_in_owner = true layout_mode = 2 text = "Save Timezone" -[node name="HSeparator2" type="HSeparator" parent="AccountSettingsDialog/VBox" unique_id=987000012] +[node name="HSeparator2" type="HSeparator" parent="AccountSettingsDialog/VBox" unique_id=802671082] layout_mode = 2 -[node name="ResetStatsBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=987000013] +[node name="ResetStatsBtn" type="Button" parent="AccountSettingsDialog/VBox" unique_id=1357708120] unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(1, 0, 0, 1) diff --git a/scenes/ui/shop_panel.tscn b/scenes/ui/shop_panel.tscn new file mode 100644 index 0000000..dc02389 --- /dev/null +++ b/scenes/ui/shop_panel.tscn @@ -0,0 +1,93 @@ +[gd_scene format=3 uid="uid://c018oue81jm44"] + +[ext_resource type="Script" path="res://scripts/ui/shop_panel.gd" id="1"] + +[node name="ShopPanel" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1") + +[node name="ColorRect" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.12, 0.12, 0.12, 0.95) + +[node name="Panel" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -300.0 +offset_top = -250.0 +offset_right = 300.0 +offset_bottom = 250.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel"] +layout_mode = 2 + +[node name="Header" type="HBoxContainer" parent="Panel/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="BackBtn" type="Button" parent="Panel/VBoxContainer/Header"] +layout_mode = 2 +text = "Back" + +[node name="Title" type="Label" parent="Panel/VBoxContainer/Header"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "SHOP" +horizontal_alignment = 1 + +[node name="TabContainer" type="TabContainer" parent="Panel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="Head" type="MarginContainer" parent="Panel/VBoxContainer/TabContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer/TabContainer/Head"] +layout_mode = 2 +columns = 3 + +[node name="Costume" type="MarginContainer" parent="Panel/VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer/TabContainer/Costume"] +layout_mode = 2 +columns = 3 + +[node name="Glove" type="MarginContainer" parent="Panel/VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer/TabContainer/Glove"] +layout_mode = 2 +columns = 3 + +[node name="Accessory" type="MarginContainer" parent="Panel/VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer/TabContainer/Accessory"] +layout_mode = 2 +columns = 3 + +[node name="StatusLabel" type="Label" parent="Panel/VBoxContainer"] +layout_mode = 2 +text = "Welcome to the Store!" +horizontal_alignment = 1 diff --git a/scripts/managers/user_profile_manager.gd b/scripts/managers/user_profile_manager.gd index 77abb66..d8719e1 100644 --- a/scripts/managers/user_profile_manager.gd +++ b/scripts/managers/user_profile_manager.gd @@ -9,6 +9,9 @@ signal avatar_changed(url: String) # Profile data var profile: Dictionary = {} var stats: Dictionary = {} +var wallet: Dictionary = {"gold": 0, "star": 0} +var inventory: Array = [] +var loadout: Dictionary = {"head": "", "costume": "", "glove": "", "accessory": ""} var is_profile_loaded: bool = false # Nakama storage collection names @@ -47,6 +50,8 @@ func load_profile() -> Dictionary: # Reset state first to ensure no old account data carries over profile = {} stats = {} + wallet = {"gold": 0, "star": 0} + inventory = [] is_profile_loaded = false if not NakamaManager.session: @@ -84,6 +89,18 @@ func load_profile() -> Dictionary: var stored_data = JSON.parse_string(storage_result.objects[0].value) if stored_data: profile.merge(stored_data, true) + if stored_data.has("loadout"): + loadout = stored_data["loadout"] + + # Parse Wallet + if account.wallet: + var w_data = JSON.parse_string(account.wallet) + if w_data: + wallet["gold"] = w_data.get("gold", 0) + wallet["star"] = w_data.get("star", 0) + + # Load Inventory + await load_inventory() # Load stats await load_stats() @@ -98,6 +115,21 @@ func load_profile() -> Dictionary: return profile +func load_inventory() -> void: + if not NakamaManager.session: return + + inventory.clear() + var result = await NakamaManager.client.list_storage_objects_async( + NakamaManager.session, + "inventory", + NakamaManager.session.user_id, + 100 + ) + + if not result.is_exception() and result.objects: + for obj in result.objects: + inventory.append(obj.key) + func load_stats() -> Dictionary: # Reset stats first to ensure fresh data for new logins stats = {} @@ -223,7 +255,8 @@ func _save_profile_data() -> bool: "avatar_index": profile.get("avatar_index", 0), "bio": profile.get("bio", ""), "country": profile.get("country", ""), - "language": profile.get("language", "en") + "language": profile.get("language", "en"), + "loadout": loadout } var write_obj := NakamaWriteStorageObject.new( @@ -247,6 +280,41 @@ func _save_profile_data() -> bool: emit_signal("profile_updated") return true +func update_loadout(category: String, item_id: String) -> bool: + if not loadout.has(category): + return false + loadout[category] = item_id + return await _save_profile_data() + +func purchase_item(item_id: String, price_gold: int, price_star: int, category: String) -> bool: + if not NakamaManager.session: return false + + var payload = JSON.stringify({ + "item_id": item_id, + "price_gold": price_gold, + "price_star": price_star, + "category": category + }) + + var result = await NakamaManager.client.rpc_async( + NakamaManager.session, + "purchase_item", + payload + ) + + if result.is_exception(): + push_error("[UserProfileManager] Purchase failed: ", result.get_exception().message) + return false + + # Update local cache + if price_gold > 0: wallet["gold"] -= price_gold + if price_star > 0: wallet["star"] -= price_star + if not inventory.has(item_id): + inventory.append(item_id) + + emit_signal("profile_updated") + return true + # ============================================================================= # Stats Management # ============================================================================= diff --git a/scripts/ui/profile_panel.gd b/scripts/ui/profile_panel.gd index fc1c460..8567fb6 100644 --- a/scripts/ui/profile_panel.gd +++ b/scripts/ui/profile_panel.gd @@ -1,163 +1,369 @@ extends Control -## Profile panel controller — full-scene loadout screen. -## Left: profile info + default character selector. -## Right: 3D SubViewport character preview. +## Profile panel — 3-column layout. +## Left : profile card, currencies, stats, account buttons. +## Center: category tabs overlay, 3D rotatable preview, char-selector overlay. +## Right : item info card, 3×3 owned-item grid, pagination. signal closed signal profile_updated -# ------------------------------------------------------------------------- -# UI References -# ------------------------------------------------------------------------- -@onready var back_btn := %BackBtn as Button -@onready var avatar_display := %AvatarDisplay as TextureRect -@onready var change_avatar_btn := %ChangeAvatarBtn as Button -@onready var display_name_input := %DisplayNameInput as LineEdit -@onready var save_name_btn := %SaveNameBtn as Button -@onready var games_played_label := %GamesPlayed as Label -@onready var win_rate_label := %WinRate as Label -@onready var high_score_label := %HighScore as Label -@onready var account_type_label := %AccountType as Label -@onready var link_account_btn := %LinkAccountBtn as Button -@onready var admin_panel_btn := %AdminPanelBtn as Button -@onready var logout_btn := %LogoutBtn as Button -@onready var status_label := %StatusLabel as Label -@onready var avatar_popup := %AvatarSelectionPopup as PopupPanel -@onready var avatar_grid := %GridContainer as GridContainer +# ───────────────────────────────────────────────────────────── +# UI references (all resolved via %UniqueName — must match .tscn) +# ───────────────────────────────────────────────────────────── -# Account Settings refs -@onready var acc_settings_dialog := %AccountSettingsDialog as AcceptDialog -@onready var old_pass_input := %OldPassInput as LineEdit -@onready var new_email_input := %NewEmailInput as LineEdit -@onready var new_pass_input := %NewPassInput as LineEdit -@onready var submit_cred_btn := %SubmitCredBtn as Button -@onready var tz_dropdown := %TzDropdown as OptionButton -@onready var save_tz_btn := %SaveTzBtn as Button -@onready var reset_stats_btn := %ResetStatsBtn as Button +# Left column +@onready var avatar_display := %AvatarDisplay as TextureRect +@onready var display_name_input := %DisplayNameInput as LineEdit +@onready var save_name_btn := %SaveNameBtn as Button +@onready var change_avatar_btn := %ChangeAvatarBtn as Button +@onready var account_type_label := %AccountType as Label +@onready var link_account_btn := %LinkAccountBtn as Button +@onready var gold_label := %GoldLabel as Label +@onready var star_label := %StarLabel as Label +@onready var games_played_label := %GamesPlayed as Label +@onready var win_rate_label := %WinRate as Label +@onready var high_score_label := %HighScore as Label +@onready var acc_settings_btn := %AccSettingsBtn as Button +@onready var admin_panel_btn := %AdminPanelBtn as Button +@onready var logout_btn := %LogoutBtn as Button +@onready var status_label := %StatusLabel as Label -# Loadout refs -@onready var char_left_btn := %CharLeftBtn as Button -@onready var char_right_btn := %CharRightBtn as Button +# Center column (overlays inside CenterWrapper) +@onready var head_tab_btn := %HeadTabBtn as Button +@onready var costume_tab_btn := %CostumeTabBtn as Button +@onready var glove_tab_btn := %GloveTabBtn as Button +@onready var acc_tab_btn := %AccTabBtn as Button +@onready var drag_zone := %DragZone as Control +@onready var character_root := %CharacterRoot as Node3D +@onready var char_left_btn := %CharLeftBtn as Button +@onready var char_right_btn := %CharRightBtn as Button @onready var loadout_char_name := %LoadoutCharName as Label -@onready var set_default_btn := %SetDefaultBtn as Button +@onready var set_default_btn := %SetDefaultBtn as Button +@onready var anim_player := %AnimationPlayer as AnimationPlayer +# Right column +@onready var item_preview := %ItemPreview as TextureRect +@onready var item_name_label := %ItemNameLabel as Label +@onready var item_rarity_label := %ItemRarityLabel as Label +@onready var item_price_label := %ItemPriceLabel as Label +@onready var equip_btn := %EquipBtn as Button +@onready var dismantle_btn := %DismantleBtn as Button +@onready var item_grid := %ItemGrid as GridContainer +@onready var prev_page_btn := %PrevPageBtn as Button +@onready var page_label := %PageLabel as Label +@onready var next_page_btn := %NextPageBtn as Button -# 3D Preview refs -@onready var character_root := %CharacterRoot as Node3D -@onready var anim_player: AnimationPlayer +# Back button + popups +@onready var back_btn := %BackBtn as Button +@onready var avatar_popup := %AvatarSelectionPopup as PopupPanel +@onready var avatar_grid := %GridContainer as GridContainer +@onready var acc_settings_dialog := %AccountSettingsDialog as AcceptDialog +@onready var old_pass_input := %OldPassInput as LineEdit +@onready var new_email_input := %NewEmailInput as LineEdit +@onready var new_pass_input := %NewPassInput as LineEdit +@onready var submit_cred_btn := %SubmitCredBtn as Button +@onready var tz_dropdown := %TzDropdown as OptionButton +@onready var save_tz_btn := %SaveTzBtn as Button +@onready var reset_stats_btn := %ResetStatsBtn as Button -# ------------------------------------------------------------------------- -# State -# ------------------------------------------------------------------------- +# New static popups +@onready var dismantle_dialog := %DismantleConfirmDialog as ConfirmationDialog +@onready var link_email_dialog := %LinkEmailDialog as AcceptDialog +@onready var link_email_input := %LinkEmailInput as LineEdit +@onready var link_pass_input := %LinkPassInput as LineEdit +@onready var reset_stats_dialog := %ResetStatsConfirmDialog as ConfirmationDialog + +# ───────────────────────────────────────────────────────────── +# Constants +# ───────────────────────────────────────────────────────────── const CHARACTERS: Array[String] = ["Copper", "Dabro", "Gatot", "Pip"] -# Maps game character name -> GLB node name in the SubViewport -# Must match the mapping in player.gd's set_character() const CHAR_NODE_MAP: Dictionary = { "Copper": "Oldpop", - "Dabro": "Masbro", - "Gatot": "Gatot", - "Pip": "Bob" + "Dabro": "Masbro", + "Gatot": "Gatot", + "Pip": "Bob" } +const RARITY_COLORS: Dictionary = { + "Common": Color(0.80, 0.80, 0.80, 1), + "Rare": Color(0.30, 0.60, 1.00, 1), + "Epic": Color(0.70, 0.30, 1.00, 1), + "Legendary": Color(1.00, 0.70, 0.10, 1) +} +## Client-side item catalog: item_id -> { name, rarity, star_value } +const ITEM_CATALOG: Dictionary = { + "head_hat1": {"name": "Cap", "rarity": "Common", "star_value": 50}, + "head_crown": {"name": "Crown", "rarity": "Epic", "star_value": 1500}, + "costume_red": {"name": "Red Suit", "rarity": "Rare", "star_value": 200}, + "costume_gold": {"name": "Gold Suit", "rarity": "Epic", "star_value": 1000}, + "glove_leather": {"name": "Leather Gloves", "rarity": "Common", "star_value": 50}, + "acc_glasses": {"name": "Sunglasses", "rarity": "Rare", "star_value": 300}, +} +const ITEMS_PER_PAGE: int = 9 -var _loadout_index: int = 0 # Index into CHARACTERS +# ───────────────────────────────────────────────────────────── +# State +# ───────────────────────────────────────────────────────────── +var _loadout_index: int = 0 var _default_character: String = "Copper" +var _current_category: String = "head" +var _category_items: Array = [] +var _current_page: int = 0 +var _selected_item_id: String = "" +var _item_slots: Array[Button] = [] + +# Drag-to-rotate +var _drag_active: bool = false +var _drag_start_x: float = 0.0 +var _model_yaw: float = 0.0 + +# ───────────────────────────────────────────────────────────── +# Lifecycle +# ───────────────────────────────────────────────────────────── func _ready() -> void: + _build_slot_refs() _connect_signals() - _load_profile_data() _setup_avatar_grid() _setup_account_settings_ui() + _load_profile_data() _load_loadout() _setup_3d_preview() + _on_category_tab_pressed("head") +func _build_slot_refs() -> void: + _item_slots.clear() + for child in item_grid.get_children(): + if child is Button: + _item_slots.append(child as Button) + +# ───────────────────────────────────────────────────────────── +# Signal wiring +# ───────────────────────────────────────────────────────────── func _connect_signals() -> void: back_btn.pressed.connect(_on_close_pressed) change_avatar_btn.pressed.connect(_on_change_avatar_pressed) save_name_btn.pressed.connect(_on_save_name_pressed) link_account_btn.pressed.connect(_on_link_account_pressed) + acc_settings_btn.pressed.connect(_open_account_settings) admin_panel_btn.pressed.connect(_on_admin_panel_pressed) logout_btn.pressed.connect(_on_logout_pressed) char_left_btn.pressed.connect(func(): _cycle_loadout_char(-1)) char_right_btn.pressed.connect(func(): _cycle_loadout_char(1)) set_default_btn.pressed.connect(_on_set_default_pressed) - UserProfileManager.profile_loaded.connect(func(p): _on_profile_updated()) + # Category tabs + head_tab_btn.pressed.connect(func(): _on_category_tab_pressed("head")) + costume_tab_btn.pressed.connect(func(): _on_category_tab_pressed("costume")) + glove_tab_btn.pressed.connect(func(): _on_category_tab_pressed("glove")) + acc_tab_btn.pressed.connect(func(): _on_category_tab_pressed("accessory")) + + # Item grid slot buttons + for i in _item_slots.size(): + _item_slots[i].pressed.connect(_on_slot_pressed.bind(i)) + + equip_btn.pressed.connect(_on_equip_pressed) + dismantle_btn.pressed.connect(_on_dismantle_pressed) + dismantle_dialog.confirmed.connect(_on_dismantle_confirmed) + link_email_dialog.confirmed.connect(_on_link_account_confirmed) + reset_stats_dialog.confirmed.connect(_on_reset_stats_confirmed) + prev_page_btn.pressed.connect(_on_prev_page) + next_page_btn.pressed.connect(_on_next_page) + + drag_zone.gui_input.connect(_on_drag_input) + + if UserProfileManager.profile_loaded.connect(func(_p): _on_profile_updated()) != OK: + push_warning("[ProfilePanel] Could not connect profile_loaded") UserProfileManager.profile_updated.connect(_on_profile_updated) UserProfileManager.profile_update_failed.connect(_on_profile_update_failed) - - # Dynamically inject Account Settings button - var acc_settings_btn = Button.new() - acc_settings_btn.text = "Account Settings" - acc_settings_btn.custom_minimum_size = Vector2(0, 44) - acc_settings_btn.add_theme_font_override("font", load("res://assets/fonts/Nougat-ExtraBlack.ttf")) - acc_settings_btn.pressed.connect(_open_account_settings) - # Insert it before Logout button - var logout_idx = logout_btn.get_index() - logout_btn.get_parent().add_child(acc_settings_btn) - logout_btn.get_parent().move_child(acc_settings_btn, logout_idx) -# ------------------------------------------------------------------------- -# Profile -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── +# Profile data +# ───────────────────────────────────────────────────────────── func _load_profile_data() -> void: - var profile := UserProfileManager.profile + var prof := UserProfileManager.profile var stats := UserProfileManager.stats - display_name_input.text = profile.get("display_name", "Guest") + display_name_input.text = prof.get("display_name", "") display_name_input.max_length = 6 - var avatar_url: String = UserProfileManager.get_avatar_url() - if ResourceLoader.exists(avatar_url): - avatar_display.texture = load(avatar_url) + var url: String = UserProfileManager.get_avatar_url() + if ResourceLoader.exists(url): + avatar_display.texture = load(url) as Texture2D games_played_label.text = "Games Played: %d" % stats.get("games_played", 0) - win_rate_label.text = "Win Rate: %.1f%%" % UserProfileManager.get_win_rate() - high_score_label.text = "High Score: %d" % stats.get("high_score", 0) + win_rate_label.text = "Win Rate: %.1f%%" % UserProfileManager.get_win_rate() + high_score_label.text = "High Score: %d" % stats.get("high_score", 0) + + gold_label.text = str(UserProfileManager.wallet.get("gold", 0)) + star_label.text = str(UserProfileManager.wallet.get("star", 0)) if AuthManager.is_guest: - account_type_label.text = "Account: Guest" + account_type_label.text = "Account : Guest" link_account_btn.visible = true - link_account_btn.text = "Link Email (Keep Progress)" else: - var mode_name := _get_auth_mode_name(AuthManager.auth_mode) - account_type_label.text = "Account: %s" % mode_name + account_type_label.text = "Account : %s" % _auth_mode_name(AuthManager.auth_mode) link_account_btn.visible = false status_label.text = "" -func _get_auth_mode_name(mode: int) -> String: +func _auth_mode_name(mode: int) -> String: match mode: - AuthManager.AuthMode.EMAIL: - return "Email" - AuthManager.AuthMode.GOOGLE: - return "Google" - AuthManager.AuthMode.APPLE: - return "Apple" - AuthManager.AuthMode.FACEBOOK: - return "Facebook" - _: - return "Guest" + AuthManager.AuthMode.EMAIL: return "Email" + AuthManager.AuthMode.GOOGLE: return "Google" + AuthManager.AuthMode.APPLE: return "Apple" + AuthManager.AuthMode.FACEBOOK: return "Facebook" + _: return "Guest" -# ------------------------------------------------------------------------- -# Loadout -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── +# Category tabs +# ───────────────────────────────────────────────────────────── +func _on_category_tab_pressed(category: String) -> void: + _current_category = category + _current_page = 0 + _selected_item_id = "" + _clear_item_info() + _rebuild_category_items() + _populate_item_grid() + _highlight_active_tab() + +func _highlight_active_tab() -> void: + var map := { + "head": head_tab_btn, + "costume": costume_tab_btn, + "glove": glove_tab_btn, + "accessory": acc_tab_btn + } + for cat: String in map: + (map[cat] as Button).modulate = Color(1.3, 1.3, 0.4, 1) if cat == _current_category else Color.WHITE + +func _rebuild_category_items() -> void: + _category_items.clear() + var prefix := _current_category + "_" + for item_id: String in UserProfileManager.inventory: + if item_id.begins_with(prefix): + _category_items.append(item_id) + +# ───────────────────────────────────────────────────────────── +# Item grid +# ───────────────────────────────────────────────────────────── +func _populate_item_grid() -> void: + var start: int = _current_page * ITEMS_PER_PAGE + var total: int = _category_items.size() + var total_pages: int = max(1, ceili(float(total) / float(ITEMS_PER_PAGE))) + + page_label.text = "%d / %d" % [_current_page + 1, total_pages] + prev_page_btn.disabled = (_current_page == 0) + next_page_btn.disabled = ((_current_page + 1) >= total_pages) + + var equipped: String = UserProfileManager.loadout.get(_current_category, "") + for i in _item_slots.size(): + var slot: Button = _item_slots[i] + var idx: int = start + i + if idx < total: + var item_id: String = _category_items[idx] + var info: Dictionary = ITEM_CATALOG.get(item_id, {}) + slot.text = info.get("name", item_id) + slot.tooltip_text = item_id + slot.modulate = Color(0.4, 1.0, 0.4, 1) if item_id == equipped else Color.WHITE + else: + slot.text = "" + slot.tooltip_text = "" + slot.modulate = Color.WHITE + +func _on_slot_pressed(slot_index: int) -> void: + var idx: int = _current_page * ITEMS_PER_PAGE + slot_index + if idx >= _category_items.size(): + return + _selected_item_id = _category_items[idx] + _show_item_info(_selected_item_id) + +func _show_item_info(item_id: String) -> void: + var info: Dictionary = ITEM_CATALOG.get(item_id, {}) + + item_name_label.text = info.get("name", item_id) + + var rarity: String = info.get("rarity", "Common") + item_rarity_label.text = rarity + item_rarity_label.add_theme_color_override( + "font_color", RARITY_COLORS.get(rarity, Color.WHITE) + ) + + var sv: int = info.get("star_value", 0) + item_price_label.text = str(sv) if sv > 0 else "" + + var equipped: String = UserProfileManager.loadout.get(_current_category, "") + equip_btn.text = "✓ Equipped" if equipped == item_id else "Equip" + equip_btn.disabled = (equipped == item_id) + dismantle_btn.disabled = false + +func _clear_item_info() -> void: + item_name_label.text = "Select an item" + item_rarity_label.text = "" + item_price_label.text = "" + equip_btn.text = "Equip" + equip_btn.disabled = true + dismantle_btn.disabled = true + +# ───────────────────────────────────────────────────────────── +# Item actions +# ───────────────────────────────────────────────────────────── +func _on_equip_pressed() -> void: + if _selected_item_id.is_empty(): return + var ok: bool = await UserProfileManager.update_loadout(_current_category, _selected_item_id) + if ok: + _set_status("Equipped: " + _selected_item_id, Color(0.4, 1.0, 0.4)) + _populate_item_grid() + _show_item_info(_selected_item_id) + else: + _set_status("Failed to equip item.", Color.RED) + +func _on_dismantle_pressed() -> void: + if _selected_item_id.is_empty(): return + dismantle_dialog.dialog_text = "Dismantle '%s'?\nThis will remove it from your inventory." % _selected_item_id + dismantle_dialog.popup_centered() + +func _on_dismantle_confirmed() -> void: + UserProfileManager.inventory.erase(_selected_item_id) + if UserProfileManager.loadout.get(_current_category, "") == _selected_item_id: + UserProfileManager.update_loadout(_current_category, "") + _selected_item_id = "" + _clear_item_info() + _rebuild_category_items() + _populate_item_grid() + _set_status("Item dismantled.", Color(1, 0.5, 0.3)) + +# ───────────────────────────────────────────────────────────── +# Pagination +# ───────────────────────────────────────────────────────────── +func _on_prev_page() -> void: + if _current_page > 0: + _current_page -= 1 + _populate_item_grid() + +func _on_next_page() -> void: + var total_pages: int = max(1, ceili(float(_category_items.size()) / float(ITEMS_PER_PAGE))) + if _current_page + 1 < total_pages: + _current_page += 1 + _populate_item_grid() + +# ───────────────────────────────────────────────────────────── +# Character loadout +# ───────────────────────────────────────────────────────────── func _load_loadout() -> void: - """Load the saved default character from profile storage.""" - var saved = UserProfileManager.profile.get("loadout_character", "Copper") - var idx = CHARACTERS.find(saved) - _loadout_index = max(idx, 0) + var saved: String = UserProfileManager.profile.get("loadout_character", "Copper") + var idx: int = CHARACTERS.find(saved) + _loadout_index = max(idx, 0) _default_character = CHARACTERS[_loadout_index] _refresh_loadout_ui() func _cycle_loadout_char(direction: int) -> void: _loadout_index = wrapi(_loadout_index + direction, 0, CHARACTERS.size()) _refresh_loadout_ui() - _update_3d_preview(CHARACTERS[_loadout_index]) func _refresh_loadout_ui() -> void: var char_name := CHARACTERS[_loadout_index] - loadout_char_name.text = char_name + loadout_char_name.text = char_name var is_default := char_name == _default_character - set_default_btn.text = "✓ DEFAULT" if is_default else "Set as Default" + set_default_btn.text = "✓ DEFAULT" if is_default else "Set as Default" set_default_btn.disabled = is_default _update_3d_preview(char_name) @@ -165,50 +371,42 @@ func _on_set_default_pressed() -> void: var char_name := CHARACTERS[_loadout_index] _default_character = char_name UserProfileManager.profile["loadout_character"] = char_name - # Also apply immediately to LobbyManager if LobbyManager.available_characters.has(char_name): LobbyManager.local_character_index = LobbyManager.available_characters.find(char_name) - status_label.text = "Loadout set to: " + char_name - status_label.add_theme_color_override("font_color", Color(0.4, 0.8, 0.4)) + _set_status("Default set to: " + char_name, Color(0.4, 0.9, 0.4)) _refresh_loadout_ui() - # Persist to storage _save_loadout_to_profile() - # Sync to leaderboard immediately UserProfileManager.submit_to_leaderboard() + # Notify lobby (and any other listeners) to refresh their 3D preview + UserProfileManager.profile_updated.emit() func _save_loadout_to_profile() -> void: - """Save loadout_character field to Nakama profile storage.""" - if not NakamaManager.session: - return + if not NakamaManager.session: return var data := { - "avatar_index": UserProfileManager.profile.get("avatar_index", 0), - "bio": UserProfileManager.profile.get("bio", ""), - "country": UserProfileManager.profile.get("country", ""), - "language": UserProfileManager.profile.get("language", "en"), - "loadout_character": _default_character + "avatar_index": UserProfileManager.profile.get("avatar_index", 0), + "bio": UserProfileManager.profile.get("bio", ""), + "country": UserProfileManager.profile.get("country", ""), + "language": UserProfileManager.profile.get("language", "en"), + "loadout_character": _default_character, + "loadout": UserProfileManager.loadout } - var write_obj := NakamaWriteStorageObject.new( - "profiles", "profile", 2, 1, JSON.stringify(data), "" - ) + var write_obj := NakamaWriteStorageObject.new("profiles", "profile", 2, 1, JSON.stringify(data), "") await NakamaManager.client.write_storage_objects_async(NakamaManager.session, [write_obj]) -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── # 3D Preview -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── func _setup_3d_preview() -> void: - anim_player = character_root.get_node_or_null("AnimationPlayer") _update_3d_preview(_default_character) func _update_3d_preview(character_name: String) -> void: - if not character_root: - return + if not character_root: return var node_name: String = CHAR_NODE_MAP.get(character_name, "Masbro") for child in character_root.get_children(): if child is Node3D: child.visible = (child.name == node_name) - # Update AnimationPlayer root if anim_player: - var new_root := character_root.get_node_or_null(node_name) + var new_root: Node3D = character_root.get_node_or_null(node_name) as Node3D if new_root: anim_player.root_node = new_root.get_path() if anim_player.has_animation("animation-pack/idle"): @@ -216,149 +414,153 @@ func _update_3d_preview(character_name: String) -> void: elif anim_player.get_animation_list().size() > 0: anim_player.play(anim_player.get_animation_list()[0]) -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── +# Drag-to-rotate +# ───────────────────────────────────────────────────────────── +func _on_drag_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + var mb := event as InputEventMouseButton + if mb.button_index == MOUSE_BUTTON_LEFT: + _drag_active = mb.pressed + _drag_start_x = mb.position.x + elif event is InputEventMouseMotion and _drag_active: + var mm := event as InputEventMouseMotion + var delta: float = mm.position.x - _drag_start_x + _drag_start_x = mm.position.x + _model_yaw += delta * 0.8 + if character_root: + character_root.rotation_degrees.y = _model_yaw + +# ───────────────────────────────────────────────────────────── # Avatar -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── func _setup_avatar_grid() -> void: - for child in avatar_grid.get_children(): - child.queue_free() - for i in range(UserProfileManager.AVATARS.size()): - var avatar_path: String = UserProfileManager.AVATARS[i] - var btn := Button.new() - btn.custom_minimum_size = Vector2(64, 64) - if ResourceLoader.exists(avatar_path): - var tex := load(avatar_path) as Texture2D - btn.icon = tex + # Assume .tscn has precisely instantiated Button nodes in AvatarGrid + var children = avatar_grid.get_children() + for i in range(min(children.size(), UserProfileManager.AVATARS.size())): + var btn = children[i] as Button + var path: String = UserProfileManager.AVATARS[i] + if ResourceLoader.exists(path): + btn.icon = load(path) as Texture2D btn.expand_icon = true else: btn.text = str(i + 1) + + # Prevent double connection if re-initializing + if btn.pressed.is_connected(_on_avatar_selected): + btn.pressed.disconnect(_on_avatar_selected) + btn.pressed.connect(_on_avatar_selected.bind(i)) - avatar_grid.add_child(btn) func _on_change_avatar_pressed() -> void: avatar_popup.popup_centered() func _on_avatar_selected(index: int) -> void: avatar_popup.hide() - status_label.text = "Saving avatar..." - var success := await UserProfileManager.update_avatar(index) - if success: - var avatar_url: String = UserProfileManager.get_avatar_url() - if ResourceLoader.exists(avatar_url): - avatar_display.texture = load(avatar_url) - status_label.text = "Avatar updated!" + _set_status("Saving avatar...", Color.WHITE) + var ok: bool = await UserProfileManager.update_avatar(index) + if ok: + var url: String = UserProfileManager.get_avatar_url() + if ResourceLoader.exists(url): + avatar_display.texture = load(url) as Texture2D + _set_status("Avatar updated!", Color(0.4, 1.0, 0.4)) else: - status_label.text = "Failed to update avatar" + _set_status("Failed to update avatar.", Color.RED) -# ------------------------------------------------------------------------- -# Name -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── +# Display name +# ───────────────────────────────────────────────────────────── func _on_save_name_pressed() -> void: var new_name := display_name_input.text.strip_edges() if new_name.is_empty(): - status_label.text = "Name cannot be empty" + _set_status("Name cannot be empty.", Color.RED) return - status_label.text = "Saving..." + _set_status("Saving...", Color.WHITE) save_name_btn.disabled = true - var success := await UserProfileManager.update_display_name(new_name) + var ok: bool = await UserProfileManager.update_display_name(new_name) save_name_btn.disabled = false - if success: - status_label.add_theme_color_override("font_color", Color.GREEN) - status_label.text = "Name updated!" + if ok: + _set_status("Name updated!", Color(0.4, 1.0, 0.4)) emit_signal("profile_updated") await get_tree().create_timer(3.0).timeout status_label.text = "" + else: + _set_status("Failed to update name.", Color.RED) -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── # Account -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── func _on_link_account_pressed() -> void: - var dialog := AcceptDialog.new() - dialog.title = "Link Email" - dialog.dialog_text = "Enter your email and password to link this guest account.\nYour progress will be preserved!" - var vbox := VBoxContainer.new() - var email_input := LineEdit.new() - email_input.placeholder_text = "Email" - var password_input := LineEdit.new() - password_input.placeholder_text = "Password" - password_input.secret = true - vbox.add_child(email_input) - vbox.add_child(password_input) - dialog.add_child(vbox) - add_child(dialog) - dialog.popup_centered() - dialog.confirmed.connect(func(): - var email := email_input.text.strip_edges() - var password := password_input.text - if email.is_empty() or password.is_empty(): - status_label.text = "Please fill in all fields" - return - status_label.text = "Linking account..." - var success := await AuthManager.link_email(email, password) - if success: - status_label.text = "Account linked successfully!" - link_account_btn.visible = false - account_type_label.text = "Account: Email" - else: - status_label.text = "Failed to link account" - dialog.queue_free() + link_email_input.text = "" + link_pass_input.text = "" + link_email_dialog.popup_centered() + +func _on_link_account_confirmed() -> void: + var e := link_email_input.text.strip_edges() + var p := link_pass_input.text + if e.is_empty() or p.is_empty(): + _set_status("Fill in all fields.", Color.RED); return + _set_status("Linking account...", Color.WHITE) + var ok: bool = await AuthManager.link_email(e, p) + _set_status( + "Account linked!" if ok else "Failed to link account.", + Color(0.4, 1.0, 0.4) if ok else Color.RED ) + if ok: + link_account_btn.visible = false + account_type_label.text = "Account: Email" func _setup_account_settings_ui() -> void: - # Populate Timezone dropdown tz_dropdown.clear() for i in range(-12, 15): - var prefix = "+" if i >= 0 else "" - tz_dropdown.add_item("GMT " + prefix + str(i)) - - # Connect buttons + tz_dropdown.add_item("GMT %s%d" % ["+" if i >= 0 else "", i]) + submit_cred_btn.pressed.connect(func(): - status_label.text = "Updating credentials..." - var payload = { + _set_status("Updating credentials...", Color.WHITE) + var payload := { "current_password": old_pass_input.text, - "new_email": new_email_input.text, - "new_password": new_pass_input.text + "new_email": new_email_input.text, + "new_password": new_pass_input.text } - var result = await NakamaManager.client.rpc_async(NakamaManager.session, "change_credentials", JSON.stringify(payload)) - if result.is_exception(): - status_label.text = "Failed: " + result.get_exception().message + var r = await NakamaManager.client.rpc_async( + NakamaManager.session, "change_credentials", JSON.stringify(payload) + ) + if r.is_exception(): + _set_status("Error: " + r.get_exception().message, Color.RED) else: - status_label.text = "Credentials updated successfully!" + _set_status("Credentials updated!", Color(0.4, 1.0, 0.4)) acc_settings_dialog.hide() ) - + save_tz_btn.pressed.connect(func(): - var selected_text = tz_dropdown.get_item_text(tz_dropdown.selected) - var res = await NakamaManager.client.update_account_async(NakamaManager.session, null, null, null, null, null, selected_text) - if res.is_exception(): - status_label.text = "TZ Failed: " + res.get_exception().message - else: - status_label.text = "Timezone saved!" - ) - - reset_stats_btn.pressed.connect(func(): - var conf = ConfirmationDialog.new() - conf.dialog_text = "Are you SURE you want to irreversibly wipe all your stats to 0?" - acc_settings_dialog.add_child(conf) - conf.popup_centered() - conf.confirmed.connect(func(): - var r = await NakamaManager.client.rpc_async(NakamaManager.session, "reset_stats", "{}") - if not r.is_exception(): - UserProfileManager.stats = { - "games_played": 0, "games_won": 0, "games_lost": 0, "total_score": 0, "high_score": 0, "play_time_minutes": 0 - } - _load_profile_data() - status_label.text = "Stats wiped completely." - conf.queue_free() - acc_settings_dialog.hide() + var sel := tz_dropdown.get_item_text(tz_dropdown.selected) + var r = await NakamaManager.client.update_account_async( + NakamaManager.session, null, null, null, null, null, sel + ) + _set_status( + "Timezone saved!" if not r.is_exception() else "TZ error: " + r.get_exception().message, + Color(0.4, 1.0, 0.4) if not r.is_exception() else Color.RED ) ) + reset_stats_btn.pressed.connect(func(): + reset_stats_dialog.popup_centered() + ) + +func _on_reset_stats_confirmed() -> void: + var r = await NakamaManager.client.rpc_async(NakamaManager.session, "reset_stats", "{}") + if not r.is_exception(): + UserProfileManager.stats = { + "games_played": 0, "games_won": 0, "games_lost": 0, + "total_score": 0, "high_score": 0, "play_time_minutes": 0 + } + _load_profile_data() + _set_status("Stats wiped.", Color(1, 0.5, 0.3)) + acc_settings_dialog.hide() + func _open_account_settings() -> void: old_pass_input.visible = not AuthManager.is_guest - old_pass_input.text = "" - new_email_input.text = "" - new_pass_input.text = "" + old_pass_input.text = ""; new_email_input.text = ""; new_pass_input.text = "" acc_settings_dialog.popup_centered() func _on_logout_pressed() -> void: @@ -366,58 +568,51 @@ func _on_logout_pressed() -> void: get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn") func _on_admin_panel_pressed() -> void: - if has_node("/root/AdminManager"): - get_node("/root/AdminManager").toggle_admin_panel() - else: - AdminManager.toggle_admin_panel() + AdminManager.toggle_admin_panel() -# ------------------------------------------------------------------------- -# Close / Show -# ------------------------------------------------------------------------- +# ───────────────────────────────────────────────────────────── +# Show / Close +# ───────────────────────────────────────────────────────────── func _on_close_pressed() -> void: hide() emit_signal("closed") func show_panel() -> void: if not UserProfileManager.is_profile_loaded: - status_label.text = "Loading profile from server..." - status_label.add_theme_color_override("font_color", Color.YELLOW) - + _set_status("Loading profile...", Color.YELLOW) _load_profile_data() _load_loadout() + _rebuild_category_items() + _populate_item_grid() _check_admin_visibility() show() - if AuthManager.is_guest: _on_link_account_pressed() - status_label.text = "Please link an email to save your progress permanently!" + _set_status("Link an email to save progress permanently!", Color.YELLOW) func _check_admin_visibility() -> void: admin_panel_btn.hide() - if not NakamaManager.client or not NakamaManager.session: - return - - # Use native Nakama account API to check user metadata for admin role + if not NakamaManager.client or not NakamaManager.session: return var account = await NakamaManager.client.get_account_async(NakamaManager.session) - if account.is_exception(): - return - - # Check account metadata for role - var metadata_str: String = account.user.metadata if account.user.metadata else "{}" - var metadata = JSON.parse_string(metadata_str) - if metadata and metadata is Dictionary: - var role = metadata.get("role", "") - if role in ["owner", "admin"]: - admin_panel_btn.show() + if account.is_exception(): return + var raw: String = account.user.metadata if account.user.metadata else "{}" + var meta = JSON.parse_string(raw) + if meta is Dictionary and meta.get("role", "") in ["owner", "admin"]: + admin_panel_btn.show() + +# ───────────────────────────────────────────────────────────── +# Helpers +# ───────────────────────────────────────────────────────────── +func _set_status(msg: String, color: Color = Color.WHITE) -> void: + status_label.add_theme_color_override("font_color", color) + status_label.text = msg -# ------------------------------------------------------------------------- -# Signal handlers -# ------------------------------------------------------------------------- func _on_profile_updated() -> void: _load_profile_data() + _rebuild_category_items() + _populate_item_grid() func _on_profile_update_failed(error: String) -> void: - status_label.add_theme_color_override("font_color", Color.RED) - status_label.text = error + _set_status(error, Color.RED) await get_tree().create_timer(3.0).timeout status_label.text = "" diff --git a/scripts/ui/shop_panel.gd b/scripts/ui/shop_panel.gd new file mode 100644 index 0000000..c8d3a27 --- /dev/null +++ b/scripts/ui/shop_panel.gd @@ -0,0 +1,91 @@ +extends Control + +signal closed + +@onready var tab_container = $Panel/VBoxContainer/TabContainer +@onready var head_grid = $Panel/VBoxContainer/TabContainer/Head/GridContainer +@onready var costume_grid = $Panel/VBoxContainer/TabContainer/Costume/GridContainer +@onready var glove_grid = $Panel/VBoxContainer/TabContainer/Glove/GridContainer +@onready var accessory_grid = $Panel/VBoxContainer/TabContainer/Accessory/GridContainer +@onready var status_label = $Panel/VBoxContainer/StatusLabel +@onready var back_btn = $Panel/VBoxContainer/Header/BackBtn + +var shop_items = { + "head": [ + {"id": "head_hat1", "name": "Cap", "gold": 100, "star": 0}, + {"id": "head_crown", "name": "Crown", "gold": 0, "star": 50} + ], + "costume": [ + {"id": "costume_red", "name": "Red Suit", "gold": 200, "star": 0}, + {"id": "costume_gold", "name": "Gold Suit", "gold": 0, "star": 100} + ], + "glove": [ + {"id": "glove_leather", "name": "Leather Gloves", "gold": 50, "star": 0} + ], + "accessory": [ + {"id": "acc_glasses", "name": "Sunglasses", "gold": 80, "star": 0} + ] +} + +func _ready(): + back_btn.pressed.connect(_on_close) + _build_shop() + +func show_panel(): + show() + _refresh_shop() + +func _build_shop(): + _populate_grid(head_grid, shop_items["head"]) + _populate_grid(costume_grid, shop_items["costume"]) + _populate_grid(glove_grid, shop_items["glove"]) + _populate_grid(accessory_grid, shop_items["accessory"]) + +func _populate_grid(grid, items): + for child in grid.get_children(): + child.queue_free() + + for item in items: + var btn = Button.new() + btn.custom_minimum_size = Vector2(120, 80) + btn.add_theme_font_size_override("font_size", 14) + btn.text = "%s\nGold: %d\nStar: %d" % [item.name, item.gold, item.star] + btn.pressed.connect(_on_buy_pressed.bind(item)) + grid.add_child(btn) + +func _refresh_shop(): + # Visual update to show which items are owned + # (For simplicity, not disabling buttons directly, relying on backend check) + status_label.text = "Welcome to the Shop!" + +func _on_buy_pressed(item: Dictionary): + if UserProfileManager.inventory.has(item.id): + status_label.text = "Already owned: " + item.name + return + + var price_gold = item.gold + var price_star = item.star + + if UserProfileManager.wallet.get("gold", 0) < price_gold or UserProfileManager.wallet.get("star", 0) < price_star: + status_label.text = "Not enough currency for " + item.name + return + + status_label.text = "Purchasing " + item.name + "..." + + # Determine category + var category = "" + if shop_items["head"].has(item): category = "head" + elif shop_items["costume"].has(item): category = "costume" + elif shop_items["glove"].has(item): category = "glove" + elif shop_items["accessory"].has(item): category = "accessory" + + var success = await UserProfileManager.purchase_item(item.id, price_gold, price_star, category) + if success: + status_label.text = "Successfully purchased: " + item.name + _refresh_shop() + else: + status_label.text = "Failed to purchase. Backend error." + +func _on_close(): + hide() + emit_signal("closed") diff --git a/scripts/ui/shop_panel.gd.uid b/scripts/ui/shop_panel.gd.uid new file mode 100644 index 0000000..5f98188 --- /dev/null +++ b/scripts/ui/shop_panel.gd.uid @@ -0,0 +1 @@ +uid://w0ddjofws4ib diff --git a/server/nakama/tekton_admin.js b/server/nakama/tekton_admin.js index 4c00052..b18c456 100644 --- a/server/nakama/tekton_admin.js +++ b/server/nakama/tekton_admin.js @@ -23,6 +23,9 @@ function InitModule(ctx, logger, nk, initializer) { initializer.registerRpc("get_user_profile", rpcGetUserProfile); initializer.registerRpc("update_user_profile", rpcUpdateUserProfile); + // Store RPCs + initializer.registerRpc("purchase_item", rpcPurchaseItem); + // Leaderboard RPCs initializer.registerRpc("get_leaderboard_stats", rpcGetLeaderboardStats); initializer.registerRpc("admin_update_stats", rpcAdminUpdateStats); @@ -377,6 +380,49 @@ function rpcAdminSetUserRole(ctx, logger, nk, payload) { } } +// ============================================================================= +// Store / Economy RPCs +// ============================================================================= + +function rpcPurchaseItem(ctx, logger, nk, payload) { + if (!ctx.userId) throw new Error("Not authenticated"); + + var request = JSON.parse(payload); + var itemId = request.item_id; + var priceGold = request.price_gold || 0; + var priceStar = request.price_star || 0; + var category = request.category || "accessory"; // head, costume, glove, accessory + + if (!itemId) throw new Error("Item ID required"); + + try { + var changeset = {}; + if (priceGold > 0) changeset["gold"] = -priceGold; + if (priceStar > 0) changeset["star"] = -priceStar; + + // Check wallet and deduct + // nk.walletUpdate throws an error if insufficient funds + nk.walletUpdate(ctx.userId, changeset, {}, true); + + // Record purchase in inventory + var inventoryObj = { + collection: "inventory", + key: itemId, + userId: ctx.userId, + value: { category: category, purchased_at: new Date().toISOString() }, + permissionRead: 1, + permissionWrite: 0 + }; + nk.storageWrite([inventoryObj]); + + logger.info("User " + ctx.userId + " purchased " + itemId); + return JSON.stringify({ success: true, item: itemId }); + } catch (e) { + logger.error("Purchase failed: " + e.message); + throw new Error("Purchase failed: " + e.message); + } +} + // ============================================================================= // User Profile RPCs // =============================================================================