Skip to content

perf/refactor: implement player batch updates#3701

Open
dudantas wants to merge 54 commits intomainfrom
dudantas/implement-player-batch-updates
Open

perf/refactor: implement player batch updates#3701
dudantas wants to merge 54 commits intomainfrom
dudantas/implement-player-batch-updates

Conversation

@dudantas
Copy link
Member

@dudantas dudantas commented Sep 14, 2025

Description

This PR refactors and fixes NPC buy/sell logic, loot pouch selling, and the container batch update system, ensuring correct validation, safer transactions, and reduced client update spam.

Motivation / Context

  • Enforce correct space, tile limit, and cost validations during NPC purchases.
  • Make selling (regular and loot pouch) safer by ignoring invalid items and properly handling custom currency failures.
  • Centralize and stabilize the container batch update lifecycle, preventing duplicated processing and unnecessary client updates.

Behaviour

Actual

  • Purchases could bypass space or tile limits in some scenarios.
  • Loot pouch selling did not consistently handle invalid items or custom currency failures.
  • Container updates were sent even while batching.

Expected

  • Purchases are correctly blocked when space, funds, or limits are exceeded.
  • Selling ignores invalid items, delivers currency safely, and aborts on errors.
  • Containers only send updates after batch processing ends.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change
  • This change requires a documentation update

How Has This Been Tested

1) NPC Buy (gold and custom currency)

  • Backpack full
    Attempt to buy a non-container item with no free slots.
    Expected: purchase is blocked.

  • Ignore enabled + tile limit
    Buy with ignore=true until exceeding the 30-item tile limit (considering required slots and free slots).
    Expected: purchase is blocked.

  • Shopping bag cost
    Buy with inBackpacks=true and verify bag cost calculation for:

    • Stackable items
    • Non-stackable items
  • Insufficient funds

    • Not enough gold/bank for totalCost → purchase blocked.
    • Not enough custom currency for totalCost, or insufficient gold/bank to pay for bags → purchase blocked.

2) NPC Sell (regular inventory)

  • Valid item sale
    Sell an item without tier or imbuements.
    Expected: correct total calculation and credit to bank or inventory gold (depending on AUTOBANK).

  • Custom currency sale
    Validate custom currency delivery in stacks.
    If currency creation or insertion fails:
    Expected: error message is shown and transaction is aborted.

  • Invalid or zero-amount items
    Attempt to sell non-sellable items or zero quantity.
    Expected: no sale occurs and message “You have no items to sell.” is shown when applicable.


3) Loot pouch selling

  • Loot pouch sale trigger
    Use ITEM_GOLD_POUCH.
    Expected: triggers batch sale of loot pouch contents.

  • Tiered / imbued items ignored
    Items with tier > 0 or imbuements are not sold.

  • Custom currency during loot sale
    Validate correct delivery.
    On failure:
    Expected: sale is aborted and player is informed.

  • Store inbox letter
    When a sale occurs, confirm a log letter is sent to the store inbox.


4) Container batch update & synchronization

  • Batch update lifecycle
    Verify beginBatchUpdate / endBatchUpdate are balanced and duplicated containers are not reprocessed.

  • Updates during batching
    Ensure item updates are suppressed while m_batching is active.

  • Drop destination (gray area)
    Move an item to an index outside container capacity.
    Expected: destination resolves to INDEX_WHEREEVER.

  • Parent notification on removal
    Removing an item by index correctly notifies the parent container.


Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I checked the PR checks reports
  • I have commented my code where necessary
  • I have made corresponding documentation changes
  • My changes generate no new warnings
  • I have added tests that prove the fix/feature works

Summary by CodeRabbit

  • New Features
    • Added "The Lootmonger" NPC with shop integration and dialogue interactions.
    • Introduced reward chest system with /addreward command for adding items to timestamped reward chests.
    • Added loot management commands: /createloot, /clearloot, /createtestshop, /countloot, and /addloot for administrative loot control.
    • Implemented batch container operations for efficient bulk item management.
    • Added container removal functions for item cleanup operations.

@dudantas dudantas changed the title iimplement player batch updates perf: implement player batch updates Sep 14, 2025
@dudantas dudantas force-pushed the dudantas/implement-player-batch-updates branch from 0141e93 to 1474a48 Compare September 23, 2025 19:39
Introduces batch update logic for containers and player inventory, refactors NPC sell logic to support batch operations, and adds new Lua talkactions for reward chest and loot pouch management. Improves container item count caching and container closing logic, and updates related tests and documentation.
@dudantas dudantas changed the title perf: implement player batch updates perf/refactor: implement player batch updates Sep 30, 2025
Copilot AI review requested due to automatic review settings October 5, 2025 17:43
@dudantas dudantas force-pushed the dudantas/implement-player-batch-updates branch from 1647bb3 to 37f0883 Compare October 5, 2025 17:43

This comment was marked as resolved.

dudantas and others added 11 commits October 20, 2025 16:15
Optimized the onPlayerSellAllLoot function by restructuring item sale data handling, improving iteration over loot pouch items, and streamlining currency and sale letter processing. Enhanced error handling for store inbox operations and removed redundant code for better maintainability.
Introduces luaPlayerGetBackpack and luaPlayerGetLootPouch methods to the Player Lua API, allowing scripts to retrieve a player's backpack and loot pouch containers. Updates createitemtest.lua to use addItemBatchToPaginedContainer for improved item addition handling and logging.
Adds a message to inform the player if the sale letter could not be added to their store inbox after selling all loot.
Corrected the variable name from 'returnVale' to 'returnValue' in the internalCollectManagedItems function for clarity and consistency.
Replaces Boost.UT with Google Test in reward_iteration_test.cpp and test_batch_update.cpp. Adds new test cases for reward item iteration and batch update logic, improves test structure, and updates CMakeLists.txt to enable the reward_iteration_test.cpp target. Also enhances test_items.hpp to ensure proper item type initialization for tests.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Updated the item search in the reward chest collection logic to use std::ranges::find instead of std::find for improved readability and modern C++ practices.
Modified sendBatchUpdateContainer to call updateState only if m_batching is false. This prevents unnecessary state updates during batch operations.
@sonarqubecloud

This comment was marked as resolved.

@github-actions
Copy link
Contributor

This PR is stale because it has been open 45 days with no activity.

@github-actions github-actions bot added the Stale No activity label Nov 21, 2025
@coderabbitai

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as outdated.

Refactored BatchUpdate to use std::shared_ptr and std::weak_ptr for Player and Container, improving memory safety and deduplication. Updated all usages and Lua bindings accordingly. Enhanced batch update logic in loot and container management, fixed related tests, and improved error handling and logging. Also made minor bug fixes and code cleanups in related files.
@dudantas dudantas force-pushed the dudantas/implement-player-batch-updates branch from 985b21c to f5fdcd6 Compare January 13, 2026 02:05
coderabbitai[bot]

This comment was marked as resolved.

@github-actions github-actions bot removed the Stale No activity label Jan 13, 2026
Corrects container cache decrement logic to prevent negative values, refactors ContainerIterator to use an internal visited set, and fixes container closing logic in Player. Adds new integration and unit tests for batch update behavior, and updates test configuration to ensure correct working directory and config file usage.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/creatures/npcs/npc.cpp`:
- Around line 765-784: The loop over saleData can leave the player in a partial
state because applySaleProceedsForLoot is called per-item and returns early on
failure; instead aggregate sale results first (e.g., accumulate per-currency
totals and build the log using saleData, totalPrice, totalItemsSold, and item
names from Item::items.getItemType) and only call applySaleProceedsForLoot once
per currency (using getCurrency() and getName() for context) after all removals
succeed; if any applySaleProceedsForLoot fails, avoid mutating player state
further (or perform a single rollback/restore) so the operation is atomic from
the player's perspective.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/creatures/players/player.cpp`:
- Around line 8076-8086: The LINK_OWNER equipment callbacks are skipped when
m_batching causes an early return in Player::postAddNotification and
Player::postRemoveNotification; move the LINK_OWNER handling out of the batching
short-circuit so equipment events always fire. Specifically, in
Player::postAddNotification call g_moveEvents().onPlayerEquip(getPlayer(),
thing->getItem(), static_cast<Slots_t>(index), false) before checking
m_batching; likewise in Player::postRemoveNotification invoke
g_moveEvents().onPlayerDeEquip(getPlayer(), thing->getItem(),
static_cast<Slots_t>(index), false) (or the existing de-equip call) prior to the
m_batching return so equip/dequip hooks run even during batch updates.
🧹 Nitpick comments (1)
src/creatures/players/player.cpp (1)

73-95: Test helper registers player globally—ensure cleanup.
initForTests() adds the player to g_game(). Consider adding a teardown helper or documenting required cleanup in tests to avoid cross‑test leakage.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/creatures/npcs/npc.cpp`:
- Around line 249-253: The CustomSaleContext array used in
Npc::onPlayerSellAllLoot contains a mislabelled context string
"[Npc::onPlayerSellItem]"; update that entry to "[Npc::onPlayerSellAllLoot]" so
the log context accurately reflects the onPlayerSellAllLoot path (locate the
CustomSaleContext initializer in src/creatures/npcs/npc.cpp inside the
Npc::onPlayerSellAllLoot handling and replace the incorrect string).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/creatures/npcs/npc.cpp`:
- Around line 234-252: The CustomSaleContext passed from
applySaleProceedsForLoot is missing the fourth field, leaving failureMessage
null and risking a crash in sendTextMessage; update the call in
applySaleProceedsForLoot to provide all four string fields (e.g., first two
context labels consistent with the loot-sale path such as
"[Npc::onPlayerSellLoot]" and "[Npc::onPlayerSellAllLoot]" and a non-null
failureMessage like "An error occurred while completing the sale of your loot.
No items were exchanged.") so that CustomSaleContext is fully initialized before
calling applyCustomSaleProceeds.

dudantas and others added 2 commits January 27, 2026 17:46
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/creatures/npcs/npc.cpp`:
- Around line 254-257: Remove the stray orphan tokens that break the anonymous
namespace: delete the extraneous ");" and the premature "}" currently at the end
of the block so that applySaleProceedsForItem and sendSaleLetterIfNeeded remain
inside the anonymous namespace and can access helpers like applyGoldSaleProceeds
and applyCustomSaleProceeds; ensure the anonymous namespace is closed only once
after sendSaleLetterIfNeeded (the intended closing brace at the original end of
the helper block).

dudantas and others added 3 commits January 27, 2026 18:02
coderabbitai[bot]

This comment was marked as resolved.

dudantas and others added 4 commits January 27, 2026 18:59
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Updated cost calculation functions in npc.cpp to use uint64_t for bagsCost and totalCost to prevent overflow. Adjusted the cleanup lambda and guard in rsa_test.cpp to use the correct pointer type, improving type safety.
coderabbitai[bot]

This comment was marked as resolved.

The balance_decrease metric is now only incremented after confirming the player has sufficient funds and the purchase is in gold coins. This prevents logging failed purchase attempts as successful balance decreases.
@sonarqubecloud
Copy link

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

This PR is stale because it has been open 45 days with no activity.

@github-actions github-actions bot added the Stale No activity label Mar 2, 2026
@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/creatures/npcs/npc.cpp (3)

776-777: ⚠️ Potential issue | 🟡 Minor

Loot-sale messages still hardcode “gold” for non-gold currencies.

Lines 777 and 800 are misleading when getCurrency() is not ITEM_GOLD_COIN.

🛠️ Suggested fix
-		const auto &itemName = Item::items.getItemType(itemId).name;
-		log += fmt::format("Sold {}x {} for {} gold.\n", data.amount, itemName, totalCost);
+		const auto &itemName = Item::items.getItemType(itemId).name;
+		const auto &currencyName = Item::items.getItemType(getCurrency()).name;
+		log += fmt::format("Sold {}x {} for {} {}.\n", data.amount, itemName, totalCost, currencyName);
@@
-		finalMessage = fmt::format("You sold {} item{} from your loot pouch for {} gold. A letter with the full list has been sent to your store inbox.", totalItemsSold, (totalItemsSold == 1 ? "" : "s"), totalPrice);
+		const auto &currencyName = Item::items.getItemType(getCurrency()).name;
+		finalMessage = fmt::format("You sold {} item{} from your loot pouch for {} {}. A letter with the full list has been sent to your store inbox.", totalItemsSold, (totalItemsSold == 1 ? "" : "s"), totalPrice, currencyName);

Also applies to: 800-800

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/creatures/npcs/npc.cpp` around lines 776 - 777, The loot-sale log
hardcodes the word "gold" when composing messages (see the format call that uses
itemName and totalCost); change the message to use the actual currency name
returned by getCurrency() instead of "gold": fetch the currency item/type name
(e.g., via Item::items.getItemType(getCurrency()).name or the project's
Currency/name accessor) and use that variable in the fmt::format calls that
build the "Sold ... for ... gold." lines (references:
Item::items.getItemType(itemId).name, data.amount, totalCost, getCurrency(),
ITEM_GOLD_COIN).

664-667: ⚠️ Potential issue | 🟠 Major

Use 64-bit arithmetic for purchase total to avoid overflow.

Line 664 computes buyPrice * amount in 32-bit space before any widening. Large values can wrap and undercharge while still passing funds checks.

🛠️ Suggested fix
-	const uint32_t totalCost = buyPrice * amount;
-	const uint32_t bagsCost = calculateBagsCost(itemType, amount, inBackpacks);
-	const uint64_t totalRequired = static_cast<uint64_t>(totalCost) + bagsCost;
+	const uint64_t totalCost = static_cast<uint64_t>(buyPrice) * static_cast<uint64_t>(amount);
+	const uint64_t bagsCost = calculateBagsCost(itemType, amount, inBackpacks);
+	const uint64_t totalRequired = totalCost + bagsCost;
 	if (hasInsufficientFunds(player, itemId, getName(), getCurrency(), totalCost, bagsCost)) {
 		return;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/creatures/npcs/npc.cpp` around lines 664 - 667, The multiplication
buyPrice * amount is done in 32-bit and can overflow; change computation to
64-bit by computing totalCost as uint64_t totalCost =
static_cast<uint64_t>(buyPrice) * amount (or similar widening), keep bagsCost as
computed by calculateBagsCost, compute totalRequired accordingly, and pass the
64-bit totals into hasInsufficientFunds (or cast in the call) so all comparisons
use 64-bit arithmetic (update variable types or casts for
totalCost/totalRequired and the hasInsufficientFunds invocation to use
uint64_t).

828-842: ⚠️ Potential issue | 🔴 Critical

Custom-currency sale path can remove items before credit is guaranteed.

Lines 828-830 remove items first, then Lines 841-842 abort when proceeds application fails. In that failure path, items are already gone, but the flow reports the sale as aborted. Please make this transactional (rollback removed items on credit failure, or pre-validate/apply proceeds before destructive removal).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/creatures/npcs/npc.cpp` around lines 828 - 842, The code currently
removes items via removeItemsFromLootPouch or removeItemsFromInventory before
ensuring currency credit via applySaleProceedsForItem, so if
applySaleProceedsForItem fails the items are already gone; change the flow to be
transactional by either (A) pre-validating/applying proceeds (call
applySaleProceedsForItem first or a new validateAndReserveProceeds step) and
only removing items after success, or (B) if you must remove first, capture the
removed items (their ids/amounts) and, on applySaleProceedsForItem failure, call
a restore function to put items back into the player/loot pouch using the same
context and batchUpdate; update the code paths referencing
removeItemsFromLootPouch, removeItemsFromInventory, applySaleProceedsForItem,
getCurrency, getName, and context to implement one of these transactional
approaches so failure does not leave the player missing items.
🧹 Nitpick comments (1)
src/game/game.cpp (1)

3284-3299: Batch source containers lazily after filters to reduce unnecessary updates

At Lines 3284-3299 all parent containers are added to BatchUpdate before quick-loot filters/capacity checks. Containers for skipped items are still batched, which can trigger avoidable container refresh work at batch end.

Also applies to: 3305-3319

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/game/game.cpp` around lines 3284 - 3299, The code currently adds every
parent container to BatchUpdate up-front (BatchUpdate batchUpdate / loop over
rewardItemsVector), causing containers for items that will be skipped by
quick-loot filters or capacity checks to be needlessly batched; change the logic
to add containers lazily after applying the item-level filters/capacity checks
(i.e., only call batchUpdate.add(container) once you know the item will be
processed), and ensure you avoid duplicate adds (use the existing
BatchUpdate.dedup behavior or maintain a local set keyed by container
pointer/ID) so only necessary containers are refreshed at batch end; apply the
same change to the analogous loop handling the later rewardItemsVector block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/game/game.cpp`:
- Around line 3325-3330: The code sends a specific lootedItemsMessage via
player->sendTextMessage when maxMoveItems limit is reached but then returns
RETURNVALUE_NOTPOSSIBLE which triggers the caller to send a second generic
cancel message; update the return to a non-canceling success code (e.g.,
RETURNVALUE_NOERROR) from the block that contains maxMoveItems,
movedRewardItems, rewardCount and lootedItemsMessage so the caller does not emit
the duplicate generic message—ensure you only change the return value after the
player->sendTextMessage call for that condition.
- Around line 3273-3274: The code dereferences rewardChest returned from
player->getRewardChest() without null checking; modify the logic around the
rewardChest variable (the local std::shared_ptr<RewardChest> rewardChest =
player->getRewardChest()) to first test rewardChest for null (e.g., if
(!rewardChest) { /* handle absent chest: return, skip, or create one */ })
before calling rewardChest->empty(), ensuring the rest of the branch handles the
absent-chest case consistently with other uses in this file.

---

Duplicate comments:
In `@src/creatures/npcs/npc.cpp`:
- Around line 776-777: The loot-sale log hardcodes the word "gold" when
composing messages (see the format call that uses itemName and totalCost);
change the message to use the actual currency name returned by getCurrency()
instead of "gold": fetch the currency item/type name (e.g., via
Item::items.getItemType(getCurrency()).name or the project's Currency/name
accessor) and use that variable in the fmt::format calls that build the "Sold
... for ... gold." lines (references: Item::items.getItemType(itemId).name,
data.amount, totalCost, getCurrency(), ITEM_GOLD_COIN).
- Around line 664-667: The multiplication buyPrice * amount is done in 32-bit
and can overflow; change computation to 64-bit by computing totalCost as
uint64_t totalCost = static_cast<uint64_t>(buyPrice) * amount (or similar
widening), keep bagsCost as computed by calculateBagsCost, compute totalRequired
accordingly, and pass the 64-bit totals into hasInsufficientFunds (or cast in
the call) so all comparisons use 64-bit arithmetic (update variable types or
casts for totalCost/totalRequired and the hasInsufficientFunds invocation to use
uint64_t).
- Around line 828-842: The code currently removes items via
removeItemsFromLootPouch or removeItemsFromInventory before ensuring currency
credit via applySaleProceedsForItem, so if applySaleProceedsForItem fails the
items are already gone; change the flow to be transactional by either (A)
pre-validating/applying proceeds (call applySaleProceedsForItem first or a new
validateAndReserveProceeds step) and only removing items after success, or (B)
if you must remove first, capture the removed items (their ids/amounts) and, on
applySaleProceedsForItem failure, call a restore function to put items back into
the player/loot pouch using the same context and batchUpdate; update the code
paths referencing removeItemsFromLootPouch, removeItemsFromInventory,
applySaleProceedsForItem, getCurrency, getName, and context to implement one of
these transactional approaches so failure does not leave the player missing
items.

---

Nitpick comments:
In `@src/game/game.cpp`:
- Around line 3284-3299: The code currently adds every parent container to
BatchUpdate up-front (BatchUpdate batchUpdate / loop over rewardItemsVector),
causing containers for items that will be skipped by quick-loot filters or
capacity checks to be needlessly batched; change the logic to add containers
lazily after applying the item-level filters/capacity checks (i.e., only call
batchUpdate.add(container) once you know the item will be processed), and ensure
you avoid duplicate adds (use the existing BatchUpdate.dedup behavior or
maintain a local set keyed by container pointer/ID) so only necessary containers
are refreshed at batch end; apply the same change to the analogous loop handling
the later rewardItemsVector block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7f61b2f7-56f2-43d1-afd0-d2581f19d271

📥 Commits

Reviewing files that changed from the base of the PR and between 436313e and 5d2f3d5.

📒 Files selected for processing (6)
  • src/creatures/creature.hpp
  • src/creatures/npcs/npc.cpp
  • src/creatures/npcs/npc.hpp
  • src/game/game.cpp
  • src/game/game.hpp
  • src/items/containers/container.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/game/game.hpp

Comment on lines +3273 to 3274
std::shared_ptr<RewardChest> rewardChest = player->getRewardChest();
if (rewardChest->empty()) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard reward chest pointer before dereference

At Line 3274, rewardChest is dereferenced without a null check. player->getRewardChest() is nullable in this file (e.g., guarded at Line 11028), so this can crash.

🔧 Proposed fix
-	std::shared_ptr<RewardChest> rewardChest = player->getRewardChest();
-	if (rewardChest->empty()) {
+	std::shared_ptr<RewardChest> rewardChest = player->getRewardChest();
+	if (!rewardChest || rewardChest->empty()) {
 		g_logger().debug("Reward chest is empty");
 		return RETURNVALUE_REWARDCHESTISEMPTY;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/game/game.cpp` around lines 3273 - 3274, The code dereferences
rewardChest returned from player->getRewardChest() without null checking; modify
the logic around the rewardChest variable (the local
std::shared_ptr<RewardChest> rewardChest = player->getRewardChest()) to first
test rewardChest for null (e.g., if (!rewardChest) { /* handle absent chest:
return, skip, or create one */ }) before calling rewardChest->empty(), ensuring
the rest of the branch handles the absent-chest case consistently with other
uses in this file.

Comment on lines +3325 to +3330
if (maxMoveItems && movedRewardItems == maxMoveItems) {
lootedItemsMessage = fmt::format("You can only collect {} items at a time. {} of {} objects were picked up.", maxMoveItems, movedRewardItems, rewardCount);
player->sendTextMessage(MESSAGE_EVENT_ADVANCE, lootedItemsMessage);
// Already send message here
return RETURNVALUE_NOTPOSSIBLE;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid duplicate client messaging on max-item limit

At Line 3326 you already send a specific message, but at Line 3329 you return RETURNVALUE_NOTPOSSIBLE, and the caller sends another generic cancel message. This creates noisy double feedback.

🔧 Proposed fix
 		if (maxMoveItems && movedRewardItems == maxMoveItems) {
 			lootedItemsMessage = fmt::format("You can only collect {} items at a time. {} of {} objects were picked up.", maxMoveItems, movedRewardItems, rewardCount);
 			player->sendTextMessage(MESSAGE_EVENT_ADVANCE, lootedItemsMessage);
 			// Already send message here
-			return RETURNVALUE_NOTPOSSIBLE;
+			return RETURNVALUE_NOERROR;
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/game/game.cpp` around lines 3325 - 3330, The code sends a specific
lootedItemsMessage via player->sendTextMessage when maxMoveItems limit is
reached but then returns RETURNVALUE_NOTPOSSIBLE which triggers the caller to
send a second generic cancel message; update the return to a non-canceling
success code (e.g., RETURNVALUE_NOERROR) from the block that contains
maxMoveItems, movedRewardItems, rewardCount and lootedItemsMessage so the caller
does not emit the duplicate generic message—ensure you only change the return
value after the player->sendTextMessage call for that condition.

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

Labels

Stale No activity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants