mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-24 14:06:22 +00:00
Compare commits
16 Commits
bb569b4d39
...
hermensbas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c9451b73f | ||
|
|
88016789ba | ||
|
|
6be860c967 | ||
|
|
9971622093 | ||
|
|
895df9b197 | ||
|
|
467b63b840 | ||
|
|
66f5f597bb | ||
|
|
cafbd4681e | ||
|
|
f5c84ee7ff | ||
|
|
b6f882886d | ||
|
|
c1222da8b0 | ||
|
|
00cb177c86 | ||
|
|
5f697e806e | ||
|
|
934e73ae20 | ||
|
|
f4b4d8967f | ||
|
|
910b8a9c53 |
@@ -630,7 +630,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
|
||||
AiPlayerbot.DisableDeathKnightLogin = 0
|
||||
|
||||
# Enable simulated expansion limitation for talents and glyphs
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LimitTalentsExpansion = 0
|
||||
@@ -1185,7 +1185,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
|
||||
@@ -242,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
||||
nextAICheckDelay = 0;
|
||||
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
|
||||
bot->IsDuringRemoveFromWorld())
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// Handle cheat options (set bot health and power if cheats are enabled)
|
||||
@@ -713,39 +713,59 @@ void PlayerbotAI::HandleTeleportAck()
|
||||
if (IsRealPlayer())
|
||||
return;
|
||||
|
||||
// Clearing motion generators and stopping movement which prevents
|
||||
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.)
|
||||
bot->GetMotionMaster()->Clear(true);
|
||||
bot->StopMoving();
|
||||
|
||||
// Near teleport (within map/instance)
|
||||
if (bot->IsBeingTeleportedNear())
|
||||
{
|
||||
// Temporary fix for instance can not enter
|
||||
if (!bot->IsInWorld())
|
||||
{
|
||||
bot->GetMap()->AddPlayerToMap(bot);
|
||||
}
|
||||
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
|
||||
{
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << (uint32)0; // supposed to be flags? not used currently
|
||||
p << (uint32)0; // time - not currently used
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
};
|
||||
// Previous versions manually added the bot to the map if it was not in the world.
|
||||
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
|
||||
// and clears IsBeingTeleportedNear().
|
||||
|
||||
Player* plMover = bot->m_mover->ToPlayer();
|
||||
if (!plMover)
|
||||
return;
|
||||
|
||||
// Send the near teleport ACK packet
|
||||
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
|
||||
p << plMover->GetPackGUID();
|
||||
p << uint32(0);
|
||||
p << uint32(0);
|
||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||
|
||||
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
|
||||
SetNextCheckDelay(urand(1000, 2000));
|
||||
}
|
||||
|
||||
// Far teleport (worldport / different map)
|
||||
if (bot->IsBeingTeleportedFar())
|
||||
{
|
||||
while (bot->IsBeingTeleportedFar())
|
||||
// Handle far teleport ACK:
|
||||
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
|
||||
// Ensure bot now has a valid map. If this fails, there is a core/session bug?
|
||||
if (!bot->GetMap())
|
||||
{
|
||||
bot->GetSession()->HandleMoveWorldportAck();
|
||||
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
|
||||
}
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
|
||||
// Instance strategies after teleport
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
|
||||
// healer DPS strategies if restrictions are enabled
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
|
||||
// Reset AI state to to before teleport conditions
|
||||
Reset(true);
|
||||
|
||||
// Slightly longer delay to simulate far teleport latency (used cmangos delays)
|
||||
SetNextCheckDelay(urand(2000, 5000));
|
||||
}
|
||||
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
||||
@@ -988,10 +1008,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
{
|
||||
if (packet.empty())
|
||||
return;
|
||||
|
||||
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.GetOpcode())
|
||||
{
|
||||
case SMSG_SPELL_FAILURE:
|
||||
@@ -1159,7 +1179,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
|
||||
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
|
||||
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
|
||||
{
|
||||
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
|
||||
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
|
||||
// when rooted during lost client control (charm + root effects)
|
||||
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
|
||||
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
|
||||
if (forceRoot)
|
||||
{
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
|
||||
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
bot->StopMoving();
|
||||
}
|
||||
else
|
||||
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
|
||||
|
||||
return;
|
||||
}
|
||||
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
|
||||
{
|
||||
WorldPacket p(packet);
|
||||
p.rpos(0);
|
||||
@@ -1331,10 +1370,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
|
||||
{
|
||||
bot->StopMoving();
|
||||
bot->GetMotionMaster()->Clear();
|
||||
bot->GetMotionMaster()->MoveIdle();
|
||||
|
||||
// Death Count to prevent skeleton piles
|
||||
// Player* master = GetMaster(); // warning here - whipowill
|
||||
if (!HasActivePlayerMaster() && !bot->InBattleground())
|
||||
@@ -1355,6 +1390,8 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
// Change engine if just ressed
|
||||
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
|
||||
{
|
||||
bot->SendMovementFlagUpdate();
|
||||
|
||||
ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
return;
|
||||
}
|
||||
@@ -1384,9 +1421,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
else if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
PlayerbotAI* masterBotAI = nullptr;
|
||||
|
||||
if (master && master->IsInWorld())
|
||||
{
|
||||
float distance = sServerFacade->GetDistance2d(bot, master);
|
||||
@@ -4106,8 +4140,7 @@ Player* PlayerbotAI::FindNewMaster()
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member || member == bot || !member->IsInWorld() ||
|
||||
!member->IsInSameRaidWith(bot))
|
||||
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
|
||||
continue;
|
||||
|
||||
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
|
||||
@@ -4342,6 +4375,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
|
||||
|
||||
bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
{
|
||||
// Early return if bot is in invalid state
|
||||
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
|
||||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
// when botActiveAlone is 100% and smartScale disabled
|
||||
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
|
||||
{
|
||||
@@ -4432,10 +4470,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
|
||||
{
|
||||
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member == bot)
|
||||
{
|
||||
@@ -4486,23 +4522,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
// HasFriend
|
||||
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
|
||||
{
|
||||
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
|
||||
// shouldnt be needed analyse in future
|
||||
if (!bot->GetGUID())
|
||||
return false;
|
||||
|
||||
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
|
||||
{
|
||||
if (!player || !player->IsInWorld())
|
||||
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
|
||||
player->GetSession()->isLogingOut())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
|
||||
if (!playerAI || !playerAI->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
// if a real player has the bot as a friend
|
||||
PlayerSocial* social = player->GetSocial();
|
||||
if (!social)
|
||||
continue;
|
||||
|
||||
if (social->HasFriend(bot->GetGUID()))
|
||||
if (social && social->HasFriend(bot->GetGUID()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4516,7 +4552,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
}
|
||||
}
|
||||
|
||||
// Bots don't need to move using PathGenerator.
|
||||
// Bots don't need react to PathGenerator activities
|
||||
if (activityType == DETAILED_MOVE_ACTIVITY)
|
||||
{
|
||||
return false;
|
||||
@@ -4552,15 +4588,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
|
||||
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
{
|
||||
if (!allowActiveCheckTimer[activityType])
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
const int activityIndex = static_cast<int>(activityType);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
|
||||
return allowActive[activityType];
|
||||
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
|
||||
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
|
||||
{
|
||||
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowActiveCheckTimer[activityIndex])
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
|
||||
return allowActive[activityIndex];
|
||||
|
||||
const bool allowed = AllowActive(activityType);
|
||||
allowActive[activityIndex] = allowed;
|
||||
allowActiveCheckTimer[activityIndex] = time(nullptr);
|
||||
|
||||
bool allowed = AllowActive(activityType);
|
||||
allowActive[activityType] = allowed;
|
||||
allowActiveCheckTimer[activityType] = time(nullptr);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
@@ -5344,15 +5390,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
@@ -5388,7 +5432,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5397,12 +5440,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
int botClass = bot->getClass();
|
||||
@@ -5418,22 +5461,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -165,7 +165,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <openssl/sha.h>
|
||||
#include <unordered_set>
|
||||
#include <openssl/sha.h>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ChannelMgr.h"
|
||||
#include "CharacterCache.h"
|
||||
@@ -34,12 +35,9 @@
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ChannelMgr.h"
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -68,6 +66,7 @@ private:
|
||||
};
|
||||
|
||||
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
|
||||
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
|
||||
|
||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
|
||||
class PlayerbotLoginQueryHolder : public LoginQueryHolder
|
||||
@@ -76,13 +75,12 @@ private:
|
||||
uint32 masterAccountId;
|
||||
PlayerbotHolder* playerbotHolder;
|
||||
public:
|
||||
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
|
||||
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
|
||||
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 GetMasterAccountId() const { return masterAccountId; }
|
||||
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
|
||||
};
|
||||
|
||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||
@@ -143,7 +141,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
|
||||
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
return;
|
||||
@@ -153,8 +151,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
|
||||
// Always login in with world session to avoid race condition
|
||||
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
|
||||
.AfterComplete([this](SQLQueryHolderBase const& holder)
|
||||
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
|
||||
.AfterComplete(
|
||||
[](SQLQueryHolderBase const& queryHolder)
|
||||
{
|
||||
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
|
||||
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
|
||||
if (masterAccountId)
|
||||
{
|
||||
// verify and find current world session of master
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (mgr)
|
||||
mgr->HandlePlayerBotLoginCallback(holder);
|
||||
else
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
});
|
||||
}
|
||||
|
||||
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
|
||||
@@ -169,8 +186,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
WorldSession* botSession =
|
||||
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
|
||||
@@ -181,26 +199,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
|
||||
botSession->LogoutPlayer(true);
|
||||
delete botSession;
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 masterAccount = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
|
||||
uint32 masterAccountId = holder.GetMasterAccountId();
|
||||
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
|
||||
|
||||
// Check if masterSession->GetPlayer() is valid
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterSession && !masterPlayer)
|
||||
{
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
|
||||
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
|
||||
masterAccountId);
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
|
||||
botLoading.erase(holder.GetGuid());
|
||||
PlayerbotHolder::botLoading.erase(holder.GetGuid());
|
||||
}
|
||||
|
||||
void PlayerbotHolder::UpdateSessions()
|
||||
|
||||
@@ -60,7 +60,7 @@ protected:
|
||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||
|
||||
PlayerBotMap playerBots;
|
||||
std::unordered_set<ObjectGuid> botLoading;
|
||||
static std::unordered_set<ObjectGuid> botLoading;
|
||||
};
|
||||
|
||||
class PlayerbotMgr : public PlayerbotHolder
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotOperation.h"
|
||||
#include "Player.h"
|
||||
@@ -16,6 +17,8 @@
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
// Group invite operation
|
||||
class GroupInviteOperation : public PlayerbotOperation
|
||||
@@ -468,18 +471,31 @@ private:
|
||||
class OnBotLoginOperation : public PlayerbotOperation
|
||||
{
|
||||
public:
|
||||
OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
|
||||
: m_botGuid(botGuid), m_holder(holder)
|
||||
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute() override
|
||||
{
|
||||
// find and verify bot still exists
|
||||
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||
if (!bot || !m_holder)
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
m_holder->OnBotLogin(bot);
|
||||
PlayerbotHolder* holder = sRandomPlayerbotMgr;
|
||||
if (m_masterAccountId)
|
||||
{
|
||||
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
|
||||
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||
if (masterPlayer)
|
||||
holder = GET_PLAYERBOT_MGR(masterPlayer);
|
||||
}
|
||||
|
||||
if (!holder)
|
||||
return false;
|
||||
|
||||
holder->OnBotLogin(bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -487,14 +503,11 @@ public:
|
||||
uint32 GetPriority() const override { return 100; }
|
||||
std::string GetName() const override { return "OnBotLogin"; }
|
||||
|
||||
bool IsValid() const override
|
||||
{
|
||||
return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr;
|
||||
}
|
||||
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
|
||||
|
||||
private:
|
||||
ObjectGuid m_botGuid;
|
||||
PlayerbotHolder* m_holder;
|
||||
uint32 m_masterAccountId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
|
||||
|
||||
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
|
||||
{
|
||||
// Basic pointer validity checks
|
||||
if (!bot || !from || !from->GetSession())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// GMs always have full access
|
||||
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NONE;
|
||||
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
{
|
||||
// (duplicate check in case of faction change)
|
||||
if (botAI->IsOpposing(from))
|
||||
{
|
||||
if (reason)
|
||||
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_DENY_ALL;
|
||||
}
|
||||
|
||||
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
|
||||
// {
|
||||
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
// {
|
||||
// if (reason)
|
||||
// *reason = PLAYERBOT_DENY_LFG;
|
||||
Group* fromGroup = from->GetGroup();
|
||||
Group* botGroup = bot->GetGroup();
|
||||
|
||||
// return PLAYERBOT_SECURITY_TALK;
|
||||
// }
|
||||
// }
|
||||
|
||||
Group* group = from->GetGroup();
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
|
||||
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
|
||||
{
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
}
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_YOURS;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
|
||||
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
|
||||
if (levelDiff > 5)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_LOW_LEVEL;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
if (sPlayerbotAIConfig->gearscorecheck)
|
||||
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
|
||||
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
|
||||
|
||||
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
|
||||
{
|
||||
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
|
||||
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
|
||||
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
|
||||
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
|
||||
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
|
||||
|
||||
if (diffPct >= reqPct)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_GEARSCORE;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}
|
||||
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
}
|
||||
}
|
||||
|
||||
/*if (bot->isDead())
|
||||
// If the bot is not in the group, we offer an invite
|
||||
botGroup = bot->GetGroup();
|
||||
if (!botGroup)
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_DEAD;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}*/
|
||||
|
||||
group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
|
||||
{
|
||||
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FAR;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->IsFull())
|
||||
if (!ignoreGroup && botGroup->IsFull())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_FULL_GROUP;
|
||||
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
|
||||
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
|
||||
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_NOT_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_TALK;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// The bot is the group leader, you can invite the initiator
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
*reason = PLAYERBOT_DENY_IS_LEADER;
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
// Non-random bots: only their master has full access
|
||||
if (botAI->GetMaster() == from)
|
||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||
|
||||
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
|
||||
{
|
||||
// If something is wrong with the pointers, we silently refuse
|
||||
if (!bot || !from || !from->GetSession())
|
||||
return false;
|
||||
|
||||
DenyReason reason = PLAYERBOT_DENY_NONE;
|
||||
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
|
||||
|
||||
if (realLevel >= level || from == bot)
|
||||
return true;
|
||||
|
||||
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
return false;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
||||
if (!botAI)
|
||||
return false;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master && botAI->IsOpposing(master))
|
||||
if (WorldSession* session = master->GetSession())
|
||||
if (session->GetSecurity() < SEC_GAMEMASTER)
|
||||
return false;
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
switch (realLevel)
|
||||
{
|
||||
case PLAYERBOT_SECURITY_DENY_ALL:
|
||||
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I'll do it later";
|
||||
break;
|
||||
case PLAYERBOT_DENY_LOW_LEVEL:
|
||||
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
|
||||
<< (uint32)bot->GetLevel();
|
||||
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
|
||||
<< uint32(bot->GetLevel());
|
||||
break;
|
||||
case PLAYERBOT_DENY_GEARSCORE:
|
||||
{
|
||||
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
|
||||
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
|
||||
int botGS = int(botAI->GetEquipGearScore(bot));
|
||||
int fromGS = int(botAI->GetEquipGearScore(from));
|
||||
int diff = (100 * (botGS - fromGS) / botGS);
|
||||
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
|
||||
|
||||
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
|
||||
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_YOURS:
|
||||
out << "I have a master already";
|
||||
break;
|
||||
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
case PLAYERBOT_DENY_FAR:
|
||||
{
|
||||
out << "You must be closer to invite me to your group. I am in ";
|
||||
|
||||
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
|
||||
{
|
||||
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_FULL_GROUP:
|
||||
out << "I am in a full group. Will do it later";
|
||||
break;
|
||||
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
out << "I am currently leading a group. I can invite you if you want.";
|
||||
break;
|
||||
case PLAYERBOT_DENY_NOT_LEADER:
|
||||
if (botAI->GetGroupLeader())
|
||||
{
|
||||
out << "I am in a group with " << botAI->GetGroupLeader()->GetName()
|
||||
<< ". You can ask him for invite.";
|
||||
}
|
||||
if (Player* leader = botAI->GetGroupLeader())
|
||||
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
|
||||
else
|
||||
{
|
||||
out << "I am in a group with someone else. You can ask him for invite.";
|
||||
}
|
||||
break;
|
||||
case PLAYERBOT_DENY_BG:
|
||||
out << "I am in a queue for BG. Will do it later";
|
||||
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
||||
std::string const text = out.str();
|
||||
ObjectGuid guid = from->GetGUID();
|
||||
time_t lastSaid = whispers[guid][text];
|
||||
|
||||
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
|
||||
{
|
||||
whispers[guid][text] = time(nullptr);
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
|
||||
// Additional protection against crashes during logout
|
||||
if (bot->IsInWorld() && from->IsInWorld())
|
||||
bot->Whisper(text, LANG_UNIVERSAL, from);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Metric.h"
|
||||
#include "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
@@ -123,24 +124,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
|
||||
{
|
||||
// Only apply to bots to prevent affecting real players
|
||||
if (!player || !player->GetSession()->IsBot())
|
||||
/* for now commmented out until proven its actually required
|
||||
* havent seen any proof CleanVisibilityReferences() is needed
|
||||
|
||||
// If the player is not safe to touch, do nothing
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// If changing maps, proactively clean visibility references to prevent
|
||||
// stale pointers in other players' visibility maps during the teleport.
|
||||
// This fixes a race condition where:
|
||||
// 1. Bot A teleports and its visible objects start getting cleaned up
|
||||
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
|
||||
// 3. Those objects may already be freed, causing a segmentation fault
|
||||
if (player->GetMapId() != mapid && player->IsInWorld())
|
||||
{
|
||||
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
}
|
||||
// If same map or not in world do nothing
|
||||
if (!player->IsInWorld() || player->GetMapId() == mapid)
|
||||
return true;
|
||||
|
||||
return true; // Allow teleport to continue
|
||||
// If real player do nothing
|
||||
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
|
||||
if (!ai || ai->IsRealPlayer())
|
||||
return true;
|
||||
|
||||
// Cross-map bot teleport: defer visibility reference cleanup.
|
||||
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
|
||||
// This is intentionally done via the event queue (instead of directly here) because erasing
|
||||
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
|
||||
// or iterator invalidation while visibility updates are in progress
|
||||
ObjectGuid guid = player->GetGUID();
|
||||
player->m_Events.AddEventAtOffset(
|
||||
[guid, mapid]()
|
||||
{
|
||||
// do nothing, if the player is not safe to touch
|
||||
Player* p = ObjectAccessor::FindPlayer(guid);
|
||||
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
|
||||
return;
|
||||
|
||||
// do nothing if we are already on the target map
|
||||
if (p->GetMapId() == mapid)
|
||||
return;
|
||||
|
||||
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
|
||||
},
|
||||
Milliseconds(0));
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
|
||||
@@ -337,6 +363,9 @@ public:
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
|
||||
sPlayerbotSpellCache->Initialize();
|
||||
|
||||
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Define.h"
|
||||
#include "FleeManager.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "GuildMgr.h"
|
||||
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
|
||||
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
|
||||
uint32 assigned = 0;
|
||||
|
||||
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
|
||||
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
|
||||
{
|
||||
uint32 accountId = allRandomBotAccounts[i];
|
||||
uint32 accountId = allRandomBotAccounts[idx];
|
||||
if (currentAssignments[accountId] == 0) // Unassigned
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
|
||||
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
|
||||
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
|
||||
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
|
||||
if (player)
|
||||
LogoutPlayerBot(botGUID);
|
||||
@@ -2594,17 +2593,14 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
|
||||
|
||||
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
|
||||
{
|
||||
if (bot && GET_PLAYERBOT_AI(bot))
|
||||
{
|
||||
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
||||
return false;
|
||||
}
|
||||
if (bot)
|
||||
{
|
||||
return IsRandomBot(bot->GetGUID().GetCounter());
|
||||
}
|
||||
if (!bot)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
if (!botAI || botAI->IsRealPlayer())
|
||||
return false;
|
||||
|
||||
return IsRandomBot(bot->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
|
||||
@@ -2712,69 +2708,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
|
||||
return std::move(BgBots);
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
|
||||
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
|
||||
{
|
||||
// load all events at once on first event load
|
||||
if (eventCache[bot].empty())
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
|
||||
// Load once
|
||||
if (!cache.loaded)
|
||||
{
|
||||
cache.events.clear();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string const eventName = fields[0].Get<std::string>();
|
||||
|
||||
CachedEvent e;
|
||||
e.value = fields[1].Get<uint32>();
|
||||
e.lastChangeTime = fields[2].Get<uint32>();
|
||||
e.validIn = fields[3].Get<uint32>();
|
||||
e.data = fields[4].Get<std::string>();
|
||||
eventCache[bot][eventName] = std::move(e);
|
||||
|
||||
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
cache.loaded = true;
|
||||
}
|
||||
|
||||
CachedEvent& e = eventCache[bot][event];
|
||||
/*if (e.IsEmpty())
|
||||
auto it = cache.events.find(event);
|
||||
if (it == cache.events.end())
|
||||
return nullptr;
|
||||
|
||||
CachedEvent& e = it->second;
|
||||
|
||||
// remove expired events
|
||||
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
{
|
||||
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
|
||||
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
|
||||
|
||||
if (results)
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
e.value = fields[0].Get<uint32>();
|
||||
e.lastChangeTime = fields[1].Get<uint32>();
|
||||
e.validIn = fields[2].Get<uint32>();
|
||||
e.data = fields[3].Get<std::string>();
|
||||
}
|
||||
cache.events.erase(it);
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
|
||||
e.value = 0;
|
||||
|
||||
return e.value;
|
||||
return &e;
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
|
||||
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
|
||||
{
|
||||
std::string data = "";
|
||||
if (GetEventValue(bot, event))
|
||||
{
|
||||
CachedEvent e = eventCache[bot][event];
|
||||
data = e.data;
|
||||
}
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->value;
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data)
|
||||
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
|
||||
{
|
||||
if (CachedEvent* e = FindEvent(bot, event))
|
||||
return e->data;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data)
|
||||
{
|
||||
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
|
||||
|
||||
@@ -2790,43 +2790,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
|
||||
stmt->SetData(0, 0);
|
||||
stmt->SetData(1, bot);
|
||||
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
|
||||
stmt->SetData(2, NowSeconds());
|
||||
stmt->SetData(3, validIn);
|
||||
stmt->SetData(4, event.c_str());
|
||||
stmt->SetData(5, value);
|
||||
if (data != "")
|
||||
{
|
||||
|
||||
if (!data.empty())
|
||||
stmt->SetData(6, data.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->SetData(6);
|
||||
}
|
||||
stmt->SetData(6); // NULL
|
||||
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
PlayerbotsDatabase.CommitTransaction(trans);
|
||||
|
||||
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
|
||||
eventCache[bot][event] = std::move(e);
|
||||
// Update in-memory cache
|
||||
BotEventCache& cache = eventCache[bot];
|
||||
cache.loaded = true;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
cache.events.erase(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CachedEvent& e = cache.events[event]; // create-on-write is OK here
|
||||
e.value = value;
|
||||
e.lastChangeTime = NowSeconds();
|
||||
e.validIn = validIn;
|
||||
e.data = data;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
|
||||
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
|
||||
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
|
||||
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
|
||||
{
|
||||
return GetValue(bot->GetGUID().GetCounter(), type);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
|
||||
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
|
||||
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
|
||||
{
|
||||
SetValue(bot->GetGUID().GetCounter(), type, value, data);
|
||||
}
|
||||
@@ -3115,7 +3127,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
|
||||
{
|
||||
SetEventValue(bot, "add", 0, 0);
|
||||
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
|
||||
currentBots.remove(bot);
|
||||
}
|
||||
|
||||
Player* RandomPlayerbotMgr::GetRandomPlayer()
|
||||
@@ -3497,7 +3509,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
|
||||
stmt->SetData(1, owner.GetCounter());
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
|
||||
eventCache[owner.GetCounter()].clear();
|
||||
uint32 botId = owner.GetCounter();
|
||||
eventCache.erase(botId);
|
||||
|
||||
LogoutPlayerBot(owner);
|
||||
}
|
||||
@@ -3514,7 +3527,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
ObjectGuid battleMasterGUID = ObjectGuid::Empty;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "NewRpgInfo.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "GameTime.h"
|
||||
|
||||
struct BattlegroundInfo
|
||||
{
|
||||
@@ -45,25 +46,20 @@ class ChatHandler;
|
||||
class PerformanceMonitorOperation;
|
||||
class WorldLocation;
|
||||
|
||||
class CachedEvent
|
||||
struct CachedEvent
|
||||
{
|
||||
public:
|
||||
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
|
||||
CachedEvent(const CachedEvent& other)
|
||||
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
|
||||
{
|
||||
}
|
||||
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
|
||||
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
uint32 value = 0;
|
||||
uint32 lastChangeTime = 0;
|
||||
uint32 validIn = 0;
|
||||
std::string data;
|
||||
|
||||
bool IsEmpty() const { return !lastChangeTime; }
|
||||
};
|
||||
|
||||
struct BotEventCache
|
||||
{
|
||||
bool loaded = false;
|
||||
std::unordered_map<std::string, CachedEvent> events;
|
||||
};
|
||||
|
||||
// https://gist.github.com/bradley219/5373998
|
||||
@@ -139,13 +135,13 @@ public:
|
||||
void Revive(Player* player);
|
||||
void ChangeStrategy(Player* player);
|
||||
void ChangeStrategyOnce(Player* player);
|
||||
uint32 GetValue(Player* bot, std::string const type);
|
||||
uint32 GetValue(uint32 bot, std::string const type);
|
||||
std::string const GetData(uint32 bot, std::string const type);
|
||||
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
|
||||
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
|
||||
uint32 GetValue(Player* bot, std::string const& type);
|
||||
uint32 GetValue(uint32 bot, std::string const& type);
|
||||
std::string GetData(uint32 bot, std::string const& type);
|
||||
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
|
||||
void Remove(Player* bot);
|
||||
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
|
||||
CreatureData const* GetCreatureDataByEntry(uint32 entry);
|
||||
void LoadBattleMastersCache();
|
||||
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
|
||||
@@ -203,10 +199,11 @@ private:
|
||||
bool _isBotInitializing = true;
|
||||
bool _isBotLogging = true;
|
||||
NewRpgStatistic rpgStasticTotal;
|
||||
uint32 GetEventValue(uint32 bot, std::string const event);
|
||||
std::string const GetEventData(uint32 bot, std::string const event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
|
||||
std::string const data = "");
|
||||
CachedEvent* FindEvent(uint32 bot, std::string const& event);
|
||||
uint32 GetEventValue(uint32 bot, std::string const& event);
|
||||
std::string GetEventData(uint32 bot, std::string const& event);
|
||||
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
|
||||
std::string const& data = "");
|
||||
void GetBots();
|
||||
std::vector<uint32> GetBgBots(uint32 bracket);
|
||||
time_t BgCheckTimer;
|
||||
@@ -228,7 +225,7 @@ private:
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
|
||||
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
|
||||
std::unordered_map<uint32, BotEventCache> eventCache;
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
@@ -238,6 +235,7 @@ private:
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
45
src/database/PlayerbotSpellCache.cpp
Normal file
45
src/database/PlayerbotSpellCache.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
void PlayerbotSpellCache::Initialize()
|
||||
{
|
||||
LOG_INFO("playerbots",
|
||||
"Playerbots: ListSpellsAction caches initialized");
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
|
||||
// Fill the vendorItems cache once from the world database.
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(static_cast<uint32>(entry));
|
||||
}
|
||||
while (results->NextRow());
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
|
||||
skillSpells.size(), vendorItems.size());
|
||||
}
|
||||
|
||||
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
|
||||
{
|
||||
auto itr = skillSpells.find(spellId);
|
||||
if (itr != skillSpells.end())
|
||||
return itr->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
|
||||
{
|
||||
return vendorItems.find(itemId) != vendorItems.end();
|
||||
}
|
||||
34
src/database/PlayerbotSpellCache.h
Normal file
34
src/database/PlayerbotSpellCache.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
class PlayerbotSpellCache
|
||||
{
|
||||
public:
|
||||
static PlayerbotSpellCache* Instance()
|
||||
{
|
||||
static PlayerbotSpellCache instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Initialize(); // call once on startup
|
||||
|
||||
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
|
||||
bool IsItemBuyable(uint32 itemId) const;
|
||||
|
||||
private:
|
||||
PlayerbotSpellCache() = default;
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
std::set<uint32> vendorItems;
|
||||
};
|
||||
|
||||
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
|
||||
|
||||
#endif
|
||||
@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
|
||||
packet << uint8(1); // accept
|
||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,20 +15,19 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool AttackAction::Execute(Event event)
|
||||
bool AttackAction::Execute(Event /*event*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackMyTargetAction::Execute(Event event)
|
||||
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
@@ -81,11 +80,13 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if bot OR target is in prohibited zone/area
|
||||
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != target) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
{
|
||||
@@ -99,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -113,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -120,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,9 +160,8 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
}
|
||||
|
||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
{
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
}
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
@@ -187,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
|
||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||
|
||||
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
|
||||
@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
|
||||
|
||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||
{
|
||||
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
||||
|
||||
if (!tItems)
|
||||
if (!tItems || !proto)
|
||||
return false;
|
||||
|
||||
uint32 itemId = proto->ItemId;
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
||||
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||
{
|
||||
if (tItems->GetItem(slot)->item == itemId)
|
||||
if (tItems->GetItem(slot)->item != itemId)
|
||||
continue;
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(10000000);
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(botMoney);
|
||||
|
||||
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||
if (newCount > oldCount)
|
||||
{
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(10000000);
|
||||
}
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
bot->SetMoney(botMoney);
|
||||
}
|
||||
|
||||
if (oldCount <
|
||||
AI_VALUE2(
|
||||
uint32, "item count",
|
||||
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "LootObjectStack.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "ServerFacade.h"
|
||||
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||
|
||||
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||
// try to resolve the raid icon directly from the group.
|
||||
if (!rtiTarget)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
std::string const rti = AI_VALUE(std::string, "rti");
|
||||
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||
if (index >= 0)
|
||||
{
|
||||
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||
if (!guid.IsEmpty())
|
||||
rtiTarget = botAI->GetUnit(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellError("I dont see my rti attack target");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,90 +7,43 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotSpellCache.h"
|
||||
|
||||
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
|
||||
std::set<uint32> ListSpellsAction::vendorItems;
|
||||
using SpellListEntry = std::pair<uint32, std::string>;
|
||||
|
||||
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
|
||||
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread.
|
||||
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
|
||||
// and only relies on spell school and spell name to keep sorting fast and bounded.
|
||||
// lhs = the left element, rhs = the right element.
|
||||
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
|
||||
{
|
||||
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
|
||||
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
|
||||
if (!si1 || !si2)
|
||||
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first);
|
||||
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first);
|
||||
|
||||
if (!lhSpellInfo || !rhSpellInfo)
|
||||
{
|
||||
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
|
||||
return false;
|
||||
}
|
||||
uint32 p1 = si1->SchoolMask * 20000;
|
||||
uint32 p2 = si2->SchoolMask * 20000;
|
||||
|
||||
uint32 skill1 = 0, skill2 = 0;
|
||||
uint32 skillValue1 = 0, skillValue2 = 0;
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
{
|
||||
if (skillLine->Spell == s1.first)
|
||||
{
|
||||
skill1 = skillLine->SkillLine;
|
||||
skillValue1 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
|
||||
if (skillLine->Spell == s2.first)
|
||||
{
|
||||
skill2 = skillLine->SkillLine;
|
||||
skillValue2 = skillLine->TrivialSkillLineRankLow;
|
||||
}
|
||||
}
|
||||
|
||||
if (skill1 && skill2)
|
||||
break;
|
||||
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first);
|
||||
// Fallback: order by spell id to keep comparator strict and deterministic.
|
||||
return lhSpell.first < rhSpell.first;
|
||||
}
|
||||
|
||||
p1 += skill1 * 500;
|
||||
p2 += skill2 * 500;
|
||||
uint32 lhsKey = lhSpellInfo->SchoolMask;
|
||||
uint32 rhsKey = rhSpellInfo->SchoolMask;
|
||||
|
||||
p1 += skillValue1;
|
||||
p2 += skillValue2;
|
||||
|
||||
if (p1 == p2)
|
||||
if (lhsKey == rhsKey)
|
||||
{
|
||||
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
|
||||
}
|
||||
// Defensive check: if DBC data is broken and spell names are nullptr,
|
||||
// fall back to id ordering instead of risking a crash in std::strcmp.
|
||||
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
|
||||
return lhSpell.first < rhSpell.first;
|
||||
|
||||
return p1 > p2;
|
||||
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
|
||||
}
|
||||
return lhsKey > rhsKey;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
|
||||
{
|
||||
if (skillSpells.empty())
|
||||
{
|
||||
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
|
||||
{
|
||||
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
|
||||
skillSpells[skillLine->Spell] = skillLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorItems.empty())
|
||||
{
|
||||
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
|
||||
if (results)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = results->Fetch();
|
||||
int32 entry = fields[0].Get<int32>();
|
||||
if (entry <= 0)
|
||||
continue;
|
||||
|
||||
vendorItems.insert(entry);
|
||||
} while (results->NextRow());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream posOut;
|
||||
std::ostringstream negOut;
|
||||
|
||||
uint32 skill = 0;
|
||||
|
||||
std::vector<std::string> ss = split(filter, ' ');
|
||||
@@ -99,13 +52,15 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
skill = chat->parseSkill(ss[0]);
|
||||
if (skill != SKILL_NONE)
|
||||
{
|
||||
filter = ss.size() > 1 ? filter = ss[1] : "";
|
||||
filter = ss.size() > 1 ? ss[1] : "";
|
||||
}
|
||||
|
||||
if (ss[0] == "first" && ss[1] == "aid")
|
||||
// Guard access to ss[1]/ss[2] to avoid out-of-bounds
|
||||
// when the player only types "first" without "aid".
|
||||
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
|
||||
{
|
||||
skill = SKILL_FIRST_AID;
|
||||
filter = ss.size() > 2 ? filter = ss[2] : "";
|
||||
filter = ss.size() > 2 ? ss[2] : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,26 +70,57 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
if (filter.find("-") != std::string::npos)
|
||||
if (filter.find('-') != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> ff = split(filter, '-');
|
||||
minLevel = atoi(ff[0].c_str());
|
||||
maxLevel = atoi(ff[1].c_str());
|
||||
filter = "";
|
||||
if (ff.size() >= 2)
|
||||
{
|
||||
minLevel = std::atoi(ff[0].c_str());
|
||||
maxLevel = std::atoi(ff[1].c_str());
|
||||
if (minLevel > maxLevel)
|
||||
std::swap(minLevel, maxLevel);
|
||||
}
|
||||
filter.clear();
|
||||
}
|
||||
|
||||
bool craftableOnly = false;
|
||||
if (filter.find("+") != std::string::npos)
|
||||
bool canCraftNow = false;
|
||||
if (filter.find('+') != std::string::npos)
|
||||
{
|
||||
craftableOnly = true;
|
||||
canCraftNow = true;
|
||||
|
||||
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
|
||||
// If no explicit skill was detected yet, try to parse the filter (without '+')
|
||||
// as a profession/skill name so that craftable-only filters still work with skills.
|
||||
if (skill == SKILL_NONE)
|
||||
{
|
||||
std::string skillFilter = filter;
|
||||
|
||||
// Remove '+' before trying to interpret the first token as a skill name.
|
||||
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
|
||||
|
||||
std::vector<std::string> skillTokens = split(skillFilter, ' ');
|
||||
if (!skillTokens.empty())
|
||||
{
|
||||
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
|
||||
if (parsedSkill != SKILL_NONE)
|
||||
{
|
||||
skill = parsedSkill;
|
||||
|
||||
// Any remaining text after the skill token becomes the "name" filter
|
||||
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
|
||||
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally remove '+' from the filter that will be used for name/range parsing.
|
||||
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
|
||||
}
|
||||
|
||||
uint32 slot = chat->parseSlot(filter);
|
||||
if (slot != EQUIPMENT_SLOT_END)
|
||||
filter = "";
|
||||
filter.clear();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells;
|
||||
std::vector<SpellListEntry> spells;
|
||||
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
||||
{
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
|
||||
@@ -150,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
|
||||
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first);
|
||||
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
|
||||
continue;
|
||||
|
||||
@@ -162,7 +148,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
bool first = true;
|
||||
int32 craftCount = -1;
|
||||
int32 craftsPossible = -1;
|
||||
std::ostringstream materials;
|
||||
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
|
||||
{
|
||||
@@ -189,12 +175,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
FindItemByIdVisitor visitor(itemid);
|
||||
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
|
||||
bool buyable = (vendorItems.find(itemid) != vendorItems.end());
|
||||
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid);
|
||||
if (!buyable)
|
||||
{
|
||||
uint32 craftable = reagentsInInventory / reagentsRequired;
|
||||
if (craftCount < 0 || craftCount > craftable)
|
||||
craftCount = craftable;
|
||||
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable))
|
||||
craftsPossible = static_cast<int32>(craftable);
|
||||
}
|
||||
|
||||
if (reagentsInInventory)
|
||||
@@ -205,8 +191,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
}
|
||||
}
|
||||
|
||||
if (craftCount < 0)
|
||||
craftCount = 0;
|
||||
if (craftsPossible < 0)
|
||||
craftsPossible = 0;
|
||||
|
||||
std::ostringstream out;
|
||||
bool filtered = false;
|
||||
@@ -218,8 +204,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
{
|
||||
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
|
||||
{
|
||||
if (craftCount)
|
||||
out << "|cffffff00(x" << craftCount << ")|r ";
|
||||
if (craftsPossible)
|
||||
out << "|cffffff00(x" << craftsPossible << ")|r ";
|
||||
|
||||
out << chat->FormatItem(proto);
|
||||
|
||||
@@ -246,7 +232,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
if (filtered)
|
||||
continue;
|
||||
|
||||
if (craftableOnly && !craftCount)
|
||||
if (canCraftNow && !craftsPossible)
|
||||
continue;
|
||||
|
||||
out << materials.str();
|
||||
@@ -275,10 +261,9 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
continue;
|
||||
|
||||
if (itr->first == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "?! {}", itr->first);
|
||||
}
|
||||
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
|
||||
|
||||
spells.emplace_back(itr->first, out.str());
|
||||
alreadySeenList += spellInfo->SpellName[0];
|
||||
alreadySeenList += ",";
|
||||
}
|
||||
@@ -294,25 +279,28 @@ bool ListSpellsAction::Execute(Event event)
|
||||
|
||||
std::string const filter = event.getParam();
|
||||
|
||||
std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
|
||||
std::vector<SpellListEntry> spells = GetSpellList(filter);
|
||||
|
||||
if (spells.empty())
|
||||
{
|
||||
// CHANGE: Give early feedback when no spells match the filter.
|
||||
botAI->TellMaster("No spells found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->TellMaster("=== Spells ===");
|
||||
|
||||
std::sort(spells.begin(), spells.end(), CompareSpells);
|
||||
|
||||
uint32 count = 0;
|
||||
for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
{
|
||||
// CHANGE: Send the full spell list again so client-side addons
|
||||
// (e.g. Multibot / Unbot) can reconstruct the
|
||||
// complete spellbook for configuration. The heavy part that caused
|
||||
// freezes before was the old CompareSpells implementation scanning
|
||||
// the entire SkillLineAbility DBC on every comparison. With the new
|
||||
// cheap comparator above, sending all lines here is safe and keeps
|
||||
// behaviour compatible with existing addons.
|
||||
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
botAI->TellMasterNoFacing(i->second);
|
||||
|
||||
// if (++count >= 50)
|
||||
// {
|
||||
// std::ostringstream msg;
|
||||
// msg << (spells.size() - 50) << " more...";
|
||||
// botAI->TellMasterNoFacing(msg.str());
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,8 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
|
||||
|
||||
private:
|
||||
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
|
||||
static std::set<uint32> vendorItems;
|
||||
static void InitSpellCaches();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -946,25 +946,36 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
|
||||
|
||||
bool MovementAction::IsMovingAllowed()
|
||||
{
|
||||
// do not allow if not vehicle driver
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
|
||||
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
|
||||
// states are set when handling certain aura effects. We don't check against
|
||||
// UNIT_STATE_ROOT here, because this state is used by vehicles.
|
||||
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
|
||||
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
|
||||
return false;
|
||||
|
||||
// Common CC effects, ordered by frequency: rooted > frozen > polymorphed
|
||||
if (bot->IsRooted() || bot->isFrozen() || bot->IsPolymorphed())
|
||||
return false;
|
||||
|
||||
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
// Traveling state: taxi flight and being teleported (relatively rare)
|
||||
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->IsBeingTeleported())
|
||||
return false;
|
||||
|
||||
// Vehicle state: is in the vehicle and can control it (rare, content-specific).
|
||||
// We need to check charmed state AFTER vehicle one, cuz that's how it works:
|
||||
// passengers are set to charmed by vehicle with CHARM_TYPE_VEHICLE.
|
||||
if ((bot->GetVehicle() && !botAI->IsInVehicle(true)) || bot->IsCharmed())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
|
||||
@@ -972,12 +983,10 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
|
||||
void MovementAction::UpdateMovementState()
|
||||
{
|
||||
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
|
||||
bot->IsRooted() ||
|
||||
bot->isFrozen() ||
|
||||
bot->IsPolymorphed() ||
|
||||
bot->HasRootAura() ||
|
||||
bot->HasStunAura() ||
|
||||
bot->HasConfuseAura() ||
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||||
bot->IsPolymorphed();
|
||||
|
||||
// no update movement flags while movement is current restricted.
|
||||
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||
|
||||
@@ -18,9 +18,7 @@ bool PetsAction::Execute(Event event)
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||
std::string param = event.getParam();
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
if (param.empty())
|
||||
{
|
||||
@@ -129,9 +127,7 @@ bool PetsAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||
if (!masterTargetGuid.IsEmpty())
|
||||
{
|
||||
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid target is selected, show an error and return.
|
||||
@@ -156,8 +152,9 @@ bool PetsAction::Execute(Event event)
|
||||
botAI->TellError(text);
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())
|
||||
&& (targetUnit->IsPlayer() || targetUnit->IsPet()))
|
||||
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
|
||||
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != targetUnit))
|
||||
{
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "ReadyCheckAction.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
|
||||
class ReadyChecker
|
||||
{
|
||||
public:
|
||||
virtual ~ReadyChecker() = default;
|
||||
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
|
||||
virtual std::string const getName() = 0;
|
||||
virtual bool PrintAlways() { return true; }
|
||||
|
||||
static std::vector<ReadyChecker*> checkers;
|
||||
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
|
||||
static std::once_flag initFlag;
|
||||
};
|
||||
|
||||
std::vector<ReadyChecker*> ReadyChecker::checkers;
|
||||
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
|
||||
std::once_flag ReadyChecker::initFlag;
|
||||
|
||||
class HealthChecker : public ReadyChecker
|
||||
{
|
||||
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
|
||||
|
||||
bool ReadyCheckAction::ReadyCheck()
|
||||
{
|
||||
if (ReadyChecker::checkers.empty())
|
||||
{
|
||||
ReadyChecker::checkers.push_back(new HealthChecker());
|
||||
ReadyChecker::checkers.push_back(new ManaChecker());
|
||||
ReadyChecker::checkers.push_back(new DistanceChecker());
|
||||
ReadyChecker::checkers.push_back(new HunterChecker());
|
||||
std::call_once(
|
||||
ReadyChecker::initFlag,
|
||||
[]()
|
||||
{
|
||||
ReadyChecker::checkers.reserve(8);
|
||||
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
|
||||
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
|
||||
}
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
|
||||
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
|
||||
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
|
||||
});
|
||||
|
||||
bool result = true;
|
||||
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
|
||||
++i)
|
||||
for (auto const& checkerPtr : ReadyChecker::checkers)
|
||||
{
|
||||
ReadyChecker* checker = *i;
|
||||
bool ok = checker->Check(botAI, context);
|
||||
if (!checkerPtr)
|
||||
continue;
|
||||
|
||||
bool ok = checkerPtr->Check(botAI, context);
|
||||
result = result && ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "WorldPacketHandlerStrategy.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
PassTroughStrategy::InitTriggers(triggers);
|
||||
@@ -69,7 +67,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr)));
|
||||
|
||||
// loot roll
|
||||
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", 10.0f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", relevance), nullptr)));
|
||||
}
|
||||
|
||||
WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
|
||||
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "NamedObjectContext.h"
|
||||
@@ -9,77 +9,255 @@ class RaidKarazhanActionContext : public NamedObjectContext<Action>
|
||||
public:
|
||||
RaidKarazhanActionContext()
|
||||
{
|
||||
creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind;
|
||||
// Trash
|
||||
creators["mana warp stun creature before warp breach"] =
|
||||
&RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach;
|
||||
|
||||
creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_mark_target;
|
||||
// Attumen the Huntsman
|
||||
creators["attumen the huntsman mark target"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_mark_target;
|
||||
|
||||
creators["karazhan maiden of virtue position boss"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_boss;
|
||||
creators["karazhan maiden of virtue position ranged"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_ranged;
|
||||
creators["attumen the huntsman split bosses"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_split_bosses;
|
||||
|
||||
creators["karazhan big bad wolf position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss;
|
||||
creators["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away;
|
||||
creators["attumen the huntsman stack behind"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_stack_behind;
|
||||
|
||||
creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target;
|
||||
creators["attumen the huntsman manage dps timer"] =
|
||||
&RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer;
|
||||
|
||||
creators["karazhan wizard of oz mark target"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_mark_target;
|
||||
creators["karazhan wizard of oz scorch strawman"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_scorch_strawman;
|
||||
// Moroes
|
||||
creators["moroes main tank attack boss"] =
|
||||
&RaidKarazhanActionContext::moroes_main_tank_attack_boss;
|
||||
|
||||
creators["karazhan the curator mark target"] = &RaidKarazhanActionContext::karazhan_the_curator_mark_target;
|
||||
creators["karazhan the curator position boss"] = &RaidKarazhanActionContext::karazhan_the_curator_position_boss;
|
||||
creators["karazhan the curator spread ranged"] = &RaidKarazhanActionContext::karazhan_the_curator_spread_ranged;
|
||||
creators["moroes mark target"] =
|
||||
&RaidKarazhanActionContext::moroes_mark_target;
|
||||
|
||||
creators["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target;
|
||||
// Maiden of Virtue
|
||||
creators["maiden of virtue move boss to healer"] =
|
||||
&RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer;
|
||||
|
||||
creators["karazhan shade of aran arcane explosion run away"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_arcane_explosion_run_away;
|
||||
creators["karazhan shade of aran flame wreath stop movement"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_movement;
|
||||
creators["karazhan shade of aran mark conjured elemental"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_mark_conjured_elemental;
|
||||
creators["karazhan shade of aran spread ranged"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_spread_ranged;
|
||||
creators["maiden of virtue position ranged"] =
|
||||
&RaidKarazhanActionContext::maiden_of_virtue_position_ranged;
|
||||
|
||||
creators["karazhan netherspite block red beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_red_beam;
|
||||
creators["karazhan netherspite block blue beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_blue_beam;
|
||||
creators["karazhan netherspite block green beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_green_beam;
|
||||
creators["karazhan netherspite avoid beam and void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_avoid_beam_and_void_zone;
|
||||
creators["karazhan netherspite banish phase avoid void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_banish_phase_avoid_void_zone;
|
||||
// The Big Bad Wolf
|
||||
creators["big bad wolf position boss"] =
|
||||
&RaidKarazhanActionContext::big_bad_wolf_position_boss;
|
||||
|
||||
creators["karazhan prince malchezaar non tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_non_tank_avoid_hazard;
|
||||
creators["karazhan prince malchezaar tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_tank_avoid_hazard;
|
||||
creators["big bad wolf run away from boss"] =
|
||||
&RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss;
|
||||
|
||||
// Romulo and Julianne
|
||||
creators["romulo and julianne mark target"] =
|
||||
&RaidKarazhanActionContext::romulo_and_julianne_mark_target;
|
||||
|
||||
// The Wizard of Oz
|
||||
creators["wizard of oz mark target"] =
|
||||
&RaidKarazhanActionContext::wizard_of_oz_mark_target;
|
||||
|
||||
creators["wizard of oz scorch strawman"] =
|
||||
&RaidKarazhanActionContext::wizard_of_oz_scorch_strawman;
|
||||
|
||||
// The Curator
|
||||
creators["the curator mark astral flare"] =
|
||||
&RaidKarazhanActionContext::the_curator_mark_astral_flare;
|
||||
|
||||
creators["the curator position boss"] =
|
||||
&RaidKarazhanActionContext::the_curator_position_boss;
|
||||
|
||||
creators["the curator spread ranged"] =
|
||||
&RaidKarazhanActionContext::the_curator_spread_ranged;
|
||||
|
||||
// Terestian Illhoof
|
||||
creators["terestian illhoof mark target"] =
|
||||
&RaidKarazhanActionContext::terestian_illhoof_mark_target;
|
||||
|
||||
// Shade of Aran
|
||||
creators["shade of aran run away from arcane explosion"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion;
|
||||
|
||||
creators["shade of aran stop moving during flame wreath"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath;
|
||||
|
||||
creators["shade of aran mark conjured elemental"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental;
|
||||
|
||||
creators["shade of aran ranged maintain distance"] =
|
||||
&RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance;
|
||||
|
||||
// Netherspite
|
||||
creators["netherspite block red beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_red_beam;
|
||||
|
||||
creators["netherspite block blue beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_blue_beam;
|
||||
|
||||
creators["netherspite block green beam"] =
|
||||
&RaidKarazhanActionContext::netherspite_block_green_beam;
|
||||
|
||||
creators["netherspite avoid beam and void zone"] =
|
||||
&RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone;
|
||||
|
||||
creators["netherspite banish phase avoid void zone"] =
|
||||
&RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone;
|
||||
|
||||
creators["netherspite manage timers and trackers"] =
|
||||
&RaidKarazhanActionContext::netherspite_manage_timers_and_trackers;
|
||||
|
||||
// Prince Malchezaar
|
||||
creators["prince malchezaar enfeebled avoid hazard"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard;
|
||||
|
||||
creators["prince malchezaar non tank avoid infernal"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal;
|
||||
|
||||
creators["prince malchezaar main tank movement"] =
|
||||
&RaidKarazhanActionContext::prince_malchezaar_main_tank_movement;
|
||||
|
||||
// Nightbane
|
||||
creators["nightbane ground phase position boss"] =
|
||||
&RaidKarazhanActionContext::nightbane_ground_phase_position_boss;
|
||||
|
||||
creators["nightbane ground phase rotate ranged positions"] =
|
||||
&RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions;
|
||||
|
||||
creators["nightbane cast fear ward on main tank"] =
|
||||
&RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank;
|
||||
|
||||
creators["nightbane control pet aggression"] =
|
||||
&RaidKarazhanActionContext::nightbane_control_pet_aggression;
|
||||
|
||||
creators["nightbane flight phase movement"] =
|
||||
&RaidKarazhanActionContext::nightbane_flight_phase_movement;
|
||||
|
||||
creators["nightbane manage timers and trackers"] =
|
||||
&RaidKarazhanActionContext::nightbane_manage_timers_and_trackers;
|
||||
}
|
||||
|
||||
private:
|
||||
static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); }
|
||||
// Trash
|
||||
static Action* mana_warp_stun_creature_before_warp_breach(
|
||||
PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); }
|
||||
|
||||
static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(botAI); }
|
||||
// Attumen the Huntsman
|
||||
static Action* attumen_the_huntsman_mark_target(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); }
|
||||
|
||||
static Action* karazhan_maiden_of_virtue_position_boss(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionBossAction(botAI); }
|
||||
static Action* karazhan_maiden_of_virtue_position_ranged(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionRangedAction(botAI); }
|
||||
static Action* attumen_the_huntsman_split_bosses(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); }
|
||||
|
||||
static Action* karazhan_big_bad_wolf_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(botAI); }
|
||||
static Action* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); }
|
||||
static Action* attumen_the_huntsman_stack_behind(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); }
|
||||
|
||||
static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(botAI); }
|
||||
static Action* attumen_the_huntsman_manage_dps_timer(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); }
|
||||
|
||||
static Action* karazhan_wizard_of_oz_mark_target(PlayerbotAI* botAI) { return new KarazhanWizardOfOzMarkTargetAction(botAI); }
|
||||
static Action* karazhan_wizard_of_oz_scorch_strawman(PlayerbotAI* botAI) { return new KarazhanWizardOfOzScorchStrawmanAction(botAI); }
|
||||
// Moroes
|
||||
static Action* moroes_main_tank_attack_boss(
|
||||
PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); }
|
||||
|
||||
static Action* karazhan_the_curator_mark_target(PlayerbotAI* botAI) { return new KarazhanTheCuratorMarkTargetAction(botAI); }
|
||||
static Action* karazhan_the_curator_position_boss(PlayerbotAI* botAI) { return new KarazhanTheCuratorPositionBossAction(botAI); }
|
||||
static Action* karazhan_the_curator_spread_ranged(PlayerbotAI* botAI) { return new KarazhanTheCuratorSpreadRangedAction(botAI); }
|
||||
static Action* moroes_mark_target(
|
||||
PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); }
|
||||
|
||||
static Action* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(botAI); }
|
||||
// Maiden of Virtue
|
||||
static Action* maiden_of_virtue_move_boss_to_healer(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); }
|
||||
|
||||
static Action* karazhan_shade_of_aran_arcane_explosion_run_away(PlayerbotAI* botAI) { return new KarazhanShadeOfAranArcaneExplosionRunAwayAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_flame_wreath_stop_movement(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopMovementAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_mark_conjured_elemental(PlayerbotAI* botAI) { return new KarazhanShadeOfAranMarkConjuredElementalAction(botAI); }
|
||||
static Action* karazhan_shade_of_aran_spread_ranged(PlayerbotAI* botAI) { return new KarazhanShadeOfAranSpreadRangedAction(botAI); }
|
||||
static Action* maiden_of_virtue_position_ranged(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); }
|
||||
|
||||
static Action* karazhan_netherspite_block_red_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockRedBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_block_blue_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockBlueBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_block_green_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockGreenBeamAction(botAI); }
|
||||
static Action* karazhan_netherspite_avoid_beam_and_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteAvoidBeamAndVoidZoneAction(botAI); }
|
||||
static Action* karazhan_netherspite_banish_phase_avoid_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
|
||||
// The Big Bad Wolf
|
||||
static Action* big_bad_wolf_position_boss(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); }
|
||||
|
||||
static Action* karazhan_prince_malchezaar_non_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarNonTankAvoidHazardAction(botAI); }
|
||||
static Action* karazhan_prince_malchezaar_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTankAvoidHazardAction(botAI); }
|
||||
static Action* big_bad_wolf_run_away_from_boss(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); }
|
||||
|
||||
// Romulo and Julianne
|
||||
static Action* romulo_and_julianne_mark_target(
|
||||
PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); }
|
||||
|
||||
// The Wizard of Oz
|
||||
static Action* wizard_of_oz_mark_target(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); }
|
||||
|
||||
static Action* wizard_of_oz_scorch_strawman(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); }
|
||||
|
||||
// The Curator
|
||||
static Action* the_curator_mark_astral_flare(
|
||||
PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); }
|
||||
|
||||
static Action* the_curator_position_boss(
|
||||
PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); }
|
||||
|
||||
static Action* the_curator_spread_ranged(
|
||||
PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); }
|
||||
|
||||
// Terestian Illhoof
|
||||
static Action* terestian_illhoof_mark_target(
|
||||
PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); }
|
||||
|
||||
// Shade of Aran
|
||||
static Action* shade_of_aran_run_away_from_arcane_explosion(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_stop_moving_during_flame_wreath(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_mark_conjured_elemental(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); }
|
||||
|
||||
static Action* shade_of_aran_ranged_maintain_distance(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); }
|
||||
|
||||
// Netherspite
|
||||
static Action* netherspite_block_red_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_block_blue_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_block_green_beam(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); }
|
||||
|
||||
static Action* netherspite_avoid_beam_and_void_zone(
|
||||
PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); }
|
||||
|
||||
static Action* netherspite_banish_phase_avoid_void_zone(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
|
||||
|
||||
static Action* netherspite_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); }
|
||||
|
||||
// Prince Malchezaar
|
||||
static Action* prince_malchezaar_enfeebled_avoid_hazard(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); }
|
||||
|
||||
static Action* prince_malchezaar_non_tank_avoid_infernal(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); }
|
||||
|
||||
static Action* prince_malchezaar_main_tank_movement(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); }
|
||||
|
||||
// Nightbane
|
||||
static Action* nightbane_ground_phase_position_boss(
|
||||
PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); }
|
||||
|
||||
static Action* nightbane_ground_phase_rotate_ranged_positions(
|
||||
PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); }
|
||||
|
||||
static Action* nightbane_cast_fear_ward_on_main_tank(
|
||||
PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); }
|
||||
|
||||
static Action* nightbane_control_pet_aggression(
|
||||
PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); }
|
||||
|
||||
static Action* nightbane_flight_phase_movement(
|
||||
PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); }
|
||||
|
||||
static Action* nightbane_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,217 +2,321 @@
|
||||
#define _PLAYERBOT_RAIDKARAZHANACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "MovementActions.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction
|
||||
class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanMoroesMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {}
|
||||
|
||||
ManaWarpStunCreatureBeforeWarpBreachAction(
|
||||
PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction
|
||||
class AttumenTheHuntsmanMarkTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction
|
||||
class AttumenTheHuntsmanSplitBossesAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanSplitBossesAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfPositionBossAction : public MovementAction
|
||||
class AttumenTheHuntsmanStackBehindAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanStackBehindAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfRunAwayAction : public MovementAction
|
||||
class AttumenTheHuntsmanManageDpsTimerAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {}
|
||||
|
||||
AttumenTheHuntsmanManageDpsTimerAction(
|
||||
PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoroesMainTankAttackBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
MoroesMainTankAttackBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoroesMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
MoroesMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MaidenOfVirtueMoveBossToHealerAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
MaidenOfVirtueMoveBossToHealerAction(
|
||||
PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MaidenOfVirtuePositionRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MaidenOfVirtuePositionRangedAction(
|
||||
PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BigBadWolfPositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
BigBadWolfPositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BigBadWolfRunAwayFromBossAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
BigBadWolfRunAwayFromBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class RomuloAndJulianneMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
RomuloAndJulianneMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class WizardOfOzMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
WizardOfOzMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class WizardOfOzScorchStrawmanAction : public Action
|
||||
{
|
||||
public:
|
||||
WizardOfOzScorchStrawmanAction(
|
||||
PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorMarkAstralFlareAction : public Action
|
||||
{
|
||||
public:
|
||||
TheCuratorMarkAstralFlareAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorPositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
TheCuratorPositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TheCuratorSpreadRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
TheCuratorSpreadRangedAction(
|
||||
PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TerestianIllhoofMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
TerestianIllhoofMarkTargetAction(
|
||||
PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranRunAwayFromArcaneExplosionAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranStopMovingDuringFlameWreathAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranMarkConjuredElementalAction : public Action
|
||||
{
|
||||
public:
|
||||
ShadeOfAranMarkConjuredElementalAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ShadeOfAranRangedMaintainDistanceAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShadeOfAranRangedMaintainDistanceAction(
|
||||
PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class NetherspiteBlockRedBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBlockRedBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
size_t currentIndex = 0;
|
||||
Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss);
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingRedBeam;
|
||||
};
|
||||
|
||||
class KarazhanRomuloAndJulianneMarkTargetAction : public Action
|
||||
class NetherspiteBlockBlueBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {}
|
||||
NetherspiteBlockBlueBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingBlueBeam;
|
||||
};
|
||||
|
||||
class NetherspiteBlockGreenBeamAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBlockGreenBeamAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, bool> _wasBlockingGreenBeam;
|
||||
};
|
||||
|
||||
class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteAvoidBeamAndVoidZoneAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
struct BeamAvoid
|
||||
{
|
||||
Unit* portal;
|
||||
float minDist, maxDist;
|
||||
};
|
||||
bool IsAwayFromBeams(float x, float y, const std::vector<BeamAvoid>& beams, Unit* netherspite);
|
||||
};
|
||||
|
||||
class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NetherspiteBanishPhaseAvoidVoidZoneAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzMarkTargetAction : public Action
|
||||
class NetherspiteManageTimersAndTrackersAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {}
|
||||
|
||||
NetherspiteManageTimersAndTrackersAction(
|
||||
PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzScorchStrawmanAction : public Action
|
||||
class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarEnfeebledAvoidHazardAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorMarkTargetAction : public Action
|
||||
class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarNonTankAvoidInfernalAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorPositionBossAction : public MovementAction
|
||||
class PrinceMalchezaarMainTankMovementAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorSpreadRangedAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanTerestianIllhoofMarkTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {}
|
||||
|
||||
PrinceMalchezaarMainTankMovementAction(
|
||||
PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction
|
||||
class NightbaneGroundPhasePositionBossAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneGroundPhasePositionBossAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranMarkConjuredElementalAction : public Action
|
||||
class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {}
|
||||
|
||||
NightbaneGroundPhaseRotateRangedPositionsAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranSpreadRangedAction : public MovementAction
|
||||
class NightbaneCastFearWardOnMainTankAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneCastFearWardOnMainTankAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockRedBeamAction : public MovementAction
|
||||
class NightbaneControlPetAggressionAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneControlPetAggressionAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction
|
||||
class NightbaneFlightPhaseMovementAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneFlightPhaseMovementAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction
|
||||
class NightbaneManageTimersAndTrackersAction : public Action
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {}
|
||||
|
||||
NightbaneManageTimersAndTrackersAction(
|
||||
PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarNonTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar non-tank avoid hazard") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,316 +1,356 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "RaidKarazhanHelpers.h"
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "PlayerbotMgr.h"
|
||||
#include "Position.h"
|
||||
#include "Spell.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
|
||||
const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.782f, 92.712f);
|
||||
const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
|
||||
namespace KarazhanHelpers
|
||||
{
|
||||
{ -10931.178f, -2116.580f, 92.179f },
|
||||
{ -10925.828f, -2102.425f, 92.180f },
|
||||
{ -10933.089f, -2088.5017f, 92.180f },
|
||||
{ -10947.59f, -2082.8147f, 92.180f },
|
||||
{ -10960.912f, -2090.4368f, 92.179f },
|
||||
{ -10966.017f, -2105.288f, 92.175f },
|
||||
{ -10959.242f, -2119.6172f, 92.180f },
|
||||
{ -10944.495f, -2123.857f, 92.180f },
|
||||
};
|
||||
// Attumen the Huntsman
|
||||
std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
|
||||
// Big Bad Wolf
|
||||
std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
|
||||
// Netherspite
|
||||
std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
|
||||
std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
|
||||
std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
// Nightbane
|
||||
std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
|
||||
std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
|
||||
std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
|
||||
std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
|
||||
std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
|
||||
|
||||
const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION = Position(-10913.391f, -1773.508f, 90.477f);
|
||||
const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] =
|
||||
{
|
||||
{ -10875.456f, -1779.036f, 90.477f },
|
||||
{ -10872.281f, -1751.638f, 90.477f },
|
||||
{ -10910.492f, -1747.401f, 90.477f },
|
||||
{ -10913.391f, -1773.508f, 90.477f },
|
||||
};
|
||||
|
||||
const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.645f, 165.765f);
|
||||
|
||||
void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f };
|
||||
const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
|
||||
{
|
||||
return;
|
||||
}
|
||||
{ -10931.178f, -2116.580f, 92.179f },
|
||||
{ -10925.828f, -2102.425f, 92.180f },
|
||||
{ -10933.089f, -2088.502f, 92.180f },
|
||||
{ -10947.590f, -2082.815f, 92.180f },
|
||||
{ -10960.912f, -2090.437f, 92.179f },
|
||||
{ -10966.017f, -2105.288f, 92.175f },
|
||||
{ -10959.242f, -2119.617f, 92.180f },
|
||||
{ -10944.495f, -2123.857f, 92.180f },
|
||||
};
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f };
|
||||
const Position BIG_BAD_WOLF_RUN_POSITION[4] =
|
||||
{
|
||||
constexpr uint8_t skullIconId = 7;
|
||||
ObjectGuid skullGuid = group->GetTargetIcon(skullIconId);
|
||||
{ -10875.456f, -1779.036f, 90.477f },
|
||||
{ -10872.281f, -1751.638f, 90.477f },
|
||||
{ -10910.492f, -1747.401f, 90.477f },
|
||||
{ -10913.391f, -1773.508f, 90.477f },
|
||||
};
|
||||
|
||||
if (skullGuid != target->GetGUID())
|
||||
const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f };
|
||||
|
||||
const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs
|
||||
const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f };
|
||||
const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f };
|
||||
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
|
||||
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
|
||||
|
||||
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector<Unit*>& units)
|
||||
{
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
if (unit && unit->IsAlive())
|
||||
{
|
||||
return unit;
|
||||
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
|
||||
if (currentGuid != target->GetGUID())
|
||||
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry)
|
||||
{
|
||||
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||
|
||||
for (auto const& npcGuid : npcs)
|
||||
void MarkTargetWithSkull(Player* bot, Unit* target)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
|
||||
}
|
||||
|
||||
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
|
||||
void MarkTargetWithSquare(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithStar(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithCircle(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
|
||||
}
|
||||
|
||||
void MarkTargetWithMoon(Player* bot, Unit* target)
|
||||
{
|
||||
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
|
||||
}
|
||||
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
|
||||
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
|
||||
|
||||
if (currentRti != rtiName || currentTarget != target)
|
||||
{
|
||||
return unit;
|
||||
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
|
||||
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
// Only one bot is needed to set/reset instance-wide timers
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
|
||||
if (!member || !member->IsAlive() || member == bot)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bot->GetExactDist2d(member) < radius)
|
||||
{
|
||||
return member;
|
||||
Player* member = ref->GetSource();
|
||||
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
|
||||
return member == bot;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsFlameWreathActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
|
||||
if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH)
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive())
|
||||
if (unit && unit->IsAlive())
|
||||
return unit;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
|
||||
{
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
|
||||
return unit;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
|
||||
{
|
||||
Unit* nearestPlayer = nullptr;
|
||||
float nearestDistance = radius;
|
||||
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (member->HasAura(SPELL_AURA_FLAME_WREATH))
|
||||
{
|
||||
return true;
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || member == bot)
|
||||
continue;
|
||||
|
||||
float distance = bot->GetExactDist2d(member);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestPlayer = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearestPlayer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Red beam blockers: tank bots, no Nether Exhaustion Red
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetRedBlockers()
|
||||
{
|
||||
std::vector<Player*> redBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
|
||||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
redBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();
|
||||
Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
|
||||
|
||||
return redBlockers;
|
||||
}
|
||||
if (currentSpell && currentSpell->m_spellInfo &&
|
||||
currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST)
|
||||
return true;
|
||||
|
||||
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and ≤25 stacks of Blue Beam debuff
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetBlueBlockers()
|
||||
{
|
||||
std::vector<Player*> blueBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool isDps = botAI->IsDps(member);
|
||||
bool isWarrior = member->getClass() == CLASS_WARRIOR;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
|
||||
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
|
||||
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26;
|
||||
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
|
||||
{
|
||||
blueBlockers.push_back(member);
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
if (member->HasAura(SPELL_FLAME_WREATH_AURA))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return blueBlockers;
|
||||
}
|
||||
|
||||
// Green beam blockers:
|
||||
// (1) Rogue and non-tank Warrior bots, no Nether Exhaustion Green
|
||||
// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff
|
||||
std::vector<Player*> RaidKarazhanHelpers::GetGreenBlockers()
|
||||
{
|
||||
std::vector<Player*> greenBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
// Red beam blockers: tank bots, no Nether Exhaustion Red
|
||||
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
std::vector<Player*> redBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
|
||||
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 26;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
|
||||
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
|
||||
bool isHealer = botAI->IsHeal(member);
|
||||
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
|
||||
if (eligibleRogueWarrior || eligibleHealer)
|
||||
{
|
||||
greenBlockers.push_back(member);
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
|
||||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
|
||||
continue;
|
||||
|
||||
redBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return redBlockers;
|
||||
}
|
||||
|
||||
return greenBlockers;
|
||||
}
|
||||
|
||||
Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss)
|
||||
{
|
||||
float bx = boss->GetPositionX();
|
||||
float by = boss->GetPositionY();
|
||||
float bz = boss->GetPositionZ();
|
||||
float px = portal->GetPositionX();
|
||||
float py = portal->GetPositionY();
|
||||
|
||||
float dx = px - bx;
|
||||
float dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length == 0.0f)
|
||||
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff
|
||||
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
return Position(bx, by, bz);
|
||||
std::vector<Player*> blueBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
|
||||
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
|
||||
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24;
|
||||
|
||||
bool isDps = botAI->IsDps(member);
|
||||
bool isWarrior = member->getClass() == CLASS_WARRIOR;
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
|
||||
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
|
||||
blueBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return blueBlockers;
|
||||
}
|
||||
|
||||
dx /= length;
|
||||
dy /= length;
|
||||
float targetX = bx + dx * distanceFromBoss;
|
||||
float targetY = by + dy * distanceFromBoss;
|
||||
float targetZ = bz;
|
||||
|
||||
return Position(targetX, targetY, targetZ);
|
||||
}
|
||||
|
||||
std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlockers()
|
||||
{
|
||||
static ObjectGuid currentRedBlocker;
|
||||
static ObjectGuid currentGreenBlocker;
|
||||
static ObjectGuid currentBlueBlocker;
|
||||
|
||||
Player* redBlocker = nullptr;
|
||||
Player* greenBlocker = nullptr;
|
||||
Player* blueBlocker = nullptr;
|
||||
|
||||
std::vector<Player*> redBlockers = GetRedBlockers();
|
||||
if (!redBlockers.empty())
|
||||
// Green beam blockers:
|
||||
// (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green
|
||||
// (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff
|
||||
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p)
|
||||
std::vector<Player*> greenBlockers;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
return p && p->GetGUID() == currentRedBlocker;
|
||||
});
|
||||
if (it != redBlockers.end())
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
bool isRogue = member->getClass() == CLASS_ROGUE;
|
||||
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
|
||||
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
|
||||
|
||||
if (eligibleRogueWarrior)
|
||||
greenBlockers.push_back(member);
|
||||
}
|
||||
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
|
||||
continue;
|
||||
|
||||
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
|
||||
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
|
||||
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24;
|
||||
bool isHealer = botAI->IsHeal(member);
|
||||
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
|
||||
|
||||
if (eligibleHealer)
|
||||
greenBlockers.push_back(member);
|
||||
}
|
||||
}
|
||||
|
||||
return greenBlockers;
|
||||
}
|
||||
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
static ObjectGuid currentRedBlocker;
|
||||
static ObjectGuid currentGreenBlocker;
|
||||
static ObjectGuid currentBlueBlocker;
|
||||
|
||||
Player* redBlocker = nullptr;
|
||||
Player* greenBlocker = nullptr;
|
||||
Player* blueBlocker = nullptr;
|
||||
|
||||
std::vector<Player*> redBlockers = GetRedBlockers(botAI, bot);
|
||||
if (!redBlockers.empty())
|
||||
{
|
||||
redBlocker = *it;
|
||||
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player)
|
||||
{
|
||||
return player && player->GetGUID() == currentRedBlocker;
|
||||
});
|
||||
|
||||
if (it != redBlockers.end())
|
||||
redBlocker = *it;
|
||||
else
|
||||
redBlocker = redBlockers.front();
|
||||
|
||||
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
redBlocker = redBlockers.front();
|
||||
currentRedBlocker = ObjectGuid::Empty;
|
||||
redBlocker = nullptr;
|
||||
}
|
||||
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRedBlocker = ObjectGuid::Empty;
|
||||
redBlocker = nullptr;
|
||||
}
|
||||
|
||||
std::vector<Player*> greenBlockers = GetGreenBlockers();
|
||||
if (!greenBlockers.empty())
|
||||
{
|
||||
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p)
|
||||
std::vector<Player*> greenBlockers = GetGreenBlockers(botAI, bot);
|
||||
if (!greenBlockers.empty())
|
||||
{
|
||||
return p && p->GetGUID() == currentGreenBlocker;
|
||||
});
|
||||
if (it != greenBlockers.end())
|
||||
{
|
||||
greenBlocker = *it;
|
||||
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player)
|
||||
{
|
||||
return player && player->GetGUID() == currentGreenBlocker;
|
||||
});
|
||||
|
||||
if (it != greenBlockers.end())
|
||||
greenBlocker = *it;
|
||||
else
|
||||
greenBlocker = greenBlockers.front();
|
||||
|
||||
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
greenBlocker = greenBlockers.front();
|
||||
currentGreenBlocker = ObjectGuid::Empty;
|
||||
greenBlocker = nullptr;
|
||||
}
|
||||
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentGreenBlocker = ObjectGuid::Empty;
|
||||
greenBlocker = nullptr;
|
||||
}
|
||||
|
||||
std::vector<Player*> blueBlockers = GetBlueBlockers();
|
||||
std::vector<Player*> blueBlockers = GetBlueBlockers(botAI, bot);
|
||||
if (!blueBlockers.empty())
|
||||
{
|
||||
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p)
|
||||
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player)
|
||||
{
|
||||
return p && p->GetGUID() == currentBlueBlocker;
|
||||
return player && player->GetGUID() == currentBlueBlocker;
|
||||
});
|
||||
|
||||
if (it != blueBlockers.end())
|
||||
{
|
||||
blueBlocker = *it;
|
||||
}
|
||||
else
|
||||
{
|
||||
blueBlocker = blueBlockers.front();
|
||||
}
|
||||
|
||||
currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty;
|
||||
}
|
||||
else
|
||||
@@ -319,91 +359,132 @@ std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlocker
|
||||
blueBlocker = nullptr;
|
||||
}
|
||||
|
||||
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
|
||||
}
|
||||
|
||||
std::vector<Unit*> RaidKarazhanHelpers::GetAllVoidZones()
|
||||
{
|
||||
std::vector<Unit*> voidZones;
|
||||
const float radius = 30.0f;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float dist = bot->GetExactDist2d(unit);
|
||||
if (dist < radius)
|
||||
{
|
||||
voidZones.push_back(unit);
|
||||
}
|
||||
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
|
||||
}
|
||||
|
||||
return voidZones;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius)
|
||||
{
|
||||
for (Unit* hazard : hazards)
|
||||
std::vector<Unit*> GetAllVoidZones(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2));
|
||||
if (dist < hazardRadius)
|
||||
std::vector<Unit*> voidZones;
|
||||
const float radius = 30.0f;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
return false;
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
|
||||
continue;
|
||||
|
||||
float dist = bot->GetExactDist2d(unit);
|
||||
if (dist < radius)
|
||||
voidZones.push_back(unit);
|
||||
}
|
||||
|
||||
return voidZones;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Unit*> RaidKarazhanHelpers::GetSpawnedInfernals() const
|
||||
{
|
||||
std::vector<Unit*> infernals;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
bool IsSafePosition(float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
|
||||
for (Unit* hazard : hazards)
|
||||
{
|
||||
infernals.push_back(unit);
|
||||
float dist = hazard->GetExactDist2d(x, y);
|
||||
if (dist < hazardRadius)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return infernals;
|
||||
}
|
||||
|
||||
bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards, float hazardRadius, float stepSize)
|
||||
{
|
||||
float sx = start.GetPositionX();
|
||||
float sy = start.GetPositionY();
|
||||
float sz = start.GetPositionZ();
|
||||
float tx = target.GetPositionX();
|
||||
float ty = target.GetPositionY();
|
||||
float tz = target.GetPositionZ();
|
||||
float totalDist = std::sqrt(std::pow(tx - sx, 2) + std::pow(ty - sy, 2));
|
||||
if (totalDist == 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
|
||||
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI)
|
||||
{
|
||||
float t = checkDist / totalDist;
|
||||
float checkX = sx + (tx - sx) * t;
|
||||
float checkY = sy + (ty - sy) * t;
|
||||
float checkZ = sz + (tz - sz) * t;
|
||||
for (Unit* hazard : hazards)
|
||||
std::vector<Unit*> infernals;
|
||||
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
for (auto const& npcGuid : npcs)
|
||||
{
|
||||
float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2));
|
||||
if (hazardDist < hazardRadius)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Unit* unit = botAI->GetUnit(npcGuid);
|
||||
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
|
||||
infernals.push_back(unit);
|
||||
}
|
||||
|
||||
return infernals;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards,
|
||||
float hazardRadius, float stepSize)
|
||||
{
|
||||
float sx = start.GetPositionX();
|
||||
float sy = start.GetPositionY();
|
||||
float sz = start.GetPositionZ();
|
||||
float tx = target.GetPositionX();
|
||||
float ty = target.GetPositionY();
|
||||
float tz = target.GetPositionZ();
|
||||
|
||||
const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY());
|
||||
if (totalDist == 0.0f)
|
||||
return true;
|
||||
|
||||
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
|
||||
{
|
||||
float t = checkDist / totalDist;
|
||||
float checkX = sx + (tx - sx) * t;
|
||||
float checkY = sy + (ty - sy) * t;
|
||||
float checkZ = sz + (tz - sz) * t;
|
||||
for (Unit* hazard : hazards)
|
||||
{
|
||||
const float hx = checkX - hazard->GetPositionX();
|
||||
const float hy = checkY - hazard->GetPositionY();
|
||||
if ((hx*hx + hy*hy) < hazardRadius * hazardRadius)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryFindSafePositionWithSafePath(
|
||||
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
|
||||
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
|
||||
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ)
|
||||
{
|
||||
float bestMoveDist = std::numeric_limits<float>::max();
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < numAngles; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numAngles;
|
||||
float dx = cos(angle);
|
||||
float dy = sin(angle);
|
||||
|
||||
for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize)
|
||||
{
|
||||
float x = centerX + dx * dist;
|
||||
float y = centerY + dy * dist;
|
||||
float z = centerZ;
|
||||
float destX = x, destY = y, destZ = z;
|
||||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ,
|
||||
destX, destY, destZ, true))
|
||||
continue;
|
||||
|
||||
if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance))
|
||||
continue;
|
||||
|
||||
if (requireSafePath)
|
||||
{
|
||||
if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ),
|
||||
hazards, safeDistance, stepSize))
|
||||
continue;
|
||||
}
|
||||
|
||||
const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY);
|
||||
if (moveDist < bestMoveDist)
|
||||
{
|
||||
bestMoveDist = moveDist;
|
||||
bestDestX = destX;
|
||||
bestDestY = destY;
|
||||
bestDestZ = destZ;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +1,136 @@
|
||||
#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_
|
||||
#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_
|
||||
|
||||
#include <ctime>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "AiObject.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
#include "Unit.h"
|
||||
|
||||
enum KarazhanSpells
|
||||
namespace KarazhanHelpers
|
||||
{
|
||||
// Maiden of Virtue
|
||||
SPELL_REPENTANCE = 29511,
|
||||
enum KarazhanSpells
|
||||
{
|
||||
// Maiden of Virtue
|
||||
SPELL_REPENTANCE = 29511,
|
||||
|
||||
// Opera Event
|
||||
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
|
||||
// Opera Event
|
||||
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
|
||||
|
||||
// Shade of Aran
|
||||
SPELL_FLAME_WREATH = 30004,
|
||||
SPELL_AURA_FLAME_WREATH = 29946,
|
||||
SPELL_ARCANE_EXPLOSION = 29973,
|
||||
SPELL_WARLOCK_BANISH = 18647, // Rank 2
|
||||
// The Curator
|
||||
SPELL_CURATOR_EVOCATION = 30254,
|
||||
|
||||
// Netherspite
|
||||
SPELL_GREEN_BEAM_DEBUFF = 30422,
|
||||
SPELL_BLUE_BEAM_DEBUFF = 30423,
|
||||
SPELL_NETHER_EXHAUSTION_RED = 38637,
|
||||
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
|
||||
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
|
||||
SPELL_NETHERSPITE_BANISHED = 39833,
|
||||
// Shade of Aran
|
||||
SPELL_FLAME_WREATH_CAST = 30004,
|
||||
SPELL_FLAME_WREATH_AURA = 29946,
|
||||
SPELL_ARCANE_EXPLOSION = 29973,
|
||||
|
||||
// Prince Malchezaar
|
||||
SPELL_ENFEEBLE = 30843,
|
||||
};
|
||||
// Netherspite
|
||||
SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura)
|
||||
SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura)
|
||||
SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura)
|
||||
SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura)
|
||||
SPELL_NETHER_EXHAUSTION_RED = 38637,
|
||||
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
|
||||
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
|
||||
SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black"
|
||||
|
||||
// Prince Malchezaar
|
||||
SPELL_ENFEEBLE = 30843,
|
||||
|
||||
// Nightbane
|
||||
SPELL_CHARRED_EARTH = 30129,
|
||||
SPELL_BELLOWING_ROAR = 36922,
|
||||
SPELL_RAIN_OF_BONES = 37091,
|
||||
|
||||
// Warlock
|
||||
SPELL_WARLOCK_BANISH = 18647,
|
||||
|
||||
// Priest
|
||||
SPELL_FEAR_WARD = 6346,
|
||||
};
|
||||
|
||||
enum KarazhanNPCs
|
||||
{
|
||||
// Trash
|
||||
NPC_SPECTRAL_RETAINER = 16410,
|
||||
NPC_MANA_WARP = 16530,
|
||||
|
||||
// Attumen the Huntsman
|
||||
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
|
||||
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
|
||||
|
||||
// Shade of Aran
|
||||
NPC_CONJURED_ELEMENTAL = 17167,
|
||||
|
||||
// Netherspite
|
||||
NPC_VOID_ZONE = 16697,
|
||||
NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity <Healing Portal>"
|
||||
NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance <Damage Portal>"
|
||||
NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance <Tanking Portal>"
|
||||
|
||||
// Prince Malchezaar
|
||||
NPC_NETHERSPITE_INFERNAL = 17646,
|
||||
};
|
||||
|
||||
const uint32 KARAZHAN_MAP_ID = 532;
|
||||
const float NIGHTBANE_FLIGHT_Z = 95.0f;
|
||||
|
||||
enum KarazhanNpcs
|
||||
{
|
||||
// Attumen the Huntsman
|
||||
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
|
||||
|
||||
// Terestian Illhoof
|
||||
NPC_KILREK = 17229,
|
||||
NPC_DEMON_CHAINS = 17248,
|
||||
|
||||
// Shade of Aran
|
||||
NPC_CONJURED_ELEMENTAL = 17167,
|
||||
|
||||
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
|
||||
// Big Bad Wolf
|
||||
extern std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
|
||||
// Netherspite
|
||||
NPC_VOID_ZONE = 16697,
|
||||
NPC_RED_PORTAL = 17369,
|
||||
NPC_BLUE_PORTAL = 17368,
|
||||
NPC_GREEN_PORTAL = 17367,
|
||||
extern std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
|
||||
extern std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
|
||||
extern std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
// Nightbane
|
||||
extern std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
|
||||
extern std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
|
||||
extern std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
|
||||
extern std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
|
||||
extern std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
|
||||
|
||||
// Prince Malchezaar
|
||||
NPC_NETHERSPITE_INFERNAL = 17646,
|
||||
};
|
||||
extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION;
|
||||
extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
|
||||
extern const Position BIG_BAD_WOLF_BOSS_POSITION;
|
||||
extern const Position BIG_BAD_WOLF_RUN_POSITION[4];
|
||||
extern const Position THE_CURATOR_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_FINAL_BOSS_POSITION;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION1;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION2;
|
||||
extern const Position NIGHTBANE_RANGED_POSITION3;
|
||||
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
|
||||
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
|
||||
|
||||
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION;
|
||||
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
|
||||
extern const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION;
|
||||
extern const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4];
|
||||
extern const Position KARAZHAN_THE_CURATOR_BOSS_POSITION;
|
||||
|
||||
class RaidKarazhanHelpers : public AiObject
|
||||
{
|
||||
public:
|
||||
explicit RaidKarazhanHelpers(PlayerbotAI* botAI) : AiObject(botAI) {}
|
||||
|
||||
void MarkTargetWithSkull(Unit* /*target*/);
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& /*units*/);
|
||||
Unit* GetFirstAliveUnitByEntry(uint32 /*entry*/);
|
||||
Unit* GetNearestPlayerInRadius(float /*radius*/ = 5.0f);
|
||||
bool IsFlameWreathActive();
|
||||
Position GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss);
|
||||
std::vector<Player*> GetRedBlockers();
|
||||
std::vector<Player*> GetBlueBlockers();
|
||||
std::vector<Player*> GetGreenBlockers();
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers();
|
||||
std::vector<Unit*> GetAllVoidZones();
|
||||
bool IsSafePosition (float x, float y, float z,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius);
|
||||
std::vector<Unit*> GetSpawnedInfernals() const;
|
||||
bool IsStraightPathSafe(const Position& start, const Position& target,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
|
||||
};
|
||||
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
|
||||
void MarkTargetWithSkull(Player* bot, Unit* target);
|
||||
void MarkTargetWithSquare(Player* bot, Unit* target);
|
||||
void MarkTargetWithStar(Player* bot, Unit* target);
|
||||
void MarkTargetWithCircle(Player* bot, Unit* target);
|
||||
void MarkTargetWithMoon(Player* bot, Unit* target);
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
|
||||
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
|
||||
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
|
||||
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot);
|
||||
std::vector<Unit*> GetAllVoidZones(PlayerbotAI *botAI, Player* bot);
|
||||
bool IsSafePosition (float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius);
|
||||
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI);
|
||||
bool IsStraightPathSafe(
|
||||
const Position& start, const Position& target,
|
||||
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
|
||||
bool TryFindSafePositionWithSafePath(
|
||||
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
|
||||
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
|
||||
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,265 +1,362 @@
|
||||
#include "RaidKarazhanMultipliers.h"
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "RaidKarazhanHelpers.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "AttackAction.h"
|
||||
#include "DruidBearActions.h"
|
||||
#include "DruidCatActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "DruidActions.h"
|
||||
#include "FollowActions.h"
|
||||
#include "GenericActions.h"
|
||||
#include "HunterActions.h"
|
||||
#include "MageActions.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PriestActions.h"
|
||||
#include "ReachTargetActions.h"
|
||||
#include "RogueActions.h"
|
||||
#include "WarriorActions.h"
|
||||
#include "ShamanActions.h"
|
||||
|
||||
static bool IsChargeAction(Action* action)
|
||||
{
|
||||
return dynamic_cast<CastChargeAction*>(action) ||
|
||||
dynamic_cast<CastInterceptAction*>(action) ||
|
||||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
|
||||
dynamic_cast<CastFeralChargeCatAction*>(action);
|
||||
}
|
||||
using namespace KarazhanHelpers;
|
||||
|
||||
float KarazhanAttumenTheHuntsmanMultiplier::GetValue(Action* action)
|
||||
// Keep tanks from jumping back and forth between Attumen and Midnight
|
||||
float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action)
|
||||
{
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) &&
|
||||
(dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<KarazhanAttumenTheHuntsmanStackBehindAction*>(action)))
|
||||
{
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
if (!midnight)
|
||||
return 1.0f;
|
||||
|
||||
Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman");
|
||||
if (!attumen)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Try to get rid of jittering when bots are stacked behind Attumen
|
||||
float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (!attumenMounted)
|
||||
return 1.0f;
|
||||
|
||||
if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanBigBadWolfMultiplier::GetValue(Action* action)
|
||||
// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight
|
||||
// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting
|
||||
float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
if (!boss)
|
||||
{
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
if (!attumenMounted)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 8;
|
||||
|
||||
auto it = attumenDpsWaitTimer.find(instanceId);
|
||||
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<KarazhanBigBadWolfRunAwayAction*>(action)) ||
|
||||
(dynamic_cast<AttackAction*>(action)))
|
||||
if (!botAI->IsMainTank(bot))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanShadeOfAranMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!boss)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
|
||||
{
|
||||
if (IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<MovementAction*>(action))
|
||||
{
|
||||
const float safeDistance = 20.0f;
|
||||
if (bot->GetDistance2d(boss) >= safeDistance)
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH);
|
||||
if (!flameWreathActive && bot->GetGroup())
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
|
||||
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (!curator)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Save Bloodlust/Heroism for Evocation (100% increased damage)
|
||||
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (!curator)
|
||||
return 1.0f;
|
||||
|
||||
if (!curator->HasAura(SPELL_CURATOR_EVOCATION))
|
||||
{
|
||||
for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
if (dynamic_cast<CastBloodlustAction*>(action) ||
|
||||
dynamic_cast<CastHeroismAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Don't charge back in when running from Arcane Explosion
|
||||
float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!aran)
|
||||
return 1.0f;
|
||||
|
||||
if (aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
|
||||
{
|
||||
if (dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
if (bot->GetDistance2d(aran) >= 20.0f)
|
||||
{
|
||||
Player* member = itr->GetSource();
|
||||
if (member && member->HasAura(SPELL_AURA_FLAME_WREATH))
|
||||
{
|
||||
flameWreathActive = true;
|
||||
break;
|
||||
}
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<FollowAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<AvoidAoeAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
if (flameWreathActive)
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// I will not move when Flame Wreath is cast or the raid blows up
|
||||
float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!aran)
|
||||
return 1.0f;
|
||||
|
||||
if (IsFlameWreathActive(botAI, bot))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<FollowAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<AvoidAoeAction*>(action) ||
|
||||
dynamic_cast<CastKillingSpreeAction*>(action) ||
|
||||
dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Try to rid of the jittering when blocking beams
|
||||
float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return 1.0f;
|
||||
|
||||
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
|
||||
|
||||
if (bot == redBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (bot == blueBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (bot == greenBlocker)
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
|
||||
dynamic_cast<ReachTargetAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action) ||
|
||||
dynamic_cast<CastKillingSpreeAction*>(action) ||
|
||||
dynamic_cast<CastReachTargetSpellAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Give tanks 5 seconds to get aggro during phase transitions
|
||||
float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return 1.0f;
|
||||
|
||||
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 5;
|
||||
|
||||
auto it = netherspiteDpsWaitTimer.find(instanceId);
|
||||
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsTank(bot))
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action)
|
||||
// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance
|
||||
float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<CastKillingSpreeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers();
|
||||
bool isBlocker = (bot == greenBlocker || bot == blueBlocker);
|
||||
if (isBlocker)
|
||||
{
|
||||
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
|
||||
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
|
||||
bool inBeam = false;
|
||||
for (Unit* portal : {bluePortal, greenPortal})
|
||||
{
|
||||
if (!portal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float bx = boss->GetPositionX(), by = boss->GetPositionY();
|
||||
float px = portal->GetPositionX(), py = portal->GetPositionY();
|
||||
float dx = px - bx, dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length == 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dx /= length; dy /= length;
|
||||
float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by;
|
||||
float t = (botdx * dx + botdy * dy);
|
||||
float beamX = bx + dx * t, beamY = by + dy * t;
|
||||
float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2));
|
||||
if (distToBeam < 0.3f && t > 0.0f && t < length)
|
||||
{
|
||||
inBeam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inBeam)
|
||||
{
|
||||
std::vector<Unit*> voidZones = karazhanHelper.GetAllVoidZones();
|
||||
bool inVoidZone = false;
|
||||
for (Unit* vz : voidZones)
|
||||
{
|
||||
if (bot->GetExactDist2d(vz) < 4.0f)
|
||||
{
|
||||
inVoidZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inVoidZone)
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Don't run back into Shadow Nova when Enfeebled
|
||||
float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
|
||||
if (bot->HasAura(SPELL_ENFEEBLE))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
RaidKarazhanHelpers karazhanHelper(botAI);
|
||||
auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers();
|
||||
static std::map<ObjectGuid, uint32> beamMoveTimes;
|
||||
static std::map<ObjectGuid, bool> lastBeamMoveSideways;
|
||||
ObjectGuid botGuid = bot->GetGUID();
|
||||
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
|
||||
if (bot == redBlocker && boss && redPortal)
|
||||
{
|
||||
Position blockingPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f);
|
||||
float bx = boss->GetPositionX();
|
||||
float by = boss->GetPositionY();
|
||||
float px = redPortal->GetPositionX();
|
||||
float py = redPortal->GetPositionY();
|
||||
float dx = px - bx;
|
||||
float dy = py - by;
|
||||
float length = sqrt(dx*dx + dy*dy);
|
||||
if (length != 0.0f)
|
||||
{
|
||||
dx /= length;
|
||||
dy /= length;
|
||||
float perpDx = -dy;
|
||||
float perpDy = dx;
|
||||
Position sidewaysPos(blockingPos.GetPositionX() + perpDx * 3.0f,
|
||||
blockingPos.GetPositionY() + perpDy * 3.0f,
|
||||
blockingPos.GetPositionZ());
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
uint32 intervalSecs = 5;
|
||||
if (beamMoveTimes[botGuid] == 0)
|
||||
{
|
||||
beamMoveTimes[botGuid] = time(nullptr);
|
||||
lastBeamMoveSideways[botGuid] = false;
|
||||
}
|
||||
if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs)
|
||||
{
|
||||
lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid];
|
||||
beamMoveTimes[botGuid] = time(nullptr);
|
||||
}
|
||||
Position targetPos = lastBeamMoveSideways[botGuid] ? sidewaysPos : blockingPos;
|
||||
float distToTarget = bot->GetExactDist2d(targetPos.GetPositionX(), targetPos.GetPositionY());
|
||||
const float positionTolerance = 1.5f;
|
||||
if (distToTarget < positionTolerance)
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
// Wait until Phase 3 to use Bloodlust/Heroism
|
||||
float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!malchezaar)
|
||||
return 1.0f;
|
||||
|
||||
if (malchezaar->GetHealthPct() > 30.0f)
|
||||
{
|
||||
if (dynamic_cast<CastBloodlustAction*>(action) ||
|
||||
dynamic_cast<CastHeroismAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Pets tend to run out of bounds and cause skeletons to spawn off the map
|
||||
// Pets also tend to pull adds from inside of the tower through the floor
|
||||
// This multiplier DOES NOT impact Hunter and Warlock pets
|
||||
// Hunter and Warlock pets are addressed in ControlPetAggressionAction
|
||||
float NightbaneDisablePetsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
|
||||
if (dynamic_cast<CastForceOfNatureAction*>(action) ||
|
||||
dynamic_cast<CastFeralSpiritAction*>(action) ||
|
||||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
|
||||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
|
||||
dynamic_cast<CastSummonWaterElementalAction*>(action) ||
|
||||
dynamic_cast<CastShadowfiendAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
|
||||
{
|
||||
if (dynamic_cast<PetAttackAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Give the main tank 8 seconds to get aggro during phase transitions
|
||||
float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
|
||||
return 1.0f;
|
||||
|
||||
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 dpsWaitSeconds = 8;
|
||||
|
||||
auto it = nightbaneDpsWaitTimer.find(instanceId);
|
||||
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
{
|
||||
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
|
||||
!dynamic_cast<CastHealingSpellAction*>(action)))
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action)
|
||||
// The "avoid aoe" strategy must be disabled for the main tank
|
||||
// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid
|
||||
// It is also disabled for all bots during the flight phase
|
||||
float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!boss || !boss->IsAlive())
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
|
||||
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (botAI->IsRanged(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
|
||||
(dynamic_cast<MovementAction*>(action) &&
|
||||
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action)))
|
||||
{
|
||||
return 0.0f;
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Disable some movement actions that conflict with the strategies
|
||||
float NightbaneDisableMovementMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return 1.0f;
|
||||
|
||||
if (dynamic_cast<CastBlinkBackAction*>(action) ||
|
||||
dynamic_cast<CastDisengageAction*>(action) ||
|
||||
dynamic_cast<FleeAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
// Disable CombatFormationMoveAction for all bots except:
|
||||
// (1) main tank and (2) only during the ground phase, other melee
|
||||
if (botAI->IsRanged(bot) ||
|
||||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
|
||||
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
|
||||
{
|
||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
|
||||
@@ -3,45 +3,131 @@
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {}
|
||||
AttumenTheHuntsmanDisableTankAssistMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {}
|
||||
AttumenTheHuntsmanStayStackedMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranMultiplier : public Multiplier
|
||||
class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {}
|
||||
AttumenTheHuntsmanWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier
|
||||
class TheCuratorDisableTankAssistMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {}
|
||||
TheCuratorDisableTankAssistMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteRedBeamMultiplier : public Multiplier
|
||||
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {}
|
||||
TheCuratorDelayBloodlustAndHeroismMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarMultiplier : public Multiplier
|
||||
class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {}
|
||||
ShadeOfAranArcaneExplosionDisableChargeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
ShadeOfAranFlameWreathDisableMovementMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NetherspiteKeepBlockingBeamMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NetherspiteKeepBlockingBeamMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NetherspiteWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NetherspiteWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarDisableAvoidAoeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarEnfeebleKeepDistanceMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisablePetsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisablePetsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneWaitForDpsMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneWaitForDpsMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisableAvoidAoeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisableAvoidAoeMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class NightbaneDisableMovementMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NightbaneDisableMovementMultiplier(
|
||||
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {}
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,79 +3,160 @@
|
||||
|
||||
void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan attumen the huntsman", NextAction::array(0,
|
||||
new NextAction("karazhan attumen the huntsman stack behind", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Trash
|
||||
triggers.push_back(new TriggerNode("mana warp is about to explode",
|
||||
NextAction::array(0, new NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan moroes", NextAction::array(0,
|
||||
new NextAction("karazhan moroes mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Attumen the Huntsman
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman need target priority",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman split bosses", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman stack behind", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting",
|
||||
NextAction::array(0, new NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan maiden of virtue", NextAction::array(0,
|
||||
new NextAction("karazhan maiden of virtue position ranged", ACTION_RAID + 1),
|
||||
new NextAction("karazhan maiden of virtue position boss", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Moroes
|
||||
triggers.push_back(new TriggerNode("moroes boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("moroes main tank attack boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("moroes need target priority",
|
||||
NextAction::array(0, new NextAction("moroes mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan big bad wolf", NextAction::array(0,
|
||||
new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan big bad wolf position boss", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Maiden of Virtue
|
||||
triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance",
|
||||
NextAction::array(0, new NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage",
|
||||
NextAction::array(0, new NextAction("maiden of virtue position ranged", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan romulo and julianne", NextAction::array(0,
|
||||
new NextAction("karazhan romulo and julianne mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Big Bad Wolf
|
||||
triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood",
|
||||
NextAction::array(0, new NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank",
|
||||
NextAction::array(0, new NextAction("big bad wolf position boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan wizard of oz", NextAction::array(0,
|
||||
new NextAction("karazhan wizard of oz scorch strawman", ACTION_RAID + 2),
|
||||
new NextAction("karazhan wizard of oz mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Romulo and Julianne
|
||||
triggers.push_back(new TriggerNode("romulo and julianne both bosses revived",
|
||||
NextAction::array(0, new NextAction("romulo and julianne mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan the curator", NextAction::array(0,
|
||||
new NextAction("karazhan the curator spread ranged", ACTION_RAID + 2),
|
||||
new NextAction("karazhan the curator position boss", ACTION_RAID + 2),
|
||||
new NextAction("karazhan the curator mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Wizard of Oz
|
||||
triggers.push_back(new TriggerNode("wizard of oz need target priority",
|
||||
NextAction::array(0, new NextAction("wizard of oz mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire",
|
||||
NextAction::array(0, new NextAction("wizard of oz scorch strawman", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan terestian illhoof", NextAction::array(0,
|
||||
new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// The Curator
|
||||
triggers.push_back(new TriggerNode("the curator astral flare spawned",
|
||||
NextAction::array(0, new NextAction("the curator mark astral flare", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("the curator boss engaged by tanks",
|
||||
NextAction::array(0, new NextAction("the curator position boss", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear",
|
||||
NextAction::array(0, new NextAction("the curator spread ranged", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan shade of aran", NextAction::array(0,
|
||||
new NextAction("karazhan shade of aran flame wreath stop movement", ACTION_EMERGENCY + 7),
|
||||
new NextAction("karazhan shade of aran arcane explosion run away", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan shade of aran spread ranged", ACTION_RAID + 2),
|
||||
new NextAction("karazhan shade of aran mark conjured elemental", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Terestian Illhoof
|
||||
triggers.push_back(new TriggerNode("terestian illhoof need target priority",
|
||||
NextAction::array(0, new NextAction("terestian illhoof mark target", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan netherspite", NextAction::array(0,
|
||||
new NextAction("karazhan netherspite block red beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite block blue beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite block green beam", ACTION_EMERGENCY + 8),
|
||||
new NextAction("karazhan netherspite avoid beam and void zone", ACTION_EMERGENCY + 7),
|
||||
new NextAction("karazhan netherspite banish phase avoid void zone", ACTION_RAID + 1),
|
||||
nullptr)));
|
||||
// Shade of Aran
|
||||
triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting",
|
||||
NextAction::array(0, new NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran flame wreath is active",
|
||||
NextAction::array(0, new NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned",
|
||||
NextAction::array(0, new NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard",
|
||||
NextAction::array(0, new NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"karazhan prince malchezaar", NextAction::array(0,
|
||||
new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6),
|
||||
new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6),
|
||||
nullptr)));
|
||||
// Netherspite
|
||||
triggers.push_back(new TriggerNode("netherspite red beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block red beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite blue beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite green beam is active",
|
||||
NextAction::array(0, new NextAction("netherspite block green beam", ACTION_EMERGENCY + 8), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite bot is not beam blocker",
|
||||
NextAction::array(0, new NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite boss is banished",
|
||||
NextAction::array(0, new NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers",
|
||||
NextAction::array(0, new NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
|
||||
));
|
||||
|
||||
// Prince Malchezaar
|
||||
triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled",
|
||||
NextAction::array(0, new NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned",
|
||||
NextAction::array(0, new NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6), nullptr)
|
||||
));
|
||||
|
||||
// Nightbane
|
||||
triggers.push_back(new TriggerNode("nightbane boss engaged by main tank",
|
||||
NextAction::array(0, new NextAction("nightbane ground phase position boss", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth",
|
||||
NextAction::array(0, new NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear",
|
||||
NextAction::array(0, new NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss",
|
||||
NextAction::array(0, new NextAction("nightbane control pet aggression", ACTION_RAID + 2), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane boss is flying",
|
||||
NextAction::array(0, new NextAction("nightbane flight phase movement", ACTION_RAID + 1), nullptr)
|
||||
));
|
||||
triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers",
|
||||
NextAction::array(0, new NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
|
||||
));
|
||||
}
|
||||
|
||||
void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||
{
|
||||
multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI));
|
||||
multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
|
||||
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
|
||||
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
|
||||
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
|
||||
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));
|
||||
multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI));
|
||||
multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI));
|
||||
multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI));
|
||||
multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class RaidKarazhanStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||
|
||||
std::string const getName() override { return "karazhan"; }
|
||||
|
||||
|
||||
@@ -9,31 +9,255 @@ class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
|
||||
public:
|
||||
RaidKarazhanTriggerContext()
|
||||
{
|
||||
creators["karazhan attumen the huntsman"] = &RaidKarazhanTriggerContext::karazhan_attumen_the_huntsman;
|
||||
creators["karazhan moroes"] = &RaidKarazhanTriggerContext::karazhan_moroes;
|
||||
creators["karazhan maiden of virtue"] = &RaidKarazhanTriggerContext::karazhan_maiden_of_virtue;
|
||||
creators["karazhan big bad wolf"] = &RaidKarazhanTriggerContext::karazhan_big_bad_wolf;
|
||||
creators["karazhan romulo and julianne"] = &RaidKarazhanTriggerContext::karazhan_romulo_and_julianne;
|
||||
creators["karazhan wizard of oz"] = &RaidKarazhanTriggerContext::karazhan_wizard_of_oz;
|
||||
creators["karazhan the curator"] = &RaidKarazhanTriggerContext::karazhan_the_curator;
|
||||
creators["karazhan terestian illhoof"] = &RaidKarazhanTriggerContext::karazhan_terestian_illhoof;
|
||||
creators["karazhan shade of aran"] = &RaidKarazhanTriggerContext::karazhan_shade_of_aran;
|
||||
creators["karazhan netherspite"] = &RaidKarazhanTriggerContext::karazhan_netherspite;
|
||||
creators["karazhan prince malchezaar"] = &RaidKarazhanTriggerContext::karazhan_prince_malchezaar;
|
||||
// Trash
|
||||
creators["mana warp is about to explode"] =
|
||||
&RaidKarazhanTriggerContext::mana_warp_is_about_to_explode;
|
||||
|
||||
// Attumen the Huntsman
|
||||
creators["attumen the huntsman need target priority"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority;
|
||||
|
||||
creators["attumen the huntsman attumen spawned"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned;
|
||||
|
||||
creators["attumen the huntsman attumen is mounted"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted;
|
||||
|
||||
creators["attumen the huntsman boss wipes aggro when mounting"] =
|
||||
&RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting;
|
||||
|
||||
// Moroes
|
||||
creators["moroes boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank;
|
||||
|
||||
creators["moroes need target priority"] =
|
||||
&RaidKarazhanTriggerContext::moroes_need_target_priority;
|
||||
|
||||
// Maiden of Virtue
|
||||
creators["maiden of virtue healers are stunned by repentance"] =
|
||||
&RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance;
|
||||
|
||||
creators["maiden of virtue holy wrath deals chain damage"] =
|
||||
&RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage;
|
||||
|
||||
// The Big Bad Wolf
|
||||
creators["big bad wolf boss engaged by tank"] =
|
||||
&RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank;
|
||||
|
||||
creators["big bad wolf boss is chasing little red riding hood"] =
|
||||
&RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood;
|
||||
|
||||
// Romulo and Julianne
|
||||
creators["romulo and julianne both bosses revived"] =
|
||||
&RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived;
|
||||
|
||||
// The Wizard of Oz
|
||||
creators["wizard of oz need target priority"] =
|
||||
&RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority;
|
||||
|
||||
creators["wizard of oz strawman is vulnerable to fire"] =
|
||||
&RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire;
|
||||
|
||||
// The Curator
|
||||
creators["the curator astral flare spawned"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_astral_flare_spawned;
|
||||
|
||||
creators["the curator boss engaged by tanks"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks;
|
||||
|
||||
creators["the curator astral flares cast arcing sear"] =
|
||||
&RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear;
|
||||
|
||||
// Terestian Illhoof
|
||||
creators["terestian illhoof need target priority"] =
|
||||
&RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority;
|
||||
|
||||
// Shade of Aran
|
||||
creators["shade of aran arcane explosion is casting"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting;
|
||||
|
||||
creators["shade of aran flame wreath is active"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active;
|
||||
|
||||
creators["shade of aran conjured elementals summoned"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned;
|
||||
|
||||
creators["shade of aran boss uses counterspell and blizzard"] =
|
||||
&RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard;
|
||||
|
||||
// Netherspite
|
||||
creators["netherspite red beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_red_beam_is_active;
|
||||
|
||||
creators["netherspite blue beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_blue_beam_is_active;
|
||||
|
||||
creators["netherspite green beam is active"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_green_beam_is_active;
|
||||
|
||||
creators["netherspite bot is not beam blocker"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker;
|
||||
|
||||
creators["netherspite boss is banished"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_boss_is_banished;
|
||||
|
||||
creators["netherspite need to manage timers and trackers"] =
|
||||
&RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers;
|
||||
|
||||
// Prince Malchezaar
|
||||
creators["prince malchezaar bot is enfeebled"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled;
|
||||
|
||||
creators["prince malchezaar infernals are spawned"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned;
|
||||
|
||||
creators["prince malchezaar boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank;
|
||||
|
||||
// Nightbane
|
||||
creators["nightbane boss engaged by main tank"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank;
|
||||
|
||||
creators["nightbane ranged bots are in charred earth"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth;
|
||||
|
||||
creators["nightbane main tank is susceptible to fear"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear;
|
||||
|
||||
creators["nightbane pets ignore collision to chase flying boss"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss;
|
||||
|
||||
creators["nightbane boss is flying"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_boss_is_flying;
|
||||
|
||||
creators["nightbane need to manage timers and trackers"] =
|
||||
&RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers;
|
||||
}
|
||||
|
||||
private:
|
||||
static Trigger* karazhan_attumen_the_huntsman(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanTrigger(botAI); }
|
||||
static Trigger* karazhan_moroes(PlayerbotAI* botAI) { return new KarazhanMoroesTrigger(botAI); }
|
||||
static Trigger* karazhan_maiden_of_virtue(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtueTrigger(botAI); }
|
||||
static Trigger* karazhan_big_bad_wolf(PlayerbotAI* botAI) { return new KarazhanBigBadWolfTrigger(botAI); }
|
||||
static Trigger* karazhan_romulo_and_julianne(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneTrigger(botAI); }
|
||||
static Trigger* karazhan_wizard_of_oz(PlayerbotAI* botAI) { return new KarazhanWizardOfOzTrigger(botAI); }
|
||||
static Trigger* karazhan_the_curator(PlayerbotAI* botAI) { return new KarazhanTheCuratorTrigger(botAI); }
|
||||
static Trigger* karazhan_terestian_illhoof(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofTrigger(botAI); }
|
||||
static Trigger* karazhan_shade_of_aran(PlayerbotAI* botAI) { return new KarazhanShadeOfAranTrigger(botAI); }
|
||||
static Trigger* karazhan_netherspite(PlayerbotAI* botAI) { return new KarazhanNetherspiteTrigger(botAI); }
|
||||
static Trigger* karazhan_prince_malchezaar(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTrigger(botAI); }
|
||||
// Trash
|
||||
static Trigger* mana_warp_is_about_to_explode(
|
||||
PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); }
|
||||
|
||||
// Attumen the Huntsman
|
||||
static Trigger* attumen_the_huntsman_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_attumen_spawned(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_attumen_is_mounted(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); }
|
||||
|
||||
static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting(
|
||||
PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); }
|
||||
|
||||
// Moroes
|
||||
static Trigger* moroes_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
static Trigger* moroes_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
// Maiden of Virtue
|
||||
static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); }
|
||||
|
||||
static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage(
|
||||
PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); }
|
||||
|
||||
// The Big Bad Wolf
|
||||
static Trigger* big_bad_wolf_boss_engaged_by_tank(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); }
|
||||
|
||||
static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood(
|
||||
PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); }
|
||||
|
||||
// Romulo and Julianne
|
||||
static Trigger* romulo_and_julianne_both_bosses_revived(
|
||||
PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); }
|
||||
|
||||
// The Wizard of Oz
|
||||
static Trigger* wizard_of_oz_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire(
|
||||
PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); }
|
||||
|
||||
// The Curator
|
||||
static Trigger* the_curator_astral_flare_spawned(
|
||||
PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* the_curator_boss_engaged_by_tanks(
|
||||
PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); }
|
||||
|
||||
static Trigger* the_curator_astral_flares_cast_arcing_sear(
|
||||
PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); }
|
||||
|
||||
// Terestian Illhoof
|
||||
static Trigger* terestian_illhoof_need_target_priority(
|
||||
PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); }
|
||||
|
||||
// Shade of Aran
|
||||
static Trigger* shade_of_aran_arcane_explosion_is_casting(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_flame_wreath_is_active(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_conjured_elementals_summoned(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); }
|
||||
|
||||
static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard(
|
||||
PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); }
|
||||
|
||||
// Netherspite
|
||||
static Trigger* netherspite_red_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_blue_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_green_beam_is_active(
|
||||
PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_bot_is_not_beam_blocker(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_boss_is_banished(
|
||||
PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); }
|
||||
|
||||
static Trigger* netherspite_need_to_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); }
|
||||
|
||||
// Prince Malchezaar
|
||||
static Trigger* prince_malchezaar_bot_is_enfeebled(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); }
|
||||
|
||||
static Trigger* prince_malchezaar_infernals_are_spawned(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); }
|
||||
|
||||
static Trigger* prince_malchezaar_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
// Nightbane
|
||||
static Trigger* nightbane_boss_engaged_by_main_tank(
|
||||
PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_ranged_bots_are_in_charred_earth(
|
||||
PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_main_tank_is_susceptible_to_fear(
|
||||
PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss(
|
||||
PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_boss_is_flying(
|
||||
PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); }
|
||||
|
||||
static Trigger* nightbane_need_to_manage_timers_and_trackers(
|
||||
PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -3,17 +3,64 @@
|
||||
#include "RaidKarazhanActions.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool KarazhanAttumenTheHuntsmanTrigger::IsActive()
|
||||
{
|
||||
RaidKarazhanHelpers helpers(botAI);
|
||||
Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
using namespace KarazhanHelpers;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
bool ManaWarpIsAboutToExplodeTrigger::IsActive()
|
||||
{
|
||||
Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp");
|
||||
return manaWarp && manaWarp->GetHealthPct() < 15;
|
||||
}
|
||||
|
||||
bool KarazhanMoroesTrigger::IsActive()
|
||||
bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsHeal(bot))
|
||||
return false;
|
||||
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
return midnight != nullptr;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsAssistTankOfIndex(bot, 0))
|
||||
return false;
|
||||
|
||||
Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN);
|
||||
return attumen != nullptr;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
|
||||
return attumenMounted && attumenMounted->GetVictim() != bot;
|
||||
}
|
||||
|
||||
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
|
||||
return midnight != nullptr;
|
||||
}
|
||||
|
||||
bool MoroesBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes");
|
||||
return moroes != nullptr;
|
||||
}
|
||||
|
||||
bool MoroesNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsDps(bot))
|
||||
return false;
|
||||
|
||||
Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe");
|
||||
Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi");
|
||||
Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck");
|
||||
@@ -21,39 +68,67 @@ bool KarazhanMoroesTrigger::IsActive()
|
||||
Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris");
|
||||
Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference");
|
||||
|
||||
return ((moroes && moroes->IsAlive()) ||
|
||||
(dorothea && dorothea->IsAlive()) ||
|
||||
(catriona && catriona->IsAlive()) ||
|
||||
(keira && keira->IsAlive()) ||
|
||||
(rafe && rafe->IsAlive()) ||
|
||||
(robin && robin->IsAlive()) ||
|
||||
(crispin && crispin->IsAlive()));
|
||||
Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin });
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanMaidenOfVirtueTrigger::IsActive()
|
||||
bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
if (!botAI->IsTank(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
return maiden && maiden->GetVictim() == bot;
|
||||
}
|
||||
|
||||
bool KarazhanBigBadWolfTrigger::IsActive()
|
||||
bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
|
||||
return maiden != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanRomuloAndJulianneTrigger::IsActive()
|
||||
bool BigBadWolfBossEngagedByTankTrigger::IsActive()
|
||||
{
|
||||
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
|
||||
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
return false;
|
||||
|
||||
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
return wolf != nullptr;
|
||||
}
|
||||
|
||||
bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
|
||||
{
|
||||
if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
|
||||
return false;
|
||||
|
||||
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
|
||||
return wolf != nullptr;
|
||||
}
|
||||
|
||||
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
|
||||
if (!romulo)
|
||||
return false;
|
||||
|
||||
return julianne && julianne->IsAlive() && romulo && romulo->IsAlive();
|
||||
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
|
||||
if (!julianne)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KarazhanWizardOfOzTrigger::IsActive()
|
||||
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
|
||||
Unit* tito = AI_VALUE2(Unit*, "find target", "tito");
|
||||
Unit* roar = AI_VALUE2(Unit*, "find target", "roar");
|
||||
@@ -61,45 +136,250 @@ bool KarazhanWizardOfOzTrigger::IsActive()
|
||||
Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead");
|
||||
Unit* crone = AI_VALUE2(Unit*, "find target", "the crone");
|
||||
|
||||
return ((dorothee && dorothee->IsAlive()) ||
|
||||
(tito && tito->IsAlive()) ||
|
||||
(roar && roar->IsAlive()) ||
|
||||
(strawman && strawman->IsAlive()) ||
|
||||
(tinhead && tinhead->IsAlive()) ||
|
||||
(crone && crone->IsAlive()));
|
||||
Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone });
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanTheCuratorTrigger::IsActive()
|
||||
bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
if (bot->getClass() != CLASS_MAGE)
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman");
|
||||
return strawman && strawman->IsAlive();
|
||||
}
|
||||
|
||||
bool KarazhanTerestianIllhoofTrigger::IsActive()
|
||||
bool TheCuratorAstralFlareSpawnedTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof");
|
||||
if (!botAI->IsDps(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare");
|
||||
return flare != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanShadeOfAranTrigger::IsActive()
|
||||
bool TheCuratorBossEngagedByTanksTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
return curator != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanNetherspiteTrigger::IsActive()
|
||||
bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
|
||||
return curator != nullptr;
|
||||
}
|
||||
|
||||
bool KarazhanPrinceMalchezaarTrigger::IsActive()
|
||||
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
return boss && boss->IsAlive();
|
||||
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
|
||||
return illhoof != nullptr;
|
||||
}
|
||||
|
||||
bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive()
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) &&
|
||||
!IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
// Exclusion of Banish is so the player may Banish elementals if they wish
|
||||
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
|
||||
{
|
||||
if (!IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
|
||||
return elemental && elemental->IsAlive() &&
|
||||
!elemental->HasAura(SPELL_WARLOCK_BANISH);
|
||||
}
|
||||
|
||||
bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
|
||||
return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) &&
|
||||
!IsFlameWreathActive(botAI, bot);
|
||||
}
|
||||
|
||||
bool NetherspiteRedBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
|
||||
return redPortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteBlueBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
|
||||
return bluePortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteGreenBeamIsActiveTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
|
||||
return greenPortal != nullptr;
|
||||
}
|
||||
|
||||
bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
|
||||
return bot != redBlocker && bot != blueBlocker && bot != greenBlocker;
|
||||
}
|
||||
|
||||
bool NetherspiteBossIsBanishedTrigger::IsActive()
|
||||
{
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
|
||||
return false;
|
||||
|
||||
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
|
||||
for (Unit* vz : voidZones)
|
||||
{
|
||||
if (bot->GetExactDist2d(vz) < 4.0f)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
|
||||
return false;
|
||||
|
||||
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
|
||||
return netherspite != nullptr;
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive()
|
||||
{
|
||||
return bot->HasAura(SPELL_ENFEEBLE);
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE))
|
||||
return false;
|
||||
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
return malchezaar != nullptr;
|
||||
}
|
||||
|
||||
bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
|
||||
return malchezaar != nullptr;
|
||||
}
|
||||
|
||||
bool NightbaneBossEngagedByMainTankTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsMainTank(bot))
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
|
||||
}
|
||||
|
||||
bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsRanged(bot))
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
|
||||
}
|
||||
|
||||
bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive()
|
||||
{
|
||||
if (bot->getClass() != CLASS_PRIEST)
|
||||
return false;
|
||||
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return false;
|
||||
|
||||
Player* mainTank = nullptr;
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (member && botAI->IsMainTank(member))
|
||||
{
|
||||
mainTank = member;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) &&
|
||||
botAI->CanCastSpell("fear ward", mainTank);
|
||||
}
|
||||
|
||||
bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane)
|
||||
return false;
|
||||
|
||||
Pet* pet = bot->GetPet();
|
||||
return pet && pet->IsAlive();
|
||||
}
|
||||
|
||||
bool NightbaneBossIsFlyingTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
|
||||
return false;
|
||||
|
||||
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
|
||||
const time_t now = std::time(nullptr);
|
||||
const uint8 flightPhaseDurationSeconds = 35;
|
||||
|
||||
return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
|
||||
(now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
|
||||
}
|
||||
|
||||
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()
|
||||
{
|
||||
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
|
||||
return nightbane != nullptr;
|
||||
}
|
||||
|
||||
@@ -3,80 +3,298 @@
|
||||
|
||||
#include "Trigger.h"
|
||||
|
||||
class KarazhanAttumenTheHuntsmanTrigger : public Trigger
|
||||
class ManaWarpIsAboutToExplodeTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {}
|
||||
ManaWarpIsAboutToExplodeTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanMoroesTrigger : public Trigger
|
||||
class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {}
|
||||
AttumenTheHuntsmanNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanMaidenOfVirtueTrigger : public Trigger
|
||||
class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {}
|
||||
AttumenTheHuntsmanAttumenSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanBigBadWolfTrigger : public Trigger
|
||||
class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {}
|
||||
AttumenTheHuntsmanAttumenIsMountedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanRomuloAndJulianneTrigger : public Trigger
|
||||
class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {}
|
||||
AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanWizardOfOzTrigger : public Trigger
|
||||
class MoroesBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {}
|
||||
MoroesBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanTheCuratorTrigger : public Trigger
|
||||
class MoroesNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {}
|
||||
MoroesNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanTerestianIllhoofTrigger : public Trigger
|
||||
class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {}
|
||||
MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanShadeOfAranTrigger : public Trigger
|
||||
class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {}
|
||||
MaidenOfVirtueHolyWrathDealsChainDamageTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanNetherspiteTrigger : public Trigger
|
||||
class BigBadWolfBossEngagedByTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {}
|
||||
BigBadWolfBossEngagedByTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KarazhanPrinceMalchezaarTrigger : public Trigger
|
||||
class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {}
|
||||
BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
RomuloAndJulianneBothBossesRevivedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class WizardOfOzNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
WizardOfOzNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
WizardOfOzStrawmanIsVulnerableToFireTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
class TheCuratorAstralFlareSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorAstralFlareSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TheCuratorBossEngagedByTanksTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorBossEngagedByTanksTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TheCuratorBossAstralFlaresCastArcingSearTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TerestianIllhoofNeedTargetPriorityTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranArcaneExplosionIsCastingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranFlameWreathIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranConjuredElementalsSummonedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteRedBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteRedBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBlueBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBlueBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteGreenBeamIsActiveTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteGreenBeamIsActiveTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBotIsNotBeamBlockerTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteBossIsBanishedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteBossIsBanishedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NetherspiteNeedToManageTimersAndTrackersTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarBotIsEnfeebledTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarInfernalsAreSpawnedTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
PrinceMalchezaarBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneBossEngagedByMainTankTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneBossEngagedByMainTankTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneRangedBotsAreInCharredEarthTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneMainTankIsSusceptibleToFearTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneBossIsFlyingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneBossIsFlyingTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
NightbaneNeedToManageTimersAndTrackersTrigger(
|
||||
PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -401,14 +401,20 @@ std::unordered_map<ObjectGuid, bool> MagtheridonSpreadRangedAction::hasReachedIn
|
||||
|
||||
bool MagtheridonSpreadRangedAction::Execute(Event event)
|
||||
{
|
||||
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
|
||||
if (!magtheridon)
|
||||
return false;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
return false;
|
||||
|
||||
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
|
||||
|
||||
// Wait for 6 seconds after Magtheridon activates to spread
|
||||
const uint8 spreadWaitSeconds = 6;
|
||||
auto it = magtheridonSpreadWaitTimer.find(bot->GetMapId());
|
||||
if (it == magtheridonSpreadWaitTimer.end() ||
|
||||
auto it = spreadWaitTimer.find(instanceId);
|
||||
if (it == spreadWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < spreadWaitSeconds)
|
||||
return false;
|
||||
|
||||
@@ -416,8 +422,8 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
|
||||
if (cubeIt != botToCubeAssignment.end())
|
||||
{
|
||||
time_t now = time(nullptr);
|
||||
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
|
||||
if (timerIt != magtheridonBlastNovaTimer.end())
|
||||
auto timerIt = blastNovaTimer.find(instanceId);
|
||||
if (timerIt != blastNovaTimer.end())
|
||||
{
|
||||
time_t lastBlastNova = timerIt->second;
|
||||
if (now - lastBlastNova >= 49)
|
||||
@@ -559,8 +565,8 @@ bool MagtheridonUseManticronCubeAction::HandleCubeRelease(Unit* magtheridon, Gam
|
||||
|
||||
bool MagtheridonUseManticronCubeAction::ShouldActivateCubeLogic(Unit* magtheridon)
|
||||
{
|
||||
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
|
||||
if (timerIt == magtheridonBlastNovaTimer.end())
|
||||
auto timerIt = blastNovaTimer.find(magtheridon->GetMap()->GetInstanceId());
|
||||
if (timerIt == blastNovaTimer.end())
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -650,52 +656,38 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
|
||||
if (!magtheridon)
|
||||
return false;
|
||||
|
||||
uint32 mapId = magtheridon->GetMapId();
|
||||
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
|
||||
const time_t now = time(nullptr);
|
||||
|
||||
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
|
||||
bool lastBlastNova = lastBlastNovaState[mapId];
|
||||
bool lastBlastNova = lastBlastNovaState[instanceId];
|
||||
|
||||
if (lastBlastNova && !blastNovaActive && IsMapIDTimerManager(botAI, bot))
|
||||
magtheridonBlastNovaTimer[mapId] = time(nullptr);
|
||||
lastBlastNovaState[mapId] = blastNovaActive;
|
||||
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
|
||||
blastNovaTimer[instanceId] = now;
|
||||
|
||||
if (IsMapIDTimerManager(botAI, bot))
|
||||
lastBlastNovaState[instanceId] = blastNovaActive;
|
||||
|
||||
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
{
|
||||
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
if (IsInstanceTimerManager(botAI, bot))
|
||||
{
|
||||
if (magtheridonSpreadWaitTimer.find(mapId) == magtheridonSpreadWaitTimer.end())
|
||||
magtheridonSpreadWaitTimer[mapId] = time(nullptr);
|
||||
|
||||
if (magtheridonBlastNovaTimer.find(mapId) == magtheridonBlastNovaTimer.end())
|
||||
magtheridonBlastNovaTimer[mapId] = time(nullptr);
|
||||
|
||||
if (magtheridonAggroWaitTimer.find(mapId) == magtheridonAggroWaitTimer.end())
|
||||
magtheridonAggroWaitTimer[mapId] = time(nullptr);
|
||||
spreadWaitTimer.try_emplace(instanceId, now);
|
||||
blastNovaTimer.try_emplace(instanceId, now);
|
||||
dpsWaitTimer.try_emplace(instanceId, now);
|
||||
}
|
||||
}
|
||||
|
||||
if (magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
else
|
||||
{
|
||||
if (!MagtheridonSpreadRangedAction::initialPositions.empty())
|
||||
MagtheridonSpreadRangedAction::initialPositions.clear();
|
||||
MagtheridonSpreadRangedAction::initialPositions.clear();
|
||||
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
|
||||
botToCubeAssignment.clear();
|
||||
|
||||
if (!MagtheridonSpreadRangedAction::hasReachedInitialPosition.empty())
|
||||
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
|
||||
|
||||
if (!botToCubeAssignment.empty())
|
||||
botToCubeAssignment.clear();
|
||||
|
||||
if (IsMapIDTimerManager(botAI, bot))
|
||||
if (IsInstanceTimerManager(botAI, bot))
|
||||
{
|
||||
if (magtheridonSpreadWaitTimer.find(mapId) != magtheridonSpreadWaitTimer.end())
|
||||
magtheridonSpreadWaitTimer.erase(mapId);
|
||||
|
||||
if (magtheridonBlastNovaTimer.find(mapId) != magtheridonBlastNovaTimer.end())
|
||||
magtheridonBlastNovaTimer.erase(mapId);
|
||||
|
||||
if (magtheridonAggroWaitTimer.find(mapId) != magtheridonAggroWaitTimer.end())
|
||||
magtheridonAggroWaitTimer.erase(mapId);
|
||||
spreadWaitTimer.erase(instanceId);
|
||||
blastNovaTimer.erase(instanceId);
|
||||
dpsWaitTimer.erase(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace MagtheridonHelpers
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, bool> lastBlastNovaState;
|
||||
std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
|
||||
std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
|
||||
std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
|
||||
std::unordered_map<uint32, time_t> blastNovaTimer;
|
||||
std::unordered_map<uint32, time_t> spreadWaitTimer;
|
||||
std::unordered_map<uint32, time_t> dpsWaitTimer;
|
||||
|
||||
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z)
|
||||
{
|
||||
@@ -209,14 +209,14 @@ namespace MagtheridonHelpers
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (member && member->IsAlive() && !botAI->IsMainTank(member) && GET_PLAYERBOT_AI(member))
|
||||
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
|
||||
return member == bot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace MagtheridonHelpers
|
||||
void MarkTargetWithCross(Player* bot, Unit* target);
|
||||
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
|
||||
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
|
||||
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
|
||||
|
||||
struct Location
|
||||
{
|
||||
@@ -82,9 +82,9 @@ namespace MagtheridonHelpers
|
||||
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids);
|
||||
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI);
|
||||
extern std::unordered_map<uint32, bool> lastBlastNovaState;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> blastNovaTimer;
|
||||
extern std::unordered_map<uint32, time_t> spreadWaitTimer;
|
||||
extern std::unordered_map<uint32, time_t> dpsWaitTimer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -41,10 +41,10 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
|
||||
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
|
||||
return 1.0f;
|
||||
|
||||
const uint8 aggroWaitSeconds = 6;
|
||||
auto it = magtheridonAggroWaitTimer.find(bot->GetMapId());
|
||||
if (it == magtheridonAggroWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < aggroWaitSeconds)
|
||||
const uint8 dpsWaitSeconds = 6;
|
||||
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
|
||||
if (it == dpsWaitTimer.end() ||
|
||||
(time(nullptr) - it->second) < dpsWaitSeconds)
|
||||
{
|
||||
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
|
||||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))
|
||||
|
||||
@@ -7,4 +7,14 @@
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool NoRtiTrigger::IsActive() { return !AI_VALUE(Unit*, "rti target"); }
|
||||
bool NoRtiTrigger::IsActive()
|
||||
{
|
||||
// Do not auto-react to raid icons while out of combat.
|
||||
// Out-of-combat RTI usage (explicit chat commands) is handled by chat triggers,
|
||||
// not by this generic trigger.
|
||||
if (!bot->IsInCombat())
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "rti target");
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
@@ -33,18 +33,14 @@ GuidVector AttackersValue::Calculate()
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && IsValidTarget(unit, bot))
|
||||
{
|
||||
targets.insert(unit);
|
||||
}
|
||||
}
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
ObjectGuid skullGuid = group->GetTargetIcon(7);
|
||||
Unit* skullTarget = botAI->GetUnit(skullGuid);
|
||||
if (skullTarget && IsValidTarget(skullTarget, bot))
|
||||
{
|
||||
targets.insert(skullTarget);
|
||||
}
|
||||
}
|
||||
|
||||
for (Unit* unit : targets)
|
||||
@@ -61,9 +57,7 @@ GuidVector AttackersValue::Calculate()
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
|
||||
{
|
||||
result.push_back(unit->GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +104,8 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
|
||||
|
||||
if (player->IsValidAttackTarget(attacker) &&
|
||||
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
targets.insert(attacker);
|
||||
}
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
}
|
||||
@@ -180,8 +173,9 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range
|
||||
if (!bot->CanSeeOrDetect(attacker))
|
||||
return false;
|
||||
|
||||
// PvP prohibition checks
|
||||
// PvP prohibition checks (skip for duels)
|
||||
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != attacker) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())))
|
||||
{
|
||||
|
||||
@@ -21,11 +21,37 @@ bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
|
||||
|
||||
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
|
||||
|
||||
bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap)
|
||||
{
|
||||
if (!a || !b || a == b)
|
||||
return false;
|
||||
|
||||
if (!a->IsInWorld() || !b->IsInWorld())
|
||||
return false;
|
||||
|
||||
if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld())
|
||||
return false;
|
||||
|
||||
Map* map = a->GetMap();
|
||||
if (!map || map != b->GetMap())
|
||||
return false;
|
||||
|
||||
outMap = map;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateTargetContext(Unit* a, Unit* b)
|
||||
{
|
||||
Map* unused = nullptr;
|
||||
return ValidateTargetContext(a, b, unused);
|
||||
}
|
||||
|
||||
WorldLocation MoveAheadFormation::GetLocation()
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master || master == bot)
|
||||
return WorldLocation();
|
||||
if (!ValidateTargetContext(master, bot))
|
||||
return Formation::NullLocation;
|
||||
|
||||
WorldLocation loc = GetLocationInternal();
|
||||
if (Formation::IsNullLocation(loc))
|
||||
@@ -40,7 +66,7 @@ WorldLocation MoveAheadFormation::GetLocation()
|
||||
// float ori = master->GetOrientation();
|
||||
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
|
||||
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
|
||||
// float ground = master->GetMap()->GetHeight(x1, y1, z);
|
||||
// float ground = map->GetHeight(x1, y1, z);
|
||||
// if (ground > INVALID_HEIGHT)
|
||||
// {
|
||||
// x = x1;
|
||||
@@ -48,7 +74,7 @@ WorldLocation MoveAheadFormation::GetLocation()
|
||||
// }
|
||||
// }
|
||||
|
||||
// float ground = master->GetMap()->GetHeight(x, y, z);
|
||||
// float ground = map->GetHeight(x, y, z);
|
||||
// if (ground <= INVALID_HEIGHT)
|
||||
// return Formation::NullLocation;
|
||||
|
||||
@@ -81,16 +107,17 @@ public:
|
||||
WorldLocation GetLocationInternal() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
float x = master->GetPositionX() + cos(angle) * range;
|
||||
float y = master->GetPositionY() + sin(angle) * range;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range;
|
||||
y = master->GetPositionY() + sin(angle) * range;
|
||||
@@ -111,8 +138,9 @@ public:
|
||||
WorldLocation GetLocationInternal() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
@@ -120,49 +148,28 @@ public:
|
||||
time_t now = time(nullptr);
|
||||
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return WorldLocation();
|
||||
lastChangeTime = now;
|
||||
|
||||
float range = sPlayerbotAIConfig->followDistance;
|
||||
float angle = GetFollowAngle();
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||
{
|
||||
lastChangeTime = now;
|
||||
dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dr = sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
// bot->GetPositionZ(), x, y, z);
|
||||
return WorldLocation(master->GetMapId(), x, y, z);
|
||||
dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
|
||||
dr = std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
float x = master->GetPositionX() + std::cos(angle) * range + dx;
|
||||
float y = master->GetPositionY() + std::sin(angle) * range + dy;
|
||||
float z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + sin(angle) * range + dy;
|
||||
// Recompute a clean fallback and clamp Z
|
||||
x = master->GetPositionX() + std::cos(angle) * range + dx;
|
||||
y = master->GetPositionY() + std::sin(angle) * range + dy;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(master->GetMapId(), x, y, z);
|
||||
}
|
||||
|
||||
@@ -182,16 +189,18 @@ public:
|
||||
|
||||
WorldLocation GetLocation() override
|
||||
{
|
||||
float range = 2.0f;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
Player* master = GetMaster();
|
||||
if (!target && target != bot)
|
||||
|
||||
// Fix: if no target OR target is the bot, fall back to master
|
||||
if (!target || target == bot)
|
||||
target = master;
|
||||
|
||||
if (!target)
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = 2.0f;
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_HUNTER:
|
||||
@@ -214,14 +223,16 @@ public:
|
||||
float x = target->GetPositionX() + cos(angle) * range;
|
||||
float y = target->GetPositionY() + sin(angle) * range;
|
||||
float z = target->GetPositionZ();
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), x, y, z))
|
||||
|
||||
if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = target->GetPositionX() + cos(angle) * range;
|
||||
y = target->GetPositionY() + sin(angle) * range;
|
||||
z = target->GetPositionZ();
|
||||
target->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||
}
|
||||
};
|
||||
@@ -249,18 +260,18 @@ public:
|
||||
float orientation = master->GetOrientation();
|
||||
|
||||
std::vector<Player*> players;
|
||||
GroupReference* gref = group->GetFirstMember();
|
||||
while (gref)
|
||||
players.reserve(group->GetMembersCount());
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (member != master)
|
||||
players.push_back(member);
|
||||
if (!member || member == master)
|
||||
continue;
|
||||
|
||||
gref = gref->next();
|
||||
players.push_back(member);
|
||||
}
|
||||
|
||||
players.insert(players.begin() + group->GetMembersCount() / 2, master);
|
||||
|
||||
players.insert(players.begin() + players.size() / 2, master);
|
||||
return MoveLine(players, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
};
|
||||
@@ -289,19 +300,17 @@ public:
|
||||
|
||||
std::vector<Player*> tanks;
|
||||
std::vector<Player*> dps;
|
||||
GroupReference* gref = group->GetFirstMember();
|
||||
while (gref)
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (member != master)
|
||||
{
|
||||
if (botAI->IsTank(member))
|
||||
tanks.push_back(member);
|
||||
else
|
||||
dps.push_back(member);
|
||||
}
|
||||
if (!member || member == master)
|
||||
continue;
|
||||
|
||||
gref = gref->next();
|
||||
if (botAI->IsTank(member))
|
||||
tanks.push_back(member);
|
||||
else
|
||||
dps.push_back(member);
|
||||
}
|
||||
|
||||
if (botAI->IsTank(master))
|
||||
@@ -310,25 +319,21 @@ public:
|
||||
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
|
||||
|
||||
if (botAI->IsTank(bot) && botAI->IsTank(master))
|
||||
{
|
||||
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
|
||||
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
|
||||
{
|
||||
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
|
||||
}
|
||||
|
||||
if (botAI->IsTank(bot) && !botAI->IsTank(master))
|
||||
{
|
||||
float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
|
||||
range);
|
||||
}
|
||||
|
||||
if (!botAI->IsTank(bot) && botAI->IsTank(master))
|
||||
{
|
||||
float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
|
||||
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
|
||||
range);
|
||||
}
|
||||
@@ -344,65 +349,69 @@ public:
|
||||
|
||||
WorldLocation GetLocation() override
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
Map* map = nullptr;
|
||||
if (!ValidateTargetContext(master, bot, map))
|
||||
return Formation::NullLocation;
|
||||
|
||||
float range = sPlayerbotAIConfig->farDistance;
|
||||
float followRange = sPlayerbotAIConfig->followDistance;
|
||||
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return Formation::NullLocation;
|
||||
|
||||
if (sServerFacade->GetDistance2d(bot, master) <= range)
|
||||
return Formation::NullLocation;
|
||||
|
||||
float angle = master->GetAngle(bot);
|
||||
float angleToBot = master->GetAngle(bot);
|
||||
float followAngle = GetFollowAngle();
|
||||
|
||||
float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
|
||||
float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
|
||||
float z = master->GetPositionZ();
|
||||
|
||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
||||
if (ground <= INVALID_HEIGHT)
|
||||
{
|
||||
float minDist = 0, minX = 0, minY = 0;
|
||||
for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f)
|
||||
float minDist = 0.f;
|
||||
float minX = 0.f, minY = 0.f;
|
||||
|
||||
for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f)
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
float dist = sServerFacade->GetDistance2d(bot, x, y);
|
||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
||||
if (ground > INVALID_HEIGHT && (!minDist || minDist > dist))
|
||||
float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange;
|
||||
float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange;
|
||||
|
||||
float dist = sServerFacade->GetDistance2d(bot, tx, ty);
|
||||
float tg = master->GetMapHeight(tx, ty, z + 30.0f);
|
||||
|
||||
if (tg > INVALID_HEIGHT && (!minDist || dist < minDist))
|
||||
{
|
||||
minDist = dist;
|
||||
minX = x;
|
||||
minY = y;
|
||||
minX = tx;
|
||||
minY = ty;
|
||||
}
|
||||
}
|
||||
|
||||
if (minDist)
|
||||
if (!minDist)
|
||||
return Formation::NullLocation;
|
||||
|
||||
float lz = z;
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), minX, minY, lz))
|
||||
{
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
return WorldLocation(bot->GetMapId(), minX, minY, z);
|
||||
lz = z + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(minX, minY, lz);
|
||||
}
|
||||
|
||||
return Formation::NullLocation;
|
||||
return WorldLocation(bot->GetMapId(), minX, minY, lz);
|
||||
}
|
||||
|
||||
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
|
||||
x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
|
||||
y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
|
||||
z = master->GetPositionZ() + master->GetHoverHeight();
|
||||
master->UpdateAllowedPositionZ(x, y, z);
|
||||
}
|
||||
|
||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||
}
|
||||
};
|
||||
@@ -653,31 +662,36 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float dif
|
||||
float orientation, float range)
|
||||
{
|
||||
float count = line.size();
|
||||
float angle = orientation - M_PI / 2.0f;
|
||||
float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff);
|
||||
float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff);
|
||||
float angleLeft = orientation - M_PI / 2.0f;
|
||||
float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff);
|
||||
float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff);
|
||||
|
||||
uint32 index = 0;
|
||||
for (Player* member : line)
|
||||
{
|
||||
if (member == bot)
|
||||
{
|
||||
float angle = orientation + M_PI / 2.0f;
|
||||
float angleRight = orientation + M_PI / 2.0f;
|
||||
float radius = range * index;
|
||||
|
||||
float lx = x + cos(angle) * radius;
|
||||
float ly = y + sin(angle) * radius;
|
||||
float lx = x0 + std::cos(angleRight) * radius;
|
||||
float ly = y0 + std::sin(angleRight) * radius;
|
||||
float lz = cz;
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master || !master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
|
||||
Map* map = master ? master->GetMap() : nullptr;
|
||||
|
||||
// if not fully in world ignore collision corrections.
|
||||
if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() ||
|
||||
master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
|
||||
{
|
||||
lx = x + cos(angle) * radius;
|
||||
ly = y + sin(angle) * radius;
|
||||
lz = cz;
|
||||
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
||||
}
|
||||
|
||||
// if fully loaded check collision and applies coordinate corrections if needed
|
||||
map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||
master->GetPositionZ(), lx, ly, lz);
|
||||
|
||||
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
return ITEM_USAGE_USE;
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
|
||||
(proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount))
|
||||
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount))
|
||||
{
|
||||
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
|
||||
|
||||
@@ -520,7 +520,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
|
||||
{
|
||||
if (quest->RequiredItemId[i] == proto->ItemId)
|
||||
{
|
||||
if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i])
|
||||
if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i])
|
||||
continue;
|
||||
|
||||
return true; // Item is directly required for a quest
|
||||
@@ -549,7 +549,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
|
||||
{
|
||||
if (quest->RequiredItemId[j] == createdItemId)
|
||||
{
|
||||
if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j])
|
||||
if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j])
|
||||
continue;
|
||||
|
||||
return true; // Item is useful because it creates a required quest item
|
||||
|
||||
@@ -64,5 +64,15 @@ Unit* RtiTargetValue::Calculate()
|
||||
sPlayerbotAIConfig->sightDistance))
|
||||
return nullptr;
|
||||
|
||||
// Also prevent chasing raid icon targets that are too far away from the master,
|
||||
// even if they are technically visible to the bot.
|
||||
if (Player* master = botAI->GetMaster())
|
||||
{
|
||||
if (master->IsInWorld() && master->GetMapId() == unit->GetMapId() &&
|
||||
sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(master, unit),
|
||||
sPlayerbotAIConfig->sightDistance))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user