-
-
Notifications
You must be signed in to change notification settings - Fork 78
A bazaar listing could be sold twice across channels #2371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,21 +55,25 @@ public override async Task ExecuteAsync(CBuyPacket packet, ClientSession clientS | |
| { | ||
| if (clientSession.Character.Gold >= price) | ||
| { | ||
| clientSession.Character.Gold -= price; | ||
| await clientSession.SendPacketAsync(clientSession.Character.GenerateGold()); | ||
|
|
||
| var itemInstance = await itemInstanceDao.FirstOrDefaultAsync(s => s!.Id == bz.ItemInstance.Id); | ||
| var item = itemProvider.Convert(itemInstance!); | ||
| item.Id = Guid.NewGuid(); | ||
| var newInv = | ||
| clientSession.Character.InventoryService.AddItemToPocket( | ||
| InventoryItemInstance.Create(item, clientSession.Character.CharacterId)); | ||
| await clientSession.SendPacketAsync(newInv!.GeneratePocketChange()); | ||
|
|
||
| // Claim the listing before anything is paid for or created. The listing | ||
| // is shared across channels, so two buyers can both pass the checks | ||
| // above; only one can win this call, and the loser must leave with | ||
| // neither the gold gone nor an item made. | ||
| var remove = await bazaarHttpClient.DeleteBazaarAsync(packet.BazaarId, packet.Amount, | ||
| clientSession.Character.Name); | ||
| if (remove) | ||
| { | ||
| clientSession.Character.Gold -= price; | ||
| await clientSession.SendPacketAsync(clientSession.Character.GenerateGold()); | ||
|
|
||
| var itemInstance = await itemInstanceDao.FirstOrDefaultAsync(s => s!.Id == bz.ItemInstance.Id); | ||
| var item = itemProvider.Convert(itemInstance!); | ||
| item.Id = Guid.NewGuid(); | ||
| var newInv = | ||
| clientSession.Character.InventoryService.AddItemToPocket( | ||
| InventoryItemInstance.Create(item, clientSession.Character.CharacterId)); | ||
| await clientSession.SendPacketAsync(newInv!.GeneratePocketChange()); | ||
|
Comment on lines
+69
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Return an item snapshot from the claim operation.
Create the purchased-item snapshot inside the listing lock before mutation or deletion. Return that snapshot from the claim operation. Build the inventory item from the returned snapshot. 🤖 Prompt for AI Agents |
||
|
|
||
| await clientSession.HandlePacketsAsync(new[] { new CBListPacket { Index = 0, ItemVNumFilter = new List<short>() } }); | ||
| await clientSession.SendPacketAsync(new RCBuyPacket(bz.SellerName!) | ||
| { | ||
|
|
@@ -95,7 +99,14 @@ await clientSession.SendPacketAsync(new SayiPacket | |
| return; | ||
| } | ||
|
|
||
| logger.LogError(logLanguage[LogLanguageKey.BAZAAR_BUY_ERROR]); | ||
| // Someone else took it first, most likely from another channel. | ||
| logger.LogInformation(logLanguage[LogLanguageKey.BAZAAR_BUY_ERROR]); | ||
| await clientSession.SendPacketAsync(new ModaliPacket | ||
| { | ||
| Type = 1, | ||
| Message = Game18NConstString.OfferUpdated | ||
| }); | ||
| await clientSession.HandlePacketsAsync(new[] { new CBListPacket { Index = 0, ItemVNumFilter = new List<short>() } }); | ||
| } | ||
| else | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Return a failed claim when the listing is already removed.
After the winning caller unregisters a fully sold listing, the next waiter acquires this lock and
GetByIdreturns null.DeleteBazaarAsyncthen throws instead of returningfalse.CBuyPacketHandleronly sendsOfferUpdatedand refreshes the list forfalse, so the losing purchase faults and skips that recovery path.Return
falsefor this expected missing-listing claim result, or map only this condition tofalsein the caller.🤖 Prompt for AI Agents