fix: trade gate round 4 - "The trade has been cancelled." + retail's

staged-item trading marker

- Cancel text: ClientTradeSystem::Handle_Trade__Recv_CloseTrade
  @0x0056DE30 shows "The trade has been cancelled." UNCONDITIONALLY
  (every close reason) as 0x1A ClientLocal - the yellow top-center
  SpewBox line. Wired at the router's onTradeClose beside ApplyClose;
  the string lives in ClientTextRefusals with its citation.
- Staged-item marker: retail's mechanism decoded end-to-end - the
  UIItem prototype (catalog 0x21000037) authors overlay child
  0x10000438 (sprite 0x06001DAE, the green frame + corner trade icon),
  bound @0x004E18FC and SetVisible(tradeState != 0) @0x004E2420;
  gmSecureTradeUI::AddItem @0x004CA801 sets
  ACCWeenieObject::SetTradeState(1) on YOUR staged items. Ported as:
  UiItemSlot.ShowTradeOverlay + TradeOverlaySprite (drawn over the
  icon), set on the trade window's self-grid cells; and
  RuntimeTradeState now borrows the canonical object table and
  maintains ClientObject.TradeState (1 at stage, 0 at remove/failure/
  reset/close/clear) - which also brings the ALREADY-PORTED placement
  policy's "You cannot move an item while it is being traded" refusal
  to life (its input field previously had no live producer).

Runtime 1,626, App 4,992/3 - green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 12:45:10 +02:00
parent ffc73f80bf
commit be3e617a2b
7 changed files with 119 additions and 6 deletions

View file

@ -48,6 +48,13 @@ public sealed class SecureTradeUiController : IRetainedPanelController
/// 0x1000007F states '', 'Highlight', 'Ghosted').</summary>
private const string AcceptedState = "Highlight";
/// <summary>The UIItem prototype's authored trade-state overlay sprite
/// (catalog 0x21000037 element 0x10000438 — the green frame + corner
/// trade icon; bound @ 0x004E18FC, shown when tradeState != 0
/// @ 0x004E2420). Retail sets tradeState=1 on YOUR staged items
/// (gmSecureTradeUI::AddItem @ 0x004CA801), so the self grid marks.</summary>
private const uint TradeOverlaySpriteId = 0x06001DAEu;
public sealed record Bindings(
IRuntimeTradeView Trade,
ClientObjectTable Objects,
@ -289,6 +296,9 @@ public sealed class SecureTradeUiController : IRetainedPanelController
// Staged rows are not drag sources — ACE has no
// per-item removal (only Clear All / reset).
AllowDragSource = false,
// Your staged items carry retail's trading marker.
ShowTradeOverlay = side == RuntimeTradeSide.Self,
TradeOverlaySprite = TradeOverlaySpriteId,
};
cell.SetItem(guid, icon);
list.AddItem(cell);

View file

@ -114,6 +114,15 @@ public class UiItemSlot : UiElement
/// <summary>RenderSurface id -> (GL texture, w, h). Set by the factory/controller.</summary>
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
/// <summary>Draw the retail trading marker over the icon — the UIItem
/// prototype's authored overlay child 0x10000438 (sprite 0x06001DAE),
/// shown when the underlying object's trade state is set
/// (<c>@ 0x004E2420</c>).</summary>
public bool ShowTradeOverlay { get; set; }
/// <summary>The authored overlay sprite id (0x06001DAE); 0 = no draw.</summary>
public uint TradeOverlaySprite { get; set; }
/// <summary>
/// DAT-authored 10%..100% radial cooldown overlays from element ids
/// <c>0x1000054F..0x10000558</c>.
@ -391,6 +400,19 @@ public class UiItemSlot : UiElement
ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
}
// Trade-state overlay: UIElement_UIItem's authored child 0x10000438
// (sprite 0x06001DAE — the green frame + corner trade icon), bound at
// @ 0x004E18FC and SetVisible(tradeState != 0) at @ 0x004E2420.
if (ShowTradeOverlay
&& ItemId != 0
&& SpriteResolve is not null
&& TradeOverlaySprite != 0)
{
var (overlayTex, _, _) = SpriteResolve(TradeOverlaySprite);
if (overlayTex != 0)
ctx.DrawSprite(overlayTex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
}
// Digit overlay: UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
// Occupancy branch (decomp 229481):
// occupied (ItemId != 0) → regular/ghosted digit set 0x10000042/43