Merge remote-tracking branch 'origin/test-staging' into feature/new_rpg_and_nav_2

# Conflicts:
#	src/Ai/World/Rpg/Action/NewRpgAction.cpp
#	src/Mgr/Travel/TravelMgr.cpp
This commit is contained in:
bash 2026-05-10 20:42:36 +02:00
commit cfcc29b86f
81 changed files with 4249 additions and 262 deletions

View File

@ -32,7 +32,7 @@
# LEVELS # LEVELS
# GEAR # GEAR
# QUESTS # QUESTS
# ACTIVITIES # ACTIVITY
# SPELLS # SPELLS
# STRATEGIES # STRATEGIES
# RPG STRATEGY # RPG STRATEGY
@ -2242,6 +2242,14 @@ AiPlayerbot.CommandPrefix = ""
# Separator for bot chat commands # Separator for bot chat commands
AiPlayerbot.CommandSeparator = "\\\\" AiPlayerbot.CommandSeparator = "\\\\"
# Enable automatic item count/trade trigger when a chat message contains
# item-related keywords. When enabled (1), mentioning items in chat
# (e.g. "food", "potion", "ammo") will automatically show inventory and
# open a trade window with the bot. Explicit "c" and "t" commands still
# work regardless of this setting.
# Default: 1 (enabled)
AiPlayerbot.EnableAutoTradeOnItemMention = 1
# Enable bots talking (say / yell / general chatting / lfg) # Enable bots talking (say / yell / general chatting / lfg)
AiPlayerbot.RandomBotTalk = 1 AiPlayerbot.RandomBotTalk = 1
# Enable bots emoting # Enable bots emoting

View File

@ -343,7 +343,7 @@ bool BGJoinAction::isUseful()
return false; return false;
// check Deserter debuff // check Deserter debuff
if (!bot->CanJoinToBattleground()) if (bot->IsDeserter())
return false; return false;
// check if has free queue slots (pointless as already making sure not in queue) // check if has free queue slots (pointless as already making sure not in queue)

View File

@ -213,13 +213,7 @@ bool BuyAction::Execute(Event event)
} }
} }
if (!vendored) return vendored;
{
botAI->TellError("There are no vendors nearby");
return false;
}
return true;
} }
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto) bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)

View File

@ -143,14 +143,17 @@ bool CastCustomSpellAction::Execute(Event event)
std::ostringstream spellName; std::ostringstream spellName;
spellName << ChatHelper::FormatSpell(spellInfo) << " on "; spellName << ChatHelper::FormatSpell(spellInfo) << " on ";
bool const hasItemTarget = itemTarget &&
(spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM);
if (bot->GetTrader()) if (bot->GetTrader())
spellName << "trade item"; spellName << "trade item";
else if (itemTarget) else if (hasItemTarget)
spellName << chat->FormatItem(itemTarget->GetTemplate()); spellName << chat->FormatItem(itemTarget->GetTemplate());
else if (target == bot) else if (target != bot)
spellName << "self";
else
spellName << target->GetName(); spellName << target->GetName();
else
spellName << "self";
if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget)) if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget))
{ {

View File

@ -9,28 +9,36 @@
#include "PlayerbotRepository.h" #include "PlayerbotRepository.h"
#include "Playerbots.h" #include "Playerbots.h"
// Helper function for prefixes used by combat and non-combat strategy commands.
static void HandleStrategyCommon(PlayerbotAI* botAI, std::string const& text, BotState state)
{
std::vector<std::string> splitted = split(text, ',');
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
{
const char* name = i->c_str();
switch (name[0])
{
case '+':
case '-':
case '~':
PlayerbotRepository::instance().Save(botAI);
break;
case '!':
botAI->SelectiveResetStrategies(state);
PlayerbotRepository::instance().Save(botAI);
break;
case '?':
break;
}
}
}
bool ChangeCombatStrategyAction::Execute(Event event) bool ChangeCombatStrategyAction::Execute(Event event)
{ {
std::string const text = event.getParam(); std::string const text = event.getParam();
botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT); botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT);
if (event.GetSource() == "co") if (event.GetSource() == "co")
{ HandleStrategyCommon(botAI, text, BOT_STATE_COMBAT);
std::vector<std::string> splitted = split(text, ',');
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
{
const char* name = i->c_str();
switch (name[0])
{
case '+':
case '-':
case '~':
PlayerbotRepository::instance().Save(botAI);
break;
case '?':
break;
}
}
}
return true; return true;
} }
@ -52,23 +60,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
botAI->ChangeStrategy(text, BOT_STATE_NON_COMBAT); botAI->ChangeStrategy(text, BOT_STATE_NON_COMBAT);
if (event.GetSource() == "nc") if (event.GetSource() == "nc")
{ HandleStrategyCommon(botAI, text, BOT_STATE_NON_COMBAT);
std::vector<std::string> splitted = split(text, ',');
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
{
const char* name = i->c_str();
switch (name[0])
{
case '+':
case '-':
case '~':
PlayerbotRepository::instance().Save(botAI);
break;
case '?':
break;
}
}
}
return true; return true;
} }

View File

@ -4,9 +4,11 @@
*/ */
#include "CheckMountStateAction.h" #include "CheckMountStateAction.h"
#include "AreaDefines.h"
#include "BattleGroundTactics.h" #include "BattleGroundTactics.h"
#include "BattlegroundEY.h" #include "BattlegroundEY.h"
#include "BattlegroundWS.h" #include "BattlegroundWS.h"
#include "DBCStores.h"
#include "Event.h" #include "Event.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
@ -14,6 +16,8 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
static constexpr uint32 SPELL_COLD_WEATHER_FLYING = 54197;
// Define the static map / init bool for caching bot preferred mount data globally // Define the static map / init bool for caching bot preferred mount data globally
std::unordered_map<uint32, PreferredMountCache> CheckMountStateAction::mountCache; std::unordered_map<uint32, PreferredMountCache> CheckMountStateAction::mountCache;
bool CheckMountStateAction::preferredMountTableChecked = false; bool CheckMountStateAction::preferredMountTableChecked = false;
@ -94,9 +98,10 @@ bool CheckMountStateAction::Execute(Event /*event*/)
} }
bool inBattleground = bot->InBattleground(); bool inBattleground = bot->InBattleground();
bool const noRealMaster = (!master || master == bot);
// If there is a master and bot not in BG, follow master's mount state regardless of group leader // If there is a master and bot not in BG, follow master's mount state regardless of group leader
if (master && !inBattleground) if (!noRealMaster && !inBattleground)
{ {
if (ShouldFollowMasterMountState(master, noAttackers, shouldMount)) if (ShouldFollowMasterMountState(master, noAttackers, shouldMount))
return Mount(); return Mount();
@ -110,8 +115,8 @@ bool CheckMountStateAction::Execute(Event /*event*/)
return false; return false;
} }
// If there is no master or bot in BG // No real master (random bot or self-bot) OR bot in BG
if ((!master || inBattleground) && !bot->IsMounted() && if ((noRealMaster || inBattleground) && !bot->IsMounted() &&
noAttackers && shouldMount && !bot->IsInCombat()) noAttackers && shouldMount && !bot->IsInCombat())
return Mount(); return Mount();
@ -228,6 +233,39 @@ void CheckMountStateAction::Dismount()
WorldPacket emptyPacket; WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
bool const wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
bool const isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bool const isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
bool const hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled))
{
bot->RemoveUnitMovementFlag(
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY);
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
}
}
void CheckMountStateAction::CompleteDismount(Player* bot)
{
if (!bot || !bot->IsInWorld())
return;
float const x = bot->GetPositionX();
float const y = bot->GetPositionY();
float const startZ = bot->GetPositionZ();
float groundZ = startZ;
bot->UpdateAllowedPositionZ(x, y, groundZ);
bot->GetMotionMaster()->MoveFall();
MovementInfo fallInfo = bot->m_movementInfo;
// Need to set the start of the fall, otherwise the fall may start from too high of a Z and kill the bot.
bot->SetFallInformation(0, startZ);
fallInfo.pos.Relocate(x, y, groundZ);
bot->HandleFall(fallInfo);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR);
} }
bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const
@ -434,6 +472,24 @@ bool CheckMountStateAction::ShouldDismountForMaster(Player* master) const
return !isMasterMounted && bot->IsMounted(); return !isMasterMounted && bot->IsMounted();
} }
static bool BotCanUseFlyingMount(Player const* bot)
{
if (bot->GetPureSkillValue(SKILL_RIDING) < 225)
return false;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetAreaId());
if (!area || !area->IsFlyable())
return false;
if (area->flags & AREA_FLAG_NO_FLY_ZONE)
return false;
uint32 const vmap = GetVirtualMapForMapAndZone(bot->GetMapId(), bot->GetZoneId());
if (vmap == MAP_NORTHREND && !bot->HasSpell(SPELL_COLD_WEATHER_FLYING))
return false;
return true;
}
int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const
{ {
// Check riding skill and level requirements // Check riding skill and level requirements
@ -443,8 +499,10 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou
if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig.useFastGroundMountAtMinLevel)) if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig.useFastGroundMountAtMinLevel))
return 59; return 59;
// If there is a master and bot not in BG, use master's aura effects. // check if bot has master and if master is self
if (master && !bot->InBattleground()) bool const noRealMaster = (!master || master == bot);
if (!noRealMaster && !bot->InBattleground())
{ {
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
if (!auraEffects.empty()) if (!auraEffects.empty())
@ -458,27 +516,27 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou
return 279; return 279;
else if (masterInShapeshiftForm == FORM_FLIGHT) else if (masterInShapeshiftForm == FORM_FLIGHT)
return 149; return 149;
} return 59; // walk pace
else
{
// Bots on their own.
int32 speed = mountData.maxSpeed;
if (bot->InBattleground() && speed > 99)
return 99;
return speed;
} }
return 59; // No real master OR battleground: pick speed by skill tier.
if (!bot->InBattleground() && BotCanUseFlyingMount(bot))
return (ridingSkill >= 300) ? 279 : 149;
int32 maxGround = (ridingSkill >= 150) ? 99 : 59;
if (bot->InBattleground() && maxGround > 99)
maxGround = 99;
return maxGround;
} }
uint32 CheckMountStateAction::GetMountType(Player* master) const uint32 CheckMountStateAction::GetMountType(Player* master) const
{ {
if (!master) bool const noRealMaster = (!master || master == bot);
return 0;
if (noRealMaster)
return (!bot->InBattleground() && BotCanUseFlyingMount(bot)) ? 1 : 0;
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
if (!auraEffects.empty()) if (!auraEffects.empty())
{ {
SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo(); SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo();

View File

@ -42,6 +42,8 @@ public:
bool isPossible() override { return true; } bool isPossible() override { return true; }
bool Mount(); bool Mount();
static void CompleteDismount(Player* bot);
private: private:
Player* master; Player* master;
ShapeshiftForm masterInShapeshiftForm; ShapeshiftForm masterInShapeshiftForm;

View File

@ -17,7 +17,7 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include "Group.h" #include "Group.h"
#include "Chat.h" #include "Chat.h"
#include "Ai/Base/Util/GenericBuffUtils.h" #include "GenericBuffUtils.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
using ai::buff::MakeAuraQualifierForBuff; using ai::buff::MakeAuraQualifierForBuff;
@ -134,7 +134,8 @@ bool CastSpellAction::isPossible()
return botAI->CanCastSpell(spell, GetTarget()); return botAI->CanCastSpell(spell, GetTarget());
} }
CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) CastMeleeSpellAction::CastMeleeSpellAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
{ {
range = ATTACK_DISTANCE; range = ATTACK_DISTANCE;
} }
@ -182,56 +183,47 @@ bool CastAuraSpellAction::isUseful()
return false; return false;
} }
CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell) CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
: CastSpellAction(botAI, spell) PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::Execute(Event /*event*/)
{ {
range = botAI->GetRange("spell"); Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && botAI->CastSpell(spell, bot, item);
} }
bool CastEnchantItemAction::isPossible()
{
// if (!CastSpellAction::isPossible())
// {
// botAI->TellMasterNoFacing("Impossible: " + spell);
// return false;
// }
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
// bool ok = AI_VALUE2(Item*, "item for spell", spellId);
// Item* item = AI_VALUE2(Item*, "item for spell", spellId);
// botAI->TellMasterNoFacing("spell: " + spell + ", spell id: " + std::to_string(spellId) + " item for spell: " +
// std::to_string(ok));
return spellId && AI_VALUE2(Item*, "item for spell", spellId);
}
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::isPossible() bool CastEnchantItemMainHandAction::isPossible()
{ {
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && !item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) && if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
item->GetTemplate()->Class == ITEM_CLASS_WEAPON; item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE ||
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
{
return false;
}
return botAI->CanCastSpell(spell, bot, item);
} }
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell) CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(
: CastEnchantItemAction(botAI, spell) {} PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
bool CastEnchantItemOffHandAction::Execute(Event /*event*/)
{
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
return item && botAI->CastSpell(spell, bot, item);
}
bool CastEnchantItemOffHandAction::isPossible() bool CastEnchantItemOffHandAction::isPossible()
{ {
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!item || item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
{
return false; return false;
}
uint32 invType = item->GetTemplate()->InventoryType; return botAI->CanCastSpell(spell, bot, item);
return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND;
} }
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount, CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
@ -245,7 +237,8 @@ bool CastHealingSpellAction::isUseful() { return CastAuraSpellAction::isUseful()
bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); } bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); }
CastCureSpellAction::CastCureSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) CastCureSpellAction::CastCureSpellAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
{ {
range = botAI->GetRange("heal"); range = botAI->GetRange("heal");
} }
@ -267,13 +260,15 @@ bool BuffOnPartyAction::Execute(Event /*event*/)
std::string castName = spell; // default = mono std::string castName = spell; // default = mono
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot); auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP); castName = ai::buff::UpgradeToGroupIfAppropriate(
bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
return botAI->CastSpell(castName, GetTarget()); return botAI->CastSpell(castName, GetTarget());
} }
// End greater buff fix // End greater buff fix
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0) CastShootAction::CastShootAction(
PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
{ {
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{ {
@ -327,7 +322,8 @@ Value<Unit*>* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue()
return context->GetValue<Unit*>("melee attacker without aura", spell); return context->GetValue<Unit*>("melee attacker without aura", spell);
} }
CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration) CastBuffSpellAction::CastBuffSpellAction(
PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
: CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration) : CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration)
{ {
range = botAI->GetRange("spell"); range = botAI->GetRange("spell");
@ -448,7 +444,8 @@ bool UseTrinketAction::UseTrinket(Item* item)
uint32 spellId = 0; uint32 spellId = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{ {
if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) if (item->GetTemplate()->Spells[i].SpellId > 0 &&
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{ {
spellId = item->GetTemplate()->Spells[i].SpellId; spellId = item->GetTemplate()->Spells[i].SpellId;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);

View File

@ -121,26 +121,21 @@ public:
std::string const GetTargetName() override { return "self target"; } std::string const GetTargetName() override { return "self target"; }
}; };
class CastEnchantItemAction : public CastSpellAction class CastEnchantItemMainHandAction : public CastSpellAction
{
public:
CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
std::string const GetTargetName() override { return "self target"; }
};
class CastEnchantItemMainHandAction : public CastEnchantItemAction
{ {
public: public:
CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell); CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell);
std::string const GetTargetName() override { return "self target"; }
bool Execute(Event event) override;
bool isPossible() override; bool isPossible() override;
}; };
class CastEnchantItemOffHandAction : public CastEnchantItemAction class CastEnchantItemOffHandAction : public CastSpellAction
{ {
public: public:
CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell); CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell);
std::string const GetTargetName() override { return "self target"; }
bool Execute(Event event) override;
bool isPossible() override; bool isPossible() override;
}; };

View File

@ -296,7 +296,7 @@ bool PetitionTurnInAction::isUseful()
bool BuyTabardAction::Execute(Event /*event*/) bool BuyTabardAction::Execute(Event /*event*/)
{ {
bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:")); bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"), true);
if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976))) if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)))
return true; return true;

View File

@ -10,6 +10,31 @@
#include "ItemVisitors.h" #include "ItemVisitors.h"
#include "Playerbots.h" #include "Playerbots.h"
namespace
{
bool isReservedQualifier(std::string const& text)
{
static std::array<std::string_view, 13> const exactQualifiers = {
"ammo",
"conjured drink",
"conjured food",
"conjured water",
"drink",
"food",
"healing potion",
"mount",
"mana potion",
"pet",
"quest",
"recipe",
"water"
};
return std::find(exactQualifiers.begin(), exactQualifiers.end(), text) != exactQualifiers.end() ||
text.rfind("usage ", 0) == 0;
}
}
void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask)
{ {
if (mask & ITERATE_ITEMS_IN_BAGS) if (mask & ITERATE_ITEMS_IN_BAGS)
@ -292,9 +317,12 @@ std::vector<Item*> InventoryAction::parseItems(std::string const text, IterateIt
found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); found.insert(visitor.GetResult().begin(), visitor.GetResult().end());
} }
FindNamedItemVisitor visitor(bot, text); if (!isReservedQualifier(text))
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); {
found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); FindNamedItemVisitor visitor(bot, text);
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
found.insert(visitor.GetResult().begin(), visitor.GetResult().end());
}
uint32 quality = chat->parseItemQuality(text); uint32 quality = chat->parseItemQuality(text);
if (quality != MAX_ITEM_QUALITY) if (quality != MAX_ITEM_QUALITY)

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "TellEmblemsAction.h"
#include <array>
#include "Event.h"
#include "Playerbots.h"
bool TellEmblemsAction::Execute(Event /*event*/)
{
static std::array<uint32, 6> const emblemIds = {
29434, // Badge of Justice
40752, // Emblem of Heroism
40753, // Emblem of Valor
45624, // Emblem of Conquest
47241, // Emblem of Triumph
49426 // Emblem of Frost
};
botAI->TellMaster("=== Emblems ===");
for (uint32 itemId : emblemIds)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
uint32 count = bot->GetItemCount(itemId, false);
std::ostringstream out;
out << chat->FormatItem(proto, count);
botAI->TellMaster(out);
}
return true;
}

View File

@ -0,0 +1,21 @@
/*
* 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_TELLEMBLEMSACTION_H
#define _PLAYERBOT_TELLEMBLEMSACTION_H
#include "InventoryAction.h"
class PlayerbotAI;
class TellEmblemsAction : public InventoryAction
{
public:
TellEmblemsAction(PlayerbotAI* botAI) : InventoryAction(botAI, "emblems") {}
bool Execute(Event event) override;
};
#endif

View File

@ -5,34 +5,23 @@
#include "TellReputationAction.h" #include "TellReputationAction.h"
#include <algorithm>
#include "Event.h" #include "Event.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "ReputationMgr.h" #include "ReputationMgr.h"
bool TellReputationAction::Execute(Event /*event*/) #include "SharedDefines.h"
std::string TellReputationAction::BuildReputationLine(FactionEntry const* entry)
{ {
Player* master = GetMaster(); ReputationMgr& repMgr = bot->GetReputationMgr();
if (!master) ReputationRank rank = repMgr.GetRank(entry);
return false; int32 reputation = repMgr.GetReputation(entry->ID);
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
uint32 faction = factionTemplate->faction;
FactionEntry const* entry = sFactionStore.LookupEntry(faction);
int32 reputation = bot->GetReputationMgr().GetReputation(faction);
std::ostringstream out; std::ostringstream out;
out << entry->name[0] << ": "; out << entry->name[0] << ": |cff";
out << "|cff";
ReputationRank rank = bot->GetReputationMgr().GetRank(entry);
switch (rank) switch (rank)
{ {
case REP_HATED: case REP_HATED:
@ -71,7 +60,65 @@ bool TellReputationAction::Execute(Event /*event*/)
base -= ReputationMgr::PointsInRank[i]; base -= ReputationMgr::PointsInRank[i];
out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")"; out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")";
botAI->TellMaster(out); return out.str();
}
bool TellReputationAction::Execute(Event event)
{
std::string const param = event.getParam();
if (param == "all")
{
ReputationMgr& repMgr = bot->GetReputationMgr();
std::vector<std::string> lines;
FactionStateList const& stateList = repMgr.GetStateList();
lines.reserve(stateList.size());
for (auto const& itr : stateList)
{
FactionState const& faction = itr.second;
if (!(faction.Flags & FACTION_FLAG_VISIBLE))
continue;
if (faction.Flags & (FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED) &&
!(faction.Flags & FACTION_FLAG_SPECIAL))
continue;
FactionEntry const* entry = sFactionStore.LookupEntry(faction.ID);
if (!entry)
continue;
lines.push_back(BuildReputationLine(entry));
}
std::sort(lines.begin(), lines.end());
botAI->TellMaster("=== Reputations ===");
for (auto const& line : lines)
botAI->TellMaster(line);
return true;
}
Player* master = GetMaster();
if (!master)
return false;
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
FactionEntry const* entry = sFactionStore.LookupEntry(factionTemplate->faction);
if (!entry)
return false;
botAI->TellMaster(BuildReputationLine(entry));
return true; return true;
} }

View File

@ -6,8 +6,11 @@
#ifndef _PLAYERBOT_TELLREPUTATIONACTION_H #ifndef _PLAYERBOT_TELLREPUTATIONACTION_H
#define _PLAYERBOT_TELLREPUTATIONACTION_H #define _PLAYERBOT_TELLREPUTATIONACTION_H
#include <string>
#include "Action.h" #include "Action.h"
struct FactionEntry;
class PlayerbotAI; class PlayerbotAI;
class TellReputationAction : public Action class TellReputationAction : public Action
@ -16,6 +19,9 @@ public:
TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {} TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {}
bool Execute(Event event) override; bool Execute(Event event) override;
private:
std::string BuildReputationLine(FactionEntry const* entry);
}; };
#endif #endif

View File

@ -66,6 +66,7 @@
#include "TaxiAction.h" #include "TaxiAction.h"
#include "TeleportAction.h" #include "TeleportAction.h"
#include "TellCastFailedAction.h" #include "TellCastFailedAction.h"
#include "TellEmblemsAction.h"
#include "TellItemCountAction.h" #include "TellItemCountAction.h"
#include "TellLosAction.h" #include "TellLosAction.h"
#include "TellReputationAction.h" #include "TellReputationAction.h"
@ -120,6 +121,7 @@ public:
creators["teleport"] = &ChatActionContext::teleport; creators["teleport"] = &ChatActionContext::teleport;
creators["taxi"] = &ChatActionContext::taxi; creators["taxi"] = &ChatActionContext::taxi;
creators["repair"] = &ChatActionContext::repair; creators["repair"] = &ChatActionContext::repair;
creators["emblems"] = &ChatActionContext::emblems;
creators["use"] = &ChatActionContext::use; creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count; creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip; creators["equip"] = &ChatActionContext::equip;
@ -276,6 +278,7 @@ private:
static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); } static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); }
static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); } static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); }
static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); } static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); }
static Action* emblems(PlayerbotAI* botAI) { return new TellEmblemsAction(botAI); }
static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); } static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); }
static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); } static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); }
static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); } static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); }

View File

@ -41,6 +41,7 @@ public:
creators["teleport"] = &ChatTriggerContext::teleport; creators["teleport"] = &ChatTriggerContext::teleport;
creators["taxi"] = &ChatTriggerContext::taxi; creators["taxi"] = &ChatTriggerContext::taxi;
creators["repair"] = &ChatTriggerContext::repair; creators["repair"] = &ChatTriggerContext::repair;
creators["emblems"] = &ChatTriggerContext::emblems;
creators["u"] = &ChatTriggerContext::use; creators["u"] = &ChatTriggerContext::use;
creators["use"] = &ChatTriggerContext::use; creators["use"] = &ChatTriggerContext::use;
creators["c"] = &ChatTriggerContext::item_count; creators["c"] = &ChatTriggerContext::item_count;
@ -235,6 +236,7 @@ private:
static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); } static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); }
static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); } static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); }
static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); } static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); }
static Trigger* emblems(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "emblems"); }
static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); } static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); }
static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); } static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); }
static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); } static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); }

View File

@ -114,6 +114,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) })); triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) })); triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) }));
} }
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@ -138,6 +139,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("teleport"); supported.push_back("teleport");
supported.push_back("taxi"); supported.push_back("taxi");
supported.push_back("repair"); supported.push_back("repair");
supported.push_back("emblems");
supported.push_back("talents"); supported.push_back("talents");
supported.push_back("spells"); supported.push_back("spells");
supported.push_back("co"); supported.push_back("co");
@ -202,8 +204,8 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("unlock items"); supported.push_back("unlock items");
supported.push_back("unlock traded item"); supported.push_back("unlock traded item");
supported.push_back("tame"); supported.push_back("tame");
supported.push_back("glyphs"); // Added for custom Glyphs supported.push_back("glyphs");
supported.push_back("glyph equip"); // Added for custom Glyphs supported.push_back("glyph equip");
supported.push_back("pet"); supported.push_back("pet");
supported.push_back("pet attack"); supported.push_back("pet attack");
supported.push_back("wait for attack time"); supported.push_back("wait for attack time");

View File

@ -39,11 +39,8 @@ bool LeaveLargeGuildTrigger::IsActive()
Player* leader = ObjectAccessor::FindPlayer(guild->GetLeaderGUID()); Player* leader = ObjectAccessor::FindPlayer(guild->GetLeaderGUID());
// Only leave the guild if we know the leader is not a real player. // Only leave the guild if the leader is an online bot (not a real player).
if (!leader || !GET_PLAYERBOT_AI(leader) || !GET_PLAYERBOT_AI(leader)->IsRealPlayer()) PlayerbotAI* leaderBotAI = leader ? GET_PLAYERBOT_AI(leader) : nullptr;
return false;
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader);
if (!leaderBotAI || leaderBotAI->IsRealPlayer()) if (!leaderBotAI || leaderBotAI->IsRealPlayer())
return false; return false;

View File

@ -297,7 +297,7 @@ bool PlayerWantsInBattlegroundTrigger::IsActive()
if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS) if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS)
return false; return false;
if (!bot->CanJoinToBattleground()) if (bot->IsDeserter())
return false; return false;
return true; return true;

View File

@ -180,19 +180,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
delete pItem; delete pItem;
if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
// Check is unique items are equipped or not // Check if unique items are equipped or not
bool needToCheckUnique = false; bool needToCheckUnique = result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS ||
if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE);
{
needToCheckUnique = true;
}
else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
needToCheckUnique = true;
}
if (needToCheckUnique) if (needToCheckUnique)
{ {
@ -206,28 +198,27 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
bool isEquipped = (totalItemCount > bagItemCount); bool isEquipped = (totalItemCount > bagItemCount);
if (isEquipped) if (isEquipped)
{
return ITEM_USAGE_NONE; // Item is already equipped return ITEM_USAGE_NONE; // Item is already equipped
}
// If not equipped, continue processing // If not equipped, continue processing
} }
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER && bot->getClass() != CLASS_HUNTER)
if (bot->getClass() != CLASS_HUNTER) return ITEM_USAGE_NONE;
return ITEM_USAGE_NONE;
if (itemProto->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER) if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER)
return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and
// only replace if non-bag is larger than bag. // only replace if non-bag is larger than bag.
if (GetSmallestBagSize() >= itemProto->ContainerSlots) if (GetSmallestBagSize() >= itemProto->ContainerSlots)
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
} }
if (itemProto->Class == ITEM_CLASS_WEAPON && itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MISC)
return ITEM_USAGE_NONE;
bool shouldEquip = false; bool shouldEquip = false;
// uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId); // uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId);
StatsWeightCalculator calculator(bot); StatsWeightCalculator calculator(bot);
@ -254,19 +245,14 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead // Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead
// This occurs with unique items that are already in the bots bags when CanEquipItem is called // This occurs with unique items that are already in the bots bags when CanEquipItem is called
if (dest == 0) if (dest == 0 && dstSlot != NULL_SLOT)
{ {
if (dstSlot != NULL_SLOT) // Construct dest from dstSlot
{ dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
// Construct dest from dstSlot
dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
}
} }
if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1)
{
possibleSlots = 2; possibleSlots = 2;
}
// Check weapon case separately to keep things a bit cleaner // Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false; bool have2HWeapon = false;
@ -283,14 +269,9 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
// If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe. // If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe.
if (bot->CanTitanGrip()) // If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all.
{ if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. return ITEM_USAGE_NONE;
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots // Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and: // If the bot can Dual Wield and:
@ -317,9 +298,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
if (shouldEquipInSlot) if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
else else
{
return ITEM_USAGE_BAD_EQUIP; return ITEM_USAGE_BAD_EQUIP;
}
} }
ItemTemplate const* oldItemProto = oldItem->GetTemplate(); ItemTemplate const* oldItemProto = oldItem->GetTemplate();
@ -328,22 +307,16 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
{ {
// uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId); // uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore) if (itemScore || oldScore)
{
shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold; shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold;
}
} }
// Bigger quiver // Bigger quiver
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER)
{ {
if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots)
{
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
}
else else
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
} }
bool existingShouldEquip = true; bool existingShouldEquip = true;

View File

@ -11,6 +11,22 @@
#include "AoeValues.h" #include "AoeValues.h"
#include "TargetValue.h" #include "TargetValue.h"
namespace
{
bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target)
{
if (!target)
return false;
Aura* existingThorns = botAI->GetAura("thorns", target, true);
if (!existingThorns)
return true;
target->RemoveOwnedAura(existingThorns, AURA_REMOVE_BY_CANCEL);
return true;
}
}
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives() std::vector<NextAction> CastAbolishPoisonAction::getAlternatives()
{ {
return NextAction::merge({ NextAction("cure poison") }, return NextAction::merge({ NextAction("cure poison") },
@ -33,6 +49,21 @@ bool CastLifebloomOnMainTankAction::isUseful()
return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000; return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000;
} }
bool CastThornsAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && CastBuffSpellAction::Execute(event);
}
bool CastThornsOnPartyAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnPartyAction::Execute(event);
}
bool CastThornsOnMainTankAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event);
}
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue() Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
{ {
return context->GetValue<Unit*>("cc target", "entangling roots"); return context->GetValue<Unit*>("cc target", "entangling roots");

View File

@ -114,18 +114,24 @@ class CastThornsAction : public CastBuffSpellAction
{ {
public: public:
CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {} CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {}
bool Execute(Event event) override;
}; };
class CastThornsOnPartyAction : public BuffOnPartyAction class CastThornsOnPartyAction : public BuffOnPartyAction
{ {
public: public:
CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {} CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {}
bool Execute(Event event) override;
}; };
class CastThornsOnMainTankAction : public BuffOnMainTankAction class CastThornsOnMainTankAction : public BuffOnMainTankAction
{ {
public: public:
CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {} CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {}
bool Execute(Event event) override;
}; };
class CastLifebloomOnMainTankAction : public BuffOnMainTankAction class CastLifebloomOnMainTankAction : public BuffOnMainTankAction

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidAq20Triggers.h" #include "RaidAq20Triggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H #ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H
#define _PLAYERBOT_RAIDAQ20STRATEGY_H #define _PLAYERBOT_RAIDAQ20STRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidAq20Strategy : public Strategy class RaidAq20Strategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidBwlTriggers.h" #include "RaidBwlTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H #ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H
#define _PLAYERBOT_RAIDBWLSTRATEGY_H #define _PLAYERBOT_RAIDBWLSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidBwlStrategy : public Strategy class RaidBwlStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidEoETriggers.h" #include "RaidEoETriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOESTRATEGY_H #ifndef _PLAYERBOT_RAIDEOESTRATEGY_H
#define _PLAYERBOT_RAIDEOESTRATEGY_H #define _PLAYERBOT_RAIDEOESTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidEoEStrategy : public Strategy class RaidEoEStrategy : public Strategy

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#include "RaidGruulsLairTriggers.h" #include "RaidGruulsLairTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger> class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H #define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidGruulsLairStrategy : public Strategy class RaidGruulsLairStrategy : public Strategy
{ {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,277 @@
/*
* 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_RAIDHYJALSUMMITACTIONS_H
#define _PLAYERBOT_RAIDHYJALSUMMITACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
// General
class HyjalSummitEraseTrackersAction : public Action
{
public:
HyjalSummitEraseTrackersAction(
PlayerbotAI* botAI) : Action(botAI, "hyjal summit erase trackers") {}
bool Execute(Event event) override;
};
// Rage Winterchill
class RageWinterchillMisdirectBossToMainTankAction : public AttackAction
{
public:
RageWinterchillMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class RageWinterchillMainTankPositionBossAction : public AttackAction
{
public:
RageWinterchillMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill main tank position boss") {}
bool Execute(Event event) override;
};
class RageWinterchillSpreadRangedInCircleAction : public MovementAction
{
public:
RageWinterchillSpreadRangedInCircleAction(
PlayerbotAI* botAI) : MovementAction(botAI, "rage winterchill spread ranged in circle") {}
bool Execute(Event event) override;
};
class RageWinterchillMeleeGetOutOfDeathAndDecayAction : public AttackAction
{
public:
RageWinterchillMeleeGetOutOfDeathAndDecayAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill melee get out of death and decay") {}
bool Execute(Event event) override;
};
// Anetheron
class AnetheronMisdirectBossAndInfernalsToTanksAction : public AttackAction
{
public:
AnetheronMisdirectBossAndInfernalsToTanksAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron misdirect boss and infernals to tanks") {}
bool Execute(Event event) override;
};
class AnetheronMainTankPositionBossAction : public AttackAction
{
public:
AnetheronMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron main tank position boss") {}
bool Execute(Event event) override;
};
class AnetheronSpreadRangedInCircleAction : public MovementAction
{
public:
AnetheronSpreadRangedInCircleAction(
PlayerbotAI* botAI) : MovementAction(botAI, "anetheron spread ranged in circle") {}
bool Execute(Event event) override;
};
class AnetheronBringInfernalToInfernalTankAction : public MovementAction
{
public:
AnetheronBringInfernalToInfernalTankAction(
PlayerbotAI* botAI) : MovementAction(botAI, "anetheron bring infernal to infernal tank") {}
bool Execute(Event event) override;
};
class AnetheronFirstAssistTankPickUpInfernalsAction : public AttackAction
{
public:
AnetheronFirstAssistTankPickUpInfernalsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron first assist tank pick up infernals") {}
bool Execute(Event event) override;
};
class AnetheronAssignDpsPriorityAction : public AttackAction
{
public:
AnetheronAssignDpsPriorityAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron assign dps priority") {}
bool Execute(Event event) override;
};
// Kaz'rogal
class KazrogalMisdirectBossToMainTankAction : public AttackAction
{
public:
KazrogalMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class KazrogalMainTankPositionBossAction : public AttackAction
{
public:
KazrogalMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal main tank position boss") {}
bool Execute(Event event) override;
};
class KazrogalAssistTanksMoveInFrontOfBossAction : public AttackAction
{
public:
KazrogalAssistTanksMoveInFrontOfBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal assist tanks move in front of boss") {}
bool Execute(Event event) override;
};
class KazrogalSpreadRangedInArcAction : public MovementAction
{
public:
KazrogalSpreadRangedInArcAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal spread ranged in arc") {}
bool Execute(Event event) override;
};
class KazrogalLowManaBotTakeDefensiveMeasuresAction : public MovementAction
{
public:
KazrogalLowManaBotTakeDefensiveMeasuresAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal low mana bot take defensive measures") {}
bool Execute(Event event) override;
};
class KazrogalCastShadowProtectionSpellAction : public Action
{
public:
KazrogalCastShadowProtectionSpellAction(
PlayerbotAI* botAI) : Action(botAI, "kaz'rogal cast shadow protection spell") {}
bool Execute(Event event) override;
};
// Azgalor
class AzgalorMisdirectBossToMainTankAction : public AttackAction
{
public:
AzgalorMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class AzgalorMainTankPositionBossAction : public AttackAction
{
public:
AzgalorMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor main tank position boss") {}
bool Execute(Event event) override;
};
class AzgalorWaitAtSafePositionAction : public MovementAction
{
public:
AzgalorWaitAtSafePositionAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor wait at safe position") {}
bool Execute(Event event) override;
};
class AzgalorDisperseRangedAction : public MovementAction
{
public:
AzgalorDisperseRangedAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor disperse ranged") {}
bool Execute(Event event) override;
};
class AzgalorMeleeGetOutOfFireAndSwapTargetsAction : public AttackAction
{
public:
AzgalorMeleeGetOutOfFireAndSwapTargetsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor melee get out of fire and swap targets") {}
bool Execute(Event event) override;
};
class AzgalorMoveToDoomguardTankAction : public MovementAction
{
public:
AzgalorMoveToDoomguardTankAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor move to doomguard tank") {}
bool Execute(Event event) override;
};
class AzgalorFirstAssistTankPositionDoomguardAction : public AttackAction
{
public:
AzgalorFirstAssistTankPositionDoomguardAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor first assist tank position doomguard") {}
bool Execute(Event event) override;
};
class AzgalorRangedDpsPrioritizeDoomguardsAction : public AttackAction
{
public:
AzgalorRangedDpsPrioritizeDoomguardsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor ranged dps prioritize doomguards") {}
bool Execute(Event event) override;
};
// Archimonde
class ArchimondeMisdirectBossToMainTankAction : public AttackAction
{
public:
ArchimondeMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "archimonde misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class ArchimondeMoveBossToInitialPositionAction : public AttackAction
{
public:
ArchimondeMoveBossToInitialPositionAction(
PlayerbotAI* botAI) : AttackAction(botAI, "archimonde move boss to initial position") {}
bool Execute(Event event) override;
};
class ArchimondeCastFearImmunitySpellAction : public Action
{
public:
ArchimondeCastFearImmunitySpellAction(
PlayerbotAI* botAI) : Action(botAI, "archimonde cast fear immunity spell") {}
bool Execute(Event event) override;
private:
bool CastFearWardOnMainTank();
bool UseTremorTotemStrategy();
};
class ArchimondeSpreadToAvoidAirBurstAction : public MovementAction
{
public:
ArchimondeSpreadToAvoidAirBurstAction(
PlayerbotAI* botAI) : MovementAction(botAI, "archimonde spread to avoid air burst") {}
bool Execute(Event event) override;
};
class ArchimondeAvoidDoomfireAction : public MovementAction
{
public:
ArchimondeAvoidDoomfireAction(
PlayerbotAI* botAI) : MovementAction(botAI, "archimonde avoid doomfire") {}
bool Execute(Event event) override;
};
class ArchimondeRemoveDoomfireDotAction : public Action
{
public:
ArchimondeRemoveDoomfireDotAction(
PlayerbotAI* botAI) : Action(botAI, "archimonde remove doomfire dot") {}
bool Execute(Event event) override;
};
#endif

View File

@ -0,0 +1,295 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidHyjalSummitMultipliers.h"
#include "RaidHyjalSummitActions.h"
#include "RaidHyjalSummitHelpers.h"
#include "AiFactory.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidBearActions.h"
#include "HunterActions.h"
#include "PaladinActions.h"
#include "RaidBossHelpers.h"
#include "ReachTargetActions.h"
#include "ShamanActions.h"
#include "WarriorActions.h"
using namespace HyjalSummitHelpers;
// Without this multiplier, Bloodlust/Heroism will not be available for
// bosses because it will be used on cooldown during trash waves
float HyjalSummitTimeBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (archimonde && archimonde->GetHealthPct() < 90.0f)
return 1.0f;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (azgalor && azgalor->GetHealthPct() < 90.0f)
return 1.0f;
Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal");
if (kazrogal && kazrogal->GetHealthPct() < 90.0f)
return 1.0f;
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (anetheron && anetheron->GetHealthPct() < 85.0f)
return 1.0f;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (winterchill && winterchill->GetHealthPct() < 90.0f)
return 1.0f;
return 0.0f;
}
return 1.0f;
}
// Rage Winterchill
float RageWinterchillDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "rage winterchill"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
float RageWinterchillMeleeControlAvoidanceMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot))
return 1.0f;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (!winterchill)
return 1.0f;
if (IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS + 2.0f))
{
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (botAI->IsMainTank(bot) || winterchill->GetVictim() == bot)
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<RageWinterchillMeleeGetOutOfDeathAndDecayAction*>(action))
return 0.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Anetheron
float AnetheronDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (bot->GetVictim() != nullptr &&
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
float AnetheronDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
float AnetheronControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER ||
!AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
return 1.0f;
}
// Kaz'rogal
float KazrogalLowManaBotStayAwayFromGroupMultiplier::GetValue(Action* action)
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT || bot->getClass() == CLASS_HUNTER)
return 1.0f;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (!isBelowManaThreshold.count(bot->GetGUID()))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<KazrogalLowManaBotTakeDefensiveMeasuresAction*>(action)))
return 0.0f;
return 1.0f;
}
float KazrogalKeepAspectOfTheViperActiveMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER || bot->GetPower(POWER_MANA) > 4000 ||
!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (dynamic_cast<CastAspectOfTheHawkAction*>(action) ||
dynamic_cast<CastAspectOfTheWildAction*>(action) ||
dynamic_cast<CastAspectOfTheDragonhawkAction*>(action) ||
dynamic_cast<CastAspectOfTheCheetahAction*>(action) ||
dynamic_cast<CastAspectOfThePackAction*>(action) ||
dynamic_cast<CastAspectOfTheMonkeyAction*>(action))
return 0.0f;
return 1.0f;
}
float KazrogalControlMovementMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
if (botAI->IsRanged(bot) && dynamic_cast<ReachTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// Azgalor
float AzgalorDisableTankActionsMultiplier::GetValue(Action* action)
{
if (bot->GetVictim() == nullptr)
return 1.0f;
if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "azgalor"))
return 1.0f;
if (dynamic_cast<TankFaceAction*>(action))
return 0.0f;
if (dynamic_cast<TankAssistAction*>(action) || dynamic_cast<AvoidAoeAction*>(action))
{
if (botAI->IsMainTank(bot))
{
return 0.0f;
}
else if (botAI->IsAssistTank(bot) && (AnyGroupMemberHasDoom(bot) ||
AI_VALUE2(Unit*, "find target", "lesser doomguard")))
{
return 0.0f;
}
}
return 1.0f;
}
float AzgalorDoomedBotPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<AvoidAoeAction*>(action) &&
!dynamic_cast<AzgalorMoveToDoomguardTankAction*>(action))
return 0.0f;
return 1.0f;
}
float AzgalorMeleeDpsControlAvoidanceMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor)
return 1.0f;
constexpr float singleTickMoveAwayDist = 6.0f;
if (IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS + singleTickMoveAwayDist))
{
if (dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AzgalorMeleeGetOutOfFireAndSwapTargetsAction*>(action))
return 0.0f;
}
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || !GET_PLAYERBOT_AI(mainTank))
return 1.0f;
TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot);
if ((tankState == TankPositionState::Unknown ||
tankState == TankPositionState::MovingToTransition) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AzgalorWaitAtSafePositionAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
// Archimonde
float ArchimondeDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "archimonde"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}

View File

@ -0,0 +1,125 @@
/*
* 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_RAIDHYJALSUMMITMULTIPLIERS_H
#define _PLAYERBOT_RAIDHYJALSUMMITMULTIPLIERS_H
#include "Multiplier.h"
class HyjalSummitTimeBloodlustAndHeroismMultiplier : public Multiplier
{
public:
HyjalSummitTimeBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hyjal summit time bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
// Rage Winterchill
class RageWinterchillDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
RageWinterchillDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class RageWinterchillMeleeControlAvoidanceMultiplier : public Multiplier
{
public:
RageWinterchillMeleeControlAvoidanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill melee control avoidance multiplier") {}
virtual float GetValue(Action* action);
};
// Anetheron
class AnetheronDisableTankActionsMultiplier : public Multiplier
{
public:
AnetheronDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable tank actions multiplier") {}
virtual float GetValue(Action* action);
};
class AnetheronDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
AnetheronDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class AnetheronControlMisdirectionMultiplier : public Multiplier
{
public:
AnetheronControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron control misdirection multiplier") {}
virtual float GetValue(Action* action);
};
// Kaz'rogal
class KazrogalLowManaBotStayAwayFromGroupMultiplier : public Multiplier
{
public:
KazrogalLowManaBotStayAwayFromGroupMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal low mana bot stay away from group multiplier") {}
virtual float GetValue(Action* action);
};
class KazrogalKeepAspectOfTheViperActiveMultiplier : public Multiplier
{
public:
KazrogalKeepAspectOfTheViperActiveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal keep aspect of the viper active multiplier") {}
virtual float GetValue(Action* action);
};
class KazrogalControlMovementMultiplier : public Multiplier
{
public:
KazrogalControlMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal control movement multiplier") {}
virtual float GetValue(Action* action);
};
// Azgalor
class AzgalorDisableTankActionsMultiplier : public Multiplier
{
public:
AzgalorDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor disable tank actions multiplier") {}
virtual float GetValue(Action* action);
};
class AzgalorDoomedBotPrioritizePositioningMultiplier : public Multiplier
{
public:
AzgalorDoomedBotPrioritizePositioningMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor doomed bot prioritize positioning multiplier") {}
virtual float GetValue(Action* action);
};
class AzgalorMeleeDpsControlAvoidanceMultiplier : public Multiplier
{
public:
AzgalorMeleeDpsControlAvoidanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor melee dps control avoidance multiplier") {}
virtual float GetValue(Action* action);
};
// Archimonde
class ArchimondeDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
ArchimondeDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "archimonde disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
#endif

View File

@ -0,0 +1,218 @@
/*
* 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_RAIDHYJALSUMMITACTIONCONTEXT_H
#define _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H
#include "RaidHyjalSummitActions.h"
#include "NamedObjectContext.h"
class RaidHyjalSummitActionContext : public NamedObjectContext<Action>
{
public:
RaidHyjalSummitActionContext()
{
// General
creators["hyjal summit erase trackers"] =
&RaidHyjalSummitActionContext::hyjal_summit_erase_trackers;
// Rage Winterchill
creators["rage winterchill misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::rage_winterchill_misdirect_boss_to_main_tank;
creators["rage winterchill main tank position boss"] =
&RaidHyjalSummitActionContext::rage_winterchill_main_tank_position_boss;
creators["rage winterchill spread ranged in circle"] =
&RaidHyjalSummitActionContext::rage_winterchill_spread_ranged_in_circle;
creators["rage winterchill melee get out of death and decay"] =
&RaidHyjalSummitActionContext::rage_winterchill_melee_get_out_of_death_and_decay;
// Anetheron
creators["anetheron misdirect boss and infernals to tanks"] =
&RaidHyjalSummitActionContext::anetheron_misdirect_boss_and_infernals_to_tanks;
creators["anetheron main tank position boss"] =
&RaidHyjalSummitActionContext::anetheron_main_tank_position_boss;
creators["anetheron spread ranged in circle"] =
&RaidHyjalSummitActionContext::anetheron_spread_ranged_in_circle;
creators["anetheron bring infernal to infernal tank"] =
&RaidHyjalSummitActionContext::anetheron_bring_infernal_to_infernal_tank;
creators["anetheron first assist tank pick up infernals"] =
&RaidHyjalSummitActionContext::anetheron_first_assist_tank_pick_up_infernals;
creators["anetheron assign dps priority"] =
&RaidHyjalSummitActionContext::anetheron_assign_dps_priority;
// Kaz'rogal
creators["kaz'rogal misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::kazrogal_misdirect_boss_to_main_tank;
creators["kaz'rogal main tank position boss"] =
&RaidHyjalSummitActionContext::kazrogal_main_tank_position_boss;
creators["kaz'rogal assist tanks move in front of boss"] =
&RaidHyjalSummitActionContext::kazrogal_assist_tanks_move_in_front_of_boss;
creators["kaz'rogal spread ranged in arc"] =
&RaidHyjalSummitActionContext::kazrogal_spread_ranged_in_arc;
creators["kaz'rogal low mana bot take defensive measures"] =
&RaidHyjalSummitActionContext::kazrogal_low_mana_bot_take_defensive_measures;
creators["kaz'rogal cast shadow protection spell"] =
&RaidHyjalSummitActionContext::kazrogal_cast_shadow_protection_spell;
// Azgalor
creators["azgalor misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::azgalor_misdirect_boss_to_main_tank;
creators["azgalor main tank position boss"] =
&RaidHyjalSummitActionContext::azgalor_main_tank_position_boss;
creators["azgalor wait at safe position"] =
&RaidHyjalSummitActionContext::azgalor_wait_at_safe_position;
creators["azgalor disperse ranged"] =
&RaidHyjalSummitActionContext::azgalor_disperse_ranged;
creators["azgalor melee get out of fire and swap targets"] =
&RaidHyjalSummitActionContext::azgalor_melee_get_out_of_fire_and_swap_targets;
creators["azgalor move to doomguard tank"] =
&RaidHyjalSummitActionContext::azgalor_move_to_doomguard_tank;
creators["azgalor first assist tank position doomguard"] =
&RaidHyjalSummitActionContext::azgalor_first_assist_tank_position_doomguard;
creators["azgalor ranged dps prioritize doomguards"] =
&RaidHyjalSummitActionContext::azgalor_ranged_dps_prioritize_doomguards;
// Archimonde
creators["archimonde misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::archimonde_misdirect_boss_to_main_tank;
creators["archimonde move boss to initial position"] =
&RaidHyjalSummitActionContext::archimonde_move_boss_to_initial_position;
creators["archimonde cast fear immunity spell"] =
&RaidHyjalSummitActionContext::archimonde_cast_fear_immunity_spell;
creators["archimonde spread to avoid air burst"] =
&RaidHyjalSummitActionContext::archimonde_spread_to_avoid_air_burst;
creators["archimonde avoid doomfire"] =
&RaidHyjalSummitActionContext::archimonde_avoid_doomfire;
creators["archimonde remove doomfire dot"] =
&RaidHyjalSummitActionContext::archimonde_remove_doomfire_dot;
}
private:
// General
static Action* hyjal_summit_erase_trackers(
PlayerbotAI* botAI) { return new HyjalSummitEraseTrackersAction(botAI); }
// Rage Winterchill
static Action* rage_winterchill_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new RageWinterchillMisdirectBossToMainTankAction(botAI); }
static Action* rage_winterchill_main_tank_position_boss(
PlayerbotAI* botAI) { return new RageWinterchillMainTankPositionBossAction(botAI); }
static Action* rage_winterchill_spread_ranged_in_circle(
PlayerbotAI* botAI) { return new RageWinterchillSpreadRangedInCircleAction(botAI); }
static Action* rage_winterchill_melee_get_out_of_death_and_decay(
PlayerbotAI* botAI) { return new RageWinterchillMeleeGetOutOfDeathAndDecayAction(botAI); }
// Anetheron
static Action* anetheron_misdirect_boss_and_infernals_to_tanks(
PlayerbotAI* botAI) { return new AnetheronMisdirectBossAndInfernalsToTanksAction(botAI); }
static Action* anetheron_main_tank_position_boss(
PlayerbotAI* botAI) { return new AnetheronMainTankPositionBossAction(botAI); }
static Action* anetheron_spread_ranged_in_circle(
PlayerbotAI* botAI) { return new AnetheronSpreadRangedInCircleAction(botAI); }
static Action* anetheron_bring_infernal_to_infernal_tank(
PlayerbotAI* botAI) { return new AnetheronBringInfernalToInfernalTankAction(botAI); }
static Action* anetheron_first_assist_tank_pick_up_infernals(
PlayerbotAI* botAI) { return new AnetheronFirstAssistTankPickUpInfernalsAction(botAI); }
static Action* anetheron_assign_dps_priority(
PlayerbotAI* botAI) { return new AnetheronAssignDpsPriorityAction(botAI); }
// Kaz'rogal
static Action* kazrogal_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new KazrogalMisdirectBossToMainTankAction(botAI); }
static Action* kazrogal_main_tank_position_boss(
PlayerbotAI* botAI) { return new KazrogalMainTankPositionBossAction(botAI); }
static Action* kazrogal_assist_tanks_move_in_front_of_boss(
PlayerbotAI* botAI) { return new KazrogalAssistTanksMoveInFrontOfBossAction(botAI); }
static Action* kazrogal_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new KazrogalSpreadRangedInArcAction(botAI); }
static Action* kazrogal_low_mana_bot_take_defensive_measures(
PlayerbotAI* botAI) { return new KazrogalLowManaBotTakeDefensiveMeasuresAction(botAI); }
static Action* kazrogal_cast_shadow_protection_spell(
PlayerbotAI* botAI) { return new KazrogalCastShadowProtectionSpellAction(botAI); }
// Azgalor
static Action* azgalor_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new AzgalorMisdirectBossToMainTankAction(botAI); }
static Action* azgalor_main_tank_position_boss(
PlayerbotAI* botAI) { return new AzgalorMainTankPositionBossAction(botAI); }
static Action* azgalor_wait_at_safe_position(
PlayerbotAI* botAI) { return new AzgalorWaitAtSafePositionAction(botAI); }
static Action* azgalor_disperse_ranged(
PlayerbotAI* botAI) { return new AzgalorDisperseRangedAction(botAI); }
static Action* azgalor_melee_get_out_of_fire_and_swap_targets(
PlayerbotAI* botAI) { return new AzgalorMeleeGetOutOfFireAndSwapTargetsAction(botAI); }
static Action* azgalor_move_to_doomguard_tank(
PlayerbotAI* botAI) { return new AzgalorMoveToDoomguardTankAction(botAI); }
static Action* azgalor_first_assist_tank_position_doomguard(
PlayerbotAI* botAI) { return new AzgalorFirstAssistTankPositionDoomguardAction(botAI); }
static Action* azgalor_ranged_dps_prioritize_doomguards(
PlayerbotAI* botAI) { return new AzgalorRangedDpsPrioritizeDoomguardsAction(botAI); }
// Archimonde
static Action* archimonde_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new ArchimondeMisdirectBossToMainTankAction(botAI); }
static Action* archimonde_move_boss_to_initial_position(
PlayerbotAI* botAI) { return new ArchimondeMoveBossToInitialPositionAction(botAI); }
static Action* archimonde_cast_fear_immunity_spell(
PlayerbotAI* botAI) { return new ArchimondeCastFearImmunitySpellAction(botAI); }
static Action* archimonde_spread_to_avoid_air_burst(
PlayerbotAI* botAI) { return new ArchimondeSpreadToAvoidAirBurstAction(botAI); }
static Action* archimonde_avoid_doomfire(
PlayerbotAI* botAI) { return new ArchimondeAvoidDoomfireAction(botAI); }
static Action* archimonde_remove_doomfire_dot(
PlayerbotAI* botAI) { return new ArchimondeRemoveDoomfireDotAction(botAI); }
};
#endif

View File

@ -0,0 +1,218 @@
/*
* 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_RAIDHYJALSUMMITTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H
#include "RaidHyjalSummitTriggers.h"
#include "NamedObjectContext.h"
class RaidHyjalSummitTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidHyjalSummitTriggerContext()
{
// General
creators["hyjal summit bot is not in combat"] =
&RaidHyjalSummitTriggerContext::hyjal_summit_bot_is_not_in_combat;
// Rage Winterchill
creators["rage winterchill pulling boss"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_pulling_boss;
creators["rage winterchill boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_boss_engaged_by_main_tank;
creators["rage winterchill boss casts death and decay on ranged"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_boss_casts_death_and_decay_on_ranged;
creators["rage winterchill melee is standing in death and decay"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_melee_is_standing_in_death_and_decay;
// Anetheron
creators["anetheron pulling boss or infernal"] =
&RaidHyjalSummitTriggerContext::anetheron_pulling_boss_or_infernal;
creators["anetheron boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::anetheron_boss_engaged_by_main_tank;
creators["anetheron boss casts carrion swarm"] =
&RaidHyjalSummitTriggerContext::anetheron_boss_casts_carrion_swarm;
creators["anetheron bot is targeted by infernal"] =
&RaidHyjalSummitTriggerContext::anetheron_bot_is_targeted_by_infernal;
creators["anetheron infernals need to be kept away from raid"] =
&RaidHyjalSummitTriggerContext::anetheron_infernals_need_to_be_kept_away_from_raid;
creators["anetheron infernals continue to spawn"] =
&RaidHyjalSummitTriggerContext::anetheron_infernals_continue_to_spawn;
// Kaz'rogal
creators["kaz'rogal pulling boss"] =
&RaidHyjalSummitTriggerContext::kazrogal_pulling_boss;
creators["kaz'rogal boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_main_tank;
creators["kaz'rogal boss engaged by assist tanks"] =
&RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_assist_tanks;
creators["kaz'rogal bot is low on mana"] =
&RaidHyjalSummitTriggerContext::kazrogal_bot_is_low_on_mana;
creators["kaz'rogal low mana bots need escape path"] =
&RaidHyjalSummitTriggerContext::kazrogal_low_mana_bots_need_escape_path;
creators["kaz'rogal mark deals shadow damage"] =
&RaidHyjalSummitTriggerContext::kazrogal_mark_deals_shadow_damage;
// Azgalor
creators["azgalor pulling boss"] =
&RaidHyjalSummitTriggerContext::azgalor_pulling_boss;
creators["azgalor boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_main_tank;
creators["azgalor main tank is positioning boss"] =
&RaidHyjalSummitTriggerContext::azgalor_main_tank_is_positioning_boss;
creators["azgalor boss engaged by ranged"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_ranged;
creators["azgalor boss casts rain of fire on melee"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_casts_rain_of_fire_on_melee;
creators["azgalor bot is doomed"] =
&RaidHyjalSummitTriggerContext::azgalor_bot_is_doomed;
creators["azgalor doomguards must be controlled"] =
&RaidHyjalSummitTriggerContext::azgalor_doomguards_must_be_controlled;
creators["azgalor doomguards must die"] =
&RaidHyjalSummitTriggerContext::azgalor_doomguards_must_die;
// Archimonde
creators["archimonde pulling boss"] =
&RaidHyjalSummitTriggerContext::archimonde_pulling_boss;
creators["archimonde boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_engaged_by_main_tank;
creators["archimonde boss casts fear"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_casts_fear;
creators["archimonde boss casts air burst"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_casts_air_burst;
creators["archimonde boss summoned doomfire"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_summoned_doomfire;
creators["archimonde bot stood in doomfire"] =
&RaidHyjalSummitTriggerContext::archimonde_bot_stood_in_doomfire;
}
private:
// General
static Trigger* hyjal_summit_bot_is_not_in_combat(
PlayerbotAI* botAI) { return new HyjalSummitBotIsNotInCombatTrigger(botAI); }
// Rage Winterchill
static Trigger* rage_winterchill_pulling_boss(
PlayerbotAI* botAI) { return new RageWinterchillPullingBossTrigger(botAI); }
static Trigger* rage_winterchill_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new RageWinterchillBossEngagedByMainTankTrigger(botAI); }
static Trigger* rage_winterchill_boss_casts_death_and_decay_on_ranged(
PlayerbotAI* botAI) { return new RageWinterchillBossCastsDeathAndDecayOnRangedTrigger(botAI); }
static Trigger* rage_winterchill_melee_is_standing_in_death_and_decay(
PlayerbotAI* botAI) { return new RageWinterchillMeleeIsStandingInDeathAndDecayTrigger(botAI); }
// Anetheron
static Trigger* anetheron_pulling_boss_or_infernal(
PlayerbotAI* botAI) { return new AnetheronPullingBossOrInfernalTrigger(botAI); }
static Trigger* anetheron_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new AnetheronBossEngagedByMainTankTrigger(botAI); }
static Trigger* anetheron_boss_casts_carrion_swarm(
PlayerbotAI* botAI) { return new AnetheronBossCastsCarrionSwarmTrigger(botAI); }
static Trigger* anetheron_bot_is_targeted_by_infernal(
PlayerbotAI* botAI) { return new AnetheronBotIsTargetedByInfernalTrigger(botAI); }
static Trigger* anetheron_infernals_need_to_be_kept_away_from_raid(
PlayerbotAI* botAI) { return new AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger(botAI); }
static Trigger* anetheron_infernals_continue_to_spawn(
PlayerbotAI* botAI) { return new AnetheronInfernalsContinueToSpawnTrigger(botAI); }
// Kaz'rogal
static Trigger* kazrogal_pulling_boss(
PlayerbotAI* botAI) { return new KazrogalPullingBossTrigger(botAI); }
static Trigger* kazrogal_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new KazrogalBossEngagedByMainTankTrigger(botAI); }
static Trigger* kazrogal_boss_engaged_by_assist_tanks(
PlayerbotAI* botAI) { return new KazrogalBossEngagedByAssistTanksTrigger(botAI); }
static Trigger* kazrogal_low_mana_bots_need_escape_path(
PlayerbotAI* botAI) { return new KazrogalLowManaBotsNeedEscapePathTrigger(botAI); }
static Trigger* kazrogal_bot_is_low_on_mana(
PlayerbotAI* botAI) { return new KazrogalBotIsLowOnManaTrigger(botAI); }
static Trigger* kazrogal_mark_deals_shadow_damage(
PlayerbotAI* botAI) { return new KazrogalMarkDealsShadowDamageTrigger(botAI); }
// Azgalor
static Trigger* azgalor_pulling_boss(
PlayerbotAI* botAI) { return new AzgalorPullingBossTrigger(botAI); }
static Trigger* azgalor_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new AzgalorBossEngagedByMainTankTrigger(botAI); }
static Trigger* azgalor_main_tank_is_positioning_boss(
PlayerbotAI* botAI) { return new AzgalorMainTankIsPositioningBossTrigger(botAI); }
static Trigger* azgalor_boss_engaged_by_ranged(
PlayerbotAI* botAI) { return new AzgalorBossEngagedByRangedTrigger(botAI); }
static Trigger* azgalor_boss_casts_rain_of_fire_on_melee(
PlayerbotAI* botAI) { return new AzgalorBossCastsRainOfFireOnMeleeTrigger(botAI); }
static Trigger* azgalor_bot_is_doomed(
PlayerbotAI* botAI) { return new AzgalorBotIsDoomedTrigger(botAI); }
static Trigger* azgalor_doomguards_must_be_controlled(
PlayerbotAI* botAI) { return new AzgalorDoomguardsMustBeControlledTrigger(botAI); }
static Trigger* azgalor_doomguards_must_die(
PlayerbotAI* botAI) { return new AzgalorDoomguardsMustDieTrigger(botAI); }
// Archimonde
static Trigger* archimonde_pulling_boss(
PlayerbotAI* botAI) { return new ArchimondePullingBossTrigger(botAI); }
static Trigger* archimonde_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new ArchimondeBossEngagedByMainTankTrigger(botAI); }
static Trigger* archimonde_boss_casts_fear(
PlayerbotAI* botAI) { return new ArchimondeBossCastsFearTrigger(botAI); }
static Trigger* archimonde_boss_casts_air_burst(
PlayerbotAI* botAI) { return new ArchimondeBossCastsAirBurstTrigger(botAI); }
static Trigger* archimonde_boss_summoned_doomfire(
PlayerbotAI* botAI) { return new ArchimondeBossSummonedDoomfireTrigger(botAI); }
static Trigger* archimonde_bot_stood_in_doomfire(
PlayerbotAI* botAI) { return new ArchimondeBotStoodInDoomfireTrigger(botAI); }
};
#endif

View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidHyjalSummitStrategy.h"
#include "RaidHyjalSummitMultipliers.h"
void RaidHyjalSummitStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// General
triggers.push_back(new TriggerNode("hyjal summit bot is not in combat", {
NextAction("hyjal summit erase trackers", ACTION_EMERGENCY + 11) }));
// Rage Winterchill
triggers.push_back(new TriggerNode("rage winterchill pulling boss", {
NextAction("rage winterchill misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("rage winterchill boss engaged by main tank", {
NextAction("rage winterchill main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("rage winterchill boss casts death and decay on ranged", {
NextAction("rage winterchill spread ranged in circle", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("rage winterchill melee is standing in death and decay", {
NextAction("rage winterchill melee get out of death and decay", ACTION_EMERGENCY + 1) }));
// Anetheron
triggers.push_back(new TriggerNode("anetheron pulling boss or infernal", {
NextAction("anetheron misdirect boss and infernals to tanks", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("anetheron boss engaged by main tank", {
NextAction("anetheron main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("anetheron boss casts carrion swarm", {
NextAction("anetheron spread ranged in circle", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("anetheron bot is targeted by infernal", {
NextAction("anetheron bring infernal to infernal tank", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("anetheron infernals need to be kept away from raid", {
NextAction("anetheron first assist tank pick up infernals", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("anetheron infernals continue to spawn", {
NextAction("anetheron assign dps priority", ACTION_RAID + 1) }));
// Kaz'rogal
triggers.push_back(new TriggerNode("kaz'rogal pulling boss", {
NextAction("kaz'rogal misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("kaz'rogal boss engaged by main tank", {
NextAction("kaz'rogal main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal boss engaged by assist tanks", {
NextAction("kaz'rogal assist tanks move in front of boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal low mana bots need escape path", {
NextAction("kaz'rogal spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal bot is low on mana", {
NextAction("kaz'rogal low mana bot take defensive measures", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal mark deals shadow damage", {
NextAction("kaz'rogal cast shadow protection spell", ACTION_EMERGENCY + 6) }));
// Azgalor
triggers.push_back(new TriggerNode("azgalor pulling boss", {
NextAction("azgalor misdirect boss to main tank", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("azgalor boss engaged by main tank", {
NextAction("azgalor main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("azgalor main tank is positioning boss", {
NextAction("azgalor wait at safe position", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("azgalor boss engaged by ranged", {
NextAction("azgalor disperse ranged", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("azgalor boss casts rain of fire on melee", {
NextAction("azgalor melee get out of fire and swap targets", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("azgalor bot is doomed", {
NextAction("azgalor move to doomguard tank", ACTION_EMERGENCY + 3) }));
triggers.push_back(new TriggerNode("azgalor doomguards must be controlled", {
NextAction("azgalor first assist tank position doomguard", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("azgalor doomguards must die", {
NextAction("azgalor ranged dps prioritize doomguards", ACTION_RAID + 1) }));
// Archimonde
triggers.push_back(new TriggerNode("archimonde pulling boss", {
NextAction("archimonde misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss engaged by main tank", {
NextAction("archimonde move boss to initial position", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss casts fear", {
NextAction("archimonde cast fear immunity spell", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss casts air burst", {
NextAction("archimonde spread to avoid air burst", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("archimonde boss summoned doomfire", {
NextAction("archimonde avoid doomfire", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("archimonde bot stood in doomfire", {
NextAction("archimonde remove doomfire dot", ACTION_EMERGENCY + 7) }));
}
void RaidHyjalSummitStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Trash
multipliers.push_back(new HyjalSummitTimeBloodlustAndHeroismMultiplier(botAI));
// Rage Winterchill
multipliers.push_back(new RageWinterchillDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new RageWinterchillMeleeControlAvoidanceMultiplier(botAI));
// Anetheron
multipliers.push_back(new AnetheronDisableTankActionsMultiplier(botAI));
multipliers.push_back(new AnetheronDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new AnetheronControlMisdirectionMultiplier(botAI));
// Kaz'rogal
multipliers.push_back(new KazrogalLowManaBotStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new KazrogalKeepAspectOfTheViperActiveMultiplier(botAI));
multipliers.push_back(new KazrogalControlMovementMultiplier(botAI));
// Azgalor
multipliers.push_back(new AzgalorDisableTankActionsMultiplier(botAI));
multipliers.push_back(new AzgalorDoomedBotPrioritizePositioningMultiplier(botAI));
multipliers.push_back(new AzgalorMeleeDpsControlAvoidanceMultiplier(botAI));
// Archimonde
multipliers.push_back(new ArchimondeDisableCombatFormationMoveMultiplier(botAI));
}

View File

@ -0,0 +1,22 @@
/*
* 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_RAIDHYJALSUMMITSTRATEGY_H_
#define _PLAYERBOT_RAIDHYJALSUMMITSTRATEGY_H_
#include "Strategy.h"
class RaidHyjalSummitStrategy : public Strategy
{
public:
RaidHyjalSummitStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "hyjal"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@ -0,0 +1,357 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidHyjalSummitTriggers.h"
#include "RaidHyjalSummitHelpers.h"
#include "RaidHyjalSummitActions.h"
#include "AiFactory.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace HyjalSummitHelpers;
// General
bool HyjalSummitBotIsNotInCombatTrigger::IsActive()
{
return !bot->IsInCombat() && bot->GetMapId() == HYJAL_SUMMIT_MAP_ID;
}
// Rage Winterchill
bool RageWinterchillPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
return winterchill && winterchill->GetHealthPct() > 95.0f;
}
bool RageWinterchillBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "rage winterchill");
}
bool RageWinterchillBossCastsDeathAndDecayOnRangedTrigger::IsActive()
{
return botAI->IsRanged(bot) &&
AI_VALUE2(Unit*, "find target", "rage winterchill");
}
bool RageWinterchillMeleeIsStandingInDeathAndDecayTrigger::IsActive()
{
if (botAI->IsRanged(bot))
return false;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (!winterchill || winterchill->GetVictim() == bot)
return false;
if (botAI->IsMainTank(bot))
return false;
return IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS);
}
// Anetheron
bool AnetheronPullingBossOrInfernalTrigger::IsActive()
{
return bot->getClass() == CLASS_HUNTER &&
AI_VALUE2(Unit*, "find target", "anetheron");
}
bool AnetheronBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron");
}
bool AnetheronBossCastsCarrionSwarmTrigger::IsActive()
{
if (botAI->IsMelee(bot))
return false;
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (!anetheron)
return false;
return GetInfernoTarget(anetheron) != bot;
}
bool AnetheronBotIsTargetedByInfernalTrigger::IsActive()
{
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (!anetheron || botAI->IsMainTank(bot))
return false;
return GetInfernoTarget(anetheron) == bot;
}
bool AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger::IsActive()
{
return botAI->IsAssistTankOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "towering infernal");
}
bool AnetheronInfernalsContinueToSpawnTrigger::IsActive()
{
return !botAI->IsTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron");
}
// Kaz'rogal
bool KazrogalPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal");
return kazrogal && kazrogal->GetHealthPct() > 95.0f;
}
bool KazrogalBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "kaz'rogal");
}
bool KazrogalBossEngagedByAssistTanksTrigger::IsActive()
{
if (!botAI->IsAssistTank(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
return bot->GetPower(POWER_MANA) > 3000;
}
bool KazrogalLowManaBotsNeedEscapePathTrigger::IsActive()
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT)
return false;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (bot->getClass() == CLASS_HUNTER)
{
return true;
}
else if (bot->GetPower(POWER_MANA) > 4000)
{
isBelowManaThreshold.erase(bot->GetGUID());
if (botAI->IsMelee(bot))
return false;
else
return true;
}
return false;
}
bool KazrogalBotIsLowOnManaTrigger::IsActive()
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT)
return false;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (botAI->HasAnyAuraOf(bot, "ice block", "divine shield", nullptr))
return false;
if (isBelowManaThreshold.count(bot->GetGUID()) ||
bot->GetPower(POWER_MANA) <= 3200)
return true;
return false;
}
bool KazrogalMarkDealsShadowDamageTrigger::IsActive()
{
if (bot->getClass() != CLASS_PALADIN && bot->getClass() != CLASS_WARLOCK)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (bot->getClass() == CLASS_PALADIN &&
(botAI->HasAura("shadow resistance aura", bot) ||
botAI->HasAura("prayer of shadow protection", bot) ||
botAI->HasAura("shadow protection", bot)))
return false;
return bot->HasAura(
static_cast<uint32>(HyjalSummitSpells::SPELL_MARK_OF_KAZROGAL));
}
// Azgalor
bool AzgalorPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
return azgalor && azgalor->GetHealthPct() > 95.0f;
}
bool AzgalorBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "azgalor");
}
bool AzgalorMainTankIsPositioningBossTrigger::IsActive()
{
if (botAI->IsRanged(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor || azgalor->GetVictim() == bot)
return false;
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || !GET_PLAYERBOT_AI(mainTank) || botAI->IsMainTank(bot))
return false;
TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot);
return tankState == TankPositionState::Unknown ||
tankState == TankPositionState::MovingToTransition;
}
bool AzgalorBossEngagedByRangedTrigger::IsActive()
{
if (botAI->IsMelee(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
return azgalor && azgalor->GetVictim() != bot &&
!bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM));
}
bool AzgalorBossCastsRainOfFireOnMeleeTrigger::IsActive()
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor || azgalor->GetVictim() == bot ||
bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return false;
return IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS);
}
bool AzgalorBotIsDoomedTrigger::IsActive()
{
return bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM));
}
bool AzgalorDoomguardsMustBeControlledTrigger::IsActive()
{
if (!botAI->IsAssistTank(bot) ||
!AI_VALUE2(Unit*, "find target", "azgalor"))
return false;
if (botAI->IsAssistTankOfIndex(bot, 0, true))
{
return AI_VALUE2(Unit*, "find target", "lesser doomguard") ||
AnyGroupMemberHasDoom(bot);
}
if (botAI->IsAssistTankOfIndex(bot, 1, true))
{
// Trigger for second assist tank only if first assist tank has Doom
Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0);
if (firstAssistTank &&
!firstAssistTank->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return false;
return AI_VALUE2(Unit*, "find target", "lesser doomguard") ||
AnyGroupMemberHasDoom(bot);
}
return false;
}
bool AzgalorDoomguardsMustDieTrigger::IsActive()
{
return botAI->IsRangedDps(bot) && AI_VALUE2(Unit*, "find target", "azgalor");
}
// Archimonde
bool ArchimondePullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 95.0f;
}
bool ArchimondeBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 95.0f;
}
bool ArchimondeBossCastsFearTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST &&
bot->getClass() != CLASS_SHAMAN)
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 10.0f;
}
bool ArchimondeBossCastsAirBurstTrigger::IsActive()
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (!archimonde || archimonde->GetHealthPct() <= 10.0f ||
archimonde->GetVictim() == bot)
return false;
return !botAI->IsMainTank(bot);
}
bool ArchimondeBossSummonedDoomfireTrigger::IsActive()
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (!archimonde || archimonde->GetHealthPct() <= 10.0f)
return false;
// If I don't make an exception, bots actually refuse to enter the
// Doomfire even when feared
return !bot->HasAura(
static_cast<uint32>(HyjalSummitSpells::SPELL_ARCHIMONDE_FEAR));
}
bool ArchimondeBotStoodInDoomfireTrigger::IsActive()
{
if (bot->getClass() != CLASS_MAGE && bot->getClass() != CLASS_ROGUE &&
bot->getClass() != CLASS_PALADIN)
return false;
return bot->GetHealthPct() < 40.0f &&
(bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOMFIRE)) ||
bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOMFIRE_DOT)));
}

View File

@ -0,0 +1,271 @@
/*
* 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_RAIDHYJALSUMMITTRIGGERS_H
#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERS_H
#include "Trigger.h"
// General
class HyjalSummitBotIsNotInCombatTrigger : public Trigger
{
public:
HyjalSummitBotIsNotInCombatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hyjal summit bot is not in combat") {}
bool IsActive() override;
};
// Rage Winterchill
class RageWinterchillPullingBossTrigger : public Trigger
{
public:
RageWinterchillPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill pulling boss") {}
bool IsActive() override;
};
class RageWinterchillBossEngagedByMainTankTrigger : public Trigger
{
public:
RageWinterchillBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss engaged by main tank") {}
bool IsActive() override;
};
class RageWinterchillBossCastsDeathAndDecayOnRangedTrigger : public Trigger
{
public:
RageWinterchillBossCastsDeathAndDecayOnRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss casts death and decay on ranged") {}
bool IsActive() override;
};
class RageWinterchillMeleeIsStandingInDeathAndDecayTrigger : public Trigger
{
public:
RageWinterchillMeleeIsStandingInDeathAndDecayTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill melee is standing in death and decay") {}
bool IsActive() override;
};
// Anetheron
class AnetheronPullingBossOrInfernalTrigger : public Trigger
{
public:
AnetheronPullingBossOrInfernalTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron pulling boss or infernal") {}
bool IsActive() override;
};
class AnetheronBossEngagedByMainTankTrigger : public Trigger
{
public:
AnetheronBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss engaged by main tank") {}
bool IsActive() override;
};
class AnetheronBossCastsCarrionSwarmTrigger : public Trigger
{
public:
AnetheronBossCastsCarrionSwarmTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss casts carrion swarm") {}
bool IsActive() override;
};
class AnetheronBotIsTargetedByInfernalTrigger : public Trigger
{
public:
AnetheronBotIsTargetedByInfernalTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron bot is targeted by infernal") {}
bool IsActive() override;
};
class AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger : public Trigger
{
public:
AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals need to be kept away from raid") {}
bool IsActive() override;
};
class AnetheronInfernalsContinueToSpawnTrigger : public Trigger
{
public:
AnetheronInfernalsContinueToSpawnTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals continue to spawn") {}
bool IsActive() override;
};
// Kaz'rogal
class KazrogalPullingBossTrigger : public Trigger
{
public:
KazrogalPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal pulling boss") {}
bool IsActive() override;
};
class KazrogalBossEngagedByMainTankTrigger : public Trigger
{
public:
KazrogalBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by main tank") {}
bool IsActive() override;
};
class KazrogalBossEngagedByAssistTanksTrigger : public Trigger
{
public:
KazrogalBossEngagedByAssistTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by assist tanks") {}
bool IsActive() override;
};
class KazrogalLowManaBotsNeedEscapePathTrigger : public Trigger
{
public:
KazrogalLowManaBotsNeedEscapePathTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal low mana bots need escape path") {}
bool IsActive() override;
};
class KazrogalBotIsLowOnManaTrigger : public Trigger
{
public:
KazrogalBotIsLowOnManaTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal bot is low on mana") {}
bool IsActive() override;
};
class KazrogalMarkDealsShadowDamageTrigger : public Trigger
{
public:
KazrogalMarkDealsShadowDamageTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal mark deals shadow damage") {}
bool IsActive() override;
};
// Azgalor
class AzgalorPullingBossTrigger : public Trigger
{
public:
AzgalorPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor pulling boss") {}
bool IsActive() override;
};
class AzgalorBossEngagedByMainTankTrigger : public Trigger
{
public:
AzgalorBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by main tank") {}
bool IsActive() override;
};
class AzgalorMainTankIsPositioningBossTrigger : public Trigger
{
public:
AzgalorMainTankIsPositioningBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor main tank is positioning boss") {}
bool IsActive() override;
};
class AzgalorBossEngagedByRangedTrigger : public Trigger
{
public:
AzgalorBossEngagedByRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by ranged") {}
bool IsActive() override;
};
class AzgalorBossCastsRainOfFireOnMeleeTrigger : public Trigger
{
public:
AzgalorBossCastsRainOfFireOnMeleeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss casts rain of fire on melee") {}
bool IsActive() override;
};
class AzgalorBotIsDoomedTrigger : public Trigger
{
public:
AzgalorBotIsDoomedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor bot is doomed") {}
bool IsActive() override;
};
class AzgalorDoomguardsMustBeControlledTrigger : public Trigger
{
public:
AzgalorDoomguardsMustBeControlledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must be controlled") {}
bool IsActive() override;
};
class AzgalorDoomguardsMustDieTrigger : public Trigger
{
public:
AzgalorDoomguardsMustDieTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must die") {}
bool IsActive() override;
};
// Archimonde
class ArchimondePullingBossTrigger : public Trigger
{
public:
ArchimondePullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde pulling boss") {}
bool IsActive() override;
};
class ArchimondeBossEngagedByMainTankTrigger : public Trigger
{
public:
ArchimondeBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss engaged by main tank") {}
bool IsActive() override;
};
class ArchimondeBossCastsFearTrigger : public Trigger
{
public:
ArchimondeBossCastsFearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts fear") {}
bool IsActive() override;
};
class ArchimondeBossCastsAirBurstTrigger : public Trigger
{
public:
ArchimondeBossCastsAirBurstTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts air burst") {}
bool IsActive() override;
};
class ArchimondeBossSummonedDoomfireTrigger : public Trigger
{
public:
ArchimondeBossSummonedDoomfireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss summoned doomfire") {}
bool IsActive() override;
};
class ArchimondeBotStoodInDoomfireTrigger : public Trigger
{
public:
ArchimondeBotStoodInDoomfireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde bot stood in doomfire") {}
bool IsActive() override;
};
#endif

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidHyjalSummitHelpers.h"
#include <algorithm>
#include "Playerbots.h"
#include "RaidBossHelpers.h"
#include "Timer.h"
namespace HyjalSummitHelpers
{
// General
bool GetGroundedStepPosition(
Player* bot, float destinationX, float destinationY, float moveDist,
float& stepX, float& stepY, float& stepZ)
{
const float distance = bot->GetExactDist2d(destinationX, destinationY);
if (distance <= 0.0f)
return false;
const float stepDistance = std::min(moveDist, distance);
const float deltaX = destinationX - bot->GetPositionX();
const float deltaY = destinationY - bot->GetPositionY();
stepX = bot->GetPositionX() + (deltaX / distance) * stepDistance;
stepY = bot->GetPositionY() + (deltaY / distance) * stepDistance;
stepZ = bot->GetMapWaterOrGroundLevel(stepX, stepY, bot->GetPositionZ());
if (stepZ <= INVALID_HEIGHT)
stepZ = bot->GetPositionZ();
bot->GetMap()->CheckCollisionAndGetValidCoords(
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
stepX, stepY, stepZ, false);
return true;
}
RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot)
{
RangedGroups result;
Group* group = bot->GetGroup();
if (!group)
return result;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !botAI->IsRanged(member))
continue;
if (botAI->IsHeal(member))
result.healers.push_back(member);
else
result.rangedDps.push_back(member);
}
return result;
}
std::pair<size_t, size_t> GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot,
const RangedGroups& groups)
{
const std::vector<Player*>& vec = botAI->IsHeal(bot) ? groups.healers : groups.rangedDps;
auto it = std::find(vec.begin(), vec.end(), bot);
size_t index = (it != vec.end()) ? std::distance(vec.begin(), it) : 0;
return {index, vec.size()};
}
// Rage Winterchill
const Position WINTERCHILL_TANK_POSITION = { 5031.061f, -1784.521f, 1321.626f };
std::unordered_map<ObjectGuid, bool> hasReachedWinterchillPosition;
std::unordered_map<uint32, DeathAndDecayData> deathAndDecayPosition;
DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId)
{
auto instanceIt = deathAndDecayPosition.find(instanceId);
if (instanceIt == deathAndDecayPosition.end())
return nullptr;
const uint32 now = getMSTime();
const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now);
if (elapsed >= DEATH_AND_DECAY_REACQUIRE_DELAY)
{
deathAndDecayPosition.erase(instanceIt);
return nullptr;
}
if (elapsed >= DEATH_AND_DECAY_DURATION)
return nullptr;
return &instanceIt->second;
}
bool IsInDeathAndDecay(Player* bot, float radius)
{
const uint32 instanceId = bot->GetMap()->GetInstanceId();
Aura* aura = bot->GetAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DEATH_AND_DECAY));
if (aura)
{
DynamicObject* dynObj = aura->GetDynobjOwner();
if (dynObj && dynObj->IsInWorld())
{
const uint32 now = getMSTime();
auto instanceIt = deathAndDecayPosition.find(instanceId);
if (instanceIt == deathAndDecayPosition.end() ||
getMSTimeDiff(instanceIt->second.spawnTime, now) >= DEATH_AND_DECAY_REACQUIRE_DELAY)
{
deathAndDecayPosition[instanceId] =
DeathAndDecayData{ dynObj->GetPosition(), now };
}
}
}
DeathAndDecayData* data = GetActiveWinterchillDeathAndDecay(instanceId);
if (!data)
return false;
return bot->GetExactDist2d(data->position) < radius;
}
// Anetheron
const Position ANETHERON_TANK_POSITION = { 5033.177f, -1765.996f, 1324.195f };
const Position ANETHERON_E_INFERNAL_POSITION = { 5016.578f, -1800.233f, 1323.070f };
const Position ANETHERON_W_INFERNAL_POSITION = { 5048.911f, -1722.164f, 1321.408f };
std::unordered_map<ObjectGuid, bool> hasReachedAnetheronPosition;
Player* GetInfernoTarget(Unit* anetheron)
{
if (!anetheron)
return nullptr;
Spell* spell = anetheron->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (spell && spell->m_spellInfo->Id ==
static_cast<uint32>(HyjalSummitSpells::SPELL_INFERNO))
{
Unit* spellTarget = spell->m_targets.GetUnitTarget();
if (spellTarget && spellTarget->IsPlayer())
return spellTarget->ToPlayer();
}
return nullptr;
}
const Position& GetClosestInfernalTankPosition(Player* bot)
{
const Position& east = ANETHERON_E_INFERNAL_POSITION;
const Position& west = ANETHERON_W_INFERNAL_POSITION;
return (bot->GetExactDist2d(east.GetPositionX(), east.GetPositionY()) <=
bot->GetExactDist2d(west.GetPositionX(), west.GetPositionY())) ? east : west;
}
// Kaz'rogal
const Position KAZROGAL_TANK_TRANSITION_POSITION = { 5528.792f, -2636.486f, 1481.293f };
const Position KAZROGAL_TANK_FINAL_POSITION = { 5511.514f, -2662.466f, 1480.288f };
std::unordered_map<ObjectGuid, TankPositionState> kazrogalTankStep;
std::unordered_map<ObjectGuid, bool> isBelowManaThreshold;
TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot)
{
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank)
return TankPositionState::Unknown;
auto it = kazrogalTankStep.find(mainTank->GetGUID());
if (it != kazrogalTankStep.end())
return it->second;
return TankPositionState::Unknown;
}
// Azgalor
const Position AZGALOR_TANK_TRANSITION_POSITION = { 5486.787f, -2696.215f, 1482.007f };
const Position AZGALOR_TANK_FINAL_POSITION = { 5496.379f, -2675.265f, 1481.053f };
const Position AZGALOR_DOOMGUARD_POSITION = { 5485.555f, -2731.659f, 1485.555f };
std::unordered_map<ObjectGuid, TankPositionState> azgalorTankStep;
std::unordered_map<uint32, RainOfFireData> rainOfFirePosition;
RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId)
{
auto instanceIt = rainOfFirePosition.find(instanceId);
if (instanceIt == rainOfFirePosition.end())
return nullptr;
const uint32 now = getMSTime();
const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now);
if (elapsed >= RAIN_OF_FIRE_REACQUIRE_DELAY)
{
rainOfFirePosition.erase(instanceIt);
return nullptr;
}
if (elapsed >= RAIN_OF_FIRE_DURATION)
return nullptr;
return &instanceIt->second;
}
TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot)
{
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank)
return TankPositionState::Unknown;
auto it = azgalorTankStep.find(mainTank->GetGUID());
if (it != azgalorTankStep.end())
return it->second;
return TankPositionState::Unknown;
}
bool IsInRainOfFire(Player* bot, float radius)
{
RainOfFireData* data = GetActiveAzgalorRainOfFire(bot->GetMap()->GetInstanceId());
if (!data)
return false;
return bot->GetExactDist2d(data->position) < radius;
}
bool AnyGroupMemberHasDoom(Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member &&
member->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return true;
}
}
return false;
}
// Archimonde
const Position ARCHIMONDE_INITIAL_POSITION = { 5640.502f, -3421.238f, 1587.453f };
std::unordered_map<uint32, AirBurstData> archimondeAirBurstTargets;
std::unordered_map<uint32, std::vector<DoomfireTrailData>> doomfireTrails;
std::unordered_map<ObjectGuid, uint32> doomfireLastSampleTime;
AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId)
{
auto instanceIt = archimondeAirBurstTargets.find(instanceId);
if (instanceIt == archimondeAirBurstTargets.end())
return nullptr;
constexpr uint32 airBurstReactionWindow = 2000;
const uint32 now = getMSTime();
if (getMSTimeDiff(instanceIt->second.castTime, now) >= airBurstReactionWindow)
{
archimondeAirBurstTargets.erase(instanceIt);
return nullptr;
}
return &instanceIt->second;
}
}

View File

@ -0,0 +1,143 @@
/*
* 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_RAIDHYJALSUMMITHELPERS_H_
#define _PLAYERBOT_RAIDHYJALSUMMITHELPERS_H_
#include <unordered_map>
#include <utility>
#include <vector>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace HyjalSummitHelpers
{
enum class HyjalSummitSpells : uint32
{
// Rage Winterchill
SPELL_DEATH_AND_DECAY = 31258,
// Anetheron
SPELL_INFERNO = 31299,
// Kaz'rogal
SPELL_MARK_OF_KAZROGAL = 31447,
// Azgalor
SPELL_RAIN_OF_FIRE = 31340,
SPELL_DOOM = 31347,
// Archimonde
SPELL_DOOMFIRE = 31944, // Damaging part of trail
SPELL_DOOMFIRE_DOT = 31969, // DoT after exiting trail
SPELL_ARCHIMONDE_FEAR = 31970,
SPELL_AIR_BURST = 32014,
// Hunter
SPELL_MISDIRECTION = 35079,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum class HyjalSummitNpcs : uint32
{
// Archimonde
NPC_DOOMFIRE = 18095,
};
enum class TankPositionState : uint8
{
MovingToTransition = 0,
MovingToFinal = 1,
Positioned = 2,
Unknown = 255,
};
// General
constexpr uint32 HYJAL_SUMMIT_MAP_ID = 534;
struct RangedGroups
{
std::vector<Player*> healers;
std::vector<Player*> rangedDps;
};
bool GetGroundedStepPosition(
Player* bot, float destinationX, float destinationY, float moveDist,
float& stepX, float& stepY, float& stepZ);
RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot);
std::pair<size_t, size_t> GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot,
const RangedGroups& groups);
// Rage Winterchill
extern const Position WINTERCHILL_TANK_POSITION;
extern std::unordered_map<ObjectGuid, bool> hasReachedWinterchillPosition;
constexpr uint32 DEATH_AND_DECAY_DURATION = 15000;
constexpr uint32 DEATH_AND_DECAY_REACQUIRE_DELAY = 20000;
constexpr float DEATH_AND_DECAY_SAFE_RADIUS = 22.0f; // 20y radius + 1.5y player hitbox + 0.5y buffer
struct DeathAndDecayData
{
Position position;
uint32 spawnTime;
};
extern std::unordered_map<uint32, DeathAndDecayData> deathAndDecayPosition;
DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId);
bool IsInDeathAndDecay(Player* bot, float radius);
// Anetheron
extern const Position ANETHERON_TANK_POSITION;
extern const Position ANETHERON_E_INFERNAL_POSITION;
extern const Position ANETHERON_W_INFERNAL_POSITION;
extern std::unordered_map<ObjectGuid, bool> hasReachedAnetheronPosition;
Player* GetInfernoTarget(Unit* anetheron);
const Position& GetClosestInfernalTankPosition(Player* bot);
// Kaz'rogal
extern const Position KAZROGAL_TANK_TRANSITION_POSITION;
extern const Position KAZROGAL_TANK_FINAL_POSITION;
extern std::unordered_map<ObjectGuid, TankPositionState> kazrogalTankStep;
extern std::unordered_map<ObjectGuid, bool> isBelowManaThreshold;
TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot);
// Azgalor
extern const Position AZGALOR_TANK_TRANSITION_POSITION;
extern const Position AZGALOR_TANK_FINAL_POSITION;
extern const Position AZGALOR_DOOMGUARD_POSITION;
extern std::unordered_map<ObjectGuid, TankPositionState> azgalorTankStep;
constexpr uint32 RAIN_OF_FIRE_DURATION = 10000;
constexpr uint32 RAIN_OF_FIRE_REACQUIRE_DELAY = 15000;
constexpr float RAIN_OF_FIRE_RADIUS = 17.0f; // 15y radius + 1.5y player hitbox + 0.5y buffer
struct RainOfFireData
{
Position position;
uint32 spawnTime;
};
extern std::unordered_map<uint32, RainOfFireData> rainOfFirePosition;
TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot);
RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId);
bool IsInRainOfFire(Player* bot, float radius);
bool AnyGroupMemberHasDoom(Player* bot);
// Archimonde
constexpr float AIR_BURST_SAFE_DISTANCE = 15.0f;
struct AirBurstData
{
ObjectGuid targetGuid;
uint32 castTime;
};
struct DoomfireTrailData
{
Position position;
uint32 recordTime;
};
extern const Position ARCHIMONDE_INITIAL_POSITION;
extern std::unordered_map<uint32, AirBurstData> archimondeAirBurstTargets;
extern std::unordered_map<uint32, std::vector<DoomfireTrailData>> doomfireTrails;
extern std::unordered_map<ObjectGuid, uint32> doomfireLastSampleTime;
AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId);
}
#endif

View File

@ -0,0 +1,211 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidHyjalSummitHelpers.h"
#include "AllCreatureScript.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "RaidBossHelpers.h"
#include "DynamicObjectScript.h"
#include "Playerbots.h"
#include "ScriptMgr.h"
#include "Spell.h"
#include "Timer.h"
using namespace HyjalSummitHelpers;
static Player* GetFirstPlayerSpellTarget(Spell* spell, Unit* caster)
{
if (!spell || !caster)
return nullptr;
if (Unit* unitTarget = spell->m_targets.GetUnitTarget())
return unitTarget->ToPlayer();
std::list<TargetInfo> const& targets = *spell->GetUniqueTargetInfo();
for (TargetInfo const& targetInfo : targets)
{
if (Player* target = ObjectAccessor::GetPlayer(*caster, targetInfo.targetGUID))
return target;
}
return nullptr;
}
static bool ShouldInterruptForArchimondeAirBurst(PlayerbotAI* botAI, Player* bot, Player* target)
{
if (!target)
return false;
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || bot == mainTank)
return false;
float distanceToMainTank = bot->GetExactDist2d(mainTank);
return (target == mainTank || target == bot) &&
distanceToMainTank < AIR_BURST_SAFE_DISTANCE;
}
// Records the active Rain of Fire dynamic object so that melee bots can avoid it by running
// away from Azgalor or swapping to a Doomguard; the standard FleePosition() logic to avoid aoe
// can take melee in front of Azgalor, resulting in them getting cleaved
class AzgalorRainOfFireScript : public DynamicObjectScript
{
public:
AzgalorRainOfFireScript() : DynamicObjectScript("AzgalorRainOfFireScript") {}
void OnUpdate(DynamicObject* dynobj, uint32 /*diff*/) override
{
if (dynobj->GetSpellId() != static_cast<uint32>(HyjalSummitSpells::SPELL_RAIN_OF_FIRE))
return;
uint32 instanceId = dynobj->GetMap()->GetInstanceId();
if (GetActiveAzgalorRainOfFire(instanceId))
return;
uint32 now = getMSTime();
auto instanceIt = rainOfFirePosition.find(instanceId);
if (instanceIt != rainOfFirePosition.end() &&
getMSTimeDiff(instanceIt->second.spawnTime, now) < RAIN_OF_FIRE_REACQUIRE_DELAY)
{
return;
}
bool shouldTrackRainOfFire = false;
Map::PlayerList const& players = dynobj->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT))
continue;
shouldTrackRainOfFire = true;
break;
}
if (!shouldTrackRainOfFire)
return;
rainOfFirePosition[instanceId] = RainOfFireData{ dynobj->GetPosition(), now };
}
};
// Records the position of each Doomfire NPC at regular intervals so that bots can avoid
// the persistent fire trail it leaves behind. Each sample is tagged with a timestamp and
// expires after TRAIL_DURATION ms, matching the lifetime of a Doomfire DynamicObject (18s)
class ArchimondeDoomfireTrailScript : public AllCreatureScript
{
public:
ArchimondeDoomfireTrailScript() : AllCreatureScript("ArchimondeDoomfireTrailScript") {}
void OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) override
{
if (creature->GetEntry() != static_cast<uint32>(HyjalSummitNpcs::NPC_DOOMFIRE))
return;
uint32 now = getMSTime();
ObjectGuid guid = creature->GetGUID();
auto& lastSample = doomfireLastSampleTime[guid];
if (getMSTimeDiff(lastSample, now) < 500)
return;
lastSample = now;
uint32 instanceId = creature->GetMap()->GetInstanceId();
auto& trail = doomfireTrails[instanceId];
DoomfireTrailData data;
data.position = creature->GetPosition();
data.recordTime = now;
trail.push_back(data);
constexpr uint32 TRAIL_DURATION = 18000;
trail.erase(std::remove_if(trail.begin(), trail.end(),
[now](const DoomfireTrailData& d)
{
return getMSTimeDiff(d.recordTime, now) > TRAIL_DURATION;
}), trail.end());
constexpr float DOOMFIRE_DANGER_RANGE = 10.0f;
Map::PlayerList const& players = creature->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) ||
creature->GetDistance(player) > DOOMFIRE_DANGER_RANGE)
{
continue;
}
botAI->RequestSpellInterrupt();
}
}
void OnCreatureRemoveWorld(Creature* creature) override
{
if (creature->GetEntry() != static_cast<uint32>(HyjalSummitNpcs::NPC_DOOMFIRE))
return;
doomfireLastSampleTime.erase(creature->GetGUID());
}
};
class ArchimondeAirBurstSpellListenerScript : public AllSpellScript
{
public:
ArchimondeAirBurstSpellListenerScript() :
AllSpellScript("ArchimondeAirBurstSpellListenerScript") {}
void OnSpellCast(
Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override
{
if (!spell || !caster || !spellInfo)
return;
if (spellInfo->Id != static_cast<uint32>(HyjalSummitSpells::SPELL_AIR_BURST))
return;
Player* target = GetFirstPlayerSpellTarget(spell, caster);
if (!target)
return;
archimondeAirBurstTargets[caster->GetMap()->GetInstanceId()] =
AirBurstData{ target->GetGUID(), getMSTime() };
Map::PlayerList const& players = caster->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) ||
!ShouldInterruptForArchimondeAirBurst(botAI, player, target))
{
continue;
}
botAI->RequestSpellInterrupt();
}
}
};
void AddSC_HyjalSummitBotScripts()
{
new AzgalorRainOfFireScript();
new ArchimondeDoomfireTrailScript();
new ArchimondeAirBurstSpellListenerScript();
}

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidIccTriggers.h" #include "RaidIccTriggers.h"

View File

@ -1,10 +1,7 @@
#ifndef _PLAYERBOT_RAIDICCSTRATEGY_H #ifndef _PLAYERBOT_RAIDICCSTRATEGY_H
#define _PLAYERBOT_RAIDICCSTRATEGY_H #define _PLAYERBOT_RAIDICCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
#include "RaidIccMultipliers.h"
class RaidIccStrategy : public Strategy class RaidIccStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H
#include "RaidKarazhanTriggers.h" #include "RaidKarazhanTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger> class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ #define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidKarazhanStrategy : public Strategy class RaidKarazhanStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H
#include "RaidMagtheridonTriggers.h" #include "RaidMagtheridonTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger> class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H #define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidMagtheridonStrategy : public Strategy class RaidMagtheridonStrategy : public Strategy
{ {

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidMcTriggers.h" #include "RaidMcTriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDMCSTRATEGY_H #ifndef _PLAYERBOT_RAIDMCSTRATEGY_H
#define _PLAYERBOT_RAIDMCSTRATEGY_H #define _PLAYERBOT_RAIDMCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidMcStrategy : public Strategy class RaidMcStrategy : public Strategy

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h" #include "RaidNaxxTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H #ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H
#define _PLAYERBOT_RAIDNAXXSTRATEGY_H #define _PLAYERBOT_RAIDNAXXSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidNaxxStrategy : public Strategy class RaidNaxxStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidOsTriggers.h" #include "RaidOsTriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSSTRATEGY_H #ifndef _PLAYERBOT_RAIDOSSTRATEGY_H
#define _PLAYERBOT_RAIDOSSTRATEGY_H #define _PLAYERBOT_RAIDOSSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidOsStrategy : public Strategy class RaidOsStrategy : public Strategy

View File

@ -99,8 +99,12 @@ bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event /*event*/)
if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius)) if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius))
return false; // Already safe return false; // Already safe
// Stop current spell first
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
// bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL); // bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(), return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->pos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
} }

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_ #ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_
#define _PLAYERBOT_RAIDONYXIAACTIONS_H_ #define _PLAYERBOT_RAIDONYXIAACTIONS_H_
#include "Action.h"
#include "AttackAction.h" #include "AttackAction.h"
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "MovementActions.h" #include "MovementActions.h"
@ -45,42 +44,45 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId) static std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
{ {
// Define your safe zone coordinates based on the map // Safe zone coordinates based on the map
// Example assumes Onyxia's lair map coordinates // Assumes Onyxia's lair map coordinates
float z = bot->GetPositionZ(); // Stay at current height
switch (spellId) switch (spellId)
{ {
case 17086: // N to S case 17086: // N to S
case 18351: // S to N case 18351: // S to N
return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f}, return {
SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone SafeZone{Position(-10.0f, -180.0f, -87.0f), 5.0f},
SafeZone{Position(-20.0f, -250.0f, -88.0f), 5.0f}
}; // Bottom Safe Zone
case 18576: // E to W case 18576: // E to W
case 18609: // W to E case 18609: // W to E
return { return {
SafeZone{Position(20.0f, -210.0f, z), 5.0f}, SafeZone{Position(20.0f, -210.0f, -85.5f), 5.0f},
SafeZone{Position(-75.0f, -210.0f, z), 5.0f}, SafeZone{Position(-75.0f, -210.0f, -83.4f), 5.0f},
}; // Left Safe Zone }; // Left Safe Zone
case 18564: // SE to NW case 18564: // SE to NW
case 18584: // NW to SE case 18584: // NW to SE
return { return {
SafeZone{Position(-60.0f, -195.0f, z), 5.0f}, SafeZone{Position(-60.0f, -195.0f, -85.0f), 5.0f},
SafeZone{Position(10.0f, -240.0f, z), 5.0f}, SafeZone{Position(10.0f, -240.0f, -85.9f), 5.0f},
}; // NW Safe Zone }; // NW Safe Zone
case 18596: // SW to NE case 18596: // SW to NE
case 18617: // NE to SW case 18617: // NE to SW
return { return {
SafeZone{Position(7.0f, -185.0f, z), 5.0f}, SafeZone{Position(7.0f, -185.0f, -86.2f), 5.0f},
SafeZone{Position(-60.0f, -240.0f, z), 5.0f}, SafeZone{Position(-60.0f, -240.0f, -85.2f), 5.0f},
}; // NE Safe Zone }; // NE Safe Zone
default: default:
return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen return {
SafeZone{Position(-40.0f, -214.0f, -86.6f), 5.0f}
}; // Fallback center - shouldn't ever happen
} }
} }
}; };

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidOnyxiaTriggers.h" #include "RaidOnyxiaTriggers.h"

View File

@ -11,6 +11,7 @@
#include "RaidNaxxStrategy.h" #include "RaidNaxxStrategy.h"
#include "RaidSSCStrategy.h" #include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h" #include "RaidTempestKeepStrategy.h"
#include "RaidHyjalSummitStrategy.h"
#include "RaidZulAmanStrategy.h" #include "RaidZulAmanStrategy.h"
#include "RaidOsStrategy.h" #include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h" #include "RaidEoEStrategy.h"
@ -33,6 +34,7 @@ public:
creators["naxx"] = &RaidStrategyContext::naxx; creators["naxx"] = &RaidStrategyContext::naxx;
creators["ssc"] = &RaidStrategyContext::ssc; creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep; creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["hyjal"] = &RaidStrategyContext::hyjal;
creators["zulaman"] = &RaidStrategyContext::zulaman; creators["zulaman"] = &RaidStrategyContext::zulaman;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
@ -52,6 +54,7 @@ private:
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); } static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); } static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); } static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* hyjal(PlayerbotAI* botAI) { return new RaidHyjalSummitStrategy(botAI); }
static Strategy* zulaman(PlayerbotAI* botAI) { return new RaidZulAmanStrategy(botAI); } static Strategy* zulaman(PlayerbotAI* botAI) { return new RaidZulAmanStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); } static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#include "RaidSSCTriggers.h" #include "RaidSSCTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidSSCTriggerContext : public NamedObjectContext<Trigger> class RaidSSCTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidSSCStrategy : public Strategy class RaidSSCStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H
#include "RaidTempestKeepTriggers.h" #include "RaidTempestKeepTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger> class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_ #define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidTempestKeepStrategy : public Strategy class RaidTempestKeepStrategy : public Strategy
{ {

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidUlduarTriggers.h" #include "RaidUlduarTriggers.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H #ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H #define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h"
#include "Strategy.h" #include "Strategy.h"
class RaidUlduarStrategy : public Strategy class RaidUlduarStrategy : public Strategy

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidVoATriggers.h" #include "RaidVoATriggers.h"

View File

@ -3,10 +3,6 @@
#define _PLAYERBOT_RAIDVOASTRATEGY_H #define _PLAYERBOT_RAIDVOASTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "PlayerbotAI.h"
#include "string"
#include "Trigger.h"
#include "vector"
class RaidVoAStrategy : public Strategy class RaidVoAStrategy : public Strategy
{ {

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H
#include "RaidZulAmanTriggers.h" #include "RaidZulAmanTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidZulAmanTriggerContext : public NamedObjectContext<Trigger> class RaidZulAmanTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_ #define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidZulAmanStrategy : public Strategy class RaidZulAmanStrategy : public Strategy
{ {

View File

@ -11,6 +11,7 @@
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h" #include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h" #include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h" #include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h"
#include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h" #include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
@ -34,6 +35,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidMagtheridonActionContext()); actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext()); actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext()); actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidHyjalSummitActionContext());
actionContexts.Add(new RaidZulAmanActionContext()); actionContexts.Add(new RaidZulAmanActionContext());
actionContexts.Add(new RaidNaxxActionContext()); actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext()); actionContexts.Add(new RaidOsActionContext());

View File

@ -11,6 +11,7 @@
#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h" #include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h" #include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h" #include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h"
#include "Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h" #include "Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
@ -35,6 +36,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidNaxxTriggerContext()); triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext()); triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext()); triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidHyjalSummitTriggerContext());
triggerContexts.Add(new RaidZulAmanTriggerContext()); triggerContexts.Add(new RaidZulAmanTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext()); triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext()); triggerContexts.Add(new RaidEoETriggerContext());

View File

@ -33,8 +33,11 @@ bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* ow
if (!ChatHelper::parseableItem(command)) if (!ChatHelper::parseableItem(command))
return false; return false;
HandleCommand("c", command, owner); if (sPlayerbotAIConfig.enableAutoTradeOnItemMention)
HandleCommand("t", command, owner); {
HandleCommand("c", command, owner);
HandleCommand("t", command, owner);
}
return true; return true;
} }

View File

@ -619,7 +619,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
else else
password = accountName; password = accountName;
AccountMgr::CreateAccount(accountName, password); sAccountMgr->CreateAccount(accountName, password);
LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str()); LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str());
} }

View File

@ -15,6 +15,7 @@
#include "ChannelMgr.h" #include "ChannelMgr.h"
#include "CharacterPackets.h" #include "CharacterPackets.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "CheckMountStateAction.h"
#include "Common.h" #include "Common.h"
#include "CreatureData.h" #include "CreatureData.h"
#include "EmoteAction.h" #include "EmoteAction.h"
@ -54,9 +55,9 @@
#include "Unit.h" #include "Unit.h"
#include "UpdateTime.h" #include "UpdateTime.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "../../../../src/server/scripts/Spells/spell_dk.cpp"
const int SPELL_TITAN_GRIP = 49152; constexpr uint32 SPELL_TITAN_GRIP = 49152;
constexpr uint32 SPELL_DK_FROST_PRESENCE = 48263;
std::vector<std::string> PlayerbotAI::dispel_whitelist = { std::vector<std::string> PlayerbotAI::dispel_whitelist = {
"mutating injection", "mutating injection",
@ -1380,6 +1381,17 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
// */ // */
return; return;
} }
case SMSG_DISMOUNT:
{
WorldPacket p(packet);
p.rpos(0);
ObjectGuid guid;
p >> guid.ReadAsPacked();
if (guid != bot->GetGUID())
return;
CheckMountStateAction::CompleteDismount(bot);
return;
}
default: default:
botOutgoingPacketHandlers.AddPacket(packet); botOutgoingPacketHandlers.AddPacket(packet);
} }
@ -1588,6 +1600,27 @@ void PlayerbotAI::ClearStrategies(BotState type)
e->removeAllStrategies(); e->removeAllStrategies();
} }
// Resets only the combat or non-combat engine: wipe strategies, repopulate with class/spec defaults,
// re-apply current map's instance strategy (if any), and call Init() to rebuild trigger/action lists.
void PlayerbotAI::SelectiveResetStrategies(BotState type)
{
Engine* e = engines[type];
if (!e)
return;
e->removeAllStrategies();
if (type == BOT_STATE_COMBAT)
AiFactory::AddDefaultCombatStrategies(bot, this, e);
else if (type == BOT_STATE_NON_COMBAT)
AiFactory::AddDefaultNonCombatStrategies(bot, this, e);
if (sPlayerbotAIConfig.applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId());
e->Init();
}
std::vector<std::string> PlayerbotAI::GetStrategies(BotState type) std::vector<std::string> PlayerbotAI::GetStrategies(BotState type)
{ {
Engine* e = engines[type]; Engine* e = engines[type];
@ -1601,11 +1634,12 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
{ {
static const std::vector<std::string> allInstanceStrategies = static const std::vector<std::string> allInstanceStrategies =
{ {
"aq20", "bwl", "karazhan", "gruulslair", "icc", "magtheridon", "moltencore", "aq20", "bwl", "karazhan", "gruulslair", "hyjal", "icc", "magtheridon",
"naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar", "voa", "wotlk-an", "wotlk-cos", "moltencore", "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar",
"wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor", "voa", "wotlk-an", "wotlk-cos", "wotlk-dtk", "wotlk-eoe", "wotlk-fos",
"wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos", "wotlk-gd", "wotlk-hol", "wotlk-hor", "wotlk-hos", "wotlk-nex", "wotlk-occ",
"wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh", "zulaman" "wotlk-ok", "wotlk-os", "wotlk-pos", "wotlk-toc", "wotlk-uk", "wotlk-up",
"wotlk-vh", "zulaman"
}; };
for (const std::string& strat : allInstanceStrategies) for (const std::string& strat : allInstanceStrategies)
@ -1635,6 +1669,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 533: case 533:
strategyName = "naxx"; // Naxxramas strategyName = "naxx"; // Naxxramas
break; break;
case 534:
strategyName = "hyjal"; // The Battle for Mount Hyjal (Hyjal Summit)
break;
case 544: case 544:
strategyName = "magtheridon"; // Magtheridon's Lair strategyName = "magtheridon"; // Magtheridon's Lair
break; break;

View File

@ -405,6 +405,7 @@ public:
std::string const qualifier = ""); std::string const qualifier = "");
void ChangeStrategy(std::string const name, BotState type); void ChangeStrategy(std::string const name, BotState type);
void ClearStrategies(BotState type); void ClearStrategies(BotState type);
void SelectiveResetStrategies(BotState type);
std::vector<std::string> GetStrategies(BotState type); std::vector<std::string> GetStrategies(BotState type);
Strategy* GetStrategy(std::string const name, BotState type); Strategy* GetStrategy(std::string const name, BotState type);
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false); void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);

View File

@ -1542,6 +1542,24 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet)
// if master is logging out, log out all bots // if master is logging out, log out all bots
case CMSG_LOGOUT_REQUEST: case CMSG_LOGOUT_REQUEST:
{ {
Player* master = GetMaster();
if (master)
{
// Replicate the AFK logout prevention checks from WorldSession::HandleLogoutRequestOpcode
// so bots are not logged out when the master's own logout is going to be prevented.
AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(master->GetAreaId());
bool preventAfkSanctuaryLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 1
&& master->isAFK() && areaEntry && areaEntry->IsSanctuary();
bool preventAfkLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 2
&& master->isAFK();
if (preventAfkSanctuaryLogout || preventAfkLogout)
{
break;
}
}
LogoutAllBots(); LogoutAllBots();
break; break;
} }

View File

@ -2070,7 +2070,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bot->DurabilityRepairAll(false, 1.0f, false); bot->DurabilityRepairAll(false, 1.0f, false);
bot->SetFullHealth(); bot->SetFullHealth();
bot->SetPvP(true); bot->SetPvP(sWorld->IsPvPRealm());
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
factory.Refresh(); factory.Refresh();
@ -2635,6 +2635,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
{ {
// ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for // ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for
// simplicity. line marked for removal. // simplicity. line marked for removal.
player->SetPvP(sWorld->IsPvPRealm());
} }
else else
{ {

View File

@ -518,6 +518,7 @@ bool PlayerbotAIConfig::Initialize()
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""), LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
allowedLogFiles); allowedLogFiles);
enableAutoTradeOnItemMention = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableAutoTradeOnItemMention", true);
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""), LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""),
tradeActionExcludedPrefixes); tradeActionExcludedPrefixes);

View File

@ -306,6 +306,7 @@ public:
uint32 iterationsPerTick; uint32 iterationsPerTick;
std::mutex m_logMtx; std::mutex m_logMtx;
bool enableAutoTradeOnItemMention;
std::vector<std::string> tradeActionExcludedPrefixes; std::vector<std::string> tradeActionExcludedPrefixes;
std::vector<std::string> allowedLogFiles; std::vector<std::string> allowedLogFiles;
std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles; std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles;

View File

@ -526,6 +526,7 @@ public:
void AddPlayerbotsSecureLoginScripts(); void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts(); void AddSC_TempestKeepBotScripts();
void AddSC_HyjalSummitBotScripts();
void AddPlayerbotsScripts() void AddPlayerbotsScripts()
{ {
@ -541,4 +542,5 @@ void AddPlayerbotsScripts()
AddPlayerbotsCommandscripts(); AddPlayerbotsCommandscripts();
PlayerBotsGuildValidationScript(); PlayerBotsGuildValidationScript();
AddSC_TempestKeepBotScripts(); AddSC_TempestKeepBotScripts();
AddSC_HyjalSummitBotScripts();
} }