feat: overhaul UI main and profile

This commit is contained in:
2026-04-15 16:26:49 +08:00
parent 01661a56ba
commit f10d777c90
16 changed files with 1888 additions and 710 deletions
@@ -14,7 +14,7 @@
[ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"] [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://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://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"] [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"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uxput"]
+1
View File
@@ -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/panel = SubResource("StyleBoxFlat_panel_bg")
TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_tab_selected") TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_tab_selected")
TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_tab_unselected") TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_tab_unselected")
TextEdit/fonts/font = null
-63
View File
@@ -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}')
-49
View File
@@ -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}')
+118 -25
View File
@@ -2,22 +2,33 @@ extends Control
# UI References - Main Menu # UI References - Main Menu
@onready var main_menu_panel = $MainMenuPanel @onready var main_menu_panel = $MainMenuPanel
@onready var main_title = $MainMenuPanel/HiddenLogic/Title @onready var main_title = %Title
@onready var username_label = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer/Username @onready var username_label = %Username
@onready var main_subtitle = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/VBoxContainer/Subtitle @onready var main_subtitle = %Subtitle
@onready var create_room_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/CreateRoomBtn @onready var create_room_btn = %CreateRoomBtn
@onready var browse_rooms_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/BrowseRoomsBtn @onready var browse_rooms_btn = %BrowseRoomsBtn
@onready var tutorial_btn = $MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel/TutorialBtn @onready var tutorial_btn = %TutorialBtn
@onready var main_menu_profile_btn = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ProfileBox/ProfileBtn @onready var main_menu_profile_btn = %MainProfileBtn
@onready var lobby_settings_btn = $MainMenuPanel/MainContainer/MarginContainer/TopRightPanel/SettingsBtn @onready var avatar_display = %AvatarDisplay
@onready var quit_btn = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/QuitBtn @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 # UI References - Server Selection
@onready var server_option = $MainMenuPanel/HiddenLogic/ServerOption @onready var server_option = %ServerOption
@onready var server_ip_input = $MainMenuPanel/HiddenLogic/ServerIPInput @onready var server_ip_input = %ServerIPInput
# Leaderboard Reference # 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 # UI References - Room List
@onready var room_list_panel = $RoomListPanel @onready var room_list_panel = $RoomListPanel
@@ -108,9 +119,9 @@ const GLOBAL_CHAT_ROOM := "global_lobby"
var _chat_channel = null var _chat_channel = null
var _chat_messages: Array = [] var _chat_messages: Array = []
@onready var chat_display: RichTextLabel = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/ChatPanel/MarginContainer/RichTextLabel @onready var chat_display: RichTextLabel = %RichTextLabel
@onready var chat_input: LineEdit = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/ChatInput @onready var chat_input: LineEdit = %ChatInput
@onready var chat_send_btn: Button = $MainMenuPanel/MainContainer/MarginContainer/LeftPanel/HBoxContainer/SendBtn @onready var chat_send_btn: Button = %SendBtn
# Server Selection Controls (Now in tscn) # Server Selection Controls (Now in tscn)
# var server_option: OptionButton # var server_option: OptionButton
@@ -142,8 +153,15 @@ func _ready():
tutorial_btn.pressed.connect(_on_tutorial_pressed) tutorial_btn.pressed.connect(_on_tutorial_pressed)
if main_menu_profile_btn: if main_menu_profile_btn:
main_menu_profile_btn.pressed.connect(_on_profile_btn_pressed) 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: if lobby_settings_btn:
lobby_settings_btn.pressed.connect(_on_settings_pressed) lobby_settings_btn.pressed.connect(_on_settings_pressed)
# Shop Button
if shop_btn:
shop_btn.pressed.connect(_on_shop_pressed)
if leaderboard_btn: if leaderboard_btn:
leaderboard_btn.pressed.connect(_on_leaderboard_pressed) leaderboard_btn.pressed.connect(_on_leaderboard_pressed)
if quit_btn: if quit_btn:
@@ -252,6 +270,33 @@ func _ready():
# Setup # 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: func _load_character_textures() -> void:
"""Load character preview textures.""" """Load character preview textures."""
var characters = { var characters = {
@@ -672,12 +717,19 @@ func _on_profile_btn_pressed() -> void:
if not profile_panel_instance: if not profile_panel_instance:
var profile_panel_scene := load("res://scenes/ui/profile_panel.tscn") var profile_panel_scene := load("res://scenes/ui/profile_panel.tscn")
profile_panel_instance = profile_panel_scene.instantiate() 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 # Full-screen overlay — fill the entire lobby viewport
profile_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) profile_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(profile_panel_instance) 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: func _on_logout_pressed() -> void:
AuthManager.logout() AuthManager.logout()
@@ -696,26 +748,60 @@ func _on_settings_pressed():
settings_menu.name = "SettingsMenu" settings_menu.name = "SettingsMenu"
add_child(settings_menu) 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") var close_btn = settings_menu.get_node_or_null("PanelContainer/VBoxContainer/Header/CloseButton")
if close_btn: if close_btn:
# settings_menu.gd handles basic visibility, but we can override or add to it close_btn.pressed.connect(func():
pass if lobby_panel and lobby_panel.visible:
pass # lobby_panel restored below via _restore_panel
_restore_after_settings()
)
if settings_menu: 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() 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: func _on_leaderboard_pressed() -> void:
if not leaderboard_panel_instance: if not leaderboard_panel_instance:
var leaderboard_panel_scene := load("res://scenes/ui/leaderboard_panel.tscn") var leaderboard_panel_scene := load("res://scenes/ui/leaderboard_panel.tscn")
if leaderboard_panel_scene: if leaderboard_panel_scene:
leaderboard_panel_instance = leaderboard_panel_scene.instantiate() 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 # Full-screen overlay
leaderboard_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) leaderboard_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(leaderboard_panel_instance) add_child(leaderboard_panel_instance)
if leaderboard_panel_instance: if leaderboard_panel_instance:
if main_menu_panel:
main_menu_panel.hide()
leaderboard_panel_instance.show_panel() leaderboard_panel_instance.show_panel()
func _go_to_login() -> void: func _go_to_login() -> void:
@@ -873,15 +959,22 @@ func _on_profile_updated() -> void:
if username_label: if username_label:
username_label.text = display_name username_label.text = display_name
if main_menu_profile_btn: if avatar_display:
var avatar_url = UserProfileManager.get_avatar_url() var avatar_url = UserProfileManager.get_avatar_url()
if ResourceLoader.exists(avatar_url): if ResourceLoader.exists(avatar_url):
main_menu_profile_btn.icon = load(avatar_url) avatar_display.texture = load(avatar_url)
main_menu_profile_btn.expand_icon = true if main_menu_profile_btn:
main_menu_profile_btn.text = "" 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 # Sync to LobbyManager
LobbyManager.set_player_name(display_name) LobbyManager.set_player_name(display_name)
_setup_3d_preview()
# ============================================================================= # =============================================================================
# Player Slot Updates # Player Slot Updates
+411 -130
View File
@@ -3,8 +3,21 @@
[ext_resource type="Script" uid="uid://b5q6yekyk0tld" path="res://scenes/lobby.gd" id="1_lp6xi"] [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="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="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="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="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] [node name="Lobby" type="Control" unique_id=1490767889]
layout_mode = 3 layout_mode = 3
@@ -16,7 +29,17 @@ grow_vertical = 2
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
script = ExtResource("1_lp6xi") 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] [node name="Background" type="TextureRect" parent="." unique_id=767675801]
modulate = Color(1, 1, 1, 0.28235295)
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -38,226 +61,480 @@ texture = ExtResource("4_nqcc7")
expand_mode = 2 expand_mode = 2
flip_h = true 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 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 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 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 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_left = 30
theme_override_constants/margin_top = 30 theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 30 theme_override_constants/margin_right = 30
theme_override_constants/margin_bottom = 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 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 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] [node name="HBoxContainer2" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=473798164]
custom_minimum_size = Vector2(80, 80)
layout_mode = 2 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 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 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 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" 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 layout_mode = 2
theme_override_colors/font_color = Color(0.71, 0.575, 0.035, 1) theme_override_colors/font_color = Color(0.7, 0.6, 0.2, 1)
theme_override_font_sizes/font_size = 18 theme_override_fonts/font = ExtResource("5_pc087")
theme_override_font_sizes/font_size = 14
text = "EU SERVER" text = "EU SERVER"
[node name="Spacer" type="Control" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=2013644806] [node name="Control" type="Control" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/HBoxContainer2" unique_id=177944371]
custom_minimum_size = Vector2(0, 20) custom_minimum_size = Vector2(80, 0)
layout_direction = 3
layout_mode = 2 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 layout_mode = 2
theme_override_constants/separation = 10
[node name="StarPanel" type="PanelContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel/CurrenciesBox" unique_id=1482932751] [node name="CurrencyRow" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=59384589]
custom_minimum_size = Vector2(160, 40)
layout_mode = 2 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] [node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=1807032398]
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]
layout_mode = 2 layout_mode = 2
size_flags_vertical = 3 size_flags_vertical = 3
[node name="ChatPanel" type="PanelContainer" parent="MainMenuPanel/MainContainer/MarginContainer/LeftPanel" unique_id=1582318454] [node name="StarPanel" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer" unique_id=509438]
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]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 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) custom_minimum_size = Vector2(40, 40)
layout_mode = 2 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 = ">" 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 layout_mode = 2
size_flags_horizontal = 8 size_flags_horizontal = 3
size_flags_vertical = 0
theme_override_constants/separation = 15
[node name="CartBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=2015877617] [node name="RightCol" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox" unique_id=1457624180]
custom_minimum_size = Vector2(60, 60) custom_minimum_size = Vector2(280, 0)
layout_mode = 2 layout_mode = 2
text = "🛒"
[node name="TicketBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/TopRightPanel" unique_id=2142342229] [node name="TopRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=20449742]
custom_minimum_size = Vector2(60, 60) custom_minimum_size = Vector2(500, 0)
layout_mode = 2 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] [node name="ProfileBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=752873406]
custom_minimum_size = Vector2(60, 60) unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44)
layout_mode = 2 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] [node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=312524216]
custom_minimum_size = Vector2(60, 60) unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44)
layout_mode = 2 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] [node name="CartBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=456149005]
custom_minimum_size = Vector2(60, 60) unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44)
layout_mode = 2 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 layout_mode = 2
size_flags_horizontal = 8 theme_override_fonts/font = ExtResource("5_pc087")
size_flags_vertical = 4 disabled = true
theme_override_constants/separation = 20 text = "BATTLE PASS"
[node name="Banner1" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=1918689135] [node name="SocialBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=82719328]
custom_minimum_size = Vector2(250, 120) unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44)
layout_mode = 2 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" text = "Banner Slot"
[node name="Banner2" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=336081993] [node name="HBoxContainer2" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners" unique_id=1932828178]
custom_minimum_size = Vector2(250, 120)
layout_mode = 2 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" text = "Banner Slot"
[node name="Banner3" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/RightPanel" unique_id=942094893] [node name="HBoxContainer3" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/Banners" unique_id=1502007727]
custom_minimum_size = Vector2(250, 120)
layout_mode = 2 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" 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 layout_mode = 2
size_flags_horizontal = 8 size_flags_vertical = 3
size_flags_vertical = 8
theme_override_constants/separation = 15
[node name="TutorialBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1468068637] [node name="BottomRightPanel" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/RightCol" unique_id=987]
custom_minimum_size = Vector2(160, 60)
layout_mode = 2 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" text = "TUTORIAL"
[node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1069893052] [node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel" unique_id=8]
custom_minimum_size = Vector2(160, 60) unique_name_in_owner = true
custom_minimum_size = Vector2(0, 50)
layout_mode = 2 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" text = "HOST"
[node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/MainContainer/MarginContainer/BottomRightPanel" unique_id=1488596739] [node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel" unique_id=4595]
custom_minimum_size = Vector2(160, 60) unique_name_in_owner = true
custom_minimum_size = Vector2(0, 50)
layout_mode = 2 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" text = "BROWSE"
[node name="HiddenLogic" type="Control" parent="MainMenuPanel" unique_id=1708805146] [node name="HiddenLogic" type="Control" parent="MainMenuPanel" unique_id=345]
visible = false visible = false
layout_mode = 3 layout_mode = 3
anchors_preset = 0 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 layout_mode = 0
text = "TEKTON DASH" 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 layout_mode = 0
selected = 3 selected = 3
item_count = 4 item_count = 4
@@ -270,7 +547,8 @@ popup/item_2/id = 2
popup/item_3/text = "Tekton Dash EU" popup/item_3/text = "Tekton Dash EU"
popup/item_3/id = 3 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 layout_mode = 0
text = "127.0.0.1" text = "127.0.0.1"
@@ -346,6 +624,7 @@ layout_mode = 2
text = "BACK" text = "BACK"
[node name="ProfileBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=1473526002] [node name="ProfileBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=1473526002]
visible = false
custom_minimum_size = Vector2(80, 44) custom_minimum_size = Vector2(80, 44)
layout_mode = 2 layout_mode = 2
text = "PROFILE" text = "PROFILE"
@@ -405,6 +684,7 @@ layout_mode = 2
theme_override_constants/separation = 10 theme_override_constants/separation = 10
[node name="ProfileBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1726139031] [node name="ProfileBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1726139031]
visible = false
custom_minimum_size = Vector2(80, 32) custom_minimum_size = Vector2(80, 32)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("5_pc087") theme_override_fonts/font = ExtResource("5_pc087")
@@ -412,6 +692,7 @@ theme_override_font_sizes/font_size = 11
text = "Profile" text = "Profile"
[node name="LogoutBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1221873519] [node name="LogoutBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=1221873519]
visible = false
custom_minimum_size = Vector2(70, 32) custom_minimum_size = Vector2(70, 32)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("5_pc087") theme_override_fonts/font = ExtResource("5_pc087")
+17 -1
View File
@@ -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://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://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="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="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://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"] [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 content_margin_bottom = 20.0
texture = SubResource("CompressedTexture2D_chjal") 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] [node name="Main" type="Node3D" unique_id=864552263]
script = ExtResource("1_xcpe3") script = ExtResource("1_xcpe3")
@@ -1746,10 +1751,12 @@ offset_right = 150.0
offset_bottom = 150.0 offset_bottom = 150.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_5poiv")
[node name="NetworkPanel" type="Panel" parent="PauseMenu/Panel" unique_id=935918250] [node name="NetworkPanel" type="Panel" parent="PauseMenu/Panel" unique_id=935918250]
layout_mode = 2 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] [node name="NetworkInfo" type="HBoxContainer" parent="PauseMenu/Panel/NetworkPanel" unique_id=1194782604]
visible = false visible = false
@@ -1778,11 +1785,13 @@ vertical_alignment = 1
[node name="VBox" type="VBoxContainer" parent="PauseMenu/Panel" unique_id=837757440] [node name="VBox" type="VBoxContainer" parent="PauseMenu/Panel" unique_id=837757440]
layout_mode = 2 layout_mode = 2
theme = ExtResource("38_5he1u")
theme_override_constants/separation = 15 theme_override_constants/separation = 15
alignment = 1 alignment = 1
[node name="Title" type="Label" parent="PauseMenu/Panel/VBox" unique_id=1740539610] [node name="Title" type="Label" parent="PauseMenu/Panel/VBox" unique_id=1740539610]
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("13_j8jky")
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 24
text = "Menu" text = "Menu"
horizontal_alignment = 1 horizontal_alignment = 1
@@ -1794,22 +1803,26 @@ layout_mode = 2
[node name="ResumeBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=79086881] [node name="ResumeBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=79086881]
custom_minimum_size = Vector2(200, 45) custom_minimum_size = Vector2(200, 45)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("13_j8jky")
text = "Resume" text = "Resume"
[node name="HowToPlayBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=987654321] [node name="HowToPlayBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=987654321]
visible = false visible = false
custom_minimum_size = Vector2(200, 45) custom_minimum_size = Vector2(200, 45)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("13_j8jky")
text = "How to Play" text = "How to Play"
[node name="SettingsBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=215865401] [node name="SettingsBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=215865401]
custom_minimum_size = Vector2(200, 45) custom_minimum_size = Vector2(200, 45)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("13_j8jky")
text = "Settings" text = "Settings"
[node name="QuitBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=1771850243] [node name="QuitBtn" type="Button" parent="PauseMenu/Panel/VBox" unique_id=1771850243]
custom_minimum_size = Vector2(200, 45) custom_minimum_size = Vector2(200, 45)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("13_j8jky")
text = "Quit Match" text = "Quit Match"
[node name="HowToPlayPanel" type="CanvasLayer" parent="." unique_id=123456789] [node name="HowToPlayPanel" type="CanvasLayer" parent="." unique_id=123456789]
@@ -1837,6 +1850,7 @@ offset_right = 300.0
offset_bottom = 250.0 offset_bottom = 250.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("38_5he1u")
[node name="VBox" type="VBoxContainer" parent="HowToPlayPanel/Panel" unique_id=123456792] [node name="VBox" type="VBoxContainer" parent="HowToPlayPanel/Panel" unique_id=123456792]
layout_mode = 2 layout_mode = 2
@@ -1845,6 +1859,7 @@ alignment = 1
[node name="Title" type="Label" parent="HowToPlayPanel/Panel/VBox" unique_id=123456793] [node name="Title" type="Label" parent="HowToPlayPanel/Panel/VBox" unique_id=123456793]
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1)
theme_override_font_sizes/font_size = 22 theme_override_font_sizes/font_size = 22
text = "How to Play" text = "How to Play"
horizontal_alignment = 1 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] [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Free Mode" unique_id=123456796]
layout_mode = 2 layout_mode = 2
theme_override_colors/default_color = Color(0.5686275, 0.36862746, 0.12941177, 1)
bbcode_enabled = true bbcode_enabled = true
text = "[b]Free Mode[/b] text = "[b]Free Mode[/b]
+20
View File
@@ -500,6 +500,10 @@ func set_character(character_name: String) -> void:
# Apply outline shader to the active character # Apply outline shader to the active character
if active_character: if active_character:
_apply_outline_recursive(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): func _apply_outline_recursive(node: Node):
if node is MeshInstance3D and node.mesh: if node is MeshInstance3D and node.mesh:
@@ -517,6 +521,22 @@ func _apply_outline_recursive(node: Node):
for child in node.get_children(): for child in node.get_children():
_apply_outline_recursive(child) _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") @rpc("any_peer", "call_local", "reliable")
func sync_character(character_name: String) -> void: func sync_character(character_name: String) -> void:
"""Sync character selection across all clients.""" """Sync character selection across all clients."""
+2
View File
@@ -186,12 +186,14 @@ layout_mode = 2
text = "Sign In" text = "Sign In"
[node name="SocialLabel" type="Label" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=840440943] [node name="SocialLabel" type="Label" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=840440943]
visible = false
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1) theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
text = "─────── or continue with ───────" text = "─────── or continue with ───────"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="SocialButtons" type="HBoxContainer" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=1899762021] [node name="SocialButtons" type="HBoxContainer" parent="CenterContainer/LayoutVBox/TabContainer/Login/VBox" unique_id=1899762021]
visible = false
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 12 theme_override_constants/separation = 12
alignment = 1 alignment = 1
+575 -192
View File
@@ -2,19 +2,21 @@
[ext_resource type="Script" uid="uid://y6wswtalyiho" path="res://scripts/ui/profile_panel.gd" id="1"] [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="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://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://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://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="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"] [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_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_source = 2
ambient_light_color = Color(0.6, 0.65, 0.8, 1) ambient_light_color = Color(0.55, 0.65, 0.9, 1)
ambient_light_energy = 0.6 ambient_light_energy = 0.7
[node name="ProfilePanel" type="Control" unique_id=229091481] [node name="ProfilePanel" type="Control" unique_id=229091481]
layout_mode = 3 layout_mode = 3
@@ -26,367 +28,748 @@ grow_vertical = 2
theme = ExtResource("2") theme = ExtResource("2")
script = ExtResource("1") script = ExtResource("1")
[node name="Background" type="ColorRect" parent="." unique_id=650894265] [node name="Background" type="ColorRect" parent="." unique_id=1526402211]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 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 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 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] [node name="MainMargin" type="MarginContainer" parent="." unique_id=949618423]
custom_minimum_size = Vector2(420, 0) layout_mode = 1
layout_mode = 2 anchors_preset = 15
size_flags_horizontal = 3 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 layout_mode = 2
theme_override_constants/separation = 16 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 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 unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44) custom_minimum_size = Vector2(100, 100)
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)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_pti1t")
expand_mode = 1 expand_mode = 1
stretch_mode = 5 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 layout_mode = 2
size_flags_horizontal = 3 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 unique_name_in_owner = true
layout_mode = 2 custom_minimum_size = Vector2(100, 32)
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)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
max_length = 50 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 unique_name_in_owner = true
custom_minimum_size = Vector2(80, 44) custom_minimum_size = Vector2(48, 32)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "Save" 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 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 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 layout_mode = 2
theme_override_colors/font_color = Color(0.41568628, 0.6392157, 0.14117648, 1) theme_override_colors/font_color = Color(0.9, 0.7, 0.3, 1)
theme_override_fonts/font = ExtResource("3_e13i6")
theme_override_font_sizes/font_size = 18 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 layout_mode = 2
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1) size_flags_horizontal = 3
theme_override_font_sizes/font_size = 11 theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1)
text = "This character will be auto-selected when you join or create a room." theme_override_fonts/font = ExtResource("3_font")
autowrap_mode = 2 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 layout_mode = 2
theme_override_constants/separation = 8 theme_override_constants/separation = 8
alignment = 1 alignment = 1
[node name="CharLeftBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection/CharacterSelector" unique_id=1225290843] [node name="Icon" type="Label" parent="MainMargin/MainHBox/LeftCol/CurrencyRow/GoldPanel/Margin/HBox" unique_id=351462654]
unique_name_in_owner = true
custom_minimum_size = Vector2(44, 44)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_colors/font_color = Color(0.8, 0.6, 0.2, 1)
text = "◀" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(120, 44)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(0.5686275, 0.36862746, 0.12941177, 1)
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
theme_override_font_sizes/font_size = 18 theme_override_font_sizes/font_size = 18
text = "Copper" text = "0"
horizontal_alignment = 1 horizontal_alignment = 2
vertical_alignment = 1
[node name="CharRightBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/LoadoutSection/CharacterSelector" unique_id=320910811] [node name="StatsPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftCol" unique_id=1663081199]
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]
layout_mode = 2 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 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 layout_mode = 2
theme_override_colors/font_color = Color(0.41568628, 0.6392157, 0.14117648, 1) theme_override_constants/separation = 4
theme_override_fonts/font = ExtResource("3_e13i6")
theme_override_font_sizes/font_size = 16
text = "Statistics"
[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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) theme_override_colors/font_color = Color(0.7, 0.5, 0.3, 1)
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "Games Played: 0" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1)
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "Win Rate: 0%" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.57, 0.32090998, 0.1767, 1) theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1)
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "High Score: 0" theme_override_font_sizes/font_size = 13
text = "Win Rate: 62.5%"
[node name="AccountSection" type="HBoxContainer" parent="MainLayout/LeftPanel/LeftVBox" unique_id=1267378790] [node name="HighScore" type="Label" parent="MainMargin/MainHBox/LeftCol/StatsPanel/Margin/VBox" unique_id=1418839301]
layout_mode = 2
theme_override_constants/separation = 8
[node name="LinkAccountBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/AccountSection" unique_id=1126355767]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_colors/font_color = Color(0.8, 0.65, 0.45, 1)
text = "Link Email (Keep Progress)" 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 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 layout_mode = 2
theme_override_colors/font_color = Color(1, 0.4, 0.4, 1) theme_override_colors/font_color = Color(1, 0.4, 0.4, 1)
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "Server Admin Panel" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("3_e13i6") theme_override_fonts/font = ExtResource("3_font")
text = "Logout" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 12 theme_override_font_sizes/font_size = 11
horizontal_alignment = 1 horizontal_alignment = 1
[node name="RightPanel" type="SubViewportContainer" parent="MainLayout" unique_id=2313053] [node name="BackBtn" type="Button" parent="MainMargin/MainHBox/LeftCol" unique_id=2005161566]
custom_minimum_size = Vector2(400, 0) 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 layout_mode = 2
size_flags_horizontal = 3 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 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 unique_name_in_owner = true
own_world_3d = true own_world_3d = true
transparent_bg = true transparent_bg = true
handle_input_locally = false handle_input_locally = false
size = Vector2i(746, 720) size = Vector2i(627, 487)
render_target_update_mode = 4 render_target_update_mode = 4
[node name="WorldEnvironment" type="WorldEnvironment" parent="MainLayout/RightPanel/PreviewViewport" unique_id=1213356390] [node name="WorldEnvironment" type="WorldEnvironment" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1660814495]
environment = SubResource("Environment_preview") environment = SubResource("Env_preview")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=524977852] [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, 0, 4, 0) 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.4 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2, 2)
light_color = Color(0.5, 0.6, 1, 1) light_color = Color(0.4, 0.55, 1, 1)
light_energy = 0.5 light_energy = 0.6
omni_range = 8.0 omni_range = 10.0
[node name="Camera3D" type="Camera3D" parent="MainLayout/RightPanel/PreviewViewport" unique_id=1682800090] [node name="Camera3D" type="Camera3D" parent="MainMargin/MainHBox/CenterCol/ViewportWrapper/ViewportContainer/PreviewViewport" unique_id=1696846826]
transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 1.6, 3.2) transform = Transform3D(1, 0, 0, 0, 0.97072834, 0.2401802, 0, -0.2401802, 0.97072834, 0, 0.8262464, 2.6238613)
current = true 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 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
visible = false visible = false
[node name="Bob" parent="MainLayout/RightPanel/PreviewViewport/CharacterRoot" unique_id=1951694644 instance=ExtResource("4_bob")] [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.484627, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.484, 0)
visible = false 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
visible = false 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) 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") root_node = NodePath("../Oldpop")
libraries/animation-pack = ExtResource("5_animlib") libraries/animation-pack = ExtResource("5_animlib")
autoplay = &"animation-pack/idle" 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 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" title = "Select Avatar"
size = Vector2i(320, 220) 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 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_left = 4.0
offset_top = 4.0 offset_top = 4.0
offset_right = 316.0 offset_right = 316.0
offset_bottom = 216.0 offset_bottom = 216.0
grow_horizontal = 2
grow_vertical = 2
columns = 3 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 unique_name_in_owner = true
title = "Account Settings" title = "Account Settings"
size = Vector2i(360, 480) 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_left = 8.0
offset_top = 8.0 offset_top = 8.0
offset_right = 352.0 offset_right = 352.0
offset_bottom = 431.0 offset_bottom = 431.0
theme_override_constants/separation = 12 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 layout_mode = 2
text = "Update Credentials" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
placeholder_text = "Current Password" placeholder_text = "Current Password"
secret = true 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
placeholder_text = "New Email" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
placeholder_text = "New Password" placeholder_text = "New Password"
secret = true 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Submit Credentials" 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 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 layout_mode = 2
text = "Timezone" 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 unique_name_in_owner = true
layout_mode = 2 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Save Timezone" 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 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(1, 0, 0, 1) theme_override_colors/font_color = Color(1, 0, 0, 1)
+93
View File
@@ -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
+69 -1
View File
@@ -9,6 +9,9 @@ signal avatar_changed(url: String)
# Profile data # Profile data
var profile: Dictionary = {} var profile: Dictionary = {}
var stats: 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 var is_profile_loaded: bool = false
# Nakama storage collection names # Nakama storage collection names
@@ -47,6 +50,8 @@ func load_profile() -> Dictionary:
# Reset state first to ensure no old account data carries over # Reset state first to ensure no old account data carries over
profile = {} profile = {}
stats = {} stats = {}
wallet = {"gold": 0, "star": 0}
inventory = []
is_profile_loaded = false is_profile_loaded = false
if not NakamaManager.session: if not NakamaManager.session:
@@ -84,6 +89,18 @@ func load_profile() -> Dictionary:
var stored_data = JSON.parse_string(storage_result.objects[0].value) var stored_data = JSON.parse_string(storage_result.objects[0].value)
if stored_data: if stored_data:
profile.merge(stored_data, true) 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 # Load stats
await load_stats() await load_stats()
@@ -98,6 +115,21 @@ func load_profile() -> Dictionary:
return profile 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: func load_stats() -> Dictionary:
# Reset stats first to ensure fresh data for new logins # Reset stats first to ensure fresh data for new logins
stats = {} stats = {}
@@ -223,7 +255,8 @@ func _save_profile_data() -> bool:
"avatar_index": profile.get("avatar_index", 0), "avatar_index": profile.get("avatar_index", 0),
"bio": profile.get("bio", ""), "bio": profile.get("bio", ""),
"country": profile.get("country", ""), "country": profile.get("country", ""),
"language": profile.get("language", "en") "language": profile.get("language", "en"),
"loadout": loadout
} }
var write_obj := NakamaWriteStorageObject.new( var write_obj := NakamaWriteStorageObject.new(
@@ -247,6 +280,41 @@ func _save_profile_data() -> bool:
emit_signal("profile_updated") emit_signal("profile_updated")
return true 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 # Stats Management
# ============================================================================= # =============================================================================
+443 -248
View File
@@ -1,163 +1,369 @@
extends Control extends Control
## Profile panel controller — full-scene loadout screen. ## Profile panel — 3-column layout.
## Left: profile info + default character selector. ## Left : profile card, currencies, stats, account buttons.
## Right: 3D SubViewport character preview. ## Center: category tabs overlay, 3D rotatable preview, char-selector overlay.
## Right : item info card, 3×3 owned-item grid, pagination.
signal closed signal closed
signal profile_updated signal profile_updated
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
# UI References # UI references (all resolved via %UniqueName — must match .tscn)
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
@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
# Account Settings refs # Left column
@onready var acc_settings_dialog := %AccountSettingsDialog as AcceptDialog @onready var avatar_display := %AvatarDisplay as TextureRect
@onready var old_pass_input := %OldPassInput as LineEdit @onready var display_name_input := %DisplayNameInput as LineEdit
@onready var new_email_input := %NewEmailInput as LineEdit @onready var save_name_btn := %SaveNameBtn as Button
@onready var new_pass_input := %NewPassInput as LineEdit @onready var change_avatar_btn := %ChangeAvatarBtn as Button
@onready var submit_cred_btn := %SubmitCredBtn as Button @onready var account_type_label := %AccountType as Label
@onready var tz_dropdown := %TzDropdown as OptionButton @onready var link_account_btn := %LinkAccountBtn as Button
@onready var save_tz_btn := %SaveTzBtn as Button @onready var gold_label := %GoldLabel as Label
@onready var reset_stats_btn := %ResetStatsBtn as Button @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 # Center column (overlays inside CenterWrapper)
@onready var char_left_btn := %CharLeftBtn as Button @onready var head_tab_btn := %HeadTabBtn as Button
@onready var char_right_btn := %CharRightBtn 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 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 # Back button + popups
@onready var character_root := %CharacterRoot as Node3D @onready var back_btn := %BackBtn as Button
@onready var anim_player: AnimationPlayer @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
# ------------------------------------------------------------------------- # New static popups
# State @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"] 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 = { const CHAR_NODE_MAP: Dictionary = {
"Copper": "Oldpop", "Copper": "Oldpop",
"Dabro": "Masbro", "Dabro": "Masbro",
"Gatot": "Gatot", "Gatot": "Gatot",
"Pip": "Bob" "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 _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: func _ready() -> void:
_build_slot_refs()
_connect_signals() _connect_signals()
_load_profile_data()
_setup_avatar_grid() _setup_avatar_grid()
_setup_account_settings_ui() _setup_account_settings_ui()
_load_profile_data()
_load_loadout() _load_loadout()
_setup_3d_preview() _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: func _connect_signals() -> void:
back_btn.pressed.connect(_on_close_pressed) back_btn.pressed.connect(_on_close_pressed)
change_avatar_btn.pressed.connect(_on_change_avatar_pressed) change_avatar_btn.pressed.connect(_on_change_avatar_pressed)
save_name_btn.pressed.connect(_on_save_name_pressed) save_name_btn.pressed.connect(_on_save_name_pressed)
link_account_btn.pressed.connect(_on_link_account_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) admin_panel_btn.pressed.connect(_on_admin_panel_pressed)
logout_btn.pressed.connect(_on_logout_pressed) logout_btn.pressed.connect(_on_logout_pressed)
char_left_btn.pressed.connect(func(): _cycle_loadout_char(-1)) char_left_btn.pressed.connect(func(): _cycle_loadout_char(-1))
char_right_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) 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_updated.connect(_on_profile_updated)
UserProfileManager.profile_update_failed.connect(_on_profile_update_failed) 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: func _load_profile_data() -> void:
var profile := UserProfileManager.profile var prof := UserProfileManager.profile
var stats := UserProfileManager.stats 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 display_name_input.max_length = 6
var avatar_url: String = UserProfileManager.get_avatar_url() var url: String = UserProfileManager.get_avatar_url()
if ResourceLoader.exists(avatar_url): if ResourceLoader.exists(url):
avatar_display.texture = load(avatar_url) avatar_display.texture = load(url) as Texture2D
games_played_label.text = "Games Played: %d" % stats.get("games_played", 0) games_played_label.text = "Games Played: %d" % stats.get("games_played", 0)
win_rate_label.text = "Win Rate: %.1f%%" % UserProfileManager.get_win_rate() win_rate_label.text = "Win Rate: %.1f%%" % UserProfileManager.get_win_rate()
high_score_label.text = "High Score: %d" % stats.get("high_score", 0) 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: if AuthManager.is_guest:
account_type_label.text = "Account: Guest" account_type_label.text = "Account : Guest"
link_account_btn.visible = true link_account_btn.visible = true
link_account_btn.text = "Link Email (Keep Progress)"
else: else:
var mode_name := _get_auth_mode_name(AuthManager.auth_mode) account_type_label.text = "Account : %s" % _auth_mode_name(AuthManager.auth_mode)
account_type_label.text = "Account: %s" % mode_name
link_account_btn.visible = false link_account_btn.visible = false
status_label.text = "" status_label.text = ""
func _get_auth_mode_name(mode: int) -> String: func _auth_mode_name(mode: int) -> String:
match mode: match mode:
AuthManager.AuthMode.EMAIL: AuthManager.AuthMode.EMAIL: return "Email"
return "Email" AuthManager.AuthMode.GOOGLE: return "Google"
AuthManager.AuthMode.GOOGLE: AuthManager.AuthMode.APPLE: return "Apple"
return "Google" AuthManager.AuthMode.FACEBOOK: return "Facebook"
AuthManager.AuthMode.APPLE: _: return "Guest"
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: func _load_loadout() -> void:
"""Load the saved default character from profile storage.""" var saved: String = UserProfileManager.profile.get("loadout_character", "Copper")
var saved = UserProfileManager.profile.get("loadout_character", "Copper") var idx: int = CHARACTERS.find(saved)
var idx = CHARACTERS.find(saved) _loadout_index = max(idx, 0)
_loadout_index = max(idx, 0)
_default_character = CHARACTERS[_loadout_index] _default_character = CHARACTERS[_loadout_index]
_refresh_loadout_ui() _refresh_loadout_ui()
func _cycle_loadout_char(direction: int) -> void: func _cycle_loadout_char(direction: int) -> void:
_loadout_index = wrapi(_loadout_index + direction, 0, CHARACTERS.size()) _loadout_index = wrapi(_loadout_index + direction, 0, CHARACTERS.size())
_refresh_loadout_ui() _refresh_loadout_ui()
_update_3d_preview(CHARACTERS[_loadout_index])
func _refresh_loadout_ui() -> void: func _refresh_loadout_ui() -> void:
var char_name := CHARACTERS[_loadout_index] 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 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 set_default_btn.disabled = is_default
_update_3d_preview(char_name) _update_3d_preview(char_name)
@@ -165,50 +371,42 @@ func _on_set_default_pressed() -> void:
var char_name := CHARACTERS[_loadout_index] var char_name := CHARACTERS[_loadout_index]
_default_character = char_name _default_character = char_name
UserProfileManager.profile["loadout_character"] = char_name UserProfileManager.profile["loadout_character"] = char_name
# Also apply immediately to LobbyManager
if LobbyManager.available_characters.has(char_name): if LobbyManager.available_characters.has(char_name):
LobbyManager.local_character_index = LobbyManager.available_characters.find(char_name) LobbyManager.local_character_index = LobbyManager.available_characters.find(char_name)
status_label.text = "Loadout set to: " + char_name _set_status("Default set to: " + char_name, Color(0.4, 0.9, 0.4))
status_label.add_theme_color_override("font_color", Color(0.4, 0.8, 0.4))
_refresh_loadout_ui() _refresh_loadout_ui()
# Persist to storage
_save_loadout_to_profile() _save_loadout_to_profile()
# Sync to leaderboard immediately
UserProfileManager.submit_to_leaderboard() 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: 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 := { var data := {
"avatar_index": UserProfileManager.profile.get("avatar_index", 0), "avatar_index": UserProfileManager.profile.get("avatar_index", 0),
"bio": UserProfileManager.profile.get("bio", ""), "bio": UserProfileManager.profile.get("bio", ""),
"country": UserProfileManager.profile.get("country", ""), "country": UserProfileManager.profile.get("country", ""),
"language": UserProfileManager.profile.get("language", "en"), "language": UserProfileManager.profile.get("language", "en"),
"loadout_character": _default_character "loadout_character": _default_character,
"loadout": UserProfileManager.loadout
} }
var write_obj := NakamaWriteStorageObject.new( var write_obj := NakamaWriteStorageObject.new("profiles", "profile", 2, 1, JSON.stringify(data), "")
"profiles", "profile", 2, 1, JSON.stringify(data), ""
)
await NakamaManager.client.write_storage_objects_async(NakamaManager.session, [write_obj]) await NakamaManager.client.write_storage_objects_async(NakamaManager.session, [write_obj])
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
# 3D Preview # 3D Preview
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
func _setup_3d_preview() -> void: func _setup_3d_preview() -> void:
anim_player = character_root.get_node_or_null("AnimationPlayer")
_update_3d_preview(_default_character) _update_3d_preview(_default_character)
func _update_3d_preview(character_name: String) -> void: func _update_3d_preview(character_name: String) -> void:
if not character_root: if not character_root: return
return
var node_name: String = CHAR_NODE_MAP.get(character_name, "Masbro") var node_name: String = CHAR_NODE_MAP.get(character_name, "Masbro")
for child in character_root.get_children(): for child in character_root.get_children():
if child is Node3D: if child is Node3D:
child.visible = (child.name == node_name) child.visible = (child.name == node_name)
# Update AnimationPlayer root
if anim_player: 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: if new_root:
anim_player.root_node = new_root.get_path() anim_player.root_node = new_root.get_path()
if anim_player.has_animation("animation-pack/idle"): 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: elif anim_player.get_animation_list().size() > 0:
anim_player.play(anim_player.get_animation_list()[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 # Avatar
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
func _setup_avatar_grid() -> void: func _setup_avatar_grid() -> void:
for child in avatar_grid.get_children(): # Assume .tscn has precisely instantiated Button nodes in AvatarGrid
child.queue_free() var children = avatar_grid.get_children()
for i in range(UserProfileManager.AVATARS.size()): for i in range(min(children.size(), UserProfileManager.AVATARS.size())):
var avatar_path: String = UserProfileManager.AVATARS[i] var btn = children[i] as Button
var btn := Button.new() var path: String = UserProfileManager.AVATARS[i]
btn.custom_minimum_size = Vector2(64, 64) if ResourceLoader.exists(path):
if ResourceLoader.exists(avatar_path): btn.icon = load(path) as Texture2D
var tex := load(avatar_path) as Texture2D
btn.icon = tex
btn.expand_icon = true btn.expand_icon = true
else: else:
btn.text = str(i + 1) 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)) btn.pressed.connect(_on_avatar_selected.bind(i))
avatar_grid.add_child(btn)
func _on_change_avatar_pressed() -> void: func _on_change_avatar_pressed() -> void:
avatar_popup.popup_centered() avatar_popup.popup_centered()
func _on_avatar_selected(index: int) -> void: func _on_avatar_selected(index: int) -> void:
avatar_popup.hide() avatar_popup.hide()
status_label.text = "Saving avatar..." _set_status("Saving avatar...", Color.WHITE)
var success := await UserProfileManager.update_avatar(index) var ok: bool = await UserProfileManager.update_avatar(index)
if success: if ok:
var avatar_url: String = UserProfileManager.get_avatar_url() var url: String = UserProfileManager.get_avatar_url()
if ResourceLoader.exists(avatar_url): if ResourceLoader.exists(url):
avatar_display.texture = load(avatar_url) avatar_display.texture = load(url) as Texture2D
status_label.text = "Avatar updated!" _set_status("Avatar updated!", Color(0.4, 1.0, 0.4))
else: 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: func _on_save_name_pressed() -> void:
var new_name := display_name_input.text.strip_edges() var new_name := display_name_input.text.strip_edges()
if new_name.is_empty(): if new_name.is_empty():
status_label.text = "Name cannot be empty" _set_status("Name cannot be empty.", Color.RED)
return return
status_label.text = "Saving..." _set_status("Saving...", Color.WHITE)
save_name_btn.disabled = true 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 save_name_btn.disabled = false
if success: if ok:
status_label.add_theme_color_override("font_color", Color.GREEN) _set_status("Name updated!", Color(0.4, 1.0, 0.4))
status_label.text = "Name updated!"
emit_signal("profile_updated") emit_signal("profile_updated")
await get_tree().create_timer(3.0).timeout await get_tree().create_timer(3.0).timeout
status_label.text = "" status_label.text = ""
else:
_set_status("Failed to update name.", Color.RED)
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
# Account # Account
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
func _on_link_account_pressed() -> void: func _on_link_account_pressed() -> void:
var dialog := AcceptDialog.new() link_email_input.text = ""
dialog.title = "Link Email" link_pass_input.text = ""
dialog.dialog_text = "Enter your email and password to link this guest account.\nYour progress will be preserved!" link_email_dialog.popup_centered()
var vbox := VBoxContainer.new()
var email_input := LineEdit.new() func _on_link_account_confirmed() -> void:
email_input.placeholder_text = "Email" var e := link_email_input.text.strip_edges()
var password_input := LineEdit.new() var p := link_pass_input.text
password_input.placeholder_text = "Password" if e.is_empty() or p.is_empty():
password_input.secret = true _set_status("Fill in all fields.", Color.RED); return
vbox.add_child(email_input) _set_status("Linking account...", Color.WHITE)
vbox.add_child(password_input) var ok: bool = await AuthManager.link_email(e, p)
dialog.add_child(vbox) _set_status(
add_child(dialog) "Account linked!" if ok else "Failed to link account.",
dialog.popup_centered() Color(0.4, 1.0, 0.4) if ok else Color.RED
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()
) )
if ok:
link_account_btn.visible = false
account_type_label.text = "Account: Email"
func _setup_account_settings_ui() -> void: func _setup_account_settings_ui() -> void:
# Populate Timezone dropdown
tz_dropdown.clear() tz_dropdown.clear()
for i in range(-12, 15): for i in range(-12, 15):
var prefix = "+" if i >= 0 else "" tz_dropdown.add_item("GMT %s%d" % ["+" if i >= 0 else "", i])
tz_dropdown.add_item("GMT " + prefix + str(i))
# Connect buttons
submit_cred_btn.pressed.connect(func(): submit_cred_btn.pressed.connect(func():
status_label.text = "Updating credentials..." _set_status("Updating credentials...", Color.WHITE)
var payload = { var payload := {
"current_password": old_pass_input.text, "current_password": old_pass_input.text,
"new_email": new_email_input.text, "new_email": new_email_input.text,
"new_password": new_pass_input.text "new_password": new_pass_input.text
} }
var result = await NakamaManager.client.rpc_async(NakamaManager.session, "change_credentials", JSON.stringify(payload)) var r = await NakamaManager.client.rpc_async(
if result.is_exception(): NakamaManager.session, "change_credentials", JSON.stringify(payload)
status_label.text = "Failed: " + result.get_exception().message )
if r.is_exception():
_set_status("Error: " + r.get_exception().message, Color.RED)
else: else:
status_label.text = "Credentials updated successfully!" _set_status("Credentials updated!", Color(0.4, 1.0, 0.4))
acc_settings_dialog.hide() acc_settings_dialog.hide()
) )
save_tz_btn.pressed.connect(func(): save_tz_btn.pressed.connect(func():
var selected_text = tz_dropdown.get_item_text(tz_dropdown.selected) var sel := 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) var r = await NakamaManager.client.update_account_async(
if res.is_exception(): NakamaManager.session, null, null, null, null, null, sel
status_label.text = "TZ Failed: " + res.get_exception().message )
else: _set_status(
status_label.text = "Timezone saved!" "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():
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()
) )
) )
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: func _open_account_settings() -> void:
old_pass_input.visible = not AuthManager.is_guest old_pass_input.visible = not AuthManager.is_guest
old_pass_input.text = "" old_pass_input.text = ""; new_email_input.text = ""; new_pass_input.text = ""
new_email_input.text = ""
new_pass_input.text = ""
acc_settings_dialog.popup_centered() acc_settings_dialog.popup_centered()
func _on_logout_pressed() -> void: 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") get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn")
func _on_admin_panel_pressed() -> void: func _on_admin_panel_pressed() -> void:
if has_node("/root/AdminManager"): AdminManager.toggle_admin_panel()
get_node("/root/AdminManager").toggle_admin_panel()
else:
AdminManager.toggle_admin_panel()
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
# Close / Show # Show / Close
# ------------------------------------------------------------------------- # ─────────────────────────────────────────────────────────────
func _on_close_pressed() -> void: func _on_close_pressed() -> void:
hide() hide()
emit_signal("closed") emit_signal("closed")
func show_panel() -> void: func show_panel() -> void:
if not UserProfileManager.is_profile_loaded: if not UserProfileManager.is_profile_loaded:
status_label.text = "Loading profile from server..." _set_status("Loading profile...", Color.YELLOW)
status_label.add_theme_color_override("font_color", Color.YELLOW)
_load_profile_data() _load_profile_data()
_load_loadout() _load_loadout()
_rebuild_category_items()
_populate_item_grid()
_check_admin_visibility() _check_admin_visibility()
show() show()
if AuthManager.is_guest: if AuthManager.is_guest:
_on_link_account_pressed() _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: func _check_admin_visibility() -> void:
admin_panel_btn.hide() admin_panel_btn.hide()
if not NakamaManager.client or not NakamaManager.session: if not NakamaManager.client or not NakamaManager.session: return
return
# Use native Nakama account API to check user metadata for admin role
var account = await NakamaManager.client.get_account_async(NakamaManager.session) var account = await NakamaManager.client.get_account_async(NakamaManager.session)
if account.is_exception(): if account.is_exception(): return
return var raw: String = account.user.metadata if account.user.metadata else "{}"
var meta = JSON.parse_string(raw)
# Check account metadata for role if meta is Dictionary and meta.get("role", "") in ["owner", "admin"]:
var metadata_str: String = account.user.metadata if account.user.metadata else "{}" admin_panel_btn.show()
var metadata = JSON.parse_string(metadata_str)
if metadata and metadata is Dictionary: # ─────────────────────────────────────────────────────────────
var role = metadata.get("role", "") # Helpers
if role in ["owner", "admin"]: # ─────────────────────────────────────────────────────────────
admin_panel_btn.show() 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: func _on_profile_updated() -> void:
_load_profile_data() _load_profile_data()
_rebuild_category_items()
_populate_item_grid()
func _on_profile_update_failed(error: String) -> void: func _on_profile_update_failed(error: String) -> void:
status_label.add_theme_color_override("font_color", Color.RED) _set_status(error, Color.RED)
status_label.text = error
await get_tree().create_timer(3.0).timeout await get_tree().create_timer(3.0).timeout
status_label.text = "" status_label.text = ""
+91
View File
@@ -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")
+1
View File
@@ -0,0 +1 @@
uid://w0ddjofws4ib
+46
View File
@@ -23,6 +23,9 @@ function InitModule(ctx, logger, nk, initializer) {
initializer.registerRpc("get_user_profile", rpcGetUserProfile); initializer.registerRpc("get_user_profile", rpcGetUserProfile);
initializer.registerRpc("update_user_profile", rpcUpdateUserProfile); initializer.registerRpc("update_user_profile", rpcUpdateUserProfile);
// Store RPCs
initializer.registerRpc("purchase_item", rpcPurchaseItem);
// Leaderboard RPCs // Leaderboard RPCs
initializer.registerRpc("get_leaderboard_stats", rpcGetLeaderboardStats); initializer.registerRpc("get_leaderboard_stats", rpcGetLeaderboardStats);
initializer.registerRpc("admin_update_stats", rpcAdminUpdateStats); 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 // User Profile RPCs
// ============================================================================= // =============================================================================