Merge pull request #2128 from mod-playerbots/test-staging

Master update from Test-staging: Fix ObjectAccessor retrieval, optimize EquipActions, and implement RaidBossHelpers
This commit is contained in:
Keleborn
2026-02-13 09:16:49 -08:00
committed by GitHub
84 changed files with 8641 additions and 1368 deletions

View File

@@ -1,13 +1,15 @@
name: Enforce test-staging → main
name: Enforce test-staging → master
on:
pull_request:
branches:
- master
- test-staging
jobs:
require-test-staging:
runs-on: ubuntu-22.04
if: github.event.pull_request.base.ref == 'master'
steps:
- name: Ensure PR source is test-staging
run: |

View File

@@ -2,9 +2,9 @@ name: Codestyle
on:
push:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
concurrency:
group: "codestyle-${{ github.event.pull_request.number }}"

View File

@@ -2,9 +2,9 @@ name: ubuntu-build
on:
push:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
concurrency:
group: "core-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: macos-build
on:
push:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
concurrency:
group: "macos-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: windows-build
on:
push:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "test-staging" ]
concurrency:
group: "windows-build-${{ github.event.pull_request.number }}"

View File

@@ -558,7 +558,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "mana" (bots have infinite mana)
# "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies (currently only for Ulduar))
# "raid" (bots use cheats implemented into raid strategies (currently only for SSC and Ulduar))
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
# Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid"
@@ -2182,4 +2182,4 @@ AiPlayerbot.SummonAtInnkeepersEnabled = 1
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots.
# Buffs will be applied on PP, Sindragosa and Lich King
AiPlayerbot.EnableICCBuffs = 1
AiPlayerbot.EnableICCBuffs = 1

View File

@@ -328,7 +328,43 @@ void EquipAction::EquipItem(Item* item)
botAI->TellMaster(out);
}
bool EquipUpgradesAction::Execute(Event event)
ItemIds EquipAction::SelectInventoryItemsToEquip()
{
CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
continue;
ItemTemplate const* itemTemplate = item->GetTemplate();
if (!itemTemplate)
continue;
//TODO Expand to Glyphs and Gems, that can be placed in equipment
//Pre-filter non-equipable items
if (itemTemplate->InventoryType == INVTYPE_NON_EQUIP)
continue;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
else
itemUsageParam = std::to_string(itemId);
ItemUsage usage = AI_VALUE2(ItemUsage, "item upgrade", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
items.insert(itemId);
}
return items;
}
bool EquipUpgradesTriggeredAction::Execute(Event event)
{
if (!sPlayerbotAIConfig.autoEquipUpgradeLoot && !sRandomPlayerbotMgr.IsRandomBot(bot))
return false;
@@ -361,72 +397,18 @@ bool EquipUpgradesAction::Execute(Event event)
p >> itemId;
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
if (item->InventoryType == INVTYPE_NON_EQUIP)
return false;
}
CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
ItemIds items = SelectInventoryItemsToEquip();
EquipItems(items);
return true;
}
bool EquipUpgradeAction::Execute(Event event)
{
CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
ItemIds items = SelectInventoryItemsToEquip();
EquipItems(items);
return true;
}

View File

@@ -8,6 +8,7 @@
#include "ChatHelper.h"
#include "InventoryAction.h"
#include "Item.h"
class FindItemVisitor;
class Item;
@@ -20,6 +21,7 @@ public:
bool Execute(Event event) override;
void EquipItems(ItemIds ids);
ItemIds SelectInventoryItemsToEquip();
private:
void EquipItem(FindItemVisitor* visitor);
@@ -27,10 +29,10 @@ private:
void EquipItem(Item* item);
};
class EquipUpgradesAction : public EquipAction
class EquipUpgradesTriggeredAction : public EquipAction
{
public:
EquipUpgradesAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {}
explicit EquipUpgradesTriggeredAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {}
bool Execute(Event event) override;
};
@@ -38,7 +40,7 @@ public:
class EquipUpgradeAction : public EquipAction
{
public:
EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {}
explicit EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {}
bool Execute(Event event) override;
};

View File

@@ -90,6 +90,8 @@ bool LootRollAction::Execute(Event event)
}
else if (sPlayerbotAIConfig.lootRollLevel == 1)
{
// Level 1 = "greed" mode: bots greed on useful items but never need
// Only downgrade NEED to GREED, preserve GREED votes as-is
if (vote == NEED)
{
if (RollUniqueCheck(proto, bot))
@@ -101,10 +103,6 @@ bool LootRollAction::Execute(Event event)
vote = GREED;
}
}
else if (vote == GREED)
{
vote = PASS;
}
}
switch (group->GetLootMethod())
{

View File

@@ -17,9 +17,9 @@ public:
SummonAction(PlayerbotAI* botAI, std::string const name = "summon") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
protected:
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras);
};

View File

@@ -120,7 +120,7 @@ public:
creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip;
creators["equip upgrades"] = &ChatActionContext::equip_upgrades;
creators["equip upgrades"] = &ChatActionContext::equip_upgrade;
creators["unequip"] = &ChatActionContext::unequip;
creators["sell"] = &ChatActionContext::sell;
creators["buy"] = &ChatActionContext::buy;
@@ -258,7 +258,6 @@ private:
static Action* talents(PlayerbotAI* botAI) { return new ChangeTalentsAction(botAI); }
static Action* equip(PlayerbotAI* botAI) { return new EquipAction(botAI); }
static Action* equip_upgrades(PlayerbotAI* botAI) { return new EquipUpgradesAction(botAI); }
static Action* unequip(PlayerbotAI* botAI) { return new UnequipAction(botAI); }
static Action* sell(PlayerbotAI* botAI) { return new SellAction(botAI); }
static Action* buy(PlayerbotAI* botAI) { return new BuyAction(botAI); }

View File

@@ -42,6 +42,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
NextAction("query item usage", relevance),
NextAction("equip upgrades", relevance) }));
triggers.push_back(new TriggerNode("item push result", { NextAction("quest item push result", relevance) }));
triggers.push_back(new TriggerNode("loot roll won", { NextAction("equip upgrades", relevance) }));
triggers.push_back(new TriggerNode("ready check finished", { NextAction("finish ready check", relevance) }));
// triggers.push_back(new TriggerNode("often", { NextAction("security check", relevance), NextAction("check mail", relevance) }));
triggers.push_back(new TriggerNode("guild invite", { NextAction("guild accept", relevance) }));

View File

@@ -19,19 +19,9 @@
ItemUsage ItemUsageValue::Calculate()
{
uint32 itemId = 0;
uint32 randomPropertyId = 0;
size_t pos = qualifier.find(",");
if (pos != std::string::npos)
{
itemId = atoi(qualifier.substr(0, pos).c_str());
randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
}
else
{
itemId = atoi(qualifier.c_str());
}
ParsedItemUsage const parsed = GetItemIdFromQualifier();
uint32 itemId = parsed.itemId;
uint32 randomPropertyId = parsed.randomPropertyId;
if (!itemId)
return ITEM_USAGE_NONE;
@@ -142,96 +132,30 @@ ItemUsage ItemUsageValue::Calculate()
// If the loot is from an item in the bots bags, ignore syncQuestWithPlayer
if (isLootFromItem && botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
// If the bot is NOT acting alone and the master needs this quest item, defer to the master
if (!isSelfBot && masterNeedsItemForQuest)
{
return ITEM_USAGE_NONE;
}
// If the bot itself needs the item for a quest, allow looting
if (botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{
if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR)
{
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
{
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
}
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
{
return ITEM_USAGE_EQUIP;
}
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
{
return ITEM_USAGE_NONE;
}
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
}
ItemUsage ammoUsage = QueryItemUsageForAmmo(proto);
if (ammoUsage != ITEM_USAGE_NONE)
return ammoUsage;
}
// Need to add something like free bagspace or item value.
if (proto->SellPrice > 0)
{
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
{
return ITEM_USAGE_AH;
}
else
{
return ITEM_USAGE_VENDOR;
}
}
return ITEM_USAGE_NONE;
@@ -480,6 +404,80 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
return ITEM_USAGE_NONE;
}
ItemUsage ItemUsageValue::QueryItemUsageForAmmo(ItemTemplate const* proto)
{
if (bot->getClass() != CLASS_HUNTER || bot->getClass() != CLASS_ROGUE || bot->getClass() != CLASS_WARRIOR)
return ITEM_USAGE_NONE;
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
return ITEM_USAGE_EQUIP;
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
return ITEM_USAGE_NONE;
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
return ITEM_USAGE_NONE;
}
ParsedItemUsage ItemUsageValue::GetItemIdFromQualifier()
{
ParsedItemUsage parsed;
size_t const pos = qualifier.find(",");
if (pos != std::string::npos)
{
parsed.itemId = atoi(qualifier.substr(0, pos).c_str());
parsed.randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
return parsed;
}
else
parsed.itemId = atoi(qualifier.c_str());
return parsed;
}
// Return smaltest bag size equipped
uint32 ItemUsageValue::GetSmallestBagSize()
{
@@ -913,3 +911,25 @@ std::string const ItemUsageValue::GetConsumableType(ItemTemplate const* proto, b
return "";
}
ItemUsage ItemUpgradeValue::Calculate()
{
ParsedItemUsage parsed = GetItemIdFromQualifier();
uint32 itemId = parsed.itemId;
uint32 randomPropertyId = parsed.randomPropertyId;
if (!itemId)
return ITEM_USAGE_NONE;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
return ITEM_USAGE_NONE;
ItemUsage equip = QueryItemUsageForEquip(proto, randomPropertyId);
if (equip != ITEM_USAGE_NONE)
return equip;
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
return QueryItemUsageForAmmo(proto);
return ITEM_USAGE_NONE;
}

View File

@@ -14,7 +14,11 @@ class Player;
class PlayerbotAI;
struct ItemTemplate;
struct ParsedItemUsage
{
uint32 itemId = 0;
int32 randomPropertyId = 0;
};
enum ItemUsage : uint32
{
ITEM_USAGE_NONE = 0,
@@ -42,8 +46,12 @@ public:
ItemUsage Calculate() override;
private:
protected:
ItemUsage QueryItemUsageForEquip(ItemTemplate const* proto, int32 randomPropertyId = 0);
ItemUsage QueryItemUsageForAmmo(ItemTemplate const* proto);
ParsedItemUsage GetItemIdFromQualifier();
private:
uint32 GetSmallestBagSize();
bool IsItemUsefulForQuest(Player* player, ItemTemplate const* proto);
bool IsItemNeededForSkill(ItemTemplate const* proto);
@@ -61,4 +69,14 @@ public:
static std::string const GetConsumableType(ItemTemplate const* proto, bool hasMana);
};
class ItemUpgradeValue : public ItemUsageValue
{
public:
ItemUpgradeValue(PlayerbotAI* botAI, std::string const name = "item upgrade") : ItemUsageValue(botAI, name)
{
}
ItemUsage Calculate() override;
};
#endif

View File

@@ -173,7 +173,7 @@ std::vector<GuidPosition> ActiveQuestGiversValue::Calculate()
continue;
}
if (guidp.isDead())
if (!guidp.IsCreatureOrGOAccessible())
continue;
retQuestGivers.push_back(guidp);
@@ -231,7 +231,7 @@ std::vector<GuidPosition> ActiveQuestTakersValue::Calculate()
for (auto& guidp : entry.second)
{
if (guidp.isDead())
if (!guidp.IsCreatureOrGOAccessible())
continue;
retQuestTakers.push_back(guidp);
@@ -298,7 +298,7 @@ std::vector<GuidPosition> ActiveQuestObjectivesValue::Calculate()
{
for (auto& guidp : entry.second)
{
if (guidp.isDead())
if (!guidp.IsCreatureOrGOAccessible())
continue;
retQuestObjectives.push_back(guidp);

View File

@@ -216,6 +216,7 @@ public:
creators["formation"] = &ValueContext::formation;
creators["stance"] = &ValueContext::stance;
creators["item usage"] = &ValueContext::item_usage;
creators["item upgrade"] = &ValueContext::item_upgrade;
creators["speed"] = &ValueContext::speed;
creators["last said"] = &ValueContext::last_said;
creators["last emote"] = &ValueContext::last_emote;
@@ -341,6 +342,7 @@ private:
static UntypedValue* already_seen_players(PlayerbotAI* botAI) { return new AlreadySeenPlayersValue(botAI); }
static UntypedValue* new_player_nearby(PlayerbotAI* botAI) { return new NewPlayerNearbyValue(botAI); }
static UntypedValue* item_usage(PlayerbotAI* botAI) { return new ItemUsageValue(botAI); }
static UntypedValue* item_upgrade(PlayerbotAI* botAI) { return new ItemUpgradeValue(botAI); }
static UntypedValue* formation(PlayerbotAI* botAI) { return new FormationValue(botAI); }
static UntypedValue* stance(PlayerbotAI* botAI) { return new StanceValue(botAI); }
static UntypedValue* mana_save_level(PlayerbotAI* botAI) { return new ManaSaveLevelValue(botAI); }

View File

@@ -46,6 +46,7 @@ public:
creators["questgiver quest details"] = &WorldPacketTriggerContext::questgiver_quest_details;
creators["item push result"] = &WorldPacketTriggerContext::item_push_result;
creators["loot roll won"] = &WorldPacketTriggerContext::loot_roll_won;
creators["party command"] = &WorldPacketTriggerContext::party_command;
creators["taxi done"] = &WorldPacketTriggerContext::taxi_done;
creators["cast failed"] = &WorldPacketTriggerContext::cast_failed;
@@ -92,6 +93,7 @@ private:
static Trigger* taxi_done(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "taxi done"); }
static Trigger* party_command(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "party command"); }
static Trigger* item_push_result(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "item push result"); }
static Trigger* loot_roll_won(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "loot roll won"); }
// quest
static Trigger* quest_update_add_kill(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "quest update add kill"); }

View File

@@ -41,6 +41,18 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
class CastUnendingBreathAction : public CastBuffSpellAction
{
public:
CastUnendingBreathAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "unending breath") {}
};
class CastUnendingBreathOnPartyAction : public BuffOnPartyAction
{
public:
CastUnendingBreathOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "unending breath") {}
};
class CreateSoulShardAction : public Action
{
public:

View File

@@ -85,6 +85,8 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
triggers.push_back(new TriggerNode("too many soul shards", { NextAction("destroy soul shard", 60.0f) }));
triggers.push_back(new TriggerNode("soul link", { NextAction("soul link", 28.0f) }));
triggers.push_back(new TriggerNode("demon armor", { NextAction("fel armor", 27.0f) }));
triggers.push_back(new TriggerNode("unending breath", { NextAction("unending breath", 12.0f) }));
triggers.push_back(new TriggerNode("unending breath on party", { NextAction("unending breath on party", 11.0f) }));
triggers.push_back(new TriggerNode("no healthstone", { NextAction("create healthstone", 26.0f) }));
triggers.push_back(new TriggerNode("no soulstone", { NextAction("create soulstone", 25.0f) }));
triggers.push_back(new TriggerNode("life tap", { NextAction("life tap", 23.0f) }));

View File

@@ -79,6 +79,16 @@ bool SoulLinkTrigger::IsActive()
return !botAI->HasAura("soul link", target);
}
bool UnendingBreathTrigger::IsActive()
{
return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool UnendingBreathOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool DemonicEmpowermentTrigger::IsActive()
{
Pet* pet = bot->GetPet();

View File

@@ -32,6 +32,20 @@ public:
bool IsActive() override;
};
class UnendingBreathTrigger : public BuffTrigger
{
public:
UnendingBreathTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "unending breath", 5 * 2000) {}
bool IsActive() override;
};
class UnendingBreathOnPartyTrigger : public BuffOnPartyTrigger
{
public:
UnendingBreathOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "unending breath on party", 2 * 2000) {}
bool IsActive() override;
};
class OutOfSoulShardsTrigger : public Trigger
{
public:

View File

@@ -143,6 +143,8 @@ public:
creators["shadow trance"] = &WarlockTriggerFactoryInternal::shadow_trance;
creators["demon armor"] = &WarlockTriggerFactoryInternal::demon_armor;
creators["soul link"] = &WarlockTriggerFactoryInternal::soul_link;
creators["unending breath"] = &WarlockTriggerFactoryInternal::unending_breath;
creators["unending breath on party"] = &WarlockTriggerFactoryInternal::unending_breath_on_party;
creators["no soul shard"] = &WarlockTriggerFactoryInternal::no_soul_shard;
creators["too many soul shards"] = &WarlockTriggerFactoryInternal::too_many_soul_shards;
creators["no healthstone"] = &WarlockTriggerFactoryInternal::HasHealthstone;
@@ -189,6 +191,8 @@ private:
static Trigger* shadow_trance(PlayerbotAI* botAI) { return new ShadowTranceTrigger(botAI); }
static Trigger* demon_armor(PlayerbotAI* botAI) { return new DemonArmorTrigger(botAI); }
static Trigger* soul_link(PlayerbotAI* botAI) { return new SoulLinkTrigger(botAI); }
static Trigger* unending_breath(PlayerbotAI* botAI) { return new UnendingBreathTrigger(botAI); }
static Trigger* unending_breath_on_party(PlayerbotAI* botAI) { return new UnendingBreathOnPartyTrigger(botAI); }
static Trigger* no_soul_shard(PlayerbotAI* botAI) { return new OutOfSoulShardsTrigger(botAI); }
static Trigger* too_many_soul_shards(PlayerbotAI* botAI) { return new TooManySoulShardsTrigger(botAI); }
static Trigger* HasHealthstone(PlayerbotAI* botAI) { return new HasHealthstoneTrigger(botAI); }
@@ -240,6 +244,8 @@ public:
creators["demon armor"] = &WarlockAiObjectContextInternal::demon_armor;
creators["demon skin"] = &WarlockAiObjectContextInternal::demon_skin;
creators["soul link"] = &WarlockAiObjectContextInternal::soul_link;
creators["unending breath"] = &WarlockAiObjectContextInternal::unending_breath;
creators["unending breath on party"] = &WarlockAiObjectContextInternal::unending_breath_on_party;
creators["create soul shard"] = &WarlockAiObjectContextInternal::create_soul_shard;
creators["destroy soul shard"] = &WarlockAiObjectContextInternal::destroy_soul_shard;
creators["create healthstone"] = &WarlockAiObjectContextInternal::create_healthstone;
@@ -313,6 +319,8 @@ private:
static Action* demon_armor(PlayerbotAI* botAI) { return new CastDemonArmorAction(botAI); }
static Action* demon_skin(PlayerbotAI* botAI) { return new CastDemonSkinAction(botAI); }
static Action* soul_link(PlayerbotAI* botAI) { return new CastSoulLinkAction(botAI); }
static Action* unending_breath(PlayerbotAI* botAI) { return new CastUnendingBreathAction(botAI); }
static Action* unending_breath_on_party(PlayerbotAI* botAI) { return new CastUnendingBreathOnPartyAction(botAI); }
static Action* create_soul_shard(PlayerbotAI* botAI) { return new CreateSoulShardAction(botAI); }
static Action* destroy_soul_shard(PlayerbotAI* botAI) { return new DestroySoulShardAction(botAI); }
static Action* create_healthstone(PlayerbotAI* botAI) { return new CastCreateHealthstoneAction(botAI); }

View File

@@ -62,7 +62,7 @@ bool MountDrakeAction::Execute(Event event)
break;
}
std::vector<Player*> players = botAI->GetPlayersInGroup();
std::vector<Player*> players = botAI->GetAllPlayersInGroup();
for (Player* player : players)
{
if (!player || !player->IsInWorld() || player->IsDuringRemoveFromWorld())

View File

@@ -2,6 +2,7 @@
#include "RaidGruulsLairHelpers.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
#include "Unit.h"
using namespace GruulsLairHelpers;
@@ -12,6 +13,8 @@ using namespace GruulsLairHelpers;
bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
MarkTargetWithSquare(bot, maulgar);
SetRtiTarget(botAI, "square", maulgar);
@@ -21,31 +24,20 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
if (maulgar->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition;
const Position& position = MAULGAR_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true);
}
float orientation = atan2(maulgar->GetPositionY() - bot->GetPositionY(),
maulgar->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(maulgar))
{
return MoveTo(maulgar->GetMapId(), maulgar->GetPositionX(), maulgar->GetPositionY(),
maulgar->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -55,6 +47,8 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (!olm)
return false;
MarkTargetWithCircle(bot, olm);
SetRtiTarget(botAI, "circle", olm);
@@ -64,29 +58,22 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
if (olm->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::OlmTankPosition;
const Position& position = OLM_TANK_POSITION;
const float maxDistance = 3.0f;
const float olmTankLeeway = 30.0f;
float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceOlmToPosition = olm->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceOlmToTankPosition > olmTankLeeway)
if (distanceOlmToPosition > olmTankLeeway)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceOlmToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceOlmToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
else if (!bot->IsWithinMeleeRange(olm))
{
return MoveTo(olm->GetMapId(), olm->GetPositionX(), olm->GetPositionY(),
olm->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
@@ -95,6 +82,8 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (!blindeye)
return false;
MarkTargetWithStar(bot, blindeye);
SetRtiTarget(botAI, "star", blindeye);
@@ -104,31 +93,20 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
if (blindeye->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition;
const Position& position = BLINDEYE_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(blindeye->GetPositionY() - bot->GetPositionY(),
blindeye->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(blindeye))
{
return MoveTo(blindeye->GetMapId(), blindeye->GetPositionX(), blindeye->GetPositionY(),
blindeye->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -138,6 +116,8 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (!krosh)
return false;
MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh);
@@ -149,25 +129,22 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
return botAI->CastSpell("fire ward", bot);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return true;
}
return Attack(krosh);
if (krosh->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::KroshTankPosition;
float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y);
const Position& position = KROSH_TANK_POSITION;
float distanceToKrosh = krosh->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
const float minDistance = 16.0f;
const float maxDistance = 29.0f;
const float tankPositionLeeway = 1.0f;
if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance)
{
if (!bot->IsWithinDist2d(tankPosition.x, tankPosition.y, tankPositionLeeway))
if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), tankPositionLeeway))
{
return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false,
false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
return MoveTo(GRUULS_LAIR_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
@@ -179,7 +156,7 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -192,20 +169,19 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event)
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (!kiggler)
return false;
MarkTargetWithDiamond(bot, kiggler);
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return true;
}
return Attack(kiggler);
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -216,120 +192,105 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
{
// Target priority 1: Blindeye
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye && blindeye->IsAlive())
if (blindeye)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(blindeye->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "star", blindeye);
if (bot->GetTarget() != blindeye->GetGUID())
{
bot->SetSelection(blindeye->GetGUID());
return Attack(blindeye);
}
return false;
}
// Target priority 2: Olm
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (olm && olm->IsAlive())
if (olm)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(olm->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "circle", olm);
if (bot->GetTarget() != olm->GetGUID())
{
bot->SetSelection(olm->GetGUID());
return Attack(olm);
}
return false;
}
// Target priority 3a: Krosh (ranged only)
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (krosh && krosh->IsAlive() && botAI->IsRanged(bot))
if (krosh && botAI->IsRanged(bot))
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "triangle", krosh);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return Attack(krosh);
}
return false;
}
// Target priority 3b: Kiggler
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (kiggler && kiggler->IsAlive())
if (kiggler)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return Attack(kiggler);
}
return false;
}
// Target priority 4: Maulgar
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (maulgar && maulgar->IsAlive())
if (maulgar)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(maulgar->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "square", maulgar);
if (bot->GetTarget() != maulgar->GetGUID())
{
bot->SetSelection(maulgar->GetGUID());
return Attack(maulgar);
}
}
return false;
@@ -338,22 +299,22 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room
bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{
const Location& fightCenter = GruulsLairLocations::MaulgarRoomCenter;
const float maxDistanceFromFight = 50.0f;
float distToFight = bot->GetExactDist2d(fightCenter.x, fightCenter.y);
const Position& center = MAULGAR_ROOM_CENTER;
const float maxDistanceFromCenter = 50.0f;
float distToCenter = bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY());
if (distToFight > maxDistanceFromFight)
if (distToCenter > maxDistanceFromCenter)
{
float angle = atan2(bot->GetPositionY() - fightCenter.y, bot->GetPositionX() - fightCenter.x);
float destX = fightCenter.x + 40.0f * cos(angle);
float destY = fightCenter.y + 40.0f * sin(angle);
float destZ = fightCenter.z;
float angle = atan2(bot->GetPositionY() - center.GetPositionY(), bot->GetPositionX() - center.GetPositionX());
float destX = center.GetPositionX() + 40.0f * cos(angle);
float destY = center.GetPositionY() + 40.0f * sin(angle);
float destZ = center.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -362,7 +323,7 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -373,6 +334,8 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
const float safeDistance = 10.0f;
float distance = bot->GetExactDist2d(maulgar);
@@ -395,7 +358,7 @@ bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -439,7 +402,7 @@ bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < felStalkers.size())
{
Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true))
if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker))
return botAI->CastSpell("banish", assignedFelStalker);
}
@@ -528,40 +491,33 @@ bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event)
// Gruul the Dragonkiller Actions
// Position in center of the room
bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event)
bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event event)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return false;
if (bot->GetVictim() != gruul)
return Attack(gruul);
if (gruul->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float maxDistance = 3.0f;
const Position& position = GRUUL_TANK_POSITION;
const float maxDistance = 5.0f;
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
{
float step = std::min(maxDistance, distanceToTankPosition);
float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance;
const float moveZ = tankPosition.z;
return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false,
const float moveZ = position.GetPositionZ();
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, moveZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(gruul->GetPositionY() - bot->GetPositionY(),
gruul->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(gruul))
{
return MoveTo(gruul->GetMapId(), gruul->GetPositionX(), gruul->GetPositionY(), gruul->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -579,16 +535,16 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition;
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (gruul && gruul->IsAlive() && gruul->GetHealth() == gruul->GetMaxHealth())
if (gruul && gruul->GetHealth() == gruul->GetMaxHealth())
{
initialPositions.clear();
hasReachedInitialPosition.clear();
initialPositions.erase(bot->GetGUID());
hasReachedInitialPosition.erase(bot->GetGUID());
}
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float centerX = tankPosition.x;
const float centerY = tankPosition.y;
float centerZ = bot->GetPositionZ();
const Position& position = GRUUL_TANK_POSITION;
const float centerX = position.GetPositionX();
const float centerY = position.GetPositionY();
const float centerZ = position.GetPositionZ();
const float minRadius = 25.0f;
const float maxRadius = 40.0f;
@@ -642,7 +598,7 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}

View File

@@ -85,10 +85,10 @@ public:
bool Execute(Event event) override;
};
class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction
class GruulTheDragonkillerTanksPositionBossAction : public AttackAction
{
public:
GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {};
GruulTheDragonkillerTanksPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller tanks position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};

View File

@@ -8,18 +8,11 @@
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "ReachTargetActions.h"
#include "WarriorActions.h"
using namespace GruulsLairHelpers;
static bool IsChargeAction(Action* action)
{
return dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastInterceptAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action)
{
if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action))
@@ -38,12 +31,10 @@ float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
(!kiggler || !kiggler->IsAlive()) &&
(!krosh || !krosh->IsAlive()) &&
(!olm || !olm->IsAlive()) &&
(!blindeye || !blindeye->IsAlive()))
!kiggler && !krosh && !olm && !blindeye)
{
if (IsChargeAction(action) || (dynamic_cast<MovementAction*>(action) &&
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
return 0.0f;
}
@@ -57,7 +48,8 @@ float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* target = AI_VALUE(Unit*, "current target");
if (krosh && target && target->GetGUID() == krosh->GetGUID() && dynamic_cast<CastArcaneShotAction*>(action))
if (krosh && target && target->GetGUID() == krosh->GetGUID() &&
dynamic_cast<CastArcaneShotAction*>(action))
return 0.0f;
return 1.0f;
@@ -101,8 +93,9 @@ float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2))
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
IsChargeAction(action))
if ((dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}

View File

@@ -22,7 +22,7 @@ public:
creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller main tank position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_main_tank_position_boss;
creators["gruul the dragonkiller tanks position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_tanks_position_boss;
creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
}
@@ -41,7 +41,7 @@ private:
static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); }
// Gruul the Dragonkiller
static Action* gruul_the_dragonkiller_main_tank_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerMainTankPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_tanks_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerTanksPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); }
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); }
};

View File

@@ -22,8 +22,8 @@ public:
creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller boss engaged by main tank"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_main_tank;
creators["gruul the dragonkiller boss engaged by range"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_range;
creators["gruul the dragonkiller boss engaged by tanks"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_tanks;
creators["gruul the dragonkiller boss engaged by ranged"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_ranged;
creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
}
@@ -41,8 +41,8 @@ private:
static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); }
// Gruul the Dragonkiller
static Trigger* gruul_the_dragonkiller_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByMainTankTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_range(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangeTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_tanks(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByTanksTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangedTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); }
};

View File

@@ -35,10 +35,10 @@ void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) }));
// Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", {
NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by tanks", {
NextAction("gruul the dragonkiller tanks position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", {
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by ranged", {
NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", {

View File

@@ -10,35 +10,35 @@ bool HighKingMaulgarIsMainTankTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive();
return botAI->IsMainTank(bot) && maulgar;
}
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive()
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive();
return botAI->IsAssistTankOfIndex(bot, 0, false) && olm;
}
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive()
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive();
return botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye;
}
bool HighKingMaulgarIsMageTankTrigger::IsActive()
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive();
return IsKroshMageTank(botAI, bot) && krosh;
}
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive()
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive();
return IsKigglerMoonkinTank(botAI, bot) && kiggler;
}
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
@@ -50,11 +50,11 @@ bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return (botAI->IsDps(bot) || botAI->IsTank(bot)) &&
!(botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive()) &&
!(IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive()) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive());
!(botAI->IsMainTank(bot) && maulgar) &&
!(botAI->IsAssistTankOfIndex(bot, 0, false) && olm) &&
!(botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye) &&
!(IsKroshMageTank(botAI, bot) && krosh) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler);
}
bool HighKingMaulgarHealerInDangerTrigger::IsActive()
@@ -66,7 +66,7 @@ bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) &&
return maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
!botAI->IsMainTank(bot);
}
@@ -74,7 +74,7 @@ bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive()
{
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker");
return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK;
return felStalker && bot->getClass() == CLASS_WARLOCK;
}
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
@@ -120,12 +120,12 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
switch (hunterIndex)
{
case 0:
return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f &&
olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank);
return olm && olm->GetHealthPct() > 98.0f &&
olmTank && botAI->CanCastSpell("misdirection", olmTank);
case 1:
return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank);
return blindeye && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && botAI->CanCastSpell("misdirection", blindeyeTank);
default:
break;
@@ -136,25 +136,24 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
// Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive()
bool GruulTheDragonkillerBossEngagedByTanksTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsMainTank(bot);
return gruul && botAI->IsTank(bot);
}
bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive()
bool GruulTheDragonkillerBossEngagedByRangedTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsRanged(bot);
return gruul && botAI->IsRanged(bot);
}
bool GruulTheDragonkillerIncomingShatterTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() &&
(bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
return gruul && (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
}

View File

@@ -73,17 +73,17 @@ public:
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger
class GruulTheDragonkillerBossEngagedByTanksTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {}
GruulTheDragonkillerBossEngagedByTanksTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by tanks") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger
class GruulTheDragonkillerBossEngagedByRangedTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {}
GruulTheDragonkillerBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by ranged") {}
bool IsActive() override;
};

View File

@@ -6,19 +6,16 @@
namespace GruulsLairHelpers
{
namespace GruulsLairLocations
{
// Olm does not chase properly due to the Core's caster movement issues
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// "MaulgarRoomCenter" is to keep healers in a centralized location
const Location MaulgarTankPosition = { 90.686f, 167.047f, -13.234f };
const Location OlmTankPosition = { 87.485f, 234.942f, -3.635f };
const Location BlindeyeTankPosition = { 99.681f, 213.989f, -10.345f };
const Location KroshTankPosition = { 116.880f, 166.208f, -14.231f };
const Location MaulgarRoomCenter = { 88.754f, 150.759f, -11.569f };
const Location GruulTankPosition = { 241.238f, 365.025f, -4.220f };
}
// Olm does not chase properly due to the Core's caster movement issues
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// "MaulgarRoomCenter" is to keep healers in a centralized location
const Position MAULGAR_TANK_POSITION = { 90.686f, 167.047f, -13.234f };
const Position OLM_TANK_POSITION = { 87.485f, 234.942f, -3.635f };
const Position BLINDEYE_TANK_POSITION = { 99.681f, 213.989f, -10.345f };
const Position KROSH_TANK_POSITION = { 116.880f, 166.208f, -14.231f };
const Position MAULGAR_ROOM_CENTER = { 88.754f, 150.759f, -11.569f };
const Position GRUUL_TANK_POSITION = { 241.238f, 365.025f, -4.220f };
bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
{
@@ -42,84 +39,43 @@ namespace GruulsLairHelpers
return false;
}
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
{
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return false;
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
// (1) First loop: Return the first assistant Mage (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE)
continue;
if (member->getClass() == CLASS_MAGE)
if (group->IsAssistant(member->GetGUID()))
return member == bot;
}
// (2) Fall back to bot Mage with highest HP
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_MAGE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
highestHpMage = member;
highestHp = hp;
}
highestHpMage = member;
highestHp = hp;
}
}
// (3) Return the found Mage tank, or nullptr if none found
return highestHpMage == bot;
}
@@ -129,30 +85,37 @@ namespace GruulsLairHelpers
if (!group)
return false;
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
// (1) First loop: Return the first assistant Moonkin (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID)
continue;
if (member->getClass() == CLASS_DRUID)
if (group->IsAssistant(member->GetGUID()) &&
AiFactory::GetPlayerSpecTab(member) == DRUID_TAB_BALANCE)
return member == bot;
}
// (2) Fall back to bot Moonkin with highest HP
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID ||
!GET_PLAYERBOT_AI(member) || AiFactory::GetPlayerSpecTab(member) != DRUID_TAB_BALANCE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
int tab = AiFactory::GetPlayerSpecTab(member);
if (tab == DRUID_TAB_BALANCE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
highestHpMoonkin = member;
highestHp = hp;
}
}
highestHpMoonkin = member;
highestHp = hp;
}
}
// (3) Return the found Moonkin tank, or nullptr if none found
return highestHpMoonkin == bot;
}

View File

@@ -2,23 +2,19 @@
#define RAID_GRUULSLAIRHELPERS_H
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace GruulsLairHelpers
{
enum GruulsLairSpells
{
// High King Maulgar
SPELL_WHIRLWIND = 33238,
SPELL_WHIRLWIND = 33238,
// Krosh Firehand
SPELL_SPELL_SHIELD = 33054,
SPELL_SPELL_SHIELD = 33054,
// Hunter
SPELL_MISDIRECTION = 35079,
// Warlock
SPELL_BANISH = 18647, // Rank 2
SPELL_MISDIRECTION = 35079,
// Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525,
@@ -30,33 +26,20 @@ namespace GruulsLairHelpers
NPC_WILD_FEL_STALKER = 18847,
};
constexpr uint32 GRUULS_LAIR_MAP_ID = 565;
bool IsAnyOgreBossAlive(PlayerbotAI* botAI);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot);
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot);
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos);
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos);
struct Location
{
float x, y, z;
};
namespace GruulsLairLocations
{
extern const Location MaulgarTankPosition;
extern const Location OlmTankPosition;
extern const Location BlindeyeTankPosition;
extern const Location KroshTankPosition;
extern const Location MaulgarRoomCenter;
extern const Location GruulTankPosition;
}
extern const Position MAULGAR_TANK_POSITION;
extern const Position OLM_TANK_POSITION;
extern const Position BLINDEYE_TANK_POSITION;
extern const Position KROSH_TANK_POSITION;
extern const Position MAULGAR_ROOM_CENTER;
extern const Position GRUUL_TANK_POSITION;
}
#endif

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers;
@@ -44,7 +45,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (attumenMounted)
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, attumenMounted);
SetRtiTarget(botAI, "star", attumenMounted);
@@ -57,7 +58,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
}
else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, midnight);
if (!botAI->IsAssistTankOfIndex(bot, 0))
@@ -180,7 +181,7 @@ bool MoroesMarkTargetAction::Execute(Event event)
if (target)
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, target);
SetRtiTarget(botAI, "skull", target);
@@ -405,7 +406,7 @@ bool TheCuratorMarkAstralFlareAction::Execute(Event event)
if (!flare)
return false;
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, flare);
SetRtiTarget(botAI, "skull", flare);
@@ -469,11 +470,11 @@ bool TheCuratorSpreadRangedAction::Execute(Event event)
// Prioritize (1) Demon Chains, (2) Kil'rek, (3) Illhoof
bool TerestianIllhoofMarkTargetAction::Execute(Event event)
{
Unit* demonChains = AI_VALUE2(Unit*, "find target", "demon chains");
Unit* kilrek = AI_VALUE2(Unit*, "find target", "kil'rek");
Unit* demonChains = GetFirstAliveUnitByEntry(botAI, NPC_DEMON_CHAINS);
Unit* kilrek = GetFirstAliveUnitByEntry(botAI, NPC_KILREK);
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
if (target)
MarkTargetWithSkull(bot, target);
@@ -1007,7 +1008,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (netherspite->GetHealth() == netherspite->GetMaxHealth() &&
!netherspite->HasAura(SPELL_GREEN_BEAM_HEAL))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.insert_or_assign(instanceId, now);
if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1018,7 +1019,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.erase(instanceId);
if (botAI->IsTank(bot))
@@ -1029,7 +1030,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.try_emplace(instanceId, now);
if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1458,7 +1459,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
nightbaneDpsWaitTimer.erase(instanceId);
}
// Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer
@@ -1466,7 +1467,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
{
nightbaneRainOfBonesHit.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{
nightbaneFlightPhaseStartTimer.erase(instanceId);
nightbaneDpsWaitTimer.try_emplace(instanceId, now);
@@ -1482,7 +1483,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{
nightbaneDpsWaitTimer.erase(instanceId);
nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now);

View File

@@ -10,6 +10,7 @@
#include "MageActions.h"
#include "Playerbots.h"
#include "PriestActions.h"
#include "RaidBossHelpers.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ShamanActions.h"
@@ -242,6 +243,9 @@ float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_ENFEEBLE))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f;

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers;
@@ -40,7 +41,7 @@ bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
@@ -110,7 +111,7 @@ bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
@@ -126,7 +127,7 @@ bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
@@ -178,7 +179,7 @@ bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
@@ -202,7 +203,7 @@ bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
@@ -279,7 +280,7 @@ bool NetherspiteBossIsBanishedTrigger::IsActive()
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
if (!botAI->IsTank(bot) && !IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");

View File

@@ -1,7 +1,6 @@
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
namespace KarazhanHelpers
{
@@ -52,75 +51,6 @@ namespace KarazhanHelpers
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
@@ -132,44 +62,6 @@ namespace KarazhanHelpers
return nullptr;
}
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();

View File

@@ -61,6 +61,11 @@ namespace KarazhanHelpers
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_TERESTIAN_ILLHOOF = 15688,
NPC_DEMON_CHAINS = 17248,
NPC_KILREK = 17229,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
@@ -74,8 +79,8 @@ namespace KarazhanHelpers
NPC_NETHERSPITE_INFERNAL = 17646,
};
const uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f;
constexpr uint32 KARAZHAN_MAP_ID = 532;
constexpr float NIGHTBANE_FLIGHT_Z = 95.0f;
// Attumen the Huntsman
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
@@ -105,17 +110,7 @@ namespace KarazhanHelpers
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);

View File

@@ -4,6 +4,7 @@
#include "ObjectAccessor.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace MagtheridonHelpers;
@@ -14,46 +15,45 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
return false;
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
MarkTargetWithSquare(bot, channelerSquare);
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
if (channelerStar)
MarkTargetWithStar(bot, channelerStar);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
if (channelerCircle)
MarkTargetWithCircle(bot, channelerCircle);
// After first three channelers are dead, wait for Magtheridon to activate
if ((!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()))
if (!channelerSquare && !channelerStar && !channelerCircle)
{
const Location& position = MagtheridonsLairLocations::WaitingForMagtheridonPosition;
if (!bot->IsWithinDist2d(position.x, position.y, 2.0f))
const Position& position = WAITING_FOR_MAGTHERIDON_POSITION;
if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), 2.0f))
{
return MoveTo(bot->GetMapId(), position.x, position.y, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, position.GetPositionX(), position.GetPositionY(),
position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
bot->SetFacingTo(position.orientation);
bot->SetFacingTo(position.GetOrientation());
return true;
}
Creature* currentTarget = nullptr;
std::string rtiName;
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
{
currentTarget = channelerSquare;
rtiName = "square";
}
else if (channelerStar && channelerStar->IsAlive())
else if (channelerStar)
{
currentTarget = channelerStar;
rtiName = "star";
}
else if (channelerCircle && channelerCircle->IsAlive())
else if (channelerCircle)
{
currentTarget = channelerCircle;
rtiName = "circle";
@@ -70,7 +70,7 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
{
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (!channelerDiamond || !channelerDiamond->IsAlive())
if (!channelerDiamond)
return false;
MarkTargetWithDiamond(bot, channelerDiamond);
@@ -81,18 +81,18 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
if (channelerDiamond->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NWChannelerTankPosition;
const Position& position = NW_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -103,7 +103,7 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
{
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!channelerTriangle || !channelerTriangle->IsAlive())
if (!channelerTriangle)
return false;
MarkTargetWithTriangle(bot, channelerTriangle);
@@ -114,18 +114,18 @@ bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
if (channelerTriangle->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NEChannelerTankPosition;
const Position& position = NE_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -175,7 +175,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
switch (hunterIndex)
{
case 0:
if (mainTank && channelerStar && channelerStar->IsAlive() &&
if (mainTank && channelerStar &&
channelerStar->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
@@ -190,7 +190,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
break;
case 1:
if (mainTank && channelerCircle && channelerCircle->IsAlive() &&
if (mainTank && channelerCircle &&
channelerCircle->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
@@ -215,90 +215,69 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event event)
{
// Listed in order of priority
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
{
SetRtiTarget(botAI, "square", channelerSquare);
if (bot->GetTarget() != channelerSquare->GetGUID())
{
bot->SetSelection(channelerSquare->GetGUID());
return Attack(channelerSquare);
}
return false;
}
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
if (channelerStar)
{
SetRtiTarget(botAI, "star", channelerStar);
if (bot->GetTarget() != channelerStar->GetGUID())
{
bot->SetSelection(channelerStar->GetGUID());
return Attack(channelerStar);
}
return false;
}
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
if (channelerCircle)
{
SetRtiTarget(botAI, "circle", channelerCircle);
if (bot->GetTarget() != channelerCircle->GetGUID())
{
bot->SetSelection(channelerCircle->GetGUID());
return Attack(channelerCircle);
}
return false;
}
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (channelerDiamond && channelerDiamond->IsAlive())
if (channelerDiamond)
{
SetRtiTarget(botAI, "diamond", channelerDiamond);
if (bot->GetTarget() != channelerDiamond->GetGUID())
{
bot->SetSelection(channelerDiamond->GetGUID());
return Attack(channelerDiamond);
}
return false;
}
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (channelerTriangle && channelerTriangle->IsAlive())
if (channelerTriangle)
{
SetRtiTarget(botAI, "triangle", channelerTriangle);
if (bot->GetTarget() != channelerTriangle->GetGUID())
{
bot->SetSelection(channelerTriangle->GetGUID());
return Attack(channelerTriangle);
}
return false;
}
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) &&
(!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()) &&
(!channelerDiamond || !channelerDiamond->IsAlive()) &&
(!channelerTriangle || !channelerTriangle->IsAlive()))
!channelerSquare && !channelerStar && !channelerCircle &&
!channelerDiamond && !channelerTriangle)
{
SetRtiTarget(botAI, "cross", magtheridon);
if (bot->GetTarget() != magtheridon->GetGUID())
{
bot->SetSelection(magtheridon->GetGUID());
return Attack(magtheridon);
}
}
return false;
@@ -343,15 +322,15 @@ bool MagtheridonWarlockCCBurningAbyssalAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < abyssals.size())
{
Unit* assignedAbyssal = abyssals[warlockIndex];
if (!assignedAbyssal->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedAbyssal, true))
if (!botAI->HasAura("banish", assignedAbyssal) && botAI->CanCastSpell("banish", assignedAbyssal))
return botAI->CastSpell("banish", assignedAbyssal);
}
for (size_t i = warlocks.size(); i < abyssals.size(); ++i)
{
Unit* excessAbyssal = abyssals[i];
if (!excessAbyssal->HasAura(SPELL_BANISH) && !excessAbyssal->HasAura(SPELL_FEAR) &&
botAI->CanCastSpell(SPELL_FEAR, excessAbyssal, true))
if (!botAI->HasAura("banish", excessAbyssal) && !botAI->HasAura("fear", excessAbyssal) &&
botAI->CanCastSpell("fear", excessAbyssal))
return botAI->CastSpell("fear", excessAbyssal);
}
@@ -373,22 +352,20 @@ bool MagtheridonMainTankPositionBossAction::Execute(Event event)
if (magtheridon->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::MagtheridonTankPosition;
const Position& position = MAGTHERIDON_TANK_POSITION;
const float maxDistance = 2.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true);
}
bot->SetFacingTo(position.orientation);
}
return false;
@@ -440,13 +417,13 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
}
bool isHealer = botAI->IsHeal(bot);
const Location& center = isHealer
? MagtheridonsLairLocations::HealerSpreadPosition
: MagtheridonsLairLocations::RangedSpreadPosition;
const Position& center = isHealer
? HEALER_SPREAD_POSITION
: RANGED_SPREAD_POSITION;
float maxSpreadRadius = isHealer ? 15.0f : 20.0f;
float centerX = center.x;
float centerY = center.y;
float centerZ = bot->GetPositionZ();
float centerX = center.GetPositionX();
float centerY = center.GetPositionY();
float centerZ = center.GetPositionZ();
const float radiusBuffer = 3.0f;
if (!initialPositions.count(bot->GetGUID()))
@@ -479,7 +456,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
hasReachedInitialPosition[bot->GetGUID()] = true;
@@ -499,7 +476,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), targetX, targetY, centerZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, centerZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -593,7 +570,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -603,7 +580,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
float fallbackY = cubeInfo.y + sin(angle) * safeWaitDistance;
float fallbackZ = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), fallbackX, fallbackY, fallbackZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, fallbackX, fallbackY, fallbackZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -638,7 +615,7 @@ bool MagtheridonUseManticronCubeAction::HandleCubeInteraction(const CubeInfo& cu
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
}
@@ -663,14 +640,14 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
if (lastBlastNova && !blastNovaActive && IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
blastNovaTimer[instanceId] = now;
lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{
spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now);
@@ -679,11 +656,12 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
}
else
{
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
botToCubeAssignment.clear();
ObjectGuid guid = bot->GetGUID();
MagtheridonSpreadRangedAction::initialPositions.erase(guid);
MagtheridonSpreadRangedAction::hasReachedInitialPosition.erase(guid);
botToCubeAssignment.erase(guid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{
spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId);

View File

@@ -6,8 +6,6 @@
#include "AttackAction.h"
#include "MovementActions.h"
using namespace MagtheridonHelpers;
class MagtheridonMainTankAttackFirstThreeChannelersAction : public AttackAction
{
public:
@@ -85,8 +83,8 @@ public:
private:
bool HandleCubeRelease(Unit* magtheridon, GameObject* cube);
bool ShouldActivateCubeLogic(Unit* magtheridon);
bool HandleWaitingPhase(const CubeInfo& cubeInfo);
bool HandleCubeInteraction(const CubeInfo& cubeInfo, GameObject* cube);
bool HandleWaitingPhase(const MagtheridonHelpers::CubeInfo& cubeInfo);
bool HandleCubeInteraction(const MagtheridonHelpers::CubeInfo& cubeInfo, GameObject* cube);
};
class MagtheridonManageTimersAndAssignmentsAction : public Action

View File

@@ -8,6 +8,7 @@
#include "GenericSpellActions.h"
#include "Playerbots.h"
#include "WarlockActions.h"
#include "WipeAction.h"
using namespace MagtheridonHelpers;
@@ -24,10 +25,10 @@ float MagtheridonUseManticronCubeMultiplier::GetValue(Action* action)
auto it = botToCubeAssignment.find(bot->GetGUID());
if (it != botToCubeAssignment.end())
{
if (dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
if (dynamic_cast<WipeAction*>(action))
return 1.0f;
return 0.0f;
else if (!dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
return 0.0f;
}
}
@@ -41,28 +42,31 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f;
if (botAI->IsMainTank(bot))
return 1.0f;
const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))
if (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action)))
return 0.0f;
}
return 1.0f;
}
// No tank assist for offtanks during the channeler phase
// So they don't try to pull channelers from each other or the main tank
float MagtheridonDisableOffTankAssistMultiplier::GetValue(Action* action)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
if (!magtheridon)
return 1.0f;
if (bot->GetVictim() == nullptr)
return 1.0f;
if ((botAI->IsAssistTankOfIndex(bot, 0) || botAI->IsAssistTankOfIndex(bot, 1)) &&
dynamic_cast<TankAssistAction*>(action))
return 0.0f;

View File

@@ -18,7 +18,7 @@ bool MagtheridonNWChannelerEngagedByFirstAssistTankTrigger::IsActive()
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 0) &&
channelerDiamond && channelerDiamond->IsAlive();
channelerDiamond;
}
bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
@@ -27,7 +27,7 @@ bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 1) &&
channelerTriangle && channelerTriangle->IsAlive();
channelerTriangle;
}
bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
@@ -38,8 +38,7 @@ bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
return magtheridon && bot->getClass() == CLASS_HUNTER &&
((channelerStar && channelerStar->IsAlive()) ||
(channelerCircle && channelerCircle->IsAlive()));
(channelerStar || channelerCircle);
}
bool MagtheridonDeterminingKillOrderTrigger::IsActive()
@@ -51,12 +50,11 @@ bool MagtheridonDeterminingKillOrderTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!magtheridon || botAI->IsHeal(bot) || botAI->IsMainTank(bot) ||
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond && channelerDiamond->IsAlive()) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle && channelerTriangle->IsAlive()))
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle))
return false;
return (channeler && channeler->IsAlive()) || (magtheridon &&
!magtheridon->HasAura(SPELL_SHADOW_CAGE));
return channeler || (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE));
}
bool MagtheridonBurningAbyssalSpawnedTrigger::IsActive()
@@ -84,10 +82,8 @@ bool MagtheridonBossEngagedByMainTankTrigger::IsActive()
bool MagtheridonBossEngagedByRangedTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
return magtheridon && botAI->IsRanged(bot) &&
!(channeler && channeler->IsAlive());
return magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) && botAI->IsRanged(bot);
}
bool MagtheridonIncomingBlastNovaTrigger::IsActive()
@@ -122,7 +118,5 @@ bool MagtheridonIncomingBlastNovaTrigger::IsActive()
bool MagtheridonNeedToManageTimersAndAssignmentsTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon;
return AI_VALUE2(Unit*, "find target", "magtheridon");
}

View File

@@ -1,22 +1,18 @@
#include "RaidMagtheridonHelpers.h"
#include "Creature.h"
#include "GameObject.h"
#include "GroupReference.h"
#include "Map.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
namespace MagtheridonHelpers
{
namespace MagtheridonsLairLocations
{
const Location WaitingForMagtheridonPosition = { 1.359f, 2.048f, -0.406f, 3.135f };
const Location MagtheridonTankPosition = { 22.827f, 2.105f, -0.406f, 3.135f };
const Location NWChannelerTankPosition = { -11.764f, 30.818f, -0.411f, 0.0f };
const Location NEChannelerTankPosition = { -12.490f, -26.211f, -0.411f, 0.0f };
const Location RangedSpreadPosition = { -14.890f, 1.995f, -0.406f, 0.0f };
const Location HealerSpreadPosition = { -2.265f, 1.874f, -0.404f, 0.0f };
}
const Position WAITING_FOR_MAGTHERIDON_POSITION = { 1.359f, 2.048f, -0.406f, 3.135f };
const Position MAGTHERIDON_TANK_POSITION = { 22.827f, 2.105f, -0.406f, 3.135f };
const Position NW_CHANNELER_TANK_POSITION = { -11.764f, 30.818f, -0.411f, 0.0f };
const Position NE_CHANNELER_TANK_POSITION = { -12.490f, -26.211f, -0.411f, 0.0f };
const Position RANGED_SPREAD_POSITION = { -14.890f, 1.995f, -0.406f, 0.0f };
const Position HEALER_SPREAD_POSITION = { -2.265f, 1.874f, -0.404f, 0.0f };
// Identify channelers by their database GUIDs
Creature* GetChanneler(Player* bot, uint32 dbGuid)
@@ -29,63 +25,11 @@ namespace MagtheridonHelpers
if (it == map->GetCreatureBySpawnIdStore().end())
return nullptr;
return it->second;
}
Creature* channeler = it->second;
if (!channeler->IsAlive())
return nullptr;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
return channeler;
}
const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS = { 43157, 43158, 43159, 43160, 43161 };
@@ -208,19 +152,4 @@ namespace MagtheridonHelpers
return true;
}
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return true;
}
}

View File

@@ -8,7 +8,6 @@
#include "Group.h"
#include "ObjectGuid.h"
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace MagtheridonHelpers
{
@@ -19,10 +18,6 @@ namespace MagtheridonHelpers
SPELL_BLAST_NOVA = 30616,
SPELL_SHADOW_GRASP = 30410,
// Warlock
SPELL_BANISH = 18647,
SPELL_FEAR = 6215,
// Hunter
SPELL_MISDIRECTION = 35079,
};
@@ -38,6 +33,7 @@ namespace MagtheridonHelpers
GO_BLAZE = 181832,
};
constexpr uint32 MAGTHERIDON_MAP_ID = 544;
constexpr uint32 SOUTH_CHANNELER = 90978;
constexpr uint32 WEST_CHANNELER = 90979;
constexpr uint32 NORTHWEST_CHANNELER = 90980;
@@ -45,31 +41,14 @@ namespace MagtheridonHelpers
constexpr uint32 NORTHEAST_CHANNELER = 90981;
Creature* GetChanneler(Player* bot, uint32 dbGuid);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location
{
float x, y, z, orientation;
};
namespace MagtheridonsLairLocations
{
extern const Location WaitingForMagtheridonPosition;
extern const Location MagtheridonTankPosition;
extern const Location NWChannelerTankPosition;
extern const Location NEChannelerTankPosition;
extern const Location RangedSpreadPosition;
extern const Location HealerSpreadPosition;
}
extern const Position WAITING_FOR_MAGTHERIDON_POSITION;
extern const Position MAGTHERIDON_TANK_POSITION;
extern const Position NW_CHANNELER_TANK_POSITION;
extern const Position NE_CHANNELER_TANK_POSITION;
extern const Position RANGED_SPREAD_POSITION;
extern const Position HEALER_SPREAD_POSITION;
struct CubeInfo
{

View File

@@ -2,6 +2,7 @@
#define _PLAYERBOT_RAIDMCACTIONCONTEXT_H
#include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h"
#include "RaidMcActions.h"

View File

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

View File

@@ -0,0 +1,142 @@
#include "RaidBossHelpers.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
// Functions to mark targets with raid target icons
// Note that these functions do not allow the player to change the icon during the encounter
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
// For bots to set their raid target icon to the specified icon on the specified target
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Return the first alive DPS bot in the specified instance map, excluding any specified bot
// Intended for purposes of storing and erasing timers and trackers in associative containers
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->GetMapId() != mapId ||
!GET_PLAYERBOT_AI(member) || !botAI->IsDps(member))
continue;
if (member != exclude)
return member == bot;
}
}
return false;
}
// Return the first matching alive unit from a cell search of nearby npcs
// More responsive than "find target," but performance cost is much higher
// Re: using the third parameter (false by default), some units are never considered
// to be in combat (e.g., totems)
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
{
if (!requireInCombat || unit->IsInCombat())
return unit;
}
}
return nullptr;
}
// Return the nearest alive player (human or bot) within the specified radius
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_RAIDBOSSHELPERS_H_
#define _PLAYERBOT_RAIDBOSSHELPERS_H_
#include "AiObject.h"
#include "Unit.h"
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude = nullptr);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat = false);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
#endif

View File

@@ -8,6 +8,7 @@
#include "RaidKarazhanStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidGruulsLairStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h"
#include "RaidVoAStrategy.h"
@@ -26,10 +27,11 @@ public:
creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["ssc"] = &RaidStrategyContext::ssc;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
creators["voa"] = &RaidStrategyContext::voa;
creators["uld"] = &RaidStrategyContext::uld;
creators["ulduar"] = &RaidStrategyContext::ulduar;
creators["onyxia"] = &RaidStrategyContext::onyxia;
creators["icc"] = &RaidStrategyContext::icc;
}
@@ -41,11 +43,12 @@ private:
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }
static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); }
static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* ulduar(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); }
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
#ifndef _PLAYERBOT_RAIDSSCACTIONS_H
#define _PLAYERBOT_RAIDSSCACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
// General
class SerpentShrineCavernEraseTimersAndTrackersAction : public Action
{
public:
SerpentShrineCavernEraseTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "serpent shrine cavern erase timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Trash
class UnderbogColossusEscapeToxicPoolAction : public MovementAction
{
public:
UnderbogColossusEscapeToxicPoolAction(
PlayerbotAI* botAI, std::string const name = "underbog colossus escape toxic pool") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class GreyheartTidecallerMarkWaterElementalTotemAction : public Action
{
public:
GreyheartTidecallerMarkWaterElementalTotemAction(
PlayerbotAI* botAI, std::string const name = "greyheart tidecaller mark water elemental totem") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstablePositionFrostTankAction : public AttackAction
{
public:
HydrossTheUnstablePositionFrostTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable position frost tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstablePositionNatureTankAction : public AttackAction
{
public:
HydrossTheUnstablePositionNatureTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable position nature tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstablePrioritizeElementalAddsAction : public AttackAction
{
public:
HydrossTheUnstablePrioritizeElementalAddsAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable prioritize elemental adds") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableFrostPhaseSpreadOutAction : public MovementAction
{
public:
HydrossTheUnstableFrostPhaseSpreadOutAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable frost phase spread out") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableMisdirectBossToTankAction : public Action
{
public:
HydrossTheUnstableMisdirectBossToTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable misdirect boss to tank") : Action(botAI, name) {}
bool Execute(Event event) override;
private:
bool TryMisdirectToFrostTank(Unit* hydross, Group* group);
bool TryMisdirectToNatureTank(Unit* hydross, Group* group);
};
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
{
public:
HydrossTheUnstableStopDpsUponPhaseChangeAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable stop dps upon phase change") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableManageTimersAction : public Action
{
public:
HydrossTheUnstableManageTimersAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable manage timers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// The Lurker Below
class TheLurkerBelowRunAroundBehindBossAction : public MovementAction
{
public:
TheLurkerBelowRunAroundBehindBossAction(
PlayerbotAI* botAI, std::string const name = "the lurker below run around behind boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowPositionMainTankAction : public AttackAction
{
public:
TheLurkerBelowPositionMainTankAction(
PlayerbotAI* botAI, std::string const name = "the lurker below position main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowSpreadRangedInArcAction : public MovementAction
{
public:
TheLurkerBelowSpreadRangedInArcAction(
PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged in arc") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowTanksPickUpAddsAction : public AttackAction
{
public:
TheLurkerBelowTanksPickUpAddsAction(
PlayerbotAI* botAI, std::string const name = "the lurker below tanks pick up adds") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowManageSpoutTimerAction : public Action
{
public:
TheLurkerBelowManageSpoutTimerAction(
PlayerbotAI* botAI, std::string const name = "the lurker below manage spout timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Leotheras the Blind
class LeotherasTheBlindTargetSpellbindersAction : public Action
{
public:
LeotherasTheBlindTargetSpellbindersAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind target spellbinders") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindPositionRangedAction : public MovementAction
{
public:
LeotherasTheBlindPositionRangedAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind position ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindDemonFormTankAttackBossAction : public AttackAction
{
public:
LeotherasTheBlindDemonFormTankAttackBossAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form tank attack boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMeleeTanksDontAttackDemonFormAction : public Action
{
public:
LeotherasTheBlindMeleeTanksDontAttackDemonFormAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind melee tanks don't attack demon form") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction
{
public:
LeotherasTheBlindRunAwayFromWhirlwindAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind run away from whirlwind") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMeleeDpsRunAwayFromBossAction : public MovementAction
{
public:
LeotherasTheBlindMeleeDpsRunAwayFromBossAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind melee dps run away from boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindDestroyInnerDemonAction : public AttackAction
{
public:
LeotherasTheBlindDestroyInnerDemonAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind destroy inner demon") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool HandleFeralTankStrategy(Unit* innerDemon);
bool HandleHealerStrategy(Unit* innerDemon);
};
class LeotherasTheBlindFinalPhaseAssignDpsPriorityAction : public AttackAction
{
public:
LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind final phase assign dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMisdirectBossToDemonFormTankAction : public AttackAction
{
public:
LeotherasTheBlindMisdirectBossToDemonFormTankAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind misdirect boss to demon form tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindManageDpsWaitTimersAction : public Action
{
public:
LeotherasTheBlindManageDpsWaitTimersAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind manage dps wait timers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Fathom-Lord Karathress
class FathomLordKarathressMainTankPositionBossAction : public AttackAction
{
public:
FathomLordKarathressMainTankPositionBossAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress main tank position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressFirstAssistTankPositionCaribdisAction : public AttackAction
{
public:
FathomLordKarathressFirstAssistTankPositionCaribdisAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position caribdis") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressSecondAssistTankPositionSharkkisAction : public AttackAction
{
public:
FathomLordKarathressSecondAssistTankPositionSharkkisAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position sharkkis") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressThirdAssistTankPositionTidalvessAction : public AttackAction
{
public:
FathomLordKarathressThirdAssistTankPositionTidalvessAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position tidalvess") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressPositionCaribdisTankHealerAction : public MovementAction
{
public:
FathomLordKarathressPositionCaribdisTankHealerAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress position caribdis tank healer") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressMisdirectBossesToTanksAction : public AttackAction
{
public:
FathomLordKarathressMisdirectBossesToTanksAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress misdirect bosses to tanks") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressAssignDpsPriorityAction : public AttackAction
{
public:
FathomLordKarathressAssignDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress assign dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressManageDpsTimerAction : public Action
{
public:
FathomLordKarathressManageDpsTimerAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress manage dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Morogrim Tidewalker
class MorogrimTidewalkerMisdirectBossToMainTankAction : public AttackAction
{
public:
MorogrimTidewalkerMisdirectBossToMainTankAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker misdirect boss to main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MorogrimTidewalkerMoveBossToTankPositionAction : public AttackAction
{
public:
MorogrimTidewalkerMoveBossToTankPositionAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker move boss to tank position") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool MoveToPhase1TankPosition(Unit* tidewalker);
bool MoveToPhase2TankPosition(Unit* tidewalker);
};
class MorogrimTidewalkerPhase2RepositionRangedAction : public MovementAction
{
public:
MorogrimTidewalkerPhase2RepositionRangedAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker phase 2 reposition ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
// Lady Vashj <Coilfang Matron>
class LadyVashjMainTankPositionBossAction : public AttackAction
{
public:
LadyVashjMainTankPositionBossAction(
PlayerbotAI* botAI, std::string const name = "lady vashj main tank position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjPhase1SpreadRangedInArcAction : public MovementAction
{
public:
LadyVashjPhase1SpreadRangedInArcAction(
PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 spread ranged in arc") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjSetGroundingTotemInMainTankGroupAction : public MovementAction
{
public:
LadyVashjSetGroundingTotemInMainTankGroupAction(
PlayerbotAI* botAI, std::string const name = "lady vashj set grounding totem in main tank group") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjStaticChargeMoveAwayFromGroupAction : public MovementAction
{
public:
LadyVashjStaticChargeMoveAwayFromGroupAction(
PlayerbotAI* botAI, std::string const name = "lady vashj static charge move away from group") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjMisdirectBossToMainTankAction : public AttackAction
{
public:
LadyVashjMisdirectBossToMainTankAction(
PlayerbotAI* botAI, std::string const name = "lady vashj misdirect boss to main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAssignPhase2AndPhase3DpsPriorityAction : public AttackAction
{
public:
LadyVashjAssignPhase2AndPhase3DpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "lady vashj assign phase 2 and phase 3 dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjMisdirectStriderToFirstAssistTankAction : public AttackAction
{
public:
LadyVashjMisdirectStriderToFirstAssistTankAction(
PlayerbotAI* botAI, std::string const name = "lady vashj misdirect strider to first assist tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjTankAttackAndMoveAwayStriderAction : public AttackAction
{
public:
LadyVashjTankAttackAndMoveAwayStriderAction(
PlayerbotAI* botAI, std::string const name = "lady vashj tank attack and move away strider") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjTeleportToTaintedElementalAction : public AttackAction
{
public:
LadyVashjTeleportToTaintedElementalAction(
PlayerbotAI* botAI, std::string const name = "lady vashj teleport to tainted elemental") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjLootTaintedCoreAction : public MovementAction
{
public:
LadyVashjLootTaintedCoreAction(
PlayerbotAI* botAI, std::string const name = "lady vashj loot tainted core") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjPassTheTaintedCoreAction : public MovementAction
{
public:
LadyVashjPassTheTaintedCoreAction(
PlayerbotAI* botAI, std::string const name = "lady vashj pass the tainted core") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger);
bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger);
bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver);
bool UseCoreOnNearestGenerator(const uint32 instanceId);
};
class LadyVashjDestroyTaintedCoreAction : public Action
{
public:
LadyVashjDestroyTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj destroy tainted core") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjEraseCorePassingTrackersAction : public Action
{
public:
LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAvoidToxicSporesAction : public MovementAction
{
public:
LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
static std::vector<Unit*> GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot);
private:
Position FindSafestNearbyPosition(const std::vector<Unit*>& spores, const Position& position, float maxRadius, float hazardRadius);
bool IsPathSafeFromSpores(const Position& start, const Position& end, const std::vector<Unit*>& spores, float hazardRadius);
};
class LadyVashjUseFreeActionAbilitiesAction : public Action
{
public:
LadyVashjUseFreeActionAbilitiesAction(PlayerbotAI* botAI, std::string const name = "lady vashj use free action abilities") : Action(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,799 @@
#include "RaidSSCMultipliers.h"
#include "RaidSSCActions.h"
#include "RaidSSCHelpers.h"
#include "ChooseTargetActions.h"
#include "DestroyItemAction.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "DruidShapeshiftActions.h"
#include "FollowActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "LootAction.h"
#include "MageActions.h"
#include "PaladinActions.h"
#include "Playerbots.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ShamanActions.h"
#include "WarlockActions.h"
#include "WarriorActions.h"
#include "WipeAction.h"
using namespace SerpentShrineCavernHelpers;
// Trash
float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_TOXIC_POOL))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Hydross the Unstable <Duke of Currents>
float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true))
return 1.0f;
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (!hydross)
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
(dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action)))
{
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f;
}
return 1.0f;
}
float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (!hydross)
return 1.0f;
Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross");
Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross");
if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true) &&
(waterElemental || natureElemental))
return 1.0f;
if (dynamic_cast<HydrossTheUnstableMisdirectBossToTankAction*>(action))
return 1.0f;
const uint32 instanceId = hydross->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
constexpr uint8 phaseChangeWaitSeconds = 1;
constexpr uint8 dpsWaitSeconds = 5;
if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot))
{
auto itDps = hydrossFrostDpsWaitTimer.find(instanceId);
auto itPhase = hydrossChangeToFrostPhaseTimer.find(instanceId);
bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() ||
(now - itDps->second) < dpsWaitSeconds);
bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
{
auto itDps = hydrossNatureDpsWaitTimer.find(instanceId);
auto itPhase = hydrossChangeToNaturePhaseTimer.find(instanceId);
bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() ||
(now - itDps->second) < dpsWaitSeconds);
bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "hydross the unstable"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
return 1.0f;
}
// The Lurker Below
float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action)
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return 1.0f;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
if (it != lurkerSpoutTimer.end() && it->second > now)
{
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<TheLurkerBelowRunAroundBehindBossAction*>(action))
return 0.0f;
}
return 1.0f;
}
float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "the lurker below"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Disable tank assist during Submerge only if there are 3 or more tanks in the raid
float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
if (bot->GetVictim() == nullptr)
return 1.0f;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
uint8 tankCount = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsTank(member))
++tankCount;
}
if (tankCount >= 3)
{
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Leotheras the Blind
float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
{
if (botAI->IsTank(bot))
return 1.0f;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
if (!leotherasHuman)
return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
(leotherasHuman->HasAura(SPELL_WHIRLWIND) ||
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return 1.0f;
if (GetPhase2LeotherasDemon(botAI) && dynamic_cast<AttackAction*>(action))
return 0.0f;
if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f;
return 1.0f;
}
float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) ||
dynamic_cast<CastCureSpellAction*>(action) ||
dynamic_cast<CurePartyMemberAction*>(action) ||
dynamic_cast<CastBuffSpellAction*>(action) ||
dynamic_cast<ResurrectPartyMemberAction*>(action) ||
dynamic_cast<PartyMemberActionNameSupport*>(action) ||
dynamic_cast<CastBearFormAction*>(action) ||
dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
if (!GetPhase2LeotherasDemon(botAI))
return 1.0f;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (chaosBlast && chaosBlast->GetStackAmount() >= 5)
{
if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras)
return 1.0f;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
if (dynamic_cast<LeotherasTheBlindMisdirectBossToDemonFormTankAction*>(action))
return 1.0f;
const uint32 instanceId = leotheras->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSecondsPhase1 = 5;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon)
{
if (botAI->IsTank(bot))
return 1.0f;
auto it = leotherasHumanFormDpsWaitTimer.find(instanceId);
if (it == leotherasHumanFormDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase1)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI);
Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon)
{
if (demonFormTank && demonFormTank == bot)
return 1.0f;
if (!demonFormTank && botAI->IsTank(bot))
return 1.0f;
auto it = leotherasDemonFormDpsWaitTimer.find(instanceId);
if (it == leotherasDemonFormDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase2)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
constexpr uint8 dpsWaitSecondsPhase3 = 8;
if (leotherasPhase3Demon)
{
if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot))
return 1.0f;
auto it = leotherasFinalPhaseDpsWaitTimer.find(instanceId);
if (it == leotherasFinalPhaseDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase3)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// Don't use Bloodlust/Heroism during the Channeler phase
float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Fathom-Lord Karathress
float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastTauntAction*>(action) ||
dynamic_cast<CastChallengingShoutAction*>(action) ||
dynamic_cast<CastThunderClapAction*>(action) ||
dynamic_cast<CastShockwaveAction*>(action) ||
dynamic_cast<CastCleaveAction*>(action) ||
dynamic_cast<CastGrowlAction*>(action) ||
dynamic_cast<CastSwipeAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) ||
dynamic_cast<CastAvengersShieldAction*>(action) ||
dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastDeathAndDecayAction*>(action) ||
dynamic_cast<CastPestilenceAction*>(action) ||
dynamic_cast<CastBloodBoilAction*>(action))
return 0.0f;
return 1.0f;
}
float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action)
{
if (!botAI->IsDps(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f;
}
}
return 1.0f;
}
float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
return 1.0f;
}
float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action)
{
if (botAI->IsTank(bot))
return 1.0f;
Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
if (!karathress)
return 1.0f;
if (dynamic_cast<FathomLordKarathressMisdirectBossesToTanksAction*>(action))
return 1.0f;
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSeconds = 12;
auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId());
if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
return 1.0f;
}
float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action)
{
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"))
{
if (dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Morogrim Tidewalker
// Use Bloodlust/Heroism after the first Murloc spawn
float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker"))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
return 1.0f;
}
float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsMainTank(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}
float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
if (!tidewalker)
return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Lady Vashj <Coilfang Matron>
// Wait until phase 3 to use Bloodlust/Heroism
// Don't use other major cooldowns in Phase 1, either
float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN)
{
if (IsLadyVashjInPhase3(botAI))
return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) ||
dynamic_cast<CastIcyVeinsAction*>(action) ||
dynamic_cast<CastColdSnapAction*>(action) ||
dynamic_cast<CastArcanePowerAction*>(action) ||
dynamic_cast<CastPresenceOfMindAction*>(action) ||
dynamic_cast<CastCombustionAction*>(action) ||
dynamic_cast<CastRapidFireAction*>(action) ||
dynamic_cast<CastReadinessAction*>(action) ||
dynamic_cast<CastAvengingWrathAction*>(action) ||
dynamic_cast<CastElementalMasteryAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastArmyOfTheDeadAction*>(action) ||
dynamic_cast<CastSummonGargoyleAction*>(action) ||
dynamic_cast<CastBerserkingAction*>(action) ||
dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<UseTrinketAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "lady vashj") &&
IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action)
{
if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
return 1.0f;
}
// Bots should not loot the core with normal looting logic
float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "lady vashj"))
{
if (dynamic_cast<LootAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase2(botAI))
return 1.0f;
if (dynamic_cast<WipeAction*>(action) ||
dynamic_cast<DestroyItemAction*>(action) ||
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player)
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (hasCore(bot))
{
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (bot == designatedLooter)
{
if (!hasCore(bot))
return 1.0f;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot != fourthCorePasser)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (AnyRecentCoreInInventory(group, botAI))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
return 1.0f;
}
// All of phases 2 and 3 require a custom movement and targeting system
// So the standard target selection system must be disabled
float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (IsLadyVashjInPhase2(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
if (IsLadyVashjInPhase3(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider");
Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite");
if (enchanted || strider || elite)
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
else if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,236 @@
#ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H
#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H
#include "Multiplier.h"
// Trash
class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier
{
public:
UnderbogColossusEscapeToxicPoolMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "underbog colossus escape toxic pool") {}
virtual float GetValue(Action* action);
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstableDisableTankActionsMultiplier : public Multiplier
{
public:
HydrossTheUnstableDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable disable tank actions") {}
virtual float GetValue(Action* action);
};
class HydrossTheUnstableWaitForDpsMultiplier : public Multiplier
{
public:
HydrossTheUnstableWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable wait for dps") {}
virtual float GetValue(Action* action);
};
class HydrossTheUnstableControlMisdirectionMultiplier : public Multiplier
{
public:
HydrossTheUnstableControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable control misdirection") {}
virtual float GetValue(Action* action);
};
// The Lurker Below
class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier
{
public:
TheLurkerBelowStayAwayFromSpoutMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below stay away from spout") {}
virtual float GetValue(Action* action);
};
class TheLurkerBelowMaintainRangedSpreadMultiplier : public Multiplier
{
public:
TheLurkerBelowMaintainRangedSpreadMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below maintain ranged spread") {}
virtual float GetValue(Action* action);
};
class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier
{
public:
TheLurkerBelowDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below disable tank assist") {}
virtual float GetValue(Action* action);
};
// Leotheras the Blind
class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier
{
public:
LeotherasTheBlindAvoidWhirlwindMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier
{
public:
LeotherasTheBlindDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier : public Multiplier
{
public:
LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind melee dps avoid chaos blast") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindFocusOnInnerDemonMultiplier : public Multiplier
{
public:
LeotherasTheBlindFocusOnInnerDemonMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind focus on inner demon") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindWaitForDpsMultiplier : public Multiplier
{
public:
LeotherasTheBlindWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind wait for dps") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind delay bloodlust and heroism") {}
virtual float GetValue(Action* action);
};
// Fathom-Lord Karathress
class FathomLordKarathressDisableTankActionsMultiplier : public Multiplier
{
public:
FathomLordKarathressDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank actions") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressDisableAoeMultiplier : public Multiplier
{
public:
FathomLordKarathressDisableAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable aoe") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressControlMisdirectionMultiplier : public Multiplier
{
public:
FathomLordKarathressControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress control misdirection") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressWaitForDpsMultiplier : public Multiplier
{
public:
FathomLordKarathressWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress wait for dps") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier : public Multiplier
{
public:
FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress caribdis tank healer maintain position") {}
virtual float GetValue(Action* action);
};
// Morogrim Tidewalker
class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker delay bloodlust and heroism") {}
virtual float GetValue(Action* action);
};
class MorogrimTidewalkerDisableTankActionsMultiplier : public Multiplier
{
public:
MorogrimTidewalkerDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable tank actions") {}
virtual float GetValue(Action* action);
};
class MorogrimTidewalkerMaintainPhase2StackingMultiplier : public Multiplier
{
public:
MorogrimTidewalkerMaintainPhase2StackingMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker maintain phase2 stacking") {}
virtual float GetValue(Action* action);
};
// Lady Vashj <Coilfang Matron>
class LadyVashjDelayCooldownsMultiplier : public Multiplier
{
public:
LadyVashjDelayCooldownsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay cooldowns") {}
virtual float GetValue(Action* action);
};
class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier
{
public:
LadyVashjMaintainPhase1RangedSpreadMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj maintain phase1 ranged spread") {}
virtual float GetValue(Action* action);
};
class LadyVashjStaticChargeStayAwayFromGroupMultiplier : public Multiplier
{
public:
LadyVashjStaticChargeStayAwayFromGroupMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj static charge stay away from group") {}
virtual float GetValue(Action* action);
};
class LadyVashjDoNotLootTheTaintedCoreMultiplier : public Multiplier
{
public:
LadyVashjDoNotLootTheTaintedCoreMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj do not loot the tainted core") {}
virtual float GetValue(Action* action);
};
class LadyVashjCorePassersPrioritizePositioningMultiplier : public Multiplier
{
public:
LadyVashjCorePassersPrioritizePositioningMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj core passers prioritize positioning") {}
virtual float GetValue(Action* action);
};
class LadyVashjDisableAutomaticTargetingAndMovementModifier : public Multiplier
{
public:
LadyVashjDisableAutomaticTargetingAndMovementModifier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj disable automatic targeting and movement") {}
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,337 @@
#ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
#include "RaidSSCActions.h"
#include "NamedObjectContext.h"
class RaidSSCActionContext : public NamedObjectContext<Action>
{
public:
RaidSSCActionContext()
{
// General
creators["serpent shrine cavern erase timers and trackers"] =
&RaidSSCActionContext::serpent_shrine_cavern_erase_timers_and_trackers;
// Trash
creators["underbog colossus escape toxic pool"] =
&RaidSSCActionContext::underbog_colossus_escape_toxic_pool;
creators["greyheart tidecaller mark water elemental totem"] =
&RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem;
// Hydross the Unstable <Duke of Currents>
creators["hydross the unstable position frost tank"] =
&RaidSSCActionContext::hydross_the_unstable_position_frost_tank;
creators["hydross the unstable position nature tank"] =
&RaidSSCActionContext::hydross_the_unstable_position_nature_tank;
creators["hydross the unstable prioritize elemental adds"] =
&RaidSSCActionContext::hydross_the_unstable_prioritize_elemental_adds;
creators["hydross the unstable frost phase spread out"] =
&RaidSSCActionContext::hydross_the_unstable_frost_phase_spread_out;
creators["hydross the unstable misdirect boss to tank"] =
&RaidSSCActionContext::hydross_the_unstable_misdirect_boss_to_tank;
creators["hydross the unstable stop dps upon phase change"] =
&RaidSSCActionContext::hydross_the_unstable_stop_dps_upon_phase_change;
creators["hydross the unstable manage timers"] =
&RaidSSCActionContext::hydross_the_unstable_manage_timers;
// The Lurker Below
creators["the lurker below run around behind boss"] =
&RaidSSCActionContext::the_lurker_below_run_around_behind_boss;
creators["the lurker below position main tank"] =
&RaidSSCActionContext::the_lurker_below_position_main_tank;
creators["the lurker below spread ranged in arc"] =
&RaidSSCActionContext::the_lurker_below_spread_ranged_in_arc;
creators["the lurker below tanks pick up adds"] =
&RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds;
creators["the lurker below manage spout timer"] =
&RaidSSCActionContext::the_lurker_below_manage_spout_timer;
// Leotheras the Blind
creators["leotheras the blind target spellbinders"] =
&RaidSSCActionContext::leotheras_the_blind_target_spellbinders;
creators["leotheras the blind demon form tank attack boss"] =
&RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss;
creators["leotheras the blind melee tanks don't attack demon form"] =
&RaidSSCActionContext::leotheras_the_blind_melee_tanks_dont_attack_demon_form;
creators["leotheras the blind position ranged"] =
&RaidSSCActionContext::leotheras_the_blind_position_ranged;
creators["leotheras the blind run away from whirlwind"] =
&RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind;
creators["leotheras the blind melee dps run away from boss"] =
&RaidSSCActionContext::leotheras_the_blind_melee_dps_run_away_from_boss;
creators["leotheras the blind destroy inner demon"] =
&RaidSSCActionContext::leotheras_the_blind_destroy_inner_demon;
creators["leotheras the blind final phase assign dps priority"] =
&RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority;
creators["leotheras the blind misdirect boss to demon form tank"] =
&RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank;
creators["leotheras the blind manage dps wait timers"] =
&RaidSSCActionContext::leotheras_the_blind_manage_dps_wait_timers;
// Fathom-Lord Karathress
creators["fathom-lord karathress main tank position boss"] =
&RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss;
creators["fathom-lord karathress first assist tank position caribdis"] =
&RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_caribdis;
creators["fathom-lord karathress second assist tank position sharkkis"] =
&RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_sharkkis;
creators["fathom-lord karathress third assist tank position tidalvess"] =
&RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_tidalvess;
creators["fathom-lord karathress position caribdis tank healer"] =
&RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer;
creators["fathom-lord karathress misdirect bosses to tanks"] =
&RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks;
creators["fathom-lord karathress assign dps priority"] =
&RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority;
creators["fathom-lord karathress manage dps timer"] =
&RaidSSCActionContext::fathom_lord_karathress_manage_dps_timer;
// Morogrim Tidewalker
creators["morogrim tidewalker misdirect boss to main tank"] =
&RaidSSCActionContext::morogrim_tidewalker_misdirect_boss_to_main_tank;
creators["morogrim tidewalker move boss to tank position"] =
&RaidSSCActionContext::morogrim_tidewalker_move_boss_to_tank_position;
creators["morogrim tidewalker phase 2 reposition ranged"] =
&RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged;
// Lady Vashj <Coilfang Matron>
creators["lady vashj main tank position boss"] =
&RaidSSCActionContext::lady_vashj_main_tank_position_boss;
creators["lady vashj phase 1 spread ranged in arc"] =
&RaidSSCActionContext::lady_vashj_phase_1_spread_ranged_in_arc;
creators["lady vashj set grounding totem in main tank group"] =
&RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group;
creators["lady vashj static charge move away from group"] =
&RaidSSCActionContext::lady_vashj_static_charge_move_away_from_group;
creators["lady vashj misdirect boss to main tank"] =
&RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank;
creators["lady vashj assign phase 2 and phase 3 dps priority"] =
&RaidSSCActionContext::lady_vashj_assign_phase_2_and_phase_3_dps_priority;
creators["lady vashj misdirect strider to first assist tank"] =
&RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank;
creators["lady vashj tank attack and move away strider"] =
&RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider;
creators["lady vashj loot tainted core"] =
&RaidSSCActionContext::lady_vashj_loot_tainted_core;
creators["lady vashj teleport to tainted elemental"] =
&RaidSSCActionContext::lady_vashj_teleport_to_tainted_elemental;
creators["lady vashj pass the tainted core"] =
&RaidSSCActionContext::lady_vashj_pass_the_tainted_core;
creators["lady vashj destroy tainted core"] =
&RaidSSCActionContext::lady_vashj_destroy_tainted_core;
creators["lady vashj erase core passing trackers"] =
&RaidSSCActionContext::lady_vashj_erase_core_passing_trackers;
creators["lady vashj avoid toxic spores"] =
&RaidSSCActionContext::lady_vashj_avoid_toxic_spores;
creators["lady vashj use free action abilities"] =
&RaidSSCActionContext::lady_vashj_use_free_action_abilities;
}
private:
// General
static Action* serpent_shrine_cavern_erase_timers_and_trackers(
PlayerbotAI* botAI) { return new SerpentShrineCavernEraseTimersAndTrackersAction(botAI); }
// Trash
static Action* underbog_colossus_escape_toxic_pool(
PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); }
static Action* greyheart_tidecaller_mark_water_elemental_totem(
PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); }
// Hydross the Unstable <Duke of Currents>
static Action* hydross_the_unstable_position_frost_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); }
static Action* hydross_the_unstable_position_nature_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); }
static Action* hydross_the_unstable_prioritize_elemental_adds(
PlayerbotAI* botAI) { return new HydrossTheUnstablePrioritizeElementalAddsAction(botAI); }
static Action* hydross_the_unstable_frost_phase_spread_out(
PlayerbotAI* botAI) { return new HydrossTheUnstableFrostPhaseSpreadOutAction(botAI); }
static Action* hydross_the_unstable_misdirect_boss_to_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableMisdirectBossToTankAction(botAI); }
static Action* hydross_the_unstable_stop_dps_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableStopDpsUponPhaseChangeAction(botAI); }
static Action* hydross_the_unstable_manage_timers(
PlayerbotAI* botAI) { return new HydrossTheUnstableManageTimersAction(botAI); }
// The Lurker Below
static Action* the_lurker_below_run_around_behind_boss(
PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); }
static Action* the_lurker_below_position_main_tank(
PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); }
static Action* the_lurker_below_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedInArcAction(botAI); }
static Action* the_lurker_below_tanks_pick_up_adds(
PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); }
static Action* the_lurker_below_manage_spout_timer(
PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); }
// Leotheras the Blind
static Action* leotheras_the_blind_target_spellbinders(
PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); }
static Action* leotheras_the_blind_demon_form_tank_attack_boss(
PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); }
static Action* leotheras_the_blind_melee_tanks_dont_attack_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeTanksDontAttackDemonFormAction(botAI); }
static Action* leotheras_the_blind_position_ranged(
PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); }
static Action* leotheras_the_blind_run_away_from_whirlwind(
PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); }
static Action* leotheras_the_blind_melee_dps_run_away_from_boss(
PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeDpsRunAwayFromBossAction(botAI); }
static Action* leotheras_the_blind_destroy_inner_demon(
PlayerbotAI* botAI) { return new LeotherasTheBlindDestroyInnerDemonAction(botAI); }
static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank(
PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); }
static Action* leotheras_the_blind_final_phase_assign_dps_priority(
PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); }
static Action* leotheras_the_blind_manage_dps_wait_timers(
PlayerbotAI* botAI) { return new LeotherasTheBlindManageDpsWaitTimersAction(botAI); }
// Fathom-Lord Karathress
static Action* fathom_lord_karathress_main_tank_position_boss(
PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); }
static Action* fathom_lord_karathress_first_assist_tank_position_caribdis(
PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionCaribdisAction(botAI); }
static Action* fathom_lord_karathress_second_assist_tank_position_sharkkis(
PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionSharkkisAction(botAI); }
static Action* fathom_lord_karathress_third_assist_tank_position_tidalvess(
PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionTidalvessAction(botAI); }
static Action* fathom_lord_karathress_position_caribdis_tank_healer(
PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); }
static Action* fathom_lord_karathress_misdirect_bosses_to_tanks(
PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); }
static Action* fathom_lord_karathress_assign_dps_priority(
PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); }
static Action* fathom_lord_karathress_manage_dps_timer(
PlayerbotAI* botAI) { return new FathomLordKarathressManageDpsTimerAction(botAI); }
// Morogrim Tidewalker
static Action* morogrim_tidewalker_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new MorogrimTidewalkerMisdirectBossToMainTankAction(botAI); }
static Action* morogrim_tidewalker_move_boss_to_tank_position(
PlayerbotAI* botAI) { return new MorogrimTidewalkerMoveBossToTankPositionAction(botAI); }
static Action* morogrim_tidewalker_phase_2_reposition_ranged(
PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); }
// Lady Vashj <Coilfang Matron>
static Action* lady_vashj_main_tank_position_boss(
PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); }
static Action* lady_vashj_phase_1_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new LadyVashjPhase1SpreadRangedInArcAction(botAI); }
static Action* lady_vashj_set_grounding_totem_in_main_tank_group(
PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); }
static Action* lady_vashj_static_charge_move_away_from_group(
PlayerbotAI* botAI) { return new LadyVashjStaticChargeMoveAwayFromGroupAction(botAI); }
static Action* lady_vashj_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); }
static Action* lady_vashj_assign_phase_2_and_phase_3_dps_priority(
PlayerbotAI* botAI) { return new LadyVashjAssignPhase2AndPhase3DpsPriorityAction(botAI); }
static Action* lady_vashj_misdirect_strider_to_first_assist_tank(
PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); }
static Action* lady_vashj_tank_attack_and_move_away_strider(
PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); }
static Action* lady_vashj_teleport_to_tainted_elemental(
PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); }
static Action* lady_vashj_loot_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjLootTaintedCoreAction(botAI); }
static Action* lady_vashj_pass_the_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjPassTheTaintedCoreAction(botAI); }
static Action* lady_vashj_destroy_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); }
static Action* lady_vashj_erase_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); }
static Action* lady_vashj_avoid_toxic_spores(
PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); }
static Action* lady_vashj_use_free_action_abilities(
PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); }
};
#endif

View File

@@ -0,0 +1,325 @@
#ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#include "RaidSSCTriggers.h"
#include "AiObjectContext.h"
class RaidSSCTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidSSCTriggerContext()
{
// General
creators["serpent shrine cavern bot is not in combat"] =
&RaidSSCTriggerContext::serpent_shrine_cavern_bot_is_not_in_combat;
// Trash
creators["underbog colossus spawned toxic pool after death"] =
&RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death;
creators["greyheart tidecaller water elemental totem spawned"] =
&RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned;
// Hydross the Unstable <Duke of Currents>
creators["hydross the unstable bot is frost tank"] =
&RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank;
creators["hydross the unstable bot is nature tank"] =
&RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank;
creators["hydross the unstable elementals spawned"] =
&RaidSSCTriggerContext::hydross_the_unstable_elementals_spawned;
creators["hydross the unstable danger from water tombs"] =
&RaidSSCTriggerContext::hydross_the_unstable_danger_from_water_tombs;
creators["hydross the unstable tank needs aggro upon phase change"] =
&RaidSSCTriggerContext::hydross_the_unstable_tank_needs_aggro_upon_phase_change;
creators["hydross the unstable aggro resets upon phase change"] =
&RaidSSCTriggerContext::hydross_the_unstable_aggro_resets_upon_phase_change;
creators["hydross the unstable need to manage timers"] =
&RaidSSCTriggerContext::hydross_the_unstable_need_to_manage_timers;
// The Lurker Below
creators["the lurker below spout is active"] =
&RaidSSCTriggerContext::the_lurker_below_spout_is_active;
creators["the lurker below boss is active for main tank"] =
&RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank;
creators["the lurker below boss casts geyser"] =
&RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser;
creators["the lurker below boss is submerged"] =
&RaidSSCTriggerContext::the_lurker_below_boss_is_submerged;
creators["the lurker below need to prepare timer for spout"] =
&RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout;
// Leotheras the Blind
creators["leotheras the blind boss is inactive"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive;
creators["leotheras the blind boss transformed into demon form"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form;
creators["leotheras the blind only warlock should tank demon form"] =
&RaidSSCTriggerContext::leotheras_the_blind_only_warlock_should_tank_demon_form;
creators["leotheras the blind boss engaged by ranged"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged;
creators["leotheras the blind boss channeling whirlwind"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind;
creators["leotheras the blind bot has too many chaos blast stacks"] =
&RaidSSCTriggerContext::leotheras_the_blind_bot_has_too_many_chaos_blast_stacks;
creators["leotheras the blind inner demon has awakened"] =
&RaidSSCTriggerContext::leotheras_the_blind_inner_demon_has_awakened;
creators["leotheras the blind entered final phase"] =
&RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase;
creators["leotheras the blind demon form tank needs aggro"] =
&RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro;
creators["leotheras the blind boss wipes aggro upon phase change"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_wipes_aggro_upon_phase_change;
// Fathom-Lord Karathress
creators["fathom-lord karathress boss engaged by main tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank;
creators["fathom-lord karathress caribdis engaged by first assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_first_assist_tank;
creators["fathom-lord karathress sharkkis engaged by second assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank;
creators["fathom-lord karathress tidalvess engaged by third assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank;
creators["fathom-lord karathress caribdis tank needs dedicated healer"] =
&RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer;
creators["fathom-lord karathress pulling bosses"] =
&RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses;
creators["fathom-lord karathress determining kill order"] =
&RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order;
creators["fathom-lord karathress tanks need to establish aggro"] =
&RaidSSCTriggerContext::fathom_lord_karathress_tanks_need_to_establish_aggro;
// Morogrim Tidewalker
creators["morogrim tidewalker boss engaged by main tank"] =
&RaidSSCTriggerContext::morogrim_tidewalker_boss_engaged_by_main_tank;
creators["morogrim tidewalker pulling boss"] =
&RaidSSCTriggerContext::morogrim_tidewalker_pulling_boss;
creators["morogrim tidewalker water globules are incoming"] =
&RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming;
// Lady Vashj <Coilfang Matron>
creators["lady vashj boss engaged by main tank"] =
&RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank;
creators["lady vashj boss engaged by ranged in phase 1"] =
&RaidSSCTriggerContext::lady_vashj_boss_engaged_by_ranged_in_phase_1;
creators["lady vashj casts shock blast on highest aggro"] =
&RaidSSCTriggerContext::lady_vashj_casts_shock_blast_on_highest_aggro;
creators["lady vashj bot has static charge"] =
&RaidSSCTriggerContext::lady_vashj_bot_has_static_charge;
creators["lady vashj pulling boss in phase 1 and phase 3"] =
&RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3;
creators["lady vashj adds spawn in phase 2 and phase 3"] =
&RaidSSCTriggerContext::lady_vashj_adds_spawn_in_phase_2_and_phase_3;
creators["lady vashj coilfang strider is approaching"] =
&RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching;
creators["lady vashj tainted elemental cheat"] =
&RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat;
creators["lady vashj tainted core was looted"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted;
creators["lady vashj tainted core is unusable"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable;
creators["lady vashj need to reset core passing trackers"] =
&RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers;
creators["lady vashj toxic sporebats are spewing poison clouds"] =
&RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds;
creators["lady vashj bot is entangled in toxic spores or static charge"] =
&RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge;
}
private:
// General
static Trigger* serpent_shrine_cavern_bot_is_not_in_combat(
PlayerbotAI* botAI) { return new SerpentShrineCavernBotIsNotInCombatTrigger(botAI); }
// Trash
static Trigger* underbog_colossus_spawned_toxic_pool_after_death(
PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); }
static Trigger* greyheart_tidecaller_water_elemental_totem_spawned(
PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); }
// Hydross the Unstable <Duke of Currents>
static Trigger* hydross_the_unstable_bot_is_frost_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); }
static Trigger* hydross_the_unstable_bot_is_nature_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); }
static Trigger* hydross_the_unstable_elementals_spawned(
PlayerbotAI* botAI) { return new HydrossTheUnstableElementalsSpawnedTrigger(botAI); }
static Trigger* hydross_the_unstable_danger_from_water_tombs(
PlayerbotAI* botAI) { return new HydrossTheUnstableDangerFromWaterTombsTrigger(botAI); }
static Trigger* hydross_the_unstable_tank_needs_aggro_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(botAI); }
static Trigger* hydross_the_unstable_aggro_resets_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(botAI); }
static Trigger* hydross_the_unstable_need_to_manage_timers(
PlayerbotAI* botAI) { return new HydrossTheUnstableNeedToManageTimersTrigger(botAI); }
// The Lurker Below
static Trigger* the_lurker_below_spout_is_active(
PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); }
static Trigger* the_lurker_below_boss_is_active_for_main_tank(
PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); }
static Trigger* the_lurker_below_boss_casts_geyser(
PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); }
static Trigger* the_lurker_below_boss_is_submerged(
PlayerbotAI* botAI) { return new TheLurkerBelowBossIsSubmergedTrigger(botAI); }
static Trigger* the_lurker_below_need_to_prepare_timer_for_spout(
PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); }
// Leotheras the Blind
static Trigger* leotheras_the_blind_boss_is_inactive(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_transformed_into_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); }
static Trigger* leotheras_the_blind_only_warlock_should_tank_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_engaged_by_ranged(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_channeling_whirlwind(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); }
static Trigger* leotheras_the_blind_bot_has_too_many_chaos_blast_stacks(
PlayerbotAI* botAI) { return new LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(botAI); }
static Trigger* leotheras_the_blind_inner_demon_has_awakened(
PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonHasAwakenedTrigger(botAI); }
static Trigger* leotheras_the_blind_entered_final_phase(
PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); }
static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro(
PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); }
static Trigger* leotheras_the_blind_boss_wipes_aggro_upon_phase_change(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger(botAI); }
// Fathom-Lord Karathress
static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_caribdis_engaged_by_first_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer(
PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); }
static Trigger* fathom_lord_karathress_pulling_bosses(
PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); }
static Trigger* fathom_lord_karathress_determining_kill_order(
PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); }
static Trigger* fathom_lord_karathress_tanks_need_to_establish_aggro(
PlayerbotAI* botAI) { return new FathomLordKarathressTanksNeedToEstablishAggroTrigger(botAI); }
// Morogrim Tidewalker
static Trigger* morogrim_tidewalker_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new MorogrimTidewalkerBossEngagedByMainTankTrigger(botAI); }
static Trigger* morogrim_tidewalker_pulling_boss(
PlayerbotAI* botAI) { return new MorogrimTidewalkerPullingBossTrigger(botAI); }
static Trigger* morogrim_tidewalker_water_globules_are_incoming(
PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); }
// Lady Vashj <Coilfang Matron>
static Trigger* lady_vashj_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); }
static Trigger* lady_vashj_boss_engaged_by_ranged_in_phase_1(
PlayerbotAI* botAI) { return new LadyVashjBossEngagedByRangedInPhase1Trigger(botAI); }
static Trigger* lady_vashj_casts_shock_blast_on_highest_aggro(
PlayerbotAI* botAI) { return new LadyVashjCastsShockBlastOnHighestAggroTrigger(botAI); }
static Trigger* lady_vashj_bot_has_static_charge(
PlayerbotAI* botAI) { return new LadyVashjBotHasStaticChargeTrigger(botAI); }
static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3(
PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); }
static Trigger* lady_vashj_adds_spawn_in_phase_2_and_phase_3(
PlayerbotAI* botAI) { return new LadyVashjAddsSpawnInPhase2AndPhase3Trigger(botAI); }
static Trigger* lady_vashj_coilfang_strider_is_approaching(
PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); }
static Trigger* lady_vashj_tainted_elemental_cheat(
PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); }
static Trigger* lady_vashj_tainted_core_was_looted(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); }
static Trigger* lady_vashj_tainted_core_is_unusable(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); }
static Trigger* lady_vashj_need_to_reset_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); }
static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(
PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); }
static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge(
PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,206 @@
#include "RaidSSCStrategy.h"
#include "RaidSSCMultipliers.h"
void RaidSSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// General
triggers.push_back(new TriggerNode("serpent shrine cavern bot is not in combat", {
NextAction("serpent shrine cavern erase timers and trackers", ACTION_EMERGENCY + 11) }));
// Trash Mobs
triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", {
NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", {
NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1) }));
// Hydross the Unstable <Duke of Currents>
triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", {
NextAction("hydross the unstable position frost tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable bot is nature tank", {
NextAction("hydross the unstable position nature tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable elementals spawned", {
NextAction("hydross the unstable prioritize elemental adds", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable danger from water tombs", {
NextAction("hydross the unstable frost phase spread out", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable tank needs aggro upon phase change", {
NextAction("hydross the unstable misdirect boss to tank", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("hydross the unstable aggro resets upon phase change", {
NextAction("hydross the unstable stop dps upon phase change", ACTION_EMERGENCY + 9) }));
triggers.push_back(new TriggerNode("hydross the unstable need to manage timers", {
NextAction("hydross the unstable manage timers", ACTION_EMERGENCY + 10) }));
// The Lurker Below
triggers.push_back(new TriggerNode("the lurker below spout is active", {
NextAction("the lurker below run around behind boss", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("the lurker below boss is active for main tank", {
NextAction("the lurker below position main tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("the lurker below boss casts geyser", {
NextAction("the lurker below spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("the lurker below boss is submerged", {
NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", {
NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10) }));
// Leotheras the Blind
triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", {
NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", {
NextAction("leotheras the blind demon form tank attack boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind only warlock should tank demon form", {
NextAction("leotheras the blind melee tanks don't attack demon form", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", {
NextAction("leotheras the blind position ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", {
NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", {
NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("leotheras the blind inner demon has awakened", {
NextAction("leotheras the blind destroy inner demon", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("leotheras the blind entered final phase", {
NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", {
NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", {
NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10) }));
// Fathom-Lord Karathress
triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", {
NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by first assist tank", {
NextAction("fathom-lord karathress first assist tank position caribdis", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by second assist tank", {
NextAction("fathom-lord karathress second assist tank position sharkkis", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by third assist tank", {
NextAction("fathom-lord karathress third assist tank position tidalvess", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", {
NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress pulling bosses", {
NextAction("fathom-lord karathress misdirect bosses to tanks", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("fathom-lord karathress determining kill order", {
NextAction("fathom-lord karathress assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress tanks need to establish aggro", {
NextAction("fathom-lord karathress manage dps timer", ACTION_EMERGENCY + 10) }));
// Morogrim Tidewalker
triggers.push_back(new TriggerNode("morogrim tidewalker boss engaged by main tank", {
NextAction("morogrim tidewalker move boss to tank position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("morogrim tidewalker water globules are incoming", {
NextAction("morogrim tidewalker phase 2 reposition ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", {
NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1) }));
// Lady Vashj <Coilfang Matron>
triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", {
NextAction("lady vashj main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", {
NextAction("lady vashj phase 1 spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", {
NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj bot has static charge", {
NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", {
NextAction("lady vashj misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", {
NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10),
NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj tainted core was looted", {
NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", {
NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", {
NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", {
NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj coilfang strider is approaching", {
NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 2),
NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", {
NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", {
NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7) }));
}
void RaidSSCStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Trash Mobs
multipliers.push_back(new UnderbogColossusEscapeToxicPoolMultiplier(botAI));
// Hydross the Unstable <Duke of Currents>
multipliers.push_back(new HydrossTheUnstableDisableTankActionsMultiplier(botAI));
multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI));
multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI));
// The Lurker Below
multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI));
multipliers.push_back(new TheLurkerBelowMaintainRangedSpreadMultiplier(botAI));
multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI));
// Leotheras the Blind
multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindFocusOnInnerDemonMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI));
// Fathom-Lord Karathress
multipliers.push_back(new FathomLordKarathressDisableTankActionsMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressDisableAoeMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressControlMisdirectionMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI));
// Morogrim Tidewalker
multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new MorogrimTidewalkerDisableTankActionsMultiplier(botAI));
multipliers.push_back(new MorogrimTidewalkerMaintainPhase2StackingMultiplier(botAI));
// Lady Vashj <Coilfang Matron>
multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI));
multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI));
multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI));
multipliers.push_back(new LadyVashjCorePassersPrioritizePositioningMultiplier(botAI));
multipliers.push_back(new LadyVashjDisableAutomaticTargetingAndMovementModifier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidSSCStrategy : public Strategy
{
public:
RaidSSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "ssc"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,670 @@
#include "RaidSSCTriggers.h"
#include "RaidSSCHelpers.h"
#include "RaidSSCActions.h"
#include "AiFactory.h"
#include "Corpse.h"
#include "LootObjectStack.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace SerpentShrineCavernHelpers;
// General
bool SerpentShrineCavernBotIsNotInCombatTrigger::IsActive()
{
return !bot->IsInCombat();
}
// Trash Mobs
bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
{
return bot->HasAura(SPELL_TOXIC_POOL);
}
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{
return botAI->IsDps(bot) &&
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
}
// Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsMainTank(bot);
}
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
{
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (hydross && hydross->GetHealthPct() < 10.0f)
return false;
if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
return false;
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
{
return botAI->IsRanged(bot) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
{
return bot->getClass() == CLASS_HUNTER &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "hydross the unstable"))
return false;
return bot->getClass() != CLASS_HUNTER &&
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// The Lurker Below
bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return it != lurkerSpoutTimer.end() && it->second > now;
}
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
if (!botAI->IsMainTank(bot))
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
(it == lurkerSpoutTimer.end() || it->second <= now);
}
bool TheLurkerBelowBossCastsGeyserTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
(it == lurkerSpoutTimer.end() || it->second <= now);
}
// Trigger will be active only if there are at least 3 tanks in the raid
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return false;
Player* mainTank = nullptr;
Player* firstAssistTank = nullptr;
Player* secondAssistTank = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (!mainTank && memberAI->IsMainTank(member))
mainTank = member;
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
firstAssistTank = member;
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
secondAssistTank = member;
}
if (!mainTank || !firstAssistTank || !secondAssistTank)
return false;
return bot == mainTank || bot == firstAssistTank || bot == secondAssistTank;
}
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "the lurker below") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Leotheras the Blind
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
}
bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (GetLeotherasDemonFormTank(bot) != bot)
return false;
return GetActiveLeotherasDemon(botAI);
}
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{
if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!GetLeotherasDemonFormTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
}
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!botAI->IsRanged(bot))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras)
return false;
return !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotheras->HasAura(SPELL_WHIRLWIND) &&
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
}
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
return false;
return leotheras->HasAura(SPELL_WHIRLWIND) ||
leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
}
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsRanged(bot))
return false;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (!chaosBlast || chaosBlast->GetStackAmount() < 5)
return false;
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
}
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
{
return bot->HasAura(SPELL_INSIDIOUS_WHISPER) &&
GetLeotherasDemonFormTank(bot) != bot;
}
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsHeal(bot))
return false;
if (GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(botAI) &&
GetLeotherasHuman(botAI);
}
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() != CLASS_HUNTER)
return false;
return AI_VALUE2(Unit*, "find target", "leotheras the blind");
}
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
botAI->IsMainTank(bot);
}
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
botAI->IsAssistTankOfIndex(bot, 0, false);
}
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
botAI->IsAssistTankOfIndex(bot, 1, false);
}
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
botAI->IsAssistTankOfIndex(bot, 2, false);
}
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
if (!caribdis)
return false;
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return false;
Player* firstAssistTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTankOfIndex(member, 0, false))
{
firstAssistTank = member;
break;
}
}
}
return firstAssistTank;
}
bool FathomLordKarathressPullingBossesTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
return karathress && karathress->GetHealthPct() > 98.0f;
}
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false;
if (botAI->IsHeal(bot))
return false;
if (botAI->IsDps(bot))
return true;
else if (botAI->IsAssistTankOfIndex(bot, 0, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
else if (botAI->IsAssistTankOfIndex(bot, 1, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
else if (botAI->IsAssistTankOfIndex(bot, 2, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
else
return false;
}
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Morogrim Tidewalker
bool MorogrimTidewalkerPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
return tidewalker && tidewalker->GetHealthPct() > 95.0f;
}
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
botAI->IsMainTank(bot);
}
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
return tidewalker && tidewalker->GetHealthPct() < 25.0f;
}
// Lady Vashj <Coilfang Matron>
bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
}
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
{
return botAI->IsRanged(bot) && IsLadyVashjInPhase1(botAI);
}
bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
{
if (bot->getClass() != CLASS_SHAMAN)
return false;
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
IsLadyVashjInPhase2(botAI))
return false;
if (!IsMainTankInSameSubgroup(bot))
return false;
return true;
}
bool LadyVashjBotHasStaticChargeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
}
return false;
}
bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj)
return false;
return (vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) ||
(!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f &&
vashj->GetHealthPct() > 40.0f);
}
bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase1(botAI);
}
bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "coilfang strider");
}
bool LadyVashjTaintedElementalCheatTrigger::IsActive()
{
if (!botAI->HasCheat(BotCheatMask::raid))
return false;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
bool taintedPresent = false;
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (taintedUnit)
taintedPresent = true;
else
{
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto const& guid : corpses)
{
LootObject loot(bot, guid);
WorldObject* object = loot.GetWorldObject(bot);
if (!object)
continue;
if (Creature* creature = object->ToCreature())
{
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{
taintedPresent = true;
break;
}
}
}
}
if (!taintedPresent)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
}
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player) -> bool
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (bot == designatedLooter)
{
if (!hasCore(bot))
return false;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return false;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return false;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return false;
}
else if (bot != fourthCorePasser)
return false;
if (AnyRecentCoreInInventory(group, botAI))
return true;
// First and second passers move to positions as soon as the elemental appears
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
return true;
return false;
}
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj)
return false;
if (!IsLadyVashjInPhase2(botAI))
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
Group* group = bot->GetGroup();
if (!group)
return false;
Player* coreHandlers[] =
{
GetDesignatedCoreLooter(group, botAI),
GetFirstTaintedCorePasser(group, botAI),
GetSecondTaintedCorePasser(group, botAI),
GetThirdTaintedCorePasser(group, botAI),
GetFourthTaintedCorePasser(group, botAI)
};
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
{
for (Player* coreHandler : coreHandlers)
{
if (coreHandler && bot == coreHandler)
return false;
}
return true;
}
return false;
}
bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj || IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) ||
GetDesignatedCoreLooter(group, botAI) == bot ||
GetFirstTaintedCorePasser(group, botAI) == bot ||
GetSecondTaintedCorePasser(group, botAI) == bot ||
GetThirdTaintedCorePasser(group, botAI) == bot ||
GetFourthTaintedCorePasser(group, botAI) == bot;
}
bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive()
{
return IsLadyVashjInPhase3(botAI);
}
bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
if (botAI->IsMelee(member))
return true;
}
}
return false;
}

View File

@@ -0,0 +1,414 @@
#ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H
#define _PLAYERBOT_RAIDSSCTRIGGERS_H
#include "Trigger.h"
// General
class SerpentShrineCavernBotIsNotInCombatTrigger : public Trigger
{
public:
SerpentShrineCavernBotIsNotInCombatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "serpent shrine cavern bot is not in combat") {}
bool IsActive() override;
};
// Trash
class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger
{
public:
UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "underbog colossus spawned toxic pool after death") {}
bool IsActive() override;
};
class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger
{
public:
GreyheartTidecallerWaterElementalTotemSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "greyheart tidecaller water elemental totem spawned") {}
bool IsActive() override;
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger
{
public:
HydrossTheUnstableBotIsFrostTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is frost tank") {}
bool IsActive() override;
};
class HydrossTheUnstableBotIsNatureTankTrigger : public Trigger
{
public:
HydrossTheUnstableBotIsNatureTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is nature tank") {}
bool IsActive() override;
};
class HydrossTheUnstableElementalsSpawnedTrigger : public Trigger
{
public:
HydrossTheUnstableElementalsSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable elementals spawned") {}
bool IsActive() override;
};
class HydrossTheUnstableDangerFromWaterTombsTrigger : public Trigger
{
public:
HydrossTheUnstableDangerFromWaterTombsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable danger from water tombs") {}
bool IsActive() override;
};
class HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger : public Trigger
{
public:
HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable tank needs aggro upon phase change") {}
bool IsActive() override;
};
class HydrossTheUnstableAggroResetsUponPhaseChangeTrigger : public Trigger
{
public:
HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable aggro resets upon phase change") {}
bool IsActive() override;
};
class HydrossTheUnstableNeedToManageTimersTrigger : public Trigger
{
public:
HydrossTheUnstableNeedToManageTimersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable need to manage timers") {}
bool IsActive() override;
};
// The Lurker Below
class TheLurkerBelowSpoutIsActiveTrigger : public Trigger
{
public:
TheLurkerBelowSpoutIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below spout is active") {}
bool IsActive() override;
};
class TheLurkerBelowBossIsActiveForMainTankTrigger : public Trigger
{
public:
TheLurkerBelowBossIsActiveForMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is active for main tank") {}
bool IsActive() override;
};
class TheLurkerBelowBossCastsGeyserTrigger : public Trigger
{
public:
TheLurkerBelowBossCastsGeyserTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss casts geyser") {}
bool IsActive() override;
};
class TheLurkerBelowBossIsSubmergedTrigger : public Trigger
{
public:
TheLurkerBelowBossIsSubmergedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is submerged") {}
bool IsActive() override;
};
class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger
{
public:
TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below need to prepare timer for spout") {}
bool IsActive() override;
};
// Leotheras the Blind
class LeotherasTheBlindBossIsInactiveTrigger : public Trigger
{
public:
LeotherasTheBlindBossIsInactiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss is inactive") {}
bool IsActive() override;
};
class LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger : public Trigger
{
public:
LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind only warlock should tank demon form") {}
bool IsActive() override;
};
class LeotherasTheBlindBossTransformedIntoDemonFormTrigger : public Trigger
{
public:
LeotherasTheBlindBossTransformedIntoDemonFormTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss transformed into demon form") {}
bool IsActive() override;
};
class LeotherasTheBlindBossEngagedByRangedTrigger : public Trigger
{
public:
LeotherasTheBlindBossEngagedByRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss engaged by ranged") {}
bool IsActive() override;
};
class LeotherasTheBlindBossChannelingWhirlwindTrigger : public Trigger
{
public:
LeotherasTheBlindBossChannelingWhirlwindTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss channeling whirlwind") {}
bool IsActive() override;
};
class LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger : public Trigger
{
public:
LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind bot has too many chaos blast stacks") {}
bool IsActive() override;
};
class LeotherasTheBlindInnerDemonHasAwakenedTrigger : public Trigger
{
public:
LeotherasTheBlindInnerDemonHasAwakenedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon has awakened") {}
bool IsActive() override;
};
class LeotherasTheBlindEnteredFinalPhaseTrigger : public Trigger
{
public:
LeotherasTheBlindEnteredFinalPhaseTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind entered final phase") {}
bool IsActive() override;
};
class LeotherasTheBlindDemonFormTankNeedsAggro : public Trigger
{
public:
LeotherasTheBlindDemonFormTankNeedsAggro(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form tank needs aggro") {}
bool IsActive() override;
};
class LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger : public Trigger
{
public:
LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss wipes aggro upon phase change") {}
bool IsActive() override;
};
// Fathom-Lord Karathress
class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger
{
public:
FathomLordKarathressBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress boss engaged by main tank") {}
bool IsActive() override;
};
class FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by first assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by second assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by third assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger : public Trigger
{
public:
FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis tank needs dedicated healer") {}
bool IsActive() override;
};
class FathomLordKarathressPullingBossesTrigger : public Trigger
{
public:
FathomLordKarathressPullingBossesTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress pulling bosses") {}
bool IsActive() override;
};
class FathomLordKarathressDeterminingKillOrderTrigger : public Trigger
{
public:
FathomLordKarathressDeterminingKillOrderTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress determining kill order") {}
bool IsActive() override;
};
class FathomLordKarathressTanksNeedToEstablishAggroTrigger : public Trigger
{
public:
FathomLordKarathressTanksNeedToEstablishAggroTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tanks need to establish aggro") {}
bool IsActive() override;
};
// Morogrim Tidewalker
class MorogrimTidewalkerPullingBossTrigger : public Trigger
{
public:
MorogrimTidewalkerPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker pulling boss") {}
bool IsActive() override;
};
class MorogrimTidewalkerBossEngagedByMainTankTrigger : public Trigger
{
public:
MorogrimTidewalkerBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker boss engaged by main tank") {}
bool IsActive() override;
};
class MorogrimTidewalkerWaterGlobulesAreIncomingTrigger : public Trigger
{
public:
MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker water globules are incoming") {}
bool IsActive() override;
};
// Lady Vashj <Coilfang Matron>
class LadyVashjBossEngagedByMainTankTrigger : public Trigger
{
public:
LadyVashjBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by main tank") {}
bool IsActive() override;
};
class LadyVashjBossEngagedByRangedInPhase1Trigger : public Trigger
{
public:
LadyVashjBossEngagedByRangedInPhase1Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by ranged in phase 1") {}
bool IsActive() override;
};
class LadyVashjCastsShockBlastOnHighestAggroTrigger : public Trigger
{
public:
LadyVashjCastsShockBlastOnHighestAggroTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj casts shock blast on highest aggro") {}
bool IsActive() override;
};
class LadyVashjBotHasStaticChargeTrigger : public Trigger
{
public:
LadyVashjBotHasStaticChargeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot has static charge") {}
bool IsActive() override;
};
class LadyVashjPullingBossInPhase1AndPhase3Trigger : public Trigger
{
public:
LadyVashjPullingBossInPhase1AndPhase3Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj pulling boss in phase 1 and phase 3") {}
bool IsActive() override;
};
class LadyVashjAddsSpawnInPhase2AndPhase3Trigger : public Trigger
{
public:
LadyVashjAddsSpawnInPhase2AndPhase3Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj adds spawn in phase 2 and phase 3") {}
bool IsActive() override;
};
class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger
{
public:
LadyVashjCoilfangStriderIsApproachingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {}
bool IsActive() override;
};
class LadyVashjTaintedElementalCheatTrigger : public Trigger
{
public:
LadyVashjTaintedElementalCheatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted elemental cheat") {}
bool IsActive() override;
};
class LadyVashjTaintedCoreWasLootedTrigger : public Trigger
{
public:
LadyVashjTaintedCoreWasLootedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core was looted") {}
bool IsActive() override;
};
class LadyVashjTaintedCoreIsUnusableTrigger : public Trigger
{
public:
LadyVashjTaintedCoreIsUnusableTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core is unusable") {}
bool IsActive() override;
};
class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger
{
public:
LadyVashjNeedToResetCorePassingTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {}
bool IsActive() override;
};
class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger
{
public:
LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj toxic sporebats are spewing poison clouds") {}
bool IsActive() override;
};
class LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger : public Trigger
{
public:
LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot is entangled in toxic spores or static charge") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,583 @@
#include "RaidSSCHelpers.h"
#include "AiFactory.h"
#include "Creature.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
namespace SerpentShrineCavernHelpers
{
// Hydross the Unstable <Duke of Currents>
const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f };
const Position HYDROSS_NATURE_TANK_POSITION = { -225.471f, -327.790f, -3.682f };
std::unordered_map<uint32, time_t> hydrossFrostDpsWaitTimer;
std::unordered_map<uint32, time_t> hydrossNatureDpsWaitTimer;
std::unordered_map<uint32, time_t> hydrossChangeToFrostPhaseTimer;
std::unordered_map<uint32, time_t> hydrossChangeToNaturePhaseTimer;
bool HasMarkOfHydrossAt100Percent(Player* bot)
{
return bot->HasAura(SPELL_MARK_OF_HYDROSS_100) ||
bot->HasAura(SPELL_MARK_OF_HYDROSS_250) ||
bot->HasAura(SPELL_MARK_OF_HYDROSS_500);
}
bool HasNoMarkOfHydross(Player* bot)
{
return !bot->HasAura(SPELL_MARK_OF_HYDROSS_10) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_25) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_50) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_100) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_250) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_500);
}
bool HasMarkOfCorruptionAt100Percent(Player* bot)
{
return bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) ||
bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) ||
bot->HasAura(SPELL_MARK_OF_CORRUPTION_500);
}
bool HasNoMarkOfCorruption(Player* bot)
{
return !bot->HasAura(SPELL_MARK_OF_CORRUPTION_10) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_25) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_50) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_500);
}
// The Lurker Below
const Position LURKER_MAIN_TANK_POSITION = { 23.706f, -406.038f, -19.686f };
std::unordered_map<uint32, time_t> lurkerSpoutTimer;
std::unordered_map<ObjectGuid, Position> lurkerRangedPositions;
bool IsLurkerCastingSpout(Unit* lurker)
{
if (!lurker || !lurker->HasUnitState(UNIT_STATE_CASTING))
return false;
Spell* currentSpell = lurker->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
return false;
uint32 spellId = currentSpell->m_spellInfo->Id;
bool isSpout = spellId == SPELL_SPOUT_VISUAL;
return isSpout;
}
// Leotheras the Blind
std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr;
}
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr;
}
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
}
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI)
{
Unit* phase2 = GetPhase2LeotherasDemon(botAI);
Unit* phase3 = GetPhase3LeotherasDemon(botAI);
return phase2 ? phase2 : phase3;
}
Player* GetLeotherasDemonFormTank(Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
// (1) First loop: Return the first assistant Warlock (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK)
continue;
if (group->IsAssistant(member->GetGUID()))
return member;
}
// (2) Fall back to first found bot Warlock
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_WARLOCK)
continue;
return member;
}
// (3) Return nullptr if none found
return nullptr;
}
// Fathom-Lord Karathress
const Position KARATHRESS_TANK_POSITION = { 474.403f, -531.118f, -7.548f };
const Position TIDALVESS_TANK_POSITION = { 511.282f, -501.162f, -13.158f };
const Position SHARKKIS_TANK_POSITION = { 508.057f, -541.109f, -10.133f };
const Position CARIBDIS_TANK_POSITION = { 464.462f, -475.820f, -13.158f };
const Position CARIBDIS_HEALER_POSITION = { 466.203f, -503.201f, -13.158f };
const Position CARIBDIS_RANGED_DPS_POSITION = { 463.197f, -501.190f, -13.158f };
std::unordered_map<uint32, time_t> karathressDpsWaitTimer;
// Morogrim Tidewalker
const Position TIDEWALKER_PHASE_1_TANK_POSITION = { 410.925f, -741.916f, -7.146f };
const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT = { 407.035f, -759.479f, -7.168f };
const Position TIDEWALKER_PHASE_2_TANK_POSITION = { 446.571f, -767.155f, -7.144f };
const Position TIDEWALKER_PHASE_2_RANGED_POSITION = { 432.595f, -766.288f, -7.145f };
std::unordered_map<ObjectGuid, uint8> tidewalkerTankStep;
std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron>
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f };
std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
std::unordered_map<ObjectGuid, Position> intendedLineup;
std::unordered_map<uint32, time_t> lastImbueAttempt;
std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot)
{
Group* group = bot->GetGroup();
if (!group || !group->isRaidGroup())
return false;
uint8 botSubGroup = group->GetMemberGroup(bot->GetGUID());
if (botSubGroup >= MAX_RAID_SUBGROUPS)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive())
continue;
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue;
if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member))
{
if (memberAI->IsMainTank(member))
return true;
}
}
return false;
}
bool IsLadyVashjInPhase1(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetHealthPct() > 70.0f &&
vashjCreature->GetReactState() != REACT_PASSIVE;
}
bool IsLadyVashjInPhase2(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetReactState() == REACT_PASSIVE;
}
bool IsLadyVashjInPhase3(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f &&
vashjCreature->GetReactState() != REACT_PASSIVE;
}
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI)
{
if (!unit || !unit->IsAlive())
return false;
uint32 entry = unit->GetEntry();
if (IsLadyVashjInPhase2(botAI))
{
return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL ||
entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER;
}
else if (IsLadyVashjInPhase3(botAI))
{
return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL ||
entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER ||
entry == NPC_TOXIC_SPOREBAT || entry == NPC_LADY_VASHJ;
}
return false;
}
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
}
}
const uint32 instanceId = vashj->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
auto it = lastCoreInInventoryTime.find(instanceId);
if (it != lastCoreInInventoryTime.end())
{
if ((now - it->second) <= static_cast<time_t>(graceSeconds))
return true;
}
return false;
}
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* leader = nullptr;
ObjectGuid leaderGuid = group->GetLeaderGUID();
if (!leaderGuid.IsEmpty())
leader = ObjectAccessor::FindPlayer(leaderGuid);
if (!botAI->HasCheat(BotCheatMask::raid))
return leader;
Player* fallback = leader;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == leader)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsMelee(member) && memberAI->IsDps(member))
return member;
if (!fallback && memberAI->IsRangedDps(member))
fallback = member;
}
return fallback ? fallback : leader;
}
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter)
continue;
return member;
}
return nullptr;
}
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 1, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser)
continue;
return member;
}
return nullptr;
}
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 2, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
return member;
}
return nullptr;
}
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
return member;
}
return nullptr;
}
const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS =
{
47482, // NW
47483, // NE
47484, // SE
47485 // SW
};
// Get the positions of all active Shield Generators by their database GUIDs
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(
Map* map, const std::vector<uint32>& generatorDbGuids)
{
std::vector<GeneratorInfo> generators;
if (!map)
return generators;
for (uint32 dbGuid : generatorDbGuids)
{
auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(dbGuid);
if (bounds.first == bounds.second)
continue;
GameObject* go = bounds.first->second;
if (!go)
continue;
if (go->GetGoState() != GO_STATE_READY)
continue;
GeneratorInfo info;
info.guid = go->GetGUID();
info.x = go->GetPositionX();
info.y = go->GetPositionY();
info.z = go->GetPositionZ();
generators.push_back(info);
}
return generators;
}
// Returns the nearest active Shield Generator to the bot
// Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures,
// which depawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference)
{
if (!reference)
return nullptr;
std::list<Creature*> triggers;
constexpr float searchRange = 150.0f;
reference->GetCreatureListWithEntryInGrid(
triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange);
Creature* nearest = nullptr;
float minDist = std::numeric_limits<float>::max();
for (Creature* creature : triggers)
{
if (!creature->IsAlive())
continue;
float dist = reference->GetDistance(creature);
if (dist < minDist)
{
minDist = dist;
nearest = creature;
}
}
return nearest;
}
const GeneratorInfo* GetNearestGeneratorToBot(
Player* bot, const std::vector<GeneratorInfo>& generators)
{
if (!bot || generators.empty())
return nullptr;
const GeneratorInfo* nearest = nullptr;
float minDist = std::numeric_limits<float>::max();
for (auto const& gen : generators)
{
float dist = bot->GetExactDist(gen.x, gen.y, gen.z);
if (dist < minDist)
{
minDist = dist;
nearest = &gen;
}
}
return nearest;
}
}

View File

@@ -0,0 +1,189 @@
#ifndef _PLAYERBOT_RAIDSSCHELPERS_H_
#define _PLAYERBOT_RAIDSSCHELPERS_H_
#include <ctime>
#include <unordered_map>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace SerpentShrineCavernHelpers
{
enum SerpentShrineCavernSpells
{
// Trash Mobs
SPELL_TOXIC_POOL = 38718,
// Hydross the Unstable <Duke of Currents>
SPELL_MARK_OF_HYDROSS_10 = 38215,
SPELL_MARK_OF_HYDROSS_25 = 38216,
SPELL_MARK_OF_HYDROSS_50 = 38217,
SPELL_MARK_OF_HYDROSS_100 = 38218,
SPELL_MARK_OF_HYDROSS_250 = 38231,
SPELL_MARK_OF_HYDROSS_500 = 40584,
SPELL_MARK_OF_CORRUPTION_10 = 38219,
SPELL_MARK_OF_CORRUPTION_25 = 38220,
SPELL_MARK_OF_CORRUPTION_50 = 38221,
SPELL_MARK_OF_CORRUPTION_100 = 38222,
SPELL_MARK_OF_CORRUPTION_250 = 38230,
SPELL_MARK_OF_CORRUPTION_500 = 40583,
SPELL_CORRUPTION = 37961,
// The Lurker Below
SPELL_SPOUT_VISUAL = 37431,
// Leotheras the Blind
SPELL_LEOTHERAS_BANISHED = 37546,
SPELL_WHIRLWIND = 37640,
SPELL_WHIRLWIND_CHANNEL = 37641,
SPELL_METAMORPHOSIS = 37673,
SPELL_CHAOS_BLAST = 37675,
SPELL_INSIDIOUS_WHISPER = 37676,
// Lady Vashj <Coilfang Matron>
SPELL_FEAR_WARD = 6346,
SPELL_POISON_BOLT = 38253,
SPELL_STATIC_CHARGE = 38280,
SPELL_ENTANGLE = 38316,
// Druid
SPELL_CAT_FORM = 768,
SPELL_BEAR_FORM = 5487,
SPELL_DIRE_BEAR_FORM = 9634,
SPELL_TREE_OF_LIFE = 33891,
// Hunter
SPELL_MISDIRECTION = 35079,
// Mage
SPELL_SLOW = 31589,
// Shaman
SPELL_GROUNDING_TOTEM_EFFECT = 8178,
// Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
};
enum SerpentShrineCavernNPCs
{
// Trash Mobs
NPC_WATER_ELEMENTAL_TOTEM = 22236,
// Hydross the Unstable <Duke of Currents>
NPC_PURE_SPAWN_OF_HYDROSS = 22035,
NPC_TAINTED_SPAWN_OF_HYDROSS = 22036,
// The Lurker Below
NPC_COILFANG_GUARDIAN = 21873,
// Leotheras the Blind
NPC_LEOTHERAS_THE_BLIND = 21215,
NPC_GREYHEART_SPELLBINDER = 21806,
NPC_INNER_DEMON = 21857,
NPC_SHADOW_OF_LEOTHERAS = 21875,
// Fathom-Lord Karathress
NPC_SPITFIRE_TOTEM = 22091,
// Lady Vashj <Coilfang Matron>
NPC_WORLD_INVISIBLE_TRIGGER = 12999,
NPC_LADY_VASHJ = 21212,
NPC_ENCHANTED_ELEMENTAL = 21958,
NPC_TAINTED_ELEMENTAL = 22009,
NPC_COILFANG_ELITE = 22055,
NPC_COILFANG_STRIDER = 22056,
NPC_TOXIC_SPOREBAT = 22140,
NPC_SPORE_DROP_TRIGGER = 22207,
};
enum SerpentShrineCavernItems
{
// Lady Vashj <Coilfang Matron>
ITEM_TAINTED_CORE = 31088,
// Tailoring
ITEM_HEAVY_NETHERWEAVE_NET = 24269,
};
constexpr uint32 SSC_MAP_ID = 548;
// Hydross the Unstable <Duke of Currents>
extern const Position HYDROSS_FROST_TANK_POSITION;
extern const Position HYDROSS_NATURE_TANK_POSITION;
extern std::unordered_map<uint32, time_t> hydrossFrostDpsWaitTimer;
extern std::unordered_map<uint32, time_t> hydrossNatureDpsWaitTimer;
extern std::unordered_map<uint32, time_t> hydrossChangeToFrostPhaseTimer;
extern std::unordered_map<uint32, time_t> hydrossChangeToNaturePhaseTimer;
bool HasMarkOfHydrossAt100Percent(Player* bot);
bool HasNoMarkOfHydross(Player* bot);
bool HasMarkOfCorruptionAt100Percent(Player* bot);
bool HasNoMarkOfCorruption(Player* bot);
// The Lurker Below
extern const Position LURKER_MAIN_TANK_POSITION;
extern std::unordered_map<uint32, time_t> lurkerSpoutTimer;
extern std::unordered_map<ObjectGuid, Position> lurkerRangedPositions;
bool IsLurkerCastingSpout(Unit* lurker);
// Leotheras the Blind
extern std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI);
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI);
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI);
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI);
Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress
extern const Position KARATHRESS_TANK_POSITION;
extern const Position TIDALVESS_TANK_POSITION;
extern const Position SHARKKIS_TANK_POSITION;
extern const Position CARIBDIS_TANK_POSITION;
extern const Position CARIBDIS_HEALER_POSITION;
extern const Position CARIBDIS_RANGED_DPS_POSITION;
extern std::unordered_map<uint32, time_t> karathressDpsWaitTimer;
// Morogrim Tidewalker
extern const Position TIDEWALKER_PHASE_1_TANK_POSITION;
extern const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT;
extern const Position TIDEWALKER_PHASE_2_TANK_POSITION;
extern const Position TIDEWALKER_PHASE_2_RANGED_POSITION;
extern std::unordered_map<ObjectGuid, uint8> tidewalkerTankStep;
extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron>
constexpr float VASHJ_PLATFORM_Z = 42.985f;
extern const Position VASHJ_PLATFORM_CENTER_POSITION;
extern std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
extern std::unordered_map<ObjectGuid, Position> intendedLineup;
extern std::unordered_map<uint32, time_t> lastImbueAttempt;
extern std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot);
bool IsLadyVashjInPhase1(PlayerbotAI* botAI);
bool IsLadyVashjInPhase2(PlayerbotAI* botAI);
bool IsLadyVashjInPhase3(PlayerbotAI* botAI);
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI);
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3);
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI);
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI);
struct GeneratorInfo { ObjectGuid guid; float x, y, z; };
extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS;
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(
Map* map, const std::vector<uint32>& generatorDbGuids);
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference);
const GeneratorInfo* GetNearestGeneratorToBot(
Player* bot, const std::vector<GeneratorInfo>& generators);
}
#endif

View File

@@ -11,7 +11,6 @@
#include "GameObject.h"
#include "Group.h"
#include "LastMovementValue.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
@@ -19,11 +18,9 @@
#include "Position.h"
#include "RaidUlduarBossHelper.h"
#include "RaidUlduarScripts.h"
#include "RaidUlduarStrategy.h"
#include "RtiValue.h"
#include "ScriptedCreature.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "Unit.h"
#include "Vehicle.h"
#include <RtiTargetValue.h>

View File

@@ -1,28 +0,0 @@
#include "RaidUlduarMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidUlduarActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float FlameLeviathanMultiplier::GetValue(Action* action)
{
// if (dynamic_cast<FleeAction*>(action))
// return 0.0f;
return 1.0f;
}

View File

@@ -1,17 +0,0 @@
#ifndef _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#define _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#include "Multiplier.h"
#include "Ai/Raid/Ulduar/RaidUlduarBossHelper.h"
class FlameLeviathanMultiplier : public Multiplier
{
public:
FlameLeviathanMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flame leviathan") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -1,169 +0,0 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include <cmath>
#include <ctime>
#include <limits>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "Log.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
const uint32 ULDUAR_MAP_ID = 603;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -1,7 +1,5 @@
#include "RaidUlduarStrategy.h"
#include "RaidUlduarMultipliers.h"
void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
//
@@ -316,8 +314,3 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"yogg-saron phase 3 positioning trigger",
{ NextAction("yogg-saron phase 3 positioning action", ACTION_RAID) }));
}
void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new FlameLeviathanMultiplier(botAI));
}

View File

@@ -3,16 +3,14 @@
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidUlduarStrategy : public Strategy
{
public:
RaidUlduarStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "uld"; }
virtual std::string const getName() override { return "ulduar"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -1634,7 +1634,7 @@ bool VezaxShadowCrashTrigger::IsActive()
return false;
}
return botAI->HasAura(SPELL_SHADOW_CRASH, bot);
return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot);
}
bool VezaxMarkOfTheFacelessTrigger::IsActive()

View File

@@ -3,187 +3,9 @@
#include "EventMap.h"
#include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidUlduarBossHelper.h"
#include "Trigger.h"
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
const float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
//
// Flame Levi
//

View File

@@ -1,4 +1,3 @@
#include "ChatHelper.h"
#include "RaidUlduarBossHelper.h"
#include "ObjectAccessor.h"
#include "GameObject.h"
@@ -9,6 +8,44 @@
#include "Playerbots.h"
#include "World.h"
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
// Prevent harpoon spam
std::unordered_map<ObjectGuid, time_t> RazorscaleBossHelper::_harpoonCooldowns;
// Prevent role assignment spam

View File

@@ -0,0 +1,341 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
constexpr uint32 ULDUAR_MAP_ID = 603;
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_VEZAX_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
constexpr float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
constexpr float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
constexpr float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
constexpr float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
constexpr float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
extern const Position ULDUAR_THORIM_NEAR_ARENA_CENTER;
extern const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_JUMP_END_POINT;
extern const Position ULDUAR_THORIM_PHASE2_TANK_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT;
extern const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT;
extern const Position ULDUAR_YOGG_SARON_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_RAIDVOAACTIONCONTEXT_H
#include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h"
#include "RaidVoAActions.h"
#include "PlayerbotAI.h"

View File

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

View File

@@ -15,8 +15,6 @@
#include "PaladinAiObjectContext.h"
#include "Playerbots.h"
#include "PriestAiObjectContext.h"
#include "RaidUlduarActionContext.h"
#include "RaidUlduarTriggerContext.h"
#include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h"
#include "SharedValueContext.h"
@@ -43,12 +41,16 @@
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
@@ -115,6 +117,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
@@ -149,6 +152,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());

View File

@@ -1799,10 +1799,6 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
{
for (uint32 itemId : sRandomItemMgr.GetCachedEquipments(requiredLevel, inventoryType))
{
if (itemId == 46978) // shaman earth ring totem
{
continue;
}
uint32 skipProb = 25;
if (urand(1, 100) <= skipProb)
continue;

View File

@@ -185,6 +185,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS_EXTENDED, "trade status extended");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response");
botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_ROLL_WON, "loot roll won");
botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
botOutgoingPacketHandlers.AddHandler(SMSG_LEVELUP_INFO, "levelup");
botOutgoingPacketHandlers.AddHandler(SMSG_LOG_XPGAIN, "xpgain");
@@ -1554,6 +1555,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 544:
strategyName = "magtheridon"; // Magtheridon's Lair
break;
case 548:
strategyName = "ssc"; // Serpentshrine Cavern
break;
case 565:
strategyName = "gruulslair"; // Gruul's Lair
break;
@@ -1585,7 +1589,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
strategyName = "wotlk-hol"; // Halls of Lightning
break;
case 603:
strategyName = "uld"; // Ulduar
strategyName = "ulduar"; // Ulduar
break;
case 604:
strategyName = "wotlk-gd"; // Gundrak
@@ -2580,7 +2584,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
return name;
}
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
std::vector<Player*> PlayerbotAI::GetRealPlayersInGroup()
{
std::vector<Player*> members;
@@ -2607,6 +2611,30 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
return members;
}
std::vector<Player*> PlayerbotAI::GetAllPlayersInGroup()
{
std::vector<Player*> members;
Group* group = bot->GetGroup();
if (!group)
return members;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
{
continue;
}
members.push_back(ref->GetSource());
}
return members;
}
bool PlayerbotAI::SayToGuild(const std::string& msg)
{
if (msg.empty())
@@ -2715,9 +2743,9 @@ bool PlayerbotAI::SayToParty(const std::string& msg)
ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName());
for (auto reciever : GetPlayersInGroup())
for (auto receiver : GetRealPlayersInGroup())
{
ServerFacade::instance().SendPacket(reciever, &data);
ServerFacade::instance().SendPacket(receiver, &data);
}
return true;
@@ -2732,9 +2760,9 @@ bool PlayerbotAI::SayToRaid(const std::string& msg)
ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName());
for (auto reciever : GetPlayersInGroup())
for (auto receiver : GetRealPlayersInGroup())
{
ServerFacade::instance().SendPacket(reciever, &data);
ServerFacade::instance().SendPacket(receiver, &data);
}
return true;

View File

@@ -446,7 +446,8 @@ public:
GameObject* GetGameObject(ObjectGuid guid);
// static GameObject* GetGameObject(GameObjectData const* gameObjectData);
WorldObject* GetWorldObject(ObjectGuid guid);
std::vector<Player*> GetPlayersInGroup();
std::vector<Player*> GetAllPlayersInGroup();
std::vector<Player*> GetRealPlayersInGroup();
const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone();
static std::string GetLocalizedAreaName(const AreaTableEntry* entry);

View File

@@ -2256,10 +2256,13 @@ void RandomItemMgr::BuildEquipCacheNew()
{
continue;
}
if (itemId == 22784)
{ // Sunwell Orb
// Unobtainable or unusable items
if (itemId == 12468 || // Chilton Wand
itemId == 22784 || // Sunwell Orb
itemId == 46978) // Totem of the Earthen Ring
continue;
}
equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId);
}
}

View File

@@ -881,107 +881,6 @@ std::vector<GameObjectData const*> WorldPosition::getGameObjectsNear(float radiu
return worker.GetResult();
}
Creature* GuidPosition::GetCreature()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
if (creatureBounds.first != creatureBounds.second)
return creatureBounds.second->second;
return nullptr;
}
return getMap()->GetCreature(*this);
}
Unit* GuidPosition::GetUnit()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
if (creatureBounds.first != creatureBounds.second)
return creatureBounds.second->second;
return nullptr;
}
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
if (IsPet())
return getMap()->GetPet(*this);
return GetCreature();
}
GameObject* GuidPosition::GetGameObject()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto gameobjectBounds = getMap()->GetGameObjectBySpawnIdStore().equal_range(GetCounter());
if (gameobjectBounds.first != gameobjectBounds.second)
return gameobjectBounds.second->second;
return nullptr;
}
return getMap()->GetGameObject(*this);
}
Player* GuidPosition::GetPlayer()
{
if (!*this)
return nullptr;
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
return nullptr;
}
bool GuidPosition::isDead()
{
if (!getMap())
return false;
if (!getMap()->IsGridLoaded(getX(), getY()))
return false;
if (IsUnit() && GetUnit() && GetUnit()->IsInWorld() && GetUnit()->IsAlive())
return false;
if (IsGameObject() && GetGameObject() && GetGameObject()->IsInWorld())
return false;
return true;
}
GuidPosition::GuidPosition(WorldObject* wo) : ObjectGuid(wo->GetGUID()), WorldPosition(wo), loadedFromDB(false) {}
GuidPosition::GuidPosition(CreatureData const& creData)
: ObjectGuid(HighGuid::Unit, creData.id1, creData.spawnId),
WorldPosition(creData.mapid, creData.posX, creData.posY, creData.posZ, creData.orientation)
{
loadedFromDB = true;
}
GuidPosition::GuidPosition(GameObjectData const& goData)
: ObjectGuid(HighGuid::GameObject, goData.id),
WorldPosition(goData.mapid, goData.posX, goData.posY, goData.posZ, goData.orientation)
{
loadedFromDB = true;
}
CreatureTemplate const* GuidPosition::GetCreatureTemplate()
{
return IsCreature() ? sObjectMgr->GetCreatureTemplate(GetEntry()) : nullptr;
@@ -1000,7 +899,7 @@ WorldObject* GuidPosition::GetWorldObject()
switch (GetHigh())
{
case HighGuid::Player:
return ObjectAccessor::FindPlayer(*this);
return GetPlayer();
case HighGuid::Transport:
case HighGuid::Mo_Transport:
case HighGuid::GameObject:
@@ -1021,8 +920,93 @@ WorldObject* GuidPosition::GetWorldObject()
return nullptr;
}
GameObject* GuidPosition::GetGameObject()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedGameObjectByDBGUID(GetMapId(), GetCounter());
return getMap()->GetGameObject(*this); // fallback
}
Unit* GuidPosition::GetUnit()
{
if (!*this)
return nullptr;
if (IsPlayer())
return GetPlayer();
if (IsPet())
return getMap()->GetPet(*this);
return GetCreature();
}
Creature* GuidPosition::GetCreature()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedCreatureByDBGUID(GetMapId(), GetCounter());
return getMap()->GetCreature(*this); // fallback
}
Player* GuidPosition::GetPlayer()
{
if (!*this)
return nullptr;
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
return nullptr;
}
bool GuidPosition::HasNpcFlag(NPCFlags flag) { return IsCreature() && GetCreatureTemplate()->npcflag & flag; }
bool GuidPosition::IsCreatureOrGOAccessible()
{
Map* map = getMap();
if (!map || !map->IsGridLoaded(GetPositionX(), GetPositionY()))
return false;
if (IsCreature())
{
Creature* creature = GetCreature();
if (creature && creature->IsInWorld() && creature->IsAlive())
return true;
}
else if (IsGameObject())
{
GameObject* go = GetGameObject();
if (go && go->IsInWorld())
return true;
}
return false;
}
GuidPosition::GuidPosition(WorldObject* wo) : ObjectGuid(wo->GetGUID()), WorldPosition(wo), loadedFromDB(false) {}
GuidPosition::GuidPosition(CreatureData const& creData)
: ObjectGuid(HighGuid::Unit, creData.id1, creData.spawnId),
WorldPosition(creData.mapid, creData.posX, creData.posY, creData.posZ, creData.orientation)
{
loadedFromDB = true;
}
GuidPosition::GuidPosition(GameObjectData const& goData)
: ObjectGuid(HighGuid::GameObject, goData.id),
WorldPosition(goData.mapid, goData.posX, goData.posY, goData.posZ, goData.orientation)
{
loadedFromDB = true;
}
std::vector<WorldPosition*> TravelDestination::getPoints(bool ignoreFull)
{
if (ignoreFull)

View File

@@ -416,14 +416,13 @@ public:
GameObjectTemplate const* GetGameObjectTemplate();
WorldObject* GetWorldObject();
Creature* GetCreature();
Unit* GetUnit();
GameObject* GetGameObject();
Unit* GetUnit();
Creature* GetCreature();
Player* GetPlayer();
bool HasNpcFlag(NPCFlags flag);
bool isDead(); // For loaded grids check if the unit/object is unloaded/dead.
bool IsCreatureOrGOAccessible(); // For loaded grids check if the creature/gameobject is in world + alive
operator bool() const { return !IsEmpty(); }
bool operator==(ObjectGuid const& guid) const { return GetRawValue() == guid.GetRawValue(); }

View File

@@ -14,9 +14,11 @@
#include "PlayerbotOperation.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotMgr.h"
#include "PlayerbotRepository.h"
#include "RandomPlayerbotMgr.h"
#include "UseMeetingStoneAction.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
@@ -74,6 +76,15 @@ public:
if (group->AddMember(target))
{
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
if (sPlayerbotAIConfig.summonWhenGroup && target->GetDistance(bot) > sPlayerbotAIConfig.sightDistance)
{
PlayerbotAI* targetAI = sPlayerbotsMgr.GetPlayerbotAI(target);
if (targetAI)
{
SummonAction summonAction(targetAI, "group summon");
summonAction.Teleport(bot, target, true);
}
}
return true;
}
else