diff --git a/audit/history.jsonl b/audit/history.jsonl index 94a288cb..e05fcf04 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -532,3 +532,4 @@ {"time": "2026-09-24T05:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P2: ServerStateChecker + module", "ported": ["UserInterface/ServerStateChecker.cpp", "UserInterface/ServerStateCheckerModule.cpp"], "adapted": [], "divergent": [], "needs_live": [], "tests": ["port.login_flow: fake state port answers CG_STATE_CHECKER, NotifyChannelState(2, 1)", "port.login_live 192.168.21.203: channel states {1: 1, 2: 1}", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["P3 item chain: ItemData, ItemManager, PythonItem, PythonExchange"]} {"time": "2026-09-24T06:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-a: ItemData, ItemManager, PythonNonPlayer + LoadLocaleData", "ported": ["GameLib/ItemData.cpp", "GameLib/ItemManager.cpp", "UserInterface/PythonNonPlayer.cpp", "UserInterface/PythonNonPlayerModule.cpp"], "adapted": ["UserInterface/PythonApplication.cpp:LoadLocaleData"], "divergent": [], "needs_live": [], "tests": ["port.login_flow 3c: item 19 Sword+9 type 1 refine 9, mob 101 Wild Dog", "port.login_live 192.168.21.203: same", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["P3-b PythonItem + PythonItemModule", "CPythonSkill (RegisterSkillDesc/Table) still pending"]} {"time": "2026-09-24T07:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-b: PythonItem + PythonItemModule", "ported": ["UserInterface/PythonItem.cpp", "UserInterface/PythonItemModule.cpp"], "adapted": ["UserInterface/PythonItemModule.cpp:itemGetIconImage", "GameLib/MapManager.cpp:CMapManager::GetNormal"], "divergent": [], "needs_live": [], "tests": ["port.login_flow: fake GC_ITEM_GROUND_ADD vnum 19 + ownership, player.PickCloseItem -> CG_ITEM_PICKUP -> GC_ITEM_GROUND_DEL removes it", "port.login_flow: item.SelectItem(19) name/type/subtype/value", "port.login_live 192.168.21.203: passes (icon pointer truncation segfault fixed)", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["P3-c PythonExchange", "PythonTextTail item tails (Register/Delete/SetOwner/Pick still stubs)", "CEffectManager (dropitem.mse)"]} +{"time": "2026-09-24T08:00:00Z", "event": "port_round", "unit": "PORT-PLAN batch 2 P3-c: PythonExchange + PythonExchangeModule", "ported": ["UserInterface/PythonExchange.cpp", "UserInterface/PythonExchangeModule.cpp"], "adapted": [], "divergent": [], "needs_live": ["exchange with a real second player"], "tests": ["port.login_flow: net.SendExchangeStartPacket -> fake GC_EXCHANGE START/ITEM_ADD/GOLD_ADD fills target side, uiexchange opens; SendExchangeExitPacket -> END closes", "port.login_live 192.168.21.203: passes", "ctest all 19/19, python_game_render_test offline PASS", "port_map.py check: 0 errors"], "todo": ["PythonTextTail item tails", "PythonMiniMap", "CEffectManager", "CPythonSkill"]} diff --git a/audit/port-map/UserInterface/PythonExchange.cpp.json b/audit/port-map/UserInterface/PythonExchange.cpp.json new file mode 100644 index 00000000..e9554b58 --- /dev/null +++ b/audit/port-map/UserInterface/PythonExchange.cpp.json @@ -0,0 +1,224 @@ +{ + "reference": "UserInterface/PythonExchange.cpp", + "reference_sha256": "ed8529ffd11f1baafe7c2cc0e83d1480ecb28df69ad74aa94813223371600a2e", + "priority": "P3", + "contracts": [], + "functions": { + "CPythonExchange::SetSelfName": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetSelfName" + ] + }, + "CPythonExchange::SetTargetName": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetTargetName" + ] + }, + "CPythonExchange::GetNameFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetNameFromSelf" + ] + }, + "CPythonExchange::GetNameFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetNameFromTarget" + ] + }, + "CPythonExchange::SetElkToTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetElkToTarget" + ] + }, + "CPythonExchange::SetElkToSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetElkToSelf" + ] + }, + "CPythonExchange::GetElkFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetElkFromTarget" + ] + }, + "CPythonExchange::GetElkFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetElkFromSelf" + ] + }, + "CPythonExchange::SetItemToTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemToTarget" + ] + }, + "CPythonExchange::SetItemToSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemToSelf" + ] + }, + "CPythonExchange::SetItemMetinSocketToTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemMetinSocketToTarget" + ] + }, + "CPythonExchange::SetItemMetinSocketToSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemMetinSocketToSelf" + ] + }, + "CPythonExchange::SetItemAttributeToTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemAttributeToTarget" + ] + }, + "CPythonExchange::SetItemAttributeToSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetItemAttributeToSelf" + ] + }, + "CPythonExchange::DelItemOfTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::DelItemOfTarget" + ] + }, + "CPythonExchange::DelItemOfSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::DelItemOfSelf" + ] + }, + "CPythonExchange::GetItemVnumFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemVnumFromTarget" + ] + }, + "CPythonExchange::GetItemVnumFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemVnumFromSelf" + ] + }, + "CPythonExchange::GetItemCountFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemCountFromTarget" + ] + }, + "CPythonExchange::GetItemCountFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemCountFromSelf" + ] + }, + "CPythonExchange::GetItemMetinSocketFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemMetinSocketFromTarget" + ] + }, + "CPythonExchange::GetItemMetinSocketFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemMetinSocketFromSelf" + ] + }, + "CPythonExchange::GetItemAttributeFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemAttributeFromTarget" + ] + }, + "CPythonExchange::GetItemAttributeFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetItemAttributeFromSelf" + ] + }, + "CPythonExchange::SetAcceptToTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetAcceptToTarget" + ] + }, + "CPythonExchange::SetAcceptToSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetAcceptToSelf" + ] + }, + "CPythonExchange::GetAcceptFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetAcceptFromTarget" + ] + }, + "CPythonExchange::GetAcceptFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetAcceptFromSelf" + ] + }, + "CPythonExchange::GetElkMode": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::GetElkMode" + ] + }, + "CPythonExchange::SetElkMode": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::SetElkMode" + ] + }, + "CPythonExchange::Start": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::Start" + ] + }, + "CPythonExchange::End": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::End" + ] + }, + "CPythonExchange::isTrading": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::isTrading" + ] + }, + "CPythonExchange::Clear": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::Clear" + ] + }, + "CPythonExchange::CPythonExchange": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::CPythonExchange" + ] + }, + "CPythonExchange::~CPythonExchange": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchange.cpp:CPythonExchange::~CPythonExchange" + ] + } + } +} diff --git a/audit/port-map/UserInterface/PythonExchangeModule.cpp.json b/audit/port-map/UserInterface/PythonExchangeModule.cpp.json new file mode 100644 index 00000000..81368cd6 --- /dev/null +++ b/audit/port-map/UserInterface/PythonExchangeModule.cpp.json @@ -0,0 +1,122 @@ +{ + "reference": "UserInterface/PythonExchangeModule.cpp", + "reference_sha256": "90fb936b2f2ac29833083fd69a2c5f4307c65eda153b6f02214942d765bdb143", + "priority": "P3", + "contracts": [], + "functions": { + "exchangeInitTrading": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeInitTrading" + ] + }, + "exchangeisTrading": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeisTrading" + ] + }, + "exchangeGetElkFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetElkFromSelf" + ] + }, + "exchangeGetElkFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetElkFromTarget" + ] + }, + "exchangeGetAcceptFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetAcceptFromSelf" + ] + }, + "exchangeGetAcceptFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetAcceptFromTarget" + ] + }, + "exchangeGetItemVnumFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemVnumFromSelf" + ] + }, + "exchangeGetItemVnumFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemVnumFromTarget" + ] + }, + "exchangeGetItemCountFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemCountFromSelf" + ] + }, + "exchangeGetItemCountFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemCountFromTarget" + ] + }, + "exchangeGetNameFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetNameFromSelf" + ] + }, + "exchangeGetNameFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetNameFromTarget" + ] + }, + "exchangeGetItemMetinSocketFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemMetinSocketFromTarget" + ] + }, + "exchangeGetItemMetinSocketFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemMetinSocketFromSelf" + ] + }, + "exchangeGetItemAttributeFromTarget": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemAttributeFromTarget" + ] + }, + "exchangeGetItemAttributeFromSelf": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetItemAttributeFromSelf" + ] + }, + "exchangeGetElkMode": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeGetElkMode" + ] + }, + "exchangeSetElkMode": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:exchangeSetElkMode" + ] + }, + "initTrade": { + "status": "PORTED", + "impl": [ + "extension/src/port/UserInterface/PythonExchangeModule.cpp:initTrade" + ] + } + } +} diff --git a/docs/PORT-PLAN.md b/docs/PORT-PLAN.md index edb4f3fe..0b263a79 100644 --- a/docs/PORT-PLAN.md +++ b/docs/PORT-PLAN.md @@ -438,6 +438,8 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次 P3-b(2026-09-24):`PythonItem.cpp` 与 `PythonItemModule.cpp` 原样移植,删除 pending 替身与 item 模块桩,`initItem()` 按 40250 顺序在 `initPlayer()` 之后;平台 `CPythonApplication::Create` 按原序调 `m_pyItem.Create()`(dropitem.mse 注册仍落在 CEffectManager 桩)。改动:`itemGetIconImage` 用 `Py_BuildPointer` 返回图标指针(40250 ILP32 用 `"i"`,64 位下被截断,真服背包 `wndMgr.SetSlot` 段错误);`D3DXMath.h` 补 `D3DXQuaternionConjugate`;pending `CMapManager::GetNormal` 以 `CTerrain::CalculateNormal` 公式按需在高度块上算法线;`CPythonTextTail` 的物品名牌四函数暂为桩,`Pick` 按 40250 空表语义返回 -1(返回 0 会把地面物品 VID 0 当作选中,点地移动失效)。假服入场时按 `CItem::EncodeInsertPacket` 发 vnum 19 的 `GC_ITEM_GROUND_ADD`+`GC_ITEM_OWNERSHIP`,收到 `CG_ITEM_PICKUP` 回 `GC_ITEM_GROUND_DEL`;`port.login_flow` 验证地面物品位置/归属/vnum,`player.PickCloseItem()` 发出拾取、物品被删除,以及 `item.SelectItem(19)` 的名称/类型/子类型/数值。下一步 P3-c:`PythonExchange`,之后 `PythonTextTail`(物品名牌)与 `PythonMiniMap`。 + P3-c(2026-09-24):`PythonExchange.cpp` 与 `PythonExchangeModule.cpp` 原样移植(无手改),删除 pending 替身与 exchange 模块桩,`initTrade()` 按 40250 顺序在 `initNonPlayer()` 之后。假服按 `CInputMain::Exchange` 应答:`CG_EXCHANGE` START 回 `GC_EXCHANGE` START(arg1 = 请求的 vid)+ 对方 ITEM_ADD(vnum 19,格 0)+ GOLD_ADD 1000,CANCEL 回 END;`port.login_flow` 用 `net.SendExchangeStartPacket` 开交易,验证 `exchange.isTrading/GetItemVnumFromTarget/GetItemCountFromTarget/GetElkFromTarget`,uiexchange 打开后 `net.SendExchangeExitPacket` 收到 END 关闭。注意:40250 `SendExchangeExitPacket` 的 arg1 未初始化(原样保留,假服不记录它);`uiexchange.CloseDialog` 调 `wndMgr.OnceIgnoreMouseLeftButtonUpEvent()`,测试先消耗这次被忽略的左键抬起,否则下一次点地的抬起被吞、角色一直按住行走。下一步 `PythonTextTail`(物品名牌)与 `PythonMiniMap`。 + 每个切片都必须同时跑 legacy/port 双路回归;只有 2V3 稳定并把默认路由切到 `port` 后,才能删除总开关和剩余 legacy 宿主。 ## 5. 数据源切换:m2dev assets → 40250 diff --git a/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp b/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp index 24075aa4..2f47d251 100644 --- a/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp +++ b/extension/src/platform/ScriptLib/GameplayModuleConstants.cpp @@ -81,13 +81,6 @@ void AddStubConstants_eventMgr(PyObject* poModule) (void) poModule; } -// 40250 UserInterface/PythonExchangeModule.cpp -void AddStubConstants_exchange(PyObject* poModule) -{ - (void) poModule; - PyModule_AddIntConstant(poModule, "EXCHANGE_ITEM_MAX_NUM", CPythonExchange::EXCHANGE_ITEM_MAX_NUM); -} - // 40250 UserInterface/PythonGuild.cpp void AddStubConstants_guild(PyObject* poModule) { diff --git a/extension/src/platform/ScriptLib/GameplayModules.cpp b/extension/src/platform/ScriptLib/GameplayModules.cpp index e9518a83..2995245e 100644 --- a/extension/src/platform/ScriptLib/GameplayModules.cpp +++ b/extension/src/platform/ScriptLib/GameplayModules.cpp @@ -8,7 +8,6 @@ void AddStubConstants_background(PyObject* poModule); void AddStubConstants_event(PyObject* poModule); void AddStubConstants_eventMgr(PyObject* poModule); -void AddStubConstants_exchange(PyObject* poModule); void AddStubConstants_guild(PyObject* poModule); void AddStubConstants_ime(PyObject* poModule); void AddStubConstants_messenger(PyObject* poModule); @@ -187,46 +186,6 @@ PyMethodDef methods_eventMgr[] = { { "Update", stub_eventMgr_Update, METH_VARARGS }, { NULL, NULL } }; -// 40250 UserInterface/PythonExchangeModule.cpp: exchange export names. -PyObject* stub_exchange_InitTrading(PyObject*, PyObject*) { return stub_call_named("exchange.InitTrading", Py_BuildNone()); } -PyObject* stub_exchange_isTrading(PyObject*, PyObject*) { return stub_call_named("exchange.isTrading", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetElkFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetElkFromSelf", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetElkFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetElkFromTarget", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemVnumFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemVnumFromSelf", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemVnumFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemVnumFromTarget", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemCountFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemCountFromSelf", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemCountFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemCountFromTarget", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetAcceptFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetAcceptFromSelf", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetAcceptFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetAcceptFromTarget", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetNameFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetNameFromSelf", Py_BuildValue("s", "")); } -PyObject* stub_exchange_GetNameFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetNameFromTarget", Py_BuildValue("s", "")); } -PyObject* stub_exchange_GetItemMetinSocketFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemMetinSocketFromTarget", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemMetinSocketFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemMetinSocketFromSelf", Py_BuildValue("i", 0)); } -PyObject* stub_exchange_GetItemAttributeFromTarget(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemAttributeFromTarget", Py_BuildValue("ii", 0, 0)); } -PyObject* stub_exchange_GetItemAttributeFromSelf(PyObject*, PyObject*) { return stub_call_named("exchange.GetItemAttributeFromSelf", Py_BuildValue("ii", 0, 0)); } -PyObject* stub_exchange_GetElkMode(PyObject*, PyObject*) { return stub_call_named("exchange.GetElkMode", Py_BuildValue("b", 0)); } -PyObject* stub_exchange_SetElkMode(PyObject*, PyObject*) { return stub_call_named("exchange.SetElkMode", Py_BuildNone()); } -PyMethodDef methods_exchange[] = { - { "InitTrading", stub_exchange_InitTrading, METH_VARARGS }, - { "isTrading", stub_exchange_isTrading, METH_VARARGS }, - { "GetElkFromSelf", stub_exchange_GetElkFromSelf, METH_VARARGS }, - { "GetElkFromTarget", stub_exchange_GetElkFromTarget, METH_VARARGS }, - { "GetItemVnumFromSelf", stub_exchange_GetItemVnumFromSelf, METH_VARARGS }, - { "GetItemVnumFromTarget", stub_exchange_GetItemVnumFromTarget, METH_VARARGS }, - { "GetItemCountFromSelf", stub_exchange_GetItemCountFromSelf, METH_VARARGS }, - { "GetItemCountFromTarget", stub_exchange_GetItemCountFromTarget, METH_VARARGS }, - { "GetAcceptFromSelf", stub_exchange_GetAcceptFromSelf, METH_VARARGS }, - { "GetAcceptFromTarget", stub_exchange_GetAcceptFromTarget, METH_VARARGS }, - { "GetNameFromSelf", stub_exchange_GetNameFromSelf, METH_VARARGS }, - { "GetNameFromTarget", stub_exchange_GetNameFromTarget, METH_VARARGS }, - { "GetItemMetinSocketFromTarget", stub_exchange_GetItemMetinSocketFromTarget, METH_VARARGS }, - { "GetItemMetinSocketFromSelf", stub_exchange_GetItemMetinSocketFromSelf, METH_VARARGS }, - { "GetItemAttributeFromTarget", stub_exchange_GetItemAttributeFromTarget, METH_VARARGS }, - { "GetItemAttributeFromSelf", stub_exchange_GetItemAttributeFromSelf, METH_VARARGS }, - { "GetElkMode", stub_exchange_GetElkMode, METH_VARARGS }, - { "SetElkMode", stub_exchange_SetElkMode, METH_VARARGS }, - { NULL, NULL } -}; // 40250 UserInterface/PythonGuild.cpp: guild export names. PyObject* stub_guild_IsGuildEnable(PyObject*, PyObject*) { return stub_call_named("guild.IsGuildEnable", Py_BuildValue("i", 0)); } PyObject* stub_guild_GuildIDToMarkID(PyObject*, PyObject*) { return stub_call_named("guild.GuildIDToMarkID", Py_BuildValue("i", 0)); } @@ -798,11 +757,6 @@ void init_2v0_gameplay_stubs() { PyModule_AddIntConstant(module, "__port_stub__", 1); AddStubConstants_eventMgr(module); } - { - PyObject* module = Py_InitModule("exchange", methods_exchange); - PyModule_AddIntConstant(module, "__port_stub__", 1); - AddStubConstants_exchange(module); - } { PyObject* module = Py_InitModule("guild", methods_guild); PyModule_AddIntConstant(module, "__port_stub__", 1); diff --git a/extension/src/platform/ScriptLib/PythonBoot.cpp b/extension/src/platform/ScriptLib/PythonBoot.cpp index 915d9cc0..f7b685b6 100644 --- a/extension/src/platform/ScriptLib/PythonBoot.cpp +++ b/extension/src/platform/ScriptLib/PythonBoot.cpp @@ -349,6 +349,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error) initPlayer(); initItem(); initNonPlayer(); + initTrade(); initChat(); initnet(); initeffect(); diff --git a/extension/src/platform/pending/UserInterface/PythonExchange.cpp b/extension/src/platform/pending/UserInterface/PythonExchange.cpp deleted file mode 100644 index 40d1255b..00000000 --- a/extension/src/platform/pending/UserInterface/PythonExchange.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// Stand-in for the 40250 logic unit UserInterface/PythonExchange.cpp, not ported yet: the members the ported units -// reference, as platform_stub.py stubs. Delete this file in the commit that ports the unit into port/ -// (docs/PORT-PLAN.md). -#include "UserInterface/StdAfx.h" -#include "UserInterface/PythonExchange.h" - -#include "../../PlatformStub.h" - -CPythonExchange::CPythonExchange() -{ - MT_PLATFORM_STUB(); -} - -CPythonExchange::~CPythonExchange() -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::Clear() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::Start() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::End() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::isTrading() -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CPythonExchange::SetSelfName(const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetTargetName(const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetElkToTarget(DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetElkToSelf(DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemToTarget(DWORD, DWORD, BYTE) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemToSelf(DWORD, DWORD, BYTE) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemMetinSocketToTarget(int, int, DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemMetinSocketToSelf(int, int, DWORD) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemAttributeToTarget(int, int, BYTE, short) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetItemAttributeToSelf(int, int, BYTE, short) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::DelItemOfTarget(BYTE) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::DelItemOfSelf(BYTE) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetAcceptToTarget(BYTE) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CPythonExchange::SetAcceptToSelf(BYTE) -> void -{ - MT_PLATFORM_STUB(); -} diff --git a/extension/src/port/UserInterface/PythonExchange.cpp b/extension/src/port/UserInterface/PythonExchange.cpp new file mode 100644 index 00000000..8e95df75 --- /dev/null +++ b/extension/src/port/UserInterface/PythonExchange.cpp @@ -0,0 +1,248 @@ +#include "StdAfx.h" +#include "PythonExchange.h" + +void CPythonExchange::SetSelfName(const char *name) +{ + strncpy(m_self.name, name, CHARACTER_NAME_MAX_LEN); +} + +void CPythonExchange::SetTargetName(const char *name) +{ + strncpy(m_victim.name, name, CHARACTER_NAME_MAX_LEN); +} + +char * CPythonExchange::GetNameFromSelf() +{ + return m_self.name; +} + +char * CPythonExchange::GetNameFromTarget() +{ + return m_victim.name; +} + +void CPythonExchange::SetElkToTarget(DWORD elk) +{ + m_victim.elk = elk; +} + +void CPythonExchange::SetElkToSelf(DWORD elk) +{ + m_self.elk = elk; +} + +DWORD CPythonExchange::GetElkFromTarget() +{ + return m_victim.elk; +} + +DWORD CPythonExchange::GetElkFromSelf() +{ + return m_self.elk; +} + +void CPythonExchange::SetItemToTarget(DWORD pos, DWORD vnum, BYTE count) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_victim.item_vnum[pos] = vnum; + m_victim.item_count[pos] = count; +} + +void CPythonExchange::SetItemToSelf(DWORD pos, DWORD vnum, BYTE count) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_self.item_vnum[pos] = vnum; + m_self.item_count[pos] = count; +} + +void CPythonExchange::SetItemMetinSocketToTarget(int pos, int imetinpos, DWORD vnum) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_victim.item_metin[pos][imetinpos] = vnum; +} + +void CPythonExchange::SetItemMetinSocketToSelf(int pos, int imetinpos, DWORD vnum) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_self.item_metin[pos][imetinpos] = vnum; +} + +void CPythonExchange::SetItemAttributeToTarget(int pos, int iattrpos, BYTE byType, short sValue) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_victim.item_attr[pos][iattrpos].bType = byType; + m_victim.item_attr[pos][iattrpos].sValue = sValue; +} + +void CPythonExchange::SetItemAttributeToSelf(int pos, int iattrpos, BYTE byType, short sValue) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_self.item_attr[pos][iattrpos].bType = byType; + m_self.item_attr[pos][iattrpos].sValue = sValue; +} + +void CPythonExchange::DelItemOfTarget(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_victim.item_vnum[pos] = 0; + m_victim.item_count[pos] = 0; +} + +void CPythonExchange::DelItemOfSelf(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + m_self.item_vnum[pos] = 0; + m_self.item_count[pos] = 0; +} + +DWORD CPythonExchange::GetItemVnumFromTarget(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_victim.item_vnum[pos]; +} + +DWORD CPythonExchange::GetItemVnumFromSelf(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_self.item_vnum[pos]; +} + +BYTE CPythonExchange::GetItemCountFromTarget(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_victim.item_count[pos]; +} + +BYTE CPythonExchange::GetItemCountFromSelf(BYTE pos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_self.item_count[pos]; +} + +DWORD CPythonExchange::GetItemMetinSocketFromTarget(BYTE pos, int iMetinSocketPos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_victim.item_metin[pos][iMetinSocketPos]; +} + +DWORD CPythonExchange::GetItemMetinSocketFromSelf(BYTE pos, int iMetinSocketPos) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return 0; + + return m_self.item_metin[pos][iMetinSocketPos]; +} + +void CPythonExchange::GetItemAttributeFromTarget(BYTE pos, int iAttrPos, BYTE * pbyType, short * psValue) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + *pbyType = m_victim.item_attr[pos][iAttrPos].bType; + *psValue = m_victim.item_attr[pos][iAttrPos].sValue; +} + +void CPythonExchange::GetItemAttributeFromSelf(BYTE pos, int iAttrPos, BYTE * pbyType, short * psValue) +{ + if (pos >= EXCHANGE_ITEM_MAX_NUM) + return; + + *pbyType = m_self.item_attr[pos][iAttrPos].bType; + *psValue = m_self.item_attr[pos][iAttrPos].sValue; +} + +void CPythonExchange::SetAcceptToTarget(BYTE Accept) +{ + m_victim.accept = Accept ? true : false; +} + +void CPythonExchange::SetAcceptToSelf(BYTE Accept) +{ + m_self.accept = Accept ? true : false; +} + +bool CPythonExchange::GetAcceptFromTarget() +{ + return m_victim.accept ? true : false; +} + +bool CPythonExchange::GetAcceptFromSelf() +{ + return m_self.accept ? true : false; +} + +bool CPythonExchange::GetElkMode() +{ + return m_elk_mode; +} + +void CPythonExchange::SetElkMode(bool value) +{ + m_elk_mode = value; +} + +void CPythonExchange::Start() +{ + m_isTrading = true; +} + +void CPythonExchange::End() +{ + m_isTrading = false; +} + +bool CPythonExchange::isTrading() +{ + return m_isTrading; +} + +void CPythonExchange::Clear() +{ + memset(&m_self, 0, sizeof(m_self)); + memset(&m_victim, 0, sizeof(m_victim)); +/* + m_self.item_vnum[0] = 30; + m_victim.item_vnum[0] = 30; + m_victim.item_vnum[1] = 40; + m_victim.item_vnum[2] = 50; +*/ +} + +CPythonExchange::CPythonExchange() +{ + Clear(); + m_isTrading = false; + m_elk_mode = false; + // Clear로 옴겨놓으면 안됨. + // trade_start 페킷이 오면 Clear를 실행하는데 + // m_elk_mode는 클리어 되선 안됨.; +} +CPythonExchange::~CPythonExchange() +{ +} \ No newline at end of file diff --git a/extension/src/port/UserInterface/PythonExchangeModule.cpp b/extension/src/port/UserInterface/PythonExchangeModule.cpp new file mode 100644 index 00000000..3ad51fb3 --- /dev/null +++ b/extension/src/port/UserInterface/PythonExchangeModule.cpp @@ -0,0 +1,191 @@ +#include "StdAfx.h" +#include "PythonExchange.h" + +PyObject * exchangeInitTrading(PyObject * poSelf, PyObject * poArgs) +{ + CPythonExchange::Instance().End(); + return Py_BuildNone(); +} + +PyObject * exchangeisTrading(PyObject * poSelf, PyObject * poArgs) +{ + return Py_BuildValue("i", CPythonExchange::Instance().isTrading()); +} + +PyObject * exchangeGetElkFromSelf(PyObject * poSelf, PyObject * poArgs) +{ + return Py_BuildValue("i", CPythonExchange::Instance().GetElkFromSelf()); +} + +PyObject * exchangeGetElkFromTarget(PyObject * poSelf, PyObject * poArgs) +{ + return Py_BuildValue("i", CPythonExchange::Instance().GetElkFromTarget()); +} + +PyObject * exchangeGetAcceptFromSelf(PyObject * poSelf, PyObject * poArgs) +{ + return Py_BuildValue("i", CPythonExchange::Instance().GetAcceptFromSelf()); +} + +PyObject * exchangeGetAcceptFromTarget(PyObject * poSelf, PyObject * poArgs) +{ + return Py_BuildValue("i", CPythonExchange::Instance().GetAcceptFromTarget()); +} + +PyObject * exchangeGetItemVnumFromSelf(PyObject * poSelf, PyObject * poArgs) +{ + int pos; + + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + + return Py_BuildValue("i", CPythonExchange::Instance().GetItemVnumFromSelf((char) pos)); +} + +PyObject * exchangeGetItemVnumFromTarget(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + + return Py_BuildValue("i", CPythonExchange::Instance().GetItemVnumFromTarget((char) pos)); +} + +PyObject * exchangeGetItemCountFromSelf(PyObject * poSelf, PyObject * poArgs) +{ + int pos; + + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + + return Py_BuildValue("i", CPythonExchange::Instance().GetItemCountFromSelf((char) pos)); +} + +PyObject * exchangeGetItemCountFromTarget(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + + return Py_BuildValue("i", CPythonExchange::Instance().GetItemCountFromTarget((char) pos)); +} + +PyObject * exchangeGetNameFromSelf(PyObject * poTarget, PyObject * poArgs) +{ + return Py_BuildValue("s", CPythonExchange::Instance().GetNameFromSelf()); +} + +PyObject * exchangeGetNameFromTarget(PyObject * poTarget, PyObject * poArgs) +{ + return Py_BuildValue("s", CPythonExchange::Instance().GetNameFromTarget()); +} + +PyObject * exchangeGetItemMetinSocketFromTarget(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + int iMetinSocketPos; + if (!PyTuple_GetInteger(poArgs, 1, &iMetinSocketPos)) + return Py_BuildException(); + return Py_BuildValue("i", CPythonExchange::Instance().GetItemMetinSocketFromTarget(pos, iMetinSocketPos)); +} + +PyObject * exchangeGetItemMetinSocketFromSelf(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + int iMetinSocketPos; + if (!PyTuple_GetInteger(poArgs, 1, &iMetinSocketPos)) + return Py_BuildException(); + return Py_BuildValue("i", CPythonExchange::Instance().GetItemMetinSocketFromSelf(pos, iMetinSocketPos)); +} + +PyObject * exchangeGetItemAttributeFromTarget(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + int iAttrSlotPos; + if (!PyTuple_GetInteger(poArgs, 1, &iAttrSlotPos)) + return Py_BuildException(); + + BYTE byType; + short sValue; + CPythonExchange::Instance().GetItemAttributeFromTarget(pos, iAttrSlotPos, &byType, &sValue); + + return Py_BuildValue("ii", byType, sValue); +} + +PyObject * exchangeGetItemAttributeFromSelf(PyObject * poTarget, PyObject * poArgs) +{ + int pos; + if (!PyTuple_GetInteger(poArgs, 0, &pos)) + return Py_BuildException(); + int iAttrSlotPos; + if (!PyTuple_GetInteger(poArgs, 1, &iAttrSlotPos)) + return Py_BuildException(); + + BYTE byType; + short sValue; + CPythonExchange::Instance().GetItemAttributeFromSelf(pos, iAttrSlotPos, &byType, &sValue); + + return Py_BuildValue("ii", byType, sValue); +} + +PyObject * exchangeGetElkMode(PyObject * poTarget, PyObject * poArgs) +{ + return Py_BuildValue("b", CPythonExchange::Instance().GetElkMode()); +} + +PyObject * exchangeSetElkMode(PyObject * poTarget, PyObject * poArgs) +{ + int elk_mode; + + if (!PyTuple_GetInteger(poArgs, 0, &elk_mode)) + return Py_BuildException(); + + CPythonExchange::Instance().SetElkMode(elk_mode ? true : false); + return Py_BuildNone(); +} + +void initTrade() +{ + static PyMethodDef s_methods[] = + { + {"InitTrading", exchangeInitTrading, METH_VARARGS}, + {"isTrading", exchangeisTrading, METH_VARARGS}, + + {"GetElkFromSelf", exchangeGetElkFromSelf, METH_VARARGS}, + {"GetElkFromTarget", exchangeGetElkFromTarget, METH_VARARGS}, + + {"GetItemVnumFromSelf", exchangeGetItemVnumFromSelf, METH_VARARGS}, + {"GetItemVnumFromTarget", exchangeGetItemVnumFromTarget, METH_VARARGS}, + + {"GetItemCountFromSelf", exchangeGetItemCountFromSelf, METH_VARARGS}, + {"GetItemCountFromTarget", exchangeGetItemCountFromTarget, METH_VARARGS}, + + {"GetAcceptFromSelf", exchangeGetAcceptFromSelf, METH_VARARGS}, + {"GetAcceptFromTarget", exchangeGetAcceptFromTarget, METH_VARARGS}, + + {"GetNameFromSelf", exchangeGetNameFromSelf, METH_VARARGS}, + {"GetNameFromTarget", exchangeGetNameFromTarget, METH_VARARGS}, + + {"GetItemMetinSocketFromTarget", exchangeGetItemMetinSocketFromTarget, METH_VARARGS}, + {"GetItemMetinSocketFromSelf", exchangeGetItemMetinSocketFromSelf, METH_VARARGS}, + + {"GetItemAttributeFromTarget", exchangeGetItemAttributeFromTarget, METH_VARARGS}, + {"GetItemAttributeFromSelf", exchangeGetItemAttributeFromSelf, METH_VARARGS}, + + {"GetElkMode", exchangeGetElkMode, METH_VARARGS}, + {"SetElkMode", exchangeSetElkMode, METH_VARARGS}, + + {NULL, NULL}, + }; + + PyObject * poModule = Py_InitModule("exchange", s_methods); + PyModule_AddIntConstant(poModule, "EXCHANGE_ITEM_MAX_NUM", CPythonExchange::EXCHANGE_ITEM_MAX_NUM); +} \ No newline at end of file diff --git a/extension/src/port/UserInterface/StdAfx.h b/extension/src/port/UserInterface/StdAfx.h index cfd66ae5..3e8cdfa6 100644 --- a/extension/src/port/UserInterface/StdAfx.h +++ b/extension/src/port/UserInterface/StdAfx.h @@ -49,6 +49,7 @@ void initchrmgr(); void initPlayer(); void initItem(); void initNonPlayer(); +void initTrade(); void initChat(); void initnet(); void initeffect(); diff --git a/extension/tests/port_login_flow_server.cpp b/extension/tests/port_login_flow_server.cpp index 7a6baa8a..efae2876 100644 --- a/extension/tests/port_login_flow_server.cpp +++ b/extension/tests/port_login_flow_server.cpp @@ -616,6 +616,45 @@ bool FakeLoginServer::ServeGame(Connection& c, int index) if (pickup.vid == kGroundItemVID && !c.Send(del)) return Fail(name + ": send GC_ITEM_GROUND_DEL"); } + // CInputMain::Exchange (input_main.cpp) → CHARACTER::ExchangeStart / CExchange::Cancel: the partner is + // played by the server itself — START names the requested vid, then the partner's item (vnum 19, cell 0) + // and gold arrive the way CExchange::AddItem/AddGold send them with is_me=false; CANCEL answers END. + if (header == HDR_CG_EXCHANGE) + { + CGExchange ex; + std::memcpy(&ex, packet, sizeof(ex)); + // arg1 only means something for START (40250 SendExchangeExitPacket leaves it uninitialised). + Event(name + ":exchange " + std::to_string(ex.subheader) + + (ex.subheader == EXCHANGE_CG_START ? " " + std::to_string(ex.arg1) : std::string())); + GCExchange out = zeroed(); + out.header = HDR_GC_EXCHANGE; + out.arg2 = {0xff, 0xffff}; // NPOS + bool ok = true; + if (ex.subheader == EXCHANGE_CG_START) + { + out.subheader = EXCHANGE_GC_START; + out.arg1 = ex.arg1; + ok = c.Send(out); + out.subheader = EXCHANGE_GC_ITEM_ADD; + out.arg1 = kExchangeItemVnum; + out.arg2 = {1, 0}; // INVENTORY, cell 0 + out.arg3 = 1; + ok = ok && c.Send(out); + out = zeroed(); + out.header = HDR_GC_EXCHANGE; + out.subheader = EXCHANGE_GC_GOLD_ADD; + out.arg2 = {0xff, 0xffff}; + out.arg1 = kExchangeGold; + ok = ok && c.Send(out); + } + else if (ex.subheader == EXCHANGE_CG_CANCEL) + { + out.subheader = EXCHANGE_GC_END; + ok = c.Send(out); + } + if (!ok) + return Fail(name + ": send GC_EXCHANGE"); + } if (header == HDR_CG_MOVE) { CGMove move; diff --git a/extension/tests/port_login_flow_server.h b/extension/tests/port_login_flow_server.h index 6ad6e0c7..343b63fb 100644 --- a/extension/tests/port_login_flow_server.h +++ b/extension/tests/port_login_flow_server.h @@ -25,6 +25,7 @@ public: static constexpr std::uint32_t kGroundItemVID = 0x00054321; static constexpr std::uint32_t kGroundItemVnum = 19; static constexpr std::int32_t kGroundItemX = kX + 150, kGroundItemY = kY; + static constexpr std::uint32_t kExchangeItemVnum = 19, kExchangeGold = 1000; ~FakeLoginServer(); diff --git a/extension/tests/port_login_flow_test.cpp b/extension/tests/port_login_flow_test.cpp index 4a9b7c03..5e6da489 100644 --- a/extension/tests/port_login_flow_test.cpp +++ b/extension/tests/port_login_flow_test.cpp @@ -50,6 +50,7 @@ #include "GameLib/ItemManager.h" #include "UserInterface/PythonItem.h" #include "UserInterface/StdAfx.h" +#include "UserInterface/PythonExchange.h" #include "UserInterface/PythonCharacterManager.h" #include "UserInterface/InstanceBase.h" #include "UserInterface/PythonBackground.h" @@ -518,6 +519,26 @@ int main(int argc, char** argv) CHECK(pump_until(2, [&] { return !ground.GetGroundItemPosition(FakeLoginServer::kGroundItemVID, &item_pos); })); } + // Exchange (CPythonExchange + exchange module): net.SendExchangeStartPacket → the fake partner's GC_EXCHANGE + // START/ITEM_ADD/GOLD_ADD fill the target side and open uiexchange; net.SendExchangeExitPacket → END. + if (server) + { + CHECK(py("import net\nnet.SendExchangeStartPacket(" + std::to_string(FakeLoginServer::kMainVID) + ")")); + CHECK(pump_until(2, [&] { return CPythonExchange::Instance().GetElkFromTarget() == FakeLoginServer::kExchangeGold; })); + CHECK(py("import exchange\n" + "assert exchange.isTrading() == 1\n" + "assert exchange.GetItemVnumFromTarget(0) == " + std::to_string(FakeLoginServer::kExchangeItemVnum) + "\n" + "assert exchange.GetItemCountFromTarget(0) == 1\n" + "assert exchange.GetElkFromSelf() == 0")); + CHECK(py("import net\nnet.SendExchangeExitPacket()")); + CHECK(pump_until(2, [&] { return !CPythonExchange::Instance().isTrading(); })); + CHECK(server->Has("game2:exchange 0 " + std::to_string(FakeLoginServer::kMainVID))); + CHECK(server->Has("game2:exchange 5")); + // uiexchange.CloseDialog armed wndMgr.OnceIgnoreMouseLeftButtonUpEvent (in the client that release belongs + // to the click that closed the dialog); spend it so step 8's click-release is not swallowed. + PythonBoot::UIMouseButton(1, false, 400, 520); + } + // 8. (批次 2V3) Click-to-move: a left click on the ground below the actor reaches game.GameWindow's // OnMouseLeftButtonDown → player.SetMouseState(MBT_LEFT, MBS_PRESS); CPythonPlayer picks the ground // through the cursor ray (CScreen::SetCursorPosition → CPythonBackground::GetPickingPoint) and the @@ -565,7 +586,8 @@ int main(int argc, char** argv) want.push_back(e); for (const auto& e : events) if (e.rfind(prefix, 0) == 0 && e.find(":move ") == std::string::npos && - e.find(":pickup ") == std::string::npos) + e.find(":pickup ") == std::string::npos && + e.find(":exchange ") == std::string::npos) got.push_back(e); CHECK(want == got); }