diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index a217e6f2..dcaf4b2d 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -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" # # @@ -1435,8 +1436,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 @@ -1448,7 +1449,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 diff --git a/data/sql/playerbots/base/playerbots_account_type.sql b/data/sql/playerbots/base/playerbots_account_type.sql new file mode 100644 index 00000000..d9ce9074 --- /dev/null +++ b/data/sql/playerbots/base/playerbots_account_type.sql @@ -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'; + diff --git a/data/sql/playerbots/updates/db_playerbots/2025_07_01_00_account_type.sql b/data/sql/playerbots/updates/db_playerbots/2025_07_01_00_account_type.sql new file mode 100644 index 00000000..1bee566f --- /dev/null +++ b/data/sql/playerbots/updates/db_playerbots/2025_07_01_00_account_type.sql @@ -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'; + diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index 8b7152d6..643899e9 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -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) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 635c675d..2bbf80ee 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -2950,14 +2950,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) diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index d84234af..6b46813c 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -118,7 +118,6 @@ bool PlayerbotAIConfig::Initialize() tellWhenAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.TellWhenAvoidAoe", false); randomGearLoweringChance = sConfigMgr->GetOption("AiPlayerbot.RandomGearLoweringChance", 0.0f); - incrementalGearInit = sConfigMgr->GetOption("AiPlayerbot.IncrementalGearInit", true); randomGearQualityLimit = sConfigMgr->GetOption("AiPlayerbot.RandomGearQualityLimit", 3); randomGearScoreLimit = sConfigMgr->GetOption("AiPlayerbot.RandomGearScoreLimit", 0); @@ -157,7 +156,7 @@ bool PlayerbotAIConfig::Initialize() LoadList>( sConfigMgr->GetOption("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"), randomBotQuestIds); - + LoadSet>(sConfigMgr->GetOption("AiPlayerbot.DisallowedGameObjects", "176213,17155"), disallowedGameObjects); botAutologin = sConfigMgr->GetOption("AiPlayerbot.BotAutologin", false); @@ -355,7 +354,7 @@ bool PlayerbotAIConfig::Initialize() { std::string setting = "AiPlayerbot.ZoneBracket." + std::to_string(zoneId); std::string value = sConfigMgr->GetOption(setting, ""); - + if (!value.empty()) { size_t commaPos = value.find(','); @@ -457,7 +456,7 @@ bool PlayerbotAIConfig::Initialize() } botCheats.clear(); - LoadListString>(sConfigMgr->GetOption("AiPlayerbot.BotCheats", "taxi"), + LoadListString>(sConfigMgr->GetOption("AiPlayerbot.BotCheats", "taxi,raid"), botCheats); botCheatMask = 0; @@ -472,6 +471,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>(sConfigMgr->GetOption("AiPlayerbot.AllowedLogFiles", ""), allowedLogFiles); @@ -625,6 +626,9 @@ bool PlayerbotAIConfig::Initialize() return true; } + // Assign account types after accounts are created + sRandomPlayerbotMgr->AssignAccountTypes(); + if (sPlayerbotAIConfig->enabled) { sRandomPlayerbotMgr->Init(); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 0d9176ee..d4304b63 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -22,7 +22,8 @@ enum class BotCheatMask : uint32 health = 4, mana = 8, power = 16, - maxMask = 32 + raid = 32, + maxMask = 64 }; enum class HealingManaEfficiency : uint8 diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 72489431..ee6c6926 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -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); diff --git a/src/RandomPlayerbotFactory.cpp b/src/RandomPlayerbotFactory.cpp index d411ffc9..a3d6a2c7 100644 --- a/src/RandomPlayerbotFactory.cpp +++ b/src/RandomPlayerbotFactory.cpp @@ -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() == 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(); + uint32 count = fields[1].Get(); - // 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; diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index fb9504a9..fd29b1df 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -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 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(); + 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 currentAssignments; + + if (existingAssignments) + { + do + { + Field* fields = existingAssignments->Fetch(); + uint32 accountId = fields[0].Get(); + uint8 accountType = fields[1].Get(); + 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::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin(); - i != sPlayerbotAIConfig->randomBotAccounts.end(); i++) + // Determine which accounts to use based on EnablePeriodicOnlineOffline + std::vector 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 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 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 allGuidInfos; - do { Field* fields = result->Fetch(); - GuidClassRaceInfo info; + CharacterInfo info; info.guid = fields[0].Get(); info.rClass = fields[1].Get(); info.rRace = fields[2].Get(); - 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 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 allianceChars; + std::vector 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(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(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(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; } diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 02d0ff9d..6a62a68b 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -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 zone2LevelBracket; std::map> 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& locs, bool hearth = false); uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ); typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*); - std::vector players; uint32 processTicks; - // std::map> rpgLocsCache; std::map>> rpgLocsCacheLevel; @@ -230,6 +228,12 @@ private: std::list currentBots; uint32 bgBotsCount; uint32 playersLevel; + + // Account lists + std::vector rndBotTypeAccounts; // Accounts marked as RNDbot (type 1) + std::vector addClassTypeAccounts; // Accounts marked as AddClass (type 2) + + //void ScaleBotActivity(); // Deprecated function }; #define sRandomPlayerbotMgr RandomPlayerbotMgr::instance() diff --git a/src/strategy/actions/CheatAction.cpp b/src/strategy/actions/CheatAction.cpp index 1092ca34..f627105f 100644 --- a/src/strategy/actions/CheatAction.cpp +++ b/src/strategy/actions/CheatAction.cpp @@ -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"; } diff --git a/src/strategy/actions/NonCombatActions.cpp b/src/strategy/actions/NonCombatActions.cpp index 6ee977a2..128e44ab 100644 --- a/src/strategy/actions/NonCombatActions.cpp +++ b/src/strategy/actions/NonCombatActions.cpp @@ -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; } diff --git a/src/strategy/mage/ArcaneMageStrategy.cpp b/src/strategy/mage/ArcaneMageStrategy.cpp index 5b585721..30c17d54 100644 --- a/src/strategy/mage/ArcaneMageStrategy.cpp +++ b/src/strategy/mage/ArcaneMageStrategy.cpp @@ -4,9 +4,9 @@ */ #include "ArcaneMageStrategy.h" - #include "Playerbots.h" +// ===== Action Node Factory ===== class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory { 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& 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& 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))); -} \ No newline at end of file diff --git a/src/strategy/mage/ArcaneMageStrategy.h b/src/strategy/mage/ArcaneMageStrategy.h index adcc51cf..f6adf23f 100644 --- a/src/strategy/mage/ArcaneMageStrategy.h +++ b/src/strategy/mage/ArcaneMageStrategy.h @@ -20,13 +20,4 @@ public: NextAction** getDefaultActions() override; }; -class ArcaneMageAoeStrategy : public CombatStrategy -{ -public: - ArcaneMageAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} - -public: - virtual void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "arcane aoe"; } -}; #endif diff --git a/src/strategy/mage/FireMageStrategy.cpp b/src/strategy/mage/FireMageStrategy.cpp index 9d81beae..f87fe882 100644 --- a/src/strategy/mage/FireMageStrategy.cpp +++ b/src/strategy/mage/FireMageStrategy.cpp @@ -4,42 +4,71 @@ */ #include "FireMageStrategy.h" - #include "Playerbots.h" #include "Strategy.h" -NextAction** FireMageStrategy::getDefaultActions() +// ===== Action Node Factory ===== +class FireMageStrategyActionNodeFactory : public NamedObjectFactory { - 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& 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& 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& 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))); } diff --git a/src/strategy/mage/FireMageStrategy.h b/src/strategy/mage/FireMageStrategy.h index 4200fbe0..4574a345 100644 --- a/src/strategy/mage/FireMageStrategy.h +++ b/src/strategy/mage/FireMageStrategy.h @@ -13,20 +13,19 @@ class PlayerbotAI; class FireMageStrategy : public GenericMageStrategy { public: - FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) {} + FireMageStrategy(PlayerbotAI* botAI); void InitTriggers(std::vector& 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& triggers) override; - std::string const getName() override { return "fire aoe"; } + std::string const getName() override { return "firestarter"; } }; - #endif diff --git a/src/strategy/mage/FrostFireMageStrategy.cpp b/src/strategy/mage/FrostFireMageStrategy.cpp index dc01684f..4af7a869 100644 --- a/src/strategy/mage/FrostFireMageStrategy.cpp +++ b/src/strategy/mage/FrostFireMageStrategy.cpp @@ -4,31 +4,56 @@ */ #include "FrostFireMageStrategy.h" - #include "Playerbots.h" -NextAction** FrostFireMageStrategy::getDefaultActions() +// ===== Action Node Factory ===== +class FrostFireMageStrategyActionNodeFactory : public NamedObjectFactory { - 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& 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& 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))); } diff --git a/src/strategy/mage/FrostFireMageStrategy.h b/src/strategy/mage/FrostFireMageStrategy.h index ef6cd9fc..0db6f934 100644 --- a/src/strategy/mage/FrostFireMageStrategy.h +++ b/src/strategy/mage/FrostFireMageStrategy.h @@ -13,20 +13,11 @@ class PlayerbotAI; class FrostFireMageStrategy : public GenericMageStrategy { public: - FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI) {} + FrostFireMageStrategy(PlayerbotAI* botAI); void InitTriggers(std::vector& 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& triggers) override; - std::string const getName() override { return "frostfire aoe"; } -}; - #endif diff --git a/src/strategy/mage/FrostMageStrategy.cpp b/src/strategy/mage/FrostMageStrategy.cpp index 55d84ad0..37a4551b 100644 --- a/src/strategy/mage/FrostMageStrategy.cpp +++ b/src/strategy/mage/FrostMageStrategy.cpp @@ -7,6 +7,7 @@ #include "Playerbots.h" +// ===== Action Node Factory ===== class FrostMageStrategyActionNodeFactory : public NamedObjectFactory { 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& 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& 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))); + } diff --git a/src/strategy/mage/FrostMageStrategy.h b/src/strategy/mage/FrostMageStrategy.h index 17a1fe40..d54c04d3 100644 --- a/src/strategy/mage/FrostMageStrategy.h +++ b/src/strategy/mage/FrostMageStrategy.h @@ -20,13 +20,4 @@ public: NextAction** getDefaultActions() override; }; -class FrostMageAoeStrategy : public CombatStrategy -{ -public: - FrostMageAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "frost aoe"; } -}; - #endif diff --git a/src/strategy/mage/GenericMageNonCombatStrategy.cpp b/src/strategy/mage/GenericMageNonCombatStrategy.cpp index 8e7a475e..24da5125 100644 --- a/src/strategy/mage/GenericMageNonCombatStrategy.cpp +++ b/src/strategy/mage/GenericMageNonCombatStrategy.cpp @@ -4,7 +4,7 @@ */ #include "GenericMageNonCombatStrategy.h" - +#include "AiFactory.h" #include "Playerbots.h" class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory @@ -52,35 +52,23 @@ void GenericMageNonCombatStrategy::InitTriggers(std::vector& 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& 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& 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& 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))); } diff --git a/src/strategy/mage/GenericMageStrategy.cpp b/src/strategy/mage/GenericMageStrategy.cpp index 8821a9e7..921f09ef 100644 --- a/src/strategy/mage/GenericMageStrategy.cpp +++ b/src/strategy/mage/GenericMageStrategy.cpp @@ -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& 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& 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& 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& triggers) { triggers.push_back(new TriggerNode("polymorph", NextAction::array(0, new NextAction("polymorph", 30.0f), nullptr))); } + +void MageAoeStrategy::InitTriggers(std::vector& 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))); + } +} diff --git a/src/strategy/mage/GenericMageStrategy.h b/src/strategy/mage/GenericMageStrategy.h index 4657eb95..66ac8de5 100644 --- a/src/strategy/mage/GenericMageStrategy.h +++ b/src/strategy/mage/GenericMageStrategy.h @@ -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& triggers) override; + std::string const getName() override { return "aoe"; } +}; + #endif diff --git a/src/strategy/mage/MageActions.cpp b/src/strategy/mage/MageActions.cpp index b32c77fa..a2fa4045 100644 --- a/src/strategy/mage/MageActions.cpp +++ b/src/strategy/mage/MageActions.cpp @@ -5,7 +5,7 @@ #include "MageActions.h" #include - +#include "UseItemAction.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "ServerFacade.h" @@ -13,6 +13,42 @@ Value* CastPolymorphAction::GetTargetValue() { return context->GetValue("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); -} \ No newline at end of file +} + +bool CancelChannelAction::Execute(Event event) +{ + if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + { + bot->InterruptSpell(CURRENT_CHANNELED_SPELL); + return true; + } + return false; +} diff --git a/src/strategy/mage/MageActions.h b/src/strategy/mage/MageActions.h index 48efd9e8..cf52a9a8 100644 --- a/src/strategy/mage/MageActions.h +++ b/src/strategy/mage/MageActions.h @@ -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* 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* 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 diff --git a/src/strategy/mage/MageAiObjectContext.cpp b/src/strategy/mage/MageAiObjectContext.cpp index ba1e4e2d..83de0705 100644 --- a/src/strategy/mage/MageAiObjectContext.cpp +++ b/src/strategy/mage/MageAiObjectContext.cpp @@ -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 @@ -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 @@ -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 MageAiObjectContext::sharedStrategyContexts; @@ -309,4 +344,4 @@ void MageAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextLis void MageAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList& valueContexts) { AiObjectContext::BuildSharedValueContexts(valueContexts); -} \ No newline at end of file +} diff --git a/src/strategy/mage/MageTriggers.cpp b/src/strategy/mage/MageTriggers.cpp index 956fa752..4d3a4945 100644 --- a/src/strategy/mage/MageTriggers.cpp +++ b/src/strategy/mage/MageTriggers.cpp @@ -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 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 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 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; +} diff --git a/src/strategy/mage/MageTriggers.h b/src/strategy/mage/MageTriggers.h index 7adf92ec..a8e06317 100644 --- a/src/strategy/mage/MageTriggers.h +++ b/src/strategy/mage/MageTriggers.h @@ -9,11 +9,15 @@ #include "CureTriggers.h" #include "GenericTriggers.h" #include "SharedDefines.h" +#include "Trigger.h" +#include "Playerbots.h" +#include "PlayerbotAI.h" +#include +#include 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 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 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 diff --git a/src/strategy/raids/ulduar/RaidUlduarActionContext.h b/src/strategy/raids/ulduar/RaidUlduarActionContext.h index da16537a..c27a2580 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActionContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarActionContext.h @@ -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 diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 901713be..3d180820 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -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); +} diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.h b/src/strategy/raids/ulduar/RaidUlduarActions.h index d39c6a1b..6fef3367 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.h +++ b/src/strategy/raids/ulduar/RaidUlduarActions.h @@ -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 diff --git a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp index d7e805c0..b7f0ba41 100644 --- a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp @@ -225,6 +225,21 @@ void RaidUlduarStrategy::InitTriggers(std::vector& 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& multipliers) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h index 84cd1104..a412748b 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h @@ -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 diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index 6deb508c..7e726eef 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -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; +} diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.h b/src/strategy/raids/ulduar/RaidUlduarTriggers.h index 987c1ce4..6ebf1a7a 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.h @@ -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