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)
# "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies)
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi")
# 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.PremadeSpecGlyph.8.0 = 42735,43339,44955,43364,43361,42751
AiPlayerbot.PremadeSpecLink.8.0.60 = 23000503110033014032310150532
AiPlayerbot.PremadeSpecLink.8.0.80 = 23000523310033015032310250532-03-203203001
AiPlayerbot.PremadeSpecLink.8.0.60 = 230005231100330150323102500321
AiPlayerbot.PremadeSpecLink.8.0.80 = 230005231100330150323102505321-03-203303001
AiPlayerbot.PremadeSpecName.8.1 = fire pve
AiPlayerbot.PremadeSpecGlyph.8.1 = 42739,43339,45737,43364,44920,42751
AiPlayerbot.PremadeSpecLink.8.1.60 = -0055030011302231053120321341
@ -1440,7 +1441,7 @@ AiPlayerbot.PremadeSpecLink.8.2.80 = 23002303110003--053303031320310003015223135
AiPlayerbot.PremadeSpecName.8.3 = frostfire pve
AiPlayerbot.PremadeSpecGlyph.8.3 = 44684,44920,42751,43339,43364,45737
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.PremadeSpecGlyph.8.4 = 42735,43364,42738,43360,43357,42752
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;
case CLASS_MAGE:
if (tab == 0)
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
engine->addStrategiesNoInit("arcane", nullptr);
else if (tab == 1)
{
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
engine->addStrategiesNoInit("frostfire", nullptr);
}
else
{
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
engine->addStrategiesNoInit("fire", nullptr);
}
}
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;
case CLASS_WARRIOR:
if (tab == 2)

View File

@ -2949,14 +2949,19 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!itemTarget)
{
// Exception for Deep Freeze (44572) - allow cast for damage on immune targets (e.g., bosses)
if (target->IsImmunedToSpell(spellInfo))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
if (spellid != 44572) // Deep Freeze
{
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
target->GetName(), spellid, bot->GetName());
}
return false;
}
return false;
// Otherwise, allow Deep Freeze even if immune
}
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);
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
@ -157,7 +156,7 @@ bool PlayerbotAIConfig::Initialize()
LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
randomBotQuestIds);
LoadSet<std::set<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", "176213,17155"),
disallowedGameObjects);
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
@ -190,7 +189,7 @@ bool PlayerbotAIConfig::Initialize()
maxRandomBotsPriceChangeInterval =
sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * HOUR);
randomBotJoinLfg = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinLfg", true);
//////////////////////////// ICC
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
@ -346,7 +345,7 @@ bool PlayerbotAIConfig::Initialize()
{
std::string setting = "AiPlayerbot.ZoneBracket." + std::to_string(zoneId);
std::string value = sConfigMgr->GetOption<std::string>(setting, "");
if (!value.empty())
{
size_t commaPos = value.find(',');
@ -448,7 +447,7 @@ bool PlayerbotAIConfig::Initialize()
}
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);
botCheatMask = 0;
@ -463,6 +462,8 @@ bool PlayerbotAIConfig::Initialize()
botCheatMask |= (uint32)BotCheatMask::mana;
if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end())
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", ""),
allowedLogFiles);
@ -616,6 +617,9 @@ bool PlayerbotAIConfig::Initialize()
return true;
}
// Assign account types after accounts are created
sRandomPlayerbotMgr->AssignAccountTypes();
if (sPlayerbotAIConfig->enabled)
{
sRandomPlayerbotMgr->Init();

View File

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

View File

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

View File

@ -393,37 +393,118 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
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()
{
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size,
// and additional class account pool size.
// Reset account types if features are disabled
// Reset is done here to precede needed accounts calculations
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.
if (sPlayerbotAIConfig->randomBotAccountCount > 0)
return sPlayerbotAIConfig->randomBotAccountCount;
// Avoid creating accounts if both maxRandom & ClassBots are set to zero.
if (sPlayerbotAIConfig->maxRandomBots == 0 &&
sPlayerbotAIConfig->addClassAccountPoolSize == 0)
return 0;
// Check existing account types
uint32 existingRndBotAccounts = 0;
uint32 existingAddClassAccounts = 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();
// Calculate max bots
int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take perodic online - offline into account
// Take periodic online - offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
}
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset
uint32 baseAccounts = maxBots / divisor;
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1;
// Calculate number of accounts needed for RNDbots
// Result is rounded up for maxBots not cleanly divisible by the divisor
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()
@ -475,8 +556,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
// 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_account_type");
// Get the database names dynamically
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;

View File

@ -515,15 +515,174 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
// 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 maxAllowedBotCount = GetEventValue(0, "bot_count");
static time_t missingBotsTimer = 0;
if (currentBots.size() < maxAllowedBotCount)
{
// Calculate how many bots to add
maxAllowedBotCount -= currentBots.size();
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 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
@ -535,26 +694,42 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
allowedAllianceCount++;
}
uint32 allowedHordeCount = maxAllowedBotCount - allowedAllianceCount;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin();
i != sPlayerbotAIConfig->randomBotAccounts.end(); i++)
// Determine which accounts to use based on EnablePeriodicOnlineOffline
std::vector<uint32> accountsToUse;
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
uint32 accountId = *i;
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
// minus addclass bots account
int32 baseAccount =
RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize;
if (baseAccount <= 0 || baseAccount > sPlayerbotAIConfig->randomBotAccounts.size())
{
LOG_ERROR("playerbots", "Account calculation error with PeriodicOnlineOffline");
return 0;
}
uint32 index = urand(0, baseAccount - 1);
accountId = sPlayerbotAIConfig->randomBotAccounts[index];
// 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++)
{
accountsToUse.push_back(shuffledAccounts[i]);
}
}
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 =
CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID);
stmt->SetData(0, accountId);
@ -562,87 +737,115 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
if (!result)
continue;
std::vector<GuidClassRaceInfo> allGuidInfos;
do
{
Field* fields = result->Fetch();
GuidClassRaceInfo info;
CharacterInfo info;
info.guid = fields[0].Get<uint32>();
info.rClass = fields[1].Get<uint8>();
info.rRace = fields[2].Get<uint8>();
allGuidInfos.push_back(info);
info.accountId = accountId;
allCharacters.push_back(info);
} 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);
bool factionNotAllowed = (!allowedAllianceCount && isAlliance) || (!allowedHordeCount && !isAlliance);
if (factionNotAllowed)
continue;
if (isAlliance)
{
allowedAllianceCount--;
}
else
{
allowedHordeCount--;
}
uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline
? urand(sPlayerbotAIConfig->minRandomBotInWorldTime,
sPlayerbotAIConfig->maxRandomBotInWorldTime)
: sPlayerbotAIConfig->permanantlyInWorldTime;
SetEventValue(guid, "add", 1, add_time);
SetEventValue(guid, "logout", 0, 0);
currentBots.push_back(guid);
maxAllowedBotCount--;
if (!maxAllowedBotCount)
break;
}
if (!maxAllowedBotCount)
break;
}
// Shuffle for class balance
std::shuffle(allCharacters.begin(), allCharacters.end(), rng);
// Separate characters by faction for phased login
std::vector<CharacterInfo> allianceChars;
std::vector<CharacterInfo> hordeChars;
for (const auto& charInfo : allCharacters)
{
if (IsAlliance(charInfo.rRace))
allianceChars.push_back(charInfo);
else
hordeChars.push_back(charInfo);
}
// Lambda to handle bot login logic
auto tryLoginBot = [&](const CharacterInfo& charInfo) -> bool
{
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
? urand(sPlayerbotAIConfig->minRandomBotInWorldTime,
sPlayerbotAIConfig->maxRandomBotInWorldTime)
: sPlayerbotAIConfig->permanantlyInWorldTime;
SetEventValue(charInfo.guid, "add", 1, add_time);
SetEventValue(charInfo.guid, "logout", 0, 0);
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--;
allowedAllianceCount--;
}
}
// PHASE 2: Log-in Horde bots up to maxAllowedBotCount
for (const auto& charInfo : hordeChars)
{
if (!maxAllowedBotCount)
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)
LOG_ERROR("playerbots",
"Not enough random bot accounts available. Try to increase RandomBotAccountCount "
"in your conf file",
ceil(maxAllowedBotCount / 10));
{
if (missingBotsTimer == 0)
missingBotsTimer = time(nullptr);
if (time(nullptr) - missingBotsTimer >= 10)
{
int divisor = RandomPlayerbotFactory::CalculateAvailableCharsPerAccount();
uint32 moreAccountsNeeded = (maxAllowedBotCount + divisor - 1) / divisor;
LOG_ERROR("playerbots",
"Can't log-in all the requested bots. Try increasing RandomBotAccountCount in your conf file.\n"
"{} more accounts needed.", moreAccountsNeeded);
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();
@ -1165,7 +1368,6 @@ void RandomPlayerbotMgr::ScheduleChangeStrategy(uint32 bot, uint32 time)
bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
{
ObjectGuid botGUID = ObjectGuid::Create<HighGuid::Player>(bot);
Player* player = GetPlayerBot(botGUID);
PlayerbotAI* botAI = player ? GET_PLAYERBOT_AI(player) : nullptr;
@ -1875,24 +2077,21 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
void RandomPlayerbotMgr::PrepareAddclassCache()
{
/// @FIXME: Modifying RandomBotAccountCount may cause the original addclass bots to be converted into rndbots,
// 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;
// Using accounts marked as type 2 (AddClass)
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++)
{
if (claz == 10)
continue;
QueryResult results = CharacterDatabase.Query(
"SELECT guid, race FROM characters "
"WHERE account = {} AND class = '{}' AND online = 0 "
"ORDER BY account DESC",
sPlayerbotAIConfig->randomBotAccounts[i], claz);
"WHERE account = {} AND class = '{}' AND online = 0",
accountId, claz);
if (results)
{
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()
@ -2286,10 +2486,13 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
if (!sPlayerbotAIConfig->IsInRandomAccountList(sCharacterCache->GetCharacterAccountIdByGuid(guid)))
return false;
if (std::find(currentBots.begin(), currentBots.end(), bot) != currentBots.end())
return true;
return false;
}
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
@ -2301,23 +2504,37 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{
return IsAddclassBot(bot->GetGUID().GetCounter());
}
return false;
}
bool RandomPlayerbotMgr::IsAddclassBot(ObjectGuid::LowType bot)
{
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(bot);
// Check the cache with faction considerations
for (uint8 claz = CLASS_WARRIOR; claz <= CLASS_DRUID; claz++)
{
if (claz == 10)
continue;
for (uint8 isAlliance = 0; isAlliance <= 1; isAlliance++)
{
if (addclassCache[GetTeamClassIdx(isAlliance, claz)].find(guid) !=
addclassCache[GetTeamClassIdx(isAlliance, claz)].end())
{
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;
}

View File

@ -60,7 +60,6 @@ public:
bool IsEmpty() { return !lastChangeTime; }
public:
uint32 value;
uint32 lastChangeTime;
uint32 validIn;
@ -104,10 +103,6 @@ public:
void LogPlayerLocation();
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
private:
//void ScaleBotActivity();
public:
uint32 activeBots = 0;
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot);
@ -189,6 +184,11 @@ public:
};
std::map<uint32, LevelBracket> zone2LevelBracket;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
// Account type management
void AssignAccountTypes();
bool IsAccountType(uint32 accountId, uint8 accountType);
protected:
void OnBotLoginInternal(Player* const bot) override;
@ -218,10 +218,8 @@ private:
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players;
uint32 processTicks;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
@ -230,6 +228,12 @@ private:
std::list<uint32> currentBots;
uint32 bgBotsCount;
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()

View File

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

View File

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

View File

@ -4,9 +4,9 @@
*/
#include "ArcaneMageStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
@ -15,69 +15,44 @@ public:
creators["arcane blast"] = &arcane_blast;
creators["arcane barrage"] = &arcane_barrage;
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:
static ActionNode* arcane_blast([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("arcane blast",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("arcane missiles"), nullptr),
/*C*/ 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);
// }
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); }
static ActionNode* arcane_missiles(PlayerbotAI*) { return new ActionNode("arcane missiles", nullptr, nullptr, nullptr); }
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", nullptr, nullptr, nullptr); }
static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", nullptr, nullptr, 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); }
};
// ===== Single Target Strategy =====
ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{
actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory());
}
// ===== Default Actions =====
NextAction** ArcaneMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("arcane blast", ACTION_DEFAULT + 0.3f),
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // arcane immune target
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT), nullptr);
return NextAction::array(0, new NextAction("arcane blast", 5.6f),
new NextAction("arcane missiles", 5.5f),
new NextAction("arcane barrage", 5.4f), // cast while moving
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)
{
GenericMageStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("arcane blast stack", NextAction::array(0, new NextAction("arcane missiles", 15.0f), NULL)));
// Proc Trigger
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;
};
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

View File

@ -4,42 +4,71 @@
*/
#include "FireMageStrategy.h"
#include "Playerbots.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),
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // fire immune target
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT), NULL);
public:
FireMageStrategyActionNodeFactory()
{
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)
{
GenericMageStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("pyroblast", NextAction::array(0, new NextAction("pyroblast", 10.0f),
// nullptr)));
triggers.push_back(
new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
triggers.push_back(
new TriggerNode("combustion", NextAction::array(0, new NextAction("combustion", 50.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)));
// Debuff Triggers
triggers.push_back(new TriggerNode("improved scorch", NextAction::array(0, new NextAction("scorch", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 18.5f), nullptr)));
// Proc Trigger
triggers.push_back(new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.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("medium aoe", NextAction::array(0,
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)));
triggers.push_back(new TriggerNode(
"blast wave off cd and medium aoe",
NextAction::array(0, new NextAction("reach melee", 25.5f), nullptr)));
}

View File

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

View File

@ -4,31 +4,56 @@
*/
#include "FrostFireMageStrategy.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),
new NextAction("shoot", ACTION_DEFAULT), NULL);
public:
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)
{
GenericMageStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
triggers.push_back(
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)));
}
// Debuff Triggers
triggers.push_back(new TriggerNode("improved scorch", NextAction::array(0, new NextAction("scorch", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("living bomb", NextAction::array(0, new NextAction("living bomb", 18.5f), nullptr)));
void FrostFireMageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
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)));
// Proc Trigger
triggers.push_back(new TriggerNode("hot streak", NextAction::array(0, new NextAction("pyroblast", 25.0f), nullptr)));
}

View File

@ -13,20 +13,11 @@ class PlayerbotAI;
class FrostFireMageStrategy : public GenericMageStrategy
{
public:
FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) {}
FrostFireMageStrategy(PlayerbotAI* botAI);
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frostfire"; }
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

View File

@ -7,6 +7,7 @@
#include "Playerbots.h"
// ===== Action Node Factory =====
class FrostMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
@ -16,96 +17,66 @@ public:
creators["ice barrier"] = &ice_barrier;
creators["summon water elemental"] = &summon_water_elemental;
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:
static ActionNode* cold_snap([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cold snap",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* ice_barrier([[maybe_unused]] PlayerbotAI* botAI)
{
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);
}
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); }
static ActionNode* summon_water_elemental(PlayerbotAI*) { return new ActionNode("summon water elemental", nullptr, nullptr, nullptr); }
static ActionNode* deep_freeze(PlayerbotAI*) { return new ActionNode("deep freeze", nullptr, nullptr, nullptr); }
static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", nullptr, nullptr, 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* fireball(PlayerbotAI*) { return new ActionNode("fireball", nullptr, nullptr, nullptr); }
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", nullptr, nullptr, nullptr); }
};
// ===== Single Target Strategy =====
FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
{
actionNodeFactories.Add(new FrostMageStrategyActionNodeFactory());
}
// ===== Default Actions =====
NextAction** FrostMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.3f),
new NextAction("fire blast", ACTION_DEFAULT + 0.2f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT + 0.1f),
new NextAction("fireball", ACTION_DEFAULT), nullptr);
return NextAction::array(0, new NextAction("frostbolt", 5.4f),
new NextAction("ice lance", 5.3f), // cast during movement
new NextAction("fire blast", 5.2f), // cast during movement if ice lance is not learned
new NextAction("shoot", 5.1f),
new NextAction("fireball", 5.0f), nullptr);
}
// ===== Trigger Initialization ===
void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& 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(
"no pet", NextAction::array(0, new NextAction("summon water elemental", ACTION_HIGH), nullptr)));
triggers.push_back(
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", ACTION_NORMAL), nullptr)));
triggers.push_back(
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", ACTION_NORMAL + 3), nullptr)));
// Combo cast the last charge of fingers of frost for double crits.
// Should only do this on the final charge of FoF.
triggers.push_back(new TriggerNode("fingers of frost single",
NextAction::array(0, new NextAction("frostbolt", ACTION_NORMAL + 2),
new NextAction("deep freeze", ACTION_NORMAL + 1), 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
// proc. triggers.push_back(new TriggerNode("fingers of frost double", NextAction::array(0, new
// NextAction("frostbolt", ACTION_NORMAL), nullptr)));
// Pet/Defensive triggers
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr)));
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), nullptr)));
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.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)));
// Proc/Freeze triggers
triggers.push_back(new TriggerNode("brain freeze", NextAction::array(0, new NextAction("frostfire bolt", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("fingers of frost", NextAction::array(0,
new NextAction("deep freeze", 19.0f),
new NextAction("frostbolt", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("frostbite on target", NextAction::array(0,
new NextAction("deep freeze", 19.0f),
new NextAction("frostbolt", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("frost nova on target", NextAction::array(0,
new NextAction("deep freeze", 19.0f),
new NextAction("frostbolt", 18.0f), nullptr)));
}

View File

@ -20,13 +20,4 @@ public:
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

View File

@ -4,7 +4,7 @@
*/
#include "GenericMageNonCombatStrategy.h"
#include "AiFactory.h"
#include "Playerbots.h"
class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
@ -52,35 +52,23 @@ void GenericMageNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigg
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(
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 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("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("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)
{
triggers.push_back(
new TriggerNode("mage armor", NextAction::array(0, new NextAction("mage armor", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("mage armor", NextAction::array(0, new NextAction("mage armor", 19.0f), nullptr)));
}
void MageBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("mage armor", NextAction::array(0, new NextAction("molten armor", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("mage armor", NextAction::array(0, new NextAction("molten armor", 19.0f), nullptr)));
}
void MageBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
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)));
triggers.push_back(new TriggerNode("arcane intellect on party", NextAction::array(0, new NextAction("arcane intellect on party", 20.0f), nullptr)));
}

View File

@ -4,7 +4,7 @@
*/
#include "GenericMageStrategy.h"
#include "AiFactory.h"
#include "Playerbots.h"
#include "RangedCombatStrategy.h"
@ -160,49 +160,119 @@ void GenericMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
RangedCombatStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell",
// ACTION_MOVE + 9), nullptr)));
triggers.push_back(
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",
NextAction::array(0, new NextAction("blink back", ACTION_MOVE + 5), nullptr)));
// Threat Triggers
triggers.push_back(new TriggerNode("high threat", NextAction::array(0, new NextAction("mirror image", 60.0f), nullptr)));
triggers.push_back(new TriggerNode("medium threat", NextAction::array(0, new NextAction("invisibility", 30.0f), nullptr)));
// Defensive Triggers
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)
{
triggers.push_back(
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", 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)));
}
void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("icy veins", NextAction::array(0, new NextAction("icy veins", 50.0f), nullptr)));
triggers.push_back(
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)));
triggers.push_back(
new TriggerNode("mirror image", NextAction::array(0, new NextAction("mirror image", 41.0f), nullptr)));
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == 0) // Arcane
{
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)
{
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"; }
};
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

View File

@ -5,7 +5,7 @@
#include "MageActions.h"
#include <cmath>
#include "UseItemAction.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "ServerFacade.h"
@ -13,6 +13,42 @@
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()
{
Unit* target = AI_VALUE(Unit*, "current target");
@ -105,4 +141,14 @@ bool CastBlinkBackAction::Execute(Event event)
// can cast spell check passed in isUseful()
bot->SetOrientation(bot->GetAngle(target) + M_PI);
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 "SharedDefines.h"
#include "UseItemAction.h"
class PlayerbotAI;
BUFF_ACTION(CastFireWardAction, "fire ward");
BUFF_ACTION(CastFrostWardAction, "frost ward");
// Buff and Out of Combat Actions
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
{
@ -57,18 +326,26 @@ public:
CastPyroblastAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "pyroblast") {}
};
class CastFlamestrikeAction : public CastDebuffSpellAction
class CastLivingBombAction : public CastDebuffSpellAction
{
public:
CastFlamestrikeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "flamestrike", true, 0.0f) {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastFrostNovaAction : public CastSpellAction
class CastLivingBombOnAttackersAction : public CastDebuffSpellOnAttackerAction
{
public:
CastFrostNovaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "frost nova") {}
bool isUseful() override;
CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
};
class CastFrostboltAction : public CastSpellAction
@ -89,12 +366,6 @@ public:
CastIceLanceAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "ice lance") {}
};
class CastDeepFreezeAction : public CastSpellAction
{
public:
CastDeepFreezeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "deep freeze") {}
};
class CastBlizzardAction : public CastSpellAction
{
public:
@ -110,143 +381,11 @@ public:
bool isUseful() override;
};
class CastArcaneIntellectAction : public CastBuffSpellAction
class CastFlamestrikeAction : public CastDebuffSpellAction
{
public:
CastArcaneIntellectAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane intellect") {}
};
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) {}
CastFlamestrikeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "flamestrike", true, 0.0f) {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
};
class CastDragonsBreathAction : public CastSpellAction
@ -265,55 +404,12 @@ public:
bool isUseful() override;
};
class CastInvisibilityAction : public CastBuffSpellAction
class CancelChannelAction : public Action
{
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;
};
#endif

View File

@ -4,7 +4,6 @@
*/
#include "MageAiObjectContext.h"
#include "ArcaneMageStrategy.h"
#include "FireMageStrategy.h"
#include "FrostFireMageStrategy.h"
@ -23,27 +22,23 @@ public:
{
creators["nc"] = &MageStrategyFactoryInternal::nc;
creators["pull"] = &MageStrategyFactoryInternal::pull;
creators["fire aoe"] = &MageStrategyFactoryInternal::fire_aoe;
creators["frostfire aoe"] = &MageStrategyFactoryInternal::frostfire_aoe;
creators["frost aoe"] = &MageStrategyFactoryInternal::frost_aoe;
creators["arcane aoe"] = &MageStrategyFactoryInternal::arcane_aoe;
creators["aoe"] = &MageStrategyFactoryInternal::aoe;
creators["cure"] = &MageStrategyFactoryInternal::cure;
creators["buff"] = &MageStrategyFactoryInternal::buff;
creators["boost"] = &MageStrategyFactoryInternal::boost;
creators["cc"] = &MageStrategyFactoryInternal::cc;
creators["firestarter"] = &MageStrategyFactoryInternal::firestarter;
}
private:
static Strategy* nc(PlayerbotAI* botAI) { return new GenericMageNonCombatStrategy(botAI); }
static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "shoot"); }
static Strategy* fire_aoe(PlayerbotAI* botAI) { return new FireMageAoeStrategy(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* aoe(PlayerbotAI* botAI) { return new MageAoeStrategy(botAI); }
static Strategy* cure(PlayerbotAI* botAI) { return new MageCureStrategy(botAI); }
static Strategy* buff(PlayerbotAI* botAI) { return new MageBuffStrategy(botAI); }
static Strategy* boost(PlayerbotAI* botAI) { return new MageBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new MageCcStrategy(botAI); }
static Strategy* firestarter(PlayerbotAI* botAI) { return new FirestarterStrategy(botAI); }
};
class MageCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -86,8 +81,7 @@ public:
creators["fireball"] = &MageTriggerFactoryInternal::fireball;
creators["pyroblast"] = &MageTriggerFactoryInternal::pyroblast;
creators["combustion"] = &MageTriggerFactoryInternal::combustion;
creators["fingers of frost single"] = &MageTriggerFactoryInternal::fingers_of_frost_single;
creators["fingers of frost double"] = &MageTriggerFactoryInternal::fingers_of_frost_double;
creators["fingers of frost"] = &MageTriggerFactoryInternal::fingers_of_frost;
creators["brain freeze"] = &MageTriggerFactoryInternal::brain_freeze;
creators["icy veins"] = &MageTriggerFactoryInternal::icy_veins;
creators["cold snap"] = &MageTriggerFactoryInternal::cold_snap;
@ -102,6 +96,7 @@ public:
creators["spellsteal"] = &MageTriggerFactoryInternal::spellsteal;
creators["hot streak"] = &MageTriggerFactoryInternal::hot_streak;
creators["living bomb"] = &MageTriggerFactoryInternal::living_bomb;
creators["living bomb on attackers"] = &MageTriggerFactoryInternal::living_bomb_on_attackers;
creators["missile barrage"] = &MageTriggerFactoryInternal::missile_barrage;
creators["arcane blast"] = &MageTriggerFactoryInternal::arcane_blast;
creators["counterspell on enemy healer"] = &MageTriggerFactoryInternal::counterspell_enemy_healer;
@ -115,6 +110,20 @@ public:
creators["frostbite on target"] = &MageTriggerFactoryInternal::frostbite_on_target;
creators["no focus magic"] = &MageTriggerFactoryInternal::no_focus_magic;
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:
@ -126,8 +135,7 @@ private:
static Trigger* fireball(PlayerbotAI* botAI) { return new FireballTrigger(botAI); }
static Trigger* pyroblast(PlayerbotAI* botAI) { return new PyroblastTrigger(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_double(PlayerbotAI* botAI) { return new FingersOfFrostDoubleTrigger(botAI); }
static Trigger* fingers_of_frost(PlayerbotAI* botAI) { return new FingersOfFrostTrigger(botAI); }
static Trigger* brain_freeze(PlayerbotAI* botAI) { return new BrainFreezeTrigger(botAI); }
static Trigger* icy_veins(PlayerbotAI* botAI) { return new IcyVeinsTrigger(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* spellsteal(PlayerbotAI* botAI) { return new SpellstealTrigger(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* arcane_blast(PlayerbotAI* botAI) { return new ArcaneBlastTrigger(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* no_focus_magic(PlayerbotAI* botAI) { return new NoFocusMagicTrigger(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>
@ -170,6 +193,7 @@ public:
creators["arcane intellect on party"] = &MageAiObjectContextInternal::arcane_intellect_on_party;
creators["conjure water"] = &MageAiObjectContextInternal::conjure_water;
creators["conjure food"] = &MageAiObjectContextInternal::conjure_food;
creators["conjure mana gem"] = &MageAiObjectContextInternal::conjure_mana_gem;
creators["molten armor"] = &MageAiObjectContextInternal::molten_armor;
creators["mage armor"] = &MageAiObjectContextInternal::mage_armor;
creators["ice armor"] = &MageAiObjectContextInternal::ice_armor;
@ -207,6 +231,14 @@ public:
creators["mirror image"] = &MageAiObjectContextInternal::mirror_image;
creators["focus magic on party"] = &MageAiObjectContextInternal::focus_magic_on_party;
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:
@ -228,6 +260,7 @@ private:
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_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* mage_armor(PlayerbotAI* botAI) { return new CastMageArmorAction(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_on_party(PlayerbotAI* botAI) { return new CastRemoveCurseOnPartyAction(botAI); }
static Action* remove_lesser_curse(PlayerbotAI* botAI) { return new CastRemoveLesserCurseAction(botAI); }
static Action* remove_lesser_curse_on_party(PlayerbotAI* botAI)
{
return new CastRemoveLesserCurseOnPartyAction(botAI);
}
static Action* remove_lesser_curse_on_party(PlayerbotAI* botAI) { return new CastRemoveLesserCurseOnPartyAction(botAI); }
static Action* icy_veins(PlayerbotAI* botAI) { return new CastIcyVeinsAction(botAI); }
static Action* cold_snap(PlayerbotAI* botAI) { return new CastColdSnapAction(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* invisibility(PlayerbotAI* botAI) { return new CastInvisibilityAction(botAI); }
static Action* evocation(PlayerbotAI* botAI) { return new CastEvocationAction(botAI); }
static Action* counterspell_on_enemy_healer(PlayerbotAI* botAI)
{
return new CastCounterspellOnEnemyHealerAction(botAI);
}
static Action* counterspell_on_enemy_healer(PlayerbotAI* botAI) { return new CastCounterspellOnEnemyHealerAction(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* 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;
@ -309,4 +344,4 @@ void MageAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextLis
void MageAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
AiObjectContext::BuildSharedValueContexts(valueContexts);
}
}

View File

@ -4,9 +4,33 @@
*/
#include "MageTriggers.h"
#include "MageActions.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()
{
@ -25,25 +49,6 @@ bool MageArmorTrigger::IsActive()
!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()
{
Unit* target = GetTarget();
@ -84,3 +89,95 @@ bool NoFocusMagicTrigger::IsActive()
}
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 "GenericTriggers.h"
#include "SharedDefines.h"
#include "Trigger.h"
#include "Playerbots.h"
#include "PlayerbotAI.h"
#include <set>
#include <unordered_set>
class PlayerbotAI;
DEFLECT_TRIGGER(FireWardTrigger, "fire ward");
DEFLECT_TRIGGER(FrostWardTrigger, "frost ward");
// Buff and Out of Combat Triggers
class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
{
@ -37,30 +41,53 @@ public:
bool IsActive() override;
};
class LivingBombTrigger : public DebuffTrigger
class NoFocusMagicTrigger : public Trigger
{
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:
FireballTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "fireball", 1, true) {}
IceBarrierTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "ice barrier") {}
};
class PyroblastTrigger : public DebuffTrigger
class NoManaGemTrigger : public Trigger
{
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
{
public:
HotStreakTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "hot streak") {}
};
class FirestarterTrigger : public HasAuraTrigger
{
public:
FirestarterTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "firestarter") {}
};
class MissileBarrageTrigger : public HasAuraTrigger
{
public:
@ -73,30 +100,19 @@ public:
ArcaneBlastTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "arcane blast") {}
};
class FingersOfFrostSingleTrigger : public HasAuraStackTrigger
class ArcaneBlastStackTrigger : public HasAuraStackTrigger
{
public:
FingersOfFrostSingleTrigger(PlayerbotAI* ai) : HasAuraStackTrigger(ai, "fingers of frost", 1, 1) {}
bool IsActive() override;
ArcaneBlastStackTrigger(PlayerbotAI* botAI) : HasAuraStackTrigger(botAI, "arcane blast", 4, 1) {}
};
class FingersOfFrostDoubleTrigger : public HasAuraStackTrigger
class ArcaneBlast4StacksAndMissileBarrageTrigger : public TwoTriggers
{
public:
FingersOfFrostDoubleTrigger(PlayerbotAI* ai) : HasAuraStackTrigger(ai, "fingers of frost", 2, 1) {}
// bool IsActive() override;
};
class BrainFreezeTrigger : public HasAuraTrigger
{
public:
BrainFreezeTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "fireball!") {}
};
class CounterspellInterruptSpellTrigger : public InterruptSpellTrigger
{
public:
CounterspellInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "counterspell") {}
ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* ai)
: TwoTriggers(ai, "arcane blast stack", "missile barrage")
{
}
};
class CombustionTrigger : public BoostTrigger
@ -105,23 +121,50 @@ public:
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
{
public:
IcyVeinsTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "icy veins") {}
};
class ColdSnapTrigger : public BoostTrigger
class ArcanePowerTrigger : public BoostTrigger
{
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
{
public:
IceBarrierTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "ice barrier") {}
};
// CC, Interrupt, and Dispel Triggers
class PolymorphTrigger : public HasCcTargetTrigger
{
@ -155,29 +198,63 @@ public:
CounterspellEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "counterspell") {}
};
class ArcanePowerTrigger : public BuffTrigger
class CounterspellInterruptSpellTrigger : public InterruptSpellTrigger
{
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:
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:
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;
};
class MirrorImageTrigger : public BoostTrigger
class PyroblastTrigger : public DebuffTrigger
{
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
@ -194,17 +271,74 @@ public:
bool IsActive() override;
};
class NoFocusMagicTrigger : public Trigger
class FlamestrikeNearbyTrigger : public Trigger
{
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;
protected:
float radius;
static const std::set<uint32> FLAMESTRIKE_SPELL_IDS;
};
class FrostfireBoltTrigger : public DebuffTrigger
class FlamestrikeBlizzardTrigger : public TwoTriggers
{
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

View File

@ -62,6 +62,9 @@ public:
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 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:
@ -111,6 +114,9 @@ private:
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_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

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_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_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
bool FlameLeviathanVehicleAction::Execute(Event event)
{
@ -484,12 +483,12 @@ bool RazorscaleAvoidDevouringFlameAction::isUseful()
float distance = bot->GetDistance2d(unit);
if (distance < safeDistance)
{
return true; // Bot is within the danger distance
return true; // Bot is within the danger distance
}
}
}
return false; // No nearby flames or bot is at a safe distance
return false; // No nearby flames or bot is at a safe distance
}
bool RazorscaleAvoidSentinelAction::Execute(Event event)
@ -529,17 +528,17 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event)
Unit* mainTankUnit = AI_VALUE(Unit*, "main tank");
Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr;
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player
{
// Iterate through the first 3 bot tanks to assign the Skull marker
for (int i = 0; i < 3; ++i)
{
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
{
Group* group = bot->GetGroup();
if (group && lowestHealthSentinel)
{
int8 skullIndex = 7; // Skull
int8 skullIndex = 7; // Skull
ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
// If there's no skull set yet, or the skull is on a different target, set the sentinel
@ -548,16 +547,16 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event)
group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID());
}
}
break; // Stop after finding the first valid bot tank
break; // Stop after finding the first valid bot tank
}
}
}
else if (isMainTank && lowestHealthSentinel) // Bot is the main tank
else if (isMainTank && lowestHealthSentinel) // Bot is the main tank
{
Group* group = bot->GetGroup();
if (group)
{
int8 skullIndex = 7; // Skull
int8 skullIndex = 7; // Skull
ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex);
// If there's no skull set yet, or the skull is on a different target, set the sentinel
@ -568,8 +567,7 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event)
}
}
return movedAway; // Return true if moved
return movedAway; // Return true if moved
}
bool RazorscaleAvoidSentinelAction::isUseful()
@ -585,13 +583,13 @@ bool RazorscaleAvoidSentinelAction::isUseful()
}
// If the main tank is a human, check if this bot is one of the first three valid bot tanks
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
{
for (int i = 0; i < 3; ++i)
{
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
{
return true; // This bot should assist with marking
return true; // This bot should assist with marking
}
}
}
@ -654,7 +652,8 @@ bool RazorscaleAvoidWhirlwindAction::isUseful()
Unit* unit = botAI->GetUnit(npc);
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)
{
@ -679,11 +678,11 @@ bool RazorscaleIgnoreBossAction::isUseful()
if (boss->GetPositionZ() >= RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD)
{
// Check if the bot is outside the designated area
if (bot->GetDistance2d(
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f)
if (bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) >
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
}
if (!botAI->IsTank(bot))
@ -698,11 +697,11 @@ bool RazorscaleIgnoreBossAction::isUseful()
}
// Check if the boss is already set as the moon marker
int8 moonIndex = 4; // Moon marker index
int8 moonIndex = 4; // Moon marker index
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
if (currentMoonTarget == boss->GetGUID())
{
return false; // Moon marker is already correctly set, no further action needed
return false; // Moon marker is already correctly set, no further action needed
}
// Proceed to tank-specific logic
@ -716,13 +715,13 @@ bool RazorscaleIgnoreBossAction::isUseful()
}
// If the main tank is a human, check if this bot is the lowest-indexed bot tank
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
{
for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes
for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes
{
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank
{
return true; // This bot should assign the marker
return true; // This bot should assign the marker
}
}
}
@ -751,18 +750,13 @@ bool RazorscaleIgnoreBossAction::Execute(Event event)
}
// Check if the bot is outside the designated area and move inside first
if (bot->GetDistance2d(
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f)
if (bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) >
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f)
{
return MoveInside(
ULDUAR_MAP_ID,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f,
MovementPriority::MOVEMENT_NORMAL
);
return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL);
}
if (!botAI->IsTank(bot))
@ -775,7 +769,7 @@ bool RazorscaleIgnoreBossAction::Execute(Event event)
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
if (currentMoonTarget == boss->GetGUID())
{
return false; // Moon marker is already correctly set
return false; // Moon marker is already correctly set
}
// Get the main tank and determine role
@ -783,33 +777,28 @@ bool RazorscaleIgnoreBossAction::Execute(Event event)
Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr;
// If the main tank is a human, assign the moon marker using the lowest-indexed bot tank
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player
{
for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes
for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes
{
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
{
group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID());
SetNextMovementDelay(1000);
break; // Assign the moon marker and stop
break; // Assign the moon marker and stop
}
}
}
else if (mainTankUnit == bot) // If this bot is the main tank
else if (mainTankUnit == bot) // If this bot is the main tank
{
group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID());
SetNextMovementDelay(1000);
}
// Tanks move inside the arena
return MoveInside(
ULDUAR_MAP_ID,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f,
MovementPriority::MOVEMENT_NORMAL
);
return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL);
}
bool RazorscaleGroundedAction::isUseful()
@ -875,9 +864,8 @@ bool RazorscaleGroundedAction::isUseful()
float bossY = boss->GetPositionY();
float bossZ = boss->GetPositionZ();
bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) &&
(fabs(bossY - landingY) < 2.0f) &&
(fabs(bossZ - landingZ) < 1.0f);
bool atInitialLandingPosition =
(fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f);
constexpr float initialLandingRadius = 14.0f;
constexpr float normalRadius = 12.0f;
@ -891,7 +879,8 @@ bool RazorscaleGroundedAction::isUseful()
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;
}
@ -911,12 +900,12 @@ bool RazorscaleGroundedAction::Execute(Event event)
Unit* mainTankUnit = AI_VALUE(Unit*, "main tank");
Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr;
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
{
// Iterate through the first 3 bot tanks to handle the moon marker
for (int i = 0; i < 3; ++i)
{
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
{
int8 moonIndex = 4;
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
@ -931,7 +920,7 @@ bool RazorscaleGroundedAction::Execute(Event event)
}
}
}
else if (botAI->IsMainTank(bot)) // Bot is the main tank
else if (botAI->IsMainTank(bot)) // Bot is the main tank
{
int8 moonIndex = 4;
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
@ -961,33 +950,22 @@ bool RazorscaleGroundedAction::Execute(Event event)
float bossY = boss->GetPositionY();
float bossZ = boss->GetPositionZ();
bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) &&
(fabs(bossY - landingY) < 2.0f) &&
(fabs(bossZ - landingZ) < 1.0f);
bool atInitialLandingPosition =
(fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f);
if (atInitialLandingPosition)
{
// 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
return MoveInside(
ULDUAR_MAP_ID,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f,
bot->GetPositionZ(),
RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 12.0f,
MovementPriority::MOVEMENT_COMBAT
);
return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
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
return MoveInside(
ULDUAR_MAP_ID,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y,
bot->GetPositionZ(),
12.0f,
MovementPriority::MOVEMENT_COMBAT
);
return MoveInside(ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X,
RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), 12.0f,
MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
@ -1064,9 +1042,7 @@ bool RazorscaleHarpoonAction::Execute(Event event)
float botDist = bot->GetDistance(closestHarpoon);
if (botDist > INTERACTION_DISTANCE - 1.0f)
{
return MoveTo(bot->GetMapId(),
closestHarpoon->GetPositionX(),
closestHarpoon->GetPositionY(),
return MoveTo(bot->GetMapId(), closestHarpoon->GetPositionX(), closestHarpoon->GetPositionY(),
closestHarpoon->GetPositionZ());
}
@ -1453,7 +1429,12 @@ bool KologarnEyebeamAction::Execute(Event event)
bool KologarnEyebeamAction::isUseful()
{
KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI);
return kologarnEyebeamTrigger.IsActive();
if (!kologarnEyebeamTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
}
bool KologarnRtiTargetAction::isUseful()
@ -1477,7 +1458,12 @@ bool KologarnRtiTargetAction::Execute(Event event)
bool KologarnCrunchArmorAction::isUseful()
{
KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI);
return kologarnCrunchArmorTrigger.IsActive();
if (!kologarnCrunchArmorTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
}
bool KologarnCrunchArmorAction::Execute(Event event)
@ -1576,6 +1562,11 @@ bool HodirBitingColdJumpAction::Execute(Event event)
// return true;
}
bool HodirBitingColdJumpAction::isUseful()
{
return botAI->HasCheat(BotCheatMask::raid);
}
bool FreyaMoveAwayNatureBombAction::isUseful()
{
// Check boss and it is alive
@ -1798,7 +1789,12 @@ bool FreyaMoveToHealingSporeAction::Execute(Event event)
bool ThorimUnbalancingStrikeAction::isUseful()
{
ThorimUnbalancingStrikeTrigger thorimUnbalancingStrikeTrigger(botAI);
return thorimUnbalancingStrikeTrigger.IsActive();
if (!thorimUnbalancingStrikeTrigger.IsActive())
{
return false;
}
return botAI->HasCheat(BotCheatMask::raid);
}
bool ThorimUnbalancingStrikeAction::Execute(Event event)
@ -2245,7 +2241,7 @@ bool MimironShockBlastAction::Execute(Event event)
float dy = bot->GetPositionY() + sin(angle) * distance;
float dz = bot->GetPositionZ();
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), dx, dy, dz))
bot->GetPositionZ(), dx, dy, dz))
{
bot->TeleportTo(target->GetMapId(), dx, dy, dz, target->GetOrientation());
return true;
@ -2448,7 +2444,7 @@ bool MimironAerialCommandUnitAction::Execute(Event event)
{
group->SetTargetIcon(crossIndex, bot->GetGUID(), boss->GetGUID());
}
if (assaultBot)
{
ObjectGuid skullTarget = group->GetTargetIcon(skullIndex);
@ -2647,3 +2643,67 @@ bool MimironCheatAction::Execute(Event event)
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:
HodirBitingColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "hodir biting cold jump") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class FreyaMoveAwayNatureBombAction : public MovementAction
@ -353,5 +354,28 @@ public:
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

View File

@ -225,6 +225,21 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"mimiron cheat trigger",
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)

View File

@ -64,6 +64,9 @@ public:
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 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:
@ -115,6 +118,9 @@ private:
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_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

View File

@ -1533,6 +1533,11 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
bool MimironCheatTrigger::IsActive()
{
if (!botAI->HasCheat(BotCheatMask::raid))
{
return false;
}
if (!botAI->IsMainTank(bot))
{
return false;
@ -1557,3 +1562,60 @@ bool MimironCheatTrigger::IsActive()
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

@ -77,6 +77,10 @@ enum UlduarIDs
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
//General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277,
// Buffs
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_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
@ -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_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
//
// Flame Levi
@ -418,4 +424,28 @@ public:
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