Files
mod-playerbots/src/LootObjectStack.cpp
Yunfan Li 38912d4a8a Auto do quest feature (new rpg strategy) (#1034)
* New rpg startup speed up and refactor

* New rpg do quest

* Fix invalid height in quest poi

* Add quest accept and reward limitation

* New rpg quest improvement

* Organize quest log, reward quests and fix grind target

* Quest dropped statistic and remove redundant code

* Decrease grind relevance lower than loot

* Fix new rpg drop quest

* Go to reward quest instead of innkeeper when quest completed

* Fix incorrect logic in do quest reward

* Fix reset quests in factory

* Fix crash on grind target value

Co-authored-by: SaW <swerkhoven@outlook.com>

* Fix a minor error in DoCompletedQuest

* Let bots get rid of impossible quests faster

* Increase loot fluency (especially for caster)

* Remove seasonal quests from auto accept

* Enhance quest accept condition check

* Add questgiver check (limit acceptation of quest 7946)

* Questgiver check and localization

* Near npc fix

* Fix quest item report

* Add lowPriorityQuest set for quests can not be done

* Improve gameobjects loot

* Do complete quest

* FIx move far to teleport check

* Accept or reward quest from game objects

* Fix possible crash in rpg game objects

* Fix ChooseNpcOrGameObjectToInteract crash

---------

Co-authored-by: SaW <swerkhoven@outlook.com>
2025-03-14 21:31:33 +08:00

413 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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<LootTarget>::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 (int 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 doesnt 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;
}
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<LootObject> ordered = OrderByDistance(maxDistance);
return !ordered.empty();
}
LootObject LootObjectStack::GetLoot(float maxDistance)
{
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return ordered.empty() ? LootObject() : *ordered.begin();
}
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
{
availableLoot.shrink(time(nullptr) - 30);
std::map<float, LootObject> 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<LootObject> result;
for (auto& [_, lootObject] : sortedMap)
result.push_back(lootObject);
return result;
}