feat: secure trade with other players - wire, RuntimeTradeState, the
authored gmSecureTradeUI window, and both retail open paths
Three-lane research first (docs/research/2026-08-14-trade-lane{A,B,C}):
retail gmSecureTradeUI decode, the byte-exact ACE/decomp/holtburger
three-way wire agreement, and the acdream seam map (which found both
open paths ALREADY classified by the ported policy - OpenSecureTrade on
Use-a-player, StartSecureTrade on drag-item-onto-player with the
DragItemOnPlayerOpensSecureTrade option - dead-ending at a stub toast).
- Core.Net: TradeRequests builders (0x1F6-0x204, retail's CM_Trade
senders byte-checked against ACE's readers; the ACE-discarded
AcceptTrade echo carries zero-count item lists - AD-94), corrected +
completed inbound parsers (0x1FD-0x208; the old AddToTrade parser
missed the SIDE dword, TradeFailure missed the reason), delegate-hole
registrars, six WorldSession sends. 10 golden-byte tests.
- Runtime: RuntimeTradeState, the third sibling J-owner (fellowship/
allegiance shape): session-scoped, clears at generation reset (new
stage Trade=14), staged teardown stage 11 (Identity/EntityObjects
shift 12/13, TeardownStageCount 14 - the FA2-era per-stage-flag test
caught the mapping exactly as designed), combined ownership ledger,
event routing with ACE's wrong-initiator RegisterTrade landmine
honored (partner = whichever guid is not mine). 7 conformance tests.
- App: SecureTradeUiController binds the dedicated authored LayoutDesc
0x2100000D (root 0x1000007A - gmSecureTradeUI::PostInit's exact ids):
partner name/status/count/grid, the authored 'Trade' accept toggle
(accept <-> decline withdraw), 'Clear All' (ACE clears BOTH sides -
surfaced honestly), the X close, drop-on-your-grid staging, per-mode
accept cues (partner icon's authored Highlight state + Trade button
Selected latch). Mounted via the vendor recipe (nine-slice chrome,
hidden until RegisterTrade). ItemInteractionController's two policy
arms now raise SecureTradeRequested instead of the stub toast; the
drag path queues the dragged item until the window registers
(ClientTradeSystem::AttemptToTradeItem @0x0056DF80's shape).
Register: AD-94 (accept-echo zero-count lists), AD-95 (numeric-only
count texts pending template verification).
Suites: App 4,990/3, Core.Net 905, Runtime 1,626 - all green. The
panel itself is user-gate acceptance (two-client connected trade), the
#372-class lesson: fixture-green alone is not acceptance for a mount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bee38b0746
commit
067cbea8a5
26 changed files with 2682 additions and 42 deletions
|
|
@ -283,7 +283,10 @@ public sealed record SocialRuntimeBindings(
|
|||
Func<uint, RuntimeCommandResult> AllegianceSwear,
|
||||
Func<uint, RuntimeCommandResult> AllegianceBreak,
|
||||
Func<uint, RuntimeCommandResult> AllegianceKick,
|
||||
Func<bool, RuntimeCommandResult> AllegianceSetUpdateSubscription);
|
||||
Func<bool, RuntimeCommandResult> AllegianceSetUpdateSubscription,
|
||||
// Secure trade (2026-08-14): the third sibling J-owner's borrowed view.
|
||||
// Trailing/optional per the established compatibility convention.
|
||||
AcDream.Runtime.Gameplay.IRuntimeTradeView? Trade = null);
|
||||
|
||||
public sealed record InventoryRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
|
|
@ -478,6 +481,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountInventory();
|
||||
MountExternalContainer();
|
||||
MountVendor();
|
||||
MountSecureTrade();
|
||||
MountItemCooldowns();
|
||||
Host.WindowManager.WindowVisibilityChanged += OnWindowVisibilityChanged;
|
||||
BindToolbarPanelButtons();
|
||||
|
|
@ -607,6 +611,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
LinkStatusUiController?.Tick();
|
||||
IndicatorBarController?.Tick();
|
||||
JumpPowerbarController?.Tick();
|
||||
SecureTradeController?.Tick();
|
||||
SelectedObjectController?.Tick(deltaSeconds);
|
||||
ExternalContainerController?.Tick();
|
||||
SocialPanelController?.Tick();
|
||||
|
|
@ -3470,6 +3475,102 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
Console.WriteLine("[M4] retail vendor browse panel mounted from LayoutDesc 0x21000012.");
|
||||
}
|
||||
|
||||
/// <summary>The mounted secure-trade window's controller — null until
|
||||
/// <see cref="MountSecureTrade"/> runs (or when the trade view/layout is
|
||||
/// unavailable).</summary>
|
||||
public Layout.SecureTradeUiController? SecureTradeController { get; private set; }
|
||||
|
||||
private void MountSecureTrade()
|
||||
{
|
||||
if (_bindings.Social.Trade is not { } tradeView)
|
||||
{
|
||||
Console.WriteLine("[M4] secure trade: no runtime trade view bound.");
|
||||
return;
|
||||
}
|
||||
|
||||
ImportedLayout? layout;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
layout = LayoutImporter.Import(
|
||||
_bindings.Assets.Dats,
|
||||
Layout.SecureTradeUiController.LayoutId,
|
||||
Layout.SecureTradeUiController.RootId,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont);
|
||||
}
|
||||
if (layout is null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"[M4] secure trade: LayoutDesc 0x2100000D root 0x1000007A not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bus = _bindings.Options.CommandBus();
|
||||
Layout.SecureTradeUiController? controller =
|
||||
Layout.SecureTradeUiController.Bind(
|
||||
layout,
|
||||
new Layout.SecureTradeUiController.Bindings(
|
||||
Trade: tradeView,
|
||||
Objects: _bindings.Inventory.Objects,
|
||||
ResolveIcon: _bindings.Inventory.ResolveIcon,
|
||||
OpenTrade: partner => bus.Publish(
|
||||
new OpenTradeNegotiationsRuntimeCmd(partner)),
|
||||
CloseTrade: () => bus.Publish(
|
||||
new CloseTradeNegotiationsRuntimeCmd()),
|
||||
AddToTrade: item => bus.Publish(
|
||||
new AddToTradeRuntimeCmd(item)),
|
||||
AcceptTrade: (selfAccepted, partnerAccepted, partner) =>
|
||||
bus.Publish(new AcceptTradeRuntimeCmd(
|
||||
partner, selfAccepted, partnerAccepted)),
|
||||
DeclineTrade: () => bus.Publish(new DeclineTradeRuntimeCmd()),
|
||||
ResetTrade: () => bus.Publish(new ResetTradeRuntimeCmd()),
|
||||
SetWindowVisible: visible =>
|
||||
{
|
||||
if (visible) Host.ShowWindow(WindowNames.SecureTrade);
|
||||
else Host.HideWindow(WindowNames.SecureTrade);
|
||||
}));
|
||||
if (controller is null)
|
||||
{
|
||||
Console.WriteLine("[M4] secure trade: required authored grids are missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
UiElement root = layout.Root;
|
||||
RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = WindowNames.SecureTrade,
|
||||
// Root 0x1000007A authors content only (the in-screen copy's
|
||||
// bevel lives in LayoutDesc 0x21000005, not here) — the
|
||||
// shared nine-slice frame surrounds it, same as the vendor.
|
||||
Chrome = RetailWindowChrome.NineSlice,
|
||||
Left = MathF.Max(0f, (Host.Root.Width - root.Width) * 0.5f),
|
||||
Top = MathF.Max(0f, (Host.Root.Height - root.Height) * 0.5f),
|
||||
ContentWidth = root.Width,
|
||||
ContentHeight = root.Height,
|
||||
MinWidth = root.Width,
|
||||
MinHeight = root.Height,
|
||||
Visible = false,
|
||||
ResizeX = false,
|
||||
ResizeY = false,
|
||||
ConstrainDragToParent = true,
|
||||
ConstrainResizeToParent = true,
|
||||
});
|
||||
|
||||
SecureTradeController = controller;
|
||||
Host.WindowManager.AttachController(WindowNames.SecureTrade, controller);
|
||||
// Both retail open paths (Use-on-player, drag-item-on-player) —
|
||||
// raised by ItemInteractionController's policy execution arms.
|
||||
_bindings.Inventory.ItemInteraction.SecureTradeRequested +=
|
||||
controller.RequestSecureTrade;
|
||||
Console.WriteLine(
|
||||
"[M4] retail secure trade panel mounted from LayoutDesc 0x2100000D.");
|
||||
}
|
||||
|
||||
private void MountItemCooldowns()
|
||||
{
|
||||
ItemCooldownAssets? assets;
|
||||
|
|
@ -3506,6 +3607,11 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
_characterSheetSubscription?.Dispose();
|
||||
Host.WindowManager.WindowVisibilityChanged -= OnWindowVisibilityChanged;
|
||||
WindowOpacity.Dispose();
|
||||
if (SecureTradeController is { } trade)
|
||||
{
|
||||
_bindings.Inventory.ItemInteraction.SecureTradeRequested -=
|
||||
trade.RequestSecureTrade;
|
||||
}
|
||||
},
|
||||
() => _itemConfirmationController?.Dispose(),
|
||||
() => _gameplayConfirmationController?.Dispose(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue