- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
130 lines
18 KiB
Markdown
130 lines
18 KiB
Markdown
# social.chat_text_tail
|
||
|
||
## Scope
|
||
|
||
审计公聊、队伍/公会/喊话频道、无 VID 系统消息、私聊收发和私聊窗口、忽略名单、聊天气泡、名字/公会/称号/等级尾标以及尾标的寿命、排布和死亡清理。对比重点是当前端是否复现 40250 的消息类型分支、服务器事件来源、私聊状态机、文本颜色/过滤/表情副作用、尾标覆盖规则和渲染布局;本地有同名 helper 或能显示文本不等于实现统一。
|
||
|
||
## Reference call chain
|
||
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
|
||
- `RecvChatPacket` 校验聊天类型;`CHAT_TYPE_COMMAND` 进入 `ServerCommand`,不进入普通聊天和聊天尾标。
|
||
- 有 VID 时,普通/队伍/公会/喊话/客户端私聊从 `"名字: "` 后取正文,执行表情解析、跨帝国文字转换和辱骂过滤;角色不存在时直接忽略;除 `CHAT_TYPE_SHOUT` 外注册 `CPythonTextTail::RegisterChatTail`,PC 消息再进入 `CPythonChat::AppendChat`。
|
||
- 无 VID 时,NOTICE/BIG_NOTICE 分别进入 `BINARY_SetTipMessage`/`BINARY_SetBigMessage`,系统和喊话按专门分支处理,不是简单追加到同一聊天列表。
|
||
- `RecvWhisperPacket` 将 mode、发送者和文本交给 `OnRecvWhisper`/错误/系统回调;私聊历史、按钮、闪烁和忽略名单由 `CPythonChat`、`uiwhisper.py` 和 `game.py` 联动。
|
||
- `UserInterface/PythonNetworkStreamCommand.cpp`
|
||
- `ServerCommand` 作为 `CHAT_TYPE_COMMAND` 的二次命令总线,解析 cube、observer、StoneDetect、stamina、mobile、gift、情感动作和 `dig_motion`,并把副作用交给 UI、角色或效果管理器;命令不进入普通聊天尾标。
|
||
- `UserInterface/PythonChat.cpp` / `PythonChat.h`
|
||
- 普通聊天维护共享聊天行队列和多个 chat set,支持 view/edit/log 三种布局、滚动步长、最大行数、延迟追加、按频道颜色和清理。
|
||
- 40250 颜色是 talking 白、info 淡红、notice/big notice 淡黄、party 青白、guild 淡紫、command/shout 青绿、whisper 绿色;私聊 mode 包含 chat、not-exist、target-blocked、sender-blocked、error、GM 和 system。
|
||
- `AppendWhisper` 按目标名维护独立会话;`IgnoreCharacter`/`IsIgnoreCharacter` 只对目标名切换黑名单;`InitWhisper`/`MakeWhisperButton` 恢复已有会话。
|
||
- `UserInterface/PythonTextTail.cpp` / `InstanceBaseEffect.cpp`
|
||
- `RegisterChatTail` 和 `RegisterInfoTail` 共用每 VID 一条尾标;聊天尾标为白色、`bNameFlag=true`,信息尾标为淡红、`bNameFlag=false`,新消息覆盖旧消息并重置 `now + 5000ms`。
|
||
- 聊天气泡会强制显示角色名字,并把名字尾标移动到气泡下方 17px;公会名、称号、等级按实际字体度量排在名字两侧/上下;角色死亡时 `DetachTextTail`。
|
||
- 公会名只在 guild id 非零时创建,名称查询失败回退 `Noname`;称号/等级受 `EnablePKTitle`、alignment grade、level 和挂马高度影响。
|
||
- `Client/Eternexus/root/game.py` / `uiwhisper.py`
|
||
- `OnRecvWhisper` 按官方 whisper mode 分流到 `AppendWhisper`、系统错误文本和界面 `RecvWhisper` 通知;聊天输入、好友/目标板和独立私聊窗口都能打开同一目标会话。
|
||
- 私聊窗口发送只有网络请求成功后才应关闭/追加本地回显;忽略按钮通过 `/ignore <name>` 与服务端/客户端状态同步。
|
||
|
||
## Current call chain
|
||
|
||
- `EntityStore::mut_chat`、`mut_whisper` 和 parser 已保存 `GC_CHAT`/`GC_WHISPER` 为 chat queue;`M2Client` 发出 `chat`、`whisper_received` 信号,基础 classic 动态包布局和长度限制存在;`GC_WHISPER` 缺少固定 type/name 字段时会拒绝,不再静默吞包。
|
||
- `project/ui/chat_ui.gd` 只有四个本地 tab(全部/私聊/系统/战斗),按 Godot `RichTextLabel` 重建整段文本;公共输入支持普通、公会、队伍、喊话和 `/w`,但普通/喊话发送失败没有统一错误回显,私聊窗口也无视 `client.whisper()` 返回值而立即本地回显并关闭。
|
||
- 当前入站私聊 `_on_whisper` 已按 40250 区分 `0=CHAT`、`1=NOT_EXIST`、`2=TARGET_BLOCKED`、`3=SENDER_BLOCKED`、`4=ERROR`、`5=GM`、`0xFF=SYSTEM`;1..4 的空文本会生成本地化错误兜底文案。live 会话已接入,颜色/字体和服务端错误正文仍未完整闭环。
|
||
- `project/whisper_chat_system.gd` 实现了独立的本地 conversation/最小化/闪烁/忽略 helper,`ChatUI.setup` 现在把它接入 `M2Client.whisper_received`;live 私聊仍没有完整的独立窗口历史渲染、任务栏按钮、ignore 命令和发送结果状态。
|
||
- `project/chat_tail.gd` 已复现单 VID 覆盖、5000ms 生命周期、聊天/信息颜色和名字跟随规则;`project/net_world.gd` 用 `Label3D` 气泡、名字和子标签实现,死亡/过期会清理节点。
|
||
- `project/text_tail.gd`、`text_tail_arrange.gd`、`name_tail_layout.gd` 已实现部分颜色、称号、等级、公会名、挂马高度和像素近似排布;但运行时仍是 Godot billboard/估算字符宽度,不是 40250 的 `CGraphicText` 实际字体度量和 `CPythonTextTail` 渲染对象。
|
||
- `ClassicParser::on_gc(GC_CHAT)` 现按 40250 先拒绝非法 chat type,并在非系统消息的 VID 不存在于 `EntityStore` 时消费但不产生 chat event;live `project/net_world.gd::_on_chat` 仍没有复现表情解析、跨帝国转换、辱骂过滤和无 VID NOTICE/BIG_NOTICE 专用 UI。
|
||
|
||
## Branch matrix
|
||
|
||
| 场景 | 40250 | 当前端 | 结论 |
|
||
|---|---|---|---|
|
||
| 普通/队伍/公会聊天 | 解析类型、角色和前缀,过滤/表情后写聊天行,必要时注册气泡 | 基础 `GC_CHAT` queue、UI tab 和气泡存在 | PARTIAL:过滤/表情/未知实体/真实显示布局缺失 |
|
||
| 喊话 | 公共聊天颜色和公共区显示,不能注册角色聊天尾标 | 当前写入公共 tab;`ChatTail` 也能保留,但 net_world 连接路径未证明按 ref 排除 | PARTIAL |
|
||
| 无 VID INFO/NOTICE/BIG_NOTICE | 专用 tip/big notice 分支,按类型显示 | 统一塞入 System/All `RichTextLabel`,没有专用 tip/big notice | GAP |
|
||
| COMMAND | 进入 `ServerCommand`,不显示普通聊天/气泡 | native 有部分 command 解析;非已识别 command 仍缺完整 side effect 矩阵 | PARTIAL |
|
||
| 表情/帝国文字/辱骂过滤 | 先解析表情或转换/过滤,再显示文本/动画 | 有发送表情 token,但没有入站 `ParseEmoticon`、帝国转换和辱骂过滤链 | GAP |
|
||
| 私聊正常收发 | mode/name/text 进入独立目标会话,按钮/历史/通知联动 | packet queue、独立输入和 live conversation 接入存在;窗口历史/按钮/通知仍不完整 | PARTIAL |
|
||
| 私聊错误/GM/system mode | 0..5/0xFF 具有不同错误/字体/颜色和通知 | mode 数值与错误/GM/system 分支已对齐;颜色、字体和 live 通知仍不等价 | PARTIAL |
|
||
| 忽略目标 | `IgnoreCharacter` 按名称切换,收到私聊不进入会话并有服务端 ignore 命令 | live ChatUI 收到普通/GM 私聊前经过会话 ignore 判断;仍无 UI 忽略按钮/服务端命令连接 | PARTIAL |
|
||
| 聊天气泡 | 每 VID 单槽覆盖,白/淡红,5000ms,聊天气泡强显名字并上移名字 17px | 单槽、颜色、生命周期和本地布局 helper 存在 | PARTIAL:实际字体/屏幕投影/已知实体和资源表现不同 |
|
||
| 名字/称号/等级/公会名 | 由角色同步、alignment/guild/item/挂马更新,死亡 detach,真实字体度量排布 | 快照+Label3D 子标签和近似字符宽度存在 | PARTIAL:权威更新源和渲染度量不完全同链 |
|
||
| 换图/重连/死亡/重复消息 | CPythonChat/TextTail 清理与重新注册,旧会话状态按客户端生命周期处理 | 本地节点过期/死亡清理存在;chat queue、私聊会话和重连恢复未端到端验证 | PARTIAL |
|
||
|
||
## Implementation-equivalence matrix
|
||
|
||
| 项目 | 状态 | 证据/限制 |
|
||
|---|---|---|
|
||
| Preconditions | PARTIAL | native 长度/阶段边界存在;缺入站 actor/type/name、ignore、输入门控和真实 UI 状态前置条件 |
|
||
| Branch structure | PARTIAL | 普通/私聊/尾标主干和 live ignore 分支存在,但无 VID notice、表情/过滤和完整私聊通知分支 |
|
||
| Algorithms/formulas | PARTIAL | 5000ms 生命周期和 17px 名字偏移有静态镜像;频道颜色、前缀、字符宽度、会话/通知算法存在明确差异 |
|
||
| State transition order | GAP | 参考是网络回包→过滤/表情/尾标→聊天/窗口/提示;当前 conversation 已随 live signal 更新,但本地回显/窗口通知和服务端结果顺序仍不同 |
|
||
| Constants/units | PARTIAL | EChatType 和尾标 5000ms/17px/颜色部分存在;多个公共/私聊颜色与 40250 不同,字体宽度是 7px 估算 |
|
||
| Timing/event sources | PARTIAL | 气泡过期和 UI 信号存在;延迟聊天、真实服务器命令、重连/后台通知和私聊闪烁未接通 |
|
||
| Resource/data sources | GAP | 40250 使用角色/公会/本地化/字体资源及服务器消息;当前标题本地化可注入但 chat 文本/颜色/宽度多为脚本常量和 Godot 默认字体 |
|
||
| Protocol side effects | PARTIAL | classic CG_CHAT/CG_WHISPER 与 GC queue 有;入站类型副作用、SendSequence/错误文案、ignore 命令和 notice/big notice UI 未完整复现 |
|
||
| Interruption/failure/cleanup | GAP | 私聊发送失败仍本地显示并关闭;会话/忽略/聊天行重连清理、未知 VID、重复/乱序回包和专用提示清理缺端到端证据 |
|
||
|
||
## Regression evidence
|
||
|
||
- `project/chat_test.gd`:公共/私聊输入路由、拾取/钓鱼消息、气泡和名字/子标签排布通过;使用 fake client,未验证真实 `GC_CHAT` 分支和发送失败。
|
||
- `project/chat_tail_test.gd`:`RegisterChatTail`/`RegisterInfoTail`、单 VID 覆盖和 5000ms 生命周期通过。
|
||
- `project/text_tail_test.gd`、`project/text_tail_arrange_test.gd`、`project/name_tail_layout_test.gd`、`project/guild_mark_tail_test.gd`:静态颜色、称号/等级、公会名、排列和 guild mark 通过;未验证真实字体资源和 C++ 尾标对象。
|
||
- `project/test_whisper_chat_parity.gd`:26/26 通过,覆盖会话、闪烁、忽略和历史;`project/chat_test.gd` 另验证 live ChatUI 的普通/GM 入站信号进入同一 conversation。
|
||
- `project/chat_mobile_ui_test.gd`、`project/test_chat_hyperlink_dice_parity.gd`:移动聊天窗口、私聊输入和额外链接/dice helper 通过;不能证明 40250 的入站消息分支。
|
||
- `build/extension/net_entity_test`、`build/extension/net_classic_session_test`:GC_CHAT/GC_WHISPER 解析和发送包基础通过;session 回归覆盖未知 VID、非法 type、已知 VID 的 chat event 矩阵;`project/chat_test.gd` 另覆盖 whisper mode 1/5/0xFF 的错误、GM、system 显示映射,但仍没有完整 0..5/0xFF live 回调、失败回显和重连测试。
|
||
|
||
## Deep audit round 2026-09-21
|
||
|
||
### Fresh regression results
|
||
|
||
- `godot --headless --path project --script chat_test.gd`、`chat_tail_test.gd`、`text_tail_test.gd`、`text_tail_arrange_test.gd`:均退出码 0;公共/私聊路由、气泡覆盖、5000ms 生命周期、颜色和尾标排布通过,但使用的是脚本/fake client 与近似字体度量。
|
||
- `godot --headless --path project --script name_tail_layout_test.gd`、`guild_mark_tail_test.gd`:均退出码 0;名字/公会标记和尾标布局 helper 通过。
|
||
- `godot --headless --path project --script test_whisper_chat_parity.gd`:退出码 0,26/26 通过;覆盖独立的 `WhisperChatSystem` 会话、闪烁、忽略和历史;`chat_test.gd` 已补 live `ChatUI` 入站网络状态验证。
|
||
- `godot --headless --path project --script chat_mobile_ui_test.gd`、`test_chat_hyperlink_dice_parity.gd`:均退出码 0;移动 UI、链接/骰子 helper 通过。
|
||
- `build/extension/net_entity_test` 与 `build/extension/net_classic_session_test`:均退出码 0;GC_CHAT/GC_WHISPER 基础解析和发送包通过,未覆盖完整消息分支矩阵。
|
||
|
||
### Static parity findings
|
||
|
||
- 40250 `RecvWhisperPacket` 的 mode 是 `0=CHAT`、`1=NOT_EXIST`、`2=TARGET_BLOCKED`、`3=SENDER_BLOCKED`、`4=ERROR`、`5=GM`、`0xFF=SYSTEM`;当前 `ChatUI::_on_whisper` 把 `1` 显示为系统、`2` 显示为 GM,且其余错误/屏蔽分支没有对应 UI 语义。
|
||
- 当前 `ChatUI` 在 `client.whisper()` 返回前就本地追加回显并关闭私聊窗口;网络发送失败时仍显示成功,违背 40250 的请求结果驱动状态转换。
|
||
- `WhisperChatSystem` 已有独立会话/忽略/闪烁实现,live `ChatUI` 现在把普通/GM `M2Client.whisper_received` 接入该状态源;独立窗口渲染、任务栏按钮和服务端 ignore 命令仍未闭环。
|
||
- 40250 `RecvChatPacket` 对未知 VID 的普通消息直接忽略,并处理表情、跨帝国转换、辱骂过滤以及无 VID 的 NOTICE/BIG_NOTICE 专用提示;当前 `_on_chat` 对未知 VID 仍能显示原文,NOTICE/BIG_NOTICE 统一进入 RichTextLabel,未接专用提示和过滤链。
|
||
- 40250 聊天气泡/尾标依赖 `CGraphicText` 实际字体、角色/公会/称号/等级权威同步并在死亡时 detach;当前 Label3D/脚本近似布局虽有局部测试,但没有真实字体资源、网络更新和重连/死亡/重复消息端到端证据。
|
||
|
||
### Active command-bus source review
|
||
|
||
- 本轮逐项核对 `PythonNetworkStreamCommand.cpp` 与当前 `EntityStore::apply_server_command`→`M2Client::poll` 信号链:当前端已经覆盖 `cube`、observer、StoneDetect、stamina、mobile、gift、combo、block mode、lover、password/refine/private-shop 以及 emotion/dig 的主要事件枚举,`net_entity_test` 对其中一组事件通过。
|
||
- 仍存在参考语义差异:40250 未识别/参数数量错误命令会走日志或特定 UI 回调;当前端对命令的统一 token/参数校验、效果资源创建、所有 emotion/observer/gift 回调和命令失败清理没有完整矩阵。`ClientCommand()` 在参考版本本身返回 false,不能把当前端已有 server-command helper 测试解释为完整 GM/命令 parity。
|
||
|
||
### Round conclusion
|
||
|
||
本轮仍为 `PARTIAL`:已按 40250 收口 native `GC_CHAT` 的非法类型/未知 VID 丢弃分支,修正 ChatUI 的 whisper mode 映射与空错误文案兜底,并把 live 普通/GM whisper 接入独立会话和 ignore 判断;窗口通知、NOTICE 分支、表情/过滤/跨帝国文字和真实尾标渲染仍未与 40250 统一。
|
||
|
||
### Active EterLib text-render review
|
||
|
||
- `EterLib/GrpText.cpp`/`GrpTextInstance.cpp`/`TextBar.cpp`/`TextTag.cpp` 以及 `GrpFontTexture.cpp`、`GrpMarkInstance.cpp` 共同负责真实字体资源、字宽/行数/字符位置、光标/描边/颜色、标记和池化对象生命周期;这正是 40250 聊天气泡、名字/公会/称号尾标的渲染基础。
|
||
- 当前 `Label3D`、`RichTextLabel` 和脚本宽度估算已覆盖局部可见结果,但没有复用同等字体纹理度量、字符定位、mark 生命周期和资源失败分支。已有布局测试使用 helper/fake client,不能替代真实字体资源与网络角色更新。
|
||
- 因此本轮完成上述 EterLib 文本核心静态核对;尾标的单槽/5000ms/17px 行为仍有镜像证据,但渲染资源和清理链不一致,合同继续为 `PARTIAL`。
|
||
|
||
## Active UserInterface chat and text-tail review
|
||
|
||
- `InsultChecker.cpp/h` 维护可清空/追加的禁词集合,按 locale-aware case-insensitive 比较处理多字节字符;`FilterInsult` 用 `*` 替换完整词,`IsInsultIn` 只报告命中,不能用 ASCII `find` 近似替代。
|
||
- `PythonChatModule.cpp` 暴露 chat set 的创建、位置/高度/步长/模式、延迟消息、忽略角色、whisper 会话、whisper 清理和 item hyperlink 解析;hyperlink 通过 `CItemManager` 查 item 数据,不是只显示原字符串。
|
||
- `PythonTextTailModule.cpp` 的生产顺序是 `UpdateAllTextTail`、`UpdateShowingTextTail`、`RegisterCharacterTextTail`、`RegisterChatTail`/`RegisterInfoTail`、标题/拾取/排列和投影位置查询;`Pick`、隐藏/显示和角色删除清理是同一状态机的一部分。
|
||
- 当前端聊天/尾标 helper 已有局部测试,live ChatUI 已将普通/GM whisper 接入独立会话和 ignore 判断;仍未形成 locale 禁词、多字节边界、chat-set 排列、item hyperlink、未知/删除 VID、投影失败和真实字体资源的同一调用链,已有 Godot 宽度估算和本地文本状态不能证明 40250 等价,合同保持 `PARTIAL`。
|
||
|
||
## Implementation fix round 2026-09-22
|
||
|
||
- `project/ui/chat_ui.gd` 已按 `PythonChat.h`/`game.py` 修正 whisper mode:`1..4` 作为错误系统消息,`5` 作为 GM,`0xFF` 作为 system;空错误正文使用本地化兜底文案。
|
||
- `extension/src/net/classic/classic_parser.cpp` 已按 `RecvWhisperPacket` 的固定头前置条件,将缺少 type/name 的 `GC_WHISPER` body 从静默成功改为失败;`net_classic_session_test` 增加对应直接 parser 回归。
|
||
- `project/chat_test.gd`、`project/test_whisper_chat_parity.gd`、`project/chat_mobile_ui_test.gd`、`project/test_chat_hyperlink_dice_parity.gd` 与 native session 回归通过;live conversation/ignore 接入已有证据,完整通知/颜色、忽略命令和公共聊天副作用仍未闭环。
|
||
|
||
## Static review evidence
|
||
|
||
已将 `InsultChecker.cpp/h`、`PythonChatModule.cpp`、`PythonTextTailModule.cpp` 纳入 active source 证据;这些文件对应的过滤、chat-set、whisper、hyperlink、尾标更新和拾取分支仍记录为实现差异或未验证分支。
|
||
|
||
## Status
|
||
|
||
`PARTIAL`。当前端已有聊天协议骨架、公共输入、私聊基础发送、聊天气泡和名字尾标的可测试镜像,但并非与 40250 统一实现:live 私聊已接入独立会话,但窗口历史/任务栏通知/服务端 ignore 命令仍未闭环;公共颜色/notice 分支不一致、入站表情/过滤/跨帝国文本缺失,且尾标渲染仍使用 Godot billboard 与宽度估算。本轮已修正 whisper mode 映射、空错误文案、截断 whisper 拒绝逻辑并接通 live conversation/ignore 判断,其他差异继续保持 PARTIAL。
|