diff --git a/src/LootObjectStack.cpp b/src/LootObjectStack.cpp index fa1cdf23..196e986c 100644 --- a/src/LootObjectStack.cpp +++ b/src/LootObjectStack.cpp @@ -81,7 +81,8 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) GameObject* go = botAI->GetGameObject(lootGUID); if (go && go->isSpawned() && go->GetGoState() == GO_STATE_READY) { - bool isQuestItemOnly = false; + bool onlyHasQuestItems = true; + bool hasAnyQuestItems = false; GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry()); for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++) @@ -89,19 +90,88 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) if (!items || i >= items->size()) break; - auto itemId = uint32((*items)[i]); + uint32 itemId = uint32((*items)[i]); + if (!itemId) + continue; + + hasAnyQuestItems = true; if (IsNeededForQuest(bot, itemId)) { this->guid = lootGUID; return; } - isQuestItemOnly |= itemId > 0; + + const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + continue; + + if (proto->Class != ITEM_CLASS_QUEST) + { + onlyHasQuestItems = false; + } } - if (isQuestItemOnly) + // Retrieve the correct loot table entry + uint32 lootEntry = go->GetGOInfo()->GetLootId(); + if (lootEntry == 0) return; + // Check the main loot template + if (const LootTemplate* lootTemplate = LootTemplates_Gameobject.GetLootFor(lootEntry)) + { + Loot loot; + lootTemplate->Process(loot, LootTemplates_Gameobject, 1, bot); + + for (const LootItem& item : loot.items) + { + uint32 itemId = item.itemid; + if (!itemId) + continue; + + const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + continue; + + if (proto->Class != ITEM_CLASS_QUEST) + { + onlyHasQuestItems = false; + break; + } + + // If this item references another loot table, process it + if (const LootTemplate* refLootTemplate = LootTemplates_Reference.GetLootFor(itemId)) + { + Loot refLoot; + refLootTemplate->Process(refLoot, LootTemplates_Reference, 1, bot); + + for (const LootItem& refItem : refLoot.items) + { + uint32 refItemId = refItem.itemid; + if (!refItemId) + continue; + + const ItemTemplate* refProto = sObjectMgr->GetItemTemplate(refItemId); + if (!refProto) + continue; + + if (refProto->Class != ITEM_CLASS_QUEST) + { + onlyHasQuestItems = false; + break; + } + } + } + } + } + + // If gameobject has only quest items that bot doesn’t need, skip it. + if (hasAnyQuestItems && onlyHasQuestItems) + return; + + // Otherwise, loot it. + guid = lootGUID; + uint32 goId = go->GetEntry(); uint32 lockId = go->GetGOInfo()->GetLockId(); LockEntry const* lockInfo = sLockStore.LookupEntry(lockId); @@ -119,6 +189,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) guid = lootGUID; } break; + case LOCK_KEY_SKILL: if (goId == 13891 || goId == 19535) // Serpentbloom { @@ -131,6 +202,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) guid = lootGUID; } break; + case LOCK_KEY_NONE: guid = lootGUID; break; @@ -200,7 +272,11 @@ LootObject::LootObject(LootObject const& other) bool LootObject::IsLootPossible(Player* bot) { - if (IsEmpty() || !GetWorldObject(bot)) + if (IsEmpty() || !bot) + return false; + + WorldObject* worldObj = GetWorldObject(bot); // Store result to avoid multiple calls + if (!worldObj) return false; PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); @@ -211,7 +287,7 @@ bool LootObject::IsLootPossible(Player* bot) if (reqItem && !bot->HasItemCount(reqItem, 1)) return false; - if (abs(GetWorldObject(bot)->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE) + if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f) return false; Creature* creature = botAI->GetCreature(guid); @@ -236,25 +312,32 @@ bool LootObject::IsLootPossible(Player* bot) uint32 skillValue = uint32(bot->GetSkillValue(skillId)); if (reqSkillValue > skillValue) return false; - - if (skillId == SKILL_MINING && !bot->HasItemCount(756, 1) && - !bot->HasItemCount(778, 1) && - !bot->HasItemCount(1819, 1) && - !bot->HasItemCount(1893, 1) && - !bot->HasItemCount(1959, 1) && - !bot->HasItemCount(2901, 1) && - !bot->HasItemCount(9465, 1) && - !bot->HasItemCount(20723, 1) && - !bot->HasItemCount(40772, 1) && - !bot->HasItemCount(40892, 1) && - !bot->HasItemCount(40893, 1) ) - - if (skillId == SKILL_SKINNING && !bot->HasItemCount(7005, 1) && - !bot->HasItemCount(40772, 1) && - !bot->HasItemCount(40893, 1) && - !bot->HasItemCount(12709, 1) && - !bot->HasItemCount(19901, 1) ) - return false; + + if (skillId == SKILL_MINING && + !bot->HasItemCount(756, 1) && + !bot->HasItemCount(778, 1) && + !bot->HasItemCount(1819, 1) && + !bot->HasItemCount(1893, 1) && + !bot->HasItemCount(1959, 1) && + !bot->HasItemCount(2901, 1) && + !bot->HasItemCount(9465, 1) && + !bot->HasItemCount(20723, 1) && + !bot->HasItemCount(40772, 1) && + !bot->HasItemCount(40892, 1) && + !bot->HasItemCount(40893, 1)) + { + return false; // Bot is missing a mining pick + } + + if (skillId == SKILL_SKINNING && + !bot->HasItemCount(7005, 1) && + !bot->HasItemCount(40772, 1) && + !bot->HasItemCount(40893, 1) && + !bot->HasItemCount(12709, 1) && + !bot->HasItemCount(19901, 1)) + { + return false; // Bot is missing a skinning knife + } return true; } @@ -297,7 +380,6 @@ LootObject LootObjectStack::GetLoot(float maxDistance) std::vector ordered = OrderByDistance(maxDistance); return ordered.empty() ? LootObject() : *ordered.begin(); } - std::vector LootObjectStack::OrderByDistance(float maxDistance) { availableLoot.shrink(time(nullptr) - 30); @@ -308,17 +390,23 @@ std::vector LootObjectStack::OrderByDistance(float maxDistance) { ObjectGuid guid = i->guid; LootObject lootObject(bot, guid); - if (!lootObject.IsLootPossible(bot)) + if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid continue; - float distance = bot->GetDistance(lootObject.GetWorldObject(bot)); + WorldObject* worldObj = lootObject.GetWorldObject(bot); + if (!worldObj) // Prevent null pointer dereference + { + continue; + } + + float distance = bot->GetDistance(worldObj); if (!maxDistance || distance <= maxDistance) sortedMap[distance] = lootObject; } std::vector result; - for (std::map::iterator i = sortedMap.begin(); i != sortedMap.end(); i++) - result.push_back(i->second); + for (auto& [_, lootObject] : sortedMap) + result.push_back(lootObject); return result; } diff --git a/src/strategy/actions/AddLootAction.cpp b/src/strategy/actions/AddLootAction.cpp index fadf3dac..c4d6eae2 100644 --- a/src/strategy/actions/AddLootAction.cpp +++ b/src/strategy/actions/AddLootAction.cpp @@ -51,29 +51,11 @@ bool AddGatheringLootAction::AddLoot(ObjectGuid guid) if (loot.IsEmpty() || !wo) return false; - if (!bot->IsWithinLOSInMap(wo)) - return false; - if (loot.skillId == SKILL_NONE) return false; if (!loot.IsLootPossible(bot)) return false; - if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, wo), INTERACTION_DISTANCE)) - { - std::list targets; - Acore::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, bot, sPlayerbotAIConfig->lootDistance); - Acore::UnitListSearcher searcher(bot, targets, u_check); - Cell::VisitAllObjects(bot, searcher, sPlayerbotAIConfig->lootDistance * 1.5f); - if (!targets.empty()) - { - std::ostringstream out; - out << "Kill that " << targets.front()->GetName() << " so I can loot freely"; - botAI->TellError(out.str()); - return false; - } - } - return AddAllLootAction::AddLoot(guid); } diff --git a/src/strategy/actions/LootAction.cpp b/src/strategy/actions/LootAction.cpp index 0b45763d..c1724e17 100644 --- a/src/strategy/actions/LootAction.cpp +++ b/src/strategy/actions/LootAction.cpp @@ -79,9 +79,16 @@ bool OpenLootAction::DoLoot(LootObject& lootObject) return false; Creature* creature = botAI->GetCreature(lootObject.guid); - if (creature && bot->GetDistance(creature) > INTERACTION_DISTANCE) + if (creature && bot->GetDistance(creature) > INTERACTION_DISTANCE - 2.0f) return false; + // Dismount if the bot is mounted + if (bot->IsMounted()) + { + bot->Dismount(); + botAI->SetNextCheckDelay(sPlayerbotAIConfig->lootDelay); // Small delay to avoid animation issues + } + if (creature && creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) { WorldPacket packet(CMSG_LOOT, 8); @@ -116,7 +123,7 @@ bool OpenLootAction::DoLoot(LootObject& lootObject) } GameObject* go = botAI->GetGameObject(lootObject.guid); - if (go && bot->GetDistance(go) > INTERACTION_DISTANCE) + if (go && bot->GetDistance(go) > INTERACTION_DISTANCE - 2.0f) return false; if (go && (go->GetGoState() != GO_STATE_READY)) @@ -418,6 +425,7 @@ bool StoreLootAction::Execute(Event event) WorldPacket packet(CMSG_AUTOSTORE_LOOT_ITEM, 1); packet << itemindex; bot->GetSession()->HandleAutostoreLootItemOpcode(packet); + botAI->SetNextCheckDelay(sPlayerbotAIConfig->lootDelay); if (proto->Quality > ITEM_QUALITY_NORMAL && !urand(0, 50) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig->randomBotEmote) botAI->PlayEmote(TEXT_EMOTE_CHEER); diff --git a/src/strategy/triggers/LootTriggers.cpp b/src/strategy/triggers/LootTriggers.cpp index 3a727668..17c13042 100644 --- a/src/strategy/triggers/LootTriggers.cpp +++ b/src/strategy/triggers/LootTriggers.cpp @@ -13,9 +13,9 @@ bool LootAvailableTrigger::IsActive() { return AI_VALUE(bool, "has available loot") && (sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), - INTERACTION_DISTANCE) || + INTERACTION_DISTANCE - 2.0f) || AI_VALUE(GuidVector, "all targets").empty()) && - !AI_VALUE2(bool, "combat", "self target") && !AI_VALUE2(bool, "mounted", "self target"); + !AI_VALUE2(bool, "combat", "self target"); } bool FarFromCurrentLootTrigger::IsActive() @@ -24,7 +24,7 @@ bool FarFromCurrentLootTrigger::IsActive() if (!loot.IsLootPossible(bot)) return false; - return AI_VALUE2(float, "distance", "loot target") > INTERACTION_DISTANCE; + return AI_VALUE2(float, "distance", "loot target") >= INTERACTION_DISTANCE - 2.0f; } bool CanLootTrigger::IsActive() { return AI_VALUE(bool, "can loot"); }