port: PythonItem + item module (batch 2 P3-b)

Verbatim 40250 CPythonItem and the item Python module replace the
pending stand-in and the module stub; CPythonApplication::Create calls
m_pyItem.Create() in 40250 order. itemGetIconImage returns its handle
through Py_BuildPointer (the ILP32 int truncated it and wndMgr.SetSlot
crashed on the live inventory). CMapManager::GetNormal evaluates
CTerrain::CalculateNormal on the native height tile; the item text-tail
members stay stubs, Pick answering -1 like an empty tail map.

The fake server drops vnum 19 next to the actor at entergame and answers
CG_ITEM_PICKUP with GC_ITEM_GROUND_DEL; login_flow picks it up through
player.PickCloseItem and checks item.SelectItem(19).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 00:50:16 +09:00
co-authored by Claude Opus 5.5
parent f0564a7f12
commit 9298500df9
19 changed files with 2178 additions and 454 deletions
@@ -550,6 +550,19 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + sizeof(greet)), 2 /* CHAT_TYPE_NOTICE */, 0, 1};
if (!c.Send(chat) || !c.Send(greet, sizeof(greet)))
return Fail(name + ": send greet notice");
// CItem::EncodeInsertPacket for an owned item in view: GC_ITEM_GROUND_ADD, then GC_ITEM_OWNERSHIP.
GCItemGroundAdd ground = zeroed<GCItemGroundAdd>();
ground.header = HDR_GC_ITEM_GROUND_ADD;
ground.x = kGroundItemX;
ground.y = kGroundItemY;
ground.vid = kGroundItemVID;
ground.vnum = kGroundItemVnum;
GCItemOwnership ownership = zeroed<GCItemOwnership>();
ownership.header = HDR_GC_ITEM_OWNERSHIP;
ownership.vid = kGroundItemVID;
std::strncpy(ownership.owner, kCharacterName, sizeof(ownership.owner) - 1);
if (!c.Send(ground) || !c.Send(ownership))
return Fail(name + ": send ground item");
Event(name + ":burst_sent");
// A ping after the burst proves that GamePhase consumed every packet above and can reply.
@@ -593,6 +606,16 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
return Fail(name + ": SEQUENCE mismatch on CG header " + std::to_string(header));
c.sequence = (c.sequence + 1) % SEQUENCE_TABLE_SIZE;
}
// CInputMain::ItemPickup → CHARACTER::PickupItem → CItem::RemoveFromGround → EncodeRemovePacket.
if (header == HDR_CG_ITEM_PICKUP)
{
CGItemPickup pickup;
std::memcpy(&pickup, packet, sizeof(pickup));
Event(name + ":pickup " + std::to_string(pickup.vid));
GCItemGroundDel del = {HDR_GC_ITEM_GROUND_DEL, pickup.vid};
if (pickup.vid == kGroundItemVID && !c.Send(del))
return Fail(name + ": send GC_ITEM_GROUND_DEL");
}
if (header == HDR_CG_MOVE)
{
CGMove move;