feat: 2.3.1

This commit is contained in:
2026-05-12 17:55:53 +08:00
parent 13f3c3d591
commit 7ca11c6534
28 changed files with 1328 additions and 578 deletions
+15
View File
@@ -1,3 +1,18 @@
## [2.3.2] — 2026-05-12
- Integrated Mailbox UI into lobby with CanvasLayer overlay (renders above 3D viewport).
- Redesigned `mailbox_panel.tscn` to 3-column layout: scrollable mail list | content area | reward slots.
- Auto read-all and auto claim-all triggered on mailbox open — no "NEW" tags remain after viewing.
- Fixed `MailboxBtn` text glitch — removed text override, replaced with `MailBadge` Label overlay in top-right corner of button.
- Fixed `mark_as_read()` in `MailManager` — now persists `read_ids` to server via new `save_mail_state` RPC.
- Admin Panel: Announcement tab `Target User` field now accepts username, display name, or user_id with **Find** button for resolution.
- Admin Panel: New **Mail Manager** tab listing all global and personal sent mails with Type, Title, Sender, Start, Expires, Status columns.
- Admin Panel: Edit dialog for mails uses `date_picker.tscn` for end date and supports reassigning recipient (username lookup + move).
- Admin Panel: **End Now** button sets `end_date` to current timestamp; **Delete** permanently removes mail from server storage.
- Server: Added `admin_list_mail` RPC — paginates all `inbox/personal` storage objects across all users via `storageList`.
- Server: Added `admin_update_mail` RPC — extract-then-reinsert pattern supports field edits and cross-user mail movement.
- Server: Added `admin_delete_mail_server` RPC — hard-removes mail from global config or personal inbox storage.
- Server: Added `save_mail_state` RPC — merges client `read_ids` into server `inbox/state` without clobbering claimed/deleted IDs.
## [2.3.1] — 2026-05-11
- Integrated DM tab system directly into the lobby chatbox — DMs now open as closeable tabs inside ChatPanel instead of a fullscreen overlay.
- Removed static DMTabBtn; DM tabs are dynamically created per friend using a scene-local DMTabTemplate with (X) close button.
+21 -1
View File
@@ -1,7 +1,27 @@
{
"latest_version": "2.3.1",
"latest_version": "2.3.2",
"minimum_app_version": "2.1.0",
"releases": [
{
"version": "2.3.2",
"date": "2026-05-12",
"pck_url": "https://raw.githubusercontent.com/adtpdn/tekton-updates/main/latest/patch.pck",
"pck_size": 0,
"changelog": [
"Integrated Mailbox UI into lobby with CanvasLayer overlay",
"Redesigned mailbox_panel.tscn to 3-column layout (mail list, content, rewards)",
"Added auto read-all and auto claim-all on mailbox open",
"Fixed MailboxBtn text glitch — replaced text override with MailBadge Label overlay",
"Admin Panel: Target user field now accepts username or user_id with Find button",
"Admin Panel: New Mail Manager tab to view, edit, end, and delete sent announcements",
"Admin Panel: Edit dialog uses DatePicker for end date and supports recipient reassignment",
"Server: Added admin_list_mail, admin_update_mail, admin_delete_mail_server RPCs",
"Server: admin_list_mail scans all users personal inboxes via storageList",
"Server: admin_update_mail supports moving mail between users and global",
"Server: Added save_mail_state RPC to persist read_ids to server",
"Fixed mark_as_read — now persists to Nakama inbox state instead of local-only pass"
]
},
{
"version": "2.3.1",
"date": "2026-05-11",
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://mw0hjqg4u58a"
path="res://.godot/imported/btn_back.png-766f18f6c608c9d18d187505f5ac889b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/btn_back.png"
dest_files=["res://.godot/imported/btn_back.png-766f18f6c608c9d18d187505f5ac889b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://byvqbe1m2dr41"
path="res://.godot/imported/btn_delete.png-cf3f17cefdd7f374d94d8379a1c9f478.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/btn_delete.png"
dest_files=["res://.godot/imported/btn_delete.png-cf3f17cefdd7f374d94d8379a1c9f478.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cs5lke40qve7y"
path="res://.godot/imported/mailbox_icon.png-c85e0603097425f2befc209c5030293f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/mailbox_icon.png"
dest_files=["res://.godot/imported/mailbox_icon.png-c85e0603097425f2befc209c5030293f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bc14cddtp0gvq"
path="res://.godot/imported/mailbox_node_01.png-a4c2ade2737ef5ed9de0d64d83793bb5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/mailbox_node_01.png"
dest_files=["res://.godot/imported/mailbox_node_01.png-a4c2ade2737ef5ed9de0d64d83793bb5.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cosjvp4msuv34"
path="res://.godot/imported/mailbox_node_02.png-c71d25c932b515f6569f65ba769a206c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/mailbox_node_02.png"
dest_files=["res://.godot/imported/mailbox_node_02.png-c71d25c932b515f6569f65ba769a206c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://exkx1hado3wi"
path="res://.godot/imported/read_all.png-3faf33804509dc0f18651b7ee0cbaf4b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gui/mailbox/read_all.png"
dest_files=["res://.godot/imported/read_all.png-3faf33804509dc0f18651b7ee0cbaf4b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
-286
View File
@@ -1,286 +0,0 @@
[gd_scene format=3 uid="uid://xpalyawdgnyl"]
[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"]
[node name="Lobby" type="Control" unique_id=1681755368]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_theme")
script = ExtResource("1_lp6xi")
[node name="Background" type="ColorRect" parent="." unique_id=610186638]
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.1, 0.08, 1)
[node name="MainMenuPanel" type="PanelContainer" parent="." unique_id=749972967]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -240.0
offset_right = 220.0
offset_bottom = 240.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MainMenuPanel" unique_id=172832102]
layout_mode = 2
theme_override_constants/separation = 18
[node name="TitleContainer" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=1254704944]
layout_mode = 2
theme_override_constants/separation = 4
[node name="Title" type="Label" parent="MainMenuPanel/VBoxContainer/TitleContainer" unique_id=1313646849]
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 44
text = "TEKTON DASH"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Subtitle" type="Label" parent="MainMenuPanel/VBoxContainer/TitleContainer" unique_id=981494139]
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 12
text = "ARMAGEDDON VERSION"
horizontal_alignment = 1
[node name="Separator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=1861761683]
layout_mode = 2
[node name="InputSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=113453689]
layout_mode = 2
theme_override_constants/separation = 10
[node name="PlayerNameLabel" type="Label" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=2007198770]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 13
text = "YOUR NAME"
[node name="PlayerNameInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=543986981]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
text = "Player"
placeholder_text = "Enter your name..."
[node name="ButtonSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=357245135]
layout_mode = 2
theme_override_constants/separation = 12
[node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1297504653]
custom_minimum_size = Vector2(0, 48)
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "CREATE ROOM"
[node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1825906901]
custom_minimum_size = Vector2(0, 48)
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "BROWSE ROOMS"
[node name="RoomListPanel" type="PanelContainer" parent="." unique_id=1969054574]
visible = false
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 = -260.0
offset_right = 300.0
offset_bottom = 260.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="RoomListPanel" unique_id=726092140]
layout_mode = 2
theme_override_constants/separation = 14
[node name="Header" type="Label" parent="RoomListPanel/VBoxContainer" unique_id=1272860526]
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 28
text = "SERVER BROWSER"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="RoomListPanel/VBoxContainer" unique_id=38843595]
layout_mode = 2
[node name="MatchIdLabel" type="Label" parent="RoomListPanel/VBoxContainer" unique_id=316758674]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 12
text = "DIRECT CONNECT (MATCH ID)"
[node name="MatchIdInput" type="LineEdit" parent="RoomListPanel/VBoxContainer" unique_id=159753310]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
placeholder_text = "Paste match ID here..."
[node name="RoomListLabel" type="Label" parent="RoomListPanel/VBoxContainer" unique_id=867057060]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 12
text = "AVAILABLE SERVERS"
[node name="RoomList" type="ItemList" parent="RoomListPanel/VBoxContainer" unique_id=203590990]
custom_minimum_size = Vector2(0, 200)
layout_mode = 2
allow_reselect = true
[node name="ButtonContainer" type="HBoxContainer" parent="RoomListPanel/VBoxContainer" unique_id=178401080]
layout_mode = 2
theme_override_constants/separation = 14
alignment = 1
[node name="RefreshBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=1068680772]
custom_minimum_size = Vector2(110, 44)
layout_mode = 2
text = "REFRESH"
[node name="JoinBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=822083530]
custom_minimum_size = Vector2(130, 44)
layout_mode = 2
text = "JOIN SERVER"
[node name="BackBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer" unique_id=1873303918]
custom_minimum_size = Vector2(110, 44)
layout_mode = 2
text = "BACK"
[node name="LobbyPanel" type="PanelContainer" parent="." unique_id=1955208668]
visible = false
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 = -260.0
offset_right = 300.0
offset_bottom = 260.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="LobbyPanel" unique_id=1443407103]
layout_mode = 2
theme_override_constants/separation = 14
[node name="RoomNameHeader" type="Label" parent="LobbyPanel/VBoxContainer" unique_id=2019479226]
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 26
text = "ROOM: "
horizontal_alignment = 1
[node name="MatchIdContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer" unique_id=280307543]
layout_mode = 2
alignment = 1
[node name="MatchIdDisplay" type="Label" parent="LobbyPanel/VBoxContainer/MatchIdContainer" unique_id=1883524532]
layout_mode = 2
theme_override_colors/font_color = Color(0.5, 0.5, 0.55, 1)
theme_override_font_sizes/font_size = 10
text = "Match ID: "
[node name="CopyIdBtn" type="Button" parent="LobbyPanel/VBoxContainer/MatchIdContainer" unique_id=1770583860]
custom_minimum_size = Vector2(80, 32)
layout_mode = 2
theme_override_font_sizes/font_size = 10
text = "COPY"
[node name="HSeparator" type="HSeparator" parent="LobbyPanel/VBoxContainer" unique_id=346787128]
layout_mode = 2
[node name="PlayersLabel" type="Label" parent="LobbyPanel/VBoxContainer" unique_id=1266635921]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 13
text = "PLAYERS"
[node name="PlayerList" type="ItemList" parent="LobbyPanel/VBoxContainer" unique_id=1481128408]
custom_minimum_size = Vector2(0, 160)
layout_mode = 2
allow_reselect = true
[node name="StatusLabel" type="Label" parent="LobbyPanel/VBoxContainer" unique_id=1750308910]
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Waiting for players..."
horizontal_alignment = 1
[node name="ButtonContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer" unique_id=941148439]
layout_mode = 2
theme_override_constants/separation = 14
alignment = 1
[node name="ReadyBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer" unique_id=386730307]
custom_minimum_size = Vector2(110, 48)
layout_mode = 2
toggle_mode = true
text = "READY"
[node name="StartGameBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer" unique_id=1510805318]
custom_minimum_size = Vector2(140, 48)
layout_mode = 2
disabled = true
text = "START GAME"
[node name="LeaveBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer" unique_id=1773320572]
custom_minimum_size = Vector2(110, 48)
layout_mode = 2
text = "LEAVE"
[node name="StatusBar" type="PanelContainer" parent="." unique_id=547557114]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = -72.0
offset_right = -24.0
offset_bottom = -24.0
grow_horizontal = 2
grow_vertical = 0
[node name="ConnectionStatus" type="Label" parent="StatusBar" unique_id=1282790307]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 12
text = "NOT CONNECTED"
horizontal_alignment = 1
[node name="VersionLabel" type="Label" parent="." unique_id=1619858099]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -130.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = 40.0
grow_horizontal = 0
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 0.6)
theme_override_font_sizes/font_size = 11
text = "v0.1.0 ALPHA"
horizontal_alignment = 2
+4 -4
View File
@@ -42,8 +42,8 @@ application/modify_resources=false
application/icon=""
application/console_wrapper_icon=""
application/icon_interpolation=4
application/file_version="2.3"
application/product_version="2.3"
application/file_version="2.3.1"
application/product_version="2.3.1"
application/company_name="DanchieGo"
application/product_name="Tekton Armageddon"
application/file_description=""
@@ -565,8 +565,8 @@ codesign/digest_algorithm=1
codesign/identity_type=0
application/modify_resources=false
application/console_wrapper_icon=""
application/file_version="2.3"
application/product_version="2.3"
application/file_version="2.3.1"
application/product_version="2.3.1"
application/company_name="DanchieGo"
application/product_name="Tekton Armageddon"
application/file_description=""
+1 -1
View File
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application]
config/name="Tekton Dash Armageddon"
config/version="2.3.1"
config/version="2.3.2"
run/main_scene="res://scenes/ui/boot_screen.tscn"
config/features=PackedStringArray("4.6", "Forward Plus")
boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1)
+27 -17
View File
@@ -30,6 +30,7 @@ extends Control
@onready var shop_btn = %CartBtn
@onready var top_right_profile_btn = %ProfileBtn
@onready var mailbox_btn = get_node_or_null("%MailboxBtn")
@onready var mail_badge = get_node_or_null("%MailBadge")
@onready var banner1_btn = get_node_or_null("%Banner1")
@onready var ticket_btn = get_node_or_null("%TicketBtn")
@onready var mailbox_panel = get_node_or_null("MailboxPanel")
@@ -869,28 +870,37 @@ func _on_profile_btn_pressed() -> void:
main_menu_panel.hide()
profile_panel_instance.show_panel()
func _on_mailbox_pressed() -> void:
if mailbox_panel:
mailbox_panel.show_panel()
if main_menu_panel:
main_menu_panel.hide()
# Connect the closed signal to reshow main menu if not connected
if not mailbox_panel.closed.is_connected(_on_mailbox_closed):
mailbox_panel.closed.connect(_on_mailbox_closed)
var _mailbox_panel_instance: Control
func _on_mailbox_closed() -> void:
if main_menu_panel:
main_menu_panel.show()
func _on_mailbox_pressed() -> void:
if not _mailbox_panel_instance:
var scene = load("res://scenes/ui/mailbox_panel.tscn")
if scene:
_mailbox_panel_instance = scene.instantiate()
_mailbox_panel_instance.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var cl := CanvasLayer.new()
cl.layer = 100
cl.name = "MailboxCanvasLayer"
add_child(cl)
cl.add_child(_mailbox_panel_instance)
if _mailbox_panel_instance.has_signal("closed"):
_mailbox_panel_instance.closed.connect(func():
_mailbox_panel_instance.get_parent().queue_free()
_mailbox_panel_instance = null
)
if _mailbox_panel_instance:
_mailbox_panel_instance.show_panel()
func _on_mail_unread_count_changed(count: int) -> void:
if mailbox_btn:
if mail_badge:
if count > 0:
mailbox_btn.text = "MAIL (%d)" % count
mailbox_btn.add_theme_color_override("font_color", Color.YELLOW)
mail_badge.text = str(count) if count < 100 else "99+"
mail_badge.visible = true
else:
mailbox_btn.text = "MAIL"
mailbox_btn.remove_theme_color_override("font_color")
mail_badge.visible = false
func _on_logout_pressed() -> void:
AuthManager.logout()
+48 -70
View File
@@ -31,6 +31,7 @@
[ext_resource type="Texture2D" uid="uid://dv782w5t0xlcc" path="res://assets/graphics/gui/lobby/friends.png" id="22_1x1aw"]
[ext_resource type="Texture2D" uid="uid://cpy5lppf3ro02" path="res://assets/graphics/gui/play/selection_play1.png" id="22_kn4i6"]
[ext_resource type="Texture2D" uid="uid://b0ovmvcm8rt2n" path="res://assets/graphics/gui/play/selection_room0.png" id="23_3jc85"]
[ext_resource type="Texture2D" uid="uid://cs5lke40qve7y" path="res://assets/graphics/gui/mailbox/mailbox_icon.png" id="23_835bk"]
[ext_resource type="Texture2D" uid="uid://bqcxrfu2jlplr" path="res://assets/graphics/gui/lobby/settings.png" id="23_twy5w"]
[ext_resource type="Texture2D" uid="uid://3p0sabd1og31" path="res://assets/graphics/gui/play/selection_room1.png" id="24_jhtcy"]
[ext_resource type="Texture2D" uid="uid://bpco6lch7homj" path="res://assets/graphics/gui/play/bg.png" id="25_iwv4c"]
@@ -113,6 +114,12 @@ bg_color = Color(0.6, 0.6, 0.6, 0)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dfnwm"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_835bk"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ucbax"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1x1aw"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_50md7"]
texture = ExtResource("25_iwv4c")
@@ -480,73 +487,6 @@ layout_mode = 2
custom_minimum_size = Vector2(0, 8)
layout_mode = 2
[node name="CurrencyRow" type="VBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol" unique_id=59384589]
visible = false
layout_mode = 2
theme_override_constants/separation = 8
[node name="HBoxContainer" type="HBoxContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow" unique_id=1807032398]
layout_mode = 2
size_flags_vertical = 3
[node name="StarPanel" type="PanelContainer" parent="MainMenuPanel/MainMargin/MainHBox/LeftCol/CurrencyRow/HBoxContainer" unique_id=509438]
layout_mode = 2
size_flags_horizontal = 3
theme = ExtResource("2_theme")
[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="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="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
@@ -764,6 +704,44 @@ grow_vertical = 2
texture = ExtResource("21_ucbax")
expand_mode = 2
[node name="MailboxBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=64042311]
unique_name_in_owner = true
custom_minimum_size = Vector2(61, 61)
layout_mode = 2
theme_override_fonts/font = ExtResource("5_pc087")
theme_override_styles/normal = SubResource("StyleBoxEmpty_835bk")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ucbax")
theme_override_styles/hover = SubResource("StyleBoxEmpty_1x1aw")
[node name="TextureRect" type="TextureRect" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel/MailboxBtn" unique_id=109558991]
custom_minimum_size = Vector2(32, 32)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("23_835bk")
expand_mode = 3
[node name="MailBadge" type="Label" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel/MailboxBtn"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -18.0
offset_top = -2.0
offset_right = 2.0
offset_bottom = 16.0
grow_horizontal = 0
grow_vertical = 1
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 10
horizontal_alignment = 1
vertical_alignment = 1
text = ""
[node name="SocialBtn" type="Button" parent="MainMenuPanel/MainMargin/MainHBox/RightCol/TopRightPanel" unique_id=82719328]
unique_name_in_owner = true
custom_minimum_size = Vector2(61, 61)
@@ -824,7 +802,7 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 120)
layout_mode = 2
size_flags_horizontal = 3
theme = ExtResource("2_theme")
theme = ExtResource("14_2630d")
theme_override_fonts/font = ExtResource("5_pc087")
theme_override_font_sizes/font_size = 30
disabled = true
@@ -845,7 +823,7 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 120)
layout_mode = 2
size_flags_horizontal = 3
theme = ExtResource("2_theme")
theme = ExtResource("14_2630d")
theme_override_fonts/font = ExtResource("5_pc087")
theme_override_font_sizes/font_size = 30
disabled = true
@@ -866,7 +844,7 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 120)
layout_mode = 2
size_flags_horizontal = 3
theme = ExtResource("2_theme")
theme = ExtResource("14_2630d")
theme_override_fonts/font = ExtResource("5_pc087")
theme_override_font_sizes/font_size = 30
disabled = true
+63 -3
View File
@@ -271,17 +271,29 @@ metadata/_tab_index = 3
[node name="TargetHBox" type="HBoxContainer" parent="Margin/VBox/Tabs/Announcements"]
layout_mode = 2
theme_override_constants/separation = 12
theme_override_constants/separation = 8
[node name="Label" type="Label" parent="Margin/VBox/Tabs/Announcements/TargetHBox"]
layout_mode = 2
text = "Target User ID:"
text = "Target:"
[node name="TargetUserEdit" type="LineEdit" parent="Margin/VBox/Tabs/Announcements/TargetHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Leave empty to send to ALL users (Global)"
placeholder_text = "Username or User ID (empty = ALL)"
[node name="FindUserBtn" type="Button" parent="Margin/VBox/Tabs/Announcements/TargetHBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
text = "Find"
[node name="ResolvedIdLabel" type="Label" parent="Margin/VBox/Tabs/Announcements/TargetHBox"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.4, 0.9, 0.4, 1)
text = ""
[node name="TitleEdit" type="LineEdit" parent="Margin/VBox/Tabs/Announcements"]
unique_name_in_owner = true
@@ -376,6 +388,54 @@ custom_minimum_size = Vector2(0, 40)
layout_mode = 2
text = "SEND ANNOUNCEMENT"
[node name="Mail Manager" type="VBoxContainer" parent="Margin/VBox/Tabs"]
visible = false
layout_mode = 2
theme_override_constants/separation = 8
metadata/_tab_index = 4
[node name="MailTree" type="Tree" parent="Margin/VBox/Tabs/Mail Manager"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
columns = 6
column_titles_visible = true
allow_reselect = true
hide_root = true
select_mode = 1
[node name="MailActionBar" type="HBoxContainer" parent="Margin/VBox/Tabs/Mail Manager"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="RefreshMailBtn" type="Button" parent="Margin/VBox/Tabs/Mail Manager/MailActionBar"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 36)
layout_mode = 2
text = "Refresh"
[node name="Spacer" type="Control" parent="Margin/VBox/Tabs/Mail Manager/MailActionBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="EditMailBtn" type="Button" parent="Margin/VBox/Tabs/Mail Manager/MailActionBar"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 36)
layout_mode = 2
text = "Edit"
[node name="EndMailBtn" type="Button" parent="Margin/VBox/Tabs/Mail Manager/MailActionBar"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 36)
layout_mode = 2
text = "End Now"
[node name="DeleteMailServerBtn" type="Button" parent="Margin/VBox/Tabs/Mail Manager/MailActionBar"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 36)
layout_mode = 2
text = "Delete"
[node name="StatusLabel" type="Label" parent="Margin/VBox"]
unique_name_in_owner = true
layout_mode = 2
+196 -88
View File
@@ -1,6 +1,39 @@
[gd_scene format=3 uid="uid://cb5cbbxyxx"]
[gd_scene format=3 uid="uid://5e1bfpagcpps"]
[ext_resource type="Script" uid="uid://df7xxyyzz" path="res://scripts/ui/mailbox_panel.gd" id="1_a"]
[ext_resource type="Script" uid="uid://b5fema68m6b2s" path="res://scripts/ui/mailbox_panel.gd" id="1_a"]
[ext_resource type="Theme" uid="uid://cxab3xxy00" path="res://assets/themes/GUI_Tekton.tres" id="1_wi8mn"]
[ext_resource type="Texture2D" uid="uid://dfmailbox" path="res://assets/graphics/gui/mainmenu/mailbox.png" id="tex_mailbox"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_middle"]
bg_color = Color(0.02, 0.04, 0.08, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0, 0, 0, 1)
corner_radius_top_left = 12
corner_radius_top_right = 12
corner_radius_bottom_right = 12
corner_radius_bottom_left = 12
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_slot"]
bg_color = Color(0.1, 0.3, 0.6, 0.6)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mailbtn"]
bg_color = Color(0.2, 0.4, 0.6, 1)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
[node name="MailboxPanel" type="Panel"]
anchors_preset = 15
@@ -8,6 +41,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_wi8mn")
script = ExtResource("1_a")
[node name="BG" type="ColorRect" parent="."]
@@ -17,7 +51,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.08, 0.15, 0.22, 1)
color = Color(0.12, 0.4, 0.9, 1)
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
@@ -26,45 +60,51 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 24
theme_override_constants/margin_top = 24
theme_override_constants/margin_right = 24
theme_override_constants/margin_bottom = 24
theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40
[node name="HBox" type="HBoxContainer" parent="Margin"]
[node name="VBox" type="VBoxContainer" parent="Margin"]
layout_mode = 2
theme_override_constants/separation = 24
theme_override_constants/separation = 20
[node name="LeftCol" type="VBoxContainer" parent="Margin/HBox"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_constants/separation = 12
[node name="HeaderHBox" type="HBoxContainer" parent="Margin/HBox/LeftCol"]
[node name="HeaderHBox" type="HBoxContainer" parent="Margin/VBox"]
layout_mode = 2
[node name="Icon" type="TextureRect" parent="Margin/HBox/LeftCol/HeaderHBox"]
custom_minimum_size = Vector2(32, 32)
[node name="Icon" type="TextureRect" parent="Margin/VBox/HeaderHBox"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
texture = ExtResource("tex_mailbox")
expand_mode = 1
stretch_mode = 5
[node name="Label" type="Label" parent="Margin/HBox/LeftCol/HeaderHBox"]
[node name="Label" type="Label" parent="Margin/VBox/HeaderHBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 42
text = "MAILBOX"
theme_override_font_sizes/font_size = 24
[node name="Scroll" type="ScrollContainer" parent="Margin/HBox/LeftCol"]
[node name="HBox" type="HBoxContainer" parent="Margin/VBox"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 20
[node name="LeftCol" type="VBoxContainer" parent="Margin/VBox/HBox"]
custom_minimum_size = Vector2(350, 0)
layout_mode = 2
theme_override_constants/separation = 12
[node name="Scroll" type="ScrollContainer" parent="Margin/VBox/HBox/LeftCol"]
layout_mode = 2
size_flags_vertical = 3
[node name="MailListVBox" type="VBoxContainer" parent="Margin/HBox/LeftCol/Scroll"]
[node name="MailListVBox" type="VBoxContainer" parent="Margin/VBox/HBox/LeftCol/Scroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
theme_override_constants/separation = 12
[node name="EmptyStateLbl" type="Label" parent="Margin/HBox/LeftCol/Scroll"]
[node name="EmptyStateLbl" type="Label" parent="Margin/VBox/HBox/LeftCol/Scroll"]
unique_name_in_owner = true
visible = false
layout_mode = 2
@@ -73,102 +113,170 @@ size_flags_vertical = 6
text = "No mails found."
horizontal_alignment = 1
[node name="CloseBtn" type="Button" parent="Margin/HBox/LeftCol"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 40)
layout_mode = 2
size_flags_horizontal = 0
text = "BACK"
[node name="MailBtnTemplate" type="Button" parent="Margin/HBox/LeftCol"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
toggle_mode = true
[node name="VBox" type="VBoxContainer" parent="Margin/HBox/LeftCol/MailBtnTemplate"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_right = 8
[node name="Title" type="Label" parent="Margin/HBox/LeftCol/MailBtnTemplate/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "No Title"
[node name="HBox" type="HBoxContainer" parent="Margin/HBox/LeftCol/MailBtnTemplate/VBox"]
layout_mode = 2
[node name="DateLbl" type="Label" parent="Margin/HBox/LeftCol/MailBtnTemplate/VBox/HBox"]
layout_mode = 2
theme_override_colors/font_color = Color(0.745098, 0.745098, 0.745098, 1)
theme_override_font_sizes/font_size = 12
[node name="Spacer" type="Control" parent="Margin/HBox/LeftCol/MailBtnTemplate/VBox/HBox"]
[node name="MiddleCol" type="PanelContainer" parent="Margin/VBox/HBox"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_middle")
[node name="StatusLbl" type="Label" parent="Margin/HBox/LeftCol/MailBtnTemplate/VBox/HBox"]
[node name="Margin" type="MarginContainer" parent="Margin/VBox/HBox/MiddleCol"]
layout_mode = 2
theme_override_font_sizes/font_size = 12
theme_override_constants/margin_left = 24
theme_override_constants/margin_top = 24
theme_override_constants/margin_right = 24
theme_override_constants/margin_bottom = 24
[node name="RightCol" type="VBoxContainer" parent="Margin/HBox"]
[node name="VBox" type="VBoxContainer" parent="Margin/VBox/HBox/MiddleCol/Margin"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 16
[node name="MailTitleLbl" type="Label" parent="Margin/HBox/RightCol"]
[node name="MailTitleLbl" type="Label" parent="Margin/VBox/HBox/MiddleCol/Margin/VBox"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 28
text = "WELCOME TO TEKTONIA"
theme_override_font_sizes/font_size = 32
text = "Message Title"
[node name="Sep" type="HSeparator" parent="Margin/HBox/RightCol"]
[node name="Sep" type="HSeparator" parent="Margin/VBox/HBox/MiddleCol/Margin/VBox"]
layout_mode = 2
[node name="MailContentText" type="RichTextLabel" parent="Margin/HBox/RightCol"]
[node name="MailContentText" type="RichTextLabel" parent="Margin/VBox/HBox/MiddleCol/Margin/VBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
theme_override_font_sizes/normal_font_size = 18
text = "Lorem ipsum..."
[node name="FooterHBox" type="HBoxContainer" parent="Margin/HBox/RightCol"]
[node name="RightCol" type="VBoxContainer" parent="Margin/VBox/HBox"]
custom_minimum_size = Vector2(250, 0)
layout_mode = 2
theme_override_constants/separation = 16
[node name="DynamicRewardsContainer" type="VBoxContainer" parent="Margin/VBox/HBox/RightCol"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 16
[node name="FooterHBox" type="HBoxContainer" parent="Margin/VBox"]
layout_mode = 2
[node name="SenderLbl" type="Label" parent="Margin/HBox/RightCol/FooterHBox"]
[node name="CloseBtn" type="Button" parent="Margin/VBox/FooterHBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 50)
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
text = "< BACK"
[node name="ReadAllBtn" type="Button" parent="Margin/VBox/FooterHBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(160, 50)
layout_mode = 2
text = "READ ALL"
[node name="Spacer" type="Control" parent="Margin/VBox/FooterHBox"]
layout_mode = 2
size_flags_horizontal = 3
text = "SENDER:
TEKTON DEV TEAM"
[node name="DynamicRewardsContainer" type="HBoxContainer" parent="Margin/HBox/RightCol/FooterHBox"]
[node name="ActionBtn" type="Button" parent="Margin/VBox/FooterHBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(180, 50)
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
text = "DELETE"
[node name="Templates" type="Control" parent="."]
visible = false
layout_mode = 1
anchors_preset = 0
[node name="MailBtnTemplate" type="Button" parent="Templates"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 100)
layout_mode = 0
toggle_mode = true
theme_override_styles/normal = SubResource("StyleBoxFlat_mailbtn")
[node name="Margin" type="MarginContainer" parent="Templates/MailBtnTemplate"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 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="Templates/MailBtnTemplate/Margin"]
layout_mode = 2
[node name="Title" type="Label" parent="Templates/MailBtnTemplate/Margin/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 22
text = "Message Title"
text_overrun_behavior = 3
[node name="Spacer" type="Control" parent="Templates/MailBtnTemplate/Margin/VBox"]
layout_mode = 2
size_flags_vertical = 3
[node name="HBox" type="HBoxContainer" parent="Templates/MailBtnTemplate/Margin/VBox"]
layout_mode = 2
[node name="DateLbl" type="Label" parent="Templates/MailBtnTemplate/Margin/VBox/HBox"]
layout_mode = 2
theme_override_colors/font_color = Color(0.9, 0.9, 0.9, 1)
theme_override_font_sizes/font_size = 14
[node name="Spacer" type="Control" parent="Templates/MailBtnTemplate/Margin/VBox/HBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="StatusLbl" type="Label" parent="Templates/MailBtnTemplate/Margin/VBox/HBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
[node name="RewardHBoxTemplate" type="PanelContainer" parent="Templates"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 80)
layout_mode = 0
theme_override_styles/panel = SubResource("StyleBoxFlat_slot")
[node name="Margin" type="MarginContainer" parent="Templates/RewardHBoxTemplate"]
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="Templates/RewardHBoxTemplate/Margin"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="RewardHBoxTemplate" type="HBoxContainer" parent="Margin/HBox/RightCol/FooterHBox"]
unique_name_in_owner = true
visible = false
[node name="IconBg" type="ColorRect" parent="Templates/RewardHBoxTemplate/Margin/HBox"]
custom_minimum_size = Vector2(56, 56)
layout_mode = 2
color = Color(0, 0, 0, 1)
[node name="Icon" type="TextureRect" parent="Margin/HBox/RightCol/FooterHBox/RewardHBoxTemplate"]
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
[node name="Icon" type="TextureRect" parent="Templates/RewardHBoxTemplate/Margin/HBox/IconBg"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
stretch_mode = 5
[node name="AmountLbl" type="Label" parent="Margin/HBox/RightCol/FooterHBox/RewardHBoxTemplate"]
[node name="VBox" type="VBoxContainer" parent="Templates/RewardHBoxTemplate/Margin/HBox"]
layout_mode = 2
text = "100"
alignment = 1
[node name="ActionBtn" type="Button" parent="Margin/HBox/RightCol/FooterHBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 48)
[node name="TypeLbl" type="Label" parent="Templates/RewardHBoxTemplate/Margin/HBox/VBox"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 0.2, 0.2, 1)
text = "DELETE"
theme_override_font_sizes/font_size = 12
text = "ITEM NAME"
[node name="AmountLbl" type="Label" parent="Templates/RewardHBoxTemplate/Margin/HBox/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "x00000"
+7 -74
View File
@@ -1,53 +1,16 @@
[gd_scene format=3 uid="uid://b1two2tvv5prx"]
[ext_resource type="Script" uid="uid://cdege6m8u5cp" path="res://scripts/ui/settings_menu.gd" id="1_script"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bg"]
bg_color = Color(0.05, 0.05, 0.08, 0.85)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.2, 0.8, 1, 0.5)
corner_radius_top_left = 20
corner_radius_top_right = 20
corner_radius_bottom_right = 20
corner_radius_bottom_left = 20
shadow_color = Color(0, 0, 0, 0.5)
shadow_size = 20
[ext_resource type="Theme" uid="uid://cxab3xxy00" path="res://assets/themes/GUI_Tekton.tres" id="2_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_title"]
font_size = 32
font_color = Color(0.2, 0.9, 1, 1)
outline_size = 8
outline_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tab_bg"]
content_margin_left = 20.0
content_margin_top = 20.0
content_margin_right = 20.0
content_margin_bottom = 20.0
bg_color = Color(0.1, 0.1, 0.15, 0.6)
corner_radius_top_left = 10
corner_radius_top_right = 10
corner_radius_bottom_right = 10
corner_radius_bottom_left = 10
[sub_resource type="LabelSettings" id="LabelSettings_heading"]
font_size = 24
font_color = Color(0.9, 0.9, 0.9, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_section_header"]
content_margin_left = 15.0
content_margin_top = 5.0
content_margin_right = 15.0
content_margin_bottom = 5.0
bg_color = Color(0.2, 0.5, 0.8, 0.3)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="LabelSettings" id="LabelSettings_section"]
font_size = 18
font_color = Color(0.4, 0.9, 1, 1)
@@ -79,47 +42,41 @@ offset_right = 400.0
offset_bottom = 300.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_bg")
theme = ExtResource("2_theme")
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer" unique_id=1608766484]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Header" type="HBoxContainer" parent="PanelContainer/VBoxContainer" unique_id=1476625293]
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
theme_override_constants/separation = 20
[node name="PanelContainer" type="Control" parent="PanelContainer/VBoxContainer/Header" unique_id=1523545144]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer/Header" unique_id=22122987]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 30
[node name="Title" type="Label" parent="PanelContainer/VBoxContainer/Header/MarginContainer" unique_id=1831070364]
layout_mode = 2
text = "SYSTEM SETTINGS"
text = "SETTINGS"
label_settings = SubResource("LabelSettings_title")
[node name="CloseButton" type="Button" parent="PanelContainer/VBoxContainer/Header" unique_id=1345773906]
custom_minimum_size = Vector2(60, 60)
layout_mode = 2
size_flags_vertical = 4
theme_override_font_sizes/font_size = 32
text = "X"
flat = true
[node name="ContentSection" type="MarginContainer" parent="PanelContainer/VBoxContainer" unique_id=1702280876]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 30
[node name="TabContainer" type="TabContainer" parent="PanelContainer/VBoxContainer/ContentSection" unique_id=1795678842]
layout_mode = 2
theme_override_constants/side_margin = 20
theme_override_styles/panel = SubResource("StyleBoxFlat_tab_bg")
current_tab = 0
[node name="Video" type="ScrollContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer" unique_id=964480166]
@@ -129,7 +86,6 @@ metadata/_tab_index = 0
[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video" unique_id=1271648419]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 15
[node name="Fullscreen" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox" unique_id=683482011]
layout_mode = 2
@@ -216,7 +172,6 @@ layout_mode = 2
[node name="Audio" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer" unique_id=1585166620]
visible = false
layout_mode = 2
theme_override_constants/separation = 20
metadata/_tab_index = 1
[node name="Master" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio" unique_id=2083739941]
@@ -272,7 +227,6 @@ metadata/_tab_index = 2
[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls" unique_id=1592525164]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 15
[node name="ControllerToggleContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000001]
layout_mode = 2
@@ -289,7 +243,6 @@ layout_mode = 2
[node name="MovementSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000004]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/MovementSection" unique_id=100000005]
layout_mode = 2
@@ -298,8 +251,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="MoveGrid" type="GridContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000006]
layout_mode = 2
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 3
[node name="ColH1" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/MoveGrid" unique_id=393496035]
@@ -391,7 +342,6 @@ text = "Right"
[node name="PowerUpSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000015]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/PowerUpSection" unique_id=100000016]
layout_mode = 2
@@ -400,8 +350,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="PowerUpGrid" type="GridContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000017]
layout_mode = 2
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 3
[node name="UsePowerupLabel" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/PowerUpGrid" unique_id=100000118]
@@ -424,7 +372,6 @@ text = "N/A"
[node name="PowerBarSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000026]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/PowerBarSection" unique_id=100000027]
layout_mode = 2
@@ -433,8 +380,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="PowerBarGrid" type="GridContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000028]
layout_mode = 2
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 3
[node name="AttackLabel" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/PowerBarGrid" unique_id=100000029]
@@ -475,7 +420,6 @@ text = "N/A"
[node name="OtherSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000033]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/OtherSection" unique_id=100000034]
layout_mode = 2
@@ -484,8 +428,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="OtherGrid" type="GridContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=100000035]
layout_mode = 2
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 3
[node name="GrabLabel" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/OtherGrid" unique_id=100000036]
@@ -514,11 +456,9 @@ metadata/_tab_index = 3
[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controller" unique_id=300000002]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 15
[node name="CtrlInfoSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controller/VBox" unique_id=300000003]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controller/VBox/CtrlInfoSection" unique_id=300000004]
layout_mode = 2
@@ -527,8 +467,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="CtrlGrid" type="GridContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controller/VBox" unique_id=300000005]
layout_mode = 2
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 2
[node name="ColH1" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controller/VBox/CtrlGrid" unique_id=300000006]
@@ -606,11 +544,9 @@ metadata/_tab_index = 4
[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput" unique_id=200000002]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 20
[node name="JoystickSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox" unique_id=200000003]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox/JoystickSection" unique_id=200000004]
layout_mode = 2
@@ -619,7 +555,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="JoystickSizeContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox" unique_id=200000005]
layout_mode = 2
theme_override_constants/separation = 8
[node name="JoystickSizeHeader" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox/JoystickSizeContainer" unique_id=200000006]
layout_mode = 2
@@ -662,7 +597,6 @@ button_pressed = true
[node name="ButtonsSection" type="PanelContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox" unique_id=200000013]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_section_header")
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox/ButtonsSection" unique_id=200000014]
layout_mode = 2
@@ -671,7 +605,6 @@ label_settings = SubResource("LabelSettings_section")
[node name="ButtonOpacityContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox" unique_id=200000015]
layout_mode = 2
theme_override_constants/separation = 8
[node name="ButtonOpacityHeader" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/TouchInput/VBox/ButtonOpacityContainer" unique_id=200000016]
layout_mode = 2
+4 -1
View File
@@ -104,6 +104,8 @@ size_flags_horizontal = 3
theme_override_styles/normal = SubResource("StyleBoxFlat_tab_inactive")
theme_override_styles/hover = SubResource("StyleBoxFlat_tab_inactive")
theme_override_styles/pressed = SubResource("StyleBoxFlat_tab_active")
toggle_mode = true
button_pressed = true
text = "FIND FRIEND"
[node name="FriendsTabBtn" type="Button" parent="Panel/Margin/VBox/TabBar" unique_id=1723617099]
@@ -111,9 +113,10 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/normal = SubResource("StyleBoxFlat_tab_active")
theme_override_styles/normal = SubResource("StyleBoxFlat_tab_inactive")
theme_override_styles/hover = SubResource("StyleBoxFlat_tab_inactive")
theme_override_styles/pressed = SubResource("StyleBoxFlat_tab_active")
toggle_mode = true
text = "FRIENDS"
[node name="HSep1" type="HSeparator" parent="Panel/Margin/VBox" unique_id=549111536]
+49 -4
View File
@@ -90,7 +90,52 @@ func delete_mail(mail_id: String) -> bool:
func mark_as_read(mail_id: String) -> void:
if mail_id in read_ids: return
# Since there's no specific RPC for just marking as read, we can just do a claim with 0 rewards if needed,
# or add an RPC for it. For now we just let claim or delete handle the server-side state.
# Let's add an empty claim or just handle it purely local until claimed/deleted if it has no rewards.
pass
read_ids.append(mail_id)
_update_unread_count()
# Persist read state to server via delete_mail RPC pattern (just saves state)
_save_inbox_state()
func _save_inbox_state() -> void:
if not NakamaManager.session: return
var state_payload = {
"claimed_ids": claimed_ids,
"deleted_ids": [],
"read_ids": read_ids
}
# We use storage write via a lightweight RPC or direct storage
var r = await NakamaManager.client.rpc_async(
NakamaManager.session, "save_mail_state",
JSON.stringify(state_payload)
)
if r.is_exception():
push_warning("[MailManager] Could not save mail state: " + r.get_exception().message)
func read_all_and_claim_all() -> void:
"""Mark all mails as read and claim all unclaimed rewards."""
if mails.is_empty(): return
# Mark all as read
for mail in mails:
var mid = mail.get("id", "")
if mid not in read_ids:
read_ids.append(mid)
# Claim all unclaimed rewards
var to_claim: Array = []
for mail in mails:
var mid = mail.get("id", "")
if mid in claimed_ids: continue
var rewards = mail.get("rewards", [])
var has_rewards = false
if typeof(rewards) == TYPE_DICTIONARY:
has_rewards = rewards.get("star", 0) > 0 or rewards.get("gold", 0) > 0
elif typeof(rewards) == TYPE_ARRAY:
has_rewards = rewards.size() > 0
if has_rewards:
to_claim.append(mid)
for mid in to_claim:
await claim_reward(mid)
_update_unread_count()
mail_updated.emit()
+365 -2
View File
@@ -38,6 +38,8 @@ var _current_dr_month: String = ""
# Tab: Announcements
@onready var target_user_edit := %TargetUserEdit as LineEdit
@onready var find_user_btn := %FindUserBtn as Button
@onready var resolved_id_label := %ResolvedIdLabel as Label
@onready var title_edit := %TitleEdit as LineEdit
@onready var content_edit := %ContentEdit as TextEdit
@onready var start_date_edit := %StartDatePicker as Button
@@ -47,6 +49,18 @@ var _current_dr_month: String = ""
@onready var reward_row_template := %RewardRowTemplate as HBoxContainer
@onready var send_mail_btn := %SendMailBtn as Button
var _resolved_user_id: String = ""
# Tab: Mail Manager
@onready var mail_tree := %MailTree as Tree
@onready var refresh_mail_btn := %RefreshMailBtn as Button
@onready var edit_mail_btn := %EditMailBtn as Button
@onready var end_mail_btn := %EndMailBtn as Button
@onready var delete_mail_server_btn := %DeleteMailServerBtn as Button
var _mail_root: TreeItem
var _all_server_mails: Array = []
const MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
# -- Data --
@@ -140,6 +154,19 @@ func _setup_columns() -> void:
lb_tree.set_column_custom_minimum_width(5, 60)
_lb_root = lb_tree.create_item()
# Mail Manager
mail_tree.set_column_title(0, "Type")
mail_tree.set_column_title(1, "Title")
mail_tree.set_column_title(2, "Sender")
mail_tree.set_column_title(3, "Start")
mail_tree.set_column_title(4, "Expires")
mail_tree.set_column_title(5, "Status")
mail_tree.set_column_custom_minimum_width(0, 100)
mail_tree.set_column_expand(0, false)
mail_tree.set_column_custom_minimum_width(5, 80)
mail_tree.set_column_expand(5, false)
_mail_root = mail_tree.create_item()
func _connect_signals() -> void:
close_btn.pressed.connect(_on_close)
refresh_btn.pressed.connect(_on_refresh)
@@ -166,6 +193,16 @@ func _connect_signals() -> void:
# Announcement actions
send_mail_btn.pressed.connect(_on_send_mail)
add_reward_btn.pressed.connect(_on_add_reward_pressed)
find_user_btn.pressed.connect(_on_find_user)
target_user_edit.text_changed.connect(func(_t): _resolved_user_id = ""; resolved_id_label.text = "")
# Mail Manager actions
refresh_mail_btn.pressed.connect(func(): await _load_mail())
mail_tree.item_selected.connect(_on_mail_item_selected)
edit_mail_btn.pressed.connect(_on_edit_mail_pressed)
end_mail_btn.pressed.connect(_on_end_mail_pressed)
delete_mail_server_btn.pressed.connect(_on_delete_mail_server_pressed)
_update_mail_action_btns(null)
# =============================================================================
# Core Panel Logic
@@ -189,6 +226,8 @@ func _on_tab_changed(tab_index: int) -> void:
await _load_leaderboard()
elif tab_index == 2:
await _load_daily_rewards_config()
elif tab_index == 4:
await _load_mail()
# =============================================================================
# RPC Helper
@@ -649,8 +688,60 @@ func _on_add_reward_pressed() -> void:
var remove_btn = row.get_node("RemoveBtn") as Button
remove_btn.pressed.connect(func(): row.queue_free())
func _on_find_user() -> void:
var input = target_user_edit.text.strip_edges()
if input.is_empty():
_resolved_user_id = ""
resolved_id_label.text = "(Global — all users)"
return
_set_status("Looking up user...")
var uid = await _resolve_target_user_id(input)
if uid.is_empty():
resolved_id_label.text = "NOT FOUND"
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_ERR)
_set_status("User not found: " + input, CLR_STATUS_ERR)
else:
_resolved_user_id = uid
resolved_id_label.text = "ID: " + uid.substr(0, 12) + "..."
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_OK)
_set_status("Found user: " + uid, CLR_STATUS_OK)
func _resolve_target_user_id(input: String) -> String:
"""Resolve a username, display_name, or user_id to a user_id.
Returns empty string if not found."""
if input.is_empty():
return ""
# If it looks like a UUID already, return as-is
if input.length() >= 32 and "-" in input:
return input
# Search in cached all_users first
for u in all_users:
var uname: String = u.get("username", "")
var dname: String = u.get("display_name", "")
var uid: String = u.get("user_id", "")
if uname.to_lower() == input.to_lower() or dname.to_lower() == input.to_lower():
return uid
# Cache miss — fetch from server
var res := await _rpc("admin_list_users", {})
if res.has("error"):
return ""
all_users = res.get("users", [])
for u in all_users:
var uname: String = u.get("username", "")
var dname: String = u.get("display_name", "")
var uid: String = u.get("user_id", "")
if uname.to_lower() == input.to_lower() or dname.to_lower() == input.to_lower():
return uid
return ""
func _on_send_mail() -> void:
var target = target_user_edit.text.strip_edges()
var input = target_user_edit.text.strip_edges()
var title = title_edit.text.strip_edges()
var content = content_edit.text.strip_edges()
var start_date = start_date_edit.get_date_iso()
@@ -659,6 +750,21 @@ func _on_send_mail() -> void:
if title.is_empty() or content.is_empty():
_set_status("Title and content cannot be empty", CLR_STATUS_ERR)
return
# Resolve target
var target_uid := ""
if not input.is_empty():
if not _resolved_user_id.is_empty():
target_uid = _resolved_user_id
else:
_set_status("Resolving user...")
target_uid = await _resolve_target_user_id(input)
if target_uid.is_empty():
_set_status("User not found: " + input + ". Use Find button.", CLR_STATUS_ERR)
return
_resolved_user_id = target_uid
resolved_id_label.text = "ID: " + target_uid.substr(0, 12) + "..."
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_OK)
var rewards_arr = []
for child in rewards_list.get_children():
@@ -679,7 +785,7 @@ func _on_send_mail() -> void:
_set_status("Sending mail...")
var payload = {
"target_user_id": target,
"target_user_id": target_uid,
"title": title,
"content": content,
"start_date": start_date,
@@ -694,6 +800,9 @@ func _on_send_mail() -> void:
_set_status("Failed to send mail: " + res.get("error", "Unknown"), CLR_STATUS_ERR)
else:
_set_status("Mail sent successfully!", CLR_STATUS_OK)
target_user_edit.text = ""
_resolved_user_id = ""
resolved_id_label.text = ""
title_edit.text = ""
content_edit.text = ""
if start_date_edit.has_method("clear_date"):
@@ -702,3 +811,257 @@ func _on_send_mail() -> void:
end_date_edit.clear_date()
for child in rewards_list.get_children():
if child.visible: child.queue_free()
# =============================================================================
# TAB 5: MAIL MANAGER
# =============================================================================
func _load_mail() -> void:
_clear_tree(mail_tree, _mail_root)
_set_status("Loading mails...")
var res := await _rpc("admin_list_mail", {})
if res.has("error"):
_set_status("Failed: " + str(res.error), CLR_STATUS_ERR)
return
_all_server_mails = res.get("mails", [])
count_label.text = "%d mails" % _all_server_mails.size()
var now_str = Time.get_datetime_string_from_system(true)
for mail in _all_server_mails:
var item := _mail_root.create_child()
var mail_type: String = mail.get("type", "global")
var mail_title: String = mail.get("title", "No Title")
var sender: String = mail.get("sender", "SYSTEM")
var start_date: String = mail.get("start_date", "")
var expiry: String = mail.get("expiry_date", "")
# Type column
item.set_text(0, mail_type.to_upper())
if mail_type == "personal":
item.set_custom_color(0, CLR_ADMIN)
else:
item.set_custom_color(0, CLR_MOD)
# Title
item.set_text(1, mail_title)
# Sender
item.set_text(2, sender)
# Start date
item.set_text(3, start_date.substr(0, 10) if start_date.length() >= 10 else start_date)
# Expiry
var expiry_short = expiry.substr(0, 10) if expiry.length() >= 10 else expiry
item.set_text(4, expiry_short)
# Status
var end_date: String = mail.get("end_date", "")
var status := "ACTIVE"
if not end_date.is_empty() and now_str > end_date:
status = "ENDED"
item.set_custom_color(5, CLR_BTN_DEL)
elif not expiry.is_empty() and now_str > expiry:
status = "EXPIRED"
item.set_custom_color(5, CLR_BTN_DEL)
else:
item.set_custom_color(5, CLR_STATUS_OK)
item.set_text(5, status)
item.set_metadata(0, mail)
_update_mail_action_btns(null)
_set_status("")
func _on_mail_item_selected() -> void:
var item = mail_tree.get_selected()
_update_mail_action_btns(item)
func _update_mail_action_btns(item) -> void:
var has_sel = item != null
edit_mail_btn.disabled = not has_sel
end_mail_btn.disabled = not has_sel
delete_mail_server_btn.disabled = not has_sel
func _get_selected_mail() -> Dictionary:
var item = mail_tree.get_selected()
if item:
return item.get_metadata(0)
return {}
func _on_edit_mail_pressed() -> void:
var mail = _get_selected_mail()
if mail.is_empty(): return
_show_edit_mail_dialog(mail)
func _show_edit_mail_dialog(mail: Dictionary) -> void:
var mail_id: String = mail.get("id", "")
var mail_type: String = mail.get("type", "global")
var target_uid: String = mail.get("target_user_id", "")
var dialog := AcceptDialog.new()
dialog.title = "Edit Mail: " + mail.get("title", "")
dialog.min_size = Vector2i(480, 360)
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 10)
var id_lbl := Label.new()
id_lbl.text = "ID: " + mail_id + " | Type: " + mail_type
id_lbl.add_theme_color_override("font_color", CLR_DIM)
vbox.add_child(id_lbl)
var grid := GridContainer.new()
grid.columns = 2
grid.add_theme_constant_override("h_separation", 8)
grid.add_theme_constant_override("v_separation", 8)
var title_lbl := Label.new(); title_lbl.text = "Title:"; grid.add_child(title_lbl)
var title_input := LineEdit.new(); title_input.text = mail.get("title", ""); title_input.custom_minimum_size.x = 300; grid.add_child(title_input)
var content_lbl := Label.new(); content_lbl.text = "Content:"; grid.add_child(content_lbl)
var content_input := TextEdit.new(); content_input.text = mail.get("content", ""); content_input.custom_minimum_size = Vector2(300, 100); grid.add_child(content_input)
var end_lbl := Label.new(); end_lbl.text = "End Date:"; grid.add_child(end_lbl)
var end_picker: Button = load("res://scenes/ui/date_picker.tscn").instantiate()
end_picker.custom_minimum_size.x = 200
grid.add_child(end_picker)
# Pre-populate if existing end_date
var existing_end: String = mail.get("end_date", "")
if not existing_end.is_empty():
var parts = existing_end.substr(0, 10).split("-")
if parts.size() == 3:
end_picker.current_date = {"year": int(parts[0]), "month": int(parts[1]), "day": int(parts[2])}
end_picker.view_date = end_picker.current_date.duplicate()
end_picker.text = existing_end.substr(0, 10)
# Recipient row
var recip_lbl := Label.new(); recip_lbl.text = "Recipient:"; grid.add_child(recip_lbl)
var recip_hbox := HBoxContainer.new()
recip_hbox.add_theme_constant_override("separation", 6)
var recip_input := LineEdit.new()
recip_input.custom_minimum_size.x = 180
recip_input.placeholder_text = "Username or ID (empty = global)"
if mail_type == "personal" and not target_uid.is_empty():
recip_input.text = target_uid.substr(0, 12) + "..."
recip_input.tooltip_text = target_uid
recip_hbox.add_child(recip_input)
var recip_find_btn := Button.new()
recip_find_btn.text = "Find"
recip_find_btn.custom_minimum_size.x = 60
recip_hbox.add_child(recip_find_btn)
var recip_resolved_lbl := Label.new()
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
recip_hbox.add_child(recip_resolved_lbl)
grid.add_child(recip_hbox)
var _edit_resolved_uid := target_uid
recip_find_btn.pressed.connect(func():
var inp = recip_input.text.strip_edges()
if inp.is_empty():
_edit_resolved_uid = ""
recip_resolved_lbl.text = "(Global)"
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
return
var uid = await _resolve_target_user_id(inp)
if uid.is_empty():
recip_resolved_lbl.text = "NOT FOUND"
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_ERR)
else:
_edit_resolved_uid = uid
recip_resolved_lbl.text = uid.substr(0, 12) + "..."
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
)
vbox.add_child(grid)
var save_btn := Button.new()
save_btn.text = "Save Changes"
save_btn.custom_minimum_size.y = 40
vbox.add_child(save_btn)
dialog.add_child(vbox)
add_child(dialog)
dialog.popup_centered()
save_btn.pressed.connect(func():
# If user typed something but didn't click Find, auto-resolve
var recip_text = recip_input.text.strip_edges()
var final_target = _edit_resolved_uid
if not recip_text.is_empty() and _edit_resolved_uid == target_uid:
# Text changed but not resolved yet
var resolved = await _resolve_target_user_id(recip_text)
if not resolved.is_empty():
final_target = resolved
elif recip_text.is_empty():
final_target = ""
_set_status("Updating mail...")
var payload = {
"mail_id": mail_id,
"type": mail_type,
"target_user_id": target_uid,
"new_target_user_id": final_target,
"title": title_input.text,
"content": content_input.text,
"end_date": end_picker.get_date_iso()
}
var res = await _rpc("admin_update_mail", payload)
if res.has("success"):
_set_status("Mail updated!", CLR_STATUS_OK)
await _load_mail()
dialog.queue_free()
)
func _on_end_mail_pressed() -> void:
var mail = _get_selected_mail()
if mail.is_empty(): return
var mail_id: String = mail.get("id", "")
var mail_type: String = mail.get("type", "global")
var target_uid: String = mail.get("target_user_id", "")
var confirm := ConfirmationDialog.new()
confirm.title = "End Mail Now?"
confirm.dialog_text = "Set end_date to NOW for:\n" + mail.get("title", "Unknown")
add_child(confirm)
confirm.popup_centered()
confirm.confirmed.connect(func():
var now_iso = Time.get_datetime_string_from_system(true)
var res = await _rpc("admin_update_mail", {
"mail_id": mail_id,
"type": mail_type,
"target_user_id": target_uid,
"end_date": now_iso
})
if res.has("success"):
_set_status("Mail ended", CLR_STATUS_OK)
await _load_mail()
confirm.queue_free()
)
func _on_delete_mail_server_pressed() -> void:
var mail = _get_selected_mail()
if mail.is_empty(): return
var mail_id: String = mail.get("id", "")
var mail_type: String = mail.get("type", "global")
var target_uid: String = mail.get("target_user_id", "")
var confirm := ConfirmationDialog.new()
confirm.title = "PERMANENTLY Delete Mail?"
confirm.dialog_text = "Remove from server storage:\n" + mail.get("title", "Unknown") + "\n\nThis cannot be undone!"
add_child(confirm)
confirm.popup_centered()
confirm.confirmed.connect(func():
var res = await _rpc("admin_delete_mail_server", {
"mail_id": mail_id,
"type": mail_type,
"target_user_id": target_uid
})
if res.has("success"):
_set_status("Mail deleted from server", CLR_STATUS_OK)
await _load_mail()
confirm.queue_free()
)
+53 -27
View File
@@ -6,11 +6,13 @@ signal closed
@onready var mail_list_vbox := %MailListVBox as VBoxContainer
@onready var mail_title_lbl := %MailTitleLbl as Label
@onready var mail_content_text := %MailContentText as RichTextLabel
@onready var sender_lbl := %SenderLbl as Label
@onready var dynamic_rewards_container := %DynamicRewardsContainer as HBoxContainer
@onready var reward_hbox_template := %RewardHBoxTemplate as HBoxContainer
@onready var sender_lbl = get_node_or_null("%SenderLbl")
@onready var dynamic_rewards_container := %DynamicRewardsContainer as VBoxContainer
@onready var reward_hbox_template = %RewardHBoxTemplate
@onready var action_btn := %ActionBtn as Button
@onready var read_all_btn := %ReadAllBtn as Button
@onready var empty_state_lbl := %EmptyStateLbl as Label
@onready var mail_btn_template := %MailBtnTemplate as Button
var _current_mail: Dictionary = {}
@@ -18,6 +20,7 @@ func _ready() -> void:
visible = false
close_btn.pressed.connect(hide_panel)
action_btn.pressed.connect(_on_action_pressed)
read_all_btn.pressed.connect(_on_read_all_pressed)
if MailManager:
MailManager.mail_updated.connect(_refresh_ui)
@@ -25,6 +28,7 @@ func show_panel() -> void:
visible = true
_clear_details()
if MailManager:
await MailManager.read_all_and_claim_all()
MailManager.fetch_mails()
_refresh_ui()
@@ -57,16 +61,15 @@ func _refresh_ui() -> void:
if _current_mail.is_empty() and mails.size() > 0:
_on_mail_selected(mails[0])
@onready var mail_btn_template := %MailBtnTemplate as Button
func _create_mail_button(mail: Dictionary) -> Button:
var btn = mail_btn_template.duplicate()
btn.visible = true
var title = btn.get_node("VBox/Title") as Label
title.text = mail.get("title", "No Title")
var title_lbl = btn.get_node("Margin/VBox/Title") as Label
if title_lbl:
title_lbl.text = mail.get("title", "No Title")
var date_lbl = btn.get_node("VBox/HBox/DateLbl") as Label
var date_lbl = btn.get_node("Margin/VBox/HBox/DateLbl") as Label
var date_str = mail.get("date", "")
var expiry_str = mail.get("expiry_date", "")
@@ -85,19 +88,21 @@ func _create_mail_button(mail: Dictionary) -> Button:
else:
label_text += " (Expired)"
date_lbl.text = label_text
if date_lbl:
date_lbl.text = label_text
var status_lbl = btn.get_node("VBox/HBox/StatusLbl") as Label
var status_lbl = btn.get_node("Margin/VBox/HBox/StatusLbl") as Label
var mail_id = mail.get("id", "")
if mail_id in MailManager.claimed_ids:
status_lbl.text = "CLAIMED"
status_lbl.add_theme_color_override("font_color", Color.GREEN)
elif mail_id in MailManager.read_ids:
status_lbl.text = "READ"
status_lbl.add_theme_color_override("font_color", Color.GRAY)
else:
status_lbl.text = "NEW"
status_lbl.add_theme_color_override("font_color", Color.YELLOW)
if status_lbl:
if mail_id in MailManager.claimed_ids:
status_lbl.text = "CLAIMED"
status_lbl.add_theme_color_override("font_color", Color.GREEN)
elif mail_id in MailManager.read_ids:
status_lbl.text = "READ"
status_lbl.add_theme_color_override("font_color", Color.GRAY)
else:
status_lbl.text = "NEW"
status_lbl.add_theme_color_override("font_color", Color.YELLOW)
return btn
@@ -113,7 +118,7 @@ func _on_mail_selected(mail: Dictionary) -> void:
func _clear_details() -> void:
mail_title_lbl.text = ""
mail_content_text.text = ""
sender_lbl.text = ""
if sender_lbl: sender_lbl.text = ""
for child in dynamic_rewards_container.get_children():
if child.visible: child.queue_free()
action_btn.hide()
@@ -121,7 +126,7 @@ func _clear_details() -> void:
func _update_details(mail: Dictionary) -> void:
mail_title_lbl.text = mail.get("title", "No Title")
mail_content_text.text = mail.get("content", "")
sender_lbl.text = "SENDER:\n" + mail.get("sender", "SYSTEM")
if sender_lbl: sender_lbl.text = "SENDER:\n" + mail.get("sender", "SYSTEM")
for child in dynamic_rewards_container.get_children():
if child.visible: child.queue_free()
@@ -141,16 +146,29 @@ func _update_details(mail: Dictionary) -> void:
var row = reward_hbox_template.duplicate()
row.visible = true
dynamic_rewards_container.add_child(row)
var amt_lbl = row.get_node("AmountLbl") as Label
var amt_lbl = row.get_node("Margin/HBox/VBox/AmountLbl") as Label
var type_lbl = row.get_node("Margin/HBox/VBox/TypeLbl") as Label
var t = r.get("type", "star")
var amt = r.get("amount", 0)
var id = r.get("id", "")
var rid = r.get("id", "")
if t == "star" or t == "gold":
amt_lbl.text = str(amt) + " " + t.to_upper()
else:
amt_lbl.text = str(amt) + " " + id
if amt_lbl: amt_lbl.text = "x" + str(amt)
if type_lbl: type_lbl.text = t.to_upper() if (t == "star" or t == "gold") else rid.to_upper()
# Fill empty slots up to 4
var added = rewards.size()
while added < 4:
added += 1
var empty_row = reward_hbox_template.duplicate()
empty_row.visible = true
dynamic_rewards_container.add_child(empty_row)
var icon_bg = empty_row.get_node_or_null("Margin/HBox/IconBg")
if icon_bg: icon_bg.color = Color(0, 0, 0, 0)
var t_lbl = empty_row.get_node_or_null("Margin/HBox/VBox/TypeLbl")
if t_lbl: t_lbl.text = ""
var a_lbl = empty_row.get_node_or_null("Margin/HBox/VBox/AmountLbl")
if a_lbl: a_lbl.text = ""
action_btn.show()
var mail_id = mail.get("id", "")
@@ -178,3 +196,11 @@ func _on_action_pressed() -> void:
if ok:
_current_mail = {}
_clear_details()
func _on_read_all_pressed() -> void:
if not MailManager: return
for mail in MailManager.mails:
var mid = mail.get("id", "")
if mid not in MailManager.read_ids:
MailManager.mark_as_read(mid)
_refresh_ui()
+5
View File
@@ -83,6 +83,11 @@ func _show_tab(tab: String) -> void:
_friends_view.visible = tab == "friends"
_dm_view.visible = tab == "dm"
_search_tab_btn.set_pressed_no_signal(tab == "search")
_friends_tab_btn.set_pressed_no_signal(tab == "friends")
if _dm_tab_btn:
_dm_tab_btn.set_pressed_no_signal(tab == "dm")
# Auto-load search results on first open
if tab == "search" and _search_results_list.get_child_count() == 0:
_on_search_pressed()
+230
View File
@@ -52,9 +52,13 @@ function InitModule(ctx, logger, nk, initializer) {
// Inbox System RPCs
initializer.registerRpc("admin_send_mail", rpcAdminSendMail);
initializer.registerRpc("admin_list_mail", rpcAdminListMail);
initializer.registerRpc("admin_update_mail", rpcAdminUpdateMail);
initializer.registerRpc("admin_delete_mail_server", rpcAdminDeleteMailServer);
initializer.registerRpc("get_mail", rpcGetMail);
initializer.registerRpc("claim_mail_reward", rpcClaimMailReward);
initializer.registerRpc("delete_mail", rpcDeleteMail);
initializer.registerRpc("save_mail_state", rpcSaveMailState);
// Steam auth hooks
initializer.registerAfterAuthenticateSteam(afterAuthenticateSteam);
@@ -1712,3 +1716,229 @@ function rpcDeleteMail(ctx, logger, nk, payload) {
return JSON.stringify({ success: true, deleted_ids: state.deleted_ids });
}
function rpcSaveMailState(ctx, logger, nk, payload) {
if (!ctx.userId) throw new Error("Not authenticated");
var request = JSON.parse(payload || "{}");
// Load existing state to merge (don't clobber claimed_ids from client)
var stateObjs = nk.storageRead([{ collection: "inbox", key: "state", userId: ctx.userId }]);
var state = { claimed_ids: [], deleted_ids: [], read_ids: [] };
if (stateObjs && stateObjs.length > 0) {
var val = stateObjs[0].value;
state.claimed_ids = val.claimed_ids || [];
state.deleted_ids = val.deleted_ids || [];
state.read_ids = val.read_ids || [];
}
// Merge read_ids from client (add any new ones)
var newReadIds = request.read_ids || [];
for (var i = 0; i < newReadIds.length; i++) {
if (state.read_ids.indexOf(newReadIds[i]) === -1) {
state.read_ids.push(newReadIds[i]);
}
}
nk.storageWrite([{
collection: "inbox",
key: "state",
userId: ctx.userId,
value: state,
permissionRead: 1,
permissionWrite: 0
}]);
return JSON.stringify({ success: true });
}
// =============================================================================
// Admin Mail Management RPCs
// =============================================================================
function rpcAdminListMail(ctx, logger, nk, payload) {
requireAdmin(ctx, nk);
// --- Global mails ---
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
var globalMails = (globalObjs && globalObjs.length > 0) ? (globalObjs[0].value.mails || []) : [];
for (var i = 0; i < globalMails.length; i++) {
globalMails[i].type = "global";
}
// --- Personal mails: scan ALL users' inbox/personal objects ---
var personalMails = [];
var cursor = null;
try {
do {
// storageList(userId, collection, limit, cursor)
// Using empty string userId means list across all users for this collection
var listResult = nk.storageList("", "inbox", 100, cursor);
var objects = listResult.objects || [];
for (var j = 0; j < objects.length; j++) {
var obj = objects[j];
if (obj.key !== "personal") continue;
var ownerUserId = obj.userId;
var mails = obj.value.mails || [];
for (var k = 0; k < mails.length; k++) {
var m = mails[k];
m.type = "personal";
m.target_user_id = ownerUserId;
personalMails.push(m);
}
}
cursor = listResult.cursor || null;
} while (cursor);
} catch (e) {
logger.warn("admin_list_mail: could not list personal inboxes: " + e);
}
var allMails = globalMails.concat(personalMails);
// Sort newest first
allMails.sort(function(a, b) {
return (b.date || "").localeCompare(a.date || "");
});
return JSON.stringify({ mails: allMails });
}
function rpcAdminUpdateMail(ctx, logger, nk, payload) {
requireAdmin(ctx, nk);
var request = JSON.parse(payload || "{}");
var mailId = request.mail_id;
if (!mailId) throw new Error("mail_id required");
var isGlobal = request.type !== "personal";
var targetUserId = request.target_user_id || "";
var newTargetUserId = request.new_target_user_id;
var hasNewTarget = (newTargetUserId !== undefined && newTargetUserId !== null);
// Step 1: Find and extract the mail object from its current location
var mailObj = null;
if (isGlobal) {
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
var globalMails = (globalObjs && globalObjs.length > 0) ? (globalObjs[0].value.mails || []) : [];
for (var i = 0; i < globalMails.length; i++) {
if (globalMails[i].id === mailId) {
mailObj = globalMails.splice(i, 1)[0];
break;
}
}
if (!mailObj) throw new Error("Mail not found in global");
// Write back without this mail (it may move)
nk.storageWrite([{
collection: "config",
key: "global_mail",
userId: "00000000-0000-0000-0000-000000000000",
value: { mails: globalMails },
permissionRead: 2,
permissionWrite: 0
}]);
} else {
if (!targetUserId) throw new Error("target_user_id required for personal mail");
var pObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: targetUserId }]);
var personalMails = (pObjs && pObjs.length > 0) ? (pObjs[0].value.mails || []) : [];
for (var j = 0; j < personalMails.length; j++) {
if (personalMails[j].id === mailId) {
mailObj = personalMails.splice(j, 1)[0];
break;
}
}
if (!mailObj) throw new Error("Mail not found in personal inbox");
// Write back without this mail
nk.storageWrite([{
collection: "inbox",
key: "personal",
userId: targetUserId,
value: { mails: personalMails },
permissionRead: 1,
permissionWrite: 0
}]);
}
// Step 2: Apply field updates to the mail object
if (request.title !== undefined) mailObj.title = request.title;
if (request.content !== undefined) mailObj.content = request.content;
if (request.end_date !== undefined) mailObj.end_date = request.end_date;
if (request.expiry_date !== undefined) mailObj.expiry_date = request.expiry_date;
// Step 3: Determine destination
var destUserId = hasNewTarget ? newTargetUserId : (isGlobal ? "" : targetUserId);
if (destUserId === "") {
// Write to global
mailObj.type = "global";
var gObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
var gMails = (gObjs && gObjs.length > 0) ? (gObjs[0].value.mails || []) : [];
gMails.push(mailObj);
nk.storageWrite([{
collection: "config",
key: "global_mail",
userId: "00000000-0000-0000-0000-000000000000",
value: { mails: gMails },
permissionRead: 2,
permissionWrite: 0
}]);
} else {
// Write to personal inbox of destUserId
mailObj.type = "personal";
var dObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: destUserId }]);
var dMails = (dObjs && dObjs.length > 0) ? (dObjs[0].value.mails || []) : [];
dMails.push(mailObj);
nk.storageWrite([{
collection: "inbox",
key: "personal",
userId: destUserId,
value: { mails: dMails },
permissionRead: 1,
permissionWrite: 0
}]);
}
logger.info("Admin updated mail " + mailId + " by " + ctx.userId + (hasNewTarget ? " (moved to " + destUserId + ")" : ""));
return JSON.stringify({ success: true });
}
function rpcAdminDeleteMailServer(ctx, logger, nk, payload) {
requireAdmin(ctx, nk);
var request = JSON.parse(payload || "{}");
var mailId = request.mail_id;
if (!mailId) throw new Error("mail_id required");
var isGlobal = request.type !== "personal";
var targetUserId = request.target_user_id || "";
if (isGlobal) {
var globalObjs = nk.storageRead([{ collection: "config", key: "global_mail", userId: "00000000-0000-0000-0000-000000000000" }]);
var globalMails = (globalObjs && globalObjs.length > 0) ? (globalObjs[0].value.mails || []) : [];
var before = globalMails.length;
globalMails = globalMails.filter(function(m) { return m.id !== mailId; });
if (globalMails.length === before) throw new Error("Mail not found");
nk.storageWrite([{
collection: "config",
key: "global_mail",
userId: "00000000-0000-0000-0000-000000000000",
value: { mails: globalMails },
permissionRead: 2,
permissionWrite: 0
}]);
} else {
if (!targetUserId) throw new Error("target_user_id required for personal mail");
var pObjs = nk.storageRead([{ collection: "inbox", key: "personal", userId: targetUserId }]);
var personalMails = (pObjs && pObjs.length > 0) ? (pObjs[0].value.mails || []) : [];
var pBefore = personalMails.length;
personalMails = personalMails.filter(function(m) { return m.id !== mailId; });
if (personalMails.length === pBefore) throw new Error("Mail not found");
nk.storageWrite([{
collection: "inbox",
key: "personal",
userId: targetUserId,
value: { mails: personalMails },
permissionRead: 1,
permissionWrite: 0
}]);
}
logger.info("Admin deleted mail " + mailId + " from server by " + ctx.userId);
return JSON.stringify({ success: true });
}