# chat_tail —— §8.8 头顶「聊天 / 信息」尾标 1:1:CPythonTextTail::RegisterChatTail / # RegisterInfoTail 的纯静态镜像(与 name_color.gd / text_tail.gd 同套路)。 # # REF 40250/ClientVS22/source/UserInterface/PythonTextTail.cpp # :16 c_TextTail_Chat_Color = D3DXCOLOR(1,1,1,1) —— 普通聊天气泡=白 # :17 c_TextTail_Info_Color = D3DXCOLOR(1, 0.785f, 0.785f, 1) —— 信息气泡=淡红 # :26 gs_TextTail_LivingTime = 5000 —— TEXTTAIL_LIVINGTIME_CONTROL # :28 TextTail_GetLivingTime() assert(gs_TextTail_LivingTime > 1000) # :34 TextTail_SetLivingTime(livingTime) # :581 RegisterChatTail(vid, szChat):m_ChatTailMap 已有该 vid → 改文本 + Color=Chat + # LivingTime = now + GetLivingTime() + bNameFlag=TRUE,直接 return; # 否则新建(高度 = 角色高 + 10),LivingTime = now + GetLivingTime(),bNameFlag=TRUE, # SetOutline(true),VERTICAL_ALIGN_BOTTOM,插入 m_ChatTailMap # :624 RegisterInfoTail(vid, szChat):同上,但 Color=Info、bNameFlag=FALSE # :328 UpdateAllTextTail:遍历 m_ChatTailMap,LivingTime < now 即 DeleteTextTail + erase # (每 vid 只有一条 chat/info 尾标,chat 与 info 共用同一张 m_ChatTailMap) # :74 UpdateAllTextTail:chat 尾标若 bNameFlag → ShowCharacterTextTail(vid)(名字也一起顶出来) # :105 UpdateShowingTextTail:chat 尾标若 bNameFlag → 名字尾标 y = chatTail.y - 17(名字挪到气泡下方) # :684 IsChatTextTail(vid):m_ChatTailMap.find(vid) != end # # REF 40250/ClientVS22/source/UserInterface/PythonNetworkStreamPhaseGame.cpp # :1301 CHAT_TYPE_COMMAND → ServerCommand,提前 return(不进尾标) # :1321 TALKING/PARTY/GUILD/SHOUT/WHISPER:line = strchr(buf,':') ? p + 2 : buf(去掉 "名字: " 前缀) # :1363 INFO/NOTICE/BIG_NOTICE/default:line = buf(原样) # :1372 if (CHAT_TYPE_SHOUT != kChat.type) RegisterChatTail(kChat.dwVID, line); # → 带 VID 的聊天里,除 SHOUT / COMMAND 外都进「聊天尾标」;RegisterInfoTail 仅 # game.py:930 / :936 技能 / 射击错误尾标显式调。 # # seam ⑨:参考端聊天尾标由 Render() 与名字尾标同屏排布(气泡在上、名字下移 17px,bNameFlag 决定 # 名字是否强制显示);本移植是 net_world 里名字 Label3D 与 "bubble" Label3D 各自 billboard 并存, # 名字不强制顶出、不随气泡下移——差异记入 §8.8。 extends RefCounted const CHAT_COLOR := Color(1.0, 1.0, 1.0) const INFO_COLOR := Color(1.0, 0.785, 0.785) const LIVING_TIME_DEFAULT := 5000 # ms(gs_TextTail_LivingTime) const LIVING_TIME_MIN := 1000 # assert 下界(TextTail_GetLivingTime) const HEIGHT_ADD := 10.0 # 角色高 + 10(RegisterChatTail 新建分支) # CHAT_TYPE_*(Packet.h:1410 enum EChatType) const CT_TALKING := 0 const CT_INFO := 1 const CT_NOTICE := 2 const CT_PARTY := 3 const CT_GUILD := 4 const CT_COMMAND := 5 const CT_SHOUT := 6 const CT_WHISPER := 7 const CT_BIG_NOTICE := 8 static var _living_time := LIVING_TIME_DEFAULT static func set_living_time(ms: int) -> void: # TextTail_SetLivingTime —— 参考端不在此断言(断言在 GetLivingTime) _living_time = ms static func living_time() -> int: # TextTail_GetLivingTime:assert(gs_TextTail_LivingTime > 1000) assert(_living_time > LIVING_TIME_MIN) return _living_time static func strip_name_prefix(raw: String) -> String: # strchr(buf, ':') ? p + 2 : buf —— 有冒号则取 ": " 之后正文,否则整串原样 var i := raw.find(":") return raw.substr(i + 2) if i != -1 else raw static func line_for_tail(chat_type: int, raw: String) -> String: # RecvChatPacket 的 switch:TALKING/PARTY/GUILD/SHOUT/WHISPER 去前缀;其余原样 match chat_type: CT_TALKING, CT_PARTY, CT_GUILD, CT_SHOUT, CT_WHISPER: return strip_name_prefix(raw) _: return raw static func wants_chat_tail(chat_type: int) -> bool: # RecvChatPacket:COMMAND 提前 return;SHOUT 显式排除;其余带 VID 聊天都进尾标 return chat_type != CT_SHOUT and chat_type != CT_COMMAND static func _register(tails: Dictionary, vid: int, text: String, now_ms: int, col: Color, name_flag: bool) -> Dictionary: var e := { "text": text, "color": col, "name_flag": name_flag, "living_time": now_ms + living_time(), } tails[vid] = e return e static func register_chat(tails: Dictionary, vid: int, text: String, now_ms: int) -> Dictionary: # CPythonTextTail::RegisterChatTail —— 白 + bNameFlag TRUE,已存在则原地替换 return _register(tails, vid, text, now_ms, CHAT_COLOR, true) static func register_info(tails: Dictionary, vid: int, text: String, now_ms: int) -> Dictionary: # CPythonTextTail::RegisterInfoTail —— 淡红 + bNameFlag FALSE,与 chat 共用同一张表 return _register(tails, vid, text, now_ms, INFO_COLOR, false) static func is_chat_text_tail(tails: Dictionary, vid: int) -> bool: # CPythonTextTail::IsChatTextTail return tails.has(vid) static func expire(tails: Dictionary, now_ms: int) -> Array: # UpdateAllTextTail:LivingTime < now 即删除;返回被清掉的 vid 列表 var dead: Array = [] for vid in tails.keys(): if int(tails[vid].get("living_time", 0)) < now_ms: dead.append(vid) for vid in dead: tails.erase(vid) return dead