Fix crash:

PlayerbotAI::FindOilFor was making the server randomly crashing
ChooseTravelTargetAction::getNewTarget: when active bot groupping was making the server crash as looking for unexisting params

Several bug fixes and tweak to Quest and Group

New fucntionnality:
Bots will now share quests randomly to their party
Bots will try to accomplish group member quest before moving on to new target
Bots will try to sells items only after few levels ( 5 ) when in group
When dropping a quest bots will try to select a new one they are on instead of idling for few time
Bots will no longuer try to invite themselfs to group or if group is full
Bots are now allowed to leave party by themself
Bots in groupe if not leader are forbbiden to tag in bgs
Bots in bot-groups no have a more limited range to look for grind target

Polish logs
This commit is contained in:
antony
2024-07-31 12:26:48 +02:00
parent 2e63381d48
commit 274101c000
19 changed files with 214 additions and 52 deletions

View File

@@ -4220,12 +4220,12 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
return stone; return stone;
} }
static const uint32 uPriorizedWizardOilIds[5] = static const std::vector<WizardOilDisplayId> uPriorizedWizardOilIds =
{ {
MINOR_WIZARD_OIL, LESSER_WIZARD_OIL, BRILLIANT_WIZARD_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL MINOR_WIZARD_OIL, LESSER_WIZARD_OIL, BRILLIANT_WIZARD_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL
}; };
static const uint32 uPriorizedManaOilIds[4] = static const std::vector<ManaOilDisplayId> uPriorizedManaOilIds =
{ {
MINOR_MANA_OIL, LESSER_MANA_OIL, BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, MINOR_MANA_OIL, LESSER_MANA_OIL, BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL,
}; };
@@ -4236,11 +4236,11 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
ItemTemplate const* pProto = weapon->GetTemplate(); ItemTemplate const* pProto = weapon->GetTemplate();
if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)) if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER))
{ {
for (uint8 i = 0; i < std::size(uPriorizedWizardOilIds); ++i) for (const auto& id : uPriorizedWizardOilIds)
{ {
oil = FindConsumable(uPriorizedWizardOilIds[i]); oil = FindConsumable(uPriorizedWizardOilIds[id]);
if (!oil) if (!oil)
oil = FindConsumable(uPriorizedManaOilIds[i]); oil = FindConsumable(uPriorizedManaOilIds[id]);
if (oil) if (oil)
return oil; return oil;
@@ -4248,11 +4248,11 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
} }
else if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)) else if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2))
{ {
for (uint8 i = 0; i < std::size(uPriorizedManaOilIds); ++i) for (const auto& id : uPriorizedManaOilIds)
{ {
oil = FindConsumable(uPriorizedManaOilIds[i]); oil = FindConsumable(uPriorizedManaOilIds[id]);
if (!oil) if (!oil)
oil = FindConsumable(uPriorizedWizardOilIds[i]); oil = FindConsumable(uPriorizedWizardOilIds[id]);
if (oil) if (oil)
return oil; return oil;
@@ -4741,3 +4741,11 @@ uint8 PlayerbotAI::FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool sw
// no free position // no free position
return NULL_SLOT; return NULL_SLOT;
} }
bool PlayerbotAI::IsSafe(Player* player)
{
return player && player->GetMapId() == bot->GetMapId() && player->GetInstanceId() == bot->GetInstanceId() && !player->IsBeingTeleported();
}
bool PlayerbotAI::IsSafe(WorldObject* obj)
{
return obj && obj->GetMapId() == bot->GetMapId() && obj->GetInstanceId() == bot->GetInstanceId() && (!obj->IsPlayer() || !((Player*)obj)->IsBeingTeleported());
}

View File

@@ -433,6 +433,11 @@ class PlayerbotAI : public PlayerbotAIBase
bool AllowActive(ActivityType activityType); bool AllowActive(ActivityType activityType);
bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false);
//Check if player is safe to use.
bool IsSafe(Player* player);
bool IsSafe(WorldObject* obj);
bool HasCheat(BotCheatMask mask) { return ((uint32)mask & (uint32)cheatMask) != 0 || ((uint32)mask & (uint32)sPlayerbotAIConfig->botCheatMask) != 0; } bool HasCheat(BotCheatMask mask) { return ((uint32)mask & (uint32)cheatMask) != 0 || ((uint32)mask & (uint32)sPlayerbotAIConfig->botCheatMask) != 0; }
BotCheatMask GetCheat() { return cheatMask; } BotCheatMask GetCheat() { return cheatMask; }
void SetCheat(BotCheatMask mask) { cheatMask = mask; } void SetCheat(BotCheatMask mask) { cheatMask = mask; }

View File

@@ -1096,27 +1096,10 @@ bool QuestRelationTravelDestination::isActive(Player* bot)
if (AI_VALUE(uint8, "free quest log slots") < 5) if (AI_VALUE(uint8, "free quest log slots") < 5)
return false; return false;
if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest npc::" + std::to_string(entry))) //Noone has yellow exclamation mark. //None has yellow exclamation mark.
if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest npc::" + std::to_string(entry)))
if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest low level npc::" + std::to_string(entry) + "need quest objective::" + std::to_string(questId))) //Noone can do this quest for a usefull reward. if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest low level npc::" + std::to_string(entry) + "need quest objective::" + std::to_string(questId))) //Noone can do this quest for a usefull reward.
return false; return false;
// higher chance bot will do rewarding quest (better gear etc)
if (dialogStatus != DIALOG_STATUS_AVAILABLE)
{
if (dialogStatus != DIALOG_STATUS_LOW_LEVEL_AVAILABLE)
{
bool hasGoodReward = false;
for (uint8 i = 0; i < questTemplate->GetRewChoiceItemsCount(); ++i)
{
ItemUsage usage = AI_VALUE2_LAZY(ItemUsage, "item usage", questTemplate->RewardChoiceItemId[i]);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE)
{
hasGoodReward = true;
break;
}
}
}
}
} }
else else
{ {
@@ -1275,6 +1258,7 @@ std::string const RpgTravelDestination::getTitle()
{ {
std::ostringstream out; std::ostringstream out;
if(entry > 0)
out << "rpg npc "; out << "rpg npc ";
out << " " << ChatHelper::FormatWorldEntry(entry); out << " " << ChatHelper::FormatWorldEntry(entry);

View File

@@ -15,7 +15,7 @@ bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
LOG_INFO("playerbots", "Quest [ {} ] accepted", quest->GetTitle()); LOG_INFO("playerbots", "{} => Quest [ {} ] accepted", bot->GetName(), quest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] accepted", LANG_UNIVERSAL); bot->Say("Quest [ " + text_quest + " ] accepted", LANG_UNIVERSAL);
} }

View File

@@ -8,6 +8,7 @@
#include "AddLootAction.h" #include "AddLootAction.h"
#include "AttackAction.h" #include "AttackAction.h"
#include "AutoLearnSpellAction.h" #include "AutoLearnSpellAction.h"
#include "ShareQuestAction.h"
#include "BattleGroundTactics.h" #include "BattleGroundTactics.h"
#include "BattleGroundJoinAction.h" #include "BattleGroundJoinAction.h"
#include "BuyAction.h" #include "BuyAction.h"
@@ -152,6 +153,7 @@ class ActionContext : public NamedObjectContext<Action>
creators["war stomp"] = &ActionContext::war_stomp; creators["war stomp"] = &ActionContext::war_stomp;
creators["auto talents"] = &ActionContext::auto_talents; creators["auto talents"] = &ActionContext::auto_talents;
creators["auto learn spell"] = &ActionContext::auto_learn_spell; creators["auto learn spell"] = &ActionContext::auto_learn_spell;
creators["auto share quest"] = &ActionContext::auto_share_quest;
creators["auto teleport for level"] = &ActionContext::auto_teleport_for_level; creators["auto teleport for level"] = &ActionContext::auto_teleport_for_level;
creators["auto upgrade equip"] = &ActionContext::auto_upgrade_equip; creators["auto upgrade equip"] = &ActionContext::auto_upgrade_equip;
creators["xp gain"] = &ActionContext::xp_gain; creators["xp gain"] = &ActionContext::xp_gain;
@@ -321,6 +323,7 @@ class ActionContext : public NamedObjectContext<Action>
static Action* war_stomp(PlayerbotAI* botAI) { return new CastWarStompAction(botAI); } static Action* war_stomp(PlayerbotAI* botAI) { return new CastWarStompAction(botAI); }
static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); } static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); }
static Action* auto_learn_spell(PlayerbotAI* botAI) { return new AutoLearnSpellAction(botAI); } static Action* auto_learn_spell(PlayerbotAI* botAI) { return new AutoLearnSpellAction(botAI); }
static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); }
static Action* auto_teleport_for_level(PlayerbotAI* botAI) { return new AutoTeleportForLevelAction(botAI); } static Action* auto_teleport_for_level(PlayerbotAI* botAI) { return new AutoTeleportForLevelAction(botAI); }
static Action* auto_upgrade_equip(PlayerbotAI* botAI) { return new AutoUpgradeEquipAction(botAI); } static Action* auto_upgrade_equip(PlayerbotAI* botAI) { return new AutoUpgradeEquipAction(botAI); }
static Action* xp_gain(PlayerbotAI* botAI) { return new XpGainAction(botAI); } static Action* xp_gain(PlayerbotAI* botAI) { return new XpGainAction(botAI); }

View File

@@ -171,7 +171,7 @@ class ChatActionContext : public NamedObjectContext<Action>
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut; creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["tell expected dps"] = &ChatActionContext::tell_expected_dps; creators["tell expected dps"] = &ChatActionContext::tell_expected_dps;
creators["join"] = &ChatActionContext::join;
} }
private: private:
@@ -268,6 +268,7 @@ class ChatActionContext : public NamedObjectContext<Action>
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); } static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); } static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); } static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
}; };
#endif #endif

View File

@@ -129,11 +129,18 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
bool ChooseRpgTargetAction::Execute(Event event) bool ChooseRpgTargetAction::Execute(Event event)
{ {
TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target");
Player* master = botAI->GetMaster();
uint32 num = 0; GuidPosition masterRpgTarget;
if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported())
{
Player* player = botAI->GetMaster();
GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target");
}
else
master = nullptr;
std::unordered_map<ObjectGuid, uint32> targets; std::unordered_map<ObjectGuid, uint32> targets;
uint32 num = 0;
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets"); GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los"); GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los");
GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players"); GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players");
@@ -216,10 +223,15 @@ bool ChooseRpgTargetAction::Execute(Event event)
for (auto it = begin(targets); it != end(targets);) for (auto it = begin(targets); it != end(targets);)
{ {
if (it->second == 0 || (hasGoodRelevance && it->second <= 1.0)) //Remove empty targets.
{ if (it->second == 0)
it = targets.erase(it);
//Remove useless targets if there's any good ones
else if (hasGoodRelevance && it->second <= 1.0)
it = targets.erase(it);
//Remove useless targets if it's not masters target.
else if (!hasGoodRelevance && master && (!masterRpgTarget || it->first != masterRpgTarget))
it = targets.erase(it); it = targets.erase(it);
}
else else
++it; ++it;
} }

View File

@@ -44,11 +44,11 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
foundTarget = SetGroupTarget(newTarget); //Join groups members foundTarget = SetGroupTarget(newTarget); //Join groups members
//Empty bags/repair //Empty bags/repair
if (!foundTarget && urand(1, 100) > 10) //90% chance 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") || 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") || AI_VALUE2(bool, "group or", "should repair,can repair,following party,near leader")
(AI_VALUE2(bool, "group or", "should sell,can ah sell,following party,near leader") && bot->GetLevel() > 5)) )
{ {
foundTarget = SetRpgTarget(newTarget); //Go to town to sell items or repair foundTarget = SetRpgTarget(newTarget); //Go to town to sell items or repair
} }
@@ -132,10 +132,12 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
//Dungeon in group. //Dungeon in group.
if (!foundTarget && urand(1, 100) > 50) //50% chance if (!foundTarget && urand(1, 100) > 50) //50% chance
{
if (AI_VALUE(bool, "can fight boss")) if (AI_VALUE(bool, "can fight boss"))
{ {
foundTarget = SetBossTarget( newTarget); //Go fight a (dungeon boss) foundTarget = SetBossTarget( newTarget); //Go fight a (dungeon boss)
} }
}
//Do quests (start, do, end) //Do quests (start, do, end)
if (!foundTarget && urand(1, 100) > 5) //95% chance if (!foundTarget && urand(1, 100) > 5) //95% chance

View File

@@ -48,7 +48,7 @@ bool DropQuestAction::Execute(Event event)
{ {
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry); const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
const std::string text_quest = ChatHelper::FormatQuest(pQuest); const std::string text_quest = ChatHelper::FormatQuest(pQuest);
LOG_INFO("playerbots", "Quest [ {} ] removed", pQuest->GetTitle()); LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL); bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
} }
@@ -103,7 +103,19 @@ bool CleanQuestLogAction::Execute(Event event)
void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete) void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete)
{ {
std::vector<uint8> slots;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
slots.push_back(slot);
if (wantNum < 100)
{
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(slots.begin(), slots.end(), g);
}
for (uint8 slot : slots)
{ {
uint32 questId = bot->GetQuestSlotQuestId(slot); uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId) if (!questId)
@@ -153,6 +165,12 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
numQuest--; numQuest--;
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
const std::string text_quest = ChatHelper::FormatQuest(quest);
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
}
botAI->TellMaster("Quest removed" + chat->FormatQuest(quest)); botAI->TellMaster("Quest removed" + chat->FormatQuest(quest));
} }
} }

View File

@@ -22,9 +22,16 @@ bool InviteToGroupAction::Invite(Player* player)
if (!player || !player->IsInWorld()) if (!player || !player->IsInWorld())
return false; return false;
if (bot == player)
return false;
if (!GET_PLAYERBOT_AI(player) && !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, true, player)) if (!GET_PLAYERBOT_AI(player) && !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, true, player))
return false; return false;
if (Group* group = player->GetGroup())
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
group->ConvertToRaid();
WorldPacket p; WorldPacket p;
uint32 roles_mask = 0; uint32 roles_mask = 0;
p << player->GetName(); p << player->GetName();
@@ -46,6 +53,9 @@ bool InviteNearbyToGroupAction::Execute(Event event)
if (player->GetGroup()) if (player->GetGroup())
continue; continue;
if (player == bot)
continue;
if (botAI) if (botAI)
{ {
if (botAI->GetGrouperType() == GrouperType::SOLO && !botAI->HasRealPlayerMaster()) // Do not invite solo players. if (botAI->GetGrouperType() == GrouperType::SOLO && !botAI->HasRealPlayerMaster()) // Do not invite solo players.
@@ -165,3 +175,18 @@ bool InviteGuildToGroupAction::isUseful()
{ {
return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful(); return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful();
}; };
bool JoinGroupAction::Execute(Event event)
{
Player* master = event.getOwner();
Group* group = master->GetGroup();
if (group && (group->IsFull() || bot->GetGroup() == group))
return false;
if (bot->GetGroup())
if (!botAI->DoSpecificAction("leave", event, true))
return false;
return Invite(bot);
}

View File

@@ -20,6 +20,14 @@ class InviteToGroupAction : public Action
virtual bool Invite(Player* player); virtual bool Invite(Player* player);
}; };
class JoinGroupAction : public InviteToGroupAction
{
public:
JoinGroupAction(PlayerbotAI* ai, std::string name = "join") : InviteToGroupAction(ai, name) {}
bool Execute(Event event) override;
bool isUseful() override { return !bot->IsBeingTeleported(); }
};
class InviteNearbyToGroupAction : public InviteToGroupAction class InviteNearbyToGroupAction : public InviteToGroupAction
{ {
public: public:

View File

@@ -97,14 +97,12 @@ bool LeaveGroupAction::Leave(Player* player)
bool LeaveFarAwayAction::Execute(Event event) bool LeaveFarAwayAction::Execute(Event event)
{ {
return Leave(nullptr); // allow bot to leave party when they want
return Leave(botAI->GetGroupMaster());
} }
bool LeaveFarAwayAction::isUseful() bool LeaveFarAwayAction::isUseful()
{ {
if (!sPlayerbotAIConfig->randomBotGroupNearby)
return false;
if (bot->InBattleground()) if (bot->InBattleground())
return false; return false;

View File

@@ -135,7 +135,7 @@ bool QuestAction::CompleteQuest(Player* player, uint32 entry)
const std::string text_quest = ChatHelper::FormatQuest(pQuest); const std::string text_quest = ChatHelper::FormatQuest(pQuest);
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
LOG_INFO("playerbots", "Quest [ {} ] completed", pQuest->GetTitle()); LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), pQuest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL); bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL);
} }
botAI->TellMasterNoFacing("Quest completed " + text_quest); botAI->TellMasterNoFacing("Quest completed " + text_quest);
@@ -244,6 +244,9 @@ bool QuestUpdateCompleteAction::Execute(Event event)
WorldPacket p(event.getPacket()); WorldPacket p(event.getPacket());
p.rpos(0); p.rpos(0);
if (p.empty())
return false;
uint32 entry, questId, available, required; uint32 entry, questId, available, required;
ObjectGuid guid; ObjectGuid guid;
p >> questId >> entry >> available >> required >> guid; p >> questId >> entry >> available >> required >> guid;
@@ -254,7 +257,7 @@ bool QuestUpdateCompleteAction::Execute(Event event)
const std::string text_quest = ChatHelper::FormatQuest(qInfo); const std::string text_quest = ChatHelper::FormatQuest(qInfo);
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
LOG_INFO("playerbots", "Quest [ {} ] completed", qInfo->GetTitle()); LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL); bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL);
} }
botAI->TellMasterNoFacing("Quest completed " + text_quest); botAI->TellMasterNoFacing("Quest completed " + text_quest);

View File

@@ -37,3 +37,74 @@ bool ShareQuestAction::Execute(Event event)
return false; return false;
} }
bool AutoShareQuestAction::Execute(Event event)
{
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
bool shared = false;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 logQuest = bot->GetQuestSlotQuestId(slot);
Quest const* quest = sObjectMgr->GetQuestTemplate(logQuest);
if (!quest)
continue;
bool partyNeedsQuest = false;
for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* player = itr->GetSource();
if (!player || player == bot || !player->IsInWorld() || !botAI->IsSafe(player)) // skip self
continue;
if (bot->GetDistance(player) > 10)
continue;
if (!player->SatisfyQuestStatus(quest, false))
continue;
if (player->GetQuestStatus(logQuest) == QUEST_STATUS_COMPLETE)
continue;
if (!player->CanTakeQuest(quest, false))
continue;
if (!player->SatisfyQuestLog(false))
continue;
if (player->GetDivider())
continue;
if (auto ai = GET_PLAYERBOT_AI(player))
{
if (PAI_VALUE(uint8, "free quest log slots") < 15 || !urand(0,5))
{
WorldPacket packet(CMSG_PUSHQUESTTOPARTY, 20);
packet << logQuest;
ai->HandleMasterIncomingPacket(packet);
}
}
else
partyNeedsQuest = true;
}
if (!partyNeedsQuest)
continue;
WorldPacket p;
p << logQuest;
bot->GetSession()->HandlePushQuestToParty(p);
botAI->TellMaster("Quest shared");
shared = true;
}
return shared;
}
bool AutoShareQuestAction::isUseful()
{
return bot->GetGroup() && !botAI->HasActivePlayerMaster();
}

View File

@@ -12,9 +12,18 @@ class PlayerbotAI;
class ShareQuestAction : public Action class ShareQuestAction : public Action
{ {
public: public:
ShareQuestAction(PlayerbotAI* botAI) : Action(botAI, "share quest") { } ShareQuestAction(PlayerbotAI* botAI, std::string name = "share quest") : Action(botAI, name) { }
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class AutoShareQuestAction : public ShareQuestAction
{
public:
AutoShareQuestAction(PlayerbotAI* ai) : ShareQuestAction(botAI, "auto share quest") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif #endif

View File

@@ -87,7 +87,7 @@ bool TalkToQuestGiverAction::TurnInQuest(Quest const* quest, Object* questGiver,
{ {
const Quest* pQuest = sObjectMgr->GetQuestTemplate(questID); const Quest* pQuest = sObjectMgr->GetQuestTemplate(questID);
const std::string text_quest = ChatHelper::FormatQuest(pQuest); const std::string text_quest = ChatHelper::FormatQuest(pQuest);
LOG_INFO("playerbots", "Quest [ {} ] completed", pQuest->GetTitle()); LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), pQuest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL); bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL);
} }

View File

@@ -20,5 +20,6 @@ void MaintenanceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("move stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr))); triggers.push_back(new TriggerNode("move stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr)));
// triggers.push_back(new TriggerNode("move long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f), new NextAction("repop", 0.8f), nullptr))); // triggers.push_back(new TriggerNode("move long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f), new NextAction("repop", 0.8f), nullptr)));
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("use random quest item", 0.9f), nullptr))); triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("use random quest item", 0.9f), nullptr)));
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("auto share quest", 0.9f), NULL)));
} }

View File

@@ -306,6 +306,10 @@ bool RpgQueueBGTrigger::IsActive()
if (!guidP.IsCreature()) if (!guidP.IsCreature())
return false; return false;
// if bot is not leader disallow tag bg
if (bot->GetGroup() && !bot->GetGroup()->IsLeader(bot->GetGUID()))
return false;
if (AI_VALUE(BattlegroundTypeId, "rpg bg type") == BATTLEGROUND_TYPE_NONE) if (AI_VALUE(BattlegroundTypeId, "rpg bg type") == BATTLEGROUND_TYPE_NONE)
return false; return false;

View File

@@ -6,6 +6,7 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "ReputationMgr.h" #include "ReputationMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "ServerFacade.h"
Unit* GrindTargetValue::Calculate() Unit* GrindTargetValue::Calculate()
{ {
@@ -80,6 +81,15 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
//if (!bot->InBattleground() && master && master->GetDistance(unit) >= sPlayerbotAIConfig->grindDistance && !sRandomPlayerbotMgr->IsRandomBot(bot)) //if (!bot->InBattleground() && master && master->GetDistance(unit) >= sPlayerbotAIConfig->grindDistance && !sRandomPlayerbotMgr->IsRandomBot(bot))
//continue; //continue;
// Bots in bot-groups no have a more limited range to look for grind target
if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT)
&& sServerFacade->GetDistance2d(master, unit) > sPlayerbotAIConfig->lootDistance)
{
if (botAI->HasStrategy("debug grind", BotState::BOT_STATE_NON_COMBAT))
botAI->TellMaster(chat->FormatWorldobject(unit) + " ignored (far from master).");
continue;
}
if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer()) if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer())
continue; continue;