feat(runtime): Slice 5.3 — RuntimeInventoryState owns the vendor browse session
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The sole VendorState joins the J4.2 inventory owners: populated by the new 0x0062 ApproachVendor route (parse via VendorApproach, wire-to- domain mapping at the routing seam, silent-drop on malformed like every sibling), borrowed by both graphical and headless hosts, and torn down through the EXISTING ExternalContainer reset stage — session reset, portal-out, and logout all funnel through the one mechanism. Close is client-local per retail (nothing on the wire): a range watcher rides the existing per-advanced-frame publishMovement callback, using the vendor's own authored UseRadius (ACE's 0.6 m fallback when absent). The dormant ItemInteractionController ActiveVendorId seam is finally wired as a live delegate — real id while open, 0 the moment the session clears. AP-160 filed in this same commit: the watcher measures plain 3D center distance rather than retail's cylinder-gap, because Runtime has no per-NPC collision radius/height source; bounded sub-meter, client- local UI only. Twelve Runtime tests: populate/field mapping, vendor replacement, range clear + within-range retention, all three generation teardowns, the ActiveVendorId seam, malformed-event drop. Clean-room complete solution: 11,302 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
70f37dbd5c
commit
9796d71522
12 changed files with 835 additions and 9 deletions
|
|
@ -83,6 +83,10 @@ public static class GameEventWiring
|
|||
Action<uint /*options1*/, uint /*options2*/>? onCharacterOptions = null,
|
||||
Func<double>? clientTime = null,
|
||||
ExternalContainerState? externalContainers = null,
|
||||
// Slice 5.3: the vendor browse session owner. Matches the existing
|
||||
// itemMana/friends/squelch/externalContainers pattern — optional so
|
||||
// every existing caller compiles unchanged.
|
||||
VendorState? vendor = null,
|
||||
Func<bool>? accepting = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dispatcher);
|
||||
|
|
@ -375,6 +379,51 @@ public static class GameEventWiring
|
|||
items.UpdateHouseRestrictions(p.Value.SenderId, p.Value.Restrictions);
|
||||
});
|
||||
|
||||
// Slice 5.3: ApproachVendor (0x0062) — the sole wire message that
|
||||
// opens a vendor's shop; it rides the ordinary Use action, there is
|
||||
// no separate "open vendor" opcode (research doc
|
||||
// docs/research/2026-08-08-slice5-vendor-browse-research.md §A.1-A.2).
|
||||
// Every event is a COMPLETE REPLACE (§A.3) — VendorState.Apply is a
|
||||
// single-phase authoritative-replace call, matching that contract.
|
||||
// A malformed payload is dropped silently: every sibling handler in
|
||||
// this section (WieldObject, InventoryPutObjInContainer,
|
||||
// HouseUpdateRestrictions above, ViewContents/CloseGroundContainer
|
||||
// below) uses the same `if (p is null) return;` shape with no
|
||||
// logging — there is no established parse-failure logging
|
||||
// convention in this file to deviate from.
|
||||
registrar.Register(GameEventType.ApproachVendor, e =>
|
||||
{
|
||||
var p = VendorApproach.TryParse(e.Payload.Span);
|
||||
if (p is null) return;
|
||||
|
||||
var profile = new VendorShopProfile(
|
||||
p.Value.Profile.MerchandiseItemTypes,
|
||||
p.Value.Profile.MerchandiseMinValue,
|
||||
p.Value.Profile.MerchandiseMaxValue,
|
||||
p.Value.Profile.DealMagicalItems,
|
||||
p.Value.Profile.BuyPrice,
|
||||
p.Value.Profile.SellPrice,
|
||||
p.Value.Profile.AlternateCurrencyWcid,
|
||||
p.Value.Profile.AlternateCurrencyAmount,
|
||||
p.Value.Profile.AlternateCurrencyPluralName);
|
||||
|
||||
var shopItems = new VendorShopItem[p.Value.Items.Count];
|
||||
for (int i = 0; i < shopItems.Length; i++)
|
||||
{
|
||||
VendorApproach.ItemProfile item = p.Value.Items[i];
|
||||
shopItems[i] = new VendorShopItem(
|
||||
item.ItemGuid,
|
||||
item.StackSize,
|
||||
item.Desc.WeenieClassId,
|
||||
item.Desc.Name,
|
||||
item.Desc.ItemType,
|
||||
item.Desc.IconId,
|
||||
item.Desc.Value);
|
||||
}
|
||||
|
||||
vendor?.Apply(p.Value.VendorGuid, profile, shopItems);
|
||||
});
|
||||
|
||||
// ViewContents (0x0196) — the server's AUTHORITATIVE full contents list for a container you
|
||||
// opened (Use 0x0036). Treat it as a full projection-only REPLACE: update membership without
|
||||
// inventing ContainerSlot values, then publish one ContainerContentsReplaced notification so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue