-
-
Notifications
You must be signed in to change notification settings - Fork 78
An offered item that left the inventory was duplicated #2370
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
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 |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| using NosCore.GameObject.Ecs.Extensions; | ||
| using NosCore.GameObject.Ecs.Interfaces; | ||
| using NosCore.GameObject.Networking.ClientSession; | ||
| using NosCore.GameObject.Services.BroadcastService; | ||
| using NosCore.GameObject.Services.InventoryService; | ||
| using NosCore.GameObject.Services.ItemGenerationService; | ||
| using NosCore.Packets.Enumerations; | ||
|
|
@@ -28,7 +29,8 @@ namespace NosCore.GameObject.Services.ExchangeService | |
| { | ||
| public class ExchangeService(IItemGenerationService itemBuilderService, | ||
| IOptions<WorldConfiguration> worldConfiguration, ILogger<ExchangeService> logger, IExchangeRequestRegistry exchangeRegistry, | ||
| ILogLanguageLocalizer<LogLanguageKey> logLanguage, IGameLanguageLocalizer gameLanguageLocalizer) | ||
| ILogLanguageLocalizer<LogLanguageKey> logLanguage, IGameLanguageLocalizer gameLanguageLocalizer, | ||
| ISessionRegistry sessionRegistry) | ||
| : IExchangeService | ||
| { | ||
| public void SetGold(long visualId, long gold, long bankGold) | ||
|
|
@@ -204,12 +206,25 @@ public bool CheckExchange(long visualId, long targetId) | |
| logger.LogError(logLanguage[LogLanguageKey.TRY_REMOVE_FAILED], data.Value); | ||
| } | ||
|
|
||
| SetInExchange(data.Key, false); | ||
| SetInExchange(data.Value, false); | ||
|
|
||
| return new ExcClosePacket | ||
| { | ||
| Type = resultType | ||
| }; | ||
| } | ||
|
|
||
| // The lifecycle owns the flag. Set at the seven call sites instead, it was simply | ||
| // never set at all, which left every InExchangeOrShop guard inert. | ||
| private void SetInExchange(long visualId, bool value) | ||
| { | ||
| if (sessionRegistry.TryGetCharacter(c => c.VisualId == visualId, out var character)) | ||
| { | ||
| character.InExchange = value; | ||
| } | ||
| } | ||
|
|
||
| public bool OpenExchange(long visualId, long targetVisualId) | ||
| { | ||
| if (CheckExchange(visualId) || CheckExchange(targetVisualId)) | ||
|
|
@@ -222,6 +237,8 @@ public bool OpenExchange(long visualId, long targetVisualId) | |
| exchangeRegistry.SetExchangeRequest(targetVisualId, visualId); | ||
| exchangeRegistry.SetExchangeData(visualId, new ExchangeData()); | ||
| exchangeRegistry.SetExchangeData(targetVisualId, new ExchangeData()); | ||
| SetInExchange(visualId, true); | ||
| SetInExchange(targetVisualId, true); | ||
| return true; | ||
| } | ||
|
|
||
|
|
@@ -257,6 +274,21 @@ bool IsFullTransfer | |
| } | ||
| } | ||
|
|
||
| // The offer captured these references when the item was put in the window. Confirm | ||
| // each is still there, at that amount, before anything is created: the destination | ||
| // item is built fresh, so a source that has since gone would duplicate it. | ||
| foreach (var transfer in pendingTransfers) | ||
| { | ||
| if (!transfer.OriginInventory.TryGetValue(transfer.OriginalItem.ItemInstanceId, out var live) | ||
| || live.ItemInstance == null | ||
| || live.ItemInstance.ItemVNum != transfer.OriginalItem.ItemInstance.ItemVNum | ||
| || live.ItemInstance.Amount < transfer.Amount) | ||
| { | ||
| logger.LogError(logLanguage[LogLanguageKey.INVALID_EXCHANGE]); | ||
| return new List<KeyValuePair<long, IvnPacket>>(); | ||
| } | ||
|
Comment on lines
+282
to
+289
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 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 '\b(Remove|RemoveItemAmountFromInventory|TryGetValue)\b' \
src/NosCore.GameObject/Services/InventoryService --glob '*.cs'
rg -n -C 8 '\bProcessExchange\b' src test --glob '*.cs'Repository: NosCoreIO/NosCore Length of output: 15128 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- exchange implementation ---'
sed -n '245,330p' src/NosCore.GameObject/Services/ExchangeService/ExchangeService.cs
printf '%s\n' '--- inventory removal implementation ---'
sed -n '430,480p' src/NosCore.GameObject/Services/InventoryService/InventoryService.cs
printf '%s\n' '--- inventory mutation contract ---'
sed -n '1,70p' src/NosCore.GameObject/Services/InventoryService/IInventoryService.cs
printf '%s\n' '--- relevant storage implementation context ---'
sed -n '480,535p' src/NosCore.GameObject/Services/InventoryService/InventoryService.cs
printf '%s\n' '--- repository-scoped guidance ---'
find /tmp/coderabbit-repo-knowledge/noscoreio-noscore-2ab562cb -type f -name '*.md' -print
for f in /tmp/coderabbit-repo-knowledge/noscoreio-noscore-2ab562cb/*/*.md; do
case "$f" in
*/learnings/*|*/architecture/*) ;;
*) printf '\n### %s\n' "$f"; cat "$f" ;;
esac
doneRepository: NosCoreIO/NosCore Length of output: 11738 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- remainder of exchange processing ---'
sed -n '320,390p' src/NosCore.GameObject/Services/ExchangeService/ExchangeService.cs
printf '%s\n' '--- source removal and dictionary mutation ---'
rg -n -C 12 'public .*Remove\(|Remove\(|DeleteById|TryRemove|LoadByItemInstanceId' \
src/NosCore.GameObject/Services/InventoryService/InventoryService.cs
printf '%s\n' '--- inventory service declaration and synchronization primitives ---'
sed -n '1,90p' src/NosCore.GameObject/Services/InventoryService/InventoryService.cs
rg -n 'lock|Semaphore|Monitor|Interlocked|Concurrent' \
src/NosCore.GameObject/Services/InventoryService src/NosCore.GameObject/Services/ExchangeService --glob '*.cs'Repository: NosCoreIO/NosCore Length of output: 11049 Make source removal atomic with destination creation.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| var addedItems = new List<(IInventoryService Inventory, InventoryItemInstance Item)>(); | ||
|
|
||
| foreach (var transfer in pendingTransfers) | ||
|
|
||
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.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: NosCoreIO/NosCore
Length of output: 50374
🏁 Script executed:
Repository: NosCoreIO/NosCore
Length of output: 37349
🏁 Script executed:
Repository: NosCoreIO/NosCore
Length of output: 50374
🏁 Script executed:
Repository: NosCoreIO/NosCore
Length of output: 15009
🏁 Script executed:
Repository: NosCoreIO/NosCore
Length of output: 25841
Use one participant identifier consistently for the exchange.
The
RequestExchangeType.Listpath passestarget.CharacterIdtoOpenExchange, whileSetInExchangematches onlyVisualId. If these values differ, the target lookup fails andOpenExchangestill returnstrue, leavingtarget.InExchangeunset. Resolve both participants before creating exchange state and roll back if either lookup fails. Add assertions for both flags after opening and closing.🤖 Prompt for AI Agents