Compare commits

...

7 Commits

Author SHA1 Message Date
kadeshar
66c88d4815
Merge pull request #1472 from liyunfan1223/drink_aura
Change drink aura (free food) to speed up
2025-07-27 08:53:09 +02:00
kadeshar
4c3906c243
Merge pull request #1468 from ThePenguinMan96/Mage-Overhaul
Mage Overhaul
2025-07-27 08:46:23 +02:00
NoxMax
db7a17ffde
Fix: Properly track RNDbot and AddClass accounts, and login faction balance issue (#1434)
* AssignAccountTypes & AddRandomBots

Fix: Properly track RNDbot and AddClass accounts, and login faction balance issue

* code style edits

* fix addclass init on first build of playerbots_account_type
2025-07-27 14:13:20 +08:00
kadeshar
1e33b28abe
- Added raid cheat to configuration to add posibility to turn off (#1465)
- Added General Vezax strategy
2025-07-27 13:51:45 +08:00
Yunfan Li
e59bad26c4 Change drink aura for free food 2025-07-27 11:36:43 +08:00
ThePenguinMan96
a632fa2194 Migrate important cooldowns over to BoostStrategy
This commit focuses on the changes requested - Migrate important cooldowns over to BoostStrategy.

Just tested it, and it is working fine - The player has more control over when they can boost. Also, added Mirror Image to the Boost Strategy, after other cooldowns are used - this way the mirror images get the buffs.
2025-07-26 13:06:41 -07:00
ThePenguinMan96
ee245f73b5 Mage Overhaul
Hello everyone,

Back again with another class overhaul. Here is a list of what changes have been made:
1. Consolidated the AoE strategies into "aoe". For light aoe (2+ enemies) the mage will use Cone of Cold (frost)/Arcane Explosion (Arcane)/Multi-Dot with Living Bomb (Fire/Frostfire). For medium aoe (3+ enemies) they will use Flamestrike -> Blizzard. Also, the mage will automatically cancel channeling their blizzard if there is less than 2 enemies around. This is huge, since the mage would often stand there and finish their entire channel during a boss fight after the adds died.
2. Organized actions, triggers, and the aiobjectcontext
3. Enabled Deep Freeze to be casted on bosses regardless of their immune status. Big benefit for frost dps on boss fights.
4. Slight tweaks in the conf so Arcane gets Arcane Barrage and Frostfire gets 2/2 Firestarter
5. Streamlined Arcane DPS to use Missile Barrage proc when at 4 stacks of Arcane Blast
5. Streamlined Fire/Frostfire DPS to keep Improved Scorch active (5% spell crit) unless there is a debuff of equal type
6. Added "firestarter" strategy, that utilizes the Fire talent Firestarter better. The mage will multi-dot Living Bomb while running towards melee, and cast Dragon's Breath -> instant cast Flamestrike -> Blast Wave -> instant cast Flamestrike -> Blizzard for bonkers damage. Disabled by default - not everyone wants their mages running into melee. Enable by typing "co +firestarter" on fire and frostfire mages.
7. Streamlined Frost DPS by finally adding support for Cold Snap for mages. It will proc when both Icy Veins and Deep Freeze are on cooldown. There is an exception to this - if the mage is level 30-59, it will not check for Deep Freeze - only Icy Veins.
8. Added Conjure Mana Gem support in the generic non-combat strategy and Use Mana Gem support in the generic combat strategy. This might be the biggest benefit of the overhaul - the gem has a 90 second cooldown, not shared with mana potions. It really prevents the mage from gassing out in longer fights. And the mana gem has 3 charges!
9. Added Mana Shield ability, which triggers on low health.
10. Changed Mirror Image from a boost ability to an anti-threat tool. Not many people know this, but it's best use in PvE is it's anti-threat modifier: "Mod Total Threat - Temporary Value: -90000000". It also doesn't do good damage, and is essentially used best as a pre-pull spell. But until the mages know how to react to a pull-timer, it's going to be used to reduce threat.

Let me know what y'all think!
2025-07-26 01:49:49 -07:00
36 changed files with 1750 additions and 764 deletions

View File

@ -501,9 +501,10 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "mana" (bots have infinite mana) # "mana" (bots have infinite mana)
# "power" (bots have infinite energy, rage, and runic power) # "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them) # "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies)
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi") # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi")
# Default: taxi is enabled # Default: taxi is enabled
AiPlayerbot.BotCheats = "taxi" AiPlayerbot.BotCheats = "taxi,raid"
# #
# #
@ -1427,8 +1428,8 @@ AiPlayerbot.PremadeSpecLink.7.5.80 = -023222301004-05032331331013501120331251
AiPlayerbot.PremadeSpecName.8.0 = arcane pve AiPlayerbot.PremadeSpecName.8.0 = arcane pve
AiPlayerbot.PremadeSpecGlyph.8.0 = 42735,43339,44955,43364,43361,42751 AiPlayerbot.PremadeSpecGlyph.8.0 = 42735,43339,44955,43364,43361,42751
AiPlayerbot.PremadeSpecLink.8.0.60 = 23000503110033014032310150532 AiPlayerbot.PremadeSpecLink.8.0.60 = 230005231100330150323102500321
AiPlayerbot.PremadeSpecLink.8.0.80 = 23000523310033015032310250532-03-203203001 AiPlayerbot.PremadeSpecLink.8.0.80 = 230005231100330150323102505321-03-203303001
AiPlayerbot.PremadeSpecName.8.1 = fire pve AiPlayerbot.PremadeSpecName.8.1 = fire pve
AiPlayerbot.PremadeSpecGlyph.8.1 = 42739,43339,45737,43364,44920,42751 AiPlayerbot.PremadeSpecGlyph.8.1 = 42739,43339,45737,43364,44920,42751
AiPlayerbot.PremadeSpecLink.8.1.60 = -0055030011302231053120321341 AiPlayerbot.PremadeSpecLink.8.1.60 = -0055030011302231053120321341
@ -1440,7 +1441,7 @@ AiPlayerbot.PremadeSpecLink.8.2.80 = 23002303110003--053303031320310003015223135
AiPlayerbot.PremadeSpecName.8.3 = frostfire pve AiPlayerbot.PremadeSpecName.8.3 = frostfire pve
AiPlayerbot.PremadeSpecGlyph.8.3 = 44684,44920,42751,43339,43364,45737 AiPlayerbot.PremadeSpecGlyph.8.3 = 44684,44920,42751,43339,43364,45737
AiPlayerbot.PremadeSpecLink.8.3.60 = -2305032012303331053120300051 AiPlayerbot.PremadeSpecLink.8.3.60 = -2305032012303331053120300051
AiPlayerbot.PremadeSpecLink.8.3.80 = -2305032012303331053120311351-023303031 AiPlayerbot.PremadeSpecLink.8.3.80 = -2305032012303331053120321351-023302031
AiPlayerbot.PremadeSpecName.8.4 = arcane pvp AiPlayerbot.PremadeSpecName.8.4 = arcane pvp
AiPlayerbot.PremadeSpecGlyph.8.4 = 42735,43364,42738,43360,43357,42752 AiPlayerbot.PremadeSpecGlyph.8.4 = 42735,43364,42738,43360,43357,42752
AiPlayerbot.PremadeSpecLink.8.4.60 = 205323200122032103303102015221 AiPlayerbot.PremadeSpecLink.8.4.60 = 205323200122032103303102015221

View File

@ -0,0 +1,8 @@
DROP TABLE IF EXISTS `playerbots_account_type`;
CREATE TABLE `playerbots_account_type` (
`account_id` int unsigned NOT NULL,
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';

View File

@ -0,0 +1,9 @@
-- Create playerbots_account_type table for tracking accounts assignments
DROP TABLE IF EXISTS `playerbots_account_type`;
CREATE TABLE `playerbots_account_type` (
`account_id` int unsigned NOT NULL,
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';

View File

@ -305,22 +305,22 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break; break;
case CLASS_MAGE: case CLASS_MAGE:
if (tab == 0) if (tab == 0)
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr); engine->addStrategiesNoInit("arcane", nullptr);
else if (tab == 1) else if (tab == 1)
{ {
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/) if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{ {
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr); engine->addStrategiesNoInit("frostfire", nullptr);
} }
else else
{ {
engine->addStrategiesNoInit("fire", "fire aoe", nullptr); engine->addStrategiesNoInit("fire", nullptr);
} }
} }
else else
engine->addStrategiesNoInit("frost", "frost aoe", nullptr); engine->addStrategiesNoInit("frost", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == 2) if (tab == 2)

View File

@ -2949,7 +2949,10 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!itemTarget) if (!itemTarget)
{ {
// Exception for Deep Freeze (44572) - allow cast for damage on immune targets (e.g., bosses)
if (target->IsImmunedToSpell(spellInfo)) if (target->IsImmunedToSpell(spellInfo))
{
if (spellid != 44572) // Deep Freeze
{ {
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{ {
@ -2958,6 +2961,8 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
} }
return false; return false;
} }
// Otherwise, allow Deep Freeze even if immune
}
if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance) if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance)
{ {

View File

@ -118,7 +118,6 @@ bool PlayerbotAIConfig::Initialize()
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false); tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false);
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f); randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true); incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3); randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0); randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
@ -448,7 +447,7 @@ bool PlayerbotAIConfig::Initialize()
} }
botCheats.clear(); botCheats.clear();
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi"), LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi,raid"),
botCheats); botCheats);
botCheatMask = 0; botCheatMask = 0;
@ -463,6 +462,8 @@ bool PlayerbotAIConfig::Initialize()
botCheatMask |= (uint32)BotCheatMask::mana; botCheatMask |= (uint32)BotCheatMask::mana;
if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end()) if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::power; botCheatMask |= (uint32)BotCheatMask::power;
if (std::find(botCheats.begin(), botCheats.end(), "raid") != botCheats.end())
botCheatMask |= (uint32)BotCheatMask::raid;
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""), LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
allowedLogFiles); allowedLogFiles);
@ -616,6 +617,9 @@ bool PlayerbotAIConfig::Initialize()
return true; return true;
} }
// Assign account types after accounts are created
sRandomPlayerbotMgr->AssignAccountTypes();
if (sPlayerbotAIConfig->enabled) if (sPlayerbotAIConfig->enabled)
{ {
sRandomPlayerbotMgr->Init(); sRandomPlayerbotMgr->Init();

View File

@ -22,7 +22,8 @@ enum class BotCheatMask : uint32
health = 4, health = 4,
mana = 8, mana = 8,
power = 16, power = 16,
maxMask = 32 raid = 32,
maxMask = 64
}; };
enum class HealingManaEfficiency : uint8 enum class HealingManaEfficiency : uint8

View File

@ -584,8 +584,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
} }
bot->SaveToDB(false, false); bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter()); bool addClassBot = sRandomPlayerbotMgr->IsAccountType(accountId, 2);
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3) if (addClassBot && master && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
{ {
// PlayerbotFactory factory(bot, master->GetLevel()); // PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false); // factory.Randomize(false);

View File

@ -393,37 +393,118 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
return std::move(botName); return std::move(botName);
} }
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
// or determining it dynamically based on MaxRandomBots, EnablePeriodicOnlineOffline and its ratio,
// and AddClassAccountPoolSize. The system also factors in the types of existing account, as assigned by
// AssignAccountTypes()
uint32 RandomPlayerbotFactory::CalculateTotalAccountCount() uint32 RandomPlayerbotFactory::CalculateTotalAccountCount()
{ {
// Calculates the total number of required accounts, either using the specified randomBotAccountCount // Reset account types if features are disabled
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size, // Reset is done here to precede needed accounts calculations
// and additional class account pool size. if (sPlayerbotAIConfig->maxRandomBots == 0 || sPlayerbotAIConfig->addClassAccountPoolSize == 0)
{
if (sPlayerbotAIConfig->maxRandomBots == 0)
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 1");
LOG_INFO("playerbots", "MaxRandomBots set to 0, any RNDbot accounts (type 1) will be unassigned (type 0)");
}
if (sPlayerbotAIConfig->addClassAccountPoolSize == 0)
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 2");
LOG_INFO("playerbots", "AddClassAccountPoolSize set to 0, any AddClass accounts (type 2) will be unassigned (type 0)");
}
// Wait for DB to reflect the change, up to 1 second max. This is needed to make sure other logs don't show wrong info
for (int waited = 0; waited < 1000; waited += 50)
{
QueryResult res = PlayerbotsDatabase.Query("SELECT COUNT(*) FROM playerbots_account_type WHERE account_type IN ({}, {})",
sPlayerbotAIConfig->maxRandomBots == 0 ? 1 : -1,
sPlayerbotAIConfig->addClassAccountPoolSize == 0 ? 2 : -1);
if (!res || res->Fetch()[0].Get<uint64>() == 0)
{
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Extra 50ms fixed delay for safety.
}
}
// Checks if randomBotAccountCount is set, otherwise calculate it dynamically. // Checks if randomBotAccountCount is set, otherwise calculate it dynamically.
if (sPlayerbotAIConfig->randomBotAccountCount > 0) if (sPlayerbotAIConfig->randomBotAccountCount > 0)
return sPlayerbotAIConfig->randomBotAccountCount; return sPlayerbotAIConfig->randomBotAccountCount;
// Avoid creating accounts if both maxRandom & ClassBots are set to zero. // Check existing account types
if (sPlayerbotAIConfig->maxRandomBots == 0 && uint32 existingRndBotAccounts = 0;
sPlayerbotAIConfig->addClassAccountPoolSize == 0) uint32 existingAddClassAccounts = 0;
return 0; uint32 existingUnassignedAccounts = 0;
//bool isWOTLK = sWorld->getIntConfig(CONFIG_EXPANSION) == EXPANSION_WRATH_OF_THE_LICH_KING; //not used, line marked for removal. QueryResult typeCheck = PlayerbotsDatabase.Query("SELECT account_type, COUNT(*) FROM playerbots_account_type GROUP BY account_type");
if (typeCheck)
{
do
{
Field* fields = typeCheck->Fetch();
uint8 accountType = fields[0].Get<uint8>();
uint32 count = fields[1].Get<uint32>();
// Determine divisor based on WOTLK condition if (accountType == 0) existingUnassignedAccounts = count;
else if (accountType == 1) existingRndBotAccounts = count;
else if (accountType == 2) existingAddClassAccounts = count;
} while (typeCheck->NextRow());
}
// Determine divisor based on Death Knight login eligibility and requested A&H faction ratio
int divisor = CalculateAvailableCharsPerAccount(); int divisor = CalculateAvailableCharsPerAccount();
// Calculate max bots // Calculate max bots
int maxBots = sPlayerbotAIConfig->maxRandomBots; int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take perodic online - offline into account // Take periodic online - offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline) if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{ {
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio; maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
} }
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset // Calculate number of accounts needed for RNDbots
uint32 baseAccounts = maxBots / divisor; // Result is rounded up for maxBots not cleanly divisible by the divisor
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1; uint32 neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
// Start with existing total
uint32 existingTotal = existingRndBotAccounts + existingAddClassAccounts + existingUnassignedAccounts;
// Calculate shortfalls after using unassigned accounts
uint32 availableUnassigned = existingUnassignedAccounts;
uint32 additionalAccountsNeeded = 0;
// Check RNDbot needs
if (neededRndBotAccounts > existingRndBotAccounts)
{
uint32 rndBotShortfall = neededRndBotAccounts - existingRndBotAccounts;
if (rndBotShortfall <= availableUnassigned)
availableUnassigned -= rndBotShortfall;
else
{
additionalAccountsNeeded += (rndBotShortfall - availableUnassigned);
availableUnassigned = 0;
}
}
// Check AddClass needs
if (neededAddClassAccounts > existingAddClassAccounts)
{
uint32 addClassShortfall = neededAddClassAccounts - existingAddClassAccounts;
if (addClassShortfall <= availableUnassigned)
availableUnassigned -= addClassShortfall;
else
{
additionalAccountsNeeded += (addClassShortfall - availableUnassigned);
availableUnassigned = 0;
}
}
// Return existing total plus any additional accounts needed
return existingTotal + additionalAccountsNeeded;
} }
uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount() uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount()
@ -475,8 +556,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_INFO("playerbots", "Deleting all random bot characters and accounts..."); LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
// First execute all the cleanup SQL commands // First execute all the cleanup SQL commands
// Clear playerbots_random_bots table // Clear playerbots_random_bots and playerbots_account_type
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots"); PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots");
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_type");
// Get the database names dynamically // Get the database names dynamically
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database; std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;

View File

@ -515,15 +515,174 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
// setActivityPercentage(activityPercentage); // setActivityPercentage(activityPercentage);
// } // }
// Assigns accounts as RNDbot accounts (type 1) based on MaxRandomBots and EnablePeriodicOnlineOffline and its ratio,
// and assigns accounts as AddClass accounts (type 2) based AddClassAccountPoolSize. Type 1 and 2 assignments are
// permenant, unless MaxRandomBots or AddClassAccountPoolSize are set to 0. If so, their associated accounts will
// be unassigned (type 0)
void RandomPlayerbotMgr::AssignAccountTypes()
{
LOG_INFO("playerbots", "Assigning account types for random bot accounts...");
// Clear existing filtered lists
rndBotTypeAccounts.clear();
addClassTypeAccounts.clear();
// First, get ALL randombot accounts from the database
std::vector<uint32> allRandomBotAccounts;
QueryResult allAccounts = LoginDatabase.Query(
"SELECT id FROM account WHERE username LIKE '{}%%' ORDER BY id",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
if (allAccounts)
{
do
{
Field* fields = allAccounts->Fetch();
uint32 accountId = fields[0].Get<uint32>();
allRandomBotAccounts.push_back(accountId);
} while (allAccounts->NextRow());
}
LOG_INFO("playerbots", "Found {} total randombot accounts in database", allRandomBotAccounts.size());
// Check existing assignments
QueryResult existingAssignments = PlayerbotsDatabase.Query("SELECT account_id, account_type FROM playerbots_account_type");
std::map<uint32, uint8> currentAssignments;
if (existingAssignments)
{
do
{
Field* fields = existingAssignments->Fetch();
uint32 accountId = fields[0].Get<uint32>();
uint8 accountType = fields[1].Get<uint8>();
currentAssignments[accountId] = accountType;
} while (existingAssignments->NextRow());
}
// Mark ALL randombot accounts as unassigned if not already assigned
for (uint32 accountId : allRandomBotAccounts)
{
if (currentAssignments.find(accountId) == currentAssignments.end())
{
PlayerbotsDatabase.Execute("INSERT INTO playerbots_account_type (account_id, account_type) VALUES ({}, 0) ON DUPLICATE KEY UPDATE account_type = account_type", accountId);
currentAssignments[accountId] = 0;
}
}
// Calculate needed RNDbot accounts
uint32 neededRndBotAccounts = 0;
if (sPlayerbotAIConfig->maxRandomBots > 0)
{
int divisor = RandomPlayerbotFactory::CalculateAvailableCharsPerAccount();
int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take periodic online-offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
}
// Calculate base accounts needed for RNDbots, ensuring round up for maxBots not cleanly divisible by the divisor
neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
}
// Count existing assigned accounts
uint32 existingRndBotAccounts = 0;
uint32 existingAddClassAccounts = 0;
for (const auto& [accountId, accountType] : currentAssignments)
{
if (accountType == 1) existingRndBotAccounts++;
else if (accountType == 2) existingAddClassAccounts++;
}
// Assign RNDbot accounts from lowest position if needed
if (existingRndBotAccounts < neededRndBotAccounts)
{
uint32 toAssign = neededRndBotAccounts - existingRndBotAccounts;
uint32 assigned = 0;
for (uint32 i = 0; i < allRandomBotAccounts.size() && assigned < toAssign; i++)
{
uint32 accountId = allRandomBotAccounts[i];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 1, assignment_date = NOW() WHERE account_id = {}", accountId);
currentAssignments[accountId] = 1;
assigned++;
}
}
if (assigned < toAssign)
{
LOG_ERROR("playerbots", "Not enough unassigned accounts to fulfill RNDbot requirements. Need {} more accounts.", toAssign - assigned);
}
}
// Assign AddClass accounts from highest position if needed
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
if (existingAddClassAccounts < neededAddClassAccounts)
{
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
uint32 assigned = 0;
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
{
uint32 accountId = allRandomBotAccounts[i];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
currentAssignments[accountId] = 2;
assigned++;
}
}
if (assigned < toAssign)
{
LOG_ERROR("playerbots", "Not enough unassigned accounts to fulfill AddClass requirements. Need {} more accounts.", toAssign - assigned);
}
}
// Populate filtered account lists with ALL accounts of each type
for (const auto& [accountId, accountType] : currentAssignments)
{
if (accountType == 1) rndBotTypeAccounts.push_back(accountId);
else if (accountType == 2) addClassTypeAccounts.push_back(accountId);
}
LOG_INFO("playerbots", "Account type assignment complete: {} RNDbot accounts, {} AddClass accounts, {} unassigned",
rndBotTypeAccounts.size(), addClassTypeAccounts.size(),
currentAssignments.size() - rndBotTypeAccounts.size() - addClassTypeAccounts.size());
}
bool RandomPlayerbotMgr::IsAccountType(uint32 accountId, uint8 accountType)
{
QueryResult result = PlayerbotsDatabase.Query("SELECT 1 FROM playerbots_account_type WHERE account_id = {} AND account_type = {}", accountId, accountType);
return result != nullptr;
}
// Logs-in bots in 4 phases. Phase 1 logs Alliance bots up to how much is expected according to the faction ratio,
// and Phase 2 logs-in the remainder Horde bots to reach the total maxAllowedBotCount. If maxAllowedBotCount is not
// reached after Phase 2, the function goes back to log-in Alliance bots and reach maxAllowedBotCount. This is done
// because not every account is guaranteed 5A/5H bots, so the true ratio might be skewed by few percentages. Finally,
// Phase 4 is reached if and only if the value of RandomBotAccountCount is lower than it should.
uint32 RandomPlayerbotMgr::AddRandomBots() uint32 RandomPlayerbotMgr::AddRandomBots()
{ {
uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); uint32 maxAllowedBotCount = GetEventValue(0, "bot_count");
static time_t missingBotsTimer = 0;
if (currentBots.size() < maxAllowedBotCount) if (currentBots.size() < maxAllowedBotCount)
{ {
// Calculate how many bots to add
maxAllowedBotCount -= currentBots.size(); maxAllowedBotCount -= currentBots.size();
maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount); maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
// Single RNG instance for all shuffling
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// Only need to track the Alliance count, as it's in Phase 1
uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio; uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio; uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
@ -535,26 +694,42 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
allowedAllianceCount++; allowedAllianceCount++;
} }
uint32 allowedHordeCount = maxAllowedBotCount - allowedAllianceCount; // Determine which accounts to use based on EnablePeriodicOnlineOffline
std::vector<uint32> accountsToUse;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin();
i != sPlayerbotAIConfig->randomBotAccounts.end(); i++)
{
uint32 accountId = *i;
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline) if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{ {
// minus addclass bots account
int32 baseAccount =
RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize;
if (baseAccount <= 0 || baseAccount > sPlayerbotAIConfig->randomBotAccounts.size()) // Calculate how many accounts can be used
// With enablePeriodicOnlineOffline, don't use all of rndBotTypeAccounts right away. Fraction results are rounded up
uint32 accountsToUseCount = (rndBotTypeAccounts.size() + sPlayerbotAIConfig->periodicOnlineOfflineRatio - 1)
/ sPlayerbotAIConfig->periodicOnlineOfflineRatio;
// Randomly select accounts
std::vector<uint32> shuffledAccounts = rndBotTypeAccounts;
std::shuffle(shuffledAccounts.begin(), shuffledAccounts.end(), rng);
for (uint32 i = 0; i < accountsToUseCount && i < shuffledAccounts.size(); i++)
{ {
LOG_ERROR("playerbots", "Account calculation error with PeriodicOnlineOffline"); accountsToUse.push_back(shuffledAccounts[i]);
return 0;
} }
uint32 index = urand(0, baseAccount - 1);
accountId = sPlayerbotAIConfig->randomBotAccounts[index];
} }
else
{
accountsToUse = rndBotTypeAccounts;
}
// Pre-map all characters from selected accounts
struct CharacterInfo
{
uint32 guid;
uint8 rClass;
uint8 rRace;
uint32 accountId;
};
std::vector<CharacterInfo> allCharacters;
for (uint32 accountId : accountsToUse)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabasePreparedStatement* stmt =
CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID); CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID);
stmt->SetData(0, accountId); stmt->SetData(0, accountId);
@ -562,62 +737,44 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
if (!result) if (!result)
continue; continue;
std::vector<GuidClassRaceInfo> allGuidInfos;
do do
{ {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
GuidClassRaceInfo info; CharacterInfo info;
info.guid = fields[0].Get<uint32>(); info.guid = fields[0].Get<uint32>();
info.rClass = fields[1].Get<uint8>(); info.rClass = fields[1].Get<uint8>();
info.rRace = fields[2].Get<uint8>(); info.rRace = fields[2].Get<uint8>();
allGuidInfos.push_back(info); info.accountId = accountId;
allCharacters.push_back(info);
} while (result->NextRow()); } while (result->NextRow());
// random shuffle for class balance
std::mt19937 rnd(time(0));
std::shuffle(allGuidInfos.begin(), allGuidInfos.end(), rnd);
std::vector<uint32> guids;
for (const auto& info : allGuidInfos)
{
ObjectGuid::LowType guid = info.guid;
uint32 rClass = info.rClass;
uint32 rRace = info.rRace;
if (GetEventValue(guid, "add"))
continue;
if (GetEventValue(guid, "logout"))
continue;
if (GetPlayerBot(guid))
continue;
if (std::find(currentBots.begin(), currentBots.end(), guid) != currentBots.end())
continue;
if (sPlayerbotAIConfig->disableDeathKnightLogin)
{
if (rClass == CLASS_DEATH_KNIGHT)
{
continue;
}
} }
uint32 isAlliance = IsAlliance(rRace); // Shuffle for class balance
bool factionNotAllowed = (!allowedAllianceCount && isAlliance) || (!allowedHordeCount && !isAlliance); std::shuffle(allCharacters.begin(), allCharacters.end(), rng);
if (factionNotAllowed) // Separate characters by faction for phased login
continue; std::vector<CharacterInfo> allianceChars;
std::vector<CharacterInfo> hordeChars;
if (isAlliance) for (const auto& charInfo : allCharacters)
{ {
allowedAllianceCount--; if (IsAlliance(charInfo.rRace))
} allianceChars.push_back(charInfo);
else else
hordeChars.push_back(charInfo);
}
// Lambda to handle bot login logic
auto tryLoginBot = [&](const CharacterInfo& charInfo) -> bool
{ {
allowedHordeCount--; if (GetEventValue(charInfo.guid, "add") ||
GetEventValue(charInfo.guid, "logout") ||
GetPlayerBot(charInfo.guid) ||
std::find(currentBots.begin(), currentBots.end(), charInfo.guid) != currentBots.end() ||
(sPlayerbotAIConfig->disableDeathKnightLogin && charInfo.rClass == CLASS_DEATH_KNIGHT))
{
return false;
} }
uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline
@ -625,24 +782,70 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
sPlayerbotAIConfig->maxRandomBotInWorldTime) sPlayerbotAIConfig->maxRandomBotInWorldTime)
: sPlayerbotAIConfig->permanantlyInWorldTime; : sPlayerbotAIConfig->permanantlyInWorldTime;
SetEventValue(guid, "add", 1, add_time); SetEventValue(charInfo.guid, "add", 1, add_time);
SetEventValue(guid, "logout", 0, 0); SetEventValue(charInfo.guid, "logout", 0, 0);
currentBots.push_back(guid); currentBots.push_back(charInfo.guid);
return true;
};
// PHASE 1: Log-in Alliance bots up to allowedAllianceCount
for (const auto& charInfo : allianceChars)
{
if (!allowedAllianceCount)
break;
if (tryLoginBot(charInfo))
{
maxAllowedBotCount--; maxAllowedBotCount--;
if (!maxAllowedBotCount) allowedAllianceCount--;
break; }
} }
// PHASE 2: Log-in Horde bots up to maxAllowedBotCount
for (const auto& charInfo : hordeChars)
{
if (!maxAllowedBotCount) if (!maxAllowedBotCount)
break; break;
if (tryLoginBot(charInfo))
maxAllowedBotCount--;
} }
// PHASE 3: If maxAllowedBotCount wasn't reached, log-in more Alliance bots
for (const auto& charInfo : allianceChars)
{
if (!maxAllowedBotCount)
break;
if (tryLoginBot(charInfo))
maxAllowedBotCount--;
}
// PHASE 4: An error is given if maxAllowedBotCount is still not reached
if (maxAllowedBotCount) if (maxAllowedBotCount)
{
if (missingBotsTimer == 0)
missingBotsTimer = time(nullptr);
if (time(nullptr) - missingBotsTimer >= 10)
{
int divisor = RandomPlayerbotFactory::CalculateAvailableCharsPerAccount();
uint32 moreAccountsNeeded = (maxAllowedBotCount + divisor - 1) / divisor;
LOG_ERROR("playerbots", LOG_ERROR("playerbots",
"Not enough random bot accounts available. Try to increase RandomBotAccountCount " "Can't log-in all the requested bots. Try increasing RandomBotAccountCount in your conf file.\n"
"in your conf file", "{} more accounts needed.", moreAccountsNeeded);
ceil(maxAllowedBotCount / 10)); missingBotsTimer = 0; // Reset timer so error is not spammed every tick
}
}
else
{
missingBotsTimer = 0; // Reset timer if logins for this interval were successful
}
}
else
{
missingBotsTimer = 0; // Reset timer if there's enough bots
} }
return currentBots.size(); return currentBots.size();
@ -1165,7 +1368,6 @@ void RandomPlayerbotMgr::ScheduleChangeStrategy(uint32 bot, uint32 time)
bool RandomPlayerbotMgr::ProcessBot(uint32 bot) bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
{ {
ObjectGuid botGUID = ObjectGuid::Create<HighGuid::Player>(bot); ObjectGuid botGUID = ObjectGuid::Create<HighGuid::Player>(bot);
Player* player = GetPlayerBot(botGUID); Player* player = GetPlayerBot(botGUID);
PlayerbotAI* botAI = player ? GET_PLAYERBOT_AI(player) : nullptr; PlayerbotAI* botAI = player ? GET_PLAYERBOT_AI(player) : nullptr;
@ -1875,24 +2077,21 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
void RandomPlayerbotMgr::PrepareAddclassCache() void RandomPlayerbotMgr::PrepareAddclassCache()
{ {
/// @FIXME: Modifying RandomBotAccountCount may cause the original addclass bots to be converted into rndbots, // Using accounts marked as type 2 (AddClass)
// which needs to be fixed by separating the two accounts in implementation
size_t poolSize = sPlayerbotAIConfig->addClassAccountPoolSize;
size_t start = sPlayerbotAIConfig->randomBotAccounts.size() > poolSize
? sPlayerbotAIConfig->randomBotAccounts.size() - poolSize
: 0;
int32 collected = 0; int32 collected = 0;
for (size_t i = start; i < sPlayerbotAIConfig->randomBotAccounts.size(); i++)
for (uint32 accountId : addClassTypeAccounts)
{ {
for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++) for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++)
{ {
if (claz == 10) if (claz == 10)
continue; continue;
QueryResult results = CharacterDatabase.Query( QueryResult results = CharacterDatabase.Query(
"SELECT guid, race FROM characters " "SELECT guid, race FROM characters "
"WHERE account = {} AND class = '{}' AND online = 0 " "WHERE account = {} AND class = '{}' AND online = 0",
"ORDER BY account DESC", accountId, claz);
sPlayerbotAIConfig->randomBotAccounts[i], claz);
if (results) if (results)
{ {
do do
@ -1907,7 +2106,8 @@ void RandomPlayerbotMgr::PrepareAddclassCache()
} }
} }
} }
LOG_INFO("playerbots", ">> {} characters collected for addclass command.", collected);
LOG_INFO("playerbots", ">> {} characters collected for addclass command from {} AddClass accounts.", collected, addClassTypeAccounts.size());
} }
void RandomPlayerbotMgr::Init() void RandomPlayerbotMgr::Init()
@ -2286,10 +2486,13 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot); ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
if (!sPlayerbotAIConfig->IsInRandomAccountList(sCharacterCache->GetCharacterAccountIdByGuid(guid))) if (!sPlayerbotAIConfig->IsInRandomAccountList(sCharacterCache->GetCharacterAccountIdByGuid(guid)))
return false; return false;
if (std::find(currentBots.begin(), currentBots.end(), bot) != currentBots.end()) if (std::find(currentBots.begin(), currentBots.end(), bot) != currentBots.end())
return true; return true;
return false; return false;
} }
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{ {
if (bot && GET_PLAYERBOT_AI(bot)) if (bot && GET_PLAYERBOT_AI(bot))
@ -2301,23 +2504,37 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{ {
return IsAddclassBot(bot->GetGUID().GetCounter()); return IsAddclassBot(bot->GetGUID().GetCounter());
} }
return false; return false;
} }
bool RandomPlayerbotMgr::IsAddclassBot(ObjectGuid::LowType bot) bool RandomPlayerbotMgr::IsAddclassBot(ObjectGuid::LowType bot)
{ {
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot); ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
// Check the cache with faction considerations
for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++) for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++)
{ {
if (claz == 10) if (claz == 10)
continue; continue;
for (uint8 isAlliance = 0; isAlliance <= 1; isAlliance++) for (uint8 isAlliance = 0; isAlliance <= 1; isAlliance++)
{ {
if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) != if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) !=
addclassCache[GetTeamClassIdx(isAlliance, claz)].end()) addclassCache[GetTeamClassIdx(isAlliance, claz)].end())
{
return true; return true;
} }
} }
}
// If not in cache, check the account type
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
if (accountId && IsAccountType(accountId, 2)) // Type 2 = AddClass
{
return true;
}
return false; return false;
} }

View File

@ -60,7 +60,6 @@ public:
bool IsEmpty() { return !lastChangeTime; } bool IsEmpty() { return !lastChangeTime; }
public:
uint32 value; uint32 value;
uint32 lastChangeTime; uint32 lastChangeTime;
uint32 validIn; uint32 validIn;
@ -104,10 +103,6 @@ public:
void LogPlayerLocation(); void LogPlayerLocation();
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override; void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
private:
//void ScaleBotActivity();
public:
uint32 activeBots = 0; uint32 activeBots = 0;
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args); static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot); bool IsRandomBot(Player* bot);
@ -189,6 +184,11 @@ public:
}; };
std::map<uint32, LevelBracket> zone2LevelBracket; std::map<uint32, LevelBracket> zone2LevelBracket;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache; std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
// Account type management
void AssignAccountTypes();
bool IsAccountType(uint32 accountId, uint8 accountType);
protected: protected:
void OnBotLoginInternal(Player* const bot) override; void OnBotLoginInternal(Player* const bot) override;
@ -218,11 +218,9 @@ private:
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false); void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ); uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*); typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players; std::vector<Player*> players;
uint32 processTicks; uint32 processTicks;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache; // std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel; std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache; std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
@ -230,6 +228,12 @@ private:
std::list<uint32> currentBots; std::list<uint32> currentBots;
uint32 bgBotsCount; uint32 bgBotsCount;
uint32 playersLevel; uint32 playersLevel;
// Account lists
std::vector<uint32> rndBotTypeAccounts; // Accounts marked as RNDbot (type 1)
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function
}; };
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance() #define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()

View File

@ -53,6 +53,9 @@ BotCheatMask CheatAction::GetCheatMask(std::string const cheat)
if (cheat == "power") if (cheat == "power")
return BotCheatMask::power; return BotCheatMask::power;
if (cheat == "raid")
return BotCheatMask::raid;
return BotCheatMask::none; return BotCheatMask::none;
} }
@ -70,6 +73,8 @@ std::string const CheatAction::GetCheatName(BotCheatMask cheatMask)
return "mana"; return "mana";
case BotCheatMask::power: case BotCheatMask::power:
return "power"; return "power";
case BotCheatMask::raid:
return "raid";
default: default:
return "none"; return "none";
} }

View File

@ -40,13 +40,13 @@ bool DrinkAction::Execute(Event event)
float delay; float delay;
if (!bot->InBattleground()) if (!bot->InBattleground())
delay = 27000.0f * (100 - p) / 100.0f; delay = 18000.0f * (100 - p) / 100.0f;
else else
delay = 20000.0f * (100 - p) / 100.0f; delay = 12000.0f * (100 - p) / 100.0f;
botAI->SetNextCheckDelay(delay); botAI->SetNextCheckDelay(delay);
bot->AddAura(24707, bot); bot->AddAura(25990, bot);
return true; return true;
// return botAI->CastSpell(24707, bot); // return botAI->CastSpell(24707, bot);
} }
@ -90,13 +90,13 @@ bool EatAction::Execute(Event event)
float delay; float delay;
if (!bot->InBattleground()) if (!bot->InBattleground())
delay = 27000.0f * (100 - p) / 100.0f; delay = 18000.0f * (100 - p) / 100.0f;
else else
delay = 20000.0f * (100 - p) / 100.0f; delay = 12000.0f * (100 - p) / 100.0f;
botAI->SetNextCheckDelay(delay); botAI->SetNextCheckDelay(delay);
bot->AddAura(24707, bot); bot->AddAura(25990, bot);
return true; return true;
} }

View File

@ -4,9 +4,9 @@
*/ */
#include "ArcaneMageStrategy.h" #include "ArcaneMageStrategy.h"
#include "Playerbots.h" #include "Playerbots.h"
// ===== Action Node Factory =====
class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode> class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{ {
public: public:
@ -15,69 +15,44 @@ public:
creators["arcane blast"] = &arcane_blast; creators["arcane blast"] = &arcane_blast;
creators["arcane barrage"] = &arcane_barrage; creators["arcane barrage"] = &arcane_barrage;
creators["arcane missiles"] = &arcane_missiles; creators["arcane missiles"] = &arcane_missiles;
// creators["firebolt"] = &firebolt; creators["fire blast"] = &fire_blast;
creators["frostbolt"] = &frostbolt;
creators["arcane power"] = &arcane_power;
creators["icy veins"] = &icy_veins;
} }
private: private:
static ActionNode* arcane_blast([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* arcane_blast(PlayerbotAI*) { return new ActionNode("arcane blast", nullptr, nullptr, nullptr); }
{ static ActionNode* arcane_barrage(PlayerbotAI*) { return new ActionNode("arcane barrage", nullptr, nullptr, nullptr); }
return new ActionNode("arcane blast", static ActionNode* arcane_missiles(PlayerbotAI*) { return new ActionNode("arcane missiles", nullptr, nullptr, nullptr); }
/*P*/ nullptr, static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", nullptr, nullptr, nullptr); }
/*A*/ NextAction::array(0, new NextAction("arcane missiles"), nullptr), static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", nullptr, nullptr, nullptr); }
/*C*/ nullptr); static ActionNode* arcane_power(PlayerbotAI*) { return new ActionNode("arcane power", nullptr, nullptr, nullptr); }
} static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", nullptr, nullptr, nullptr); }
static ActionNode* arcane_barrage([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("arcane barrage",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* arcane_missiles([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("arcane missiles",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("fireball"), nullptr),
/*C*/ nullptr);
}
// static ActionNode* firebolt([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode ("firebolt",
// /*P*/ nullptr,
// /*A*/ NextAction::array(0, new NextAction("shoot"), nullptr),
// /*C*/ nullptr);
// }
}; };
// ===== Single Target Strategy =====
ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{ {
actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory()); actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory());
} }
// ===== Default Actions =====
NextAction** ArcaneMageStrategy::getDefaultActions() NextAction** ArcaneMageStrategy::getDefaultActions()
{ {
return NextAction::array(0, new NextAction("arcane blast", ACTION_DEFAULT + 0.3f), return NextAction::array(0, new NextAction("arcane blast", 5.6f),
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // arcane immune target new NextAction("arcane missiles", 5.5f),
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement new NextAction("arcane barrage", 5.4f), // cast while moving
new NextAction("shoot", ACTION_DEFAULT), nullptr); new NextAction("fire blast", 5.3f), // cast while moving if arcane barrage isn't available/learned
new NextAction("frostbolt", 5.2f), // for arcane immune targets
new NextAction("shoot", 5.1f), nullptr);
} }
// ===== Trigger Initialization ===
void ArcaneMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void ArcaneMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericMageStrategy::InitTriggers(triggers); GenericMageStrategy::InitTriggers(triggers);
triggers.push_back( // Proc Trigger
new TriggerNode("arcane blast stack", NextAction::array(0, new NextAction("arcane missiles", 15.0f), NULL))); triggers.push_back(new TriggerNode("arcane blast 4 stacks and missile barrage", NextAction::array(0, new NextAction("arcane missiles", 15.0f), nullptr)));
}
void ArcaneMageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// triggers.push_back(new TriggerNode(
// "high aoe",
// NextAction::array(0, new NextAction("arcane explosion", 39.0f), NULL)));
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0, new NextAction("blizzard", 40.0f), NULL)));
} }

View File

@ -20,13 +20,4 @@ public:
NextAction** getDefaultActions() override; NextAction** getDefaultActions() override;
}; };
class ArcaneMageAoeStrategy : public CombatStrategy
{
public:
ArcaneMageAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {}
public:
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "arcane aoe"; }
};
#endif #endif

View File

@ -4,42 +4,71 @@
*/ */
#include "FireMageStrategy.h" #include "FireMageStrategy.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "Strategy.h" #include "Strategy.h"
NextAction** FireMageStrategy::getDefaultActions() // ===== Action Node Factory =====
class FireMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{ {
return NextAction::array(0, new NextAction("fireball", ACTION_DEFAULT + 0.3f), public:
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // fire immune target FireMageStrategyActionNodeFactory()
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement {
new NextAction("shoot", ACTION_DEFAULT), NULL); creators["fireball"] = &fireball;
creators["frostbolt"] = &frostbolt;
creators["fire blast"] = &fire_blast;
creators["pyroblast"] = &pyroblast;
creators["scorch"] = &scorch;
creators["living bomb"] = &living_bomb;
creators["combustion"] = &combustion;
} }
private:
static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", nullptr, nullptr, nullptr); }
static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", nullptr, nullptr, nullptr); }
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", nullptr, nullptr, nullptr); }
static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", nullptr, nullptr, nullptr); }
static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", nullptr, nullptr, nullptr); }
static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", nullptr, nullptr, nullptr); }
static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", nullptr, nullptr, nullptr); }
};
// ===== Single Target Strategy =====
FireMageStrategy::FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{
actionNodeFactories.Add(new FireMageStrategyActionNodeFactory());
}
// ===== Default Actions =====
NextAction** FireMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("fireball", 5.3f),
new NextAction("frostbolt", 5.2f), // fire immune target
new NextAction("fire blast", 5.1f), // cast during movement
new NextAction("shoot", 5.0f), nullptr);
}
// ===== Trigger Initialization =====
void FireMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void FireMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericMageStrategy::InitTriggers(triggers); GenericMageStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("pyroblast", NextAction::array(0, new NextAction("pyroblast", 10.0f), // Debuff Triggers
// nullptr))); triggers.push_back(new TriggerNode("improved scorch", NextAction::array(0, new NextAction("scorch", 19.0f), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 18.5f), nullptr)));
new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
triggers.push_back( // Proc Trigger
new TriggerNode("combustion", NextAction::array(0, new NextAction("combustion", 50.0f), nullptr))); triggers.push_back(new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
triggers.push_back(
new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 19.0f), nullptr)));
// triggers.push_back(new TriggerNode("enemy too close for spell", NextAction::array(0, new NextAction("dragon's
// breath", 70.0f), nullptr)));
} }
void FireMageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) // Combat strategy to run to melee for Dragon's Breath and Blast Wave
// Disabled by default for the Fire/Frostfire spec
// To enable, type "co +firestarter"
// To disable, type "co -firestarter"
FirestarterStrategy::FirestarterStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
void FirestarterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
// higher priority to cast before move away triggers.push_back(new TriggerNode(
triggers.push_back( "blast wave off cd and medium aoe",
new TriggerNode("medium aoe", NextAction::array(0, NextAction::array(0, new NextAction("reach melee", 25.5f), nullptr)));
new NextAction("dragon's breath", ACTION_MOVE + 9),
new NextAction("flamestrike", ACTION_MOVE + 8),
new NextAction("blast wave", ACTION_MOVE + 7),
new NextAction("living bomb on attackers", 21.0f),
new NextAction("blizzard", 20.0f), nullptr)));
} }

View File

@ -13,20 +13,19 @@ class PlayerbotAI;
class FireMageStrategy : public GenericMageStrategy class FireMageStrategy : public GenericMageStrategy
{ {
public: public:
FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) {} FireMageStrategy(PlayerbotAI* botAI);
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "fire"; } std::string const getName() override { return "fire"; }
NextAction** getDefaultActions() override; NextAction** getDefaultActions() override;
}; };
class FireMageAoeStrategy : public CombatStrategy class FirestarterStrategy : public CombatStrategy
{ {
public: public:
FireMageAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} FirestarterStrategy(PlayerbotAI* botAI);
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "fire aoe"; } std::string const getName() override { return "firestarter"; }
}; };
#endif #endif

View File

@ -4,31 +4,56 @@
*/ */
#include "FrostFireMageStrategy.h" #include "FrostFireMageStrategy.h"
#include "Playerbots.h" #include "Playerbots.h"
NextAction** FrostFireMageStrategy::getDefaultActions() // ===== Action Node Factory =====
class FrostFireMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{ {
return NextAction::array(0, new NextAction("frostfire bolt", ACTION_DEFAULT + 0.1f), public:
new NextAction("shoot", ACTION_DEFAULT), NULL); FrostFireMageStrategyActionNodeFactory()
{
creators["frostfire bolt"] = &frostfire_bolt;
creators["fire blast"] = &fire_blast;
creators["pyroblast"] = &pyroblast;
creators["combustion"] = &combustion;
creators["icy veins"] = &icy_veins;
creators["scorch"] = &scorch;
creators["living bomb"] = &living_bomb;
} }
private:
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", nullptr, nullptr, nullptr); }
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", nullptr, nullptr, nullptr); }
static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", nullptr, nullptr, nullptr); }
static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", nullptr, nullptr, nullptr); }
static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", nullptr, nullptr, nullptr); }
static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", nullptr, nullptr, nullptr); }
static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", nullptr, nullptr, nullptr); }
};
// ===== Single Target Strategy =====
FrostFireMageStrategy::FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{
actionNodeFactories.Add(new FrostFireMageStrategyActionNodeFactory());
}
// ===== Default Actions =====
NextAction** FrostFireMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("frostfire bolt", 5.2f),
new NextAction("fire blast", 5.1f), // cast during movement
new NextAction("shoot", 5.0f), nullptr);
}
// ===== Trigger Initialization =====
void FrostFireMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void FrostFireMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericMageStrategy::InitTriggers(triggers); GenericMageStrategy::InitTriggers(triggers);
triggers.push_back( // Debuff Triggers
new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr))); triggers.push_back(new TriggerNode("improved scorch", NextAction::array(0, new NextAction("scorch", 19.0f), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 18.5f), nullptr)));
new TriggerNode("combustion", NextAction::array(0, new NextAction("combustion", 50.0f), nullptr)));
triggers.push_back(
new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 60.0f), nullptr)));
}
void FrostFireMageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) // Proc Trigger
{ triggers.push_back(new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("flamestrike", 20.0f), nullptr)));
triggers.push_back(
new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 25.0f), nullptr)));
} }

View File

@ -13,20 +13,11 @@ class PlayerbotAI;
class FrostFireMageStrategy : public GenericMageStrategy class FrostFireMageStrategy : public GenericMageStrategy
{ {
public: public:
FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) {} FrostFireMageStrategy(PlayerbotAI* botAI);
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frostfire"; } std::string const getName() override { return "frostfire"; }
NextAction** getDefaultActions() override; NextAction** getDefaultActions() override;
}; };
class FrostFireMageAoeStrategy : public CombatStrategy
{
public:
FrostFireMageAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frostfire aoe"; }
};
#endif #endif

View File

@ -7,6 +7,7 @@
#include "Playerbots.h" #include "Playerbots.h"
// ===== Action Node Factory =====
class FrostMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode> class FrostMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{ {
public: public:
@ -16,96 +17,66 @@ public:
creators["ice barrier"] = &ice_barrier; creators["ice barrier"] = &ice_barrier;
creators["summon water elemental"] = &summon_water_elemental; creators["summon water elemental"] = &summon_water_elemental;
creators["deep freeze"] = &deep_freeze; creators["deep freeze"] = &deep_freeze;
creators["icy veins"] = &icy_veins;
creators["frostbolt"] = &frostbolt;
creators["ice lance"] = &ice_lance;
creators["fire blast"] = &fire_blast;
creators["fireball"] = &fireball;
creators["frostfire bolt"] = &frostfire_bolt;
} }
private: private:
static ActionNode* cold_snap([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* cold_snap(PlayerbotAI*) { return new ActionNode("cold snap", nullptr, nullptr, nullptr); }
{ static ActionNode* ice_barrier(PlayerbotAI*) { return new ActionNode("ice barrier", nullptr, nullptr, nullptr); }
return new ActionNode("cold snap", static ActionNode* summon_water_elemental(PlayerbotAI*) { return new ActionNode("summon water elemental", nullptr, nullptr, nullptr); }
/*P*/ nullptr, static ActionNode* deep_freeze(PlayerbotAI*) { return new ActionNode("deep freeze", nullptr, nullptr, nullptr); }
/*A*/ nullptr, static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", nullptr, nullptr, nullptr); }
/*C*/ nullptr); static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", nullptr, nullptr, nullptr); }
} static ActionNode* ice_lance(PlayerbotAI*) { return new ActionNode("ice lance", nullptr, nullptr, nullptr); }
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", nullptr, nullptr, nullptr); }
static ActionNode* ice_barrier([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", nullptr, nullptr, nullptr); }
{ static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", nullptr, nullptr, nullptr); }
return new ActionNode("ice barrier",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* summon_water_elemental([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("summon water elemental",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* deep_freeze([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("deep freeze",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("ice lance"), nullptr),
/*C*/ nullptr);
}
}; };
// ===== Single Target Strategy =====
FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{ {
actionNodeFactories.Add(new FrostMageStrategyActionNodeFactory()); actionNodeFactories.Add(new FrostMageStrategyActionNodeFactory());
} }
// ===== Default Actions =====
NextAction** FrostMageStrategy::getDefaultActions() NextAction** FrostMageStrategy::getDefaultActions()
{ {
return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.3f), return NextAction::array(0, new NextAction("frostbolt", 5.4f),
new NextAction("fire blast", ACTION_DEFAULT + 0.2f), // cast during movement new NextAction("ice lance", 5.3f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT + 0.1f), new NextAction("fire blast", 5.2f), // cast during movement if ice lance is not learned
new NextAction("fireball", ACTION_DEFAULT), nullptr); new NextAction("shoot", 5.1f),
new NextAction("fireball", 5.0f), nullptr);
} }
// ===== Trigger Initialization ===
void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericMageStrategy::InitTriggers(triggers); GenericMageStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 50.0f), nullptr)));
// No logic currently for cold snap usage.. possibly use right after icy veins drops off?
// triggers.push_back(new TriggerNode("cold snap", NextAction::array(0, new NextAction("cold snap", 50.0f),
// nullptr)));
triggers.push_back(new TriggerNode( // Pet/Defensive triggers
"no pet", NextAction::array(0, new NextAction("summon water elemental", ACTION_HIGH), nullptr))); triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), nullptr)));
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", ACTION_NORMAL), nullptr)));
triggers.push_back( // Proc/Freeze triggers
new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode("brain freeze", NextAction::array(0, new NextAction("frostfire bolt", 19.5f), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode("fingers of frost", NextAction::array(0,
"brain freeze", NextAction::array(0, new NextAction("frostfire bolt", ACTION_NORMAL + 3), nullptr))); new NextAction("deep freeze", 19.0f),
// Combo cast the last charge of fingers of frost for double crits. new NextAction("frostbolt", 18.0f), nullptr)));
// Should only do this on the final charge of FoF.
triggers.push_back(new TriggerNode("fingers of frost single", triggers.push_back(new TriggerNode("frostbite on target", NextAction::array(0,
NextAction::array(0, new NextAction("frostbolt", ACTION_NORMAL + 2), new NextAction("deep freeze", 19.0f),
new NextAction("deep freeze", ACTION_NORMAL + 1), nullptr))); new NextAction("frostbolt", 18.0f), nullptr)));
// May not need this, frostbolt is the default action so probably don't need to specify.
// Maybe uncomment if you find the mage is prioritising auxillary spells while this buff is up, and wasting the triggers.push_back(new TriggerNode("frost nova on target", NextAction::array(0,
// proc. triggers.push_back(new TriggerNode("fingers of frost double", NextAction::array(0, new new NextAction("deep freeze", 19.0f),
// NextAction("frostbolt", ACTION_NORMAL), nullptr))); new NextAction("frostbolt", 18.0f), nullptr)));
// Same 2-spell combo for various freeze procs
triggers.push_back(new TriggerNode("frost nova on target",
NextAction::array(0, new NextAction("frostbolt", ACTION_NORMAL + 2),
new NextAction("deep freeze", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode("frostbite on target",
NextAction::array(0, new NextAction("frostbolt", ACTION_NORMAL + 2),
new NextAction("deep freeze", ACTION_NORMAL + 1), nullptr)));
}
void FrostMageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("blizzard", ACTION_HIGH), nullptr)));
triggers.push_back(
new TriggerNode("light aoe", NextAction::array(0, new NextAction("cone of cold", ACTION_HIGH + 1), nullptr)));
} }

View File

@ -20,13 +20,4 @@ public:
NextAction** getDefaultActions() override; NextAction** getDefaultActions() override;
}; };
class FrostMageAoeStrategy : public CombatStrategy
{
public:
FrostMageAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frost aoe"; }
};
#endif #endif

View File

@ -4,7 +4,7 @@
*/ */
#include "GenericMageNonCombatStrategy.h" #include "GenericMageNonCombatStrategy.h"
#include "AiFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode> class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
@ -52,35 +52,23 @@ void GenericMageNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigg
{ {
NonCombatStrategy::InitTriggers(triggers); NonCombatStrategy::InitTriggers(triggers);
triggers.push_back( triggers.push_back(new TriggerNode("arcane intellect", NextAction::array(0, new NextAction("arcane intellect", 21.0f), nullptr)));
new TriggerNode("arcane intellect", NextAction::array(0, new NextAction("arcane intellect", 21.0f), nullptr))); triggers.push_back(new TriggerNode("no focus magic", NextAction::array(0, new NextAction("focus magic on party", 19.0f), nullptr)));
triggers.push_back(
new TriggerNode("no focus magic", NextAction::array(0, new NextAction("focus magic on party", 19.0f), nullptr)));
// triggers.push_back(new TriggerNode("no drink", NextAction::array(0, new NextAction("conjure water", 16.0f),
// nullptr))); triggers.push_back(new TriggerNode("no food", NextAction::array(0, new NextAction("conjure
// food", 15.0f), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr))); triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("no mana gem", NextAction::array(0, new NextAction("conjure mana gem", 20.0f), nullptr)));
} }
void MageBuffManaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageBuffManaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(new TriggerNode("mage armor", NextAction::array(0, new NextAction("mage armor", 19.0f), nullptr)));
new TriggerNode("mage armor", NextAction::array(0, new NextAction("mage armor", 19.0f), nullptr)));
} }
void MageBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(new TriggerNode("mage armor", NextAction::array(0, new NextAction("molten armor", 19.0f), nullptr)));
new TriggerNode("mage armor", NextAction::array(0, new NextAction("molten armor", 19.0f), nullptr)));
} }
void MageBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(new TriggerNode("arcane intellect on party", NextAction::array(0, new NextAction("arcane intellect on party", 20.0f), nullptr)));
new TriggerNode("arcane intellect on party",
NextAction::array(0, new NextAction("arcane intellect on party", 20.0f), nullptr)));
// triggers.push_back(new TriggerNode("give water", NextAction::array(0, new NextAction("give water", 14.0f),
// nullptr))); triggers.push_back(new TriggerNode("give food", NextAction::array(0, new NextAction("give
// food", 13.0f), nullptr)));
} }

View File

@ -4,7 +4,7 @@
*/ */
#include "GenericMageStrategy.h" #include "GenericMageStrategy.h"
#include "AiFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RangedCombatStrategy.h" #include "RangedCombatStrategy.h"
@ -160,49 +160,119 @@ void GenericMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
RangedCombatStrategy::InitTriggers(triggers); RangedCombatStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", // Threat Triggers
// ACTION_MOVE + 9), nullptr))); triggers.push_back(new TriggerNode("high threat", NextAction::array(0, new NextAction("mirror image", 60.0f), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode("medium threat", NextAction::array(0, new NextAction("invisibility", 30.0f), nullptr)));
new TriggerNode("enemy is close", NextAction::array(0, new NextAction("frost nova", 50.0f), nullptr)));
triggers.push_back(
new TriggerNode("counterspell on enemy healer",
NextAction::array(0, new NextAction("counterspell on enemy healer", 40.0f), nullptr)));
triggers.push_back(
new TriggerNode("critical health", NextAction::array(0, new NextAction("ice block", 80.0f), nullptr)));
triggers.push_back(
new TriggerNode("spellsteal", NextAction::array(0, new NextAction("spellsteal", 40.0f), nullptr)));
triggers.push_back(
new TriggerNode("medium threat", NextAction::array(0, new NextAction("invisibility", 60.0f), nullptr)));
triggers.push_back(
new TriggerNode("low mana", NextAction::array(0, new NextAction("evocation", ACTION_EMERGENCY + 5), nullptr)));
triggers.push_back(
new TriggerNode("fire ward", NextAction::array(0, new NextAction("fire ward", ACTION_EMERGENCY), nullptr)));
triggers.push_back(
new TriggerNode("frost ward", NextAction::array(0, new NextAction("frost ward", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", // Defensive Triggers
NextAction::array(0, new NextAction("blink back", ACTION_MOVE + 5), nullptr))); triggers.push_back(new TriggerNode("critical health", NextAction::array(0, new NextAction("ice block", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("mana shield", 85.0f), nullptr)));
triggers.push_back(new TriggerNode("fire ward", NextAction::array(0, new NextAction("fire ward", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("frost ward", NextAction::array(0, new NextAction("frost ward", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("enemy is close and no firestarter strategy", NextAction::array(0, new NextAction("frost nova", 50.0f), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell and no firestarter strategy", NextAction::array(0, new NextAction("blink back", 35.0f), nullptr)));
// Mana Threshold Triggers
Player* bot = botAI->GetBot();
if (bot->HasSpell(42985)) // Mana Sapphire
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana sapphire", 90.0f), nullptr)));
else if (bot->HasSpell(27101)) // Mana Emerald
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana emerald", 90.0f), nullptr)));
else if (bot->HasSpell(10054)) // Mana Ruby
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana ruby", 90.0f), nullptr)));
else if (bot->HasSpell(10053)) // Mana Citrine
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana citrine", 90.0f), nullptr)));
else if (bot->HasSpell(3552)) // Mana Jade
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana jade", 90.0f), nullptr)));
else if (bot->HasSpell(759)) // Mana Agate
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("use mana agate", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("medium mana", NextAction::array(0, new NextAction("mana potion", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("evocation", 90.0f), nullptr)));
// Counterspell / Spellsteal Triggers
triggers.push_back(new TriggerNode("spellsteal", NextAction::array(0, new NextAction("spellsteal", 40.0f), nullptr)));
triggers.push_back(new TriggerNode("counterspell on enemy healer", NextAction::array(0, new NextAction("counterspell on enemy healer", 40.0f), nullptr)));
} }
void MageCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(new TriggerNode("remove curse", NextAction::array(0, new NextAction("remove curse", 41.0f), nullptr)));
new TriggerNode("remove curse", NextAction::array(0, new NextAction("remove curse", 41.0f), nullptr))); triggers.push_back(new TriggerNode("remove curse on party", NextAction::array(0, new NextAction("remove curse on party", 40.0f), nullptr)));
triggers.push_back(new TriggerNode("remove curse on party",
NextAction::array(0, new NextAction("remove curse on party", 40.0f), nullptr)));
} }
void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 50.0f), nullptr))); Player* bot = botAI->GetBot();
triggers.push_back( int tab = AiFactory::GetPlayerSpecTab(bot);
new TriggerNode("presence of mind", NextAction::array(0, new NextAction("presence of mind", 42.0f), nullptr)));
// triggers.push_back(new TriggerNode("arcane power", NextAction::array(0, new NextAction("arcane power", 41.0f), nullptr))); if (tab == 0) // Arcane
triggers.push_back( {
new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 41.0f), nullptr))); triggers.push_back(new TriggerNode("arcane power", NextAction::array(0, new NextAction("arcane power", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 28.5f), nullptr)));
triggers.push_back(new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 28.0f), nullptr)));
}
else if (tab == 1)
{
if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/)
{ // Frostfire
triggers.push_back(new TriggerNode("combustion", NextAction::array(0, new NextAction("combustion", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 17.5f), nullptr)));
triggers.push_back(new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 17.0f), nullptr)));
}
else
{ // Fire
triggers.push_back(new TriggerNode("combustion", NextAction::array(0, new NextAction("combustion", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 17.5f), nullptr)));
}
}
else if (tab == 2) // Frost
{
triggers.push_back(new TriggerNode("cold snap", NextAction::array(0, new NextAction("cold snap", 28.0f), nullptr)));
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 27.5f), nullptr)));
triggers.push_back(new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 26.0f), nullptr)));
}
} }
void MageCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MageCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back(new TriggerNode("polymorph", NextAction::array(0, new NextAction("polymorph", 30.0f), nullptr))); triggers.push_back(new TriggerNode("polymorph", NextAction::array(0, new NextAction("polymorph", 30.0f), nullptr)));
} }
void MageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("blizzard channel check", NextAction::array(0, new NextAction("cancel channel", 26.0f), nullptr)));
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == 0) // Arcane
{
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", NextAction::array(0, new NextAction("blizzard", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0,
new NextAction("flamestrike", 23.0f),
new NextAction("blizzard", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("light aoe", NextAction::array(0, new NextAction("arcane explosion", 21.0f), nullptr)));
}
else if (tab == 1) // Fire and Frostfire
{
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0,
new NextAction("dragon's breath", 39.0f),
new NextAction("blast wave", 38.0f),
new NextAction("flamestrike", 23.0f),
new NextAction("blizzard", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", NextAction::array(0, new NextAction("blizzard", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("firestarter", NextAction::array(0, new NextAction("flamestrike", 40.0f), nullptr)));
triggers.push_back(new TriggerNode("living bomb on attackers", NextAction::array(0, new NextAction("living bomb on attackers", 21.0f), nullptr)));
}
else if (tab == 2) // Frost
{
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", NextAction::array(0, new NextAction("blizzard", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0,
new NextAction("flamestrike", 23.0f),
new NextAction("blizzard", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("light aoe", NextAction::array(0, new NextAction("cone of cold", 21.0f), nullptr)));
}
}

View File

@ -51,4 +51,13 @@ public:
std::string const getName() override { return "cc"; } std::string const getName() override { return "cc"; }
}; };
class MageAoeStrategy : public CombatStrategy
{
public:
MageAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "aoe"; }
};
#endif #endif

View File

@ -5,7 +5,7 @@
#include "MageActions.h" #include "MageActions.h"
#include <cmath> #include <cmath>
#include "UseItemAction.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
@ -13,6 +13,42 @@
Value<Unit*>* CastPolymorphAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); } Value<Unit*>* CastPolymorphAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
bool UseManaSapphireAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(33312, false) > 0; // Mana Sapphire
}
bool UseManaEmeraldAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(22044, false) > 0; // Mana Emerald
}
bool UseManaRubyAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(8008, false) > 0; // Mana Ruby
}
bool UseManaCitrineAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(8007, false) > 0; // Mana Citrine
}
bool UseManaJadeAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(5513, false) > 0; // Mana Jade
}
bool UseManaAgateAction::isUseful()
{
Player* bot = botAI->GetBot();
return AI_VALUE2(bool, "combat", "self target") && bot->GetItemCount(5514, false) > 0; // Mana Agate
}
bool CastFrostNovaAction::isUseful() bool CastFrostNovaAction::isUseful()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
@ -106,3 +142,13 @@ bool CastBlinkBackAction::Execute(Event event)
bot->SetOrientation(bot->GetAngle(target) + M_PI); bot->SetOrientation(bot->GetAngle(target) + M_PI);
return CastSpellAction::Execute(event); return CastSpellAction::Execute(event);
} }
bool CancelChannelAction::Execute(Event event)
{
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
return true;
}
return false;
}

View File

@ -8,11 +8,280 @@
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "UseItemAction.h"
class PlayerbotAI; class PlayerbotAI;
BUFF_ACTION(CastFireWardAction, "fire ward"); // Buff and Out of Combat Actions
BUFF_ACTION(CastFrostWardAction, "frost ward");
class CastMoltenArmorAction : public CastBuffSpellAction
{
public:
CastMoltenArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "molten armor") {}
};
class CastMageArmorAction : public CastBuffSpellAction
{
public:
CastMageArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mage armor") {}
};
class CastIceArmorAction : public CastBuffSpellAction
{
public:
CastIceArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice armor") {}
};
class CastFrostArmorAction : public CastBuffSpellAction
{
public:
CastFrostArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "frost armor") {}
};
class CastArcaneIntellectAction : public CastBuffSpellAction
{
public:
CastArcaneIntellectAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane intellect") {}
};
class CastArcaneIntellectOnPartyAction : public BuffOnPartyAction
{
public:
CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "arcane intellect") {}
};
class CastFocusMagicOnPartyAction : public CastSpellAction
{
public:
CastFocusMagicOnPartyAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "focus magic") {}
Unit* GetTarget() override;
};
class CastSummonWaterElementalAction : public CastBuffSpellAction
{
public:
CastSummonWaterElementalAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "summon water elemental") {}
};
// Boost Actions
class CastCombustionAction : public CastBuffSpellAction
{
public:
CastCombustionAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "combustion") {}
};
class CastArcanePowerAction : public CastBuffSpellAction
{
public:
CastArcanePowerAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane power") {}
};
class CastPresenceOfMindAction : public CastBuffSpellAction
{
public:
CastPresenceOfMindAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "presence of mind") {}
};
class CastIcyVeinsAction : public CastBuffSpellAction
{
public:
CastIcyVeinsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "icy veins") {}
};
class CastColdSnapAction : public CastBuffSpellAction
{
public:
CastColdSnapAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cold snap") {}
};
// Defensive Actions
class CastFireWardAction : public CastBuffSpellAction
{
public:
CastFireWardAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "fire ward") {}
};
class CastFrostWardAction : public CastBuffSpellAction
{
public:
CastFrostWardAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "frost ward") {}
};
class CastIceBarrierAction : public CastBuffSpellAction
{
public:
CastIceBarrierAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice barrier") {}
};
class CastInvisibilityAction : public CastBuffSpellAction
{
public:
CastInvisibilityAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "invisibility") {}
};
class CastIceBlockAction : public CastBuffSpellAction
{
public:
CastIceBlockAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice block") {}
};
class CastMirrorImageAction : public CastBuffSpellAction
{
public:
CastMirrorImageAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mirror image") {}
};
class CastBlinkBackAction : public CastSpellAction
{
public:
CastBlinkBackAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "blink") {}
bool Execute(Event event) override;
};
class CastManaShieldAction : public CastBuffSpellAction
{
public:
CastManaShieldAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mana shield") {}
};
// Utility Actions
class CastEvocationAction : public CastSpellAction
{
public:
CastEvocationAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "evocation") {}
std::string const GetTargetName() override { return "self target"; }
};
class CastConjureManaGemAction : public CastBuffSpellAction
{
public:
CastConjureManaGemAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "conjure mana gem") {}
};
class CastConjureFoodAction : public CastBuffSpellAction
{
public:
CastConjureFoodAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "conjure food") {}
};
class CastConjureWaterAction : public CastBuffSpellAction
{
public:
CastConjureWaterAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "conjure water") {}
};
class UseManaSapphireAction : public UseItemAction
{
public:
UseManaSapphireAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana sapphire") {}
bool isUseful() override;
};
class UseManaEmeraldAction : public UseItemAction
{
public:
UseManaEmeraldAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana emerald") {}
bool isUseful() override;
};
class UseManaRubyAction : public UseItemAction
{
public:
UseManaRubyAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana ruby") {}
bool isUseful() override;
};
class UseManaCitrineAction : public UseItemAction
{
public:
UseManaCitrineAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana citrine") {}
bool isUseful() override;
};
class UseManaJadeAction : public UseItemAction
{
public:
UseManaJadeAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana jade") {}
bool isUseful() override;
};
class UseManaAgateAction : public UseItemAction
{
public:
UseManaAgateAction(PlayerbotAI* botAI) : UseItemAction(botAI, "mana agate") {}
bool isUseful() override;
};
// CC, Interrupt, and Dispel Actions
class CastPolymorphAction : public CastBuffSpellAction
{
public:
CastPolymorphAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "polymorph") {}
Value<Unit*>* GetTargetValue() override;
};
class CastSpellstealAction : public CastSpellAction
{
public:
CastSpellstealAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "spellsteal") {}
};
class CastCounterspellAction : public CastSpellAction
{
public:
CastCounterspellAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "counterspell") {}
};
class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
{
public:
CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI) : CastSpellOnEnemyHealerAction(botAI, "counterspell") {}
};
class CastFrostNovaAction : public CastSpellAction
{
public:
CastFrostNovaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "frost nova") {}
bool isUseful() override;
};
class CastDeepFreezeAction : public CastSpellAction
{
public:
CastDeepFreezeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "deep freeze") {}
bool isPossible() override { return true; }
};
class CastRemoveCurseAction : public CastCureSpellAction
{
public:
CastRemoveCurseAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "remove curse") {}
};
class CastRemoveLesserCurseAction : public CastCureSpellAction
{
public:
CastRemoveLesserCurseAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "remove lesser curse") {}
};
class CastRemoveCurseOnPartyAction : public CurePartyMemberAction
{
public:
CastRemoveCurseOnPartyAction(PlayerbotAI* botAI) : CurePartyMemberAction(botAI, "remove curse", DISPEL_CURSE) {}
};
class CastRemoveLesserCurseOnPartyAction : public CurePartyMemberAction
{
public:
CastRemoveLesserCurseOnPartyAction(PlayerbotAI* botAI)
: CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE)
{
}
};
// Damage and Debuff Actions
class CastFireballAction : public CastSpellAction class CastFireballAction : public CastSpellAction
{ {
@ -57,18 +326,26 @@ public:
CastPyroblastAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "pyroblast") {} CastPyroblastAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "pyroblast") {}
}; };
class CastFlamestrikeAction : public CastDebuffSpellAction class CastLivingBombAction : public CastDebuffSpellAction
{ {
public: public:
CastFlamestrikeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "flamestrike", true, 0.0f) {} CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
}; };
class CastFrostNovaAction : public CastSpellAction class CastLivingBombOnAttackersAction : public CastDebuffSpellOnAttackerAction
{ {
public: public:
CastFrostNovaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "frost nova") {} CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {}
bool isUseful() override; bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
}; };
class CastFrostboltAction : public CastSpellAction class CastFrostboltAction : public CastSpellAction
@ -89,12 +366,6 @@ public:
CastIceLanceAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "ice lance") {} CastIceLanceAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "ice lance") {}
}; };
class CastDeepFreezeAction : public CastSpellAction
{
public:
CastDeepFreezeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "deep freeze") {}
};
class CastBlizzardAction : public CastSpellAction class CastBlizzardAction : public CastSpellAction
{ {
public: public:
@ -110,143 +381,11 @@ public:
bool isUseful() override; bool isUseful() override;
}; };
class CastArcaneIntellectAction : public CastBuffSpellAction class CastFlamestrikeAction : public CastDebuffSpellAction
{ {
public: public:
CastArcaneIntellectAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane intellect") {} CastFlamestrikeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "flamestrike", true, 0.0f) {}
}; ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
class CastArcaneIntellectOnPartyAction : public BuffOnPartyAction
{
public:
CastArcaneIntellectOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "arcane intellect") {}
};
class CastRemoveCurseAction : public CastCureSpellAction
{
public:
CastRemoveCurseAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "remove curse") {}
};
class CastRemoveLesserCurseAction : public CastCureSpellAction
{
public:
CastRemoveLesserCurseAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "remove lesser curse") {}
};
class CastIcyVeinsAction : public CastBuffSpellAction
{
public:
CastIcyVeinsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "icy veins") {}
};
class CastColdSnapAction : public CastBuffSpellAction
{
public:
CastColdSnapAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cold snap") {}
};
class CastIceBarrierAction : public CastBuffSpellAction
{
public:
CastIceBarrierAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice barrier") {}
};
class CastSummonWaterElementalAction : public CastBuffSpellAction
{
public:
CastSummonWaterElementalAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "summon water elemental") {}
};
class CastCombustionAction : public CastBuffSpellAction
{
public:
CastCombustionAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "combustion") {}
};
BEGIN_SPELL_ACTION(CastCounterspellAction, "counterspell")
END_SPELL_ACTION()
class CastRemoveCurseOnPartyAction : public CurePartyMemberAction
{
public:
CastRemoveCurseOnPartyAction(PlayerbotAI* botAI) : CurePartyMemberAction(botAI, "remove curse", DISPEL_CURSE) {}
};
class CastRemoveLesserCurseOnPartyAction : public CurePartyMemberAction
{
public:
CastRemoveLesserCurseOnPartyAction(PlayerbotAI* botAI)
: CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE)
{
}
};
class CastConjureFoodAction : public CastBuffSpellAction
{
public:
CastConjureFoodAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "conjure food") {}
};
class CastConjureWaterAction : public CastBuffSpellAction
{
public:
CastConjureWaterAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "conjure water") {}
};
class CastIceBlockAction : public CastBuffSpellAction
{
public:
CastIceBlockAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice block") {}
};
class CastMoltenArmorAction : public CastBuffSpellAction
{
public:
CastMoltenArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "molten armor") {}
};
class CastMageArmorAction : public CastBuffSpellAction
{
public:
CastMageArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mage armor") {}
};
class CastIceArmorAction : public CastBuffSpellAction
{
public:
CastIceArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ice armor") {}
};
class CastFrostArmorAction : public CastBuffSpellAction
{
public:
CastFrostArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "frost armor") {}
};
class CastPolymorphAction : public CastBuffSpellAction
{
public:
CastPolymorphAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "polymorph") {}
Value<Unit*>* GetTargetValue() override;
};
class CastSpellstealAction : public CastSpellAction
{
public:
CastSpellstealAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "spellsteal") {}
};
class CastLivingBombAction : public CastDebuffSpellAction
{
public:
CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {}
};
class CastLivingBombOnAttackersAction : public CastDebuffSpellOnAttackerAction
{
public:
CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {}
}; };
class CastDragonsBreathAction : public CastSpellAction class CastDragonsBreathAction : public CastSpellAction
@ -265,55 +404,12 @@ public:
bool isUseful() override; bool isUseful() override;
}; };
class CastInvisibilityAction : public CastBuffSpellAction class CancelChannelAction : public Action
{ {
public: public:
CastInvisibilityAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "invisibility") {} CancelChannelAction(PlayerbotAI* botAI) : Action(botAI, "cancel channel") {}
};
class CastEvocationAction : public CastSpellAction
{
public:
CastEvocationAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "evocation") {}
std::string const GetTargetName() override { return "self target"; }
};
class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
{
public:
CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI) : CastSpellOnEnemyHealerAction(botAI, "counterspell") {}
};
class CastArcanePowerAction : public CastBuffSpellAction
{
public:
CastArcanePowerAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane power") {}
};
class CastPresenceOfMindAction : public CastBuffSpellAction
{
public:
CastPresenceOfMindAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "presence of mind") {}
};
class CastMirrorImageAction : public CastBuffSpellAction
{
public:
CastMirrorImageAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mirror image") {}
};
class CastFocusMagicOnPartyAction : public CastSpellAction
{
public:
CastFocusMagicOnPartyAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "focus magic") {}
Unit* GetTarget() override;
};
class CastBlinkBackAction : public CastSpellAction
{
public:
CastBlinkBackAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "blink") {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
#endif #endif

View File

@ -4,7 +4,6 @@
*/ */
#include "MageAiObjectContext.h" #include "MageAiObjectContext.h"
#include "ArcaneMageStrategy.h" #include "ArcaneMageStrategy.h"
#include "FireMageStrategy.h" #include "FireMageStrategy.h"
#include "FrostFireMageStrategy.h" #include "FrostFireMageStrategy.h"
@ -23,27 +22,23 @@ public:
{ {
creators["nc"] = &MageStrategyFactoryInternal::nc; creators["nc"] = &MageStrategyFactoryInternal::nc;
creators["pull"] = &MageStrategyFactoryInternal::pull; creators["pull"] = &MageStrategyFactoryInternal::pull;
creators["fire aoe"] = &MageStrategyFactoryInternal::fire_aoe; creators["aoe"] = &MageStrategyFactoryInternal::aoe;
creators["frostfire aoe"] = &MageStrategyFactoryInternal::frostfire_aoe;
creators["frost aoe"] = &MageStrategyFactoryInternal::frost_aoe;
creators["arcane aoe"] = &MageStrategyFactoryInternal::arcane_aoe;
creators["cure"] = &MageStrategyFactoryInternal::cure; creators["cure"] = &MageStrategyFactoryInternal::cure;
creators["buff"] = &MageStrategyFactoryInternal::buff; creators["buff"] = &MageStrategyFactoryInternal::buff;
creators["boost"] = &MageStrategyFactoryInternal::boost; creators["boost"] = &MageStrategyFactoryInternal::boost;
creators["cc"] = &MageStrategyFactoryInternal::cc; creators["cc"] = &MageStrategyFactoryInternal::cc;
creators["firestarter"] = &MageStrategyFactoryInternal::firestarter;
} }
private: private:
static Strategy* nc(PlayerbotAI* botAI) { return new GenericMageNonCombatStrategy(botAI); } static Strategy* nc(PlayerbotAI* botAI) { return new GenericMageNonCombatStrategy(botAI); }
static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "shoot"); } static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "shoot"); }
static Strategy* fire_aoe(PlayerbotAI* botAI) { return new FireMageAoeStrategy(botAI); } static Strategy* aoe(PlayerbotAI* botAI) { return new MageAoeStrategy(botAI); }
static Strategy* frostfire_aoe(PlayerbotAI* botAI) { return new FrostFireMageAoeStrategy(botAI); }
static Strategy* frost_aoe(PlayerbotAI* botAI) { return new FrostMageAoeStrategy(botAI); }
static Strategy* arcane_aoe(PlayerbotAI* botAI) { return new ArcaneMageAoeStrategy(botAI); }
static Strategy* cure(PlayerbotAI* botAI) { return new MageCureStrategy(botAI); } static Strategy* cure(PlayerbotAI* botAI) { return new MageCureStrategy(botAI); }
static Strategy* buff(PlayerbotAI* botAI) { return new MageBuffStrategy(botAI); } static Strategy* buff(PlayerbotAI* botAI) { return new MageBuffStrategy(botAI); }
static Strategy* boost(PlayerbotAI* botAI) { return new MageBoostStrategy(botAI); } static Strategy* boost(PlayerbotAI* botAI) { return new MageBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new MageCcStrategy(botAI); } static Strategy* cc(PlayerbotAI* botAI) { return new MageCcStrategy(botAI); }
static Strategy* firestarter(PlayerbotAI* botAI) { return new FirestarterStrategy(botAI); }
}; };
class MageCombatStrategyFactoryInternal : public NamedObjectContext<Strategy> class MageCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -86,8 +81,7 @@ public:
creators["fireball"] = &MageTriggerFactoryInternal::fireball; creators["fireball"] = &MageTriggerFactoryInternal::fireball;
creators["pyroblast"] = &MageTriggerFactoryInternal::pyroblast; creators["pyroblast"] = &MageTriggerFactoryInternal::pyroblast;
creators["combustion"] = &MageTriggerFactoryInternal::combustion; creators["combustion"] = &MageTriggerFactoryInternal::combustion;
creators["fingers of frost single"] = &MageTriggerFactoryInternal::fingers_of_frost_single; creators["fingers of frost"] = &MageTriggerFactoryInternal::fingers_of_frost;
creators["fingers of frost double"] = &MageTriggerFactoryInternal::fingers_of_frost_double;
creators["brain freeze"] = &MageTriggerFactoryInternal::brain_freeze; creators["brain freeze"] = &MageTriggerFactoryInternal::brain_freeze;
creators["icy veins"] = &MageTriggerFactoryInternal::icy_veins; creators["icy veins"] = &MageTriggerFactoryInternal::icy_veins;
creators["cold snap"] = &MageTriggerFactoryInternal::cold_snap; creators["cold snap"] = &MageTriggerFactoryInternal::cold_snap;
@ -102,6 +96,7 @@ public:
creators["spellsteal"] = &MageTriggerFactoryInternal::spellsteal; creators["spellsteal"] = &MageTriggerFactoryInternal::spellsteal;
creators["hot streak"] = &MageTriggerFactoryInternal::hot_streak; creators["hot streak"] = &MageTriggerFactoryInternal::hot_streak;
creators["living bomb"] = &MageTriggerFactoryInternal::living_bomb; creators["living bomb"] = &MageTriggerFactoryInternal::living_bomb;
creators["living bomb on attackers"] = &MageTriggerFactoryInternal::living_bomb_on_attackers;
creators["missile barrage"] = &MageTriggerFactoryInternal::missile_barrage; creators["missile barrage"] = &MageTriggerFactoryInternal::missile_barrage;
creators["arcane blast"] = &MageTriggerFactoryInternal::arcane_blast; creators["arcane blast"] = &MageTriggerFactoryInternal::arcane_blast;
creators["counterspell on enemy healer"] = &MageTriggerFactoryInternal::counterspell_enemy_healer; creators["counterspell on enemy healer"] = &MageTriggerFactoryInternal::counterspell_enemy_healer;
@ -115,6 +110,20 @@ public:
creators["frostbite on target"] = &MageTriggerFactoryInternal::frostbite_on_target; creators["frostbite on target"] = &MageTriggerFactoryInternal::frostbite_on_target;
creators["no focus magic"] = &MageTriggerFactoryInternal::no_focus_magic; creators["no focus magic"] = &MageTriggerFactoryInternal::no_focus_magic;
creators["frostfire bolt"] = &MageTriggerFactoryInternal::frostfire_bolt; creators["frostfire bolt"] = &MageTriggerFactoryInternal::frostfire_bolt;
creators["firestarter"] = &MageTriggerFactoryInternal::firestarter;
creators["improved scorch"] = &MageTriggerFactoryInternal::improved_scorch;
creators["flamestrike nearby"] = &MageTriggerFactoryInternal::flamestrike_nearby;
creators["flamestrike active and medium aoe"] = &MageTriggerFactoryInternal::flamestrike_blizzard;
creators["arcane blast 4 stacks and missile barrage"] = &MageTriggerFactoryInternal::arcane_blast_4_stacks_and_missile_barrage;
creators["icy veins on cd"] = &MageTriggerFactoryInternal::icy_veins_on_cd;
creators["deep freeze on cd"] = &MageTriggerFactoryInternal::deep_freeze_on_cd;
creators["no mana gem"] = &MageTriggerFactoryInternal::NoManaGem;
creators["blizzard channel check"] = &MageTriggerFactoryInternal::blizzard_channel_check;
creators["blast wave off cd"] = &MageTriggerFactoryInternal::blast_wave_off_cd;
creators["blast wave off cd and medium aoe"] = &MageTriggerFactoryInternal::blast_wave_off_cd_and_medium_aoe;
creators["no firestarter strategy"] = &MageTriggerFactoryInternal::no_firestarter_strategy;
creators["enemy is close and no firestarter strategy"] = &MageTriggerFactoryInternal::enemy_is_close_and_no_firestarter_strategy;
creators["enemy too close for spell and no firestarter strategy"] = &MageTriggerFactoryInternal::enemy_too_close_for_spell_and_no_firestarter_strategy;
} }
private: private:
@ -126,8 +135,7 @@ private:
static Trigger* fireball(PlayerbotAI* botAI) { return new FireballTrigger(botAI); } static Trigger* fireball(PlayerbotAI* botAI) { return new FireballTrigger(botAI); }
static Trigger* pyroblast(PlayerbotAI* botAI) { return new PyroblastTrigger(botAI); } static Trigger* pyroblast(PlayerbotAI* botAI) { return new PyroblastTrigger(botAI); }
static Trigger* combustion(PlayerbotAI* botAI) { return new CombustionTrigger(botAI); } static Trigger* combustion(PlayerbotAI* botAI) { return new CombustionTrigger(botAI); }
static Trigger* fingers_of_frost_single(PlayerbotAI* botAI) { return new FingersOfFrostSingleTrigger(botAI); } static Trigger* fingers_of_frost(PlayerbotAI* botAI) { return new FingersOfFrostTrigger(botAI); }
static Trigger* fingers_of_frost_double(PlayerbotAI* botAI) { return new FingersOfFrostDoubleTrigger(botAI); }
static Trigger* brain_freeze(PlayerbotAI* botAI) { return new BrainFreezeTrigger(botAI); } static Trigger* brain_freeze(PlayerbotAI* botAI) { return new BrainFreezeTrigger(botAI); }
static Trigger* icy_veins(PlayerbotAI* botAI) { return new IcyVeinsTrigger(botAI); } static Trigger* icy_veins(PlayerbotAI* botAI) { return new IcyVeinsTrigger(botAI); }
static Trigger* cold_snap(PlayerbotAI* botAI) { return new ColdSnapTrigger(botAI); } static Trigger* cold_snap(PlayerbotAI* botAI) { return new ColdSnapTrigger(botAI); }
@ -141,6 +149,7 @@ private:
static Trigger* polymorph(PlayerbotAI* botAI) { return new PolymorphTrigger(botAI); } static Trigger* polymorph(PlayerbotAI* botAI) { return new PolymorphTrigger(botAI); }
static Trigger* spellsteal(PlayerbotAI* botAI) { return new SpellstealTrigger(botAI); } static Trigger* spellsteal(PlayerbotAI* botAI) { return new SpellstealTrigger(botAI); }
static Trigger* living_bomb(PlayerbotAI* botAI) { return new LivingBombTrigger(botAI); } static Trigger* living_bomb(PlayerbotAI* botAI) { return new LivingBombTrigger(botAI); }
static Trigger* living_bomb_on_attackers(PlayerbotAI* botAI) { return new LivingBombOnAttackersTrigger(botAI); }
static Trigger* missile_barrage(PlayerbotAI* botAI) { return new MissileBarrageTrigger(botAI); } static Trigger* missile_barrage(PlayerbotAI* botAI) { return new MissileBarrageTrigger(botAI); }
static Trigger* arcane_blast(PlayerbotAI* botAI) { return new ArcaneBlastTrigger(botAI); } static Trigger* arcane_blast(PlayerbotAI* botAI) { return new ArcaneBlastTrigger(botAI); }
static Trigger* counterspell_enemy_healer(PlayerbotAI* botAI) { return new CounterspellEnemyHealerTrigger(botAI); } static Trigger* counterspell_enemy_healer(PlayerbotAI* botAI) { return new CounterspellEnemyHealerTrigger(botAI); }
@ -150,6 +159,20 @@ private:
static Trigger* frostbite_on_target(PlayerbotAI* botAI) { return new FrostbiteOnTargetTrigger(botAI); } static Trigger* frostbite_on_target(PlayerbotAI* botAI) { return new FrostbiteOnTargetTrigger(botAI); }
static Trigger* no_focus_magic(PlayerbotAI* botAI) { return new NoFocusMagicTrigger(botAI); } static Trigger* no_focus_magic(PlayerbotAI* botAI) { return new NoFocusMagicTrigger(botAI); }
static Trigger* frostfire_bolt(PlayerbotAI* botAI) { return new FrostfireBoltTrigger(botAI); } static Trigger* frostfire_bolt(PlayerbotAI* botAI) { return new FrostfireBoltTrigger(botAI); }
static Trigger* improved_scorch(PlayerbotAI* botAI) { return new ImprovedScorchTrigger(botAI); }
static Trigger* firestarter(PlayerbotAI* botAI) { return new FirestarterTrigger(botAI); }
static Trigger* flamestrike_nearby(PlayerbotAI* botAI) { return new FlamestrikeNearbyTrigger(botAI); }
static Trigger* flamestrike_blizzard(PlayerbotAI* botAI) { return new FlamestrikeBlizzardTrigger(botAI); }
static Trigger* arcane_blast_4_stacks_and_missile_barrage(PlayerbotAI* botAI) { return new ArcaneBlast4StacksAndMissileBarrageTrigger(botAI); }
static Trigger* icy_veins_on_cd(PlayerbotAI* botAI) { return new IcyVeinsCooldownTrigger(botAI); }
static Trigger* deep_freeze_on_cd(PlayerbotAI* botAI) { return new DeepFreezeCooldownTrigger(botAI); }
static Trigger* NoManaGem(PlayerbotAI* botAI) { return new NoManaGemTrigger(botAI); }
static Trigger* blizzard_channel_check(PlayerbotAI* botAI) { return new BlizzardChannelCheckTrigger(botAI); }
static Trigger* blast_wave_off_cd(PlayerbotAI* botAI) { return new BlastWaveOffCdTrigger(botAI); }
static Trigger* blast_wave_off_cd_and_medium_aoe(PlayerbotAI* botAI) { return new BlastWaveOffCdTriggerAndMediumAoeTrigger(botAI); }
static Trigger* no_firestarter_strategy(PlayerbotAI* botAI) { return new NoFirestarterStrategyTrigger(botAI); }
static Trigger* enemy_is_close_and_no_firestarter_strategy(PlayerbotAI* botAI) { return new EnemyIsCloseAndNoFirestarterStrategyTrigger(botAI); }
static Trigger* enemy_too_close_for_spell_and_no_firestarter_strategy(PlayerbotAI* botAI) { return new EnemyTooCloseForSpellAndNoFirestarterStrategyTrigger(botAI); }
}; };
class MageAiObjectContextInternal : public NamedObjectContext<Action> class MageAiObjectContextInternal : public NamedObjectContext<Action>
@ -170,6 +193,7 @@ public:
creators["arcane intellect on party"] = &MageAiObjectContextInternal::arcane_intellect_on_party; creators["arcane intellect on party"] = &MageAiObjectContextInternal::arcane_intellect_on_party;
creators["conjure water"] = &MageAiObjectContextInternal::conjure_water; creators["conjure water"] = &MageAiObjectContextInternal::conjure_water;
creators["conjure food"] = &MageAiObjectContextInternal::conjure_food; creators["conjure food"] = &MageAiObjectContextInternal::conjure_food;
creators["conjure mana gem"] = &MageAiObjectContextInternal::conjure_mana_gem;
creators["molten armor"] = &MageAiObjectContextInternal::molten_armor; creators["molten armor"] = &MageAiObjectContextInternal::molten_armor;
creators["mage armor"] = &MageAiObjectContextInternal::mage_armor; creators["mage armor"] = &MageAiObjectContextInternal::mage_armor;
creators["ice armor"] = &MageAiObjectContextInternal::ice_armor; creators["ice armor"] = &MageAiObjectContextInternal::ice_armor;
@ -207,6 +231,14 @@ public:
creators["mirror image"] = &MageAiObjectContextInternal::mirror_image; creators["mirror image"] = &MageAiObjectContextInternal::mirror_image;
creators["focus magic on party"] = &MageAiObjectContextInternal::focus_magic_on_party; creators["focus magic on party"] = &MageAiObjectContextInternal::focus_magic_on_party;
creators["blink back"] = &MageAiObjectContextInternal::blink_back; creators["blink back"] = &MageAiObjectContextInternal::blink_back;
creators["use mana sapphire"] = &MageAiObjectContextInternal::use_mana_sapphire;
creators["use mana emerald"] = &MageAiObjectContextInternal::use_mana_emerald;
creators["use mana ruby"] = &MageAiObjectContextInternal::use_mana_ruby;
creators["use mana citrine"] = &MageAiObjectContextInternal::use_mana_citrine;
creators["use mana jade"] = &MageAiObjectContextInternal::use_mana_jade;
creators["use mana agate"] = &MageAiObjectContextInternal::use_mana_agate;
creators["cancel channel"] = &MageAiObjectContextInternal::cancel_channel;
creators["mana shield"] = &MageAiObjectContextInternal::mana_shield;
} }
private: private:
@ -228,6 +260,7 @@ private:
static Action* arcane_intellect_on_party(PlayerbotAI* botAI) { return new CastArcaneIntellectOnPartyAction(botAI); } static Action* arcane_intellect_on_party(PlayerbotAI* botAI) { return new CastArcaneIntellectOnPartyAction(botAI); }
static Action* conjure_water(PlayerbotAI* botAI) { return new CastConjureWaterAction(botAI); } static Action* conjure_water(PlayerbotAI* botAI) { return new CastConjureWaterAction(botAI); }
static Action* conjure_food(PlayerbotAI* botAI) { return new CastConjureFoodAction(botAI); } static Action* conjure_food(PlayerbotAI* botAI) { return new CastConjureFoodAction(botAI); }
static Action* conjure_mana_gem(PlayerbotAI* botAI) { return new CastConjureManaGemAction(botAI); }
static Action* molten_armor(PlayerbotAI* botAI) { return new CastMoltenArmorAction(botAI); } static Action* molten_armor(PlayerbotAI* botAI) { return new CastMoltenArmorAction(botAI); }
static Action* mage_armor(PlayerbotAI* botAI) { return new CastMageArmorAction(botAI); } static Action* mage_armor(PlayerbotAI* botAI) { return new CastMageArmorAction(botAI); }
static Action* ice_armor(PlayerbotAI* botAI) { return new CastIceArmorAction(botAI); } static Action* ice_armor(PlayerbotAI* botAI) { return new CastIceArmorAction(botAI); }
@ -241,10 +274,7 @@ private:
static Action* remove_curse(PlayerbotAI* botAI) { return new CastRemoveCurseAction(botAI); } static Action* remove_curse(PlayerbotAI* botAI) { return new CastRemoveCurseAction(botAI); }
static Action* remove_curse_on_party(PlayerbotAI* botAI) { return new CastRemoveCurseOnPartyAction(botAI); } static Action* remove_curse_on_party(PlayerbotAI* botAI) { return new CastRemoveCurseOnPartyAction(botAI); }
static Action* remove_lesser_curse(PlayerbotAI* botAI) { return new CastRemoveLesserCurseAction(botAI); } static Action* remove_lesser_curse(PlayerbotAI* botAI) { return new CastRemoveLesserCurseAction(botAI); }
static Action* remove_lesser_curse_on_party(PlayerbotAI* botAI) static Action* remove_lesser_curse_on_party(PlayerbotAI* botAI) { return new CastRemoveLesserCurseOnPartyAction(botAI); }
{
return new CastRemoveLesserCurseOnPartyAction(botAI);
}
static Action* icy_veins(PlayerbotAI* botAI) { return new CastIcyVeinsAction(botAI); } static Action* icy_veins(PlayerbotAI* botAI) { return new CastIcyVeinsAction(botAI); }
static Action* cold_snap(PlayerbotAI* botAI) { return new CastColdSnapAction(botAI); } static Action* cold_snap(PlayerbotAI* botAI) { return new CastColdSnapAction(botAI); }
static Action* ice_barrier(PlayerbotAI* botAI) { return new CastIceBarrierAction(botAI); } static Action* ice_barrier(PlayerbotAI* botAI) { return new CastIceBarrierAction(botAI); }
@ -259,13 +289,18 @@ private:
static Action* blast_wave(PlayerbotAI* botAI) { return new CastBlastWaveAction(botAI); } static Action* blast_wave(PlayerbotAI* botAI) { return new CastBlastWaveAction(botAI); }
static Action* invisibility(PlayerbotAI* botAI) { return new CastInvisibilityAction(botAI); } static Action* invisibility(PlayerbotAI* botAI) { return new CastInvisibilityAction(botAI); }
static Action* evocation(PlayerbotAI* botAI) { return new CastEvocationAction(botAI); } static Action* evocation(PlayerbotAI* botAI) { return new CastEvocationAction(botAI); }
static Action* counterspell_on_enemy_healer(PlayerbotAI* botAI) static Action* counterspell_on_enemy_healer(PlayerbotAI* botAI) { return new CastCounterspellOnEnemyHealerAction(botAI); }
{
return new CastCounterspellOnEnemyHealerAction(botAI);
}
static Action* mirror_image(PlayerbotAI* botAI) { return new CastMirrorImageAction(botAI); } static Action* mirror_image(PlayerbotAI* botAI) { return new CastMirrorImageAction(botAI); }
static Action* focus_magic_on_party(PlayerbotAI* botAI) { return new CastFocusMagicOnPartyAction(botAI); } static Action* focus_magic_on_party(PlayerbotAI* botAI) { return new CastFocusMagicOnPartyAction(botAI); }
static Action* blink_back(PlayerbotAI* botAI) { return new CastBlinkBackAction(botAI); } static Action* blink_back(PlayerbotAI* botAI) { return new CastBlinkBackAction(botAI); }
static Action* use_mana_sapphire(PlayerbotAI* botAI) { return new UseManaSapphireAction(botAI); }
static Action* use_mana_emerald(PlayerbotAI* botAI) { return new UseManaEmeraldAction(botAI); }
static Action* use_mana_ruby(PlayerbotAI* botAI) { return new UseManaRubyAction(botAI); }
static Action* use_mana_citrine(PlayerbotAI* botAI) { return new UseManaCitrineAction(botAI); }
static Action* use_mana_jade(PlayerbotAI* botAI) { return new UseManaJadeAction(botAI); }
static Action* use_mana_agate(PlayerbotAI* botAI) { return new UseManaAgateAction(botAI); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
static Action* mana_shield(PlayerbotAI* botAI) { return new CastManaShieldAction(botAI); }
}; };
SharedNamedObjectContextList<Strategy> MageAiObjectContext::sharedStrategyContexts; SharedNamedObjectContextList<Strategy> MageAiObjectContext::sharedStrategyContexts;

View File

@ -4,9 +4,33 @@
*/ */
#include "MageTriggers.h" #include "MageTriggers.h"
#include "MageActions.h" #include "MageActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "Player.h"
#include "Spell.h"
#include "DynamicObject.h"
#include "Value.h"
#include "SpellAuraEffects.h"
#include "ServerFacade.h"
bool NoManaGemTrigger::IsActive()
{
static const std::vector<uint32> gemIds = {
33312, // Mana Sapphire
22044, // Mana Emerald
8008, // Mana Ruby
8007, // Mana Citrine
5513, // Mana Jade
5514 // Mana Agate
};
Player* bot = botAI->GetBot();
for (uint32 gemId : gemIds)
{
if (bot->GetItemCount(gemId, false) > 0) // false = only in bags
return false;
}
return true;
}
bool ArcaneIntellectOnPartyTrigger::IsActive() bool ArcaneIntellectOnPartyTrigger::IsActive()
{ {
@ -25,25 +49,6 @@ bool MageArmorTrigger::IsActive()
!botAI->HasAura("molten armor", target) && !botAI->HasAura("mage armor", target); !botAI->HasAura("molten armor", target) && !botAI->HasAura("mage armor", target);
} }
bool FingersOfFrostSingleTrigger::IsActive()
{
// Fingers of Frost "stack" count is always 1.
// The value is instead stored in the charges.
Aura* aura = botAI->GetAura("fingers of frost", bot, false, true, -1);
return (aura && aura->GetCharges() == 1);
}
bool ArcaneBlastStackTrigger::IsActive()
{
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, 3);
if (!aura)
return false;
if (aura->GetStackAmount() >= 4)
return true;
bool hasMissileBarrage = botAI->HasAura(44401, bot);
return hasMissileBarrage;
}
bool FrostNovaOnTargetTrigger::IsActive() bool FrostNovaOnTargetTrigger::IsActive()
{ {
Unit* target = GetTarget(); Unit* target = GetTarget();
@ -84,3 +89,95 @@ bool NoFocusMagicTrigger::IsActive()
} }
return true; return true;
} }
bool DeepFreezeCooldownTrigger::IsActive()
{
Player* bot = botAI->GetBot();
static const uint32 DEEP_FREEZE_SPELL_ID = 44572;
// If the bot does NOT have Deep Freeze, treat as "on cooldown"
if (!bot->HasSpell(DEEP_FREEZE_SPELL_ID))
return true;
// Otherwise, use the default cooldown logic
return SpellCooldownTrigger::IsActive();
}
const std::set<uint32> FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = {2120, 2121, 8422, 8423, 10215,
10216, 27086, 42925, 42926};
bool FlamestrikeNearbyTrigger::IsActive()
{
Player* bot = botAI->GetBot();
for (uint32 spellId : FLAMESTRIKE_SPELL_IDS)
{
Aura* aura = bot->GetAura(spellId, bot->GetGUID());
if (!aura)
continue;
DynamicObject* dynObj = aura->GetDynobjOwner();
if (!dynObj)
continue;
float dist = bot->GetDistance2d(dynObj->GetPositionX(), dynObj->GetPositionY());
if (dist <= radius)
return true;
}
return false;
}
bool ImprovedScorchTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target || !target->IsAlive() || !target->IsInWorld())
return false;
// List of all spell IDs for Improved Scorch, Winter's Chill, and Shadow Mastery
static const uint32 ImprovedScorchExclusiveDebuffs[] = {// Shadow Mastery
17794, 17797, 17798, 17799, 17800,
// Winter's Chill
12579,
// Improved Scorch
22959};
for (uint32 spellId : ImprovedScorchExclusiveDebuffs)
{
if (target->HasAura(spellId))
return false;
}
// Use default DebuffTrigger logic for the rest (only trigger if debuff is missing or expiring)
return DebuffTrigger::IsActive();
}
const std::set<uint32> BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = {
10, // Blizzard Rank 1
6141, // Blizzard Rank 2
8427, // Blizzard Rank 3
10185, // Blizzard Rank 4
10186, // Blizzard Rank 5
10187, // Blizzard Rank 6
27085, // Blizzard Rank 7
42938, // Blizzard Rank 8
42939 // Blizzard Rank 9
};
bool BlizzardChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{
// Only trigger if the spell being channeled is Blizzard
if (BLIZZARD_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
}
// Not channeling Blizzard
return false;
}

View File

@ -9,11 +9,15 @@
#include "CureTriggers.h" #include "CureTriggers.h"
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "Trigger.h"
#include "Playerbots.h"
#include "PlayerbotAI.h"
#include <set>
#include <unordered_set>
class PlayerbotAI; class PlayerbotAI;
DEFLECT_TRIGGER(FireWardTrigger, "fire ward"); // Buff and Out of Combat Triggers
DEFLECT_TRIGGER(FrostWardTrigger, "frost ward");
class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
{ {
@ -37,30 +41,53 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class LivingBombTrigger : public DebuffTrigger class NoFocusMagicTrigger : public Trigger
{ {
public: public:
LivingBombTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "living bomb", 1, true) {} NoFocusMagicTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no focus magic") {}
bool IsActive() override;
}; };
class FireballTrigger : public DebuffTrigger class IceBarrierTrigger : public BuffTrigger
{ {
public: public:
FireballTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "fireball", 1, true) {} IceBarrierTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "ice barrier") {}
}; };
class PyroblastTrigger : public DebuffTrigger class NoManaGemTrigger : public Trigger
{ {
public: public:
PyroblastTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "pyroblast", 1, true) {} NoManaGemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no mana gem") {}
bool IsActive() override;
}; };
class FireWardTrigger : public DeflectSpellTrigger
{
public:
FireWardTrigger(PlayerbotAI* botAI) : DeflectSpellTrigger(botAI, "fire ward") {}
};
class FrostWardTrigger : public DeflectSpellTrigger
{
public:
FrostWardTrigger(PlayerbotAI* botAI) : DeflectSpellTrigger(botAI, "frost ward") {}
};
// Proc and Boost Triggers
class HotStreakTrigger : public HasAuraTrigger class HotStreakTrigger : public HasAuraTrigger
{ {
public: public:
HotStreakTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "hot streak") {} HotStreakTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "hot streak") {}
}; };
class FirestarterTrigger : public HasAuraTrigger
{
public:
FirestarterTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "firestarter") {}
};
class MissileBarrageTrigger : public HasAuraTrigger class MissileBarrageTrigger : public HasAuraTrigger
{ {
public: public:
@ -73,30 +100,19 @@ public:
ArcaneBlastTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane blast") {} ArcaneBlastTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane blast") {}
}; };
class FingersOfFrostSingleTrigger : public HasAuraStackTrigger class ArcaneBlastStackTrigger : public HasAuraStackTrigger
{ {
public: public:
FingersOfFrostSingleTrigger(PlayerbotAI* ai) : HasAuraStackTrigger(ai, "fingers of frost", 1, 1) {} ArcaneBlastStackTrigger(PlayerbotAI* botAI) : HasAuraStackTrigger(botAI, "arcane blast", 4, 1) {}
bool IsActive() override;
}; };
class FingersOfFrostDoubleTrigger : public HasAuraStackTrigger class ArcaneBlast4StacksAndMissileBarrageTrigger : public TwoTriggers
{ {
public: public:
FingersOfFrostDoubleTrigger(PlayerbotAI* ai) : HasAuraStackTrigger(ai, "fingers of frost", 2, 1) {} ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* ai)
// bool IsActive() override; : TwoTriggers(ai, "arcane blast stack", "missile barrage")
};
class BrainFreezeTrigger : public HasAuraTrigger
{ {
public: }
BrainFreezeTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "fireball!") {}
};
class CounterspellInterruptSpellTrigger : public InterruptSpellTrigger
{
public:
CounterspellInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "counterspell") {}
}; };
class CombustionTrigger : public BoostTrigger class CombustionTrigger : public BoostTrigger
@ -105,23 +121,50 @@ public:
CombustionTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "combustion") {} CombustionTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "combustion") {}
}; };
class IcyVeinsCooldownTrigger : public SpellCooldownTrigger
{
public:
IcyVeinsCooldownTrigger(PlayerbotAI* botAI) : SpellCooldownTrigger(botAI, "icy veins") {}
};
class DeepFreezeCooldownTrigger : public SpellCooldownTrigger
{
public:
DeepFreezeCooldownTrigger(PlayerbotAI* botAI) : SpellCooldownTrigger(botAI, "deep freeze") {}
bool IsActive() override;
};
class ColdSnapTrigger : public TwoTriggers
{
public:
ColdSnapTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "icy veins on cd", "deep freeze on cd") {}
};
class MirrorImageTrigger : public BoostTrigger
{
public:
MirrorImageTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "mirror image") {}
};
class IcyVeinsTrigger : public BoostTrigger class IcyVeinsTrigger : public BoostTrigger
{ {
public: public:
IcyVeinsTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "icy veins") {} IcyVeinsTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "icy veins") {}
}; };
class ColdSnapTrigger : public BoostTrigger class ArcanePowerTrigger : public BoostTrigger
{ {
public: public:
ColdSnapTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "cold snap") {} ArcanePowerTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "arcane power") {}
};
class PresenceOfMindTrigger : public BoostTrigger
{
public:
PresenceOfMindTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "presence of mind") {}
}; };
class IceBarrierTrigger : public BuffTrigger // CC, Interrupt, and Dispel Triggers
{
public:
IceBarrierTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "ice barrier") {}
};
class PolymorphTrigger : public HasCcTargetTrigger class PolymorphTrigger : public HasCcTargetTrigger
{ {
@ -155,29 +198,63 @@ public:
CounterspellEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "counterspell") {} CounterspellEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "counterspell") {}
}; };
class ArcanePowerTrigger : public BuffTrigger class CounterspellInterruptSpellTrigger : public InterruptSpellTrigger
{ {
public: public:
ArcanePowerTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane power") {} CounterspellInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "counterspell") {}
}; };
class PresenceOfMindTrigger : public BuffTrigger // Damage and Debuff Triggers
class LivingBombTrigger : public DebuffTrigger
{ {
public: public:
PresenceOfMindTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "presence of mind") {} LivingBombTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "living bomb", 1, true) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
}; };
class ArcaneBlastStackTrigger : public HasAuraStackTrigger class LivingBombOnAttackersTrigger : public DebuffOnAttackerTrigger
{ {
public: public:
ArcaneBlastStackTrigger(PlayerbotAI* botAI) : HasAuraStackTrigger(botAI, "arcane blast", 3, 1) {} LivingBombOnAttackersTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "living bomb", true) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
class FireballTrigger : public DebuffTrigger
{
public:
FireballTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "fireball", 1, true) {}
};
class ImprovedScorchTrigger : public DebuffTrigger
{
public:
ImprovedScorchTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "improved scorch", 1, true, 0.5f) {}
bool IsActive() override; bool IsActive() override;
}; };
class MirrorImageTrigger : public BoostTrigger class PyroblastTrigger : public DebuffTrigger
{ {
public: public:
MirrorImageTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "mirror image") {} PyroblastTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "pyroblast", 1, true) {}
};
class FrostfireBoltTrigger : public DebuffTrigger
{
public:
FrostfireBoltTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frostfire bolt", 1, true) {}
};
class FingersOfFrostTrigger : public HasAuraTrigger
{
public:
FingersOfFrostTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "fingers of frost") {}
};
class BrainFreezeTrigger : public HasAuraTrigger
{
public:
BrainFreezeTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "fireball!") {}
}; };
class FrostNovaOnTargetTrigger : public DebuffTrigger class FrostNovaOnTargetTrigger : public DebuffTrigger
@ -194,17 +271,74 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class NoFocusMagicTrigger : public Trigger class FlamestrikeNearbyTrigger : public Trigger
{ {
public: public:
NoFocusMagicTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no focus magic") {} FlamestrikeNearbyTrigger(PlayerbotAI* botAI, float radius = 30.0f)
: Trigger(botAI, "flamestrike nearby"), radius(radius)
{
}
bool IsActive() override; bool IsActive() override;
protected:
float radius;
static const std::set<uint32> FLAMESTRIKE_SPELL_IDS;
}; };
class FrostfireBoltTrigger : public DebuffTrigger class FlamestrikeBlizzardTrigger : public TwoTriggers
{ {
public: public:
FrostfireBoltTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frostfire bolt", 1, true) {} FlamestrikeBlizzardTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "flamestrike nearby", "medium aoe") {}
};
class BlizzardChannelCheckTrigger : public Trigger
{
public:
BlizzardChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2)
: Trigger(botAI, "blizzard channel check"), minEnemies(minEnemies) {}
bool IsActive() override;
protected:
uint32 minEnemies;
static const std::set<uint32> BLIZZARD_SPELL_IDS;
};
class BlastWaveOffCdTrigger : public SpellNoCooldownTrigger
{
public:
BlastWaveOffCdTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "blast wave") {}
};
class BlastWaveOffCdTriggerAndMediumAoeTrigger : public TwoTriggers
{
public:
BlastWaveOffCdTriggerAndMediumAoeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "blast wave off cd", "medium aoe") {}
};
class NoFirestarterStrategyTrigger : public Trigger
{
public:
NoFirestarterStrategyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no firestarter strategy") {}
bool IsActive() override
{
return !botAI->HasStrategy("firestarter", BOT_STATE_COMBAT);
}
};
class EnemyIsCloseAndNoFirestarterStrategyTrigger : public TwoTriggers
{
public:
EnemyIsCloseAndNoFirestarterStrategyTrigger(PlayerbotAI* botAI)
: TwoTriggers(botAI, "enemy is close", "no firestarter strategy") {}
};
class EnemyTooCloseForSpellAndNoFirestarterStrategyTrigger : public TwoTriggers
{
public:
EnemyTooCloseForSpellAndNoFirestarterStrategyTrigger(PlayerbotAI* botAI)
: TwoTriggers(botAI, "enemy too close for spell", "no firestarter strategy") {}
}; };
#endif #endif

View File

@ -62,6 +62,9 @@ public:
creators["mimiron rocket strike action"] = &RaidUlduarActionContext::mimiron_rocket_strike_action; creators["mimiron rocket strike action"] = &RaidUlduarActionContext::mimiron_rocket_strike_action;
creators["mimiron phase 4 mark dps action"] = &RaidUlduarActionContext::mimiron_phase_4_mark_dps_action; creators["mimiron phase 4 mark dps action"] = &RaidUlduarActionContext::mimiron_phase_4_mark_dps_action;
creators["mimiron cheat action"] = &RaidUlduarActionContext::mimiron_cheat_action; creators["mimiron cheat action"] = &RaidUlduarActionContext::mimiron_cheat_action;
creators["vezax cheat action"] = &RaidUlduarActionContext::vezax_cheat_action;
creators["vezax shadow crash action"] = &RaidUlduarActionContext::vezax_shadow_crash_action;
creators["vezax mark of the faceless action"] = &RaidUlduarActionContext::vezax_mark_of_the_faceless_action;
} }
private: private:
@ -111,6 +114,9 @@ private:
static Action* mimiron_rocket_strike_action(PlayerbotAI* ai) { return new MimironRocketStrikeAction(ai); } static Action* mimiron_rocket_strike_action(PlayerbotAI* ai) { return new MimironRocketStrikeAction(ai); }
static Action* mimiron_phase_4_mark_dps_action(PlayerbotAI* ai) { return new MimironPhase4MarkDpsAction(ai); } static Action* mimiron_phase_4_mark_dps_action(PlayerbotAI* ai) { return new MimironPhase4MarkDpsAction(ai); }
static Action* mimiron_cheat_action(PlayerbotAI* ai) { return new MimironCheatAction(ai); } static Action* mimiron_cheat_action(PlayerbotAI* ai) { return new MimironCheatAction(ai); }
static Action* vezax_cheat_action(PlayerbotAI* ai) { return new VezaxCheatAction(ai); }
static Action* vezax_shadow_crash_action(PlayerbotAI* ai) { return new VezaxShadowCrashAction(ai); }
static Action* vezax_mark_of_the_faceless_action(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessAction(ai); }
}; };
#endif #endif

View File

@ -42,7 +42,6 @@ const Position ULDUAR_KOLOGARN_RESTORE_POSITION = Position(1764.3749f, -24.02903
const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f);
const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f);
const Position ULDUAR_THORIM_JUMP_START_POINT = Position(2137.137f, -291.19025f, 438.24753f, 1.7059844f); const Position ULDUAR_THORIM_JUMP_START_POINT = Position(2137.137f, -291.19025f, 438.24753f, 1.7059844f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
bool FlameLeviathanVehicleAction::Execute(Event event) bool FlameLeviathanVehicleAction::Execute(Event event)
{ {
@ -568,7 +567,6 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event)
} }
} }
return movedAway; // Return true if moved return movedAway; // Return true if moved
} }
@ -654,7 +652,8 @@ bool RazorscaleAvoidWhirlwindAction::isUseful()
Unit* unit = botAI->GetUnit(npc); Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL)
{ {
if (unit->HasAura(RazorscaleBossHelper::SPELL_SENTINEL_WHIRLWIND) || unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) if (unit->HasAura(RazorscaleBossHelper::SPELL_SENTINEL_WHIRLWIND) ||
unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{ {
if (bot->GetDistance2d(unit) < radius) if (bot->GetDistance2d(unit) < radius)
{ {
@ -679,9 +678,9 @@ bool RazorscaleIgnoreBossAction::isUseful()
if (boss->GetPositionZ() >= RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD) if (boss->GetPositionZ() >= RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD)
{ {
// Check if the bot is outside the designated area // Check if the bot is outside the designated area
if (bot->GetDistance2d( if (bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) >
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f) RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f)
{ {
return true; // Movement to the center is the top priority for all bots return true; // Movement to the center is the top priority for all bots
} }
@ -751,18 +750,13 @@ bool RazorscaleIgnoreBossAction::Execute(Event event)
} }
// Check if the bot is outside the designated area and move inside first // Check if the bot is outside the designated area and move inside first
if (bot->GetDistance2d( if (bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) >
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f) RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f)
{ {
return MoveInside( return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL);
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f,
MovementPriority::MOVEMENT_NORMAL
);
} }
if (!botAI->IsTank(bot)) if (!botAI->IsTank(bot))
@ -802,14 +796,9 @@ bool RazorscaleIgnoreBossAction::Execute(Event event)
} }
// Tanks move inside the arena // Tanks move inside the arena
return MoveInside( return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL);
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f,
MovementPriority::MOVEMENT_NORMAL
);
} }
bool RazorscaleGroundedAction::isUseful() bool RazorscaleGroundedAction::isUseful()
@ -875,9 +864,8 @@ bool RazorscaleGroundedAction::isUseful()
float bossY = boss->GetPositionY(); float bossY = boss->GetPositionY();
float bossZ = boss->GetPositionZ(); float bossZ = boss->GetPositionZ();
bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) && bool atInitialLandingPosition =
(fabs(bossY - landingY) < 2.0f) && (fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f);
(fabs(bossZ - landingZ) < 1.0f);
constexpr float initialLandingRadius = 14.0f; constexpr float initialLandingRadius = 14.0f;
constexpr float normalRadius = 12.0f; constexpr float normalRadius = 12.0f;
@ -891,7 +879,8 @@ bool RazorscaleGroundedAction::isUseful()
return distanceToAdjustedCenter > initialLandingRadius; return distanceToAdjustedCenter > initialLandingRadius;
} }
float distanceToCenter = bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y); float distanceToCenter = bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y);
return distanceToCenter > normalRadius; return distanceToCenter > normalRadius;
} }
@ -961,33 +950,22 @@ bool RazorscaleGroundedAction::Execute(Event event)
float bossY = boss->GetPositionY(); float bossY = boss->GetPositionY();
float bossZ = boss->GetPositionZ(); float bossZ = boss->GetPositionZ();
bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) && bool atInitialLandingPosition =
(fabs(bossY - landingY) < 2.0f) && (fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f);
(fabs(bossZ - landingZ) < 1.0f);
if (atInitialLandingPosition) if (atInitialLandingPosition)
{ {
// If at the initial landing position, use 12-yard radius with a // If at the initial landing position, use 12-yard radius with a
// 20 yard offset on the Y axis so everyone is behind the boss // 20 yard offset on the Y axis so everyone is behind the boss
return MoveInside( return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f, bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 12.0f, MovementPriority::MOVEMENT_COMBAT);
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 12.0f,
MovementPriority::MOVEMENT_COMBAT
);
} }
// Otherwise, move inside a 12-yard radius around the arena center // Otherwise, move inside a 12-yard radius around the arena center
return MoveInside( return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), 12.0f,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, MovementPriority::MOVEMENT_COMBAT);
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
12.0f,
MovementPriority::MOVEMENT_COMBAT
);
} }
return false; return false;
} }
@ -1064,9 +1042,7 @@ bool RazorscaleHarpoonAction::Execute(Event event)
float botDist = bot->GetDistance(closestHarpoon); float botDist = bot->GetDistance(closestHarpoon);
if (botDist > INTERACTION_DISTANCE - 1.0f) if (botDist > INTERACTION_DISTANCE - 1.0f)
{ {
return MoveTo(bot->GetMapId(), return MoveTo(bot->GetMapId(), closestHarpoon->GetPositionX(), closestHarpoon->GetPositionY(),
closestHarpoon->GetPositionX(),
closestHarpoon->GetPositionY(),
closestHarpoon->GetPositionZ()); closestHarpoon->GetPositionZ());
} }
@ -1453,7 +1429,12 @@ bool KologarnEyebeamAction::Execute(Event event)
bool KologarnEyebeamAction::isUseful() bool KologarnEyebeamAction::isUseful()
{ {
KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI);
return kologarnEyebeamTrigger.IsActive(); if (!kologarnEyebeamTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
} }
bool KologarnRtiTargetAction::isUseful() bool KologarnRtiTargetAction::isUseful()
@ -1477,7 +1458,12 @@ bool KologarnRtiTargetAction::Execute(Event event)
bool KologarnCrunchArmorAction::isUseful() bool KologarnCrunchArmorAction::isUseful()
{ {
KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI); KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI);
return kologarnCrunchArmorTrigger.IsActive(); if (!kologarnCrunchArmorTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
} }
bool KologarnCrunchArmorAction::Execute(Event event) bool KologarnCrunchArmorAction::Execute(Event event)
@ -1576,6 +1562,11 @@ bool HodirBitingColdJumpAction::Execute(Event event)
// return true; // return true;
} }
bool HodirBitingColdJumpAction::isUseful()
{
return botAI->HasCheat(BotCheatMask::raid);
}
bool FreyaMoveAwayNatureBombAction::isUseful() bool FreyaMoveAwayNatureBombAction::isUseful()
{ {
// Check boss and it is alive // Check boss and it is alive
@ -1798,7 +1789,12 @@ bool FreyaMoveToHealingSporeAction::Execute(Event event)
bool ThorimUnbalancingStrikeAction::isUseful() bool ThorimUnbalancingStrikeAction::isUseful()
{ {
ThorimUnbalancingStrikeTrigger thorimUnbalancingStrikeTrigger(botAI); ThorimUnbalancingStrikeTrigger thorimUnbalancingStrikeTrigger(botAI);
return thorimUnbalancingStrikeTrigger.IsActive(); if (!thorimUnbalancingStrikeTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
} }
bool ThorimUnbalancingStrikeAction::Execute(Event event) bool ThorimUnbalancingStrikeAction::Execute(Event event)
@ -2647,3 +2643,67 @@ bool MimironCheatAction::Execute(Event event)
return true; return true;
} }
bool VezaxCheatAction::Execute(Event event)
{
// Restore bot's mana to full
uint32 maxMana = bot->GetMaxPower(POWER_MANA);
if (maxMana > 0)
{
bot->SetPower(POWER_MANA, maxMana);
}
return true;
}
bool VezaxShadowCrashAction::Execute(Event event)
{
// Find General Vezax boss
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
if (!boss || !boss->IsAlive())
{
return false;
}
// Get bot's current position relative to boss
float bossX = boss->GetPositionX();
float bossY = boss->GetPositionY();
float bossZ = boss->GetPositionZ();
float botX = bot->GetPositionX();
float botY = bot->GetPositionY();
// Calculate current angle and distance from boss
float currentAngle = atan2(botY - bossY, botX - bossX);
float currentDistance = bot->GetDistance2d(boss);
// Set desired distance from boss (stay close enough for melee, far enough for ranged)
float desiredDistance = 15.0f;
// If too close or too far, adjust distance first
if (currentDistance < desiredDistance - 2.0f || currentDistance > desiredDistance + 2.0f)
{
currentDistance = desiredDistance;
}
// Calculate movement increment - move in increments around the boss
float angleIncrement = M_PI / 10;
float newAngle = currentAngle + angleIncrement;
// Calculate new position
float newX = bossX + currentDistance * cos(newAngle);
float newY = bossY + currentDistance * sin(newAngle);
float newZ = bossZ; // Keep same Z level as boss
// Move to the new position
return MoveTo(boss->GetMapId(), newX, newY, newZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT,
true);
}
bool VezaxMarkOfTheFacelessAction::Execute(Event event)
{
return MoveTo(bot->GetMapId(), ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(),
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionY(),
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionZ(), false, false, false, true,
MovementPriority::MOVEMENT_FORCED, true, false);
}

View File

@ -198,6 +198,7 @@ class HodirBitingColdJumpAction : public MovementAction
public: public:
HodirBitingColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "hodir biting cold jump") {} HodirBitingColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "hodir biting cold jump") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class FreyaMoveAwayNatureBombAction : public MovementAction class FreyaMoveAwayNatureBombAction : public MovementAction
@ -353,5 +354,28 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class VezaxCheatAction : public Action
{
public:
VezaxCheatAction(PlayerbotAI* ai) : Action(ai, "vezax cheat action") {}
bool Execute(Event event) override;
};
class VezaxShadowCrashAction : public MovementAction
{
public:
VezaxShadowCrashAction(PlayerbotAI* ai) : MovementAction(ai, "vezax shadow crash action") {}
bool Execute(Event event) override;
};
class VezaxMarkOfTheFacelessAction : public MovementAction
{
public:
VezaxMarkOfTheFacelessAction(PlayerbotAI* ai) : MovementAction(ai, "vezax mark of the faceless action") {}
bool Execute(Event event) override;
};
#endif #endif

View File

@ -225,6 +225,21 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"mimiron cheat trigger", "mimiron cheat trigger",
NextAction::array(0, new NextAction("mimiron cheat action", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("mimiron cheat action", ACTION_RAID), nullptr)));
//
// General Vezax
//
triggers.push_back(new TriggerNode(
"vezax cheat trigger",
NextAction::array(0, new NextAction("vezax cheat action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"vezax shadow crash trigger",
NextAction::array(0, new NextAction("vezax shadow crash action", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode(
"vezax mark of the faceless trigger",
NextAction::array(0, new NextAction("vezax mark of the faceless action", ACTION_RAID), nullptr)));
} }
void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)

View File

@ -64,6 +64,9 @@ public:
creators["mimiron rocket strike trigger"] = &RaidUlduarTriggerContext::mimiron_rocket_strike_trigger; creators["mimiron rocket strike trigger"] = &RaidUlduarTriggerContext::mimiron_rocket_strike_trigger;
creators["mimiron phase 4 mark dps trigger"] = &RaidUlduarTriggerContext::mimiron_phase_4_mark_dps_trigger; creators["mimiron phase 4 mark dps trigger"] = &RaidUlduarTriggerContext::mimiron_phase_4_mark_dps_trigger;
creators["mimiron cheat trigger"] = &RaidUlduarTriggerContext::mimiron_cheat_trigger; creators["mimiron cheat trigger"] = &RaidUlduarTriggerContext::mimiron_cheat_trigger;
creators["vezax cheat trigger"] = &RaidUlduarTriggerContext::vezax_cheat_trigger;
creators["vezax shadow crash trigger"] = &RaidUlduarTriggerContext::vezax_shadow_crash_trigger;
creators["vezax mark of the faceless trigger"] = &RaidUlduarTriggerContext::vezax_mark_of_the_faceless_trigger;
} }
private: private:
@ -115,6 +118,9 @@ private:
static Trigger* mimiron_rocket_strike_trigger(PlayerbotAI* ai) { return new MimironRocketStrikeTrigger(ai); } static Trigger* mimiron_rocket_strike_trigger(PlayerbotAI* ai) { return new MimironRocketStrikeTrigger(ai); }
static Trigger* mimiron_phase_4_mark_dps_trigger(PlayerbotAI* ai) { return new MimironPhase4MarkDpsTrigger(ai); } static Trigger* mimiron_phase_4_mark_dps_trigger(PlayerbotAI* ai) { return new MimironPhase4MarkDpsTrigger(ai); }
static Trigger* mimiron_cheat_trigger(PlayerbotAI* ai) { return new MimironCheatTrigger(ai); } static Trigger* mimiron_cheat_trigger(PlayerbotAI* ai) { return new MimironCheatTrigger(ai); }
static Trigger* vezax_cheat_trigger(PlayerbotAI* ai) { return new VezaxCheatTrigger(ai); }
static Trigger* vezax_shadow_crash_trigger(PlayerbotAI* ai) { return new VezaxShadowCrashTrigger(ai); }
static Trigger* vezax_mark_of_the_faceless_trigger(PlayerbotAI* ai) { return new VezaxMarkOfTheFacelessTrigger(ai); }
}; };
#endif #endif

View File

@ -1533,6 +1533,11 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
bool MimironCheatTrigger::IsActive() bool MimironCheatTrigger::IsActive()
{ {
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
if (!botAI->IsMainTank(bot)) if (!botAI->IsMainTank(bot))
{ {
return false; return false;
@ -1557,3 +1562,60 @@ bool MimironCheatTrigger::IsActive()
return false; return false;
} }
bool VezaxCheatTrigger::IsActive()
{
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
// Check boss and it is alive
if (!boss || !boss->IsAlive())
{
return false;
}
if (!AI_VALUE2(bool, "has mana", "self target"))
{
return false;
}
return AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig->lowMana;
}
bool VezaxShadowCrashTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
// Check boss and it is alive
if (!boss || !boss->IsAlive())
{
return false;
}
return botAI->HasAura(SPELL_SHADOW_CRASH, bot);
}
bool VezaxMarkOfTheFacelessTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
// Check boss and it is alive
if (!boss || !boss->IsAlive())
{
return false;
}
if (!botAI->HasAura(SPELL_MARK_OF_THE_FACELESS, bot))
{
return false;
}
float distance = bot->GetDistance2d(ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(),
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionY());
return distance > 2.0f;
}

View File

@ -78,6 +78,10 @@ enum UlduarIDs
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274, SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300, SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
//General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277,
// Buffs // Buffs
SPELL_FROST_TRAP = 13809 SPELL_FROST_TRAP = 13809
}; };
@ -106,6 +110,7 @@ const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f
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_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_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_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_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_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_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
@ -117,6 +122,7 @@ const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 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_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_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);
// //
// Flame Levi // Flame Levi
@ -418,4 +424,28 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
//
// General Vezax
//
class VezaxCheatTrigger : public Trigger
{
public:
VezaxCheatTrigger(PlayerbotAI* ai) : Trigger(ai, "vezax cheat trigger") {}
bool IsActive() override;
};
class VezaxShadowCrashTrigger : public Trigger
{
public:
VezaxShadowCrashTrigger(PlayerbotAI* ai) : Trigger(ai, "vezax shadow crash trigger") {}
bool IsActive() override;
};
class VezaxMarkOfTheFacelessTrigger : public Trigger
{
public:
VezaxMarkOfTheFacelessTrigger(PlayerbotAI* ai) : Trigger(ai, "vezax mark of the faceless trigger") {}
bool IsActive() override;
};
#endif #endif