feat: bug fix social system
This commit is contained in:
@@ -23,7 +23,7 @@ func setup(uid: String, uname: String, state: int, panel: Control) -> void:
|
||||
FriendManager.STATE_FRIEND:
|
||||
_dm_btn.visible = true
|
||||
_remove_btn.visible = true
|
||||
_dm_btn.pressed.connect(func(): panel.call("_open_dm", uid, uname))
|
||||
_dm_btn.pressed.connect(func(): panel.call("open_dm", uid, uname))
|
||||
_remove_btn.pressed.connect(func(): FriendManager.remove_friend(uid))
|
||||
FriendManager.STATE_INVITE_OUT:
|
||||
_state_label.text = "(invite sent)"
|
||||
|
||||
+190
-122
@@ -1,140 +1,216 @@
|
||||
extends Control
|
||||
## SocialPanel — Friend list with DM and global chat tabs.
|
||||
## Nodes defined in social_panel.tscn; this script handles all logic.
|
||||
## SocialPanel — Search, Requests, Friends, DM tabs.
|
||||
## All UI nodes are defined in social_panel.tscn.
|
||||
|
||||
signal closed
|
||||
|
||||
# ─── Node references via %UniqueName ─────────────────────────────────────
|
||||
@onready var _close_btn: Button = %CloseBtn
|
||||
@onready var _friends_tab_btn: Button = %FriendsTabBtn
|
||||
@onready var _global_tab_btn: Button = %GlobalTabBtn
|
||||
@onready var _dm_tab_btn: Button = %DMTabBtn
|
||||
@onready var _friends_view: VBoxContainer = %FriendsView
|
||||
@onready var _global_view: VBoxContainer = %GlobalView
|
||||
@onready var _dm_view: VBoxContainer = %DMView
|
||||
@onready var _add_friend_input: LineEdit = %AddFriendInput
|
||||
@onready var _add_friend_btn: Button = %AddFriendBtn
|
||||
@onready var _friend_list: VBoxContainer = %FriendList
|
||||
@onready var _global_log: RichTextLabel = %GlobalLog
|
||||
@onready var _global_input: LineEdit = %GlobalInput
|
||||
@onready var _global_send_btn: Button = %GlobalSendBtn
|
||||
@onready var _dm_back_btn: Button = %DMBackBtn
|
||||
@onready var _dm_username_label: Label = %DMUsernameLabel
|
||||
@onready var _dm_log: RichTextLabel = %DMLog
|
||||
@onready var _dm_input: LineEdit = %DMInput
|
||||
@onready var _dm_send_btn: Button = %DMSendBtn
|
||||
# ─── Node references ─────────────────────────────────────────────────────────
|
||||
@onready var _close_btn: Button = %CloseBtn
|
||||
|
||||
# ─── State ────────────────────────────────────────────────────────────────
|
||||
var _active_dm_user_id: String = ""
|
||||
var _active_dm_username: String = ""
|
||||
var _dm_history: Dictionary = {}
|
||||
var _global_chat_channel = null
|
||||
var _current_tab: String = "friends"
|
||||
# Tab buttons
|
||||
@onready var _search_tab_btn: Button = %SearchTabBtn
|
||||
@onready var _requests_tab_btn: Button = %RequestsTabBtn
|
||||
@onready var _friends_tab_btn: Button = %FriendsTabBtn
|
||||
@onready var _dm_tab_btn: Button = %DMTabBtn
|
||||
|
||||
# ─── Lifecycle ────────────────────────────────────────────────────────────
|
||||
# Views
|
||||
@onready var _search_view: VBoxContainer = %SearchView
|
||||
@onready var _requests_view: VBoxContainer = %RequestsView
|
||||
@onready var _friends_view: VBoxContainer = %FriendsView
|
||||
@onready var _dm_view: VBoxContainer = %DMView
|
||||
|
||||
# Search tab nodes
|
||||
@onready var _search_input: LineEdit = %SearchInput
|
||||
@onready var _search_btn: Button = %SearchBtn
|
||||
@onready var _no_search_results: Label = %NoSearchResultsLabel
|
||||
@onready var _search_results_list: VBoxContainer = %SearchResultsList
|
||||
@onready var _search_result_tmpl: HBoxContainer = %SearchResultTemplate
|
||||
|
||||
# Requests tab nodes
|
||||
@onready var _no_requests_label: Label = %NoRequestsLabel
|
||||
@onready var _requests_list: VBoxContainer = %RequestsList
|
||||
@onready var _request_row_tmpl: HBoxContainer = %RequestRowTemplate
|
||||
|
||||
# Friends tab nodes
|
||||
@onready var _no_friends_label: Label = %NoFriendsLabel
|
||||
@onready var _friend_list: VBoxContainer = %FriendList
|
||||
|
||||
# DM tab nodes
|
||||
@onready var _dm_back_btn: Button = %DMBackBtn
|
||||
@onready var _dm_username_label: Label = %DMUsernameLabel
|
||||
@onready var _dm_log: RichTextLabel = %DMLog
|
||||
@onready var _dm_input: LineEdit = %DMInput
|
||||
@onready var _dm_send_btn: Button = %DMSendBtn
|
||||
|
||||
# ─── State ───────────────────────────────────────────────────────────────────
|
||||
var _current_tab: String = "search"
|
||||
var _active_dm_user_id: String = ""
|
||||
var _dm_history: Dictionary = {}
|
||||
|
||||
# ─── Lifecycle ───────────────────────────────────────────────────────────────
|
||||
func _ready() -> void:
|
||||
_close_btn.pressed.connect(func(): emit_signal("closed"); hide())
|
||||
|
||||
# Tab buttons
|
||||
_search_tab_btn.pressed.connect(func(): _show_tab("search"))
|
||||
_requests_tab_btn.pressed.connect(func(): _show_tab("requests"))
|
||||
_friends_tab_btn.pressed.connect(func(): _show_tab("friends"))
|
||||
_global_tab_btn.pressed.connect(func(): _show_tab("global"))
|
||||
_dm_tab_btn.pressed.connect(func(): _show_tab("dm"))
|
||||
_add_friend_btn.pressed.connect(_on_add_friend_pressed)
|
||||
_add_friend_input.text_submitted.connect(func(_t): _on_add_friend_pressed())
|
||||
_global_send_btn.pressed.connect(_send_global_message)
|
||||
_global_input.text_submitted.connect(func(_t): _send_global_message())
|
||||
_dm_back_btn.pressed.connect(func(): _show_tab("friends"))
|
||||
|
||||
# Search
|
||||
_search_btn.pressed.connect(_on_search_pressed)
|
||||
_search_input.text_submitted.connect(func(_t): _on_search_pressed())
|
||||
|
||||
# DM
|
||||
_dm_send_btn.pressed.connect(_send_dm)
|
||||
_dm_input.text_submitted.connect(func(_t): _send_dm())
|
||||
_dm_back_btn.pressed.connect(func(): _show_tab("friends"))
|
||||
|
||||
FriendManager.friends_updated.connect(_refresh_friend_list)
|
||||
FriendManager.dm_message_received.connect(_on_dm_received)
|
||||
NakamaManager.connected_to_nakama.connect(_join_global_chat)
|
||||
if NakamaManager.socket and NakamaManager.socket.is_connected_to_host():
|
||||
_join_global_chat()
|
||||
FriendManager.load_friends()
|
||||
_show_tab("friends")
|
||||
|
||||
func _refresh_friend_list(friends: Array) -> void:
|
||||
if not _friend_list:
|
||||
# FriendManager signals
|
||||
FriendManager.friends_updated.connect(_on_friends_updated)
|
||||
FriendManager.dm_message_received.connect(_on_dm_received)
|
||||
|
||||
# Replay already-loaded friends in case FriendManager loaded before this panel was ready
|
||||
if FriendManager.friends.size() > 0:
|
||||
_on_friends_updated(FriendManager.friends)
|
||||
else:
|
||||
FriendManager.load_friends()
|
||||
|
||||
_show_tab("search")
|
||||
|
||||
# ─── Tab Switching ───────────────────────────────────────────────────────────
|
||||
func _show_tab(tab: String) -> void:
|
||||
_current_tab = tab
|
||||
_search_view.visible = tab == "search"
|
||||
_requests_view.visible = tab == "requests"
|
||||
_friends_view.visible = tab == "friends"
|
||||
_dm_view.visible = tab == "dm"
|
||||
|
||||
# Auto-load search results on first open
|
||||
if tab == "search" and _search_results_list.get_child_count() == 0:
|
||||
_on_search_pressed()
|
||||
|
||||
# ─── Search Tab ──────────────────────────────────────────────────────────────
|
||||
func _on_search_pressed() -> void:
|
||||
var query := _search_input.text.strip_edges()
|
||||
|
||||
# UUID → add directly
|
||||
if query.length() == 36 and query.count("-") == 4:
|
||||
FriendManager.add_friend_by_id(query)
|
||||
_search_input.text = ""
|
||||
return
|
||||
|
||||
_search_btn.disabled = true
|
||||
var payload = JSON.stringify({"query": query})
|
||||
var result = await NakamaManager.client.rpc_async(NakamaManager.session, "search_users", payload)
|
||||
_search_btn.disabled = false
|
||||
|
||||
if result.is_exception():
|
||||
push_warning("[Social] Search failed: " + result.get_exception().message)
|
||||
return
|
||||
|
||||
var response = JSON.parse_string(result.payload)
|
||||
if not response or not response.has("users"):
|
||||
return
|
||||
|
||||
_populate_search_results(response.users)
|
||||
|
||||
func _populate_search_results(users: Array) -> void:
|
||||
for ch in _search_results_list.get_children():
|
||||
ch.queue_free()
|
||||
|
||||
_no_search_results.visible = users.is_empty()
|
||||
|
||||
var my_id = NakamaManager.session.user_id if NakamaManager.session else ""
|
||||
|
||||
for u in users:
|
||||
if u.user_id == my_id:
|
||||
continue # skip self
|
||||
var row: HBoxContainer = _search_result_tmpl.duplicate()
|
||||
row.show()
|
||||
row.get_node("SRNameLabel").text = u.display_name + " (@" + u.username + ")"
|
||||
var add_btn: Button = row.get_node("SRAddBtn")
|
||||
add_btn.pressed.connect(func():
|
||||
FriendManager.add_friend_by_id(u.user_id)
|
||||
add_btn.text = "Sent ✓"
|
||||
add_btn.disabled = true
|
||||
)
|
||||
_search_results_list.add_child(row)
|
||||
|
||||
# ─── Requests Tab ────────────────────────────────────────────────────────────
|
||||
func _populate_requests(incoming: Array) -> void:
|
||||
for ch in _requests_list.get_children():
|
||||
ch.queue_free()
|
||||
|
||||
_no_requests_label.visible = incoming.is_empty()
|
||||
|
||||
for f in incoming:
|
||||
var row: HBoxContainer = _request_row_tmpl.duplicate()
|
||||
row.show()
|
||||
row.get_node("RRNameLabel").text = f.username
|
||||
var accept_btn: Button = row.get_node("RRAcceptBtn")
|
||||
var decline_btn: Button = row.get_node("RRDeclineBtn")
|
||||
var uid: String = f.user_id
|
||||
accept_btn.pressed.connect(func():
|
||||
FriendManager.add_friend_by_id(uid)
|
||||
row.queue_free()
|
||||
)
|
||||
decline_btn.pressed.connect(func():
|
||||
FriendManager.remove_friend(uid)
|
||||
row.queue_free()
|
||||
)
|
||||
_requests_list.add_child(row)
|
||||
|
||||
# Badge on tab button
|
||||
if incoming.is_empty():
|
||||
_requests_tab_btn.text = "Requests"
|
||||
else:
|
||||
_requests_tab_btn.text = "Requests (%d)" % incoming.size()
|
||||
|
||||
# ─── Friends Tab ─────────────────────────────────────────────────────────────
|
||||
func _populate_friends(mutual: Array) -> void:
|
||||
for ch in _friend_list.get_children():
|
||||
ch.queue_free()
|
||||
|
||||
if friends.is_empty():
|
||||
var empty_lbl := Label.new()
|
||||
empty_lbl.text = "No friends yet. Add someone above!"
|
||||
_friend_list.add_child(empty_lbl)
|
||||
return
|
||||
|
||||
|
||||
_no_friends_label.visible = mutual.is_empty()
|
||||
|
||||
var friend_row_scene := preload("res://scenes/ui/friend_row.tscn")
|
||||
for f in friends:
|
||||
var uid: String = f.get("user_id", "")
|
||||
var uname: String = f.get("username", "?")
|
||||
var state: int = f.get("state", 0)
|
||||
var row: Control = friend_row_scene.instantiate()
|
||||
for f in mutual:
|
||||
var row: Control = friend_row_scene.instantiate()
|
||||
_friend_list.add_child(row)
|
||||
row.setup(uid, uname, state, self)
|
||||
row.setup(f.user_id, f.username, f.state, self)
|
||||
|
||||
func _on_add_friend_pressed() -> void:
|
||||
var val := _add_friend_input.text.strip_edges()
|
||||
if val.is_empty():
|
||||
return
|
||||
_add_friend_input.text = ""
|
||||
if val.length() == 36 and val.count("-") == 4:
|
||||
FriendManager.add_friend_by_id(val)
|
||||
else:
|
||||
FriendManager.add_friend_by_username(val)
|
||||
# ─── FriendManager Callbacks ─────────────────────────────────────────────────
|
||||
func _on_friends_updated(friends: Array) -> void:
|
||||
print("[SocialPanel] _on_friends_updated: total=%d" % friends.size())
|
||||
var incoming := friends.filter(func(f): return f.state == FriendManager.STATE_INVITE_IN)
|
||||
var mutual := friends.filter(func(f): return f.state == FriendManager.STATE_FRIEND)
|
||||
print("[SocialPanel] incoming=%d mutual=%d" % [incoming.size(), mutual.size()])
|
||||
_populate_requests(incoming)
|
||||
_populate_friends(mutual)
|
||||
|
||||
func _join_global_chat() -> void:
|
||||
if _global_chat_channel:
|
||||
return
|
||||
var socket = NakamaManager.socket
|
||||
if not socket:
|
||||
return
|
||||
var channel = await socket.join_chat_async(
|
||||
"social_global", NakamaSocket.ChannelType.Room, true, false)
|
||||
if channel.is_exception():
|
||||
return
|
||||
_global_chat_channel = channel
|
||||
if not socket.received_channel_message.is_connected(_on_global_message):
|
||||
socket.received_channel_message.connect(_on_global_message)
|
||||
|
||||
func _send_global_message() -> void:
|
||||
var text = _global_input.text.strip_edges()
|
||||
if text.is_empty() or not _global_chat_channel:
|
||||
return
|
||||
_global_input.text = ""
|
||||
var socket = NakamaManager.socket
|
||||
if socket:
|
||||
socket.write_chat_message_async(_global_chat_channel.id, {"msg": text})
|
||||
|
||||
func _on_global_message(msg) -> void:
|
||||
if not _global_chat_channel or msg.channel_id != _global_chat_channel.id:
|
||||
return
|
||||
var text: String = ""
|
||||
var parsed = JSON.parse_string(msg.content)
|
||||
if typeof(parsed) == TYPE_DICTIONARY:
|
||||
text = parsed.get("msg", msg.content)
|
||||
else:
|
||||
text = msg.content
|
||||
var sender_name: String = msg.username if msg.username else "?"
|
||||
if _global_log:
|
||||
_global_log.append_text("[b]%s:[/b] %s\n" % [sender_name, text])
|
||||
|
||||
func _open_dm(user_id: String, username: String) -> void:
|
||||
_active_dm_user_id = user_id
|
||||
_active_dm_username = username
|
||||
# ─── DM ──────────────────────────────────────────────────────────────────────
|
||||
func open_dm(user_id: String, username: String) -> void:
|
||||
_active_dm_user_id = user_id
|
||||
_dm_username_label.text = "DM: %s" % username
|
||||
_dm_tab_btn.visible = true
|
||||
# Reload history
|
||||
_dm_log.clear()
|
||||
var history: Array = _dm_history.get(user_id, [])
|
||||
for entry in history:
|
||||
var is_self = entry.get("from") == "me"
|
||||
var prefix = "[b]%s:[/b]" % ("You" if is_self else username)
|
||||
_dm_log.append_text("%s %s\n" % [prefix, entry.get("msg", "")])
|
||||
# Open channel
|
||||
FriendManager.open_dm(user_id)
|
||||
_show_tab("dm")
|
||||
|
||||
_dm_log.append_text("[i]Loading history...[/i]\n")
|
||||
var history = await FriendManager.get_dm_history(user_id)
|
||||
|
||||
_dm_log.clear()
|
||||
_dm_history[user_id] = []
|
||||
|
||||
var my_id = NakamaManager.session.user_id if NakamaManager.session else ""
|
||||
|
||||
for entry in history:
|
||||
var is_self = entry.get("from") == my_id
|
||||
var sender_name = "You" if is_self else username
|
||||
_dm_history[user_id].append({"from": "me" if is_self else entry.get("from"), "msg": entry.get("msg")})
|
||||
_dm_log.append_text("[b]%s:[/b] %s\n" % [sender_name, entry.get("msg", "")])
|
||||
|
||||
FriendManager.open_dm(user_id)
|
||||
|
||||
func _send_dm() -> void:
|
||||
var text = _dm_input.text.strip_edges()
|
||||
@@ -152,13 +228,5 @@ func _on_dm_received(from_user_id: String, from_name: String, message: String) -
|
||||
if not _dm_history.has(from_user_id):
|
||||
_dm_history[from_user_id] = []
|
||||
_dm_history[from_user_id].append({"from": from_user_id, "msg": message})
|
||||
|
||||
if _active_dm_user_id == from_user_id and _current_tab == "dm":
|
||||
_dm_log.append_text("[b]%s:[/b] %s\n" % [from_name, message])
|
||||
|
||||
# ─── Tab switching ─────────────────────────────────────────────────────────
|
||||
func _show_tab(tab: String) -> void:
|
||||
_current_tab = tab
|
||||
_friends_view.visible = tab == "friends"
|
||||
_global_view.visible = tab == "global"
|
||||
_dm_view.visible = tab == "dm"
|
||||
|
||||
Reference in New Issue
Block a user