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
+1
View File
@@ -542,3 +542,4 @@
{"time": "2026-09-24T18:00:00Z", "event": "correction", "unit": "batch 3-c todo", "detail": "The sword next to the actor in python_game_render_test is the fake server ground item (GC_ITEM_GROUND_ADD vnum 19, P3-b), not a weapon-attach bug; the fake character has no weapon, AttachWeapon is never called. Todo withdrawn."}
{"time": "2026-09-24T19:00:00Z", "event": "port", "unit": "batch 4-a text input: EterLib/IME.cpp (ADAPTED), UserInterface/PythonIME.cpp, PythonIMEModule.cpp", "detail": "ime module ported; IMM/TSF-free CIME in platform/; Godot key events -> ui_ime_key/ui_char -> CWindowManager RunIME*; login typed through the IME; in-game chat CG_CHAT round-trip", "tests": ["port.login_flow (typed id/password with edits, chat send/echo/close)", "port.login_live", "python_game_render_test (Godot key events)"]}
{"time": "2026-09-24T20:00:00Z", "event": "verify", "unit": "batch 4-b combat: InstanceBaseBattle / ActorInstanceCollisionDetection / PythonPlayerInput (already ported)", "detail": "fake server spawns a stray dog outside the BANPK safe disc, answers CG_TARGET/CG_ATTACK with GC_TARGET/GC_DAMAGE_INFO/GC_DEAD; held left button walks up and combo-attacks until death; damage numbers wait on CPythonSystem (4-g)", "tests": ["port.login_flow (step 9 held-button kill)", "port.login_live", "python_game_render_test (Godot mouse events, target HP 100/66/33/0)"]}
{"time": "2026-09-24T21:00:00Z", "event": "port", "unit": "batch 4-c shop: UserInterface/PythonShop.cpp (+ PythonApplicationCursor.cpp, SetMouseHandler adapted)", "detail": "shop module ported verbatim; app cursor state moved into the platform CPythonApplication (uishop reads app.GetCursor, which dereferenced the missing application object); fake server general store answers CG_ON_CLICK/CG_SHOP BUY/SELL2/END like CShop::AddGuest/Buy and CShopManager::Sell", "tests": ["port.login_flow (step 10 buy/sell/ESC)", "port.login_live", "python_game_render_test"]}
@@ -47,7 +47,12 @@
"status": "TODO"
},
"CPythonApplication::SetMouseHandler": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:SetMouseHandler"
],
"note": "Body verbatim on a file-static m_poMouseHandler (no application object yet); SetCursorNum calls its ChangeCursor.",
"test": "extension/tests/port_login_flow_test.cpp (step 10)"
},
"CPythonApplication::CheckDeviceState": {
"status": "TODO"
@@ -0,0 +1,72 @@
{
"reference": "UserInterface/PythonApplicationCursor.cpp",
"reference_sha256": "acd34facc55665509839d75c04feb0af074d6db40f39195c01af492a40b31e93",
"priority": "P4",
"contracts": [],
"functions": {
"CPythonApplication::CreateCursors": {
"status": "N_A",
"note": "Win32 LoadImage/DestroyCursor of the IDC_CURSOR_* resources; the host draws no hardware cursor, so SetCursorNum treats every shape in [0, CURSOR_COUNT) as loaded."
},
"CPythonApplication::DestroyCursors": {
"status": "N_A",
"note": "Win32 LoadImage/DestroyCursor of the IDC_CURSOR_* resources; the host draws no hardware cursor, so SetCursorNum treats every shape in [0, CURSOR_COUNT) as loaded."
},
"CPythonApplication::SetCursorVisible": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:SetCursorVisible"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::GetCursorVisible": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:GetCursorVisible"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::GetLiarCursorOn": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:GetLiarCursorOn"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::GetCursorMode": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:GetCursorMode"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::__IsContinuousChangeTypeCursor": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:__IsContinuousChangeTypeCursor"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::SetCursorNum": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:SetCursorNum"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
},
"CPythonApplication::SetCursorMode": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/UserInterface/PythonApplication.cpp:SetCursorMode"
],
"note": "Body verbatim in the platform CPythonApplication stand-in on file-static members (there is no application object yet); ShowCursor/SetCursor calls dropped.",
"test": "extension/tests/port_login_flow_test.cpp (step 10: uishop reads app.GetCursor)"
}
}
}
@@ -0,0 +1,236 @@
{
"reference": "UserInterface/PythonShop.cpp",
"reference_sha256": "23c46a5fe95a979c2be6a52fbd4699e6cba6c6a66dca10bc2dc7d720017bb2c5",
"priority": "P3",
"contracts": [],
"functions": {
"CPythonShop::SetTabCoinType": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::SetTabCoinType"
]
},
"CPythonShop::GetTabCoinType": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::GetTabCoinType"
]
},
"CPythonShop::SetTabName": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::SetTabName"
]
},
"CPythonShop::GetTabName": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::GetTabName"
]
},
"CPythonShop::SetItemData": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::SetItemData"
]
},
"CPythonShop::GetItemData": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::GetItemData"
]
},
"CPythonShop::ClearPrivateShopStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::ClearPrivateShopStock"
]
},
"CPythonShop::AddPrivateShopItemStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::AddPrivateShopItemStock"
]
},
"CPythonShop::DelPrivateShopItemStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::DelPrivateShopItemStock"
]
},
"CPythonShop::GetPrivateShopItemPrice": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::GetPrivateShopItemPrice"
]
},
"CPythonShop::BuildPrivateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::BuildPrivateShop"
]
},
"CPythonShop::Open": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::Open"
]
},
"CPythonShop::Close": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::Close"
]
},
"CPythonShop::IsOpen": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::IsOpen"
]
},
"CPythonShop::IsPrivateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::IsPrivateShop"
]
},
"CPythonShop::IsMainPlayerPrivateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::IsMainPlayerPrivateShop"
]
},
"CPythonShop::Clear": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::Clear"
]
},
"CPythonShop::CPythonShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::CPythonShop"
]
},
"CPythonShop::~CPythonShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:CPythonShop::~CPythonShop"
]
},
"shopOpen": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopOpen"
]
},
"shopClose": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopClose"
]
},
"shopIsOpen": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopIsOpen"
]
},
"shopIsPrviateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopIsPrviateShop"
]
},
"shopIsMainPlayerPrivateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopIsMainPlayerPrivateShop"
]
},
"shopGetItemID": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetItemID"
]
},
"shopGetItemCount": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetItemCount"
]
},
"shopGetItemPrice": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetItemPrice"
]
},
"shopGetItemMetinSocket": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetItemMetinSocket"
]
},
"shopGetItemAttribute": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetItemAttribute"
]
},
"shopClearPrivateShopStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopClearPrivateShopStock"
]
},
"shopAddPrivateShopItemStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopAddPrivateShopItemStock"
]
},
"shopDelPrivateShopItemStock": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopDelPrivateShopItemStock"
]
},
"shopGetPrivateShopItemPrice": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetPrivateShopItemPrice"
]
},
"shopBuildPrivateShop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopBuildPrivateShop"
]
},
"shopGetTabCount": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetTabCount"
]
},
"shopGetTabName": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetTabName"
]
},
"shopGetTabCoinType": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:shopGetTabCoinType"
]
},
"initshop": {
"status": "PORTED",
"impl": [
"extension/src/port/UserInterface/PythonShop.cpp:initshop"
]
}
}
}