Skip to content

fix(exchange): confirm/cancel packets are dropped once a trade is open - #2427

Open
Khalilx11 wants to merge 2 commits into
NosCoreIO:masterfrom
Khalilx11:fix/exchange-confirm-branches-swapped
Open

Khalilx11 wants to merge 2 commits into
NosCoreIO:masterfrom
Khalilx11:fix/exchange-confirm-branches-swapped

Conversation

@Khalilx11

@Khalilx11 Khalilx11 commented Sep 20, 2026 •

Copy link
Copy Markdown

Summary

  • ExchangeRequestPacket declares Scopes = InGame only, but RequestExchangeType.Confirmed/Cancelled are only ever sent while a trade is already open (InExchange = true). WorldPacketHandlingStrategy.ValidateScope was silently dropping every confirm and cancel before the handler ran because the packet lacks Scope.InTrade. Special-cased those two request types past the gate.
  • Separately, ExchangeRequestPacketHandler's Confirmed case had its success/failure branches inverted: a validated exchange (Success, null packet dict) took the branch that dereferences the packet dict, throwing on every legitimately successful confirm.

Test plan

  • dotnet test test/NosCore.PacketHandlers.Tests --filter FullyQualifiedName~Exchange (11/11 pass)
  • dotnet test test/NosCore.GameObject.Tests --filter FullyQualifiedName~Exchange (23/23 pass)
  • dotnet test NosCore.sln (GameObject.Tests 538/538, PacketHandlers.Tests 414/414, WebApi.Tests 17/17, Database.Tests 11/11 all pass; unrelated pre-existing failures in Core.Tests/Parser.Tests over Bazaar resource keys and BCard vocabulary, untouched by this change)
  • Live multi-client verification: full gold-for-gold trade (request -> accept -> open -> both offer -> both confirm -> commit), confirmed the second confirm is what triggers the commit and gold transfers exactly once on both sides
  • Live verification of cancellation: immediate close, no gold/inventory mutation on either side
  • Live verification of an invalid gold offer: silently rejected server-side, partner never sees the bad value
  • Regression: full solution build clean, basic login/world-entry/chat still functioning

?? Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Exchange confirmations and cancellations now process correctly when a character is in an exchange or shop state.
    • Fixed exchange validation so successful exchanges transfer items and currency, while failed validations notify participants appropriately.
    • Characters can no longer initiate an exchange with themselves.
    • Fixed exchange updates for partial stack transfers to show the recipient’s actual merged quantity.
    • Gold-only exchanges no longer close or reject valid offers because of empty packet entries.

RequestExchangeType.Confirmed/Cancelled are only ever sent while
InExchange is already true, but ExchangeRequestPacket's declared scope
is InGame only (missing InTrade), so WorldPacketHandlingStrategy's
generic scope gate silently rejected every confirm and cancel before
the handler ran. Special-case those two request types past the gate.

Separately, ExchangeRequestPacketHandler's Confirmed case had its
success/failure branches inverted: a validated exchange (Success, null
packet dict) took the branch that dereferences the packet dict,
throwing on every legitimately successful confirm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NosCoreIO/NosCore/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 6d8d1bbe-bbc5-49e4-84b8-d8f772d0a3b6

📥 Commits

Reviewing files that changed from the base of the PR and between c4c1f46 and 4d6d50e.

📒 Files selected for processing (6)
  • src/NosCore.GameObject/Services/ExchangeService/ExchangeService.cs
  • src/NosCore.PacketHandlers/Exchange/ExcListPacketHandler.cs
  • src/NosCore.PacketHandlers/Exchange/ExchangeRequestPacketHandler.cs
  • test/NosCore.GameObject.Tests/Services/ExchangeService/ExchangeServiceTests.cs
  • test/NosCore.PacketHandlers.Tests/Exchange/ExcListPacketHandlerTests.cs
  • test/NosCore.PacketHandlers.Tests/Exchange/ExchangeRequestPacketHandlerTests.cs

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.


Walkthrough

The change corrects exchange scope validation, self-target rejection, phantom offer handling, confirmation result branching, and destination inventory notifications. Tests cover self-exchange requests, zero-amount offer fillers, and merged partial stacks.

Changes

Exchange handling

Layer / File(s) Summary
Lifecycle and request validation
src/NosCore.GameObject/Networking/ClientSession/WorldPacketHandlingStrategy.cs, src/NosCore.PacketHandlers/Exchange/ExchangeRequestPacketHandler.cs, test/NosCore.PacketHandlers.Tests/Exchange/ExchangeRequestPacketHandlerTests.cs
Confirmed and cancelled exchange requests can bypass the shop rejection for eligible sessions. Requests targeting the requesting character are rejected.
Offer parsing and confirmation
src/NosCore.PacketHandlers/Exchange/ExcListPacketHandler.cs, src/NosCore.PacketHandlers/Exchange/ExchangeRequestPacketHandler.cs, test/NosCore.PacketHandlers.Tests/Exchange/ExcListPacketHandlerTests.cs
Zero-amount deserializer fillers are excluded from exchange offers. Validation failures now send result packets, while non-failure results continue to transfer items and gold.
Transfer notification
src/NosCore.GameObject/Services/ExchangeService/ExchangeService.cs, test/NosCore.GameObject.Tests/Services/ExchangeService/ExchangeServiceTests.cs
Destination notifications now use the item returned by AddItemToPocket. A test covers merged partial stacks.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix

Suggested reviewers: erwan-joly

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the primary exchange fix: allowing confirmation and cancellation packets after a trade opens. It is concise and specific.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…e receiver's amount

ExcListPacket.SubPackets deserializes with a minimum of 3 default-valued
fillers regardless of how many real sub-packets were sent, because the
trailing list has no wire count prefix. Amount has a declared
[Range(1, ...)], so a filler (Amount == 0) can never be a genuine offer -
same phantom-entry shape as QstlistPacket/FinitPacket, filtered in
ExcListPacketHandler the same way those are filtered client-side.

Separately, ExchangeRequestPacketHandler's Requested case had no guard
against a player targeting their own VisualId, letting a self-invite be
sent, accepted, and opened.

And ExchangeService.ProcessExchange built the receiving side's inventory
notification from the sender's own item object instead of the item
actually added to the receiver's inventory. Partial transfers mutate the
sender's item in place when removing the offered amount, so the receiver
was notified with the sender's post-removal remaining balance rather
than their own real (possibly merged) total. The persisted inventory was
always correct; only the notification packet was wrong.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
}

if (session.HasSelectedCharacter && (attr.Scopes & Scope.InTrade) == 0 && session.Character.InExchangeOrShop)
var isExchangeLifecycleAction = packet is ExchangeRequestPacket

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely the fix shouldnt be here but in noscorepackets then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants