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
|
|
@ -71,6 +71,38 @@ public sealed class PowerbarLayoutProbeTests
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>2026-08-14 trade research: locate the LayoutDesc that authors
|
||||
/// gmSecureTradeUI's element tree (lane A gave element ids 0x10000085-8B
|
||||
/// but not the layout id) and dump it.</summary>
|
||||
[Fact]
|
||||
public void ProbeSecureTradeLayout()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_POWERBAR") != "1")
|
||||
return;
|
||||
|
||||
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||
?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call");
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var strings = new DatStringResolver(dats);
|
||||
|
||||
int scanned = 0;
|
||||
for (uint layoutId = 0x21000000u; layoutId <= 0x210001FFu; layoutId++)
|
||||
{
|
||||
ElementInfo? root = LayoutImporter.ImportInfos(dats, layoutId);
|
||||
if (root is null)
|
||||
continue;
|
||||
scanned++;
|
||||
if (FindById(root, 0x10000088u) is null)
|
||||
continue;
|
||||
Console.WriteLine($"[pbprobe] SECURE TRADE LAYOUT = 0x{layoutId:X8}");
|
||||
DumpElement(strings, root, 0);
|
||||
}
|
||||
Console.WriteLine($"[pbprobe] scanned {scanned} layouts");
|
||||
}
|
||||
|
||||
private static ElementInfo? FindById(ElementInfo element, uint id)
|
||||
{
|
||||
if (element.Id == id) return element;
|
||||
|
|
|
|||
143
tests/AcDream.Core.Net.Tests/Messages/TradeRequestsTests.cs
Normal file
143
tests/AcDream.Core.Net.Tests/Messages/TradeRequestsTests.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Net.Tests.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Golden-byte coverage for the secure-trade GameAction builders and inbound
|
||||
/// parsers — the 2026-08-14 lane B wire tables (ACE + retail decomp +
|
||||
/// holtburger three-way agreement).
|
||||
/// </summary>
|
||||
public sealed class TradeRequestsTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildOpenTradeNegotiations_WritesEnvelopeSequenceOpcodePartner()
|
||||
{
|
||||
byte[] body = TradeRequests.BuildOpenTradeNegotiations(7, 0x50001234u);
|
||||
|
||||
Assert.Equal(16, body.Length);
|
||||
Assert.Equal(TradeRequests.GameActionEnvelope,
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(0)));
|
||||
Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(4)));
|
||||
Assert.Equal(0x01F6u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8)));
|
||||
Assert.Equal(0x50001234u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x01F7u)] // CloseTradeNegotiations
|
||||
[InlineData(0x01FBu)] // DeclineTrade
|
||||
[InlineData(0x0204u)] // ResetTrade
|
||||
public void EmptyBodiedActions_WriteEnvelopeSequenceOpcodeOnly(uint opcode)
|
||||
{
|
||||
byte[] body = opcode switch
|
||||
{
|
||||
0x01F7u => TradeRequests.BuildCloseTradeNegotiations(3),
|
||||
0x01FBu => TradeRequests.BuildDeclineTrade(3),
|
||||
_ => TradeRequests.BuildResetTrade(3),
|
||||
};
|
||||
|
||||
Assert.Equal(12, body.Length);
|
||||
Assert.Equal(TradeRequests.GameActionEnvelope,
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(0)));
|
||||
Assert.Equal(3u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(4)));
|
||||
Assert.Equal(opcode, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAddToTrade_WritesItemAndSlot()
|
||||
{
|
||||
byte[] body = TradeRequests.BuildAddToTrade(5, 0x60000001u, 2u);
|
||||
|
||||
Assert.Equal(20, body.Length);
|
||||
Assert.Equal(0x01F8u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8)));
|
||||
Assert.Equal(0x60000001u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12)));
|
||||
Assert.Equal(2u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(16)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAcceptTrade_WritesTradePackFixedFieldsAndTwoEmptyLists()
|
||||
{
|
||||
byte[] body = TradeRequests.BuildAcceptTrade(
|
||||
gameActionSequence: 11,
|
||||
partnerGuid: 0x50000B0Bu,
|
||||
tradeStamp: 0d,
|
||||
tradeStatus: 0u,
|
||||
initiatorGuid: 0x5000A0A0u,
|
||||
initiatorAccepts: true,
|
||||
partnerAccepts: false);
|
||||
|
||||
Assert.Equal(48, body.Length);
|
||||
Assert.Equal(0x01FAu, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8)));
|
||||
Assert.Equal(0x50000B0Bu, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12)));
|
||||
Assert.Equal(0d, BinaryPrimitives.ReadDoubleLittleEndian(body.AsSpan(16)));
|
||||
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(24)));
|
||||
Assert.Equal(0x5000A0A0u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(28)));
|
||||
Assert.Equal(1u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(32)));
|
||||
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(36)));
|
||||
// Two zero-count PackableLists (AD-94 — ACE discards the payload).
|
||||
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(40)));
|
||||
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(44)));
|
||||
}
|
||||
|
||||
// ── Inbound parsers ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ParseRegisterTrade_ReadsInitiatorPartnerStamp()
|
||||
{
|
||||
byte[] payload = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x50000001u);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), 0x50000002u);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(8), 0uL);
|
||||
|
||||
var parsed = GameEvents.ParseRegisterTrade(payload);
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x50000001u, parsed!.Value.Initiator);
|
||||
Assert.Equal(0x50000002u, parsed.Value.Partner);
|
||||
Assert.Equal(0uL, parsed.Value.Stamp);
|
||||
Assert.Null(GameEvents.ParseRegisterTrade(payload.AsSpan(0, 12)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseAddToTrade_ReadsGuidSideSlot()
|
||||
{
|
||||
byte[] payload = new byte[12];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x60000009u);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), 2u);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(8), 0u);
|
||||
|
||||
var parsed = GameEvents.ParseAddToTrade(payload);
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x60000009u, parsed!.Value.ItemGuid);
|
||||
Assert.Equal(2u, parsed.Value.Side);
|
||||
Assert.Equal(0u, parsed.Value.SlotIndex);
|
||||
Assert.Null(GameEvents.ParseAddToTrade(payload.AsSpan(0, 8)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseTradeFailure_ReadsGuidAndReason()
|
||||
{
|
||||
byte[] payload = new byte[8];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x60000042u);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), 0x426u);
|
||||
|
||||
var parsed = GameEvents.ParseTradeFailure(payload);
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x60000042u, parsed!.Value.ItemGuid);
|
||||
Assert.Equal(0x426u, parsed.Value.Reason);
|
||||
Assert.Null(GameEvents.ParseTradeFailure(payload.AsSpan(0, 4)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleGuidParsers_ReadTheGuid()
|
||||
{
|
||||
byte[] payload = new byte[4];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x50000C0Cu);
|
||||
|
||||
Assert.Equal(0x50000C0Cu, GameEvents.ParseAcceptTrade(payload));
|
||||
Assert.Equal(0x50000C0Cu, GameEvents.ParseDeclineTrade(payload));
|
||||
Assert.Equal(0x50000C0Cu, GameEvents.ParseResetTrade(payload));
|
||||
Assert.Equal(0x50000C0Cu, GameEvents.ParseCloseTrade(payload));
|
||||
}
|
||||
}
|
||||
|
|
@ -239,6 +239,7 @@ public sealed class GameRuntimeTests
|
|||
GameRuntimeTeardownStage.CommunicationDisposed,
|
||||
GameRuntimeTeardownStage.FellowshipDisposed,
|
||||
GameRuntimeTeardownStage.AllegianceDisposed,
|
||||
GameRuntimeTeardownStage.TradeDisposed,
|
||||
GameRuntimeTeardownStage.IdentityDisposed,
|
||||
GameRuntimeTeardownStage.EntityObjectsDisposed,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
var movement = new RuntimeLocalPlayerMovementState();
|
||||
var fellowship = new RuntimeFellowshipState();
|
||||
var allegiance = new RuntimeAllegianceState();
|
||||
var trade = new RuntimeTradeState();
|
||||
|
||||
movement.Execute(RuntimeMovementCommand.ToggleRunLock);
|
||||
inventory.Transactions.IncrementBusyCount();
|
||||
|
|
@ -38,7 +39,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
actions,
|
||||
movement,
|
||||
fellowship,
|
||||
allegiance);
|
||||
allegiance,
|
||||
trade);
|
||||
Assert.False(populated.IsConverged);
|
||||
|
||||
movement.Dispose();
|
||||
|
|
@ -48,6 +50,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
inventory.Dispose();
|
||||
fellowship.Dispose();
|
||||
allegiance.Dispose();
|
||||
trade.Dispose();
|
||||
entities.Dispose();
|
||||
|
||||
RuntimeSimulationOwnershipSnapshot retired =
|
||||
|
|
@ -59,7 +62,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
actions,
|
||||
movement,
|
||||
fellowship,
|
||||
allegiance);
|
||||
allegiance,
|
||||
trade);
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.True(retired.EntityObjects.IsDisposed);
|
||||
Assert.True(retired.Physics.IsDisposed);
|
||||
|
|
@ -76,6 +80,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
var movement = new RuntimeLocalPlayerMovementState();
|
||||
var fellowship = new RuntimeFellowshipState();
|
||||
var allegiance = new RuntimeAllegianceState();
|
||||
var trade = new RuntimeTradeState();
|
||||
movement.Execute(RuntimeMovementCommand.ToggleRunLock);
|
||||
inventory.Shortcuts.Changed += static () => { };
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
|
|
@ -139,7 +144,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
actions,
|
||||
movement,
|
||||
fellowship,
|
||||
allegiance);
|
||||
allegiance,
|
||||
trade);
|
||||
|
||||
Assert.False(populated.IsConverged);
|
||||
Assert.Equal(1, populated.Inventory.ShortcutCount);
|
||||
|
|
@ -161,6 +167,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
inventory.Dispose();
|
||||
fellowship.Dispose();
|
||||
allegiance.Dispose();
|
||||
trade.Dispose();
|
||||
subscription.Dispose();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot retired =
|
||||
|
|
@ -171,7 +178,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
actions,
|
||||
movement,
|
||||
fellowship,
|
||||
allegiance);
|
||||
allegiance,
|
||||
trade);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(0, retired.Inventory.ShortcutSubscriberCount);
|
||||
|
|
@ -191,6 +199,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
var movement = new RuntimeLocalPlayerMovementState();
|
||||
var fellowship = new RuntimeFellowshipState();
|
||||
var allegiance = new RuntimeAllegianceState();
|
||||
var trade = new RuntimeTradeState();
|
||||
|
||||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
|
|
@ -215,6 +224,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
communication.Dispose();
|
||||
fellowship.Dispose();
|
||||
allegiance.Dispose();
|
||||
trade.Dispose();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot retired =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
|
|
@ -224,7 +234,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
actions,
|
||||
movement,
|
||||
fellowship,
|
||||
allegiance);
|
||||
allegiance,
|
||||
trade);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(1, retired.Communication.DispatchFailureCount);
|
||||
|
|
|
|||
142
tests/AcDream.Runtime.Tests/Gameplay/RuntimeTradeStateTests.cs
Normal file
142
tests/AcDream.Runtime.Tests/Gameplay/RuntimeTradeStateTests.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Secure-trade owner conformance (2026-08-14): the 0x01FD–0x0208 event
|
||||
/// family against the lane B wire truths — including ACE's
|
||||
/// wrong-initiator RegisterTrade, its both-sides ResetTrade, and retail's
|
||||
/// remove-before-notice TradeFailure handling.
|
||||
/// </summary>
|
||||
public sealed class RuntimeTradeStateTests
|
||||
{
|
||||
private const uint Self = 0x50000001u;
|
||||
private const uint Partner = 0x50000002u;
|
||||
private const uint ItemA = 0x60000001u;
|
||||
private const uint ItemB = 0x60000002u;
|
||||
|
||||
[Fact]
|
||||
public void RegisterOpensAndDerivesPartnerDespiteAceGuidLandmine()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
|
||||
// ACE sends initiator == partner == the non-self guid on BOTH sides.
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
|
||||
RuntimeTradeSnapshot snapshot = trade.View.Snapshot;
|
||||
Assert.True(snapshot.IsOpen);
|
||||
Assert.Equal(Partner, snapshot.PartnerGuid);
|
||||
Assert.False(snapshot.SelfAccepted);
|
||||
Assert.False(snapshot.PartnerAccepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStagesPerSideAndDropsBothAcceptances()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
trade.ApplyAccept(Self, Self);
|
||||
trade.ApplyAccept(Partner, Self);
|
||||
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemB, (uint)RuntimeTradeSide.Partner, 0u));
|
||||
// Duplicate echo is a no-op stage-wise.
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
|
||||
RuntimeTradeSnapshot snapshot = trade.View.Snapshot;
|
||||
Assert.Equal(1, snapshot.SelfItemCount);
|
||||
Assert.Equal(1, snapshot.PartnerItemCount);
|
||||
Assert.Equal([ItemA], trade.View.GetItems(RuntimeTradeSide.Self));
|
||||
Assert.Equal([ItemB], trade.View.GetItems(RuntimeTradeSide.Partner));
|
||||
// Staging invalidates prior acceptance (the ClearTradeAcceptance
|
||||
// mirror).
|
||||
Assert.False(snapshot.SelfAccepted);
|
||||
Assert.False(snapshot.PartnerAccepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AcceptDeclineTrackSelfVersusPartnerByGuid()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
|
||||
trade.ApplyAccept(Partner, Self);
|
||||
Assert.True(trade.View.Snapshot.PartnerAccepted);
|
||||
Assert.False(trade.View.Snapshot.SelfAccepted);
|
||||
|
||||
trade.ApplyAccept(Self, Self);
|
||||
Assert.True(trade.View.Snapshot.SelfAccepted);
|
||||
|
||||
trade.ApplyDecline(Partner, Self);
|
||||
Assert.False(trade.View.Snapshot.PartnerAccepted);
|
||||
Assert.True(trade.View.Snapshot.SelfAccepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetClearsBothSidesButKeepsTheWindowOpen()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemB, (uint)RuntimeTradeSide.Partner, 0u));
|
||||
trade.ApplyAccept(Self, Self);
|
||||
|
||||
trade.ApplyReset();
|
||||
|
||||
RuntimeTradeSnapshot snapshot = trade.View.Snapshot;
|
||||
Assert.True(snapshot.IsOpen);
|
||||
Assert.Equal(0, snapshot.SelfItemCount);
|
||||
Assert.Equal(0, snapshot.PartnerItemCount);
|
||||
Assert.False(snapshot.SelfAccepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailureRemovesTheItemAndRecordsTheReason()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
|
||||
trade.ApplyFailure(new GameEvents.TradeFailure(ItemA, 0x426u));
|
||||
|
||||
RuntimeTradeSnapshot snapshot = trade.View.Snapshot;
|
||||
Assert.Equal(0, snapshot.SelfItemCount);
|
||||
Assert.Equal(ItemA, snapshot.LastFailureItemGuid);
|
||||
Assert.Equal(0x426u, snapshot.LastFailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseAndGenerationClearConvergeTheLedger()
|
||||
{
|
||||
var trade = new RuntimeTradeState();
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
|
||||
trade.ApplyClose();
|
||||
Assert.False(trade.View.Snapshot.IsOpen);
|
||||
Assert.Equal(0, trade.View.Snapshot.SelfItemCount);
|
||||
|
||||
trade.ApplyRegister(new GameEvents.RegisterTrade(Partner, Partner, 0uL), Self);
|
||||
trade.Clear();
|
||||
Assert.False(trade.View.Snapshot.IsOpen);
|
||||
|
||||
trade.Dispose();
|
||||
Assert.True(trade.CaptureOwnership().IsConverged);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EventsBeforeRegisterAreIgnored()
|
||||
{
|
||||
using var trade = new RuntimeTradeState();
|
||||
|
||||
trade.ApplyAdd(new GameEvents.AddToTrade(ItemA, (uint)RuntimeTradeSide.Self, 0u));
|
||||
trade.ApplyAccept(Partner, Self);
|
||||
trade.ApplyReset();
|
||||
|
||||
RuntimeTradeSnapshot snapshot = trade.View.Snapshot;
|
||||
Assert.False(snapshot.IsOpen);
|
||||
Assert.Equal(0, snapshot.SelfItemCount);
|
||||
Assert.False(snapshot.PartnerAccepted);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue