feat: bug fix social system
This commit is contained in:
+46
-21
@@ -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,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
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user