Files
mod-playerbots/src/Ai/Base/Actions/ChooseTravelTargetAction.cpp
Keleborn 441f9f7552 Warnings PR 1: Event warnings and headers (#2106)
# Pull Request

This is the first in a series of PRs intended to eliminate warnings in
the module. The design intent is to eliminate the calling event when not
needed in the body of the function. Based off of SmashingQuasars work.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-14 20:55:10 +01:00

998 lines
32 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ChooseTravelTargetAction.h"
#include "ChatHelper.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
bool ChooseTravelTargetAction::Execute(Event /*event*/)
{
// Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); //not used, line marked for removal.
//Get the current travel target. This target is no longer active.
TravelTarget* oldTarget = context->GetValue<TravelTarget*>("travel target")->Get();
//Select a new target to travel to.
TravelTarget newTarget = TravelTarget(botAI);
if (!oldTarget) return false;
if (!oldTarget->isForced() || oldTarget->getStatus() == TravelStatus::TRAVEL_STATUS_EXPIRED)
getNewTarget(&newTarget, oldTarget);
else
newTarget.copyTarget(oldTarget);
//If the new target is not active we failed.
if (!newTarget.isActive() && !newTarget.isForced())
return false;
setNewTarget(&newTarget, oldTarget);
return true;
}
// Select a new travel target.
// Currently this selectes mostly based on priority (current quest > new quest).
// This works fine because destinations can be full (max 15 bots per quest giver, max 1 bot per quest mob).
//
// Eventually we want to rewrite this to be more intelligent.
void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
{
// Join groups members
bool foundTarget = foundTarget = SetGroupTarget(newTarget);
//Empty bags/repair
if (!foundTarget && urand(1, 100) > 10 && bot->GetLevel() > 5) //90% chance
{
if (AI_VALUE2(bool, "group or", "should sell,can sell,following party,near leader") ||
AI_VALUE2(bool, "group or", "should repair,can repair,following party,near leader")
)
{
foundTarget = SetRpgTarget(newTarget); //Go to town to sell items or repair
}
}
//Rpg in city
if (!foundTarget && urand(1, 100) > 90 && bot->GetLevel() > 5) //10% chance
{
foundTarget = SetNpcFlagTarget(newTarget, { UNIT_NPC_FLAG_BANKER,UNIT_NPC_FLAG_BATTLEMASTER,UNIT_NPC_FLAG_AUCTIONEER });
}
// PvP activities
bool pvpActivate = false;
if (pvpActivate && !foundTarget && urand(0, 4) && bot->GetLevel() > 50)
{
WorldPosition pos = WorldPosition(bot);
WorldPosition* botPos = &pos;
TravelTarget* target = context->GetValue<TravelTarget*>("travel target")->Get();
TravelDestination* dest = ChooseTravelTargetAction::FindDestination(bot, "Tarren Mill");
if (dest)
{
std::vector<WorldPosition*> points = dest->nextPoint(botPos, true);
if (!points.empty())
{
target->setTarget(dest, points.front());
target->setForced(true);
std::ostringstream out; out << "Traveling to " << dest->getTitle();
botAI->TellMasterNoFacing(out.str());
foundTarget = true;
}
}
}
//Grind for money
if (!foundTarget && AI_VALUE(bool, "should get money"))
{
//Empty mail for money
//if (AI_VALUE(bool, "can get mail"))
//{
// foundTarget = SetGOTypeTarget(requester, newTarget, GAMEOBJECT_TYPE_MAILBOX, "", false); //Find a mailbox
//}
if (!foundTarget)
{
// 50% Focus on active quests for money.
if (urand(1, 100) > 50)
{
// 50% Focus on active quests for money.
if (urand(1, 100) > 50)
{
// Turn in quests for money.
foundTarget = SetQuestTarget(newTarget, true, false, true, true);
}
if (!foundTarget)
{
// Find new (low) level quests
foundTarget = SetQuestTarget(newTarget, false, true, false, false);
}
}
else
{
// Go grind mobs for money
foundTarget = SetGrindTarget(newTarget);
}
}
}
//Continue current target. 90% chance
if (!foundTarget && urand(1, 100) > 10)
{
// Extend current target.
foundTarget = SetCurrentTarget(newTarget, oldTarget);
}
//Get mail 30% chance
//if (!foundTarget && urand(1, 100) > 70)
//{
// if (AI_VALUE(bool, "can get mail"))
// {
// foundTarget = SetGOTypeTarget(requester, newTarget, GAMEOBJECT_TYPE_MAILBOX, "", false); //Find a mailbox
// }
//}
//Dungeon in group. 50% chance
if (!foundTarget && urand(1, 100) > 50)
{
if (AI_VALUE(bool, "can fight boss"))
{
// Go fight a (dungeon boss)
foundTarget = SetBossTarget(newTarget);
}
}
//Do quests (start, do, end) 95% chance
if (!foundTarget && urand(1, 100) > 5)
{
// Do any nearby
foundTarget = SetQuestTarget(newTarget, false, true, true, true);
}
//Explore a nearby unexplored area.
if (!foundTarget && botAI->HasStrategy("explore", BotState::BOT_STATE_NON_COMBAT) && urand(1, 100) > 90) //10% chance Explore a unexplored sub-zone.
{
foundTarget = SetExploreTarget(newTarget);
}
//Just hang with an npc 50% chance
if (!foundTarget && urand(1, 100) > 50)
{
foundTarget = SetRpgTarget(newTarget);
if (foundTarget)
newTarget->setForced(true);
}
if (!foundTarget)
{
foundTarget = SetGrindTarget(newTarget);
}
// Idle a bit.
if (!foundTarget)
SetNullTarget(newTarget);
}
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
{
// Tell the master where we are going.
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
ReportTravelTarget(newTarget, oldTarget);
// If we are heading to a creature/npc clear it from the ignore list.
if (oldTarget && oldTarget == newTarget && newTarget->getEntry())
{
GuidSet& ignoreList = context->GetValue<GuidSet&>("ignore rpg target")->Get();
for (ObjectGuid const guid : ignoreList)
{
if (guid.GetEntry() == newTarget->getEntry())
{
ignoreList.erase(guid);
}
}
context->GetValue<GuidSet&>("ignore rpg target")->Set(ignoreList);
}
// Actually apply the new target to the travel target used by the bot.
oldTarget->copyTarget(newTarget);
// If we are idling but have a master. Idle only 10 seconds.
if (botAI->GetMaster() && oldTarget->isActive() &&
oldTarget->getDestination()->getName() == "NullTravelDestination")
oldTarget->setExpireIn(10 * IN_MILLISECONDS);
else if (oldTarget->isForced()) // Make sure travel goes into cooldown after getting to the destination.
oldTarget->setExpireIn(HOUR * IN_MILLISECONDS);
// Clear rpg and pull/grind target. We want to travel, not hang around some more.
RESET_AI_VALUE(GuidPosition, "rpg target");
RESET_AI_VALUE(ObjectGuid, "pull target");
}
// Tell the master what travel target we are moving towards.
// This should at some point be rewritten to be denser or perhaps logic moved to ->getTitle()
void ChooseTravelTargetAction::ReportTravelTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
{
TravelDestination* destination = newTarget->getDestination();
TravelDestination* oldDestination = oldTarget->getDestination();
std::ostringstream out;
if (newTarget->isForced())
out << "(Forced) ";
if (destination->getName() == "QuestRelationTravelDestination" ||
destination->getName() == "QuestObjectiveTravelDestination")
{
QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination;
Quest const* quest = QuestDestination->GetQuestTemplate();
WorldPosition botLocation(bot);
CreatureTemplate const* cInfo = nullptr;
GameObjectTemplate const* gInfo = nullptr;
if (destination->getEntry() > 0)
cInfo = sObjectMgr->GetCreatureTemplate(destination->getEntry());
else
gInfo = sObjectMgr->GetGameObjectTemplate(destination->getEntry() * -1);
std::string Sub;
if (newTarget->isGroupCopy())
out << "Following group ";
else if (oldDestination && oldDestination == destination)
out << "Continuing ";
else
out << "Traveling ";
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
out << " for " << chat->FormatQuest(quest);
out << " to " << QuestDestination->getTitle();
botAI->TellMaster(out);
}
else if (destination->getName() == "RpgTravelDestination")
{
RpgTravelDestination* RpgDestination = (RpgTravelDestination*)destination;
WorldPosition botLocation(bot);
if (newTarget->isGroupCopy())
out << "Following group ";
else if (oldDestination && oldDestination == destination)
out << "Continuing ";
else
out << "Traveling ";
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
out << " for ";
if (AI_VALUE2(bool, "group or", "should sell,can sell"))
out << "selling items";
else if (AI_VALUE2(bool, "group or", "should repair,can repair"))
out << "repairing";
else
out << "rpg";
out << " to " << RpgDestination->getTitle();
botAI->TellMaster(out);
}
else if (destination->getName() == "ExploreTravelDestination")
{
ExploreTravelDestination* ExploreDestination = (ExploreTravelDestination*)destination;
WorldPosition botLocation(bot);
if (newTarget->isGroupCopy())
out << "Following group ";
else if (oldDestination && oldDestination == destination)
out << "Continuing ";
else
out << "Traveling ";
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
out << " for exploration";
out << " to " << ExploreDestination->getTitle();
botAI->TellMaster(out);
}
else if (destination->getName() == "GrindTravelDestination")
{
GrindTravelDestination* GrindDestination = (GrindTravelDestination*)destination;
WorldPosition botLocation(bot);
if (newTarget->isGroupCopy())
out << "Following group ";
else if (oldDestination && oldDestination == destination)
out << "Continuing ";
else
out << "Traveling ";
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
out << " for grinding money";
out << " to " << GrindDestination->getTitle();
botAI->TellMaster(out);
}
else if (destination->getName() == "BossTravelDestination")
{
BossTravelDestination* BossDestination = (BossTravelDestination*)destination;
WorldPosition botLocation(bot);
if (newTarget->isGroupCopy())
out << "Following group ";
else if (oldDestination && oldDestination == destination)
out << "Continuing ";
else
out << "Traveling ";
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
out << " for good loot";
out << " to " << BossDestination->getTitle();
botAI->TellMaster(out);
}
else if (destination->getName() == "NullTravelDestination")
{
if (!oldTarget->getDestination() || oldTarget->getDestination()->getName() != "NullTravelDestination")
{
botAI->TellMaster("No where to travel. Idling a bit.");
}
}
}
bool ChooseTravelTargetAction::getBestDestination(std::vector<TravelDestination*>* activeDestinations,
std::vector<WorldPosition*>* activePoints)
{
if (activeDestinations->empty() || activePoints->empty()) // No targets or no points.
return false;
WorldPosition botLocation(bot);
std::vector<WorldPosition*> availablePoints =
TravelMgr::instance().getNextPoint(&botLocation, *activePoints); // Pick a good point.
if (availablePoints.empty()) // No points available.
return false;
TravelDestination* targetDestination;
for (auto activeTarget : *activeDestinations) // Pick the destination that has this point.
if (activeTarget->distanceTo(availablePoints.front()) == 0)
targetDestination = activeTarget;
if (!targetDestination)
return false;
activeDestinations->clear();
activePoints->clear();
activeDestinations->push_back(targetDestination);
activePoints->push_back(availablePoints.front());
return true;
}
bool ChooseTravelTargetAction::SetGroupTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
GuidList groupPlayers;
Group* group = bot->GetGroup();
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (ref->GetSource() != bot)
{
if (ref->getSubGroup() != bot->GetSubGroup())
{
groupPlayers.push_back(ref->GetSource()->GetGUID());
}
else
{
groupPlayers.push_front(ref->GetSource()->GetGUID());
}
}
}
}
// Find targets of the group.
for (auto& member : groupPlayers)
{
Player* player = ObjectAccessor::FindPlayer(member);
if (!player)
continue;
PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player);
if (!playerBotAI)
continue;
if (!playerBotAI->GetAiObjectContext())
continue;
TravelTarget* groupTarget = playerBotAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
if (groupTarget->isGroupCopy())
continue;
if (!groupTarget->isActive())
continue;
if (!groupTarget->getDestination()->isActive(bot) ||
groupTarget->getDestination()->getName() == "RpgTravelDestination")
continue;
activeDestinations.push_back(groupTarget->getDestination());
activePoints.push_back(groupTarget->getPosition());
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front(), true);
return target->isActive();
}
bool ChooseTravelTargetAction::SetCurrentTarget(TravelTarget* target, TravelTarget* oldTarget)
{
TravelDestination* oldDestination = oldTarget->getDestination();
if (oldTarget->isMaxRetry(false))
return false;
if (!oldDestination) // Does this target have a destination?
return false;
if (!oldDestination->isActive(bot)) // Is the destination still valid?
return false;
WorldPosition botLocation(bot);
std::vector<WorldPosition*> availablePoints = oldDestination->nextPoint(&botLocation);
if (availablePoints.empty())
return false;
target->setTarget(oldTarget->getDestination(), availablePoints.front());
target->setStatus(TRAVEL_STATUS_TRAVEL);
target->setRetry(false, oldTarget->getRetryCount(false) + 1);
return target->isActive();
}
bool ChooseTravelTargetAction::SetQuestTarget(TravelTarget* target, bool onlyCompleted, bool newQuests, bool activeQuests, bool completedQuests)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
bool onlyClassQuest = !urand(0, 10);
if (newQuests)
{
// Prefer new quests near the player at lower levels.
activeDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, -1, true, false, 400 + bot->GetLevel() * 10);
}
if (activeQuests || completedQuests)
{
QuestStatusMap& questMap = bot->getQuestStatusMap();
//Find destinations related to the active quests.
for (auto& quest : questMap)
{
if (bot->IsQuestRewarded(quest.first))
continue;
uint32 questId = quest.first;
// QuestStatusData* questStatus = &quest.second; //not used, line marked for removal.
const auto questTemplate = sObjectMgr->GetQuestTemplate(questId);
if (!activeQuests && !bot->CanRewardQuest(questTemplate, false))
continue;
if (!completedQuests && bot->CanRewardQuest(questTemplate, false))
continue;
//Find quest takers or objectives
std::vector<TravelDestination*> questDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, questId, true, false, 0);
if (onlyClassQuest && activeDestinations.size() && questDestinations.size()) //Only do class quests if we have any.
{
if (activeDestinations.front()->GetQuestTemplate()->GetRequiredClasses() && !questTemplate->GetRequiredClasses())
continue;
if (!activeDestinations.front()->GetQuestTemplate()->GetRequiredClasses() && questTemplate->GetRequiredClasses())
activeDestinations.clear();
}
activeDestinations.insert(activeDestinations.end(), questDestinations.begin(), questDestinations.end());
}
}
if (newQuests && activeDestinations.empty())
activeDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, -1, true, false); //If we really don't find any new quests look futher away.
if (botAI->HasStrategy("debug travel", BotState::BOT_STATE_NON_COMBAT))
botAI->TellMasterNoFacing(std::to_string(activeDestinations.size()) + " quest destinations found.");
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
bool ChooseTravelTargetAction::SetNewQuestTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
// Find quest givers.
std::vector<TravelDestination*> TravelDestinations =
TravelMgr::instance().getQuestTravelDestinations(bot, -1, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
// Pick one good point per destination.
for (auto& activeTarget : activeDestinations)
{
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
if (!points.empty())
activePoints.push_back(points.front());
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
bool ChooseTravelTargetAction::SetRpgTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
// Find rpg npcs
std::vector<TravelDestination*> TravelDestinations =
TravelMgr::instance().getRpgTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
// Pick one good point per destination.
for (auto& activeTarget : activeDestinations)
{
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
if (!points.empty())
activePoints.push_back(points.front());
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
bool ChooseTravelTargetAction::SetGrindTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
// Find grind mobs.
std::vector<TravelDestination*> TravelDestinations =
TravelMgr::instance().getGrindTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
// Pick one good point per destination.
for (auto& activeTarget : activeDestinations)
{
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
if (!points.empty())
activePoints.push_back(points.front());
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
bool ChooseTravelTargetAction::SetBossTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
// Find boss mobs.
std::vector<TravelDestination*> TravelDestinations =
TravelMgr::instance().getBossTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
// Pick one good point per destination.
for (auto& activeTarget : activeDestinations)
{
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
if (!points.empty())
activePoints.push_back(points.front());
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
bool ChooseTravelTargetAction::SetExploreTarget(TravelTarget* target)
{
std::vector<TravelDestination*> activeDestinations;
std::vector<WorldPosition*> activePoints;
WorldPosition botLocation(bot);
// Find quest givers.
std::vector<TravelDestination*> TravelDestinations = TravelMgr::instance().getExploreTravelDestinations(bot, true, true);
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
/*
//Pick one good point per destination.
for (auto& activeTarget : activeDestinations)
{
//271 south shore
//35 booty bay
//380 The Barrens The Crossroads
if (((ExploreTravelDestination * )activeTarget)->getAreaId() == 380)
{
activePoints.push_back(activeTarget->getPoints(true)[0]);
}
}
*/
if (activePoints.empty())
{
TravelDestinations = TravelMgr::instance().getExploreTravelDestinations(bot, botAI->HasRealPlayerMaster());
for (auto& activeTarget : activeDestinations)
{
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
if (!points.empty())
{
activePoints.push_back(points.front());
}
}
}
if (!getBestDestination(&activeDestinations, &activePoints))
return false;
target->setTarget(activeDestinations.front(), activePoints.front());
return target->isActive();
}
char* strstri(char const* haystack, char const* needle);
bool ChooseTravelTargetAction::SetNpcFlagTarget(TravelTarget* target, std::vector<NPCFlags> flags,
std::string const name, std::vector<uint32> items)
{
WorldPosition botPos(bot);
std::vector<TravelDestination*> dests;
for (auto& d : TravelMgr::instance().getRpgTravelDestinations(bot, true, true))
{
if (!d->getEntry())
continue;
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(d->getEntry());
if (!cInfo)
continue;
bool foundFlag = false;
for (auto flag : flags)
if (cInfo->npcflag & flag)
{
foundFlag = true;
break;
}
if (!foundFlag)
continue;
if (!name.empty() && !strstri(cInfo->Name.c_str(), name.c_str()) &&
!strstri(cInfo->SubName.c_str(), name.c_str()))
continue;
if (!items.empty())
{
bool foundItem = false;
VendorItemData const* vItems = sObjectMgr->GetNpcVendorItemList(d->getEntry());
if (vItems)
{
for (auto item : items)
{
for (auto vitem : vItems->m_items)
{
if (vitem->item == item)
{
foundItem = true;
break;
}
}
}
}
if (!foundItem)
continue;
}
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
ReputationRank reaction = Unit::GetFactionReactionTo(botAI->GetBot()->GetFactionTemplateEntry(), factionEntry);
if (reaction < REP_NEUTRAL)
continue;
dests.push_back(d);
}
if (!dests.empty())
{
TravelDestination* dest = *std::min_element(dests.begin(), dests.end(),
[botPos](TravelDestination* i, TravelDestination* j) {
return i->distanceTo(const_cast<WorldPosition*>(&botPos)) <
j->distanceTo(const_cast<WorldPosition*>(&botPos));
});
std::vector<WorldPosition*> points = dest->nextPoint(const_cast<WorldPosition*>(&botPos), true);
if (points.empty())
return false;
target->setTarget(dest, points.front());
target->setForced(true);
return true;
}
return false;
}
std::vector<TravelDestination*> TravelMgr::getBossTravelDestinations(Player* bot, bool ignoreFull, bool ignoreInactive,
float maxDistance)
{
WorldPosition botLocation(bot);
std::vector<TravelDestination*> retTravelLocations;
for (auto& dest : bossMobs)
{
if (!ignoreInactive && !dest->isActive(bot))
continue;
if (dest->isFull(ignoreFull))
continue;
if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
continue;
retTravelLocations.push_back(dest);
}
return retTravelLocations;
}
bool ChooseTravelTargetAction::SetNullTarget(TravelTarget* target)
{
target->setTarget(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().nullWorldPosition, true);
return true;
}
std::vector<std::string> split(std::string const s, char delim);
char* strstri(char const* haystack, char const* needle);
TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::string const name, bool zones, bool npcs, bool quests, bool mobs, bool bosses)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// AiObjectContext* context = botAI->GetAiObjectContext(); //not used, line marked for removal.
std::vector<TravelDestination*> dests;
//Quests
if (quests)
{
for (auto& d : TravelMgr::instance().getQuestTravelDestinations(bot, 0, true, true))
{
if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d);
}
}
//Zones
if (zones)
{
for (auto& d : TravelMgr::instance().getExploreTravelDestinations(bot, true, true))
{
if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d);
}
}
//Npcs
if (npcs)
{
for (auto& d : TravelMgr::instance().getRpgTravelDestinations(bot, true, true))
{
if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d);
}
}
//Mobs
if (mobs)
{
for (auto& d : TravelMgr::instance().getGrindTravelDestinations(bot, true, true, 5000.0f))
{
if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d);
}
}
//Bosses
if (bosses)
{
for (auto& d : TravelMgr::instance().getBossTravelDestinations(bot, true, true))
{
if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d);
}
}
WorldPosition botPos(bot);
if (dests.empty())
return nullptr;
return *std::min_element(dests.begin(), dests.end(),
[botPos](TravelDestination* i, TravelDestination* j)
{
return i->distanceTo(const_cast<WorldPosition*>(&botPos)) < j->distanceTo(const_cast<WorldPosition*>(&botPos));
});
};
bool ChooseTravelTargetAction::isUseful()
{
if (!botAI->AllowActivity(TRAVEL_ACTIVITY))
return false;
return !context->GetValue<TravelTarget*>("travel target")->Get()->isActive() &&
!context->GetValue<LootObject>("loot target")->Get().IsLootPossible(bot) && !bot->IsInCombat();
}
bool ChooseTravelTargetAction::needForQuest(Unit* target)
{
bool justCheck = (bot->GetGUID() == target->GetGUID());
QuestStatusMap& questMap = bot->getQuestStatusMap();
for (auto& quest : questMap)
{
Quest const* questTemplate = sObjectMgr->GetQuestTemplate(quest.first);
if (!questTemplate)
continue;
uint32 questId = questTemplate->GetQuestId();
if (!questId)
continue;
QuestStatus status = bot->GetQuestStatus(questId);
if ((status == QUEST_STATUS_COMPLETE && !bot->GetQuestRewardStatus(questId)))
{
if (!justCheck && !target->hasInvolvedQuest(questId))
continue;
return true;
}
else if (status == QUEST_STATUS_INCOMPLETE)
{
QuestStatusData questStatus = quest.second;
if (questTemplate->GetQuestLevel() > bot->GetLevel())
continue;
for (int j = 0; j < QUEST_OBJECTIVES_COUNT; j++)
{
int32 entry = questTemplate->RequiredNpcOrGo[j];
if (entry && entry > 0)
{
int required = questTemplate->RequiredNpcOrGoCount[j];
int available = questStatus.CreatureOrGOCount[j];
if (required && available < required && (target->GetEntry() == entry || justCheck))
return true;
}
if (justCheck)
{
int32 itemId = questTemplate->RequiredItemId[j];
if (itemId && itemId > 0)
{
int required = questTemplate->RequiredItemCount[j];
int available = questStatus.ItemCount[j];
if (required && available < required)
return true;
}
}
}
if (!justCheck)
{
if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
{
if (uint32 lootId = data->lootid)
{
if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
return true;
}
}
}
}
}
return false;
}
bool ChooseTravelTargetAction::needItemForQuest(uint32 itemId, const Quest* questTemplate,
const QuestStatusData* questStatus)
{
for (uint32 i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
{
if (questTemplate->RequiredItemId[i] != itemId)
continue;
uint32 required = questTemplate->RequiredItemCount[i];
uint32 available = questStatus->ItemCount[i];
if (!required)
continue;
return available < required;
}
return false;
}