mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 17:09:08 +00:00
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>
This commit is contained in:
801
src/strategy/rpg/NewRpgBaseAction.cpp
Normal file
801
src/strategy/rpg/NewRpgBaseAction.cpp
Normal file
@@ -0,0 +1,801 @@
|
||||
#include "NewRpgBaseAction.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "G3D/Vector2.h"
|
||||
#include "GameObject.h"
|
||||
#include "GossipDef.h"
|
||||
#include "GridTerrainData.h"
|
||||
#include "IVMapMgr.h"
|
||||
#include "NewRpgInfo.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PathGenerator.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
#include "QuestDef.h"
|
||||
#include "Random.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "Timer.h"
|
||||
#include "TravelMgr.h"
|
||||
#include "BroadcastHelper.h"
|
||||
|
||||
bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
||||
{
|
||||
if (dest == WorldPosition())
|
||||
return false;
|
||||
|
||||
if (dest != botAI->rpgInfo.moveFarPos)
|
||||
{
|
||||
// clear stuck information if it's a new dest
|
||||
botAI->rpgInfo.SetMoveFarTo(dest);
|
||||
}
|
||||
|
||||
float dis = bot->GetExactDist(dest);
|
||||
if (dis < pathFinderDis)
|
||||
{
|
||||
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
|
||||
false, true);
|
||||
}
|
||||
|
||||
// performance optimization
|
||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// stuck check
|
||||
float disToDest = bot->GetDistance(dest);
|
||||
if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
|
||||
{
|
||||
botAI->rpgInfo.nearestMoveFarDis = disToDest;
|
||||
botAI->rpgInfo.stuckTs = getMSTime();
|
||||
botAI->rpgInfo.stuckAttempts = 0;
|
||||
}
|
||||
else if (++botAI->rpgInfo.stuckAttempts >= 10 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
|
||||
{
|
||||
// Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
|
||||
botAI->rpgInfo.stuckTs = getMSTime();
|
||||
botAI->rpgInfo.stuckAttempts = 0;
|
||||
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
|
||||
std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry);
|
||||
LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(),
|
||||
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(),
|
||||
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
|
||||
return bot->TeleportTo(dest);
|
||||
}
|
||||
|
||||
float minDelta = M_PI;
|
||||
const float x = bot->GetPositionX();
|
||||
const float y = bot->GetPositionY();
|
||||
const float z = bot->GetPositionZ();
|
||||
float rx, ry, rz;
|
||||
bool found = false;
|
||||
int attempt = 3;
|
||||
while (--attempt)
|
||||
{
|
||||
float angle = bot->GetAngle(&dest);
|
||||
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
|
||||
angle += delta;
|
||||
float dis = rand_norm() * pathFinderDis;
|
||||
float dx = x + cos(angle) * dis;
|
||||
float dy = y + sin(angle) * dis;
|
||||
float dz = z + 0.5f;
|
||||
PathGenerator path(bot);
|
||||
path.CalculatePath(dx, dy, dz);
|
||||
PathType type = path.GetPathType();
|
||||
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
||||
bool canReach = !(type & (~typeOk));
|
||||
|
||||
if (canReach && fabs(delta) <= minDelta)
|
||||
{
|
||||
found = true;
|
||||
const G3D::Vector3& endPos = path.GetActualEndPosition();
|
||||
rx = endPos.x;
|
||||
ry = endPos.y;
|
||||
rz = endPos.z;
|
||||
minDelta = fabs(delta);
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::MoveNpcTo(ObjectGuid guid, float distance)
|
||||
{
|
||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (!unit)
|
||||
return false;
|
||||
float x = unit->GetPositionX();
|
||||
float y = unit->GetPositionY();
|
||||
float z = unit->GetPositionZ();
|
||||
float mapId = unit->GetMapId();
|
||||
float angle = 0.f;
|
||||
|
||||
if (!unit->isMoving())
|
||||
angle = unit->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0); // Closest 45 degrees towards the target
|
||||
else
|
||||
angle = unit->GetOrientation() +
|
||||
(M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement)
|
||||
|
||||
float rnd = rand_norm();
|
||||
x += cos(angle) * distance * rnd;
|
||||
y += sin(angle) * distance * rnd;
|
||||
if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(),
|
||||
unit->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = unit->GetPositionX();
|
||||
y = unit->GetPositionY();
|
||||
z = unit->GetPositionZ();
|
||||
}
|
||||
return MoveTo(mapId, x, y, z, false, false, false, true);
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
|
||||
{
|
||||
if (IsWaitingForLastMove(priority))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float distance = rand_norm() * moveStep;
|
||||
Map* map = bot->GetMap();
|
||||
const float x = bot->GetPositionX();
|
||||
const float y = bot->GetPositionY();
|
||||
const float z = bot->GetPositionZ();
|
||||
int attempts = 5;
|
||||
while (--attempts)
|
||||
{
|
||||
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
|
||||
float dx = x + distance * cos(angle);
|
||||
float dy = y + distance * sin(angle);
|
||||
float dz = z;
|
||||
|
||||
PathGenerator path(bot);
|
||||
path.CalculatePath(dx, dy, dz);
|
||||
PathType type = path.GetPathType();
|
||||
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
||||
bool canReach = !(type & (~typeOk));
|
||||
|
||||
if (!canReach)
|
||||
continue;
|
||||
|
||||
if (!map->CanReachPositionAndGetValidCoords(bot, dx, dy, dz))
|
||||
continue;
|
||||
|
||||
if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
|
||||
continue;
|
||||
|
||||
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true, priority);
|
||||
if (moved)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority)
|
||||
{
|
||||
AI_VALUE(LastMovement&, "last movement").Set(bot->GetMapId(),
|
||||
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), duration, priority);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @TODO: Fix redundant code
|
||||
/// Quest related method refer to TalkToQuestGiverAction.h
|
||||
bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid)
|
||||
{
|
||||
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
|
||||
if (!object || !bot->CanInteractWithQuestGiver(object))
|
||||
return false;
|
||||
|
||||
// Creature* creature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
|
||||
// if (creature)
|
||||
// {
|
||||
// WorldPacket packet(CMSG_GOSSIP_HELLO);
|
||||
// packet << guid;
|
||||
// bot->GetSession()->HandleGossipHelloOpcode(packet);
|
||||
// }
|
||||
|
||||
bot->PrepareQuestMenu(guid);
|
||||
const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
|
||||
if (menu.Empty())
|
||||
return true;
|
||||
|
||||
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
|
||||
{
|
||||
const QuestMenuItem &item = menu.GetItem(idx);
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
|
||||
if (!quest)
|
||||
continue;
|
||||
|
||||
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
|
||||
if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
|
||||
bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
|
||||
{
|
||||
AcceptQuest(quest, guid);
|
||||
if (botAI->GetMaster())
|
||||
botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest));
|
||||
BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest);
|
||||
botAI->rpgStatistic.questAccepted++;
|
||||
LOG_DEBUG("playerbots", "[New rpg] {} accept quest {}", bot->GetName(), quest->GetQuestId());
|
||||
}
|
||||
if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
|
||||
{
|
||||
TurnInQuest(quest, guid);
|
||||
if (botAI->GetMaster())
|
||||
botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest));
|
||||
BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest);
|
||||
botAI->rpgStatistic.questRewarded++;
|
||||
LOG_DEBUG("playerbots", "[New rpg] {} turned in quest {}", bot->GetName(), quest->GetQuestId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::AcceptQuest(Quest const* quest, ObjectGuid guid)
|
||||
{
|
||||
WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST);
|
||||
uint32 unk1 = 0;
|
||||
p << guid << quest->GetQuestId() << unk1;
|
||||
p.rpos(0);
|
||||
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::TurnInQuest(Quest const* quest, ObjectGuid guid)
|
||||
{
|
||||
uint32 questID = quest->GetQuestId();
|
||||
|
||||
if (bot->GetQuestRewardStatus(questID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->CanRewardQuest(quest, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->PlayDistanceSound(621);
|
||||
|
||||
WorldPacket p(CMSG_QUESTGIVER_CHOOSE_REWARD);
|
||||
p << guid << quest->GetQuestId();
|
||||
if (quest->GetRewChoiceItemsCount() <= 1)
|
||||
{
|
||||
p << 0;
|
||||
bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 bestId = BestReward(quest);
|
||||
p << bestId;
|
||||
bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 NewRpgBaseAction::BestReward(Quest const* quest)
|
||||
{
|
||||
ItemIds returnIds;
|
||||
ItemUsage bestUsage = ITEM_USAGE_NONE;
|
||||
if (quest->GetRewChoiceItemsCount() <= 1)
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
|
||||
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE)
|
||||
bestUsage = ITEM_USAGE_EQUIP;
|
||||
else if (usage == ITEM_USAGE_BAD_EQUIP && bestUsage != ITEM_USAGE_EQUIP)
|
||||
bestUsage = usage;
|
||||
else if (usage != ITEM_USAGE_NONE && bestUsage == ITEM_USAGE_NONE)
|
||||
bestUsage = usage;
|
||||
}
|
||||
StatsWeightCalculator calc(bot);
|
||||
uint32 best = 0;
|
||||
float bestScore = 0;
|
||||
for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
|
||||
if (usage == bestUsage || usage == ITEM_USAGE_REPLACE)
|
||||
{
|
||||
float score = calc.CalculateItem(quest->RewardChoiceItemId[i]);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::IsQuestWorthDoing(Quest const* quest)
|
||||
{
|
||||
bool isLowLevelQuest = bot->GetLevel() > (bot->GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
|
||||
|
||||
if (isLowLevelQuest)
|
||||
return false;
|
||||
|
||||
if (quest->IsRepeatable())
|
||||
return false;
|
||||
|
||||
if (quest->IsSeasonal())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::IsQuestCapableDoing(Quest const* quest)
|
||||
{
|
||||
bool highLevelQuest = bot->GetLevel() + 3 < bot->GetQuestLevel(quest);
|
||||
if (highLevelQuest)
|
||||
return false;
|
||||
|
||||
// Elite quest and dungeon quest etc
|
||||
if (quest->GetType() != 0)
|
||||
return false;
|
||||
|
||||
// now we only capable of doing solo quests
|
||||
if (quest->GetSuggestedPlayers() >= 2)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::OrganizeQuestLog()
|
||||
{
|
||||
int32 freeSlotNum = 0;
|
||||
|
||||
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(i);
|
||||
if (!questId)
|
||||
freeSlotNum++;
|
||||
}
|
||||
|
||||
// it's ok if we have two more free slots
|
||||
if (freeSlotNum >= 2)
|
||||
return false;
|
||||
|
||||
int32 dropped = 0;
|
||||
// remove quests that not worth doing or not capable of doing
|
||||
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(i);
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!IsQuestWorthDoing(quest) ||
|
||||
!IsQuestCapableDoing(quest) ||
|
||||
bot->GetQuestStatus(questId) == QUEST_STATUS_FAILED)
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
|
||||
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
|
||||
packet << (uint8)i;
|
||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||
if (botAI->GetMaster())
|
||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
||||
botAI->rpgStatistic.questDropped++;
|
||||
dropped++;
|
||||
}
|
||||
}
|
||||
|
||||
// drop more than 8 quests at once to avoid repeated accept and drop
|
||||
if (dropped >= 8)
|
||||
return true;
|
||||
|
||||
// remove festival/class quests and quests in different zone
|
||||
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(i);
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (quest->GetZoneOrSort() < 0 ||
|
||||
(quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
|
||||
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
|
||||
packet << (uint8)i;
|
||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||
if (botAI->GetMaster())
|
||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
||||
botAI->rpgStatistic.questDropped++;
|
||||
dropped++;
|
||||
}
|
||||
}
|
||||
|
||||
if (dropped >= 8)
|
||||
return true;
|
||||
|
||||
// clear quests log
|
||||
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(i);
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
|
||||
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
|
||||
packet << (uint8)i;
|
||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||
if (botAI->GetMaster())
|
||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
||||
botAI->rpgStatistic.questDropped++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::SearchQuestGiverAndAcceptOrReward()
|
||||
{
|
||||
OrganizeQuestLog();
|
||||
if (ObjectGuid npc = ChooseNpcOrGameObjectToInteract(true, 80.0f))
|
||||
{
|
||||
const WorldObject* object = ObjectAccessor::GetWorldObject(*bot, npc);
|
||||
if (bot->GetDistance(object) <= INTERACTION_DISTANCE)
|
||||
{
|
||||
InteractWithNpcOrGameObjectForQuest(npc);
|
||||
ForceToWait(5000);
|
||||
return true;
|
||||
}
|
||||
return MoveNpcTo(npc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly, float distanceLimit)
|
||||
{
|
||||
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets");
|
||||
GuidVector possibleGameObjects = AI_VALUE(GuidVector, "possible new rpg game objects");
|
||||
|
||||
if (possibleTargets.empty() && possibleGameObjects.empty())
|
||||
return ObjectGuid();
|
||||
|
||||
WorldObject* nearestObject = nullptr;
|
||||
for (ObjectGuid& guid: possibleTargets)
|
||||
{
|
||||
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
|
||||
|
||||
if (!object || !object->IsInWorld())
|
||||
continue;
|
||||
|
||||
if (distanceLimit && bot->GetDistance(object) > distanceLimit)
|
||||
continue;
|
||||
|
||||
if (HasQuestToAcceptOrReward(object))
|
||||
{
|
||||
if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
|
||||
nearestObject = object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (ObjectGuid& guid: possibleGameObjects)
|
||||
{
|
||||
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
|
||||
|
||||
if (!object || !object->IsInWorld())
|
||||
continue;
|
||||
|
||||
if (distanceLimit && bot->GetDistance(object) > distanceLimit)
|
||||
continue;
|
||||
|
||||
if (HasQuestToAcceptOrReward(object))
|
||||
{
|
||||
if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
|
||||
nearestObject = object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestObject)
|
||||
return nearestObject->GetGUID();
|
||||
|
||||
// No questgiver to accept or reward
|
||||
if (questgiverOnly)
|
||||
return ObjectGuid();
|
||||
|
||||
if (possibleTargets.empty())
|
||||
return ObjectGuid();
|
||||
|
||||
int idx = urand(0, possibleTargets.size() - 1);
|
||||
ObjectGuid guid = possibleTargets[idx];
|
||||
WorldObject* object = ObjectAccessor::GetCreatureOrPetOrVehicle(*bot, guid);
|
||||
if (!object)
|
||||
object = ObjectAccessor::GetGameObject(*bot, guid);
|
||||
|
||||
if (object && object->IsInWorld())
|
||||
{
|
||||
return object->GetGUID();
|
||||
}
|
||||
return ObjectGuid();
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object)
|
||||
{
|
||||
ObjectGuid guid = object->GetGUID();
|
||||
bot->PrepareQuestMenu(guid);
|
||||
const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
|
||||
if (menu.Empty())
|
||||
return false;
|
||||
|
||||
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
|
||||
{
|
||||
const QuestMenuItem &item = menu.GetItem(idx);
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
|
||||
if (!quest)
|
||||
continue;
|
||||
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
|
||||
if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
|
||||
{
|
||||
const QuestMenuItem &item = menu.GetItem(idx);
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
|
||||
if (!quest)
|
||||
continue;
|
||||
|
||||
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
|
||||
if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
|
||||
bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<float> GenerateRandomWeights(int n) {
|
||||
std::vector<float> weights(n);
|
||||
float sum = 0.0;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
weights[i] = rand_norm();
|
||||
sum += weights[i];
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
weights[i] /= sum;
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo> &poiInfo, bool toComplete)
|
||||
{
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
return false;
|
||||
|
||||
const QuestPOIVector* poiVector = sObjectMgr->GetQuestPOIVector(questId);
|
||||
if (!poiVector)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
|
||||
|
||||
if (toComplete && q_status.Status == QUEST_STATUS_COMPLETE)
|
||||
{
|
||||
for (const QuestPOI &qPoi : *poiVector)
|
||||
{
|
||||
if (qPoi.MapId != bot->GetMapId())
|
||||
continue;
|
||||
|
||||
// not the poi pos to reward quest
|
||||
if (qPoi.ObjectiveIndex != -1)
|
||||
continue;
|
||||
|
||||
if (qPoi.points.size() == 0)
|
||||
continue;
|
||||
|
||||
float dx = 0, dy = 0;
|
||||
std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
|
||||
for (size_t i = 0; i < qPoi.points.size(); i++)
|
||||
{
|
||||
const QuestPOIPoint &point = qPoi.points[i];
|
||||
dx += point.x * weights[i];
|
||||
dy += point.y * weights[i];
|
||||
}
|
||||
|
||||
if (bot->GetDistance2d(dx, dy) >= 1500.0f)
|
||||
continue;
|
||||
|
||||
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
|
||||
|
||||
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
|
||||
continue;
|
||||
|
||||
if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
|
||||
continue;
|
||||
|
||||
poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
|
||||
}
|
||||
|
||||
if (poiInfo.empty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (q_status.Status != QUEST_STATUS_INCOMPLETE)
|
||||
return false;
|
||||
|
||||
// Get incomplete quest objective index
|
||||
std::vector<int32> incompleteObjectiveIdx;
|
||||
for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
|
||||
{
|
||||
int32 npcOrGo = quest->RequiredNpcOrGo[i];
|
||||
if (!npcOrGo)
|
||||
continue;
|
||||
|
||||
if (q_status.CreatureOrGOCount[i] < quest->RequiredNpcOrGoCount[i])
|
||||
incompleteObjectiveIdx.push_back(i);
|
||||
}
|
||||
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
|
||||
{
|
||||
uint32 itemId = quest->RequiredItemId[i];
|
||||
if (!itemId)
|
||||
continue;
|
||||
|
||||
if (q_status.ItemCount[i] < quest->RequiredItemCount[i])
|
||||
incompleteObjectiveIdx.push_back(QUEST_OBJECTIVES_COUNT + i);
|
||||
}
|
||||
|
||||
// Get POIs to go
|
||||
for (const QuestPOI &qPoi : *poiVector)
|
||||
{
|
||||
if (qPoi.MapId != bot->GetMapId())
|
||||
continue;
|
||||
|
||||
bool inComplete = false;
|
||||
for (uint32 objective : incompleteObjectiveIdx)
|
||||
{
|
||||
if (qPoi.ObjectiveIndex == objective)
|
||||
{
|
||||
inComplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inComplete)
|
||||
continue;
|
||||
if (qPoi.points.size() == 0)
|
||||
continue;
|
||||
float dx = 0, dy = 0;
|
||||
std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
|
||||
for (size_t i = 0; i < qPoi.points.size(); i++)
|
||||
{
|
||||
const QuestPOIPoint &point = qPoi.points[i];
|
||||
dx += point.x * weights[i];
|
||||
dy += point.y * weights[i];
|
||||
}
|
||||
|
||||
if (bot->GetDistance2d(dx, dy) >= 1500.0f)
|
||||
continue;
|
||||
|
||||
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
|
||||
|
||||
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
|
||||
continue;
|
||||
|
||||
if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
|
||||
continue;
|
||||
|
||||
poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
|
||||
}
|
||||
|
||||
if (poiInfo.size() == 0) {
|
||||
// LOG_DEBUG("playerbots", "[New rpg] {}: No available poi can be found for quest {}", bot->GetName(), questId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
|
||||
{
|
||||
const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
|
||||
float hiRange = 500.0f;
|
||||
float loRange = 2500.0f;
|
||||
if (bot->GetLevel() < 5)
|
||||
{
|
||||
hiRange /= 10;
|
||||
loRange /= 10;
|
||||
}
|
||||
std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
|
||||
for (auto& loc : locs)
|
||||
{
|
||||
if (bot->GetMapId() != loc.GetMapId())
|
||||
continue;
|
||||
|
||||
if (bot->GetExactDist(loc) > 2500.0f)
|
||||
continue;
|
||||
|
||||
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
|
||||
bot->GetZoneId())
|
||||
continue;
|
||||
|
||||
if (bot->GetExactDist(loc) < 500.0f)
|
||||
{
|
||||
hi_prepared_locs.push_back(loc);
|
||||
}
|
||||
|
||||
if (bot->GetExactDist(loc) < 2500.0f)
|
||||
{
|
||||
lo_prepared_locs.push_back(loc);
|
||||
}
|
||||
}
|
||||
WorldPosition dest{};
|
||||
if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
|
||||
{
|
||||
uint32 idx = urand(0, hi_prepared_locs.size() - 1);
|
||||
dest = hi_prepared_locs[idx];
|
||||
}
|
||||
else if (!lo_prepared_locs.empty())
|
||||
{
|
||||
uint32 idx = urand(0, lo_prepared_locs.size() - 1);
|
||||
dest = lo_prepared_locs[idx];
|
||||
}
|
||||
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
|
||||
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
|
||||
hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
|
||||
return dest;
|
||||
}
|
||||
|
||||
WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot)
|
||||
{
|
||||
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
|
||||
? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
|
||||
: sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
|
||||
std::vector<WorldLocation> prepared_locs;
|
||||
for (auto& loc : locs)
|
||||
{
|
||||
if (bot->GetMapId() != loc.GetMapId())
|
||||
continue;
|
||||
|
||||
float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
|
||||
if (bot->GetExactDist(loc) > range)
|
||||
continue;
|
||||
|
||||
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
|
||||
bot->GetZoneId())
|
||||
continue;
|
||||
|
||||
prepared_locs.push_back(loc);
|
||||
}
|
||||
WorldPosition dest{};
|
||||
if (!prepared_locs.empty())
|
||||
{
|
||||
uint32 idx = urand(0, prepared_locs.size() - 1);
|
||||
dest = prepared_locs[idx];
|
||||
}
|
||||
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
|
||||
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
|
||||
prepared_locs.size(), locs.size());
|
||||
return dest;
|
||||
}
|
||||
Reference in New Issue
Block a user