修bug
This commit is contained in:
@@ -122,6 +122,10 @@ var _current_map: String = ""
|
||||
# 游戏 token
|
||||
var _game_token: String = ""
|
||||
|
||||
# 发送后本地回显去重(避免服务端也回发导致重复显示)
|
||||
const SELF_ECHO_DEDUPE_WINDOW: float = 10.0
|
||||
var _pending_self_messages: Array[Dictionary] = []
|
||||
|
||||
# ============================================================================
|
||||
# 生命周期方法
|
||||
# ============================================================================
|
||||
@@ -240,7 +244,10 @@ func send_chat_message(content: String, scope: String = "local") -> void:
|
||||
|
||||
# 发送消息(JSON 字符串)
|
||||
var json_string := JSON.stringify(message_data)
|
||||
_websocket_manager.send_message(json_string)
|
||||
var send_err: Error = _websocket_manager.send_message(json_string)
|
||||
if send_err != OK:
|
||||
_handle_error("SEND_FAILED", "WebSocket send failed: %s" % error_string(send_err))
|
||||
return
|
||||
|
||||
# 记录发送时间
|
||||
_record_message_timestamp()
|
||||
@@ -253,6 +260,24 @@ func send_chat_message(content: String, scope: String = "local") -> void:
|
||||
"is_self": true
|
||||
})
|
||||
|
||||
var now_timestamp: float = Time.get_unix_time_from_system()
|
||||
|
||||
# 记录待去重的“自己消息”(如果服务端也回发 chat_render,则避免重复显示)
|
||||
_pending_self_messages.append({
|
||||
"content": content,
|
||||
"expires_at": now_timestamp + SELF_ECHO_DEDUPE_WINDOW
|
||||
})
|
||||
|
||||
# 本地回显:UI 目前只订阅 CHAT_MESSAGE_RECEIVED,所以这里也发一次 received
|
||||
chat_message_received.emit(_current_username, content, true, now_timestamp)
|
||||
EventSystem.emit_event(EventNames.CHAT_MESSAGE_RECEIVED, {
|
||||
"from_user": _current_username,
|
||||
"content": content,
|
||||
"show_bubble": true,
|
||||
"timestamp": now_timestamp,
|
||||
"is_self": true
|
||||
})
|
||||
|
||||
print("📤 发送聊天消息: ", content)
|
||||
|
||||
# 消息发送完成回调
|
||||
@@ -427,6 +452,7 @@ func _on_history_loaded(messages: Array) -> void:
|
||||
"content": message.get("content", ""),
|
||||
"show_bubble": false,
|
||||
"timestamp": message.get("timestamp", 0.0),
|
||||
"is_self": (not _current_username.is_empty() and message.get("from_user", "") == _current_username),
|
||||
"is_history": true # 标记为历史消息
|
||||
})
|
||||
|
||||
@@ -504,6 +530,8 @@ func _on_data_received(message: String) -> void:
|
||||
_handle_login_success(data)
|
||||
"login_error":
|
||||
_handle_login_error(data)
|
||||
"chat":
|
||||
_handle_chat_render(data)
|
||||
"chat_sent":
|
||||
_handle_chat_sent(data)
|
||||
"chat_error":
|
||||
@@ -580,10 +608,24 @@ func _handle_chat_error(data: Dictionary) -> void:
|
||||
|
||||
# 处理接收到的聊天消息
|
||||
func _handle_chat_render(data: Dictionary) -> void:
|
||||
var from_user: String = data.get("from", "")
|
||||
var content: String = data.get("txt", "")
|
||||
var show_bubble: bool = data.get("bubble", false)
|
||||
var timestamp: float = float(data.get("timestamp", "0.0"))
|
||||
# 兼容不同后端字段命名:
|
||||
# - chat_render: {from, txt, bubble, timestamp}
|
||||
# - chat: {content, scope, (可选 from/username/timestamp)}
|
||||
var from_user: String = data.get("from", data.get("from_user", data.get("username", "")))
|
||||
var content: String = data.get("txt", data.get("content", ""))
|
||||
var show_bubble: bool = bool(data.get("bubble", data.get("show_bubble", false)))
|
||||
|
||||
var timestamp: float = _parse_chat_timestamp_to_unix(data.get("timestamp", 0.0))
|
||||
|
||||
var is_self: bool = (not _current_username.is_empty() and from_user == _current_username)
|
||||
if is_self and _consume_pending_self_message(content):
|
||||
# 已经本地回显过,避免重复显示
|
||||
return
|
||||
|
||||
# 如果服务端没带发送者信息,但内容匹配最近自己发送的消息,则认为是自己消息
|
||||
if from_user.is_empty() and _consume_pending_self_message(content):
|
||||
from_user = _current_username
|
||||
is_self = true
|
||||
|
||||
print("📨 收到聊天消息: ", from_user, " -> ", content)
|
||||
|
||||
@@ -592,7 +634,7 @@ func _handle_chat_render(data: Dictionary) -> void:
|
||||
"from_user": from_user,
|
||||
"content": content,
|
||||
"timestamp": timestamp,
|
||||
"is_self": false
|
||||
"is_self": is_self
|
||||
})
|
||||
|
||||
# 发射信号
|
||||
@@ -603,9 +645,44 @@ func _handle_chat_render(data: Dictionary) -> void:
|
||||
"from_user": from_user,
|
||||
"content": content,
|
||||
"show_bubble": show_bubble,
|
||||
"timestamp": timestamp
|
||||
"timestamp": timestamp,
|
||||
"is_self": is_self
|
||||
})
|
||||
|
||||
# 解析聊天消息时间戳(兼容 unix 秒 / ISO 8601 字符串)
|
||||
func _parse_chat_timestamp_to_unix(timestamp_raw: Variant) -> float:
|
||||
if typeof(timestamp_raw) == TYPE_INT or typeof(timestamp_raw) == TYPE_FLOAT:
|
||||
var ts := float(timestamp_raw)
|
||||
return ts if ts > 0.0 else Time.get_unix_time_from_system()
|
||||
|
||||
var ts_str := str(timestamp_raw)
|
||||
if ts_str.strip_edges().is_empty():
|
||||
return Time.get_unix_time_from_system()
|
||||
|
||||
# 纯数字字符串(必须整串都是数字/小数点,避免把 ISO 字符串前缀 "2026" 误判成时间戳)
|
||||
var numeric_regex := RegEx.new()
|
||||
numeric_regex.compile("^\\s*-?\\d+(?:\\.\\d+)?\\s*$")
|
||||
if numeric_regex.search(ts_str) != null:
|
||||
var ts_num := float(ts_str)
|
||||
return ts_num if ts_num > 0.0 else Time.get_unix_time_from_system()
|
||||
|
||||
# ISO 8601: 2026-01-19T15:15:43.930Z
|
||||
var regex := RegEx.new()
|
||||
regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})")
|
||||
var result := regex.search(ts_str)
|
||||
if result == null:
|
||||
return Time.get_unix_time_from_system()
|
||||
|
||||
var utc_dict := {
|
||||
"year": int(result.get_string(1)),
|
||||
"month": int(result.get_string(2)),
|
||||
"day": int(result.get_string(3)),
|
||||
"hour": int(result.get_string(4)),
|
||||
"minute": int(result.get_string(5)),
|
||||
"second": int(result.get_string(6))
|
||||
}
|
||||
return Time.get_unix_time_from_datetime_dict(utc_dict)
|
||||
|
||||
# 处理位置更新成功
|
||||
func _handle_position_updated(data: Dictionary) -> void:
|
||||
var stream: String = data.get("stream", "")
|
||||
@@ -665,6 +742,24 @@ func _record_message_timestamp() -> void:
|
||||
var current_time := Time.get_unix_time_from_system()
|
||||
_message_timestamps.append(current_time)
|
||||
|
||||
# 消费一个待去重的“自己消息”(允许相同内容多次发送:每次消费一个)
|
||||
func _consume_pending_self_message(content: String) -> bool:
|
||||
var now := Time.get_unix_time_from_system()
|
||||
|
||||
# 先清理过期项
|
||||
for i in range(_pending_self_messages.size() - 1, -1, -1):
|
||||
var item: Dictionary = _pending_self_messages[i]
|
||||
if float(item.get("expires_at", 0.0)) < now:
|
||||
_pending_self_messages.remove_at(i)
|
||||
|
||||
# 再匹配内容
|
||||
for i in range(_pending_self_messages.size() - 1, -1, -1):
|
||||
if str(_pending_self_messages[i].get("content", "")) == content:
|
||||
_pending_self_messages.remove_at(i)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
# 添加消息到当前会话历史
|
||||
func _add_message_to_history(message: Dictionary) -> void:
|
||||
_message_history.append(message)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
extends Panel
|
||||
extends PanelContainer
|
||||
|
||||
# ============================================================================
|
||||
# ChatMessage.gd - 聊天消息气泡组件
|
||||
@@ -49,22 +49,25 @@ var user_info_container: HBoxContainer
|
||||
# ============================================================================
|
||||
|
||||
func _ready() -> void:
|
||||
# 使用 get_node 获取节点引用
|
||||
username_label = get_node_or_null("VBoxContainer/UserInfoContainer/UsernameLabel")
|
||||
timestamp_label = get_node_or_null("VBoxContainer/UserInfoContainer/TimestampLabel")
|
||||
content_label = get_node_or_null("VBoxContainer/ContentLabel")
|
||||
user_info_container = get_node_or_null("VBoxContainer/UserInfoContainer")
|
||||
|
||||
# 调试输出
|
||||
if username_label:
|
||||
print("✅ UsernameLabel found")
|
||||
else:
|
||||
print("❌ UsernameLabel NOT found!")
|
||||
# 打印所有子节点帮助调试
|
||||
print("Available children: ", _get_all_children_names(self))
|
||||
|
||||
# 应用最大宽度限制
|
||||
custom_minimum_size.x = min(max_width, get_parent().size.x)
|
||||
_cache_node_refs()
|
||||
|
||||
func _cache_node_refs() -> void:
|
||||
if not username_label:
|
||||
username_label = get_node_or_null("VBoxContainer/UserInfoContainer/UsernameLabel")
|
||||
if not timestamp_label:
|
||||
timestamp_label = get_node_or_null("VBoxContainer/UserInfoContainer/TimestampLabel")
|
||||
if not content_label:
|
||||
content_label = get_node_or_null("VBoxContainer/ContentLabel")
|
||||
if not user_info_container:
|
||||
user_info_container = get_node_or_null("VBoxContainer/UserInfoContainer")
|
||||
|
||||
# 内容换行与自适应高度
|
||||
if content_label:
|
||||
content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
content_label.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
content_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
content_label.fit_content = true
|
||||
content_label.scroll_active = false
|
||||
|
||||
# ============================================================================
|
||||
# 成员变量
|
||||
@@ -89,10 +92,15 @@ var _is_self: bool = false
|
||||
# message.set_message("Alice", "Hello!", 1703500800.0, false)
|
||||
func set_message(from_user: String, content: String, timestamp: float, is_self: bool = false) -> void:
|
||||
_is_self = is_self
|
||||
_cache_node_refs()
|
||||
|
||||
var safe_from_user := from_user
|
||||
if safe_from_user.strip_edges().is_empty():
|
||||
safe_from_user = "我" if is_self else "玩家"
|
||||
|
||||
# 设置用户名(带空值检查)
|
||||
if username_label:
|
||||
username_label.text = from_user
|
||||
username_label.text = safe_from_user
|
||||
else:
|
||||
push_error("ChatMessage: username_label is null!")
|
||||
return
|
||||
@@ -100,7 +108,7 @@ func set_message(from_user: String, content: String, timestamp: float, is_self:
|
||||
# 设置内容
|
||||
if content_label:
|
||||
content_label.clear() # 清除默认文本和所有内容
|
||||
content_label.bbcode_text = content # 使用 bbcode_text 因为 bbcode_enabled = true
|
||||
content_label.append_text(content) # 作为纯文本追加,避免 BBCode 解析导致内容不显示
|
||||
else:
|
||||
push_error("ChatMessage: content_label is null!")
|
||||
return
|
||||
@@ -124,10 +132,6 @@ func _apply_style() -> void:
|
||||
if not username_label or not timestamp_label or not user_info_container:
|
||||
return
|
||||
|
||||
# 设置大小约束 - 宽度固定,高度适应内容
|
||||
if get_parent():
|
||||
custom_minimum_size.x = min(max_width, get_parent().size.x)
|
||||
|
||||
# 重要:设置垂直 size flags 让 Panel 适应内容高度
|
||||
size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
@@ -142,6 +146,8 @@ func _apply_style() -> void:
|
||||
# 设置文字颜色 - ID使用金色 #FFD700
|
||||
username_label.add_theme_color_override("font_color", Color(1.0, 0.8431373, 0.0))
|
||||
timestamp_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||||
if content_label:
|
||||
content_label.add_theme_color_override("default_color", Color(0.95, 0.97, 1.0))
|
||||
else:
|
||||
# 他人的消息:左侧对齐,灰色背景
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
@@ -153,11 +159,13 @@ func _apply_style() -> void:
|
||||
# 设置文字颜色 - ID使用蓝色 #69c0ff
|
||||
username_label.add_theme_color_override("font_color", Color(0.4117647, 0.7529412, 1.0))
|
||||
timestamp_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5))
|
||||
if content_label:
|
||||
content_label.add_theme_color_override("default_color", Color(0.15, 0.15, 0.15))
|
||||
|
||||
# 获取自己消息的样式
|
||||
func _get_self_style() -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.6, 1.0, 0.3)
|
||||
style.bg_color = Color(0.2, 0.6, 1.0, 0.8)
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 10
|
||||
@@ -171,7 +179,7 @@ func _get_self_style() -> StyleBoxFlat:
|
||||
# 获取他人消息的样式
|
||||
func _get_other_style() -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.9, 0.9, 0.9, 0.5)
|
||||
style.bg_color = Color(0.9, 0.9, 0.9, 0.8)
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 2
|
||||
|
||||
@@ -2,19 +2,15 @@
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/prefabs/ui/ChatMessage.gd" id="1"]
|
||||
|
||||
[node name="ChatMessage" type="Panel"]
|
||||
[node name="ChatMessage" type="PanelContainer"]
|
||||
layout_mode = 2
|
||||
offset_right = 400.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 6
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="UserInfoContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
|
||||
@@ -272,8 +272,12 @@ func _on_chat_message_received(data: Dictionary) -> void:
|
||||
var content: String = data.get("content", "")
|
||||
var timestamp: float = data.get("timestamp", 0.0)
|
||||
|
||||
var is_self: bool = bool(data.get("is_self", false))
|
||||
if not data.has("is_self") and not _current_username.is_empty() and from_user == _current_username:
|
||||
is_self = true
|
||||
|
||||
# 添加到消息历史
|
||||
add_message_to_history(from_user, content, timestamp, false)
|
||||
add_message_to_history(from_user, content, timestamp, is_self)
|
||||
|
||||
# 处理聊天错误
|
||||
func _on_chat_error(data: Dictionary) -> void:
|
||||
@@ -307,15 +311,23 @@ func add_message_to_history(from_user: String, content: String, timestamp: float
|
||||
if not _is_chat_visible:
|
||||
show_chat()
|
||||
|
||||
# 每条消息用一行容器包起来,方便左右对齐且不挤在一起
|
||||
var row := HBoxContainer.new()
|
||||
row.layout_mode = 2 # 让 VBoxContainer 接管布局,否则会重叠在同一位置
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
row.alignment = BoxContainer.ALIGNMENT_END if is_self else BoxContainer.ALIGNMENT_BEGIN
|
||||
|
||||
# 创建消息节点
|
||||
var message_node: ChatMessage = chat_message_scene.instantiate()
|
||||
|
||||
# 先加入场景树,再设置内容(避免 ChatMessage._ready 尚未执行导致节点引用为空)
|
||||
message_list.add_child(row)
|
||||
row.add_child(message_node)
|
||||
|
||||
# 设置消息内容
|
||||
message_node.set_message(from_user, content, timestamp, is_self)
|
||||
|
||||
# 添加到列表
|
||||
message_list.add_child(message_node)
|
||||
|
||||
# 自动滚动到底部
|
||||
call_deferred("_scroll_to_bottom")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user