From 6b0df4ff6c1c4d3d1646d22ed37e770dc261c6c5 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 9 May 2026 00:41:54 -0500 Subject: [PATCH] Fix ambiguous item parsing in bot text (#2356) ## Pull Request Description This change fixes two cases of broken bot text with respect to inventory items. 1. Reserved inventory qualifiers such as "mount," "food," "drink," etc. no longer also trigger generic item name matching. I first noticed this problem when my resto Shaman who had the "Mounting Vengeance" weapon in her inventory would repeatedly give error messages of failing to use it while mounting (because mounting also causes bots to use items that fit the reserved "mount," which due to this bug, also caused bots to try to use any item with "mount" in its name). 2. Custom cast output text no longer reports an inferred bag item as the spell target for normal unit-targeted casts such as "cast chain heal on Keleborn." There was a bug where the action would first parse the actual target and then parse the spell text and then try to match the last word of the string to a bag item (so the bot would say it was casting chain heal on a healing potion, even though the heal was in fact cast correctly on a player). ## Feature Evaluation - Describe the **minimum logic** required to achieve the intended behavior. - Add a reserved-qualifier check in InventoryAction::parseItems() so reserved selectors do not also run through FindNamedItemVisitor. - In custom cast output text, choose the displayed target based on the actual target type already resolved for the cast. - It does not change mount selection behavior itself. - It does not add new spell-target parsing rules. - Describe the **processing cost** when this logic executes across many bots. - None. ## How to Test the Changes Reserved qualifiers: 1. Give a bot in your party the "Mounting Vengeance" weapon. 2. Mount up, and the bot should mount too without saying anything (before the fix, the bot would say it is using the weapon and that the item was not found). Spell cast text: 1. Give a bot an inventory item whose name overlaps with part of a spell name, such as a healing potion. 2. Command a bot to cast some heal on a player. 3. The bot should cast the spell on the intended player (as was the case previously), and the status text names the player instead of the inventory item. ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - [x] No, not at all - [ ] Minimal impact (**explain below**) - Does this change modify default bot behavior? - [x] No - [ ] Yes (**explain why**) - Does this change add new decision branches or increase maintenance complexity? - [ ] No - [x] Yes (**explain below**) Very minor changes. InventoryAction gets one explicit reserved-qualifier guard. Custom cast text selection becomes more explicit about which target type should be displayed. ## AI Assistance Was AI assistance used while working on this change? - [ ] No - [x] Yes (**explain below**) GPT-5.4 was used to trace the relevant code paths for the errors and propose the changes. ## Final Checklist - [x] Stability is not compromised. - [x] Performance impact is understood, tested, and acceptable. - [x] Added logic complexity is justified and explained. - [x] Any new bot dialogue lines are translated. - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com> Co-authored-by: bash Co-authored-by: Revision Co-authored-by: kadeshar --- src/Ai/Base/Actions/CastCustomSpellAction.cpp | 11 +++--- src/Ai/Base/Actions/InventoryAction.cpp | 34 +++++++++++++++++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Ai/Base/Actions/CastCustomSpellAction.cpp b/src/Ai/Base/Actions/CastCustomSpellAction.cpp index 15c35ee43..3defe55d1 100644 --- a/src/Ai/Base/Actions/CastCustomSpellAction.cpp +++ b/src/Ai/Base/Actions/CastCustomSpellAction.cpp @@ -143,14 +143,17 @@ bool CastCustomSpellAction::Execute(Event event) std::ostringstream spellName; spellName << ChatHelper::FormatSpell(spellInfo) << " on "; + bool const hasItemTarget = itemTarget && + (spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM); + if (bot->GetTrader()) spellName << "trade item"; - else if (itemTarget) + else if (hasItemTarget) spellName << chat->FormatItem(itemTarget->GetTemplate()); - else if (target == bot) - spellName << "self"; - else + else if (target != bot) spellName << target->GetName(); + else + spellName << "self"; if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget)) { diff --git a/src/Ai/Base/Actions/InventoryAction.cpp b/src/Ai/Base/Actions/InventoryAction.cpp index 83fc00f12..8d3046061 100644 --- a/src/Ai/Base/Actions/InventoryAction.cpp +++ b/src/Ai/Base/Actions/InventoryAction.cpp @@ -10,6 +10,31 @@ #include "ItemVisitors.h" #include "Playerbots.h" +namespace +{ +bool isReservedQualifier(std::string const& text) +{ + static std::array const exactQualifiers = { + "ammo", + "conjured drink", + "conjured food", + "conjured water", + "drink", + "food", + "healing potion", + "mount", + "mana potion", + "pet", + "quest", + "recipe", + "water" + }; + + return std::find(exactQualifiers.begin(), exactQualifiers.end(), text) != exactQualifiers.end() || + text.rfind("usage ", 0) == 0; +} +} + void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) { if (mask & ITERATE_ITEMS_IN_BAGS) @@ -292,9 +317,12 @@ std::vector InventoryAction::parseItems(std::string const text, IterateIt found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); } - FindNamedItemVisitor visitor(bot, text); - IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); - found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + if (!isReservedQualifier(text)) + { + FindNamedItemVisitor visitor(bot, text); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } uint32 quality = chat->parseItemQuality(text); if (quality != MAX_ITEM_QUALITY)