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:
shenlei
2026-09-24 00:59:05 +09:00
co-authored by Claude Opus 5.5
parent 9298500df9
commit 72b19b677d
14 changed files with 853 additions and 162 deletions
@@ -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;