/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it * and/or modify it under version 2 of the License, or (at your option), any later version. */ #include "LootObjectStack.h" #include "LootMgr.h" #include "Playerbots.h" #include "Unit.h" #define MAX_LOOT_OBJECT_COUNT 200 LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {} LootTarget::LootTarget(LootTarget const& other) { guid = other.guid; asOfTime = other.asOfTime; } LootTarget& LootTarget::operator=(LootTarget const& other) { if ((void*)this == (void*)&other) return *this; guid = other.guid; asOfTime = other.asOfTime; return *this; } bool LootTarget::operator<(LootTarget const& other) const { return guid < other.guid; } void LootTargetList::shrink(time_t fromTime) { for (std::set::iterator i = begin(); i != end();) { if (i->asOfTime <= fromTime) erase(i++); else ++i; } } LootObject::LootObject(Player* bot, ObjectGuid guid) : guid(), skillId(SKILL_NONE), reqSkillValue(0), reqItem(0) { Refresh(bot, guid); } void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) { skillId = SKILL_NONE; reqSkillValue = 0; reqItem = 0; guid.Clear(); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (!botAI) { return; } Creature* creature = botAI->GetCreature(lootGUID); if (creature && creature->getDeathState() == DeathState::Corpse) { if (creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) guid = lootGUID; if (creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) { skillId = creature->GetCreatureTemplate()->GetRequiredLootSkill(); uint32 targetLevel = creature->GetLevel(); reqSkillValue = targetLevel < 10 ? 1 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; if (botAI->HasSkill((SkillType)skillId) && bot->GetSkillValue(skillId) >= reqSkillValue) guid = lootGUID; } return; } GameObject* go = botAI->GetGameObject(lootGUID); if (go && go->isSpawned() && go->GetGoState() == GO_STATE_READY) { bool onlyHasQuestItems = true; bool hasAnyQuestItems = false; GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry()); for (size_t i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++) { if (!items || i >= items->size()) break; uint32 itemId = uint32((*items)[i]); if (!itemId) continue; hasAnyQuestItems = true; if (IsNeededForQuest(bot, itemId)) { this->guid = lootGUID; return; } const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId); if (!proto) continue; if (proto->Class != ITEM_CLASS_QUEST) { onlyHasQuestItems = false; } } // 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); if (!lockInfo) return; for (uint8 i = 0; i < 8; ++i) { switch (lockInfo->Type[i]) { case LOCK_KEY_ITEM: if (lockInfo->Index[i] > 0) { reqItem = lockInfo->Index[i]; guid = lootGUID; } break; case LOCK_KEY_SKILL: if (goId == 13891 || goId == 19535) // Serpentbloom { this->guid = lootGUID; } else if (SkillByLockType(LockType(lockInfo->Index[i])) > 0) { skillId = SkillByLockType(LockType(lockInfo->Index[i])); reqSkillValue = std::max((uint32)1, lockInfo->Skill[i]); guid = lootGUID; } break; case LOCK_KEY_NONE: guid = lootGUID; break; } } } } bool LootObject::IsNeededForQuest(Player* bot, uint32 itemId) { for (int qs = 0; qs < MAX_QUEST_LOG_SIZE; ++qs) { uint32 questId = bot->GetQuestSlotQuestId(qs); if (questId == 0) continue; QuestStatusData& qData = bot->getQuestStatusMap()[questId]; if (qData.Status != QUEST_STATUS_INCOMPLETE) continue; Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); if (!qInfo) continue; for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) { if (!qInfo->RequiredItemCount[i] || (qInfo->RequiredItemCount[i] - qData.ItemCount[i]) <= 0) continue; if (qInfo->RequiredItemId[i] != itemId) continue; return true; } } return false; } WorldObject* LootObject::GetWorldObject(Player* bot) { Refresh(bot, guid); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (!botAI) { return nullptr; } Creature* creature = botAI->GetCreature(guid); if (creature && creature->getDeathState() == DeathState::Corpse && creature->IsInWorld()) return creature; GameObject* go = botAI->GetGameObject(guid); if (go && go->isSpawned() && go->IsInWorld()) return go; return nullptr; } LootObject::LootObject(LootObject const& other) { guid = other.guid; skillId = other.skillId; reqSkillValue = other.reqSkillValue; reqItem = other.reqItem; } bool LootObject::IsLootPossible(Player* 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); if (!botAI) { return false; } if (reqItem && !bot->HasItemCount(reqItem, 1)) return false; if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f) return false; Creature* creature = botAI->GetCreature(guid); if (creature && creature->getDeathState() == DeathState::Corpse) { if (!bot->isAllowedToLoot(creature) && skillId != SKILL_SKINNING) return false; } // Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) GameObject* go = botAI->GetGameObject(guid); if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE)) return false; if (skillId == SKILL_NONE) return true; if (skillId == SKILL_FISHING) return false; if (!botAI->HasSkill((SkillType)skillId)) return false; if (!reqSkillValue) return true; 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)) { 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; } bool LootObjectStack::Add(ObjectGuid guid) { if (availableLoot.size() >= MAX_LOOT_OBJECT_COUNT) { availableLoot.shrink(time(nullptr) - 30); } if (availableLoot.size() >= MAX_LOOT_OBJECT_COUNT) { availableLoot.clear(); } if (!availableLoot.insert(guid).second) return false; return true; } void LootObjectStack::Remove(ObjectGuid guid) { LootTargetList::iterator i = availableLoot.find(guid); if (i != availableLoot.end()) availableLoot.erase(i); } void LootObjectStack::Clear() { availableLoot.clear(); } bool LootObjectStack::CanLoot(float maxDistance) { std::vector ordered = OrderByDistance(maxDistance); return !ordered.empty(); } 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); std::map sortedMap; LootTargetList safeCopy(availableLoot); for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++) { ObjectGuid guid = i->guid; LootObject lootObject(bot, guid); if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid continue; 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 (auto& [_, lootObject] : sortedMap) result.push_back(lootObject); return result; }