Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
phases, EntityStore world model, ~all GC/CG headers. char create/delete,
private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
token), system-option + game-option + ESC system menu, private-shop 39-grid,
party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.
Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.
Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).
ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
57 KiB
m2dev-client → mtgodot-poc 1:1 功能差距
对照范围:
../m2dev-client-src-main/(客户端源码)、../m2dev-client-main/(可执行客户端与配置)以及本工程当前实现。本文按“协议、数据、交互、表现均与原客户端等价”的严格 1:1 标准审计;
CLIENT-ROADMAP.md中的“P0–P10 首版完成”只表示主要纵向流程已有首版,不等于 1:1 完成。 渲染细节另见PARITY-GAP.md,动画、材质和资产管线另见BACKLOG.md,网络实现另见CLIENT-PORT.md。更新日期:2026-08-31(增量 38)
状态列图例:✅ 达到本文末“1:1 完成判据” · 🟡 有首版 / 部分语义 · ⬜ 未实现 · ❌ 明确不做(超出 m2dev-client 参考范围,除非另立需求)。
不做清单(❌)
- 拍卖行 / auction house:m2dev-client 无此系统 ——
0x08xx段只有SHOP/MYSHOP/SAFEBOX_*/MALL_*,全源码 / 协议头 / 资产零auction命中。 要做即“新造协议 + 配套服务端”,不属 1:1 移植。标注为不做,除非后续明确提需求。- 反作弊(anti-cheat):
CG_HACK(0x0B03) 客户端异常上报,以及桌面外壳的ProcessCRC/ProcessScanner/ 内存扫描等。自用客户端不需要,标注为不做, 除非服务端强制校验才回头做最小上报。变更记录
- 2026-08-31 增量 30(EterGrnLib 对齐复核):查代码确认以下已落地,表内相关行同步。 ① 材质 blend / two-sided 改读 GR2 材质信息(
metin2_model::decide_blend/decide_two_sided优先gr2::MaterialInfo.alpha_blend/two_sided=dump_materials端口Material.cpp:233, body + weapon 两处;m2_material加cull_disabledshader 变体)。 ② LOD 淡入(LodGhost冻结旧 mesh +lod_fadeuniform,0.18s,距离阈值 15% 迟滞)。 ③ crossfade 权重改 ease-in2w²−w³,对齐GrannySetControlEaseInCurve(0,0,1,1)。 ④.msamotion event 类型分派(game_scene._on_local_motion_event:sound / effect / SCREEN_WAVING 震屏 / SCREEN_FLASHING 屏闪 / FLY·WARP 钩子)。 ⑤ 角色接触阴影(project/ui/char_shadow.gd:强制cast_shadow=ON+ 脚下渐变 Decal,player_view/mob_view调用)。 ⑥ 点选精度(player_controller._ray_pick_t:射线 vs 子 mesh 合并世界 AABB slab 相交, 无 mesh 回退胶囊)。 ⑦ 武器挂点复核:隔离探针实测 weapon MI 原点 ==equip_right_hand骨骼原点 Δ≤0.06 m(旧「偏 10 m」为 cpu_skin / MapCoord 修复前的失效结论);新增 grip pre-transformweapon_pre = mul4x3(weaponInvWorld[0], weaponLocal[0])(对齐ModelInstanceUpdate.cpp:171的weaponComposite[0]),30 cm 位移夹断退回原始手部位姿。03150类网格在 .gr2 里就离骨骼原点 ~3 m(资产缺陷,非变换)。 新增测试eterngrn_polish_test.gd;ctest 10/10+ 29 套 GDScript + iOS 全绿。- 2026-08-31 增量 38(ESC 系统菜单 + 游戏设置窗): ①
system_menu_ui.gd(systemdialog.py真 uiscript,thinboard 8 键)=uisystem.pySystemDialog.__LoadSystemMenu_Default的 1:1:system_option_button→system_option_ui.open(),game_option_button→game_option_ui.open(),change_button→client.say(0,"/phase_select")(=net.ExitGame()里SendChatPacket("/phase_select")),logout_button→"/logout"(=net.LogOutGame()),mall_button→"/in_game_mall",exit_button→get_tree().quit(),help_button→ 占位提示,cancel_button/ 标题栏 → 关。ESC 现在开这个菜单(game_scene把 ESC 从直接开 system_option 改成开 system_menu)。 ②game_option_ui.gd(gameoptiondialog.py真 uiscript)=uigameoption.py:block_{exchange,party,guild,whisper,friend,party_request}_button(toggle) →client.say(0,"/setblockmode " + str(mask ^ bit))(EBlockAction位 1<<0..1<<5,本地_block_mode跟踪 + 持久化,服务器回包同步待补);pvp_{peace,revenge,guild,free}(radio) →"/pkmode {0,1,4,2}"(__OnClickPvPMode*);name_color/target_board/view_chat/always_show_name/show_damage/salestextradio → 持久化user://system_option.cfg [gameopt](systemSetting.Set*Flag等价;渲染侧钩子待补)。 新增system_menu_ui_test.gd(两窗装载 +/setblockmode位运算 +/pkmode+/phase_select//logout//in_game_mall+ 显示开关持久化 + 二次实例读 cfg)。C++ 无改动;ctest 10/10+ 34 套 GDScript + 导入 + iOS 全绿。- 2026-08-31 增量 37(组队成员信息板 +
CG_PARTY_SET_STATE): ① C++:wire.h加CGPartySetState{u16 hdr,u16 len,u32 pid,u8 role,u8 on}(断言 10)
EPartyRole枚举(NORMAL 0/LEADER 1/ATTACKER 2/TANKER 3/BUFFER 4/SKILL_MASTER 5/ BERSERKER 6/DEFENDER 7);game_client.send_party_set_state(pid,role,on);M2Client.party_set_state(pid,role,on)+ bind;get_party()dict 补state(完整角色字节) 和affects[7]。 ②party_ui.gd从「名字 + 一条 HP」重做成 1:1 迁移assets/root/uiparty.pyPartyMemberInfoBoard+partymemberinfoboard.py布局:每员 strip = 角色状态按钮 (队长可点 → 弹角色菜单 普通/攻击/坦克/狂战/辅助/宗师/防御 →party_set_state;普通 = 清当前角色on=false;+ 踢出 →party_leave=SendPartyRemovePacket)+ 名字(+★队长)- HP gauge +
affects[7]非零 → 附加效果 chip(tooltip 带值,槽位名暂定)。点名字 →set_target(vid)。顶部:EXP 分配开关(party_set_distribute0 不均分 / 1 均分)+ 组队治疗(party_use_skill(PARTY_SKILL_HEAL=1, 0))。队长判定 =get_party()里本地get_main_vid()那条的leader。角色菜单按党技能等级门控(Tanker≥10…Defender≥40)暂略。 ③ 修p8_test隐患:_init原来_run()未await,_run第一个await process_frame之后的所有断言(含增量 34 的shop_ex多货架)从未真正生效 —— 改成await _run()后 暴露shop_ui.refresh()的queue_free延迟 bug(旧 row 当帧未消失 →get_child_count多计),已改remove_child+queue_free。ctest 10/10+ 33 套 GDScript + 导入 + iOS 全绿。- 2026-08-31 增量 36(系统设置窗 + 私人商店/选魔石补真 uiscript): ① 系统设置窗
project/ui/system_option_ui.gd——UiScript+UiBuild装assets/uiscript/uiscript/systemoptiondialog.py;控件绑定逐字对照assets/root/uisystemoption.py:music/sound_volume_controller(sliderbar) →snd.SetMusicVolume/SetSoundVolume(接Audio.master_bgm/master_sfx+ 即时改在播 BGM 声道);camera_short/long(radio) →game_camera.max_dist(近 11 / 远 20,并夹当前 dist);fog_level0/1/2(浓/中/淡 radio) →Environment.fog_enabled+fog_density[0.055/0.018/0.004];tiling_cpu/gpu+apply(原 CPU/GPU 分块渲染)Godot 渲染器无对应, 保留占位。设置持久化到user://system_option.cfg(= 原systemSetting配置文件),setup()时读回即时生效,.msenv换Environment后_on_main_set重新_apply_all。game_scene:ESC 呼出(ui_manager先关最顶层窗并吃掉事件 → 到game_scene说明无窗打开)。 新增system_option_ui_test.gd(装载 + 音量/镜头/雾绑定 + cfg 持久化 + 二次实例读 cfg)。 ② 私人商店窗改走真 uiscriptprivateshopbuilder.py(board + TitleBar + NameLine +ItemSlot5×8=40 grid + Ok/Close):40 格从_win.nodes索引,NameLine(text) 上盖LineEdit,背包候选面板挂窗右侧(原客户端靠主背包拖拽,此处点选)。itemStock交互模型 (拿起 / 落位 / 价格弹窗 / 排序 + display_pos / 撤下 / 개설 / 철수)不变。测试拆出独立private_shop_ui_test.gd(uiscript,缺资产跳过),shop_cube_mall_test私人商店段移除。 ③ 选魔石窗select_item_ui.gd早已走真 uiscriptselectitemwindow.py(增量 33),无需改。 C++ 无改动;ctest 10/10+ 33 套 GDScript + 导入 + iOS 全绿。- 2026-08-31 增量 35(私人商店开设窗 =
PrivateShopBuilder交互模型):private_shop_ui.gd从「前 5 行 checkbox」重做成 1:1 复刻assets/root/uiprivateshopbuilder.py的itemStock模型:左侧背包候选(点一件“拿起”)+ 右侧 40 格(shop.SHOP_SLOT_COUNT,5×8); 拿着件点空格 → 价格输入(uiCommon.MoneyInputDialog)→ 落位(AddPrivateShopItemStock+itemStock[targetSlot]=(src));点已占用格 → 撤下(OnSelectItemSlot/DelPrivateShopItemStock)。개설(OkButton) 把 stock 按格号排序、display_pos = 格号、 上限 39(PRIVATE_SHOP_ITEM_MAX_NUM=TPacketCGMyShop::bCountcap)打包成M2Client.open_private_shop(sign, [{vnum,count,inv_cell,price,display_pos}])(=TPacketCGMyShop+TShopItemTable×N,字段 1:1)。shop_cube_mall_test私人商店段 重写(拿起 / 落位 / 价格弹窗 / 排序 + display_pos / 撤下 / 철수)。C++ 无改动;ctest 10/10+ 31 套 GDScript + 导入 + iOS 全绿。 NOTE: 面板仍手绘(与 mall_ui / cube_ui 一致),真.sub版privateshopbuilder.pyuiscript 待后续。- 2026-08-31 增量 34(
SHOP_GC_START_EX多货架商店):entity_store新增SHOP_GC_START_EX(10) 解析 ——TPacketGCShopStartEx{u32 owner_vid, u8 tab_count}
tab_count×{ShopTabHead(char name[32]+u8 coin_type=33B), ShopItem[40]}(wire.h加ShopTabHead+ 断言 33,SHOP_TAB_NAME_MAX=32/SHOP_TAB_COUNT_MAX=3)。ShopEntry加pos(货架内槽位,买位置用),ShopTab{name,coin_type,items}+m_shop_tabs;SHOP_GC_START也改成按固定 40 槽数组填pos;SHOP_GC_END清货架。shop_items()仍是 tab 0 的镜像(旧调用不动)。M2Client.get_shop()→{vid, open, tabs:[{name, coin_type, items:[{pos,vnum,price,count}]}]}。shop_ui.gd:tabs>1显示 货架按钮,买位置 =tabIdx*SHOP_SLOT_COUNT(40)+slotPos(1:1 对齐uishop.pyGetIndexFromSlotPos)。net_entity_test加双货架合成包断言(名称 / coin_type / 槽位保留 / tab0 镜像 / END 清空),p8_test加多货架 UI +buy pos 5/buy pos 43断言。ctest 10/10+ 31 套 GDScript + 导入 + iOS 全绿。- 2026-08-31 增量 33(选魔石窗,样板第 2 例):新增
project/ui/select_item_ui.gd——UiScript+UiBuild装载assets/uiscript/uiscript/selectitemwindow.py(5×8grid_table);填格逻辑逐字照assets/root/uiselectitem.py.RefreshSlot:遍历背包前INVENTORY_PAGE_SIZE*2格,只留item.IsMetin(proto.type == ITEM_TYPE_METIN= 10) 且GetItemGrade <= 2(= 物品内部名最后一位数字,非数字按 0)的物品,最多 54 个。 点格 →client.script_select_item(inventoryCell)(=net.SendSelectItemPacket,CG_SCRIPT_SELECT_ITEM0x0903)+ 关窗;ExitButton / 关闭 →script_select_item(0)(uiselectitem.Close)。触发链 1:1:quest_dialog.parse_script识别 EventManager[SELECT_ITEM]token → 新增select_item_requested信号(对应PythonEventManagerEVENT_TYPE_SELECT_ITEM→interfacemodule.BINARY_OpenSelectItemWindow)→game_scene接到select_item_ui.open()。新增select_item_ui_test.gd(token 解析 + 过滤 + 选包 + 关窗发 0);ctest 10/10+ 31 套 GDScript + 导入 + iOS 全绿。- 2026-08-31 增量 32(角色状态窗 = 1:1 窗口迁移样板):新增
project/ui/char_status_ui.gd——直接用UiScript+UiBuild装载assets/uiscript/uiscript/characterwindow.py真布局;数值绑定逐字对照assets/root/uicharacter.py.RefreshStatus(Level/Exp/RestExp、HP/SP、 STR/DEX/HTH/INT、ATT=(min|max)+ATT_BONUS+ATTACKER_BONUS、 DEF=DEF_GRADE(+DEF_BONUS)、MATT=MAG_ATT+(min|max)MAGIC_WEP、MDEF、 ASPD/MSPD/CSPD/ER,EPointTypes 索引取自 m2devPacket.h)。加点 + / - 按钮发client.say(0,"/stat ht"|"/stat- ht" …),与原statusPlusCommandDict/statusMinusCommandDict完全一致;POINT_STAT>0才显示加点按钮。STATUS/SKILL/ EMOTICON/QUEST 四页做SetState页签切换(技能 / 表情 / 任务的数据仍在各自专用窗)。 名称 / 帮会 / 职业头像取get_entity(get_main_vid())。game_scene挂 V / C 热键。 新增char_status_ui_test.gd;ctest 10/10+ 30 套 GDScript + 导入 + iOS 全绿。 这是「原interfacemodule.py逐窗口迁移」的样板,后续选道具网格 / 商店 START_EX / 私人商店 39 格照抄这套(uiscript 装载 + 逐字数值绑定 + 原聊天命令 / 封包)。- 2026-08-31 增量 31(真服 E2E 二轮扫,单会话):以
MT_NET_TRACE=1 MT_E2E_SWEEP=1 ./build/extension/net_e2e完成一次 auth→选人→PHASE_GAME 会话;出站明文 / 加密字节均已落盘。10 个安全探针全部发送成功、 450ms 后仍在线、unknown_header=0:CG_CHARACTER_POSITION(5B)、CG_SYNC_POSITION(16B)、CG_SCRIPT_SELECT_ITEM(8B)、CG_QUEST_CANCEL(4B)、CG_PARTY_USE_SKILL(9B)、CG_FLY_TARGETING/CG_ADD_FLY_TARGETING(16B)、CG_FISHING(5B)、CG_SHOOT(5B)、CG_USE_SKILL(12B)。CG_QUEST_CANCEL触发 1 条 quest-change;钓鱼收到真服Please choose a Fishing Pole.(无鱼竿的预期业务拒绝),其余 无业务事件但均无断连 / 无解析错误。被动GC_LAND_LIST436B 在登录洪流中真服收到并解析。 扫描暴露一个代码缺口:GC_FLY_TARGETING/GC_ADD_FLY_TARGETING与GC_CHANGE_SKILL_GROUP已有EntityStore实现却未列入GameClient世界分派,现已补路由并由net_entity_test复核。CG_DUNGEON、CG_WARP、改名 / 符号 / 物品变更等破坏性或独立 连接协议本轮未发送,仍保持“待真服验”。- 2026-08-31 增量 5(实现复核):修正文档中已经落后的功能状态。 情侣状态已不再是“整体未做”:
GC_LOVER_INFO(70B) /GC_LOVE_POINT_UPDATE(5B) 已接入EntityStore,并通过M2Client.get_lover()/lover_changed驱动常驻love_ui,C++ 状态测试和love_ui_test.gd均有覆盖;仍缺完整情侣交互与真服验收。 角色 / 装备渲染已补本地主角的身体盔甲 / 时装 parts、头盔 / 假发 / 发型、右手武器、 左手盾牌、item_proto.specular/100球面高光、角色 LOD 迟滞 + 0.18s 淡入、动作切换 ease-in、.msa音效 / 特效 / 震屏 / 屏闪事件和角色接触阴影首版。地图 / 材质行同步 记录 16 层 splat、原 UV / 源分辨率、水纹序列 / 水深 alpha、terrain patch 剔除和物体 Y-up 旋转修复。协议审计总缺口仍为 24 个 header,但GC_LOVER_*已从“声明未消费” 清单移除;输入行修正为已支持 F1–F4 与数字键 1–9。 本次同时补充“参考模块覆盖索引”,把协议表之外的聊天、TextTail、HUD、UI 运行时、输入 / 相机、 登录串场和桌面外壳差距单独列出。- 2026-08-31 增量 6(位置同步上行):补齐
CG_CHARACTER_POSITION(0x0A60) /CG_SYNC_POSITION(0x0303) 的 wire 结构、尺寸断言、GameClient发送接口和M2Client.character_position()/sync_positions()API;回环测试覆盖姿态包、最多 16 个 坐标元素、字段值和空批次 / 超限拒绝。两项已从“缺失 header”移入“已声明但待核”——仍需 接入原版的自动碰撞纠偏时机、姿态动作和真服务器端到端验收。- 2026-08-31 增量 7(任务应答协议):补齐
CG_SCRIPT_SELECT_ITEM(0x0903) /CG_QUEST_CANCEL(0x0906) 的 wire 结构、尺寸断言、GameClient发送接口和M2Client.script_select_item()/quest_cancel()API;回环测试验证选择值与取消包。 同时让QuestDialog支持基础[INPUT]文本框和 Escape 取消;缺失 header 清单由 22 项 降为 20 项。任务窗口仍需接入选道具网格、原版 IME / 输入校验和完整EventManager标签语义。- 2026-08-31 增量 8(组队技能上行):补齐
CG_PARTY_USE_SKILL(0x0705) 的 wire 结构、尺寸断言、GameClient.send_party_use_skill()和M2Client.party_use_skill(); 回环测试验证技能索引 / 目标 VID。缺失 header 清单由 20 项降为 19 项;队伍技能的 冷却、权限和队员世界标记仍待补齐。- 2026-08-31 增量 9(飞行目标广播):补齐
GC_FLY_TARGETING(0x0411) /GC_ADD_FLY_TARGETING(0x0412) 的 20B wire 结构;EntityStore持久化 shooter 的目标 VID / 坐标并产生追加标记,M2Client.fly_targeting信号向 Godot 暴露;C++ 实体测试覆盖 设置目标、追加目标和坐标回退。缺失 header 清单由 19 项降为 17 项。- 2026-08-31 增量 10(技能组切换):补齐
GC_CHANGE_SKILL_GROUP(0x021D) 的 5B wire 结构、EntityStore技能组状态 / dirty 标记、M2Client.get_skill_group()和skill_group_changed信号;切换会同时使技能快照失效,C++ 实体测试覆盖状态更新。 缺失 header 清单由 17 项降为 16 项。- 2026-08-31 增量 11(角色改名):补齐
CG_CHANGE_NAME(0x010B) /GC_CHANGE_NAME(0x010C) 的固定长度 wire 结构、GameClient改名发送与角色槽位更新、M2Client.change_name()及char_name_changed信号;回环测试覆盖名称字节和 pid 广播。 缺失 header 清单由 16 项降为 14 项。- 2026-08-31 增量 12(公会创建请求):补齐
GC_REQUEST_MAKE_GUILD(0x0731) 的 无负载包解析,GameClient产生请求事件,M2Client.guild_make_requested向 Godot 暴露, 回环测试覆盖事件消费。缺失 header 清单由 14 项降为 13 项。- 2026-08-31 增量 13(主动传送请求):补齐
CG_WARP(0x0305) 的 4B wire 结构、GameClient.send_warp()和M2Client.request_warp();回环测试验证上行请求及现有GC_WARP目标回传。缺失 header 清单由 13 项降为 12 项。- 2026-08-31 增量 14(挖矿动作广播):补齐
GC_DIG_MOTION(0x0308) 的 13B wire 结构、EntityStore挖矿动作事件、M2Client.dig_motion信号和NetWorld朝向 / 动画 元数据处理;实体、回环和 Godot 桥接测试覆盖。缺失 header 清单由 12 项降为 11 项。- 2026-08-31 增量 15(公会符号下载):补齐
CG_SYMBOL_CRC(0x0723) /GC_SYMBOL_DATA(0x0732) 的 16B / 8B 结构和原始数据流;MarkClient支持 CRC 请求与变长符号文件接收,M2Client.download_guild_symbol()/get_guild_symbol()/guild_symbol_ready已接入, 回环测试覆盖 CRC 字段、guild id 和数据长度。缺失 header 清单由 11 项降为 9 项。- 2026-08-31 增量 16(钓鱼协议):补齐
CG_FISHING(0x0B01) /GC_FISHING(0x0B10) 的 5B / 10B wire 结构、旋转方向换算、EntityStore事件和M2Client.fishing()/fishing_event;回环与 Godot 桥接测试覆盖。缺失 header 清单由 9 项降为 7 项。- 2026-08-31 增量 17(副本协议):补齐
CG_DUNGEON(0x0B02) /GC_DUNGEON(0x0B11) 的 4B / 5B wire 结构,支持目的地坐标变长 body,接入M2Client.request_dungeon()/dungeon_event;实体、回环和 Godot 桥接测试覆盖。缺失 header 清单由 7 项降为 5 项。- 2026-08-31 增量 18(领地与观战者):补齐
GC_LAND_LIST(0x0B12) 的 4B 头 + 24B 元素变长解析,以及GC_OBSERVER_ADD/REMOVE/MOVE(0x0B20–0x0B22) 的 12B / 8B wire 结构;EntityStore保存领地和观战者状态,M2Client.get_land_areas()/get_observers()与land_areas_changed/observer_event已接入;小地图现在消费观战者 状态并绘制紫色菱形标记。实体、回环、Godot 桥接和p9_test.gd覆盖。缺失 header 清单由 5 项降为 1 项(仅剩CG_TEXT,参考端未发现实际发送方)。- 2026-08-31 增量 19(钓鱼动作消费):
NetWorld和NetPlay现在消费fishing_event的 START / STOP / REACT / SUCCESS / FAIL 子头,记录方向并尝试驱动远端及 本地主角的钓鱼动作;FISH 子头按参考客户端语义保留为鱼获物品事件,不误当作角色 VID。combat_fx_test.gd、netbridge_test.gd覆盖动作分发。- 2026-08-31 增量 20(钓鱼结果日志):
ChatUI消费GC_FISHING的 SUCCESS / FAIL / FISH 事件,将命中、失败和鱼获物品写入系统 / 战斗日志;chat_test.gd覆盖结果路由。- 2026-08-31 增量 21(小地图领地标记):小地图继续消费
GC_LAND_LIST,把全局厘米坐标 的领地矩形绘制为公会金色 / 中性青色边框;p9_test.gd补充领地数据供 headless 重绘路径 自检。观战者紫色菱形与领地边框均已接入,仍缺原版 minimap 贴图和 Atlas 交互。- 2026-08-31 增量 22(小地图缩放):
minimap.gd新增set_scale()/get_scale(), 对齐原CPythonMiniMap::SetScale的像素 / 米缩放语义;鼠标滚轮在小地图区域内调整缩放,p9_test.gd验证缩放会改变世界坐标投影。仍缺原版贴图、Atlas 拖动和坐标查询窗口。- 2026-08-31 增量 23(飞行目标消费):
NetWorld为每个 shooter 保存FLY_TARGETING/ADD_FLY_TARGETING目标队列;GC_CREATE_FLY在终点 VID 缺失时回退到 队列中的目标 VID 或全局厘米坐标,补上原版SetFlyTarget/AddFlyTarget的关键语义。netbridge_test.gd覆盖无终点 VID 的投射物创建;真实弹道模型、命中时机和特效仍待补齐。- 2026-08-31 增量 24(弓技能发射时机):
SkillTable.is_ranged()根据weapon_limit=BOW识别远程技能;GameScene为弓技能暂存CG_SHOOT,在.msa的 FLY motion event 到达时发送,超时自动清理,避免近战技能误发。skill_test.gd与eterngrn_polish_test.gd覆盖远近程识别及一次性发射。- 2026-08-31 增量 25(Atlas 大地图窗口):新增
AtlasUI,复用地图区块minimap.dds构建 Atlas 底图;支持 M 键打开 / 关闭、地图名称、玩家位置(全局厘米)显示、 左键拖动和右键复位。无底图资源时仍保留可交互坐标查询;atlas_test.gd与gamescene_test.gd覆盖窗口装配和输入路径。- 2026-08-31 增量 26(队伍地图标记):圆形小地图和 Atlas 从
get_party()/ 实体快照 区分队员,绘制绿色队员点、队长环和 Atlas 队员标记;p9_test.gd/atlas_test.gd覆盖 队伍标记绘制路径。队伍世界标记已不再是纯 UI 列表,仍需按原版颜色和离线状态逐项校准。- 2026-08-31 增量 27(输入差距复核):根据
game_camera.gd当前实现修正文档:桌面右键 拖拽环绕、滚轮缩放和触屏单指 / 双指手势已经存在,不再列为缺失功能;剩余差距改为相机 模式、灵敏度 / 键位持久化及窗口冲突规则。- 2026-08-31 增量 28(移动速度校准):
GC_CHANGE_SPEED已暴露实体的moving_speed;NetPlay在主角entity_info到达时调用PlayerController.set_server_speed(), 以原版 100 为基准缩放本地步行 / 奔跑预测(0.25–3.0 限幅),netplay_test.gd覆盖 150 → 1.5×。- 2026-08-31 增量 29(攻击速度校准):
GC_CHARACTER_ADD[2]/GC_CHARACTER_UPDATE的attack_speed已进入EntityStore/M2Client.entity_dict();本地主角的POINT_ATT_SPEED也通过get_points()暴露。NetPlay按 m2dev 的attack_speed / 100.0动作速率换算自动攻击间隔(0.25–3.0 倍限幅,100 → 0.6 秒),并由netplay_test.gd/net_entity_test覆盖实体字段和 150 → 0.4 秒。仍需用真实.msamotion duration、武器 / 坐骑 规则和服务端攻击合法性逐帧校准。- 2026-08-31 增量 4(真机验证):
net_e2e加 post-2026-08-30 协议探针(env 门禁MT_E2E_CHARCREATE/MT_E2E_MYSHOP/MT_E2E_CUBE)。真服192.168.21.203确认 1:1:CG_CHARACTER_CREATE(77B/u16 job) +GC_PLAYER_CREATE_FAILURE(5B) +GC_EMPIRE(5B) +GUILD_GC_SKILL_INFO(17B body) +GUILD_GC_WAR+CG_MYSHOP(38B+13B×N) 全部被真服接受 / 解析正确,无 unknown header。Cube/MALL 送出路径通但 需 NPC 交互;CG_CHARACTER_DELETE/ 龙魂精炼 / 会徽上传(破坏性)仍未真机验。- 2026-08-31 增量 3:两个 ⬜ 项落地首版 —— 私人商店 / 道具商城 (
CG_MYSHOP/GC_MALL_OPEN|SET|DEL/CG_MALL_CHECKOUT+private_shop_ui/mall_ui)、Cube 제작(GC_CHAT/COMMAND的cube …总线 →CubeState+cube_ui)。仍缺能量条、原版拖放 UI 细节。 拍卖行经核查 m2dev-client 无此系统 → 标注 ❌ 不做(见上「不做清单」)。 反作弊(CG_HACK+ProcessCRC/ProcessScanner)→ 标注 ❌ 不做。 「协议覆盖审计」段改为逐条清单:脚本核对wire.hvsPacket.h,主网络层 仍缺 24 个 header(位置同步上行 / 战斗广播 / 任务 / 社交 / 改名 / 世界玩法整组;CG_HACK已移入「不做」),另有 3 个「已声明未消费 / 待核」。- 2026-08-31 增量 2:
- 角色创建 / 删除流程完成首版并接近 1:1(原为“暂不纳入”)——见新表行 「选人 / 建号 / 删号」。
- 怪物 / NPC 真模型渲染修复:
root/npclist.txt(vnum→代号→目录)解析 +net_world.catch_up()补拉协程化setup()漏掉的初始 spawn 洪流。世界玩法行 的“NPC / 怪物首版”由“能出占位胶囊”提升为“能出真模型 + 贴图 + 动作 + 名字牌 / 血条”。GC_EMPIRE(0x0109) 已暴露为M2Client.get_empire();选人页显示国家名 + 国旗。- 关键 1:1 差距表新增“状态”列。
结论
mtgodot-poc 已经不是早期“只能登录、移动和攻击”的骨架:登录到 PHASE_GAME、基础战斗、
背包 / 装备、技能 UI、任务、聊天、组队 / 好友、商店 / 交易 / 仓库、公会、精炼、龙魂、
地图和部分特效都已有可运行首版。
当前的主要差距已从“有没有功能”转为:
- 部分关键协议只实现了表现包或收包,没有完成原客户端的完整上行语义;
- 数据模型已扩到 24 个 wear 位置和独立 16 格腰带,并接入部分装备 / 时装换模,但原版 costume / belt 窗口、拖放、tooltip 和部位规则仍不完整;
- 多数玩法窗口是简化实现,未复现原 Python UI 的完整行为;
- EffectLib、SpeedTree、材质和世界表现仍有明确占位或近似;
- 打包资产路径、桌面配置和输入体验尚未形成原客户端等价闭环。
因此当前应称为“核心功能纵切完成”,不能称为 m2dev-client 的 1:1 客户端。
已具备的首版能力
- 登录、服务器 / 频道选择、频道负载状态、断线重连、选人、建号 / 删号、进入游戏;
- Metin2 风选人页(背景 / 3D 角色 / 职业书法字 / 国家 + 国旗 / 四维条 / ◀▶ 槽位 / 建删弹窗);
- 基础移动、攻击、选目标、生命 / 法力 / 经验、伤害飘字、affect、死亡与复活窗口;
- 背包、基础装备换模、地面物品、技能窗口和快捷栏读取;
- 聊天、私聊、基础任务对话和任务日志;
- 组队、好友、情侣名 / 爱意值状态、NPC 商店、交易、仓库;
- 公会、公会技能、公会战、会徽上 / 下载、精炼、龙魂精炼;
- 地形、建筑、水体、NPC / 怪物真模型(vnum→npclist.txt→目录)、小地图、频道、昼夜和天气首版;
- GR2 模型 / 动画、换装首版、武器 / 盾挂点、距离 LOD / 淡入、角色接触阴影、
item_proto驱动的球面高光和.mse粒子首版。
这些项目只说明已有入口和基础闭环;是否 1:1 仍以下表为准。
关键 1:1 差距
| 优先级 | 状态 | 模块 | 当前实现 | 需要迁移的原客户端语义 |
|---|---|---|---|---|
| P1 | 🟡 | 选人 / 建号 / 删号 / 改名 | CG_CHARACTER_CREATE(0x0201) / CG_CHARACTER_DELETE(0x0202) / CG_CHANGE_NAME(0x010B) / GC_PLAYER_CREATE_SUCCESS(0x020C) / GC_PLAYER_CREATE_FAILURE(0x020D) / GC_PLAYER_DELETE_SUCCESS(0x020E) / GC_PLAYER_DELETE_WRONG_SOCIAL_ID(0x020F) / GC_CHANGE_NAME(0x010C) 全部有尺寸断言;GameClient 发送接口、角色槽位补丁和 drain_*_events();M2Client 同名建删改 API、4 个建删结果信号及 char_name_changed;net.loopback_flow 覆盖建删和改名名称字节 / pid 广播。真机验证(2026-08-31,net_e2e MT_E2E_CHARCREATE=1):建号、GC_PLAYER_CREATE_FAILURE、GC_EMPIRE 已验。选人页有建号 / 删号弹窗与国家国旗 |
CG_CHARACTER_DELETE 破坏性,真机未验;改名 UI / 改名卡消耗和重复名错误提示未接;建号缺外形 / 发型选择与属性再分配 UI(现发 shape=0 + 职业基础四维);缺 SELECT_EMPIRE 选国界面;原版选人台座、镜头动画、OnCreateFailure 完整错误码文案 |
| P0 | 🟡 | 技能施放协议 | CG_USE_SKILL、CG_FLY_TARGETING、CG_ADD_FLY_TARGETING 与 CG_SHOOT 已有尺寸断言、原生发送接口和 loopback 字节验证;服务端 GC_FLY_TARGETING / GC_ADD_FLY_TARGETING 已进入 EntityStore,通过 M2Client.fly_targeting 暴露,且已补入 GameClient 世界分派;GC_CHANGE_SKILL_GROUP 会更新 get_skill_group() 并使技能快照失效;快捷栏先发 use_skill(skill_id,target_vid),再同步 CG_MOVE 技能动作;弓技能会在 .msa FLY 事件发送 CG_SHOOT。真服单会话已发送 CG_USE_SKILL / CG_FLY_TARGETING / CG_ADD_FLY_TARGETING / CG_SHOOT,均无断连 / unknown |
仍需实现范围 / 多目标选择器来自动驱动 ADD_FLY_TARGETING;冷却、目标合法性、真实弹道和客户端表现也须按技能类型细分;尚未在真服观察到对应 GC 飞行目标推送 |
| P0 | 🟡 | 快捷栏持久化 | 已覆盖 36 槽(4 页 × 9 格)、F1–F4 切页和 1–9 激活;拖放 / 清除 / 交换会分别发送 CG_QUICKSLOT_ADD/DEL/SWAP,服务器 GC_* 可完整恢复;技能冷却读取对应 .msk 的 CoolTimeFormula |
仍需补命令 / 表情快捷槽、物品拖放来源、鼠标物品态及原 UI 的完整快捷栏交互;冷却还未叠加原客户端的施法速度与所有特殊技能规则 |
| P0 | 🟡 | 物品操作 | 已有移动、使用、丢弃、拾取;Shift 选“来源→目标”发 ITEM_USE_TO_ITEM,Ctrl+右键向当前目标 ITEM_GIVE,Alt+右键以数量框发 ITEM_DROP2;ITEM_OWNERSHIP 会实时更新地面名条;GC_VIEW_EQUIP 已保存 11 格检查快照并弹出装备查看窗 |
仍需复刻原鼠标物品态、操作确认 / 错误提示、宝石专用规则,以及装备查看窗的原版槽位图标、3D 纸娃娃与交互 |
| P0 | 🟡 | 装备 / 背包数据模型 | 常规背包维持 90 格;运行时模型已扩展到 24 wear 位置,并将腰带改为独立 16 格;背包右侧动态呈现能力 / 时装 / 戒指 / 腰带和 4×4 腰带格,发包使用原客户端 INVENTORY 全局 cell(90 + wear / 152 + belt),按 values[0] 的原腰带等级规则锁格。EquipModel 已把本地主角 parts[](时装优先)和装备槽接到身体 shape / TargetSkin、头盔 / 假发 / 发型、右手武器、左手盾牌,并用 item_proto.specular/100 驱动护甲球面高光 |
仍需复刻原 costume / belt 窗口的槽位贴图、分页、tooltip 和鼠标物品态;补齐饰品 / 翅膀等多部件遮挡规则、远端角色同等换装以及逐部位高光 / 卸装复位,不以当前兼容性扩展面板代替原 UI |
| P0 | 🟡 | 移动与战斗同步 | 有 GC_MOVE、本地预测、攻击;M2Client.character_position() 可发送姿态回报,M2Client.sync_positions() 可按原包批量发送最多 16 个坐标,M2Client.request_warp() 可发送 CG_WARP 主动传送请求;GC_SYNC_POSITION 批量坐标纠偏、GC_CHANGE_SPEED、GC_WALK_MODE、GC_CHARACTER_POSITION 和 GC_DIG_MOTION 已恢复到实体状态,NetPlay 按 moving_speed/100 校准主角步行 / 奔跑预测,挖矿广播可驱动目标朝向 / dig 动画,WALK_MODE 会驱动远端 walk/run 动画选择;真服单会话已发送 CG_CHARACTER_POSITION(5B) 与 CG_SYNC_POSITION(16B),均无断连 / unknown |
仍需按原客户端同步窗口、碰撞纠偏触发时机与保留动作处理,而不是只靠距离阈值和 lerp;姿态动作还没有完整 UI / 动画语义 |
| P0 | 🟡 | PVP / 决斗 / 阵营 | GC_PVP 和 DUEL_START 已进入状态层,并以 pvp_changed / duel_started 信号提供给 Godot;挑战 / 战斗 / 复仇关系会在世界实体上显示标签;实体已有部分 empire / alignment / pk 字段 |
仍需 PK 模式上行、原版名字颜色、可攻击判定、阵营规则和原版目标板 / 决斗 UI |
| P1 | 🟡 | Actor 战斗运行时 | 有骨骼动画、基础 combo / 硬直 / 相机抖动;动作切换按 Granny EaseInCurve(0,0,1,1) 的 2w²−w³ 逐骨 crossfade;角色 mesh 支持距离 LOD、15% 迟滞和约 0.18s 新旧 mesh 淡入(LodGhost + lod_fade uniform);右手武器 / 左手盾按 weaponComposite[0] = weaponInvWorld[0]·weaponLocal[0] grip 复合挂 equip_right_hand(探针实测 Δ≤0.06 m,非标准米级偏移文件夹断回退);.msa motion event 已分派声音、EffectPosition 特效、震屏和屏闪(FLY / WARP 留钩子);玩家 / 怪物强制实时投影并附脚底接触阴影 Decal;实体 attack_speed 与本地 POINT_ATT_SPEED 已按原版 100 基准影响攻击请求间隔 |
仍需用真实 .msa motion duration、武器 / 坐骑规则和服务端合法性校准攻击节奏;另补 combo 取消窗口、精确受击 / 击退、角色碰撞、同步碰撞、武器拖尾、剩余 motion event / 骨骼挂点语义、GPU skin LOD、双持 / 左手武器和目标锁定相机;03150 类偏移网格需 per-weapon 偏移数据或修 .gr2 |
| P1 | 🟡 | 飞行物与命中特效 | GC_FLY_TARGETING / GC_ADD_FLY_TARGETING 保存 shooter 的目标队列和 VID / 坐标;GC_CREATE_FLY 可消费队列并生成 tween 发光球 |
对齐 FlyingObjectManager:真实技能弹道模型、命中时机 / 命中特效、消失条件、声音与 EffectLib 特效 |
| P1 | 🟡 | 任务 / NPC | 支持 [ENTER]、[CLEAR]、[NEXT]、[DONE]、基础 [QUESTION]、[INPUT] 文本框和任务日志;M2Client.script_select_item() / quest_cancel() 已可发送原协议应答,Escape 会走取消包;真服单会话已发送 CG_SCRIPT_SELECT_ITEM(8B) 与 CG_QUEST_CANCEL(4B),取消触发 1 条 quest-change 且无断连 / unknown |
补选道具网格、原版 CJK IME / 输入校验、QuestButton、立绘、完整 EventManager 标签、世界箭头和屏幕边缘指示;选道具包本轮未出现脚本业务回包 |
| P1 | 🟡 | 商店 / 交易 / 仓库 | 普通 NPC 商店、交易状态和仓库列表已有首版;SHOP_GC_START_EX 多货架已解析(m_shop_tabs,get_shop()),shop_ui 带货架按钮 + tabIdx*40+slot 买位置 |
补图标格网格、数量输入、交易回滚提示、仓库密码 / 改密码、完整拖放规则、coin_type 非金币货币显示 |
| P1 | 🟡 | 私人商店 / 道具商城 | 首版:CG_MYSHOP(0x0802) CGMyShopHead(38) + MyShopItem(13)×N 有尺寸断言 + GameClient::send_open_private_shop / send_close_private_shop(=SHOP_CG_END) + loopback 字节级;GC_MALL_OPEN(0x0841)/GC_MALL_SET(0x0842)/GC_MALL_DEL(0x0843) 路由进 EntityStore(m_mall[135] + mall_open/size/slot/dirty),CG_MALL_CHECKOUT(0x0840) CGMallCheckout(8) 发送;M2Client open/close_private_shop、get_mall_items/mall_checkout + mall_opened/mall_changed 信号;ui/private_shop_ui.gd(已重做成 PrivateShopBuilder.itemStock 模型:40 格 grid + 拿起 / 落位 / 逐件价格弹窗 / 点占用格撤下 / 개설按格号排序 + display_pos + 上限 39)、ui/mall_ui.gd(列表 + 取出)。真机验证(2026-08-31,net_e2e MT_E2E_MYSHOP=1):CG_MYSHOP 38B head + 13B item×1 被真服接受,无断连 / 无 shop error,SHOP_CG_END 关店正常。CG_MALL_CHECKOUT 送出不断连,但 mall 需点 NPC 才 open。他人开店 SHOP_GC_START_EX 多货架已解析(见增量 34)。⬜ SYMBOL_DATA/GC_SHOP_SIGN 头顶招牌 3D、真 .sub 版 privateshopbuilder.py uiscript、鼠标真拖放物品态 |
|
| — | ❌ | 拍卖行 / auction house | 不做 | m2dev-client 无此系统(0x08xx 段只有 shop/myshop/safebox/mall,全源码 / 协议 / 资产零 auction 命中)。需自定义协议 + 服务端支持才能实现,标注为不做,除非后续明确提需求 |
| P1 | 🟡 | Cube (제작) / 能量系统 | 首版:Cube 走 GC_CHAT/CHAT_TYPE_COMMAND 文本总线(对齐 ServerCommand()),EntityStore::apply_server_command 解析 cube open/close/info/success/fail/r_list/m_info(配方 v,c 列表 + @/&/` |
//分层材料 + 金币)→CubeState+drain_cube_events();GameClient::send_cube_make/material_info/result_list(/cube make |
| P1 | 🟡 | 组队 | 邀请、接受、离队;成员信息板(party_ui.gd,1:1 迁移 uiparty.PartyMemberInfoBoard):每员角色状态按钮(队长弹菜单 → CG_PARTY_SET_STATE 0x0704 分配 攻/坦/狂/辅/宗/防 或踢人)+ 名字(★队长) + HP gauge + affects[7] 附加效果 chip;顶部 EXP 分配开关(party_set_distribute)+ 组队治疗(party_use_skill(1,0));真服单会话 CG_PARTY_USE_SKILL(9B) 后仍在线;小地图 / Atlas 绿色队员点 + 队长环 |
补角色菜单按党技能等级门控(Tanker≥10…Defender≥40)、affects[7] 精确槽位语义、真版 .sub 图标 / 颜色、队伍技能冷却 / 权限、离线颜色语义 |
| P1 | 🟡 | 好友 / 情侣 | 好友列表、上线状态和私聊入口已有;GC_LOVER_INFO(70B) / GC_LOVE_POINT_UPDATE(5B) 已有尺寸断言、EntityStore::LoverInfo 状态、M2Client.get_lover() / lover_changed,love_ui 常驻显示伴侣名和 0–100 爱意值;net.entity_store 与 love_ui_test.gd 覆盖 |
补好友分组和完整 messenger 行为;情侣仍需完整交互 / 状态图标与 affect 表现、断线 / 切图生命周期、原版文案和真服务器端到端验收 |
| P1 | 🟡 | 公会 | 信息、成员、技能、公会战、会徽上 / 下载和部分操作已有首版;GC_REQUEST_MAKE_GUILD(0x0731) 已解析并以 M2Client.guild_make_requested 通知;CG_SYMBOL_CRC / GC_SYMBOL_DATA 已支持原始符号文件下载 |
补完整权限 / 等级页、公告与日志、成员管理、创建公会 UI / 应答流程、公会战应答、积分板、领地和公会建筑 |
| P1 | 🟡 | 世界玩法 | 有 CG_WARP 主动请求、GC_WARP 同服 / 跨服处理、CG_FISHING / GC_FISHING 钓鱼方向与状态事件、NetWorld / NetPlay 钓鱼动作分发、ChatUI 鱼获 / 失败日志、CG_DUNGEON / GC_DUNGEON 副本请求与状态事件、GC_DIG_MOTION 挖矿动作广播、GC_LAND_LIST 领地状态、GC_OBSERVER_ADD/REMOVE/MOVE 观战者状态、频道、时间、NPC marker、天气粒子和 mount_vnum 字段;真服登录洪流已收到并解析 GC_LAND_LIST(436B),单会话发送 CG_FISHING(5B) 后收到“Please choose a Fishing Pole.”业务拒绝且连接保持;同服 GC_WARP 直接挪玩家,跨服 addr≠0 由 M2Client 复用 login key 直连目标 game server,断点重连也走该快路径 |
补完整钓鱼 UI / 鱼获背包落地、副本计时 / 入口 UI、坐骑真模型 / 骑乘动作 / 移速、地形贴花、DungeonBlock 和地图天气配置;CG_DUNGEON / CG_WARP 本轮为避免副作用未发送,跨服换图仍需真服端到端验收与场景生命周期清理 |
| P1 | 🟡 | 小地图 / Atlas | 可画玩家、实体、NPC、任务标记、观战者紫色菱形和 GC_LAND_LIST 领地边框;支持 set_scale() / 鼠标滚轮缩放;Atlas 支持底图、地图名称、玩家坐标、拖动和坐标查询 |
补原 minimap / Atlas 贴图细节、提示、队员 / NPC 分类、世界箭头、地图驱动天气配置和原版窗口视觉 |
| P1 | 🟡 | EffectLib | .mse Particle 已映射到 GPUParticles3D |
粒子 .dds 目前被程序化径向渐变替代,.mde 用 box 占位;需补 EffectMesh、SimpleLight、TexAni、MovingType、骨骼挂点和完整时间轴 |
| P1 | 🟡 | SpeedTree | .spt 只嗅探真实 bark / composite 贴图,几何为程序化 proxy |
迁移或离线转换真实树干、叶片、frond、LOD、billboard、风和阴影;当前不能作为 1:1 树木 |
| P1 | 🟡 | 地图 / 材质渲染 | A1 地形、静态物、水和 .msenv 环境已有首版;splat 已支持每区块最多 16 个活动层、原客户端 UV / offset 与最大源分辨率,地形拆 patch 做视锥 / 距离剔除;水使用原 30 帧 DDS 序列、水深 alpha 和轻微高度动画;静态物 Y-up 旋转共轭已修正。GR2 blend / two-sided 优先读材质信息,球面高光管线已接 item_proto.specular;角色有实时投影 + 接触阴影 |
补其余固定功能材质与多贴图分支、精确 alpha-test / blend / cull、透明排序、逐 patch 几何 LOD、baked / realtime 阴影 mask、血迹 / 施法阵等 Decal、云层和 lens flare;按受控参考帧校准水深、LOD、阴影、光照和材质 |
| P1 | 🟡 | UI 控件语义 | 80 个 uiscript 可解析和构建,覆盖约 20 类基础节点 |
解析成功不等于窗口完成;需补动画图片、slot 拖放、scrollbar/listbox 行为、tooltip、radio group、候选窗、原层级 / 锚点 / 焦点 / modal 规则 |
| P1 | 🟡 | UI 窗口行为 | 背包、技能、任务、聊天、社交和商店等有简化窗口;腰带背包、扩展装备和装备查看已有兼容窗口;选人页已按 Metin2 风重做;角色状态窗(char_status_ui.gd)已按 characterwindow.py 真布局 + uicharacter.py.RefreshStatus 逐字绑定 + 原 /stat 加点命令做成,作为 1:1 窗口迁移样板 |
选魔石窗 select_item_ui.gd、系统设置窗 system_option_ui.gd、游戏设置窗 game_option_ui.gd(/setblockmode + /pkmode 真命令 + 显示开关持久化)、ESC 系统菜单 system_menu_ui.gd(systemdialog.py + /phase_select//logout//in_game_mall)、私人商店窗 private_shop_ui.gd、组队成员板 party_ui.gd 均已按样板做成 |
| P2 | 🟡 | 输入 / 快捷键 | WASD、点地、触屏、右键拖拽环绕、滚轮缩放、双指捏合、I/K/J/O/G/L/Z、F1–F4 快捷栏切页和数字键 1–9 激活已接;实体点选从固定半径提升为优先使用子 mesh 合并世界 AABB,无码型实体才回退胶囊近似 | 对齐空格连续攻击、显示名字、鼠标物品态、PK 模式、正式客户端截图、相机模式与灵敏度配置持久化、键位配置和窗口冲突规则 |
| P2 | 🟡 | CJK 输入与本地化 | 支持 KEY→VALUE 查表和基础字体注入 | 补 PythonIME 中 / 日 / 韩候选窗、复数 / 性别规则、字体自动切换、完整 item / mob 名称与 tooltip 本地化 |
| P2 | 🟡 | 音频 | 有 BGM crossfade、UI 音效池和 3D 音效 | 补地图 BGM 自动选择、.msenv 环境循环音、武器 / 技能 / 怪物 / NPC 声音事件和原距离衰减规则 |
| P2 | 🟡 | 客户端外壳 | Godot 工程已有 macOS / iOS / Android 构建脚本 | 补 config.exe 对应的分辨率、显示、音量、阴影、语言和鼠标配置;Movie / Web / Logo、补丁器约束、Discord RPC 按发布目标取舍 |
| P0 发布 | 🟡 | Pack 资产统一读取 | PackMount / AssetSource 已实现并有 roundtrip 测试 |
Metin2World / Metin2Model 等仍有直接 AssetResolver::resolve() 的散文件路径;必须统一切到 AssetSource::read/to_path(),否则 .epk/.eix 打包客户端不等价 |
参考模块覆盖索引
下表以 m2dev-client-src-main/src/UserInterface/ 的模块职责为索引,补充上表未展开的基础能力。
“已有”只表示当前有可运行首版;仍需满足本文末的 1:1 完成判据。
| 参考模块 / 能力域 | 当前实现 | 与 m2dev-client 的差距 | 优先级 |
|---|---|---|---|
AccountConnector / PythonNetworkStreamPhase* 登录与相位 |
auth → game → 选人 → LOADING → PHASE_GAME 已打通;LoadingScreen、ReconnectUI、跨服 GC_WARP 重连和频道状态探针均有首版 |
登录 / 选服 / 选频道的原版错误码、表单校验、超时与会话失效文案,服务器列表刷新和所有相位边界清理仍未逐项对齐 | P1 |
PythonChat / PythonTextTail / PythonPlayer |
聊天四标签、私聊 / 组队 / 公会 / 喊话路由,实体气泡、名字、HP 条、伤害飘字和本地气泡已有 | 缺称号、帮会名、名字颜色和目标高亮框;GC_DAMAGE_INFO、经验 / 金钱变化的原版日志行、notice / big-notice 排版、历史滚动和 InsultChecker 过滤未完整迁移 |
P1 |
PythonPlayer HUD / affect / target |
HP、SP、经验、等级、目标血条、死亡复活窗和 affect 色块条已绑定 | 原版状态栏布局、图标贴图、持续时间倒计时、目标板按钮 / 交互、所有 point 类型和战斗日志格式仍是简化版 | P1 |
PythonPlayerInput* / CameraProcedure / PythonApplicationCursor |
WASD、点地、触屏点选 / 单指相机拖动 / 双指捏合、桌面右键拖拽与滚轮缩放、网格 AABB 点选、相机遮挡淡出和碰撞已有;GC_CHANGE_SPEED 会校准本地移动速度 |
缺原版光标状态(talk / attack / buy / sell / pick 等)、相机模式与灵敏度配置持久化、键位配置、窗口冲突和空格连续攻击语义 | P1 |
EterPythonLib / PythonApplicationModule UI 运行时 |
80 个真实 uiscript 可解析;约 20 类控件、窗口栈、modal、拖动标题栏和通用弹窗已有 |
ani_image、mark、真实 grid_table / scrollbar / list 行为、.dds UI 贴图、鼠标物品态、焦点 / 锚点 / 层级和原版九宫格视觉仍未完全对齐 |
P1 |
PythonEffect / PythonFly / EffectLib |
.mse Particle → GPUParticles3D、技能 / 服务器特效触发、基础飞行球和动作事件已有 |
.dds 粒子纹理、.mde mesh、SimpleLight、MovingType / TexAni 时间轴、骨骼挂点、真实弹道 / 命中事件和声音仍缺 |
P1 |
PythonMiniMap / PythonBackground |
圆形小地图、实体 / NPC / 任务点、观战者、领地边框、队员 / 队长标记、鼠标滚轮缩放;Atlas 已有区块底图、拖动、坐标查询、地图名称和玩家 / 队员标记;地形 / 建筑 / 水体 / 天气和昼夜首版已有 | 缺原 minimap / Atlas 贴图细节、世界箭头、地图驱动天气配置和原版窗口视觉 | P1 |
PythonSystem / MovieMan / PythonApplicationWebPage / Discord |
Godot macOS / iOS / Android 构建脚本、基础窗口和音频设置入口已有 | 缺 config.exe 的分辨率 / 阴影 / 语言 / 鼠标配置持久化、Movie / Logo / Web 页面、补丁器约束及按发布目标取舍的 Discord RPC |
P2 |
ProcessCRC / ProcessScanner / PythonProfiler |
反作弊明确列为不做 | 若服务端未来强制校验,只补最小协议上报;性能采样、崩溃上报和诊断工具不属于当前 1:1 交付范围 | — |
协议覆盖审计
以 m2dev-client-src-main/src/UserInterface/Packet.h 为基准(namespace CG 68 个 +
namespace GC 109 个 = 177 个协议键)。增量 2/3 已补 CG_CHARACTER_CREATE /
CG_CHARACTER_DELETE / GC_PLAYER_CREATE_SUCCESS / GC_PLAYER_CREATE_FAILURE /
GC_PLAYER_DELETE_SUCCESS / GC_PLAYER_DELETE_WRONG_SOCIAL_ID / CG_MYSHOP /
CG_MALL_CHECKOUT / GC_MALL_OPEN / GC_MALL_SET / GC_MALL_DEL 共 11 个,并把已声明的
GC_EMPIRE 接到 M2Client.get_empire()。增量 5 又把此前仅声明的 GC_LOVER_INFO /
GC_LOVE_POINT_UPDATE 接入状态、信号和 UI;增量 6 又补了
CG_CHARACTER_POSITION / CG_SYNC_POSITION 的上行构造和 API。这两项不再计入缺失清单,
故当前总数从 24 降为 22 个 header;增量 7 再补任务选择 / 取消上行,增量 8 补队伍技能上行,
增量 9 补飞行目标广播,增量 10 补技能组切换,增量 11 补角色改名,增量 12 补公会创建请求,
增量 13 补主动传送,增量 14 补挖矿动作,增量 15 补会徽符号下载,增量 16 补钓鱼协议,
增量 17 补副本协议,增量 18 补领地与观战者,当前为 1 个 header。
排除仅命名不同、功能已覆盖的别名(GC_CHAR_ADDITIONAL_INFO=GC_CHAR_ADD_INFO、
GC_TARGET=GC_TARGET_INFO、GC_TARGET_CREATE_NEW=GC_TARGET_CREATE、
GC_CHARACTER_UPDATE2=GC_CHARACTER_UPDATE、GC_OWNERSHIP=GC_ITEM_OWNERSHIP、
GC_SEPCIAL_EFFECT=GC_SPECIAL_EFFECT、GC_REFINE_INFORMATION_NEW=GC_REFINE_INFO_NEW),
以及 CG_STATE_CHECKER / GC_RESPOND_CHANNELSTATUS(project/net/channel_status.gd 单独实现)
和 CG_HACK(反作弊,见「不做清单」),
当前主网络层仍缺 1 个 header(脚本核对 wire.h vs Packet.h):
账号 / 命令通道
| header | 值 | 说明 |
|---|---|---|
CG_TEXT |
0x0011 | 简易命令通道(Cube 现借 CG_CHAT/CHAT_TYPE_COMMAND 实现,非原路径) |
已声明但待核(不计入上面 1 个)
CG_CHARACTER_POSITION(0x0A60) /CG_SYNC_POSITION(0x0303):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已分别发送 5B / 16B,均保持在线且无 unknown;仍缺原版 自动触发语义、姿态动作链和服务端位置纠偏回包验收。CG_SCRIPT_SELECT_ITEM(0x0903) /CG_QUEST_CANCEL(0x0906):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已分别发送 8B / 4B,取消触发 1 条 quest-change 且无 unknown;基础[INPUT]/ Escape 取消已接入,仍缺选道具网格、原版输入校验和有脚本时的任务 流程验收。CG_PARTY_USE_SKILL(0x0705):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已发送 9B 且保持在线 / 无 unknown,但无队伍技能业务回包;仍缺冷却 / 权限、UI 入口和有效队伍真服 验收。GC_FLY_TARGETING(0x0411) /GC_ADD_FLY_TARGETING(0x0412):wire、EntityStore 状态、M2Client.fly_targeting信号和NetWorld目标队列消费已覆盖;本轮发现并修复GameClient未分派这两个 GC header 的 bug;真服本轮未主动推送,仍缺真实弹道模型、命中时机 和服务端广播验收。GC_CHANGE_SKILL_GROUP(0x021D):wire、EntityStore 状态、dirty 失效和M2Client.skill_group_changed已覆盖;本轮发现并修复GameClient未分派该 header 的 bug; 仍缺技能组选择 UI、服务端权限和真服推送验收。CG_CHANGE_NAME(0x010B) /GC_CHANGE_NAME(0x010C):wire、角色槽位更新、M2Client.change_name()和char_name_changed已覆盖;仍缺改名 UI、改名卡消耗 / 重名错误 文案和真服验收。GC_REQUEST_MAKE_GUILD(0x0731):无负载 wire 解析和guild_make_requested信号已覆盖;仍缺 创建公会窗口、名称校验 / 应答和真服验收。CG_SYMBOL_CRC(0x0723) /GC_SYMBOL_DATA(0x0732):16B / 8B wire、MarkClientCRC 请求、 变长数据接收和M2Client.guild_symbol_ready已覆盖;仍缺本地符号缓存失效策略、UI 预览和 真服验收。CG_FISHING(0x0B01) /GC_FISHING(0x0B10):5B / 10B wire、旋转方向换算、EntityStore事件、M2Client.fishing_event、远端 / 本地主角动作分发和 ChatUI 结果日志已 覆盖;真服单会话已发送 5B,收到“Please choose a Fishing Pole.”业务拒绝且无断连 / unknown, 证明包布局和错误路径可达;仍缺持竿后的完整钓鱼 UI、鱼获背包落地和成功流程验收。CG_DUNGEON(0x0B02) /GC_DUNGEON(0x0B11):4B / 5B wire、目的地坐标变长 body、EntityStore事件和M2Client.dungeon_event已覆盖;仍缺副本计时 / 入口 UI、地图生命周期 清理和真服验收。GC_LAND_LIST(0x0B12):4B 固定头 + 24BLandPacketElement变长解析、领地状态、M2Client.get_land_areas()/land_areas_changed和小地图领地矩形边框已覆盖;真服登录洪流 已收到并解析 436B;仍缺领地贴图 / 建筑和有实际领地变更时的推送验收。GC_OBSERVER_ADD/REMOVE/MOVE(0x0B20–0x0B22):12B / 8B wire、持久观战者状态、M2Client.get_observers()/observer_event和小地图紫色菱形标记已覆盖;仍缺真服验收。CG_WARP(0x0305):4B wire、GameClient.send_warp()、M2Client.request_warp()和回环 请求 /GC_WARP回传测试已覆盖;仍缺传送确认 UI、冷却 / 失败提示和真服验收。GC_DIG_MOTION(0x0308):13B wire、EntityStore事件、M2Client.dig_motion和NetWorld朝向 / 动画元数据处理已覆盖;仍缺真实挖矿动作资源、音效 / 特效和真服验收。GC_ITEM_DROP(0x0513):疑似被GC_ITEM_GROUND_ADD(0x0515) 取代,暂按等价处理,未逐字节核。GC_LOGIN_KEY(0x0107):auth 握手用;net_e2e真机 auth→PHASE_GAME 全程已打通,功能等价 (可能走GC_AUTH_SUCCESS0x0108),未单独立项。
“header 已声明”也不能直接视为完成:还需检查封包尺寸、变长 body、所有 sub-header、
EntityStore 状态、GDScript 信号、UI 消费和真服务器端到端行为。
建议迁移顺序
M1:服务器语义正确
- 把远程
SHOOT时机、范围目标与技能类型冷却接入现有技能协议; - 快捷栏的命令 / 表情 / 物品拖放语义与特殊冷却规则;
- 细化 24 wear / 腰带 / 装备查看的原版窗口表现,并完成完整物品操作;
- 移动同步、PVP / 决斗和 Actor 战斗状态机;
- 用 loopback + 真服抓包为每类协议建立双向验收。
M2:主要玩法完整
- 任务完整标签 / 输入 / 选道具 / 指引;
- 商店 START_EX、仓库密码、商城、私人商店、Cube(拍卖行 m2dev-client 无,除非另起协议);
- 组队角色、公会完整页面、情侣完整交互、领地与公会建筑;
- 钓鱼、副本、坐骑、观战者和 Atlas。
M3:UI 与表现 1:1
- 以原
interfacemodule.py为窗口清单,逐窗口迁移行为; - 完成 tooltip、拖放、光标、IME、快捷键和系统设置;
- 完成 EffectLib、SpeedTree、材质、地图表现和声音事件;
- 固定原客户端参考机位、角色、地图、时间和 UI 状态,建立截图 / 录屏签核。
M4:发布闭环
- 所有资产调用方统一使用
AssetSource,验证纯 pack 运行; - 覆盖目标平台构建、前后台、输入和性能;
- 按服务器要求决定 Discord / Web / Movie 等桌面能力是否迁移(反作弊
ProcessCRC/ProcessScanner已标注 ❌ 不做,除非服务端强制校验)。
1:1 完成判据
一个功能只有同时满足以下条件才标记为完成:
- 原客户端涉及的 CG / GC 包和 sub-header 全部覆盖,并有尺寸断言;
- 数据状态能正确恢复、重连、切图和清理;
- UI 操作、错误提示、拖放、快捷键和窗口互斥与原客户端一致;
- 模型、动画、特效、声音和名字 / tooltip 表现一致;
- loopback、真服务器端到端、受控截图或交互录屏至少有一种自动门禁和一种人工签核。
在达到以上判据前,应继续使用“首版”“部分完成”描述,避免把“能解析布局”“有一个按钮”或 “能播放动作”写成 1:1 完成。