port: P3-c PythonExchange + exchange module (1:1 40250)
Copy PythonExchange.cpp/PythonExchangeModule.cpp verbatim, drop the pending stand-in and the exchange module stub, register initTrade() after initNonPlayer(). The fake server answers CG_EXCHANGE START with the partner's item + gold and CANCEL with END; port.login_flow drives the exchange round-trip through the Python exchange/net modules and uiexchange. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9298500df9
commit
72b19b677d
@@ -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<GCExchange>();
|
||||
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<GCExchange>();
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user