Port route 4-c: NPC shop (PythonShop.cpp) + app cursor state

- UserInterface/PythonShop.cpp ported verbatim (shop module, initshop after initfly);
  pending stub and module stubs removed.
- 40250 PythonApplicationCursor.cpp and SetMouseHandler moved into the platform
  CPythonApplication on file-static members; GetCursorNum out of line, since uishop's
  app.GetCursor() dereferenced the missing application object.
- Fake server: general store (npclist 9003) answers CG_ON_CLICK with GC_SHOP START,
  CG_SHOP BUY/SELL2/END like CShop::Buy / CShopManager::Sell.
- port.login_flow step 10: click the shopkeeper, right-click buy, drag-sell, ESC close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 09:30:47 +09:00
co-authored by Claude Opus 5.5
parent 85e176bdd5
commit a12e7fea42
16 changed files with 1060 additions and 115 deletions
@@ -92,14 +92,6 @@ void AddStubConstants_safebox(PyObject* poModule)
}
// 40250 UserInterface/PythonShop.cpp
void AddStubConstants_shop(PyObject* poModule)
{
(void) poModule;
PyModule_AddIntConstant(poModule, "SHOP_SLOT_COUNT", SHOP_HOST_ITEM_MAX_NUM);
PyModule_AddIntConstant(poModule, "SHOP_COIN_TYPE_GOLD", SHOP_COIN_TYPE_GOLD);
PyModule_AddIntConstant(poModule, "SHOP_COIN_TYPE_SECONDARY_COIN", SHOP_COIN_TYPE_SECONDARY_COIN);
}
// 40250 UserInterface/PythonSkill.cpp
// 40250 UserInterface/PythonSoundManagerModule.cpp
void AddStubConstants_snd(PyObject* poModule)
@@ -12,7 +12,6 @@ void AddStubConstants_messenger(PyObject* poModule);
void AddStubConstants_profiler(PyObject* poModule);
void AddStubConstants_quest(PyObject* poModule);
void AddStubConstants_safebox(PyObject* poModule);
void AddStubConstants_shop(PyObject* poModule);
void AddStubConstants_snd(PyObject* poModule);
void AddStubConstants_systemSetting(PyObject* poModule);
void AddStubConstants_udp(PyObject* poModule);
@@ -217,46 +216,6 @@ PyMethodDef methods_safebox[] = {
{ "GetMallSize", stub_safebox_GetMallSize, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonShop.cpp: shop export names.
PyObject* stub_shop_Open(PyObject*, PyObject*) { return stub_call_named("shop.Open", Py_BuildNone()); }
PyObject* stub_shop_Close(PyObject*, PyObject*) { return stub_call_named("shop.Close", Py_BuildNone()); }
PyObject* stub_shop_IsOpen(PyObject*, PyObject*) { return stub_call_named("shop.IsOpen", Py_BuildValue("i", 0)); }
PyObject* stub_shop_IsPrivateShop(PyObject*, PyObject*) { return stub_call_named("shop.IsPrivateShop", Py_BuildValue("i", 0)); }
PyObject* stub_shop_IsMainPlayerPrivateShop(PyObject*, PyObject*) { return stub_call_named("shop.IsMainPlayerPrivateShop", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetItemID(PyObject*, PyObject*) { return stub_call_named("shop.GetItemID", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetItemCount(PyObject*, PyObject*) { return stub_call_named("shop.GetItemCount", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetItemPrice(PyObject*, PyObject*) { return stub_call_named("shop.GetItemPrice", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetItemMetinSocket(PyObject*, PyObject*) { return stub_call_named("shop.GetItemMetinSocket", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetItemAttribute(PyObject*, PyObject*) { return stub_call_named("shop.GetItemAttribute", Py_BuildValue("ii", 0, 0)); }
PyObject* stub_shop_GetTabCount(PyObject*, PyObject*) { return stub_call_named("shop.GetTabCount", Py_BuildValue("i", 0)); }
PyObject* stub_shop_GetTabName(PyObject*, PyObject*) { return stub_call_named("shop.GetTabName", Py_BuildValue("s", "")); }
PyObject* stub_shop_GetTabCoinType(PyObject*, PyObject*) { return stub_call_named("shop.GetTabCoinType", Py_BuildValue("i", 0)); }
PyObject* stub_shop_ClearPrivateShopStock(PyObject*, PyObject*) { return stub_call_named("shop.ClearPrivateShopStock", Py_BuildNone()); }
PyObject* stub_shop_AddPrivateShopItemStock(PyObject*, PyObject*) { return stub_call_named("shop.AddPrivateShopItemStock", Py_BuildNone()); }
PyObject* stub_shop_DelPrivateShopItemStock(PyObject*, PyObject*) { return stub_call_named("shop.DelPrivateShopItemStock", Py_BuildNone()); }
PyObject* stub_shop_GetPrivateShopItemPrice(PyObject*, PyObject*) { return stub_call_named("shop.GetPrivateShopItemPrice", Py_BuildValue("i", 0)); }
PyObject* stub_shop_BuildPrivateShop(PyObject*, PyObject*) { return stub_call_named("shop.BuildPrivateShop", Py_BuildNone()); }
PyMethodDef methods_shop[] = {
{ "Open", stub_shop_Open, METH_VARARGS },
{ "Close", stub_shop_Close, METH_VARARGS },
{ "IsOpen", stub_shop_IsOpen, METH_VARARGS },
{ "IsPrivateShop", stub_shop_IsPrivateShop, METH_VARARGS },
{ "IsMainPlayerPrivateShop", stub_shop_IsMainPlayerPrivateShop, METH_VARARGS },
{ "GetItemID", stub_shop_GetItemID, METH_VARARGS },
{ "GetItemCount", stub_shop_GetItemCount, METH_VARARGS },
{ "GetItemPrice", stub_shop_GetItemPrice, METH_VARARGS },
{ "GetItemMetinSocket", stub_shop_GetItemMetinSocket, METH_VARARGS },
{ "GetItemAttribute", stub_shop_GetItemAttribute, METH_VARARGS },
{ "GetTabCount", stub_shop_GetTabCount, METH_VARARGS },
{ "GetTabName", stub_shop_GetTabName, METH_VARARGS },
{ "GetTabCoinType", stub_shop_GetTabCoinType, METH_VARARGS },
{ "ClearPrivateShopStock", stub_shop_ClearPrivateShopStock, METH_VARARGS },
{ "AddPrivateShopItemStock", stub_shop_AddPrivateShopItemStock, METH_VARARGS },
{ "DelPrivateShopItemStock", stub_shop_DelPrivateShopItemStock, METH_VARARGS },
{ "GetPrivateShopItemPrice", stub_shop_GetPrivateShopItemPrice, METH_VARARGS },
{ "BuildPrivateShop", stub_shop_BuildPrivateShop, METH_VARARGS },
{ NULL, NULL }
};
// 40250 UserInterface/PythonSoundManagerModule.cpp: snd export names.
PyObject* stub_snd_PlaySound(PyObject*, PyObject*) { return stub_call_named("snd.PlaySound", Py_BuildNone()); }
PyObject* stub_snd_PlaySound3D(PyObject*, PyObject*) { return stub_call_named("snd.PlaySound3D", Py_BuildNone()); }
@@ -420,11 +379,6 @@ void init_2v0_gameplay_stubs() {
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_safebox(module);
}
{
PyObject* module = Py_InitModule("shop", methods_shop);
PyModule_AddIntConstant(module, "__port_stub__", 1);
AddStubConstants_shop(module);
}
{
PyObject* module = Py_InitModule("snd", methods_snd);
PyModule_AddIntConstant(module, "__port_stub__", 1);
@@ -402,6 +402,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
initMiniMap();
initeffect();
initfly();
initshop();
initskill();
initBackground();
initServerStateChecker();
@@ -301,15 +301,113 @@ void CPythonApplication::OnUIRender()
UI::CWindowManager& rkUIMgr=UI::CWindowManager::Instance();
rkUIMgr.Render();
}
BOOL CPythonApplication::SetCursorNum(int) { MT_PLATFORM_STUB(); return FALSE; }
void CPythonApplication::SetCursorVisible(BOOL, bool) { MT_PLATFORM_STUB(); }
BOOL CPythonApplication::GetCursorVisible() { MT_PLATFORM_STUB(); return FALSE; }
bool CPythonApplication::GetLiarCursorOn() { MT_PLATFORM_STUB(); return false; }
void CPythonApplication::SetCursorMode(int) { MT_PLATFORM_STUB(); }
// 40250 returns m_iCursorMode, CURSOR_MODE_HARDWARE from the constructor until SetCursorMode (PythonApplicationCursor.cpp:92).
// PORT: SetCursorMode is still a stub, so the mode stays the constructor's.
int CPythonApplication::GetCursorMode() { return CURSOR_MODE_HARDWARE; }
void CPythonApplication::SetMouseHandler(PyObject*) { MT_PLATFORM_STUB(); }
// 40250 PythonApplicationCursor.cpp. PORT: the members live here until the application object exists
// (initial values from the constructor and CreateCursors); the host draws no hardware cursor, so every
// shape in [0, CURSOR_COUNT) counts as loaded in place of m_CursorHandleMap.
namespace
{
int s_iCursorNum = CPythonApplication::CURSOR_SHAPE_NORMAL; // m_iCursorNum
int s_iContinuousCursorNum = CPythonApplication::CURSOR_SHAPE_NORMAL; // m_iContinuousCursorNum
BOOL s_bCursorVisible = TRUE; // m_bCursorVisible
bool s_bLiarCursorOn = false; // m_bLiarCursorOn
int s_iCursorMode = CPythonApplication::CURSOR_MODE_HARDWARE; // m_iCursorMode
PyObject* s_poMouseHandler = NULL; // m_poMouseHandler
}
void CPythonApplication::SetCursorVisible(BOOL bFlag, bool bLiarCursorOn)
{
s_bCursorVisible = bFlag;
s_bLiarCursorOn = bLiarCursorOn;
// PORT: 40250 also counts ShowCursor up or down in hardware mode; the host has no OS cursor to hide.
}
BOOL CPythonApplication::GetCursorVisible()
{
return s_bCursorVisible;
}
bool CPythonApplication::GetLiarCursorOn()
{
return s_bLiarCursorOn;
}
int CPythonApplication::GetCursorMode()
{
return s_iCursorMode;
}
int CPythonApplication::GetCursorNum()
{
return s_iCursorNum;
}
BOOL CPythonApplication::__IsContinuousChangeTypeCursor(int iCursorNum)
{
switch (iCursorNum)
{
case CURSOR_SHAPE_NORMAL:
case CURSOR_SHAPE_ATTACK:
case CURSOR_SHAPE_TARGET:
case CURSOR_SHAPE_MAGIC:
case CURSOR_SHAPE_BUY:
case CURSOR_SHAPE_SELL:
return TRUE;
break;
}
return FALSE;
}
BOOL CPythonApplication::SetCursorNum(int iCursorNum)
{
if (CURSOR_SHAPE_NORMAL == iCursorNum)
{
if (!__IsContinuousChangeTypeCursor(s_iCursorNum))
{
iCursorNum = s_iContinuousCursorNum;
}
}
else
{
if (__IsContinuousChangeTypeCursor(s_iCursorNum))
{
s_iContinuousCursorNum = s_iCursorNum;
}
}
if (CURSOR_MODE_HARDWARE == s_iCursorMode)
{
// PORT: m_CursorHandleMap.find(iCursorNum) and SetCursor(hCursor).
if (iCursorNum < 0 || iCursorNum >= CURSOR_COUNT)
return FALSE;
}
s_iCursorNum = iCursorNum;
PyCallClassMemberFunc(s_poMouseHandler, "ChangeCursor", Py_BuildValue("(i)", s_iCursorNum));
return TRUE;
}
void CPythonApplication::SetCursorMode(int iMode)
{
switch (iMode)
{
case CURSOR_MODE_HARDWARE:
s_iCursorMode = CURSOR_MODE_HARDWARE;
break;
case CURSOR_MODE_SOFTWARE:
s_iCursorMode = CURSOR_MODE_SOFTWARE;
break;
}
}
// 40250 PythonApplication.cpp:871.
void CPythonApplication::SetMouseHandler(PyObject* poMouseHandler)
{
s_poMouseHandler = poMouseHandler;
}
void CPythonApplication::SetGlobalCenterPosition(LONG, LONG) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetFPS(int) { MT_PLATFORM_STUB(); }
time_t CPythonApplication::GetServerTime() { MT_PLATFORM_STUB(); return 0; }
@@ -1,48 +0,0 @@
// Stand-in for the 40250 logic unit UserInterface/PythonShop.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/PythonShop.h"
#include "../../PlatformStub.h"
CPythonShop::CPythonShop()
{
MT_PLATFORM_STUB();
}
CPythonShop::~CPythonShop()
{
MT_PLATFORM_STUB();
}
auto CPythonShop::Clear() -> void
{
MT_PLATFORM_STUB();
}
auto CPythonShop::SetItemData(DWORD, const TShopItemData &) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonShop::SetItemData(BYTE, DWORD, const TShopItemData &) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonShop::SetTabCoinType(BYTE, BYTE) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonShop::SetTabName(BYTE, const char *) -> void
{
MT_PLATFORM_STUB();
}
auto CPythonShop::IsOpen() -> BOOL
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<BOOL>();
}