# 客户端重构与 40250 逻辑对齐实施方案 ## 1. 现状诊断与核心痛点 ### 1.1 现状与优势 - **渲染层(已基本稳定)**: - `extension/src/metin2_model.{h,cpp}`:基于开源 libgr2 的模型网格解析、材质贴图加载、GPU/CPU 骨骼蒙皮。 - `extension/src/metin2_anim.{h,cpp}`:骨骼动画采样、双动画平滑融合(Crossfade)、挂点武器同步。 - `extension/src/metin2_world.{h,cpp}`:地形高度场采样、贴图混合、水体、静态建筑碰撞。 - `project/game_camera.gd`:轨道摄像机、防穿模逻辑。 - **结论**:渲染层已具备优秀的跨平台能力(macOS/iOS/Android/Linux/Web),**无需推倒,纯粹作为“哑终端”画面渲染器(View)予以保留**。 ### 1.2 逻辑层的混乱根源 当前各种 Bug(“WASD翻跟头”、“连击不流畅/手感怪异”、“NPC交互后商店空白”、“任务无法打开”)的共同根源在于: 1. **庞大的 GDScript 意大利面条代码**: - `net_play.gd` (~2200 行)、`game_scene.gd` (~1800 行)、`net_world.gd` (~1760 行) 试图用 GDScript 凭空重写 40250 的几万行 C++ 客户端逻辑。 2. **凭感觉实现的“伪逻辑”**: - 没有严格对照 40250 C++ 源码的状态机(如按键优先级、阻尼公式、连击输入窗、NPC点击距离分流、封包处理链),而是用自制计时器和启发式逻辑拼接,导致产生大量边界 Bug 和时序脱节。 3. **数据没有单一真实来源(Single Source of Truth)**: - 实体数据同时散落在 C++ `EntityStore`、`net_play.gd`、`net_world.gd`、`client.ents` 和 `node.meta` 中,互相通过数十个信号来回同步,极易丢状态或时序错乱。 --- ## 2. 1:1 复刻的边界与原则(复刻哪些,不复刻哪些) > [!IMPORTANT] > **核心原则**:**“形式上”不要 1:1 照搬类文件,但“内核上”必须 1:1 严格对齐封包、状态机与数学公式。** ### 2.1 坚决不要 1:1 复刻的部分(直接使用 Godot 原生) 40250 源码中超过 60% 的代码是在重复造 2004 年 DirectX 8 / Windows 的轮子,这些在现代跨平台环境中是沉重的技术包袱: | 40250 原版模块 | 为什么不要 1:1 复刻 | Godot 现成替代方案 | | :--- | :--- | :--- | | **`EterLib` 图形底层**
(`CGraphicDevice`, `CStateManager`, `CRenderTarget`) | 深度强绑定 DirectX 8/9,引入会导致无法在 macOS (Metal) 及移动端 (Vulkan) 编译。 | **Godot 渲染引擎**(原生支持 Metal / Vulkan / WebGL) | | **`EterPythonLib` 2D UI 系统**
(`CWindowManager`, `CWindow`, `CButton`, `CScrollBar`) | 20 年前手工编写的固定坐标 2D 贴图拼接系统,不支持高分屏自适应、矢量字体、触屏手势。 | **Godot Control 体系**(完善的锚点自适应、触屏支持) | | **`milesLib` 音频系统** | 2004 年的 Miles 闭源声卡驱动库。 | **Godot AudioServer / AudioStreamPlayer** | | **`CCamera` 摄像机类** | 自行维护的 D3D 视锥矩阵,死板且不灵活。 | **Godot `Camera3D`**(现有的 `game_camera.gd` 表现良好) | | **`EterGrnLib` 模型封装** | 依赖 Granny 2.8 闭源 Windows DLL。 | **现有 `metin2_model` + `metin2_anim`**(基于开源 libgr2,功能已完备) | | **内嵌 Python 2.7 解释器** | Python 2.7 早已停止维护,在 iOS / Android 上打包极其臃肿且受 App Store 审核限制。 | **C++ 纯逻辑导出给 GDScript 调用** | ### 2.2 必须 1:1 严格复刻的部分(下沉至 C++ GDExtension 核心逻辑层) 下面这些是 Metin2 的“灵魂与运行规则”,必须 100% 对齐 40250 源码,漏掉一个判断分支就会导致手感完全走样: 1. **网络协议与封包结构(1:1 严丝合缝)**: - 对应源码:`Packet.h` - 要求:每个 `TPacketGC*`(如 `TPacketGCShop`、`TPacketGCCharacterAdd`、`TPacketGCDamageInfo`)的结构体字段、字节对齐、Subheader 常量 100% 照搬。 2. **角色移动与物理状态机(1:1 算法复刻)**: - 对应源码:`PythonPlayerInput.cpp`、`InstanceBaseMovement.cpp`、`ActorInstanceMovement.cpp` - 要求:按键优先级判定(`NEW_GetMultiKeyDirRotation`)、转向角 45° 阻尼与防过冲单向贴齐(`GetRotatingDirection`、`SetAdvancingRotation`)、碰撞切线滑移(`AdjustDynamicCollisionMovement`)、位移累加(`__AccumulationMovement`)。 3. **战斗与打击判定状态机(1:1 状态机复刻)**: - 对应源码:`ActorInstanceBattle.cpp`、`InstanceBaseBattle.cpp` - 要求:连击段位状态转移(Combo 1→2→3→4)与按键输入时间窗(`ComboInputData`)、`.msa` 多命中时间窗(`HitDataContainer`)的毫秒级触发、普通受击方位判定与击倒击飞硬直。 4. **业务数据模型与操作上下文(1:1 逻辑复刻)**: - 对应源码:`PythonShop.cpp`(商店货架数据)、`PythonQuest.cpp`(任务状态树)、`PythonPlayerSkill.cpp`(技能冷却与释放条件三门)。 --- ## 3. 架构重构结论与策略 ### 3.1 架构重构的必要性 现有项目积累了大量技术债务,表面上看是“连击不连贯”、“WASD翻跟头”、“NPC打不开商店”等散发 Bug,深层原因是架构失衡: 1. **“上帝文件(God Class)”过载**:`net_play.gd` (2200+行) 与 `game_scene.gd` (1800+行) 把网络封包解码、角色状态、背包/商店业务、3D渲染节点管理、点击拾取、UI开关全部混在一起,牵一发而动全身。 2. **缺乏单一真实来源(No Single Source of Truth)**:角色坐标、朝向、连击段位在 `player_controller`、`net_play`、`net_world` 和 C++ `EntityStore` 中各自维护,靠数十个信号来回同步,极易时序混乱或丢状态。 3. **启发式“伪逻辑”**:没有遵循 40250 的严密数学公式与状态机,而是用散落的计时器和直觉代码修补。 ### 3.2 重构策略:拒绝推倒重来,采用“绞杀者式渐进解耦(Strangler Fig Pattern)” - **保留成果(绝不推倒)**:已稳定的 C++ GR2 渲染层(`metin2_model`、`metin2_anim`、`metin2_world`)和摄像机防穿模逻辑完全保留。 - **业务逐块下沉**:按“战斗 -> 商店/NPC -> 任务 -> 背包”的顺序,把上帝文件中的业务逐个抽成独立的 Manager/状态机,先解耦跑通,再下沉对齐 40250。 --- ## 4. 推荐的目标系统架构(`extension/src/m2_core/`) 在 C++ GDExtension 中建立清晰、模块化、无 DirectX 依赖的逻辑层: ``` extension/src/m2_core/ ├── protocol/ # 1:1 严格对齐 40250 的网络层 │ ├── packets.h # 1:1 复制 40250 Packet.h 的所有包结构体 │ └── packet_dispatcher.h # 1:1 复制 PythonNetworkStreamPhaseGame 的封包解析分发 │ ├── player/ # 1:1 复制 CPythonPlayer 的核心逻辑 │ ├── player_input.cpp # 1:1 移植 PythonPlayerInput.cpp (按键优先级、朝向合成、预约动作) │ ├── player_state.cpp # 1:1 移植 玩家属性、金币、背包、技能CD数据 │ └── player_combat.cpp # 1:1 移植 连击判定、攻击输入窗 │ ├── world/ # 1:1 复制 CPythonCharacterManager & CInstanceBase │ ├── instance_entity.cpp # 1:1 移植 实体状态机、45°阻尼移动、切线碰撞滑移 │ └── combat_resolver.cpp # 1:1 移植 命中范围、受击硬直计算 │ └── gameplay/ # 1:1 复制业务逻辑数据源 ├── shop_system.cpp # 1:1 移植 PythonShop.cpp (解决商店商品空白) └── quest_system.cpp # 1:1 移植 PythonQuest.cpp (解决NPC任务交互) ``` ### 运行时数据与渲染流向: ```mermaid flowchart TD subgraph View ["Godot 4 表现与输入层 (View & Input)"] InputHandler["Input / Touch / UI 事件捕获"] GodotNodes["PlayerView / MobView (Metin2Model + Metin2AnimPlayer)"] UI_Screens["ShopUI / QuestUI / InventoryUI / CharacterUI"] end subgraph LogicCore ["GDExtension 40250 纯逻辑核心 (Core Logic)"] CPP_Input["PlayerInput (按键优先级/朝向合成/输入队列)"] CPP_Move["InstanceEntity (45°阻尼/切线滑动/累积位移)"] CPP_Combat["Combat & Combo (命中窗/连击状态机)"] CPP_Net["NetworkStream & Dispatcher (收发严丝合缝对齐 40250)"] CPP_Data["Shop & Quest Data (货架缓存/任务对话树)"] end subgraph Server ["40250 游戏服务端"] GameServer["TCP Server"] end InputHandler -->|按键/点击事件| CPP_Input CPP_Input --> CPP_Move CPP_Move -->|状态包| CPP_Net CPP_Combat <--> CPP_Net CPP_Net <-->|TCP 封包| GameServer CPP_Move -.->|输出权威坐标 (x,y,z) 与朝向 yaw| GodotNodes CPP_Combat -.->|输出当前动作名、时间与打击帧| GodotNodes CPP_Data -.->|响应数据变动通知| UI_Screens ``` --- ## 5. 移动端(iOS / Android)操作界面与跨端支持设计 > [!NOTE] > **结论**:**当前规划的解耦架构天然、完美支持后续无缝接入移动端专属操作界面与触控系统。** ### 5.1 核心解耦机制:输入意图抽象层(Input Intent Abstraction) 重构后的架构将**“硬件输入采集”**与**“游戏角色行为”**彻底解耦。底层逻辑不再监听 `KEY_W`、`KEY_SPACE` 或鼠标点击,而是消费高阶**输入意图(Input Intents)**: ```mermaid flowchart TD subgraph InputSources ["输入源采集(多端隔离)"] PC_Input["PC 桌面端输入
(WASD / 鼠标左右键 / 快捷键 1~4 / 空格)"] Mobile_Input["移动端触控输入
(虚拟摇杆 / 技能轮盘 / 普攻大按钮 / 目标切换)"] end subgraph IntentLayer ["输入意图抽象接口 (PlayerInputIntent)"] I_Move["move_intent(dir: Vector2, is_run: bool)"] I_Attack["attack_intent()"] I_Skill["skill_intent(slot_idx: int)"] I_Target["target_intent(vid: int)"] I_Interact["interact_intent(vid: int)"] end subgraph CoreLogic ["40250 核心状态机 (跨平台纯逻辑)"] M2_Move["45° 转向阻尼 / 切线滑移碰撞"] M2_Combat["连击输入时间窗 / 多段命中判定"] M2_Target["500cm 距离门限 / 预约走向交互"] end PC_Input -->|转换映射| IntentLayer Mobile_Input -->|转换映射| IntentLayer IntentLayer --> CoreLogic ``` 1. **移动控制无缝适配**: - PC 端:WASD 键位经过 40250 `NEW_GetMultiKeyDirRotation` 算成方向向量。 - 移动端:屏幕左侧的**虚拟摇杆(Virtual Joystick)**直接输出方向向量给 `move_intent`,底层 45° 转向阻尼与碰撞滑移算法无需修改一行代码。 2. **战斗与技能轮盘**: - 移动端右侧可布局经典“大按键普攻 + 环形技能轮盘”,普攻按钮每次点击触发 `attack_intent()`,底层直接进入 40250 的 `ComboInputData` 输入缓冲队列。 ### 5.2 双端自适应 UI 表现层(Dual-Mode Adaptive UI) 由于业务数据(`ShopManager`、`InventoryManager`、`QuestManager`)已与界面彻底解耦: - **数据核心统一**:所有数据模型只负责维护商品列表、道具属性、背包格子、对话选项,通过信号/事件对外广播。 - **UI 视图按端加载**: - PC 端:加载 `DesktopHUD.tscn`(支持鼠标 Hover 查看装备 Tooltip、右键穿戴、拖拽格子)。 - 移动端:根据 `OS.has_feature("mobile")` 或屏幕分辨率动态加载 `MobileHUD.tscn`(更大的触控热区、点击弹出操作抽屉 Drawer、全屏大卡片商店展示)。 - **完全零冗余**:两套 UI 共享 100% 的底层数据逻辑、网络协议与状态机。 --- ## 6. 分阶段实施路线图 按照“**先主要解决战斗系统不一致,再解决 NPC/商店/任务交互混乱**”的优先级分步推进: ### 阶段一:战斗与连击系统精准对齐(优先级:最高) > 对应 40250 源码:`ActorInstanceBattle.cpp`、`InstanceBaseBattle.cpp`、`formats/msa.h` 1. **普通攻击与连击链(Combo Chain)1:1 复刻**: - 移植 40250 `ActorInstanceBattle.cpp:157-308` 状态机: - `m_dwcurComboIndex`(连击段数 1→2→3→4) - `ComboInputData`:严格按照 `.msa` 的 `InputStartTime`、`InputEndTime`、`NextComboTime` 判定是否允许按下一次普攻。 ### 阶段一:移动与战斗核心状态机 40250 对齐(优先级:最高)✅ [COMPLETED] > 对应 40250 源码:`PythonPlayerInput.cpp`、`ActorInstanceBattle.cpp`、`InstanceBaseBattle.cpp` 1. **废弃原有人为的外层普攻 CD 拦截**: - 彻底废除 `_attack_cd` 吞键逻辑,完全对齐 40250 普攻/连击内禀门控机制。 2. **严格 1:1 接入 `.msa` 的连击输入数据窗**: - 读取并严格应用 `duration`、`InputStartTime`、`NextComboTime`、`InputEndTime`。 - 完美支持长按空格连续 4 击(1→2→3→4 段无缝切换与回 Wait 重置)与快速连点。 3. **受击与击倒对齐**: - 动作播放时长内动态释放受击锁,触发屏幕震颤与击退同步。 ### 阶段二:NPC 交互与商店系统修复(优先级:高)✅ [COMPLETED] > 对应 40250 源码:`PythonPlayerInputMouse.cpp`、`PythonNetworkStreamPhaseGame.cpp`、`PythonShop.cpp`、`uishop.py` 1. **NPC 点击与预约走向交互(__OnClickActor)**: - 点击 NPC:距离 `<= 500 cm`(`CLICK_DIST_NPC`)立即停步、面朝 NPC 发送 `HEADER_CG_ON_CLICK` (26)。 - 距离 `> 500 cm`:进入 `m_eReservedMode = CLICK_ACTOR` 预约模式,驱动角色寻路靠近,进入 500cm 瞬间停步交互。 - 加入 40250 `CPythonPlayer::__SendClickActorPacket` 的 1000ms 狂点节流保护。 2. **修复商店数据流与属性丢失(彻底解决白板商品 Bug)**: - C++ `mtnet::ShopEntry` 补齐 `sockets[3]` 与 `attrs[7]`,解析 `START` / `START_EX` / `UPDATE_ITEM` 全量属性。 - `m2_client` 修复切页被冲掉问题,仅在初次打开触发 `shop_opened`,格位更新触发 `shop_updated(pos)`。 - UI 接入 40-slot 货架、多 Tab 切换(`tabIdx * 40 + slot`)、右键直接秒买(`UnselectItemSlot`)、超出 10m 离开自动关闭窗口。 ### 阶段三:任务系统与对话流对齐(优先级:中)✅ [COMPLETED] > 对应 40250 源码:`PythonEventManager.h/cpp`、`PythonQuest.cpp`、`PythonNetworkStreamPhaseGame.cpp`(`RecvScriptPacket`)、`uiquest.py` 1. **40250 任务与 NPC 交互协议全套对齐**: - 封包层精准对齐:`HEADER_GC_SCRIPT` (45)、`HEADER_CG_SCRIPT_ANSWER` (29)、`HEADER_CG_SCRIPT_BUTTON` (66)、`HEADER_CG_QUEST_INPUT_STRING` (30)、`HEADER_GC_QUEST_CONFIRM` (46)、`HEADER_CG_QUEST_CONFIRM` (31)。 - 在 C++ 网络会话层 (`ClassicSession`) 与 `M2Client` 实现全套安全发包接口:`send_script_answer`、`send_script_button`、`send_quest_input`、`send_quest_confirm`、`send_quest_cancel`。 2. **40250 `CPythonEventManager` 脚本语法与表现层 1:1 解析**: - 支持标准 40250 分号/管道符问题格式 `[QUESTION 1;选项一|2;选项二|...]`,0-indexed 答案映射,保留向后兼容旧格式。 - 实现 40250 `uiquest.py` 8 选项多页分页机制(`MAX_CHOICES_PER_PAGE = 8`),自动挂载 `[上一页]` / `[下一页]` 导航行,精确计算全局索引 `page * 8 + slot`。 - 皮肤与立绘对齐:`SKIN_NOWINDOW` (1) 与 `SKIN_CINEMA` (5) 隐藏背景板;`LEFTIMAGE` 自动偏移文本与按钮内容区宽度。 - 支持 `[INPUT]` 文本输入框即时提交、`[NEXT]` 剧情推进与 `[DONE]` 流程收尾(`script_answer(254)`),以及 ESC 键安全取消(`OnCancel`)。 - 编写 `test_quest_dialog_parity.gd` 覆盖 9 大测试场景 45 项断言全部 100% 通过。 ### 阶段四:角色属性面板、背包与技能系统对齐(优先级:中)✅ [COMPLETED] > 对应 40250 源码:`PythonPlayerSkill.cpp`、`PythonSkill.h`、`PythonItem.cpp`、`GameType.h`、`Packet.h`、`uicharacter.py` 1. **主动技能释放链与三层完整门控(40250 CheckSkillUsable 对齐)**: - 严格 1:1 实现死亡状态(`CANNOT_ACT`)、安全区攻击拦截(`IN_SAFE`,增益 Buff 技能放行)、武器类型匹配(`NOT_MATCHABLE_WEAPON`)、弓箭数量检验(`EMPTY_ARROW`)、SP/HP 消耗门检(`NOT_ENOUGH_SP`)、冷却时间阻断(`WAIT_COOLTIME`)以及施法距离/目标判定(超出距离进入 `RESERVED` 预约模式,进入范围后放行)。 2. **装备与背包双端线协议对齐(40250 ItemPos 对齐)**: - `_to_wire` 严格映射 40250 `TItemPos`:背包槽位 `0..89` 映射 `WINDOW_INVENTORY (1)`;装备槽位 `90..100` 映射 `WINDOW_EQUIPMENT (2)` 且内部 cell 严格从 `0` 开始偏移;时装槽位 `109..113` 映射 `WINDOW_INVENTORY (1)`。 - 右键穿脱发送 `HEADER_CG_ITEM_USE` (11),拖拽移动发送 `HEADER_CG_ITEM_MOVE` (12),并严格走 `EquipRules` 职业、等级(`LIMIT_LEVEL`)与性别门控。 3. **角色属性面板实时换算与指令对齐(uicharacter.py:RefreshStatus 对齐)**: - 物攻格式严格呈现 `min-max`(或相等时单值显示)、物防 `DEF_GRADE + DEF_BONUS`、魔攻 `MAG_ATT + MAGIC_WEP` 纯正公式。 - 加点与洗点协议通过安全网络通道发送原生 `/stat ht`、`/stat iq`、`/stat st`、`/stat dx` 与 `/stat-` 指令。 4. **快捷栏(Quickbar)持久化与全量同步(40250 TQuickSlot 协议)**: - 快捷栏配置新增发送 `HEADER_CG_QUICKSLOT_ADD` (16)(支持技能、物品、动作表情)、交换发送 `HEADER_CG_QUICKSLOT_SWAP` (18)、清空发送 `HEADER_CG_QUICKSLOT_DEL` (17),并支持全量 36 格状态本地与服务端双向恢复。 - 编写 `test_skill_inventory_parity.gd` 覆盖 4 大领域 42 项测试断言全部 100% 通过。 --- ## 7. 技术实施规范 1. **严禁“自造算法”**: - 任何涉及移动、旋转、连击、受击、技能、网络包逻辑的代码,必须明确注明对应的 40250 源码文件和行号(例如 `// 对齐 40250 ActorInstanceBattle.cpp:215`),1:1 复刻其状态转移条件。 2. **职责边界分明**: - **C++ (GDExtension)**:拥有所有数据、状态机、网络封包、物理数学计算。 - **GDScript / Godot Node**:只做两件事:① 把 C++ 的位置和动作画在屏幕上;② 把屏幕上的按键和点击事件传给 C++。 3. **严格单向数据流**: - 废除 GDScript 层各节点之间混乱的信号交叉监听,所有状态统一归拢到核心状态机。 --- ## 8. 验证计划 1. **单元测试与 Headless 自动化测试**: - 为连击状态机编写类似 `test_wasd_steering_parity.gd` 的无头自动化回归测试。 - 模拟封包注入测试商店数据解析与 NPC 交互流程。 2. **实服联机验证**: - 配合真实 40250 服务端,联机点击杂货商/铁匠,验证商品列表 100% 正常展示并成功购买。 - 联机打怪,验证普通攻击连击节奏、打击僵直与伤害数字飘字时机与原版客户端完全吻合。