# 客户端重构与 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% 正常展示并成功购买。
- 联机打怪,验证普通攻击连击节奏、打击僵直与伤害数字飘字时机与原版客户端完全吻合。