feat: bug fix social system

This commit is contained in:
2026-04-30 04:18:46 +08:00
parent 2a1a76e682
commit 54be7bbb25
9 changed files with 607 additions and 235 deletions
+46 -21
View File
@@ -125,7 +125,7 @@ var _bot_names: Dictionary = {}
# =============================================================================
# Chat System
# =============================================================================
const GLOBAL_CHAT_ROOM := "global_lobby"
const GLOBAL_CHAT_ROOM := "social_global"
var _chat_channel = null
var _chat_messages: Array = []
@@ -1311,15 +1311,30 @@ func _join_global_chat() -> void:
if not socket.received_channel_message.is_connected(_on_chat_message_received):
socket.received_channel_message.connect(_on_chat_message_received)
# Load history and render (Nakama sends recent messages on join via received_channel_message)
# Load history
_chat_messages.clear()
var history_result = await NakamaManager.client.list_channel_messages_async(NakamaManager.session, _chat_channel.id, 50, false)
if not history_result.is_exception() and history_result.messages:
var msgs = history_result.messages.duplicate()
msgs.reverse() # Oldest to newest
for msg in msgs:
_add_chat_message(msg, false)
_trim_old_messages()
_refresh_chat_display()
func _on_chat_message_received(message) -> void:
"""Nakama socket signal: a message arrived on any channel."""
# message is ApiChannelMessage — use direct property access, NOT .get()
if _chat_channel == null or message.channel_id != _chat_channel.id:
return
# Ignore messages from ourselves (we inject them locally for instant feedback)
if NakamaManager.session and message.sender_id == NakamaManager.session.user_id:
return
_add_chat_message(message, true)
func _add_chat_message(message, refresh_display: bool) -> void:
# content is a String (JSON) — parse to extract our "msg" field
var text: String = ""
var parsed = JSON.parse_string(message.content)
@@ -1330,8 +1345,10 @@ func _on_chat_message_received(message) -> void:
# Sender: use username property directly (falls back to first 8 chars of sender_id)
var sender: String = message.username
if sender.is_empty():
if sender.is_empty() and message.sender_id:
sender = message.sender_id.substr(0, 8)
elif sender.is_empty():
sender = "Unknown"
# Timestamp → HH:MM
var ts_str: String = _format_nakama_time(message.create_time)
@@ -1340,11 +1357,12 @@ func _on_chat_message_received(message) -> void:
"sender": sender,
"content": text,
"ts": ts_str,
"date": message.create_time.substr(0, 10)
"date": message.create_time.substr(0, 10) if message.create_time else Time.get_date_string_from_system()
})
_trim_old_messages()
_refresh_chat_display()
if refresh_display:
_trim_old_messages()
_refresh_chat_display()
func _on_chat_send_pressed() -> void:
"""Send a message to the global chat channel."""
@@ -1355,10 +1373,11 @@ func _on_chat_send_pressed() -> void:
chat_input.text = ""
chat_input.grab_focus()
# Instantly show locally for best UX
_inject_local_message(text)
var socket = NakamaManager.socket
if not socket or _chat_channel == null:
# Offline fallback: show locally only
_inject_local_message(text)
return
# Nakama GDScript SDK: write_chat_message_async takes a Dictionary, not a JSON string
@@ -1366,8 +1385,6 @@ func _on_chat_send_pressed() -> void:
var result = await socket.write_chat_message_async(_chat_channel.id, content)
if result.is_exception():
push_warning("[Chat] Failed to send message: " + result.get_exception().message)
# Still show it locally
_inject_local_message(text)
func _inject_local_message(text: String) -> void:
"""Display a message as the local player when offline/fallback."""
@@ -1382,9 +1399,9 @@ func _inject_local_message(text: String) -> void:
_refresh_chat_display()
func _trim_old_messages() -> void:
"""Remove messages from previous calendar days (daily clear)."""
var today: String = Time.get_date_string_from_system()
_chat_messages = _chat_messages.filter(func(m): return m.get("date", today) == today)
"""Keep only the most recent 100 messages to prevent memory/UI bloat."""
if _chat_messages.size() > 100:
_chat_messages = _chat_messages.slice(-100)
func _refresh_chat_display() -> void:
"""Re-render the RichTextLabel with all buffered messages."""
@@ -1402,20 +1419,28 @@ func _refresh_chat_display() -> void:
# Scroll to bottom
await get_tree().process_frame
chat_display.scroll_to_line(chat_display.get_line_count())
if chat_display:
var scrollbar = chat_display.get_v_scroll_bar()
if scrollbar:
chat_display.scroll_to_line(chat_display.get_line_count())
func _format_nakama_time(iso_str: String) -> String:
"""Convert Nakama ISO timestamp '2026-04-14T10:30:00Z' → local 'HH:MM'."""
# Parse the UTC time components
if iso_str.length() < 19:
func _format_nakama_time(time_str: String) -> String:
"""Convert Nakama time to local 'HH:MM'."""
# Nakama returns UNIX epoch as string (e.g. "1714418656") or ISO string.
if time_str.is_valid_int():
var unix_time = time_str.to_int()
var dict = Time.get_time_dict_from_unix_time(unix_time)
return "%02d:%02d" % [dict.hour, dict.minute]
# Fallback for ISO strings or empty
if time_str.length() < 19:
return _get_local_time_hhmm()
var t_parts = iso_str.split("T")
var t_parts = time_str.split("T")
if t_parts.size() < 2:
return _get_local_time_hhmm()
var time_part = t_parts[1].replace("Z", "").split(":")
if time_part.size() < 2:
return _get_local_time_hhmm()
# Use UTC hours/minutes directly (simple, avoids TZ complexity in Godot)
return "%s:%s" % [time_part[0], time_part[1]]
func _get_local_time_hhmm() -> String:
+1 -1
View File
@@ -1,4 +1,4 @@
[gd_scene format=3 uid="uid://bdfogx1k2q1fl"]
[gd_scene format=3 uid="uid://c330qhn1hqr3b"]
[ext_resource type="Script" uid="uid://vgyrq5y5p7jw" path="res://scripts/ui/boot_screen.gd" id="1_boot"]
[ext_resource type="Texture2D" uid="uid://2d1ks5pmblc7" path="res://assets/graphics/main_menu/bg_back.png" id="2_17ab1"]
+134 -67
View File
@@ -24,10 +24,10 @@ anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -240.0
offset_top = -280.0
offset_right = 240.0
offset_bottom = 280.0
offset_left = -250.0
offset_top = -310.0
offset_right = 250.0
offset_bottom = 310.0
[node name="VBox" type="VBoxContainer" parent="Panel"]
layout_mode = 2
@@ -39,141 +39,208 @@ layout_mode = 2
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Social"
[node name="CloseBtn" type="Button" parent="Panel/VBox/Header"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "X"
[node name="HSep" type="HSeparator" parent="Panel/VBox"]
[node name="HSep0" type="HSeparator" parent="Panel/VBox"]
layout_mode = 2
[node name="TabBar" type="HBoxContainer" parent="Panel/VBox"]
layout_mode = 2
[node name="SearchTabBtn" type="Button" parent="Panel/VBox/TabBar"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Search"
[node name="RequestsTabBtn" type="Button" parent="Panel/VBox/TabBar"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Requests"
[node name="FriendsTabBtn" type="Button" parent="Panel/VBox/TabBar"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Friends"
[node name="GlobalTabBtn" type="Button" parent="Panel/VBox/TabBar"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Global Chat"
[node name="DMTabBtn" type="Button" parent="Panel/VBox/TabBar"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "DM"
visible = false
[node name="ContentStack" type="Control" parent="Panel/VBox"]
[node name="HSep1" type="HSeparator" parent="Panel/VBox"]
layout_mode = 2
size_flags_vertical = 3
custom_minimum_size = Vector2(0, 380)
[node name="FriendsView" type="VBoxContainer" parent="Panel/VBox/ContentStack"]
[node name="SearchView" type="VBoxContainer" parent="Panel/VBox"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
[node name="AddFriendRow" type="HBoxContainer" parent="Panel/VBox/ContentStack/FriendsView"]
layout_mode = 2
[node name="AddFriendInput" type="LineEdit" parent="Panel/VBox/ContentStack/FriendsView/AddFriendRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Username or ID..."
[node name="AddFriendBtn" type="Button" parent="Panel/VBox/ContentStack/FriendsView/AddFriendRow"]
unique_name_in_owner = true
layout_mode = 2
text = "Add"
[node name="FriendScroll" type="ScrollContainer" parent="Panel/VBox/ContentStack/FriendsView"]
layout_mode = 2
size_flags_vertical = 3
[node name="FriendList" type="VBoxContainer" parent="Panel/VBox/ContentStack/FriendsView/FriendScroll"]
[node name="SearchRow" type="HBoxContainer" parent="Panel/VBox/SearchView"]
layout_mode = 2
[node name="SearchInput" type="LineEdit" parent="Panel/VBox/SearchView/SearchRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Search by username..."
[node name="GlobalView" type="VBoxContainer" parent="Panel/VBox/ContentStack"]
[node name="SearchBtn" type="Button" parent="Panel/VBox/SearchView/SearchRow"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Search"
[node name="NoSearchResultsLabel" type="Label" parent="Panel/VBox/SearchView"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "No users found."
visible = false
[node name="GlobalLog" type="RichTextLabel" parent="Panel/VBox/ContentStack/GlobalView"]
unique_name_in_owner = true
[node name="SearchScroll" type="ScrollContainer" parent="Panel/VBox/SearchView"]
layout_mode = 2
size_flags_vertical = 3
bbcode_enabled = true
scroll_following = true
[node name="GlobalInputRow" type="HBoxContainer" parent="Panel/VBox/ContentStack/GlobalView"]
layout_mode = 2
[node name="GlobalInput" type="LineEdit" parent="Panel/VBox/ContentStack/GlobalView/GlobalInputRow"]
[node name="SearchResultsList" type="VBoxContainer" parent="Panel/VBox/SearchView/SearchScroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Type a message..."
[node name="GlobalSendBtn" type="Button" parent="Panel/VBox/ContentStack/GlobalView/GlobalInputRow"]
[node name="SearchResultTemplate" type="HBoxContainer" parent="Panel/VBox/SearchView"]
unique_name_in_owner = true
layout_mode = 2
text = "Send"
[node name="DMView" type="VBoxContainer" parent="Panel/VBox/ContentStack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
visible = false
[node name="DMHeader" type="HBoxContainer" parent="Panel/VBox/ContentStack/DMView"]
[node name="SRNameLabel" type="Label" parent="Panel/VBox/SearchView/SearchResultTemplate"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
[node name="DMBackBtn" type="Button" parent="Panel/VBox/ContentStack/DMView/DMHeader"]
[node name="SRAddBtn" type="Button" parent="Panel/VBox/SearchView/SearchResultTemplate"]
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Add Friend"
[node name="RequestsView" type="VBoxContainer" parent="Panel/VBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
visible = false
[node name="NoRequestsLabel" type="Label" parent="Panel/VBox/RequestsView"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "No incoming friend requests."
visible = false
[node name="RequestsScroll" type="ScrollContainer" parent="Panel/VBox/RequestsView"]
layout_mode = 2
size_flags_vertical = 3
[node name="RequestsList" type="VBoxContainer" parent="Panel/VBox/RequestsView/RequestsScroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="RequestRowTemplate" type="HBoxContainer" parent="Panel/VBox/RequestsView"]
unique_name_in_owner = true
layout_mode = 2
visible = false
[node name="RRNameLabel" type="Label" parent="Panel/VBox/RequestsView/RequestRowTemplate"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
[node name="RRAcceptBtn" type="Button" parent="Panel/VBox/RequestsView/RequestRowTemplate"]
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Accept"
[node name="RRDeclineBtn" type="Button" parent="Panel/VBox/RequestsView/RequestRowTemplate"]
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Decline"
[node name="FriendsView" type="VBoxContainer" parent="Panel/VBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
visible = false
[node name="NoFriendsLabel" type="Label" parent="Panel/VBox/FriendsView"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "No friends yet!"
visible = false
[node name="FriendScroll" type="ScrollContainer" parent="Panel/VBox/FriendsView"]
layout_mode = 2
size_flags_vertical = 3
[node name="FriendList" type="VBoxContainer" parent="Panel/VBox/FriendsView/FriendScroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="DMView" type="VBoxContainer" parent="Panel/VBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
visible = false
[node name="DMHeader" type="HBoxContainer" parent="Panel/VBox/DMView"]
layout_mode = 2
[node name="DMBackBtn" type="Button" parent="Panel/VBox/DMView/DMHeader"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "<- Back"
[node name="DMUsernameLabel" type="Label" parent="Panel/VBox/ContentStack/DMView/DMHeader"]
[node name="DMUsernameLabel" type="Label" parent="Panel/VBox/DMView/DMHeader"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
[node name="DMLog" type="RichTextLabel" parent="Panel/VBox/ContentStack/DMView"]
[node name="DMLog" type="RichTextLabel" parent="Panel/VBox/DMView"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
theme_override_colors/default_color = Color(0.3, 0.18, 0.1, 1)
bbcode_enabled = true
scroll_following = true
[node name="DMInputRow" type="HBoxContainer" parent="Panel/VBox/ContentStack/DMView"]
[node name="DMInputRow" type="HBoxContainer" parent="Panel/VBox/DMView"]
layout_mode = 2
[node name="DMInput" type="LineEdit" parent="Panel/VBox/ContentStack/DMView/DMInputRow"]
[node name="DMInput" type="LineEdit" parent="Panel/VBox/DMView/DMInputRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Type a message..."
[node name="DMSendBtn" type="Button" parent="Panel/VBox/ContentStack/DMView/DMInputRow"]
[node name="DMSendBtn" type="Button" parent="Panel/VBox/DMView/DMInputRow"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.3, 0.18, 0.1, 1)
text = "Send"