extends Control ## SocialPanel — Friend list with DM and global chat tabs. ## Nodes defined in social_panel.tscn; this script handles all logic. 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 # ─── 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" # ─── Lifecycle ──────────────────────────────────────────────────────────── func _ready() -> void: _close_btn.pressed.connect(func(): emit_signal("closed"); hide()) _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_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: return 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 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() _friend_list.add_child(row) row.setup(uid, uname, 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) 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_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") func _send_dm() -> void: var text = _dm_input.text.strip_edges() if text.is_empty() or _active_dm_user_id.is_empty(): return _dm_input.text = "" var sent = await FriendManager.send_dm(_active_dm_user_id, text) if sent: if not _dm_history.has(_active_dm_user_id): _dm_history[_active_dm_user_id] = [] _dm_history[_active_dm_user_id].append({"from": "me", "msg": text}) _dm_log.append_text("[b]You:[/b] %s\n" % text) func _on_dm_received(from_user_id: String, from_name: String, message: String) -> void: 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"