diff --git a/.github/workflows/macos_build.yml b/.github/workflows/macos_build.yml index a0827729..5ea55963 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -1,9 +1,9 @@ name: macos-build on: push: - branches: [never-match-this-branch] + branches: [ "master" ] pull_request: - branches: [never-match-this-branch] + branches: [ "master" ] # concurrency: # group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 57f5548d..518ae5ea 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -80,6 +80,7 @@ AiPlayerbot.Enabled = 1 AiPlayerbot.RandomBotAutologin = 1 # Random bot account +# Please ensure that RandomBotAccountCount is greater than (MaxRandomBots / 10 + AddClassAccountPoolSize) AiPlayerbot.RandomBotAccountCount = 200 # Random bot count @@ -108,12 +109,16 @@ AiPlayerbot.DeleteRandomBotAccounts = 0 AiPlayerbot.MaxAddedBots = 40 # Maximum number of bots per class added by one account -AiPlayerbot.MaxAddedBotsPerClass = 10 +AiPlayerbot.MaxAddedBotsPerClass = 40 # Enable/Disable create bot by addclass command (0 = GM only, 1 = enable) # default: 1 (enable) AiPlayerbot.AddClassCommand = 1 +# Set the addclass command account pool size +# Addclass command uses a subset of accounts from RandomBotAccountCount +AiPlayerbot.AddClassAccountPoolSize = 50 + # Bot group invitation permission level (0 = GM only, 1 = accept based on level, 2 = always accept) # default: 1 (accept based on level) AiPlayerbot.GroupInvitationPermission = 1 @@ -211,6 +216,9 @@ AiPlayerbot.AutoEquipUpgradeLoot = 1 # Default: 1.1 (Equip when the equipment score is 1.1 times higher than the current) AiPlayerbot.EquipUpgradeThreshold = 1.1 +# Two rounds of equipment initialization to create more suitable gear +AiPlayerbot.TwoRoundsGearInit = 0 + # # # @@ -337,6 +345,7 @@ AiPlayerbot.MediumHealth = 65 AiPlayerbot.AlmostFullHealth = 85 AiPlayerbot.LowMana = 15 AiPlayerbot.MediumMana = 40 +AiPlayerbot.HighMana = 65 # # @@ -348,7 +357,7 @@ AiPlayerbot.MediumMana = 40 # # -# Bots pick their quest reward (yes = picks first useful item, no = list all rewards, ask = pick useful item and lists if multiple) +# Bots pick their quest reward (yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple) AiPlayerbot.AutoPickReward = yes # Sync quests with player (Bots will complete quests the moment you hand them in. Bots will ignore looting quest items.) @@ -376,9 +385,11 @@ AiPlayerbot.ApplyInstanceStrategies = 1 # Default: 1 (enable) AiPlayerbot.AutoAvoidAoe = 1 -# Tell which spell is avoiding (experimental) -# Default: 1 (enable) -AiPlayerbot.TellWhenAvoidAoe = 1 +# Only avoid aoe spells with a radius smaller than this value +AiPlayerbot.MaxAoeAvoidRadius = 15.0 + +# A whitelist of aoe spell IDs that should not be avoided +AiPlayerbot.AoeAvoidSpellWhitelist = 50759,57491,13810 # Enable healer bot save mana # Default: 1 (enable) @@ -765,12 +776,12 @@ AiPlayerbot.RandomBotInWorldWithRotationDisabled = 31104000 AiPlayerbot.PremadeSpecName.1.0 = arms pve AiPlayerbot.PremadeSpecGlyph.1.0 = 43418,43395,43423,43399,49084,43421 -AiPlayerbot.PremadeSpecLink.1.0.60 = 3022032023335100202012013031241 -AiPlayerbot.PremadeSpecLink.1.0.80 = 3022032123335100202012013031251-32505010002 +AiPlayerbot.PremadeSpecLink.1.0.60 = 3022032023335100002012211231241 +AiPlayerbot.PremadeSpecLink.1.0.80 = 3022032023335100102012213231251-305-2033 AiPlayerbot.PremadeSpecName.1.1 = fury pve AiPlayerbot.PremadeSpecGlyph.1.1 = 43418,43395,43414,43399,49084,43432 AiPlayerbot.PremadeSpecLink.1.1.60 = -305053000500310053120501351 -AiPlayerbot.PremadeSpecLink.1.1.80 = 30202300233-305053000500310153120511351 +AiPlayerbot.PremadeSpecLink.1.1.80 = 32002300233-305053000500310153120511351 AiPlayerbot.PremadeSpecName.1.2 = prot pve AiPlayerbot.PremadeSpecGlyph.1.2 = 43424,43395,43425,43399,49084,45793 AiPlayerbot.PremadeSpecLink.1.2.60 = --053351225000210521030113321 @@ -815,13 +826,25 @@ AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732 AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243100511351 AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112253100531351-015305021 AiPlayerbot.PremadeSpecName.3.1 = mm pve -AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42915,43351,43338,45732 -AiPlayerbot.PremadeSpecLink.3.1.60 = -015305101230013233135030051 -AiPlayerbot.PremadeSpecLink.3.1.80 = 502-035305101230013233135031351-5000002 +AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732 +AiPlayerbot.PremadeSpecLink.3.1.60 = -025315101030013233125031051 +AiPlayerbot.PremadeSpecLink.3.1.80 = 502-025335101030013233135031351-5000002 AiPlayerbot.PremadeSpecName.3.2 = surv pve AiPlayerbot.PremadeSpecGlyph.3.2 = 42912,43350,45731,43351,43338,45732 -AiPlayerbot.PremadeSpecLink.3.2.60 = --5000032500033330502135001331 -AiPlayerbot.PremadeSpecLink.3.2.80 = -005305101-5000032500033330522135301331 +AiPlayerbot.PremadeSpecLink.3.2.60 = --5000032500033330502135201311 +AiPlayerbot.PremadeSpecLink.3.2.80 = -005305101-5000032500033330532135301321 + +# HUNTER PET +# +# Ferocity +AiPlayerbot.PremadeHunterPetLink.0.16 = 2100003030103010101 +AiPlayerbot.PremadeHunterPetLink.0.20 = 2100013030103010122 +# Tenacity +AiPlayerbot.PremadeHunterPetLink.1.16 = 21103000300120101001 +AiPlayerbot.PremadeHunterPetLink.1.20 = 21303010300120101002 +# Cunning +AiPlayerbot.PremadeHunterPetLink.2.16 = 2100020330000211001 +AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221 # # @@ -889,8 +912,8 @@ AiPlayerbot.PremadeSpecLink.6.1.60 = -32003350332203012300023101351 AiPlayerbot.PremadeSpecLink.6.1.80 = -32002350352203012300033101351-230200305003 AiPlayerbot.PremadeSpecName.6.2 = unholy pve AiPlayerbot.PremadeSpecGlyph.6.2 = 43542,43673,45804,43544,43672,43549 -AiPlayerbot.PremadeSpecLink.6.2.60 = --2300303050032152000150213130051 -AiPlayerbot.PremadeSpecLink.6.2.80 = -320053500002-2300303050032152000150213130051 +AiPlayerbot.PremadeSpecLink.6.2.60 = --2301303050032151000150013131151 +AiPlayerbot.PremadeSpecLink.6.2.80 = -320033500002-2301303050032151000150013133151 AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve AiPlayerbot.PremadeSpecGlyph.6.3 = 45805,43673,43827,43544,43672,43554 AiPlayerbot.PremadeSpecLink.6.3.60 = 005512153330030320102013-305 @@ -911,7 +934,7 @@ AiPlayerbot.PremadeSpecGlyph.7.0 = 41536,43385,41532,43386,44923,45776 AiPlayerbot.PremadeSpecLink.7.0.60 = 4530001520213351102301351 AiPlayerbot.PremadeSpecLink.7.0.80 = 3530001523213351322301351-005050031 AiPlayerbot.PremadeSpecName.7.1 = enh pve -AiPlayerbot.PremadeSpecGlyph.7.1 = 41530,43385,41539,43386,44923,41540 +AiPlayerbot.PremadeSpecGlyph.7.1 = 41542,43385,41539,43386,44923,45771 AiPlayerbot.PremadeSpecLink.7.1.60 = -30205033005001333031131131051 AiPlayerbot.PremadeSpecLink.7.1.80 = 053030052-30205033005021333031131131051 AiPlayerbot.PremadeSpecName.7.2 = resto pve @@ -959,7 +982,7 @@ AiPlayerbot.PremadeSpecLink.9.0.70 = 2350022001113510053500131151--55 AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001113510253500331151--5500000501 AiPlayerbot.PremadeSpecName.9.1 = emo pve AiPlayerbot.PremadeSpecGlyph.9.1 = 45785,43390,50077,43394,43393,42459 -AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530131201-55 +AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051 AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55 AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005 AiPlayerbot.PremadeSpecName.9.2 = destro pve @@ -991,7 +1014,7 @@ AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031501531050013051 AiPlayerbot.PremadeSpecLink.11.2.80 = 05320001--230033312031512531153313051 AiPlayerbot.PremadeSpecName.11.3 = cat pve AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,44922,45604 -AiPlayerbot.PremadeSpecLink.11.3.60 = -553202032322010052100030310501 +AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501 AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012 # @@ -1159,11 +1182,11 @@ AiPlayerbot.RandomClassSpecIndex.8.2 = 2 # # -AiPlayerbot.RandomClassSpecProb.9.0 = 40 +AiPlayerbot.RandomClassSpecProb.9.0 = 45 AiPlayerbot.RandomClassSpecIndex.9.0 = 0 -AiPlayerbot.RandomClassSpecProb.9.1 = 40 +AiPlayerbot.RandomClassSpecProb.9.1 = 45 AiPlayerbot.RandomClassSpecIndex.9.1 = 1 -AiPlayerbot.RandomClassSpecProb.9.2 = 20 +AiPlayerbot.RandomClassSpecProb.9.2 = 10 AiPlayerbot.RandomClassSpecIndex.9.2 = 2 # @@ -1178,11 +1201,12 @@ AiPlayerbot.RandomClassSpecIndex.9.2 = 2 AiPlayerbot.RandomClassSpecProb.11.0 = 20 AiPlayerbot.RandomClassSpecIndex.11.0 = 0 -AiPlayerbot.RandomClassSpecProb.11.1 = 40 +AiPlayerbot.RandomClassSpecProb.11.1 = 25 AiPlayerbot.RandomClassSpecIndex.11.1 = 1 -AiPlayerbot.RandomClassSpecProb.11.2 = 40 +AiPlayerbot.RandomClassSpecProb.11.2 = 35 AiPlayerbot.RandomClassSpecIndex.11.2 = 2 - +AiPlayerbot.RandomClassSpecProb.11.3 = 20 +AiPlayerbot.RandomClassSpecIndex.11.3 = 3 # # # @@ -1259,6 +1283,10 @@ AiPlayerbot.LogInGroupOnly = 1 AiPlayerbot.LogValuesPerTick = 0 AiPlayerbot.RandomChangeMultiplier = 1 +# Tell which spell is avoiding (experimental) +# Default: 0 (disable) +AiPlayerbot.TellWhenAvoidAoe = 0 + # Enables/Disables performance monitor AiPlayerbot.PerfMonEnabled = 0 diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index 05f2daec..d107b0dc 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -71,7 +71,6 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot) max = tabs[i]; } } - return tab; } else @@ -481,11 +480,11 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2)) engine->addStrategiesNoInit("caster", "caster aoe", nullptr); - if (player->getClass() == CLASS_DRUID && tab == 1) - engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr); + // if (player->getClass() == CLASS_DRUID && tab == 1) + // engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr); - if (player->getClass() == CLASS_ROGUE) - engine->addStrategiesNoInit(/*"behind",*/ "stealth", nullptr); + // if (player->getClass() == CLASS_ROGUE) + // engine->addStrategiesNoInit(/*"behind",*/ "stealth", nullptr); } } diff --git a/src/ChatHelper.cpp b/src/ChatHelper.cpp index 156dc0ea..8f464d5d 100644 --- a/src/ChatHelper.cpp +++ b/src/ChatHelper.cpp @@ -593,34 +593,41 @@ void ChatHelper::eraseAllSubStr(std::string& mainStr, std::string const toErase) } } -std::set ChatHelper::ExtractAllQuestIds(const std::string& text) +std::set extractGeneric(std::string_view text, std::string_view prefix) { - std::set ids; + std::set ids; + std::string_view text_view = text; - std::regex rgx("Hquest:[0-9]+"); - auto begin = std::sregex_iterator(text.begin(), text.end(), rgx); - auto end = std::sregex_iterator(); - for (std::sregex_iterator i = begin; i != end; ++i) + size_t pos = 0; + while ((pos = text_view.find(prefix, pos)) != std::string::npos) { - std::smatch match = *i; - ids.insert(std::stoi(match.str().erase(0, 7))); + // skip "Hquest:/Hitem:" + pos += prefix.size(); + + // extract everything after "Hquest:/Hitem:" + size_t end_pos = text_view.find_first_not_of("0123456789", pos); + std::string_view number_str = text_view.substr(pos, end_pos - pos); + + uint32 number = 0; + + auto [ptr, ec] = std::from_chars(number_str.data(), number_str.data() + number_str.size(), number); + + if (ec == std::errc()) + { + ids.insert(number); + } + pos = end_pos; } return ids; } +std::set ChatHelper::ExtractAllQuestIds(const std::string& text) +{ + return extractGeneric(text, "Hquest:"); +} + std::set ChatHelper::ExtractAllItemIds(const std::string& text) { - std::set ids; - - std::regex rgx("Hitem:[0-9]+"); - auto begin = std::sregex_iterator(text.begin(), text.end(), rgx); - auto end = std::sregex_iterator(); - for (std::sregex_iterator i = begin; i != end; ++i) - { - std::smatch match = *i; - ids.insert(std::stoi(match.str().erase(0, 6))); - } - - return ids; + return extractGeneric(text, "Hitem:"); } diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 07b07293..453544ff 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -310,24 +310,23 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) bot->SetPower(bot->getPowerType(), bot->GetMaxPower(bot->getPowerType())); } - if (!CanUpdateAI()) - return; - - // check activity AllowActivity(); - // if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { - // return; - // } Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); - if (currentSpell && currentSpell->getState() == SPELL_STATE_CASTING && currentSpell->GetCastTime()) + if (currentSpell && currentSpell->getState() == SPELL_STATE_PREPARING) { - nextAICheckDelay = currentSpell->GetCastTime() + sPlayerbotAIConfig->reactDelay; - SetNextCheckDelay(nextAICheckDelay); - if (!CanUpdateAI()) - return; + if (currentSpell->m_targets.GetUnitTarget() && !currentSpell->m_targets.GetUnitTarget()->IsAlive() && + currentSpell->GetSpellInfo() && !currentSpell->GetSpellInfo()->IsAllowingDeadTarget()) + { + bot->InterruptSpell(CURRENT_GENERIC_SPELL); + SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); + } + return; } + if (!CanUpdateAI()) + return; + if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup()) { Player* leader = bot->GetGroup()->GetLeader(); @@ -898,9 +897,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) p >> casterGuid.ReadAsPacked(); if (casterGuid != bot->GetGUID()) return; - + uint8 count, result; uint32 spellId; - p >> spellId; + p >> count >> spellId >> result; SpellInterrupted(spellId); return; } @@ -1005,26 +1004,25 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) if (lang == LANG_ADDON) return; - // Disable since ExtractAllItemIds bad performance - // if (message.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) && - // (GetChatHelper()->ExtractAllItemIds(message).size() > 0 || - // GetChatHelper()->ExtractAllQuestIds(message).size() > 0) && - // sPlayerbotAIConfig->toxicLinksRepliesChance) - // { - // if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig->toxicLinksRepliesChance) - // { - // return; - // } - // } - // else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) && - // sPlayerbotAIConfig->thunderfuryRepliesChance)) - // { - // if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig->thunderfuryRepliesChance) - // { - // return; - // } - // } - // else + if (message.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) && + (GetChatHelper()->ExtractAllItemIds(message).size() > 0 || + GetChatHelper()->ExtractAllQuestIds(message).size() > 0) && + sPlayerbotAIConfig->toxicLinksRepliesChance) + { + if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig->toxicLinksRepliesChance) + { + return; + } + } + else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) && + sPlayerbotAIConfig->thunderfuryRepliesChance)) + { + if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig->thunderfuryRepliesChance) + { + return; + } + } + else { if (isFromFreeBot && urand(0, 20)) return; @@ -1135,21 +1133,15 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) void PlayerbotAI::SpellInterrupted(uint32 spellid) { + for (uint8 type = CURRENT_MELEE_SPELL; type < CURRENT_CHANNELED_SPELL; type++) + { + Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type); + if (!spell) + continue; + if (spell->GetSpellInfo()->Id == spellid) + bot->InterruptSpell((CurrentSpellTypes)type); + } LastSpellCast& lastSpell = aiObjectContext->GetValue("last spell cast")->Get(); - if (!spellid || lastSpell.id != spellid) - return; - - time_t now = time(nullptr); - if (now <= lastSpell.timer) - return; - - uint32 castTimeSpent = 1000 * (now - lastSpell.timer); - int32 globalCooldown = CalculateGlobalCooldown(lastSpell.id); - if (static_cast(castTimeSpent) < globalCooldown) - SetNextCheckDelay(globalCooldown - castTimeSpent); - else - SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); - lastSpell.id = 0; } @@ -1507,17 +1499,21 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 409: strategyName = "mc"; break; + case 509: + strategyName = "aq20"; + break; default: break; } - + if (strategyName.empty()) + return; engines[BOT_STATE_COMBAT]->addStrategy(strategyName); engines[BOT_STATE_NON_COMBAT]->addStrategy(strategyName); if (tellMaster && !strategyName.empty()) { std::ostringstream out; out << "Add " << strategyName << " instance strategy"; - TellMaster(out.str()); + TellMasterNoFacing(out.str()); } } @@ -1628,10 +1624,10 @@ void PlayerbotAI::ResetStrategies(bool load) // sPlayerbotDbStore->Load(this); } -bool PlayerbotAI::IsRanged(Player* player) +bool PlayerbotAI::IsRanged(Player* player, bool bySpec) { PlayerbotAI* botAi = GET_PLAYERBOT_AI(player); - if (botAi) + if (!bySpec && botAi) return botAi->ContainsStrategy(STRATEGY_TYPE_RANGED); int tab = AiFactory::GetPlayerSpecTab(player); @@ -1664,18 +1660,18 @@ bool PlayerbotAI::IsRanged(Player* player) return true; } -bool PlayerbotAI::IsMelee(Player* player) { return !IsRanged(player); } +bool PlayerbotAI::IsMelee(Player* player, bool bySpec) { return !IsRanged(player, bySpec); } -bool PlayerbotAI::IsCaster(Player* player) { return IsRanged(player) && player->getClass() != CLASS_HUNTER; } +bool PlayerbotAI::IsCaster(Player* player, bool bySpec) { return IsRanged(player, bySpec) && player->getClass() != CLASS_HUNTER; } -bool PlayerbotAI::IsCombo(Player* player) +bool PlayerbotAI::IsCombo(Player* player, bool bySpec) { // int tab = AiFactory::GetPlayerSpecTab(player); return player->getClass() == CLASS_ROGUE || (player->getClass() == CLASS_DRUID && player->HasAura(768)); // cat druid } -bool PlayerbotAI::IsRangedDps(Player* player) { return IsRanged(player) && IsDps(player); } +bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(player, bySpec) && IsDps(player, bySpec); } bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) { @@ -1898,10 +1894,10 @@ int32 PlayerbotAI::GetMeleeIndex(Player* player) return 0; } -bool PlayerbotAI::IsTank(Player* player) +bool PlayerbotAI::IsTank(Player* player, bool bySpec) { PlayerbotAI* botAi = GET_PLAYERBOT_AI(player); - if (botAi) + if (!bySpec && botAi) return botAi->ContainsStrategy(STRATEGY_TYPE_TANK); int tab = AiFactory::GetPlayerSpecTab(player); @@ -1936,10 +1932,10 @@ bool PlayerbotAI::IsTank(Player* player) return false; } -bool PlayerbotAI::IsHeal(Player* player) +bool PlayerbotAI::IsHeal(Player* player, bool bySpec) { PlayerbotAI* botAi = GET_PLAYERBOT_AI(player); - if (botAi) + if (!bySpec && botAi) return botAi->ContainsStrategy(STRATEGY_TYPE_HEAL); int tab = AiFactory::GetPlayerSpecTab(player); @@ -1973,10 +1969,10 @@ bool PlayerbotAI::IsHeal(Player* player) return false; } -bool PlayerbotAI::IsDps(Player* player) +bool PlayerbotAI::IsDps(Player* player, bool bySpec) { PlayerbotAI* botAi = GET_PLAYERBOT_AI(player); - if (botAi) + if (!bySpec && botAi) return botAi->ContainsStrategy(STRATEGY_TYPE_DPS); int tab = AiFactory::GetPlayerSpecTab(player); @@ -1998,6 +1994,10 @@ bool PlayerbotAI::IsDps(Player* player) { return true; } + if (tab == DRUID_TAB_FERAL && !IsTank(player)) + { + return true; + } break; case CLASS_SHAMAN: if (tab != SHAMAN_TAB_RESTORATION) @@ -2792,8 +2792,8 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell, } uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration(); - bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT; - if ((CastingTime || interruptOnMove) && bot->isMoving()) + // bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT; + if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving()) { if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) { @@ -3610,6 +3610,7 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo void PlayerbotAI::WaitForSpellCast(Spell* spell) { + return; SpellInfo const* spellInfo = spell->GetSpellInfo(); uint32 castTime = spell->GetCastTime(); // float castTime = spell->GetCastTime(); @@ -4319,7 +4320,7 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vectorInventoryType; - uint32 level = mixed ? proto->ItemLevel * (1 + proto->Quality) : proto->ItemLevel; + uint32 level = mixed ? proto->ItemLevel * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(proto->Quality)) : proto->ItemLevel; switch (type) { @@ -5174,6 +5175,32 @@ uint32 PlayerbotAI::GetBuffedCount(Player* player, std::string const spellname) return bcount; } +int32 PlayerbotAI::GetNearGroupMemberCount(float dis) +{ + int count = 1; // yourself + if (Group* group = bot->GetGroup()) + { + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (member == bot) // calculated + continue; + + if (!member || !member->IsInWorld()) + continue; + + if (member->GetMapId() != bot->GetMapId()) + continue; + + if (member->GetExactDist(bot) > dis) + continue; + + count++; + } + } + return count; +} + bool PlayerbotAI::CanMove() { // do not allow if not vehicle driver @@ -5224,6 +5251,7 @@ bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2) return true; } +// A custom CanEquipItem (remove AutoUnequipOffhand in FindEquipSlot to prevent unequip on `item usage` calculation) InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading) const { dest = 0; @@ -5244,11 +5272,9 @@ InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, if (pItem->IsBindedNotWith(bot)) return EQUIP_ERR_DONT_OWN_THAT_ITEM; - // Yunfan: skip it - // // check count of items (skip for auto move for same player from bank) - // InventoryResult res = bot->CanTakeMoreSimilarItems(pItem); - // if (res != EQUIP_ERR_OK) - // return res; + InventoryResult res = bot->CanTakeMoreSimilarItems(pItem); + if (res != EQUIP_ERR_OK) + return res; ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution @@ -5267,7 +5293,7 @@ InventoryResult PlayerbotAI::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, if (!bot->CanUseAttackType(bot->GetAttackBySlot(eslot))) return EQUIP_ERR_NOT_WHILE_DISARMED; - InventoryResult res = bot->CanUseItem(pItem, not_loading); + res = bot->CanUseItem(pItem, not_loading); if (res != EQUIP_ERR_OK) return res; @@ -5804,4 +5830,33 @@ void PlayerbotAI::PetFollow() charmInfo->RemoveStayPosition(); charmInfo->SetForcedSpell(0); charmInfo->SetForcedTargetGUID(); +} + +float PlayerbotAI::GetItemScoreMultiplier(ItemQualities quality) +{ + switch (quality) + { + // each quality increase 1.1x + case ITEM_QUALITY_POOR: + return 1.0f; + break; + case ITEM_QUALITY_NORMAL: + return 1.1f; + break; + case ITEM_QUALITY_UNCOMMON: + return 1.21f; + break; + case ITEM_QUALITY_RARE: + return 1.331f; + break; + case ITEM_QUALITY_EPIC: + return 1.4641f; + break; + case ITEM_QUALITY_LEGENDARY: + return 1.61051f; + break; + default: + break; + } + return 1.0f; } \ No newline at end of file diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 61242629..062b7d60 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -401,14 +401,14 @@ public: void ResetStrategies(bool load = false); void ReInitCurrentEngine(); void Reset(bool full = false); - static bool IsTank(Player* player); - static bool IsHeal(Player* player); - static bool IsDps(Player* player); - static bool IsRanged(Player* player); - static bool IsMelee(Player* player); - static bool IsCaster(Player* player); - static bool IsCombo(Player* player); - static bool IsRangedDps(Player* player); + static bool IsTank(Player* player, bool bySpec = false); + static bool IsHeal(Player* player, bool bySpec = false); + static bool IsDps(Player* player, bool bySpec = false); + static bool IsRanged(Player* player, bool bySpec = false); + static bool IsMelee(Player* player, bool bySpec = false); + static bool IsCaster(Player* player, bool bySpec = false); + static bool IsCombo(Player* player, bool bySpec = false); + static bool IsRangedDps(Player* player, bool bySpec = false); static bool IsMainTank(Player* player); bool IsAssistTank(Player* player); bool IsAssistTankOfIndex(Player* player, int index); @@ -469,6 +469,7 @@ public: void ImbueItem(Item* item); void EnchantItemT(uint32 spellid, uint8 slot); uint32 GetBuffedCount(Player* player, std::string const spellname); + int32 GetNearGroupMemberCount(float dis = sPlayerbotAIConfig->sightDistance); virtual bool CanCastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr); virtual bool CastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr); @@ -568,6 +569,7 @@ public: std::set GetAllCurrentQuestIds(); std::set GetCurrentIncompleteQuestIds(); void PetFollow(); + static float GetItemScoreMultiplier(ItemQualities quality); private: static void _fillGearScoreData(Player* player, Item* item, std::vector* gearScore, uint32& twoHandScore, diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 41c0360c..0e111958 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -13,6 +13,7 @@ #include "Playerbots.h" #include "RandomItemMgr.h" #include "RandomPlayerbotFactory.h" +#include "RandomPlayerbotMgr.h" #include "Talentspec.h" template @@ -28,6 +29,19 @@ void LoadList(std::string const value, T& list) } } +template +void LoadSet(std::string const value, T& set) +{ + std::vector ids = split(value, ','); + for (std::vector::iterator i = ids.begin(); i != ids.end(); i++) + { + uint32 id = atoi((*i).c_str()); + // if (!id) + // continue; + set.insert(id); + } +} + template void LoadListString(std::string const value, T& list) { @@ -93,10 +107,14 @@ bool PlayerbotAIConfig::Initialize() almostFullHealth = sConfigMgr->GetOption("AiPlayerbot.AlmostFullHealth", 85); lowMana = sConfigMgr->GetOption("AiPlayerbot.LowMana", 15); mediumMana = sConfigMgr->GetOption("AiPlayerbot.MediumMana", 40); + highMana = sConfigMgr->GetOption("AiPlayerbot.HighMana", 65); autoSaveMana = sConfigMgr->GetOption("AiPlayerbot.AutoSaveMana", true); saveManaThreshold = sConfigMgr->GetOption("AiPlayerbot.SaveManaThreshold", 60); autoAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.AutoAvoidAoe", true); - tellWhenAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.TellWhenAvoidAoe", true); + maxAoeAvoidRadius = sConfigMgr->GetOption("AiPlayerbot.MaxAoeAvoidRadius", 15.0f); + LoadSet>(sConfigMgr->GetOption("AiPlayerbot.AoeAvoidSpellWhitelist", "50759,57491,13810"), + aoeAvoidSpellWhitelist); + tellWhenAvoidAoe = sConfigMgr->GetOption("AiPlayerbot.TellWhenAvoidAoe", false); randomGearLoweringChance = sConfigMgr->GetOption("AiPlayerbot.RandomGearLoweringChance", 0.0f); randomGearQualityLimit = sConfigMgr->GetOption("AiPlayerbot.RandomGearQualityLimit", 3); @@ -330,14 +348,32 @@ bool PlayerbotAIConfig::Initialize() } } for (uint32 spec = 0; spec < 3; ++spec) + { + for (uint32 points = 0; points < 21; ++points) + { + std::ostringstream os; + os << "AiPlayerbot.PremadeHunterPetLink." << spec << "." << points; + premadeHunterPetLink[spec][points] = sConfigMgr->GetOption(os.str().c_str(), "", false); + parsedHunterPetLinkOrder[spec][points] = + ParseTempPetTalentsOrder(spec, premadeHunterPetLink[spec][points]); + } + } + for (uint32 spec = 0; spec < MAX_SPECNO; ++spec) { std::ostringstream os; os << "AiPlayerbot.RandomClassSpecProb." << cls << "." << spec; - randomClassSpecProb[cls][spec] = sConfigMgr->GetOption(os.str().c_str(), 33); + uint32 def; + if (spec <= 1) + def = 33; + else if (spec == 2) + def = 34; + else + def = 0; + randomClassSpecProb[cls][spec] = sConfigMgr->GetOption(os.str().c_str(), def, false); os.str(""); os.clear(); os << "AiPlayerbot.RandomClassSpecIndex." << cls << "." << spec; - randomClassSpecIndex[cls][spec] = sConfigMgr->GetOption(os.str().c_str(), spec + 1); + randomClassSpecIndex[cls][spec] = sConfigMgr->GetOption(os.str().c_str(), spec, false); } } @@ -420,6 +456,7 @@ bool PlayerbotAIConfig::Initialize() maxAddedBots = sConfigMgr->GetOption("AiPlayerbot.MaxAddedBots", 40); maxAddedBotsPerClass = sConfigMgr->GetOption("AiPlayerbot.MaxAddedBotsPerClass", 10); addClassCommand = sConfigMgr->GetOption("AiPlayerbot.AddClassCommand", 1); + addClassAccountPoolSize = sConfigMgr->GetOption("AiPlayerbot.AddClassAccountPoolSize", 50); maintenanceCommand = sConfigMgr->GetOption("AiPlayerbot.MaintenanceCommand", 1); autoGearCommand = sConfigMgr->GetOption("AiPlayerbot.AutoGearCommand", 1); autoGearQualityLimit = sConfigMgr->GetOption("AiPlayerbot.AutoGearQualityLimit", 3); @@ -450,6 +487,7 @@ bool PlayerbotAIConfig::Initialize() autoPickReward = sConfigMgr->GetOption("AiPlayerbot.AutoPickReward", "yes"); autoEquipUpgradeLoot = sConfigMgr->GetOption("AiPlayerbot.AutoEquipUpgradeLoot", true); equipUpgradeThreshold = sConfigMgr->GetOption("AiPlayerbot.EquipUpgradeThreshold", 1.1f); + twoRoundsGearInit = sConfigMgr->GetOption("AiPlayerbot.TwoRoundsGearInit", false); syncQuestWithPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestWithPlayer", true); syncQuestForPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestForPlayer", false); autoTrainSpells = sConfigMgr->GetOption("AiPlayerbot.AutoTrainSpells", "yes"); @@ -478,6 +516,9 @@ bool PlayerbotAIConfig::Initialize() { return true; } + if (sPlayerbotAIConfig->addClassCommand) + sRandomPlayerbotMgr->PrepareAddclassCache(); + sRandomItemMgr->Init(); sRandomItemMgr->InitAfterAhBot(); sPlayerbotTextMgr->LoadBotTexts(); @@ -747,3 +788,48 @@ std::vector> PlayerbotAIConfig::ParseTempTalentsOrder(uint32 } return res; } + +std::vector> PlayerbotAIConfig::ParseTempPetTalentsOrder(uint32 spec, std::string tab_link) +{ + // check bad link + // uint32 classMask = 1 << (cls - 1); + std::vector spells; + std::vector> orders; + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) + { + TalentEntry const* talentInfo = sTalentStore.LookupEntry(i); + if (!talentInfo) + continue; + + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + if (!talentTabInfo) + continue; + + if (!((1 << spec) & talentTabInfo->petTalentMask)) + continue; + // skip some duplicate spells like dash/dive + if (talentInfo->TalentID == 2201 || talentInfo->TalentID == 2208 || talentInfo->TalentID == 2219 || + talentInfo->TalentID == 2203) + continue; + + spells.push_back(talentInfo); + } + std::sort(spells.begin(), spells.end(), + [&](TalentEntry const* lhs, TalentEntry const* rhs) + { return lhs->Row != rhs->Row ? lhs->Row < rhs->Row : lhs->Col < rhs->Col; }); + for (int i = 0; i < tab_link.size(); i++) + { + if (i >= spells.size()) + { + break; + } + int lvl = tab_link[i] - '0'; + if (lvl == 0) + continue; + orders.push_back({spells[i]->Row, spells[i]->Col, (uint32)lvl}); + } + // sort by talent tab size + std::sort(orders.begin(), orders.end(), [&](auto& lhs, auto& rhs) { return lhs.size() > rhs.size(); }); + + return orders; +} diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 21dbee67..6caf1179 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -63,10 +63,12 @@ public: tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance, targetPosRecalcDistance, farDistance, healDistance, aggroDistance; uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth; - uint32 lowMana, mediumMana; + uint32 lowMana, mediumMana, highMana; bool autoSaveMana; uint32 saveManaThreshold; bool autoAvoidAoe; + float maxAoeAvoidRadius; + std::set aoeAvoidSpellWhitelist; bool tellWhenAvoidAoe; uint32 openGoSpell; @@ -195,7 +197,9 @@ public: std::string premadeSpecGlyph[MAX_CLASSES][MAX_SPECNO]; std::vector parsedSpecGlyph[MAX_CLASSES][MAX_SPECNO]; std::string premadeSpecLink[MAX_CLASSES][MAX_SPECNO][MAX_LEVEL]; + std::string premadeHunterPetLink[3][21]; std::vector> parsedSpecLinkOrder[MAX_CLASSES][MAX_SPECNO][MAX_LEVEL]; + std::vector> parsedHunterPetLinkOrder[3][21]; uint32 randomClassSpecProb[MAX_CLASSES][MAX_SPECNO]; uint32 randomClassSpecIndex[MAX_CLASSES][MAX_SPECNO]; @@ -269,6 +273,7 @@ public: std::string autoPickReward; bool autoEquipUpgradeLoot; float equipUpgradeThreshold; + bool twoRoundsGearInit; bool syncQuestWithPlayer; bool syncQuestForPlayer; std::string autoTrainSpells; @@ -306,6 +311,7 @@ public: float autoInitEquipLevelLimitRatio; int32 maxAddedBots, maxAddedBotsPerClass; int32 addClassCommand; + int32 addClassAccountPoolSize; int32 maintenanceCommand; int32 autoGearCommand, autoGearQualityLimit, autoGearScoreLimit; @@ -324,6 +330,7 @@ public: void loadWorldBuf(uint32 factionId, uint32 classId, uint32 minLevel, uint32 maxLevel); static std::vector> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order); + static std::vector> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order); }; #define sPlayerbotAIConfig PlayerbotAIConfig::instance() diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 46f884f8..d1b92d7a 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -18,12 +18,14 @@ #include "Group.h" #include "GroupMgr.h" #include "ObjectAccessor.h" +#include "ObjectGuid.h" #include "ObjectMgr.h" #include "PlayerbotAIConfig.h" #include "PlayerbotDbStore.h" #include "PlayerbotFactory.h" #include "PlayerbotSecurity.h" #include "Playerbots.h" +#include "RandomPlayerbotMgr.h" #include "SharedDefines.h" #include "WorldSession.h" #include "ChannelMgr.h" @@ -126,6 +128,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con allowed = false; out << "Failure: You are not allowed to control bot " << bot->GetName().c_str(); } + if (allowed && masterSession) { Player* player = masterSession->GetPlayer(); @@ -143,6 +146,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con out << "Failure: You have added too many bots for this class"; } } + if (allowed) { sRandomPlayerbotMgr->OnPlayerLogin(bot); @@ -722,14 +726,14 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); factory.Randomize(false); - return "ok, gear score limit: " + std::to_string(mixedGearScore / (ITEM_QUALITY_EPIC + 1)) + + return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + "(for epic)"; } else if (cmd.starts_with("init=") && sscanf(cmd.c_str(), "init=%d", &gs) != -1) { PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs); factory.Randomize(false); - return "ok, gear score limit: " + std::to_string(gs / (ITEM_QUALITY_EPIC + 1)) + "(for epic)"; + return "ok, gear score limit: " + std::to_string(gs / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + "(for epic)"; } } @@ -843,6 +847,22 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (!strncmp(cmd, "initself=", 9)) { + if (!strcmp(cmd, "initself=uncommon")) + { + if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + { + // OnBotLogin(master); + PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON); + factory.Randomize(false); + messages.push_back("initself ok"); + return messages; + } + else + { + messages.push_back("ERROR: Only GM can use this command."); + return messages; + } + } if (!strcmp(cmd, "initself=rare")) { if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) @@ -875,6 +895,22 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg return messages; } } + if (!strcmp(cmd, "initself=legendary")) + { + if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + { + // OnBotLogin(master); + PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY); + factory.Randomize(false); + messages.push_back("initself ok"); + return messages; + } + else + { + messages.push_back("ERROR: Only GM can use this command."); + return messages; + } + } int32 gs; if (sscanf(cmd, "initself=%d", &gs) != -1) { @@ -1003,44 +1039,20 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg messages.push_back("Error: Invalid Class. Try again."); return messages; } - uint8 master_race = master->getRace(); - std::string race_limit; - switch (master_race) + uint8 teamId = master->GetTeamId(true); + std::vector &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)]; + for (size_t i = 0; i < guidCache.size(); i++) { - case 1: - case 3: - case 4: - case 7: - case 11: - race_limit = "1, 3, 4, 7, 11"; - break; - case 2: - case 5: - case 6: - case 8: - case 10: - race_limit = "2, 5, 6, 8, 10"; - break; - } - uint32 maxAccountId = sPlayerbotAIConfig->randomBotAccounts.back(); - // find a bot fit conditions and not in any guild - QueryResult results = CharacterDatabase.Query( - "SELECT guid FROM characters " - "WHERE name IN (SELECT name FROM playerbots_names) AND class = '{}' AND online = 0 AND race IN ({}) AND " - "guid NOT IN ( SELECT guid FROM guild_member ) " - "AND account <= {} " - "ORDER BY account DESC LIMIT 1", - claz, race_limit, maxAccountId); - if (results) - { - Field* fields = results->Fetch(); - ObjectGuid guid = ObjectGuid(HighGuid::Player, fields[0].Get()); + ObjectGuid guid = guidCache[i]; + if (botLoading.find(guid) != botLoading.end()) + continue; + if (ObjectAccessor::FindConnectedPlayer(guid)) + continue; AddPlayerBot(guid, master->GetSession()->GetAccountId()); - messages.push_back("Add class " + std::string(charname)); return messages; } - messages.push_back("Add class failed."); + messages.push_back("Add class failed, no available characters!"); return messages; } @@ -1295,7 +1307,7 @@ uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { Player* const bot = it->second; - if (bot->getClass() == cls) + if (bot && bot->IsInWorld() && bot->getClass() == cls) { count++; } diff --git a/src/RandomItemMgr.cpp b/src/RandomItemMgr.cpp index 2d9f3d5b..157ca308 100644 --- a/src/RandomItemMgr.cpp +++ b/src/RandomItemMgr.cpp @@ -769,49 +769,61 @@ bool RandomItemMgr::CanEquipWeapon(uint8 clazz, ItemTemplate const* proto) { case CLASS_PRIEST: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && - proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE) + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER) return false; break; case CLASS_MAGE: case CLASS_WARLOCK: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && - proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) return false; break; case CLASS_WARRIOR: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && - proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && - proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_PALADIN: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) + case CLASS_DEATH_KNIGHT: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) return false; break; case CLASS_SHAMAN: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_DRUID: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_HUNTER: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && - proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW) + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_ROGUE: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && - proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && - proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && - proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE) return false; break; } @@ -972,7 +984,8 @@ void RandomItemMgr::BuildItemInfoCache() strstr(proto->Name1.c_str(), " TEST") || strstr(proto->Name1.c_str(), "2200 ") || strstr(proto->Name1.c_str(), "Deprecated ") || strstr(proto->Name1.c_str(), "Unused ") || strstr(proto->Name1.c_str(), "Monster ") || strstr(proto->Name1.c_str(), "[PH]") || - strstr(proto->Name1.c_str(), "(OLD)") || strstr(proto->Name1.c_str(), "QR")) + strstr(proto->Name1.c_str(), "(OLD)") || strstr(proto->Name1.c_str(), "QR") || + strstr(proto->Name1.c_str(), "zzOLD")) { itemForTest.insert(proto->ItemId); continue; diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 80f090ce..3595190b 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -370,7 +370,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) break; } - if (loginBots) + if (loginBots && botLoading.empty()) { loginBots += updateBots; loginBots = std::min(loginBots, maxNewBots); @@ -1041,6 +1041,9 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) SetEventValue(bot, "login", 0, 0); + if (!player->IsInWorld()) + return false; + if (player->GetGroup() || player->HasUnitState(UNIT_STATE_IN_FLIGHT)) return false; @@ -1473,40 +1476,44 @@ void RandomPlayerbotMgr::PrepareTeleportCache() } while (results->NextRow()); } LOG_INFO("playerbots", "{} banker locations for level collected.", collected_locs); +} - // temporary only use locsPerLevelCache, so disable rpgLocsCacheLevel cache - - // LOG_INFO("playerbots", "Preparing RPG teleport caches for {} factions...", sFactionTemplateStore.GetNumRows()); - // results = WorldDatabase.Query("SELECT map, position_x, position_y, position_z, r.race, r.minl, r.maxl FROM - // creature c INNER JOIN playerbots_rpg_races r ON c.id1 = r.entry " - // "WHERE r.race < 15"); - // if (results) - // { - // do - // { - // Field* fields = results->Fetch(); - // uint16 mapId = fields[0].Get(); - // float x = fields[1].Get(); - // float y = fields[2].Get(); - // float z = fields[3].Get(); - // uint32 race = fields[4].Get(); - // uint32 minl = fields[5].Get(); - // uint32 maxl = fields[6].Get(); - - // for (uint32 level = 1; level < sPlayerbotAIConfig->randomBotMaxLevel + 1; level++) - // { - // if (level > maxl || level < minl) - // continue; - - // WorldLocation loc(mapId, x, y, z, 0); - // for (uint32 r = 1; r < MAX_RACES; r++) - // { - // if (race == r || race == 0) - // rpgLocsCacheLevel[r][level].push_back(loc); - // } - // } - // } while (results->NextRow()); - // } +void RandomPlayerbotMgr::PrepareAddclassCache() +{ + int32 maxAccountId = sPlayerbotAIConfig->randomBotAccounts.back(); + int32 minIdx = + sPlayerbotAIConfig->randomBotAccounts.size() - 1 >= sPlayerbotAIConfig->addClassAccountPoolSize + ? sPlayerbotAIConfig->randomBotAccounts.size() - sPlayerbotAIConfig->addClassAccountPoolSize : 0; + int32 minAccountId = sPlayerbotAIConfig->randomBotAccounts[minIdx]; + if (minAccountId < 0) + { + LOG_ERROR("playerbots", "No available account for add class!"); + } + int32 collected = 0; + 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 account <= {} AND class = '{}' AND online = 0 AND " + "guid NOT IN ( SELECT guid FROM guild_member ) " + "ORDER BY account DESC", + minAccountId, maxAccountId, claz); + if (results) + { + do + { + Field* fields = results->Fetch(); + ObjectGuid guid = ObjectGuid(HighGuid::Player, fields[0].Get()); + uint32 race = fields[1].Get(); + bool isAlliance = race == 1 || race == 3 || race == 4 || race == 7 || race == 11; + addclassCache[GetTeamClassIdx(isAlliance, claz)].push_back(guid); + collected++; + } while (results->NextRow()); + } + } + LOG_INFO("playerbots", "{} characters collected for addclass command.", collected); } void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) @@ -1860,6 +1867,7 @@ void RandomPlayerbotMgr::GetBots() PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_EVENT); stmt->SetData(0, 0); stmt->SetData(1, "add"); + uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) { do @@ -1868,6 +1876,9 @@ void RandomPlayerbotMgr::GetBots() uint32 bot = fields[0].Get(); if (GetEventValue(bot, "add")) currentBots.push_back(bot); + + if (currentBots.size() >= maxAllowedBotCount) + break; } while (result->NextRow()); } } @@ -2396,52 +2407,13 @@ void RandomPlayerbotMgr::PrintStats() else ++engine_dead; - uint8 spec = AiFactory::GetPlayerSpecTab(bot); - switch (bot->getClass()) - { - case CLASS_DRUID: - if (spec == 2) - ++heal; - else - ++dps; - break; - case CLASS_PALADIN: - if (spec == 1) - ++tank; - else if (spec == 0) - ++heal; - else - ++dps; - break; - case CLASS_PRIEST: - if (spec != 2) - ++heal; - else - ++dps; - break; - case CLASS_SHAMAN: - if (spec == 2) - ++heal; - else - ++dps; - break; - case CLASS_WARRIOR: - if (spec == 2) - ++tank; - else - ++dps; - break; - case CLASS_DEATH_KNIGHT: - if (spec == 0) - tank++; - else - dps++; - break; - default: - ++dps; - break; - } - + if (botAI->IsHeal(bot, true)) + ++heal; + else if (botAI->IsTank(bot, true)) + ++tank; + else + ++dps; + if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get()) { TravelState state = target->getTravelState(); @@ -2779,4 +2751,3 @@ ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, Battlegrou return battleMasterGUID; } - diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index f1e80ab6..5c35aefa 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H #define _PLAYERBOT_RANDOMPLAYERBOTMGR_H +#include "ObjectGuid.h" #include "PlayerbotMgr.h" struct BattlegroundInfo @@ -166,7 +167,10 @@ public: float getActivityMod() { return activityMod; } float getActivityPercentage() { return activityMod * 100.0f; } void setActivityPercentage(float percentage) { activityMod = percentage / 100.0f; } + static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; } + void PrepareAddclassCache(); + std::map> addclassCache; protected: void OnBotLoginInternal(Player* const bot) override; diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 511d92a0..eb4ee127 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -39,6 +39,8 @@ #define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) +const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) | + (1LL << 33) | (1LL << 24) | (1LL << 34); uint32 PlayerbotFactory::tradeSkills[] = {SKILL_ALCHEMY, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_TAILORING, SKILL_LEATHERWORKING, SKILL_ENGINEERING, SKILL_HERBALISM, SKILL_MINING, SKILL_BLACKSMITHING, SKILL_COOKING, SKILL_FIRST_AID, SKILL_FISHING, @@ -205,7 +207,8 @@ void PlayerbotFactory::Randomize(bool incremental) { ClearAllItems(); } - // bot->SaveToDB(false, false); + bot->RemoveAllSpellCooldown(); + UnbindInstance(); bot->GiveLevel(level); bot->InitStatsForLevel(); @@ -233,6 +236,7 @@ void PlayerbotFactory::Randomize(bool incremental) pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Spells1"); LOG_DEBUG("playerbots", "Initializing spells (step 1)..."); // bot->LearnDefaultSkills(); + bot->LearnDefaultSkills(); InitClassSpells(); InitAvailableSpells(); if (pmo) @@ -286,7 +290,7 @@ void PlayerbotFactory::Randomize(bool incremental) LOG_DEBUG("playerbots", "Initializing equipmemt..."); if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) { - InitEquipment(incremental); + InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit); } // bot->SaveToDB(false, false); if (pmo) @@ -423,7 +427,10 @@ void PlayerbotFactory::Refresh() InitFood(); InitReagents(); // InitPotions(); - InitTalentsTree(true, true, true); + if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) + { + InitTalentsTree(true, true, true); + } InitPet(); InitPetTalents(); InitClassSpells(); @@ -611,8 +618,9 @@ void PlayerbotFactory::InitPetTalents() // pet_family->petTalentType); return; } - // pet->resetTalents(); std::unordered_map> spells; + bool diveTypePet = (1LL << ci->family) & diveMask; + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) { TalentEntry const* talentInfo = sTalentStore.LookupEntry(i); @@ -624,49 +632,115 @@ void PlayerbotFactory::InitPetTalents() // prevent learn talent for different family (cheating) if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask)) continue; - + bool diveClass = talentInfo->TalentID == 2201 || talentInfo->TalentID == 2208 || talentInfo->TalentID == 2219 || + talentInfo->TalentID == 2203; + if (diveClass && !diveTypePet) + continue; + bool dashClass = talentInfo->TalentID == 2119 || talentInfo->TalentID == 2207 || talentInfo->TalentID == 2111 || + talentInfo->TalentID == 2109; + if (dashClass && diveTypePet) + continue; spells[talentInfo->Row].push_back(talentInfo); } - uint32 curTalentPoints = pet->GetFreeTalentPoints(); + std::vector> order = + sPlayerbotAIConfig->parsedHunterPetLinkOrder[pet_family->petTalentType][20]; uint32 maxTalentPoints = pet->GetMaxTalentPointsForLevel(pet->GetLevel()); - int row = 0; - // LOG_INFO("playerbots", "{} learning, max talent points: {}, cur: {}", bot->GetName().c_str(), maxTalentPoints, - // curTalentPoints); - for (auto i = spells.begin(); i != spells.end(); ++i, ++row) - { - std::vector& spells_row = i->second; - if (spells_row.empty()) - { - LOG_INFO("playerbots", "{}: No spells for talent row {}", bot->GetName().c_str(), i->first); - continue; - } - int attemptCount = 0; - // keep learning for the last row - while (!spells_row.empty() && - ((((int)maxTalentPoints - (int)pet->GetFreeTalentPoints()) < 3 * (row + 1)) || (row == 5)) && - attemptCount++ < 10 && pet->GetFreeTalentPoints()) - { - int index = urand(0, spells_row.size() - 1); - TalentEntry const* talentInfo = spells_row[index]; - int maxRank = 0; - for (int rank = 0; rank < std::min((uint32)MAX_TALENT_RANK, (uint32)pet->GetFreeTalentPoints()); ++rank) - { - uint32 spellId = talentInfo->RankID[rank]; - if (!spellId) - continue; - maxRank = rank; - } - // LOG_INFO("playerbots", "{} learn pet talent {}({})", bot->GetName().c_str(), talentInfo->TalentID, - // maxRank); - if (talentInfo->DependsOn) + if (order.empty()) + { + int row = 0; + for (auto i = spells.begin(); i != spells.end(); ++i, ++row) + { + std::vector& spells_row = i->second; + if (spells_row.empty()) { - bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn, - std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1)); + LOG_INFO("playerbots", "{}: No spells for talent row {}", bot->GetName().c_str(), i->first); + continue; + } + int attemptCount = 0; + // keep learning for the last row + while (!spells_row.empty() && + ((((int)maxTalentPoints - (int)pet->GetFreeTalentPoints()) < 3 * (row + 1)) || (row == 5)) && + attemptCount++ < 10 && pet->GetFreeTalentPoints()) + { + int index = urand(0, spells_row.size() - 1); + TalentEntry const* talentInfo = spells_row[index]; + int maxRank = 0; + for (int rank = 0; rank < std::min((uint32)MAX_TALENT_RANK, (uint32)pet->GetFreeTalentPoints()); ++rank) + { + uint32 spellId = talentInfo->RankID[rank]; + if (!spellId) + continue; + + maxRank = rank; + } + // LOG_INFO("playerbots", "{} learn pet talent {}({})", bot->GetName().c_str(), talentInfo->TalentID, + // maxRank); + if (talentInfo->DependsOn) + { + bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn, + std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1)); + } + bot->LearnPetTalent(pet->GetGUID(), talentInfo->TalentID, maxRank); + spells_row.erase(spells_row.begin() + index); + } + } + } + else + { + uint32 spec = pet_family->petTalentType; + uint32 startPoints = pet->GetMaxTalentPointsForLevel(pet->GetLevel()); + while (startPoints > 1 && startPoints < 20 && + sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][startPoints].size() == 0) + { + startPoints--; + } + + for (uint32 points = startPoints; points <= 20; points++) + { + if (sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][points].size() == 0) + continue; + for (std::vector& p : sPlayerbotAIConfig->parsedHunterPetLinkOrder[spec][points]) + { + uint32 row = p[0], col = p[1], lvl = p[2]; + uint32 talentID = 0; + uint32 learnLevel = 0; + std::vector& spell = spells[row]; + for (TalentEntry const* talentInfo : spell) + { + if (talentInfo->Col != col) + { + continue; + } + if (talentInfo->DependsOn) + { + bot->LearnPetTalent(pet->GetGUID(), talentInfo->DependsOn, + std::min(talentInfo->DependsOnRank, bot->GetFreeTalentPoints() - 1)); + } + talentID = talentInfo->TalentID; + + uint32 currentTalentRank = 0; + for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank) + { + if (talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank])) + { + currentTalentRank = rank + 1; + break; + } + } + learnLevel = std::min(lvl, pet->GetFreeTalentPoints() + currentTalentRank) - 1; + } + bot->LearnPetTalent(pet->GetGUID(), talentID, learnLevel); + if (pet->GetFreeTalentPoints() == 0) + { + break; + } + } + if (pet->GetFreeTalentPoints() == 0) + { + break; } - bot->LearnPetTalent(pet->GetGUID(), talentInfo->TalentID, maxRank); - spells_row.erase(spells_row.begin() + index); } } bot->SendTalentsInfoData(true); @@ -816,7 +890,7 @@ void PlayerbotFactory::ClearEverything() ClearSpells(); ClearInventory(); ResetQuests(); - bot->SaveToDB(false, false); + // bot->SaveToDB(false, false); } void PlayerbotFactory::ClearSpells() @@ -878,14 +952,37 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa uint32 total_tabs = tabs[0] + tabs[1] + tabs[2]; if (increment && total_tabs != 0) { + /// @todo: match current talent with template specTab = AiFactory::GetPlayerSpecTab(bot); + /// @todo: fix cat druid hardcode + if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && + !bot->HasAura(16931)) + specTab = 3; } else { - uint32 point = urand(0, 100); - uint32 p1 = sPlayerbotAIConfig->randomClassSpecProb[cls][0]; - uint32 p2 = p1 + sPlayerbotAIConfig->randomClassSpecProb[cls][1]; - specTab = point < p1 ? 0 : (point < p2 ? 1 : 2); + uint32 pointSum = 0; + for (int i = 0; i < MAX_SPECNO; i++) + { + pointSum += sPlayerbotAIConfig->randomClassSpecProb[cls][i]; + } + uint32 point = urand(1, pointSum); + uint32 currentP = 0; + int i; + for (i = 0; i < MAX_SPECNO; i++) + { + currentP += sPlayerbotAIConfig->randomClassSpecProb[cls][i]; + if (point <= currentP) + { + specTab = i; + break; + } + } + if (i == MAX_SPECNO) + { + specTab = 0; + LOG_ERROR("playerbots", "Fail to select spec num for bot {}! Set to 0.", bot->GetName()); + } } if (reset) { @@ -896,12 +993,13 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa { InitTalentsByTemplate(specTab); } - else - { - InitTalents(specTab); - if (bot->GetFreeTalentPoints()) - InitTalents((specTab + 1) % 3); - } + // always use template now + // else + // { + // InitTalents(specTab); + // if (bot->GetFreeTalentPoints()) + // InitTalents((specTab + 1) % 3); + // } bot->SendTalentsInfoData(false); } @@ -1199,7 +1297,7 @@ bool PlayerbotFactory::CanEquipWeapon(ItemTemplate const* proto) { case CLASS_PRIEST: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && - proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE) + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER) return false; break; case CLASS_MAGE: @@ -1213,15 +1311,12 @@ bool PlayerbotFactory::CanEquipWeapon(ItemTemplate const* proto) proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && - proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_PALADIN: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) - return false; - break; case CLASS_DEATH_KNIGHT: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && @@ -1233,28 +1328,30 @@ bool PlayerbotFactory::CanEquipWeapon(ItemTemplate const* proto) if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && - proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) return false; break; case CLASS_DRUID: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && - proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_HUNTER: - if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && - proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && - proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW) + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM) return false; break; case CLASS_ROGUE: if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && - proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && - proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE) return false; break; } @@ -1274,7 +1371,8 @@ bool PlayerbotFactory::CanEquipItem(ItemTemplate const* proto) return true; uint32 requiredLevel = proto->RequiredLevel; - bool hasItem = bot->HasItemCount(proto->ItemId, 1, true); + // disable since bad performance + bool hasItem = bot->HasItemCount(proto->ItemId, 1, false); // bot->GetItemCount() // !requiredLevel -> it's a quest reward item if (!requiredLevel && hasItem) @@ -1430,10 +1528,10 @@ void Shuffle(std::vector& items) // } // } -void PlayerbotFactory::InitEquipment(bool incremental) +void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) { std::unordered_map> items; - int tab = AiFactory::GetPlayerSpecTab(bot); + // int tab = AiFactory::GetPlayerSpecTab(bot); uint32 blevel = bot->GetLevel(); int32 delta = 2; @@ -1450,6 +1548,7 @@ void PlayerbotFactory::InitEquipment(bool incremental) else if (blevel == 80) delta = 9; + StatsWeightCalculator calculator(bot); for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) { if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) @@ -1467,6 +1566,15 @@ void PlayerbotFactory::InitEquipment(bool incremental) if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2)) continue; + Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + + if (second_chance && oldItem) + { + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + } + + oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + uint32 desiredQuality = itemQuality; if (urand(0, 100) < 100 * sPlayerbotAIConfig->randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) { @@ -1481,10 +1589,13 @@ void PlayerbotFactory::InitEquipment(bool incremental) { for (uint32 itemId : sRandomItemMgr->GetCachedEquipments(requiredLevel, inventoryType)) { - if (itemId == 46978) - { // shaman earth ring totem + if (itemId == 46978) // shaman earth ring totem + { continue; } + uint32 skipProb = 25; + if (urand(1, 100) <= skipProb) + continue; // disable next expansion gear if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 60 && itemId >= 23728) @@ -1510,10 +1621,10 @@ void PlayerbotFactory::InitEquipment(bool incremental) if (proto->Quality != desiredQuality) continue; + // delay heavy check + // if (!CanEquipItem(proto)) + // continue; - if (!CanEquipItem(proto)) - continue; - if (proto->Class == ITEM_CLASS_ARMOR && (slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS || slot == EQUIPMENT_SLOT_CHEST || slot == EQUIPMENT_SLOT_WAIST || @@ -1528,95 +1639,153 @@ void PlayerbotFactory::InitEquipment(bool incremental) if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) continue; - - uint16 dest = 0; - if (CanEquipUnseenItem(slot, dest, itemId)) - items[slot].push_back(itemId); + // delay heavy check + // uint16 dest = 0; + // if (CanEquipUnseenItem(slot, dest, itemId)) + items[slot].push_back(itemId); } } if (items[slot].size() >= 25) break; } } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_NORMAL); - } - - StatsWeightCalculator calculator(bot); - for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) - { - if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) - continue; - - if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) - continue; - - if (level < 25 && slot == EQUIPMENT_SLOT_NECK) - continue; - - if (level < 25 && slot == EQUIPMENT_SLOT_HEAD) - continue; std::vector& ids = items[slot]; if (ids.empty()) { continue; } - Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); - - if (incremental && !IsDesiredReplacement(oldItem)) - { - continue; - } float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; for (int index = 0; index < ids.size(); index++) { - uint32 skipProb = 25; - if (urand(0, 100) <= skipProb) - continue; - uint32 newItemId = ids[index]; - uint16 dest; - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); - if (!CanEquipItem(proto)) - continue; - - if (oldItem && oldItem->GetTemplate()->ItemId == newItemId) - continue; - - if (!CanEquipUnseenItem(slot, dest, newItemId)) - continue; - float cur_score = calculator.CalculateItem(newItemId); if (cur_score > bestScoreForSlot) { + // delay heavy check to here + if (!CanEquipItem(proto)) + continue; + uint16 dest; + if (!CanEquipUnseenItem(slot, dest, newItemId)) + continue; bestScoreForSlot = cur_score; bestItemForSlot = newItemId; } } + if (bestItemForSlot == 0) { continue; } - if (oldItem) - { - bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); - } uint16 dest; if (!CanEquipUnseenItem(slot, dest, bestItemForSlot)) { continue; } - Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true); - if (newItem) + + if (incremental && oldItem) { - newItem->AddToWorld(); - newItem->AddToUpdateQueueOf(bot); - // bot->AutoUnequipOffhandIfNeed(); - // EnchantItem(newItem); + float old_score = calculator.CalculateItem(oldItem->GetEntry()); + if (bestScoreForSlot < 1.2f * old_score) + continue; + } + + + if (oldItem) + { + uint8 bagIndex = oldItem->GetBagSlot(); + uint8 slot = oldItem->GetSlot(); + uint8 dstBag = NULL_BAG; + + WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3); + packet << bagIndex << slot << dstBag; + bot->GetSession()->HandleAutoStoreBagItemOpcode(packet); + } + + oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + // fail to store in bag + if (oldItem) + continue; + + Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true); + bot->AutoUnequipOffhandIfNeed(); + // if (newItem) + // { + // newItem->AddToWorld(); + // newItem->AddToUpdateQueueOf(bot); + // } + } + // Secondary init for better equips + /// @todo: clean up duplicate code + if (second_chance) + { + for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) + continue; + + if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) + continue; + + if (level < 30 && slot == EQUIPMENT_SLOT_NECK) + continue; + + if (level < 25 && slot == EQUIPMENT_SLOT_HEAD) + continue; + + if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2)) + continue; + + if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); + + std::vector& ids = items[slot]; + if (ids.empty()) + continue; + + float bestScoreForSlot = -1; + uint32 bestItemForSlot = 0; + for (int index = 0; index < ids.size(); index++) + { + uint32 newItemId = ids[index]; + + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(newItemId); + + float cur_score = calculator.CalculateItem(newItemId); + if (cur_score > bestScoreForSlot) + { + // delay heavy check to here + if (!CanEquipItem(proto)) + continue; + uint16 dest; + if (!CanEquipUnseenItem(slot, dest, newItemId)) + continue; + bestScoreForSlot = cur_score; + bestItemForSlot = newItemId; + } + } + + if (bestItemForSlot == 0) + { + continue; + } + uint16 dest; + if (!CanEquipUnseenItem(slot, dest, bestItemForSlot)) + { + continue; + } + Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true); + bot->AutoUnequipOffhandIfNeed(); + // if (newItem) + // { + // newItem->AddToWorld(); + // newItem->AddToUpdateQueueOf(bot); + // } } } } @@ -1788,11 +1957,11 @@ void PlayerbotFactory::InitBags(bool destroyOld) continue; } Item* newItem = bot->EquipNewItem(dest, newItemId, true); - if (newItem) - { - newItem->AddToWorld(); - newItem->AddToUpdateQueueOf(bot); - } + // if (newItem) + // { + // newItem->AddToWorld(); + // newItem->AddToUpdateQueueOf(bot); + // } } } @@ -1892,7 +2061,8 @@ bool PlayerbotFactory::CanEquipUnseenItem(uint8 slot, uint16& dest, uint32 item) if (Item* pItem = Item::CreateItem(item, 1, bot, false, 0, true)) { - InventoryResult result = bot->CanEquipItem(slot, dest, pItem, true, false); + InventoryResult result = botAI ? botAI->CanEquipItem(slot, dest, pItem, true, true) + : bot->CanEquipItem(slot, dest, pItem, true, true); pItem->RemoveFromUpdateQueueOf(bot); delete pItem; return result == EQUIP_ERR_OK; @@ -2147,8 +2317,6 @@ void PlayerbotFactory::SetRandomSkill(uint16 id) void PlayerbotFactory::InitAvailableSpells() { - bot->LearnDefaultSkills(); - if (trainerIdCache.empty()) { CreatureTemplateContainer const* creatureTemplateContainer = sObjectMgr->GetCreatureTemplates(); @@ -2166,6 +2334,8 @@ void PlayerbotFactory::InitAvailableSpells() trainerIdCache.push_back(trainerId); } } + // uint32 learnedCounter = 0; + // uint32 oktest = 0; for (uint32 trainerId : trainerIdCache) { TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId); @@ -2209,16 +2379,19 @@ void PlayerbotFactory::InitAvailableSpells() { continue; } - + // oktest++; if (tSpell->learnedSpell[0]) { bot->learnSpell(tSpell->learnedSpell[0], false); } - else - { - botAI->CastSpell(tSpell->spell, bot); - } + // else + // { + // botAI->CastSpell(tSpell->spell, bot); + // } } + // LOG_INFO("playerbots", "C: {}, ok: {}", ++learnedCounter, oktest); + // if (++learnedCounter > 20) + // break; } } @@ -2628,7 +2801,10 @@ void PlayerbotFactory::InitAmmo() bot->SetAmmo(entry); } -uint32 PlayerbotFactory::CalcMixedGearScore(uint32 gs, uint32 quality) { return gs * (quality + 1); } +uint32 PlayerbotFactory::CalcMixedGearScore(uint32 gs, uint32 quality) +{ + return gs * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(quality)); +} void PlayerbotFactory::InitMounts() { @@ -2796,7 +2972,8 @@ std::vector PlayerbotFactory::GetCurrentGemsCount() Item* pItem2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i); if (pItem2 && !pItem2->IsBroken() && pItem2->HasSocket()) { - for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot) + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; + ++enchant_slot) { if (enchant_slot == BONUS_ENCHANTMENT_SLOT) continue; @@ -3062,6 +3239,9 @@ void PlayerbotFactory::InitGlyphs(bool increment) uint8 cls = bot->getClass(); uint8 tab = AiFactory::GetPlayerSpecTab(bot); + /// @todo: fix cat druid hardcode + if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931)) + tab = 3; std::list glyphs; ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); for (ItemTemplateContainer::const_iterator i = itemTemplates->begin(); i != itemTemplates->end(); ++i) @@ -3301,10 +3481,10 @@ void PlayerbotFactory::InitInventoryEquip() if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto)) continue; - + if (proto->Quality != desiredQuality) continue; - + if (!CanEquipItem(proto)) continue; @@ -3701,7 +3881,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) } availableGems.push_back(enchantGem); } - + StatsWeightCalculator calculator(bot); for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) { if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) @@ -3751,7 +3931,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - if (enchant->requiredSkill && (!bot->HasSkill(enchant->requiredSkill) || (bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue))) + if (enchant->requiredSkill && + (!bot->HasSkill(enchant->requiredSkill) || + (bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue))) { continue; } @@ -3759,7 +3941,6 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - StatsWeightCalculator calculator(bot); float score = calculator.CalculateEnchant(enchant_id); if (score >= bestScore) { @@ -3788,7 +3969,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) int32 enchantIdChosen = -1; int32 colorChosen; float bestGemScore = -1; - for (uint32 &enchantGem : availableGems) + for (uint32& enchantGem : availableGems) { ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(enchantGem); if (!gemTemplate) @@ -3798,7 +3979,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) if (!gemProperties) continue; - if ((socketColor & gemProperties->color) == 0) + if ((socketColor & gemProperties->color) == 0 && gemProperties->color == 1) // meta socket continue; uint32 enchant_id = gemProperties->spellitemenchantement; @@ -3809,6 +3990,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) StatsWeightCalculator calculator(bot); float score = calculator.CalculateEnchant(enchant_id); if (curCount[0] != 0) + { // Ensure meta gem activation for (int i = 1; i < curCount.size(); i++) { @@ -3818,6 +4000,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) break; } } + } + if (socketColor & gemProperties->color) + score *= 1.2; if (score > bestGemScore) { enchantIdChosen = enchant_id; diff --git a/src/factory/PlayerbotFactory.h b/src/factory/PlayerbotFactory.h index f88ce96e..e77947e5 100644 --- a/src/factory/PlayerbotFactory.h +++ b/src/factory/PlayerbotFactory.h @@ -122,7 +122,7 @@ public: static void InitTalentsByParsedSpecLink(Player* bot, std::vector> parsedSpecLink, bool reset); void InitAvailableSpells(); void InitClassSpells(); - void InitEquipment(bool incremental); + void InitEquipment(bool incremental, bool second_chance = false); void InitPet(); void InitAmmo(); static uint32 CalcMixedGearScore(uint32 gs, uint32 quality); diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 1216b35c..bc73fcb6 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -11,7 +11,7 @@ #include "SpellMgr.h" #include "UpdateFields.h" -StatsCollector::StatsCollector(CollectorType type) : type_(type) { Reset(); } +StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); } void StatsCollector::Reset() { @@ -233,7 +233,7 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) { if (type_ != CollectorType::SPELL_HEAL) stats[STATS_TYPE_CRIT] += 50; return true; - case 59620: + case 59620: // Berserk if (type_ == CollectorType::MELEE) stats[STATS_TYPE_ATTACK_POWER] += 120; return true; @@ -243,6 +243,18 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) { case 67771: // Death's Verdict (heroic) stats[STATS_TYPE_ATTACK_POWER] += 260; return true; + case 71406: // Tiny Abomination in a Jar + if (cls_ == CLASS_PALADIN) + stats[STATS_TYPE_ATTACK_POWER] += 600; + else + stats[STATS_TYPE_ATTACK_POWER] += 150; + return true; + case 71545: // Tiny Abomination in a Jar (heroic) + if (cls_ == CLASS_PALADIN) + stats[STATS_TYPE_ATTACK_POWER] += 800; + else + stats[STATS_TYPE_ATTACK_POWER] += 200; + return true; case 71519: // Deathbringer's Will stats[STATS_TYPE_ATTACK_POWER] += 350; return true; @@ -254,6 +266,10 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) { /// Noticing that heroic item can not be triggered, probably a bug to report to AC if (type_ == CollectorType::SPELL_HEAL) return true; + break; + case 71903: // Shadowmourne + stats[STATS_TYPE_STRENGTH] += 200; + return true; default: break; } diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h index fa0634ce..b51dddf9 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -57,7 +57,7 @@ enum CollectorType : uint8 class StatsCollector { public: - StatsCollector(CollectorType type); + StatsCollector(CollectorType type, int32 cls = -1); StatsCollector(StatsCollector& stats) = default; void Reset(); void CollectItemStats(ItemTemplate const* proto); @@ -78,6 +78,7 @@ private: private: CollectorType type_; + uint32 cls_; }; #endif \ No newline at end of file diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 543de321..e77ac102 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -12,6 +12,7 @@ #include "ItemTemplate.h" #include "ObjectMgr.h" #include "PlayerbotAI.h" +#include "PlayerbotFactory.h" #include "SharedDefines.h" #include "StatsCollector.h" #include "Unit.h" @@ -26,10 +27,19 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) type_ = CollectorType::MELEE; else type_ = CollectorType::RANGED; - collector_ = std::make_unique(type_); - cls = player->getClass(); tab = AiFactory::GetPlayerSpecTab(player); + collector_ = std::make_unique(type_, cls); + + + if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY) + hitOverflowType_ = CollectorType::SPELL; + else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT) + hitOverflowType_ = CollectorType::SPELL; + else if (cls == CLASS_ROGUE) + hitOverflowType_ = CollectorType::SPELL; + else + hitOverflowType_ = type_; enable_overflow_penalty_ = true; enable_item_set_bonus_ = true; @@ -49,14 +59,17 @@ void StatsWeightCalculator::Reset() float StatsWeightCalculator::CalculateItem(uint32 itemId) { ItemTemplate const* proto = &sObjectMgr->GetItemTemplateStore()->at(itemId); - + if (!proto) return 0.0f; Reset(); - + collector_->CollectItemStats(proto); + if (enable_overflow_penalty_) + ApplyOverflowPenalty(player_); + GenerateWeights(player_); for (uint32 i = 0; i < STATS_TYPE_MAX; i++) { @@ -67,27 +80,30 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId) if (enable_item_set_bonus_) CalculateItemSetBonus(player_, proto); - + CalculateSocketBonus(player_, proto); if (enable_quality_blend_) // Blend with item quality and level - weight_ *= (proto->Quality + 1) * proto->ItemLevel; - + weight_ *= PlayerbotFactory::CalcMixedGearScore(proto->ItemLevel, proto->Quality); + return weight_; } float StatsWeightCalculator::CalculateEnchant(uint32 enchantId) { SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId); - + if (!enchant) return 0.0f; Reset(); - + collector_->CollectEnchantStats(enchant); + if (enable_overflow_penalty_) + ApplyOverflowPenalty(player_); + GenerateWeights(player_); for (uint32 i = 0; i < STATS_TYPE_MAX; i++) { @@ -101,9 +117,7 @@ void StatsWeightCalculator::GenerateWeights(Player* player) { GenerateBasicWeights(player); GenerateAdditionalWeights(player); - - if (enable_overflow_penalty_) - ApplyOverflowPenalty(player); + ApplyWeightFinetune(player); } void StatsWeightCalculator::GenerateBasicWeights(Player* player) @@ -114,49 +128,61 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) { - stats_weights_[STATS_TYPE_AGILITY] += 2.4f; + stats_weights_[STATS_TYPE_AGILITY] += 2.5f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.3f; - stats_weights_[STATS_TYPE_HIT] += 1.6f; - stats_weights_[STATS_TYPE_CRIT] += 1.5f; - stats_weights_[STATS_TYPE_HASTE] += 1.4f; - stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; - } + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f; + stats_weights_[STATS_TYPE_HIT] += 1.7f; + stats_weights_[STATS_TYPE_CRIT] += 1.4f; + stats_weights_[STATS_TYPE_HASTE] += 1.6f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 7.5f; + } else if (cls == CLASS_HUNTER && tab == HUNTER_TAB_MARKSMANSHIP) { - stats_weights_[STATS_TYPE_AGILITY] += 2.2f; + stats_weights_[STATS_TYPE_AGILITY] += 2.3f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.2f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.25f; stats_weights_[STATS_TYPE_HIT] += 2.1f; stats_weights_[STATS_TYPE_CRIT] += 2.0f; stats_weights_[STATS_TYPE_HASTE] += 1.8f; - stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 10.0f; } - else if ((cls == CLASS_ROGUE && tab == ROGUE_TAB_COMBAT) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))) + else if (cls == CLASS_ROGUE && tab == ROGUE_TAB_COMBAT) { - stats_weights_[STATS_TYPE_AGILITY] += 1.8f; + stats_weights_[STATS_TYPE_AGILITY] += 1.9f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.8f; + stats_weights_[STATS_TYPE_HIT] += 2.1f; + stats_weights_[STATS_TYPE_CRIT] += 1.4f; + stats_weights_[STATS_TYPE_HASTE] += 1.7f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; + } + else if (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player)) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.2f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.4f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.3f; + stats_weights_[STATS_TYPE_HIT] += 1.9f; + stats_weights_[STATS_TYPE_CRIT] += 1.5f; + stats_weights_[STATS_TYPE_HASTE] += 2.1f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 15.0f; + } + else if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)) + { + stats_weights_[STATS_TYPE_AGILITY] += 1.5f; stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.2f; - stats_weights_[STATS_TYPE_HIT] += 2.0f; - stats_weights_[STATS_TYPE_CRIT] += 1.6f; - stats_weights_[STATS_TYPE_HASTE] += 1.4f; - stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; - } - else if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)) - { - stats_weights_[STATS_TYPE_AGILITY] += 1.7f; - stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; - stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.0f; - stats_weights_[STATS_TYPE_HIT] += 1.6f; - stats_weights_[STATS_TYPE_CRIT] += 1.3f; - stats_weights_[STATS_TYPE_HASTE] += 1.5f; - stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_HIT] += 2.1f; + stats_weights_[STATS_TYPE_CRIT] += 1.1f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f; stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; } - else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury + else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury { stats_weights_[STATS_TYPE_AGILITY] += 1.8f; stats_weights_[STATS_TYPE_STRENGTH] += 2.6f; @@ -168,7 +194,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; } - else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm + else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm { stats_weights_[STATS_TYPE_AGILITY] += 1.6f; stats_weights_[STATS_TYPE_STRENGTH] += 2.3f; @@ -180,73 +206,88 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; } - else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk + else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk { - stats_weights_[STATS_TYPE_AGILITY] += 1.8f; - stats_weights_[STATS_TYPE_STRENGTH] += 2.6f; + stats_weights_[STATS_TYPE_AGILITY] += 1.7f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.8f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.1f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.7f; stats_weights_[STATS_TYPE_HIT] += 2.3f; stats_weights_[STATS_TYPE_CRIT] += 2.2f; - stats_weights_[STATS_TYPE_HASTE] += 1.8f; + stats_weights_[STATS_TYPE_HASTE] += 2.1f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f; stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; } else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY) { - stats_weights_[STATS_TYPE_AGILITY] += 0.5f; + stats_weights_[STATS_TYPE_AGILITY] += 0.9f; stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.0f; - stats_weights_[STATS_TYPE_HIT] += 1.8f; - stats_weights_[STATS_TYPE_CRIT] += 1.0f; - stats_weights_[STATS_TYPE_HASTE] += 1.7f; - stats_weights_[STATS_TYPE_EXPERTISE] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.3f; + stats_weights_[STATS_TYPE_HIT] += 2.2f; + stats_weights_[STATS_TYPE_CRIT] += 1.7f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; + stats_weights_[STATS_TYPE_EXPERTISE] += 1.5f; stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; } - else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution + else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution { - stats_weights_[STATS_TYPE_AGILITY] += 1.1f; + stats_weights_[STATS_TYPE_AGILITY] += 1.6f; stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; - stats_weights_[STATS_TYPE_INTELLECT] += 0.15f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.1f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; stats_weights_[STATS_TYPE_SPELL_POWER] += 0.3f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.8f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f; stats_weights_[STATS_TYPE_HIT] += 1.9f; - stats_weights_[STATS_TYPE_CRIT] += 1.2f; - stats_weights_[STATS_TYPE_HASTE] += 1.3f; + stats_weights_[STATS_TYPE_CRIT] += 1.7f; + stats_weights_[STATS_TYPE_HASTE] += 1.6f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f; } else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement { - stats_weights_[STATS_TYPE_AGILITY] += 1.6f; + stats_weights_[STATS_TYPE_AGILITY] += 1.4f; stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; - stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.2f; - stats_weights_[STATS_TYPE_HIT] += 1.7f; - stats_weights_[STATS_TYPE_CRIT] += 1.4f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 0.95f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.9f; + stats_weights_[STATS_TYPE_HIT] += 2.1f; + stats_weights_[STATS_TYPE_CRIT] += 1.5f; stats_weights_[STATS_TYPE_HASTE] += 1.8f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 5.2f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f; } - else if (cls == CLASS_WARLOCK || - cls == CLASS_MAGE || + else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) || (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow - (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ELEMENTAL) || // element (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance { - stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; - stats_weights_[STATS_TYPE_SPIRIT] += 0.4f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.6f; stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; - stats_weights_[STATS_TYPE_SPELL_PENETRATION] += 1.0f; stats_weights_[STATS_TYPE_HIT] += 1.1f; stats_weights_[STATS_TYPE_CRIT] += 0.8f; stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; } + else if (cls == CLASS_MAGE && tab == MAGE_TAB_FIRE) + { + stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.7f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.2f; + stats_weights_[STATS_TYPE_CRIT] += 1.1f; + stats_weights_[STATS_TYPE_HASTE] += 0.8f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; + } + else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ELEMENTAL) + { + stats_weights_[STATS_TYPE_INTELLECT] += 0.25f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.1f; + stats_weights_[STATS_TYPE_CRIT] += 0.8f; + stats_weights_[STATS_TYPE_HASTE] += 1.0f; + } else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy (cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION) || // heal @@ -260,7 +301,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; } - else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)) + else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || + (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)) { stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; @@ -323,6 +365,8 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) { if (player->HasAura(34484)) stats_weights_[STATS_TYPE_INTELLECT] += 1.1f; + if (player->HasAura(56341)) + stats_weights_[STATS_TYPE_STAMINA] += 0.3f; } else if (cls == CLASS_WARRIOR) { @@ -349,33 +393,33 @@ void StatsWeightCalculator::CalculateItemSetBonus(Player* player, ItemTemplate c if (player->ItemSetEff[i]) { ItemSetEffect* eff = player->ItemSetEff[i]; - + uint32 setId = eff->setid; if (itemSet != setId) continue; - - const ItemSetEntry *setEntry = sItemSetStore.LookupEntry(setId); + + const ItemSetEntry* setEntry = sItemSetStore.LookupEntry(setId); if (!setEntry) continue; - + uint32 itemCount = eff->item_count; uint32 max_items = 0; for (size_t j = 0; j < MAX_ITEM_SET_SPELLS; j++) max_items = std::max(max_items, setEntry->items_to_triggerspell[j]); if (itemCount < max_items) { - multiplier += 0.1f * itemCount; // 10% bonus for each item already equipped + multiplier += 0.1f * itemCount; // 10% bonus for each item already equipped } else { - multiplier = 1.0f; // All item set effect has been triggerred + multiplier = 1.0f; // All item set effect has been triggerred } break; } } if (i == player->ItemSetEff.size()) - multiplier = 1.05f; // this is the first item in the item set + multiplier = 1.05f; // this is the first item in the item set weight_ *= multiplier; } @@ -383,29 +427,30 @@ void StatsWeightCalculator::CalculateItemSetBonus(Player* player, ItemTemplate c void StatsWeightCalculator::CalculateSocketBonus(Player* player, ItemTemplate const* proto) { uint32 socketNum = 0; - for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot) + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; + ++enchant_slot) { uint8 socketColor = proto->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color; - - if (!socketColor) // no socket slot + + if (!socketColor) // no socket slot continue; socketNum++; } - float multiplier = 1.0f + socketNum * 0.03f; // 3% bonus for socket + float multiplier = 1.0f + socketNum * 0.03f; // 3% bonus for socket weight_ *= multiplier; } void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) { - // penalty for different type armor - if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH && - proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass)) - { - weight_ *= 0.8; - } + // // penalty for different type armor + // if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH && + // proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass)) + // { + // weight_ *= 1.0; + // } // double hand if (proto->Class == ITEM_CLASS_WEAPON) { @@ -430,7 +475,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) } // spec with double hand // fury without duel wield, arms, bear, retribution, blood dk - if (isDoubleHand && + if (!isDoubleHand && ((cls == CLASS_HUNTER && !player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) || @@ -438,13 +483,13 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) || (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))) { - weight_ *= 10; + weight_ *= 0.1; } // fury with titan's grip - if (isDoubleHand && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) && (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip())) { - weight_ *= 10; + weight_ *= 0.1; } } if (proto->Class == ITEM_CLASS_WEAPON) @@ -457,16 +502,19 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) { weight_ *= 0.1; } - if (cls == CLASS_ROGUE && player_->HasAura(13964) - && (proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE)) + if (cls == CLASS_ROGUE && player_->HasAura(13964) && + (proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE)) { weight_ *= 1.1; } - if (cls == CLASS_WARRIOR && player_->HasAura(12785) - && (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2)) + if (cls == CLASS_WARRIOR && player_->HasAura(12785) && + (proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2)) { weight_ *= 1.1; } + bool slowDelay = proto->Delay > 2500; + if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay) + weight_ *= 1.1; } } @@ -491,50 +539,74 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) { { float hit_current, hit_overflow; - if (type_ == CollectorType::SPELL) + float validPoints; + // m_modMeleeHitChance = (float)GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + // m_modMeleeHitChance += GetRatingBonusValue(CR_HIT_MELEE); + if (hitOverflowType_ == CollectorType::SPELL) { - hit_current = player->GetRatingBonusValue(CR_HIT_SPELL); + hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE); + hit_current += player->GetRatingBonusValue(CR_HIT_SPELL); hit_overflow = SPELL_HIT_OVERFLOW; + if (hit_overflow > hit_current) + validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL); + else + validPoints = 0; } - else if (type_ == CollectorType::MELEE) + else if (hitOverflowType_ == CollectorType::MELEE) { - hit_current = player->GetRatingBonusValue(CR_HIT_MELEE); + hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + hit_current += player->GetRatingBonusValue(CR_HIT_MELEE); hit_overflow = MELEE_HIT_OVERFLOW; + if (hit_overflow > hit_current) + validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_MELEE); + else + validPoints = 0; } else { - hit_current = player->GetRatingBonusValue(CR_HIT_RANGED); + hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + hit_current += player->GetRatingBonusValue(CR_HIT_RANGED); hit_overflow = RANGED_HIT_OVERFLOW; + if (hit_overflow > hit_current) + validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_RANGED); + else + validPoints = 0; } - if (hit_current >= hit_overflow) - stats_weights_[STATS_TYPE_HIT] = 0.0f; - else if (hit_current >= hit_overflow * 0.8) - stats_weights_[STATS_TYPE_HIT] /= 1.5; + collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints); } - + { if (type_ == CollectorType::MELEE) { float expertise_current, expertise_overflow; - expertise_current = player->GetRatingBonusValue(CR_EXPERTISE); + expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE); + expertise_current += player->GetRatingBonusValue(CR_EXPERTISE); expertise_overflow = EXPERTISE_OVERFLOW; - if (expertise_current >= expertise_overflow) - stats_weights_[STATS_TYPE_EXPERTISE] = 0.0f; - else if (expertise_current >= expertise_overflow * 0.8) - stats_weights_[STATS_TYPE_EXPERTISE] /= 1.5; + + float validPoints; + if (expertise_overflow > expertise_current) + validPoints = (expertise_overflow - expertise_current) / player->GetRatingMultiplier(CR_EXPERTISE); + else + validPoints = 0; + + collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints); } } - + { if (type_ == CollectorType::MELEE) { float defense_current, defense_overflow; defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL); defense_overflow = DEFENSE_OVERFLOW; - if (defense_current >= defense_overflow) - stats_weights_[STATS_TYPE_DEFENSE] /= 2; - else if (defense_current >= defense_overflow * 0.8) - stats_weights_[STATS_TYPE_DEFENSE] /= 1.5; + + float validPoints; + if (defense_overflow > defense_current) + validPoints = (defense_overflow - defense_current) / player->GetRatingMultiplier(CR_DEFENSE_SKILL); + else + validPoints = 0; + + collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints); } } @@ -544,10 +616,27 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) float armor_penetration_current, armor_penetration_overflow; armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); armor_penetration_overflow = ARMOR_PENETRATION_OVERFLOW; - if (armor_penetration_current >= armor_penetration_overflow) - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] = 0.0f; - if (armor_penetration_current >= armor_penetration_overflow * 0.8) - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] /= 1.5; + + float validPoints; + if (armor_penetration_overflow > armor_penetration_current) + validPoints = (armor_penetration_overflow - armor_penetration_current) / player->GetRatingMultiplier(CR_ARMOR_PENETRATION); + else + validPoints = 0; + + collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints); } } -} \ No newline at end of file +} + +void StatsWeightCalculator::ApplyWeightFinetune(Player* player) +{ + { + if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + { + float armor_penetration_current, armor_penetration_overflow; + armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); + if (armor_penetration_current > 50) + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f; + } + } +} diff --git a/src/factory/StatsWeightCalculator.h b/src/factory/StatsWeightCalculator.h index 721eff9e..d65966ff 100644 --- a/src/factory/StatsWeightCalculator.h +++ b/src/factory/StatsWeightCalculator.h @@ -15,7 +15,7 @@ enum StatsOverflowThreshold { - SPELL_HIT_OVERFLOW = 17, + SPELL_HIT_OVERFLOW = 14, MELEE_HIT_OVERFLOW = 8, RANGED_HIT_OVERFLOW = 8, EXPERTISE_OVERFLOW = 26, @@ -48,10 +48,12 @@ private: bool NotBestArmorType(uint32 item_subclass_armor); void ApplyOverflowPenalty(Player* player); + void ApplyWeightFinetune(Player* player); private: Player* player_; CollectorType type_; + CollectorType hitOverflowType_; std::unique_ptr collector_; uint8 cls; int tab; diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 9052b61b..1735d56d 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -24,6 +24,8 @@ #include "raids/naxxramas/RaidNaxxTriggerContext.h" #include "raids/moltencore/RaidMcActionContext.h" #include "raids/moltencore/RaidMcTriggerContext.h" +#include "raids/aq20/RaidAq20ActionContext.h" +#include "raids/aq20/RaidAq20TriggerContext.h" AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) { @@ -40,6 +42,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new RaidNaxxActionContext()); actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidMcActionContext()); + actionContexts.Add(new RaidAq20ActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -48,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new RaidNaxxTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidMcTriggerContext()); + triggerContexts.Add(new RaidAq20TriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/src/strategy/Strategy.cpp b/src/strategy/Strategy.cpp index e6fe779d..3f1c3010 100644 --- a/src/strategy/Strategy.cpp +++ b/src/strategy/Strategy.cpp @@ -94,7 +94,7 @@ private: { return new ActionNode("mana potion", /*P*/ nullptr, - /*A*/ NextAction::array(0, new NextAction("drink"), nullptr), + /*A*/ nullptr, /*C*/ nullptr); } diff --git a/src/strategy/actions/AcceptQuestAction.cpp b/src/strategy/actions/AcceptQuestAction.cpp index fcbd2438..259e31db 100644 --- a/src/strategy/actions/AcceptQuestAction.cpp +++ b/src/strategy/actions/AcceptQuestAction.cpp @@ -51,7 +51,7 @@ bool AcceptQuestAction::Execute(Event event) guid = unit->GetGUID().GetRawValue(); break; } - if (unit && text == "*" && sqrt(bot->GetDistance(unit)) <= INTERACTION_DISTANCE) + if (unit && text == "*" && bot->GetDistance(unit) <= INTERACTION_DISTANCE) hasAccept |= QuestAction::ProcessQuests(unit); } GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los"); @@ -63,7 +63,7 @@ bool AcceptQuestAction::Execute(Event event) guid = go->GetGUID().GetRawValue(); break; } - if (go && text == "*" && sqrt(bot->GetDistance(go)) <= INTERACTION_DISTANCE) + if (go && text == "*" && bot->GetDistance(go) <= INTERACTION_DISTANCE) hasAccept |= QuestAction::ProcessQuests(go); } } diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 76c1bc54..d56c5e6f 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -172,7 +172,7 @@ public: creators["rtsc"] = &ChatActionContext::rtsc; creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut; creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; - creators["tell expected dps"] = &ChatActionContext::tell_expected_dps; + creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps; creators["join"] = &ChatActionContext::join; creators["calc"] = &ChatActionContext::calc; } @@ -271,7 +271,7 @@ private: static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); } static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); } static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); } - static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); } + static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); } static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); } static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } }; diff --git a/src/strategy/actions/ChooseRpgTargetAction.cpp b/src/strategy/actions/ChooseRpgTargetAction.cpp index dfd1cef5..4b00cfe5 100644 --- a/src/strategy/actions/ChooseRpgTargetAction.cpp +++ b/src/strategy/actions/ChooseRpgTargetAction.cpp @@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target) bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - Player* master = botAI->GetGroupMaster(); + Player* gmaster = botAI->GetGroupMaster(); Player* realMaster = botAI->GetMaster(); AiObjectContext* context = botAI->GetAiObjectContext(); @@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) return false; } - if (!master || bot == master) + if (!gmaster || bot == gmaster) return true; if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) return true; - if (bot->GetDistance(master) > sPlayerbotAIConfig->rpgDistance * 2) + if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2) return false; Formation* formation = AI_VALUE(Formation*, "formation"); - float distance = master->GetDistance2d(pos.getX(), pos.getY()); + float distance = gmaster->GetDistance2d(pos.getX(), pos.getY()); if (!botAI->HasActivePlayerMaster() && distance < 50.0f) { - Player* player = master; - if (!master->isMoving() || + Player* player = gmaster; + if (gmaster && !gmaster->isMoving() || PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance) return true; } - if ((inDungeon || !master->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == master && distance > 5.0f) + if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f) return false; - if (!master->isMoving() && distance < 25.0f) + if (!gmaster->isMoving() && distance < 25.0f) return true; if (distance < formation->GetMaxDistance()) diff --git a/src/strategy/actions/GenericSpellActions.cpp b/src/strategy/actions/GenericSpellActions.cpp index 05938358..9285f20a 100644 --- a/src/strategy/actions/GenericSpellActions.cpp +++ b/src/strategy/actions/GenericSpellActions.cpp @@ -162,8 +162,14 @@ bool CastMeleeDebuffSpellAction::isUseful() bool CastAuraSpellAction::isUseful() { - return GetTarget() && (GetTarget() != nullptr) && CastSpellAction::isUseful() && - !botAI->HasAura(spell, GetTarget(), false, isOwner); + if (!GetTarget() || !CastSpellAction::isUseful()) + return false; + Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration); + if (!aura) + return true; + if (beforeDuration && aura->GetDuration() < beforeDuration) + return true; + return false; } CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell) @@ -251,8 +257,8 @@ Value* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue() return context->GetValue("melee attacker without aura", spell); } -CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner) - : CastAuraSpellAction(botAI, spell, checkIsOwner) +CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration) + : CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration) { range = botAI->GetRange("spell"); } @@ -364,5 +370,5 @@ bool CastDebuffSpellAction::isUseful() return false; } return CastAuraSpellAction::isUseful() && - (target->GetHealth() / AI_VALUE(float, "expected group dps")) >= needLifeTime; + (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime; } \ No newline at end of file diff --git a/src/strategy/actions/GenericSpellActions.h b/src/strategy/actions/GenericSpellActions.h index b9870011..2ab49bb0 100644 --- a/src/strategy/actions/GenericSpellActions.h +++ b/src/strategy/actions/GenericSpellActions.h @@ -37,16 +37,20 @@ protected: class CastAuraSpellAction : public CastSpellAction { public: - CastAuraSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false) + CastAuraSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, bool checkDuration = false, uint32 beforeDuration = 0) : CastSpellAction(botAI, spell) { this->isOwner = isOwner; + this->beforeDuration = beforeDuration; + this->checkDuration = checkDuration; } bool isUseful() override; protected: bool isOwner; + bool checkDuration; + uint32 beforeDuration; }; class CastMeleeSpellAction : public CastSpellAction @@ -107,7 +111,7 @@ public: class CastBuffSpellAction : public CastAuraSpellAction { public: - CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false); + CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0); std::string const GetTargetName() override { return "self target"; } }; diff --git a/src/strategy/actions/ListSpellsAction.cpp b/src/strategy/actions/ListSpellsAction.cpp index 75d25ac9..80277ea8 100644 --- a/src/strategy/actions/ListSpellsAction.cpp +++ b/src/strategy/actions/ListSpellsAction.cpp @@ -11,13 +11,13 @@ std::map ListSpellsAction::skillSpells; std::set ListSpellsAction::vendorItems; -bool CompareSpells(std::pair& s1, std::pair& s2) +bool CompareSpells(const std::pair& s1, const std::pair& s2) { SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first); SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first); if (!si1 || !si2) { - LOG_ERROR("playerbots", "SpellInfo missing."); + LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first); return false; } uint32 p1 = si1->SchoolMask * 20000; @@ -54,7 +54,7 @@ bool CompareSpells(std::pair& s1, std::pairSpellName[0], si1->SpellName[1]) > 0; + return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0; } return p1 > p2; @@ -273,7 +273,11 @@ std::vector> ListSpellsAction::GetSpellList(std:: if (out.str().empty()) continue; - + + if (itr->first == 0) + { + LOG_ERROR("playerbots", "?! {}", itr->first); + } spells.push_back(std::pair(itr->first, out.str())); alreadySeenList += spellInfo->SpellName[0]; alreadySeenList += ","; diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 7f47f9e3..49c8df2b 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -762,7 +762,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // return true; } -bool MovementAction::MoveTo(Unit* target, float distance, MovementPriority priority) +bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriority priority) { if (!IsMovingAllowed(target)) return false; @@ -814,39 +814,25 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance) float combatDistance = bot->GetCombatReach() + target->GetCombatReach(); distance += combatDistance; - if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD)) // target is moving forward, predict the position - { - float needToGo = bot->GetExactDist(target) - distance; - float timeToGo = MoveDelay(abs(needToGo)) + sPlayerbotAIConfig->reactDelay / 1000.0f; - float targetMoveDist = timeToGo * target->GetSpeed(MOVE_RUN); - targetMoveDist = std::min(5.0f, targetMoveDist); - tx += targetMoveDist * cos(target->GetOrientation()); - ty += targetMoveDist * sin(target->GetOrientation()); - if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), - target->GetPositionZ(), tx, ty, tz)) - { - // disable prediction if position is invalid - tx = target->GetPositionX(); - ty = target->GetPositionY(); - tz = target->GetPositionZ(); - } - // Prediction may cause this, which makes ShortenPathUntilDist fail - if (bot->GetExactDist(tx, ty, tz) <= distance) - { - tx = target->GetPositionX(); - ty = target->GetPositionY(); - tz = target->GetPositionZ(); - } - } if (bot->GetExactDist(tx, ty, tz) <= distance) return false; + PathGenerator path(bot); path.CalculatePath(tx, ty, tz, false); PathType type = path.GetPathType(); int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE; if (!(type & typeOk)) return false; - path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), distance); + float shortenTo = distance; + + // Avoid walking too far when moving towards each other + if (bot->GetDistance(tx, ty, tz) >= 10.0f) + shortenTo = std::max(distance, bot->GetDistance(tx, ty, tz) / 2); + + if (bot->GetExactDist(tx, ty, tz) <= shortenTo) + return false; + + path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo); G3D::Vector3 endPos = path.GetPath().back(); return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); @@ -874,7 +860,7 @@ float MovementAction::GetFollowAngle() return 0; } -bool MovementAction::IsMovingAllowed(Unit* target) +bool MovementAction::IsMovingAllowed(WorldObject* target) { if (!target) return false; @@ -1272,6 +1258,8 @@ bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle) // bot->GetMotionMaster()->Clear(); bot->GetMotionMaster()->MoveChase((Unit*)obj, distance); + + // TODO shouldnt this use "last movement" value? WaitForReach(bot->GetExactDist2d(obj) - distance); return true; } @@ -1295,6 +1283,7 @@ float MovementAction::MoveDelay(float distance) return delay; } +// TODO should this be removed? (or modified to use "last movement" value?) void MovementAction::WaitForReach(float distance) { float delay = 1000.0f * MoveDelay(distance); @@ -1313,6 +1302,15 @@ void MovementAction::WaitForReach(float distance) botAI->SetNextCheckDelay((uint32)delay); } +// similiar to botAI->SetNextCheckDelay() but only stops movement +void MovementAction::SetNextMovementDelay(float delayMillis) +{ + AI_VALUE(LastMovement&, "last movement") + .Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), + delayMillis, + MovementPriority::MOVEMENT_FORCED); +} + bool MovementAction::Flee(Unit* target) { Player* master = GetMaster(); @@ -1821,12 +1819,17 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj() { return false; } + if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) + return false; + DynamicObject* dynOwner = aura->GetDynobjOwner(); if (!dynOwner || !dynOwner->IsInWorld()) { return false; } float radius = dynOwner->GetRadius(); + if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius) + return false; if (bot->GetDistance(dynOwner) > radius) { return false; @@ -1840,7 +1843,7 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj() lastTellTimer = time(NULL); lastMoveTimer = getMSTime(); std::ostringstream out; - out << "I'm avoiding " << name.str() << "..."; + out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Aura]"; bot->Say(out.str(), LANG_UNIVERSAL); } return true; @@ -1871,17 +1874,28 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage() { continue; } + // 0 trap with no despawn after cast. 1 trap despawns after cast. 2 bomb casts on spawn. + if (goInfo->trap.type != 0) + continue; + uint32 spellId = goInfo->trap.spellId; if (!spellId) { continue; } + + if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) + continue; + const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo || spellInfo->IsPositive()) { continue; } + float radius = (float)goInfo->trap.diameter / 2; + if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius) + continue; // for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { // if (spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA) { // if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE) { @@ -1905,7 +1919,7 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage() lastTellTimer = time(NULL); lastMoveTimer = getMSTime(); std::ostringstream out; - out << "I'm avoiding " << name.str() << "..."; + out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Trap]"; bot->Say(out.str(), LANG_UNIVERSAL); } return true; @@ -1948,6 +1962,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura() sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell); if (!triggerSpellInfo) continue; + if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) + return false; for (int j = 0; j < MAX_SPELL_EFFECTS; j++) { if (triggerSpellInfo->Effects[j].Effect == SPELL_EFFECT_SCHOOL_DAMAGE) @@ -1957,6 +1973,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura() { break; } + if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius) + continue; std::ostringstream name; name << triggerSpellInfo->SpellName[LOCALE_enUS]; //<< "] (unit)"; if (FleePosition(unit->GetPosition(), radius)) @@ -1966,7 +1984,7 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura() lastTellTimer = time(NULL); lastMoveTimer = getMSTime(); std::ostringstream out; - out << "I'm avoiding " << name.str() << "..."; + out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")" << " Radius " << radius << " - [Unit Trigger]"; bot->Say(out.str(), LANG_UNIVERSAL); } } diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index 6ef93475..7edf119b 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -30,7 +30,7 @@ protected: bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false, bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); - bool MoveTo(Unit* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); + bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); float GetFollowAngle(); bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance); @@ -39,7 +39,8 @@ protected: bool ReachCombatTo(Unit* target, float distance = 0.0f); float MoveDelay(float distance); void WaitForReach(float distance); - bool IsMovingAllowed(Unit* target); + void SetNextMovementDelay(float delayMillis); + bool IsMovingAllowed(WorldObject* target); bool IsMovingAllowed(uint32 mapId, float x, float y, float z); bool IsDuplicateMove(uint32 mapId, float x, float y, float z); bool IsWaitingForLastMove(MovementPriority priority); diff --git a/src/strategy/actions/SayAction.cpp b/src/strategy/actions/SayAction.cpp index 6bf973ab..93d36a00 100644 --- a/src/strategy/actions/SayAction.cpp +++ b/src/strategy/actions/SayAction.cpp @@ -205,21 +205,20 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint return; } - // Disable since ExtractAllItemIds bad performance - // //toxic links - // if (msg.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) - // && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0)) - // { - // HandleToxicLinksReply(bot, chatChannelSource, msg, name); - // return; - // } + //toxic links + if (msg.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) + && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0)) + { + HandleToxicLinksReply(bot, chatChannelSource, msg, name); + return; + } - // //thunderfury - // if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019)) - // { - // HandleThunderfuryReply(bot, chatChannelSource, msg, name); - // return; - // } + //thunderfury + if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019)) + { + HandleThunderfuryReply(bot, chatChannelSource, msg, name); + return; + } auto messageRepy = GenerateReplyMessage(bot, msg, guid1, name); SendGeneralResponse(bot, chatChannelSource, messageRepy, name); @@ -245,6 +244,8 @@ bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chat GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL); break; } + default: + break; } GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 25)); @@ -309,6 +310,8 @@ bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatC GET_PLAYERBOT_AI(bot)->SayToGuild(BOT_TEXT2("suggest_toxic_links", placeholders)); break; } + default: + break; } GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 60)); @@ -317,8 +320,6 @@ bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatC } bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { - // Disable since ExtractAllItemIds bad performance - return false; auto messageItemIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg); if (messageItemIds.empty()) @@ -404,6 +405,8 @@ bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatCha } break; } + default: + break; } GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 60)); } @@ -412,8 +415,6 @@ bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatCha } bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { - // Disable since ExtractAllQuestIds bad performance - return false; auto messageQuestIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg); if (messageQuestIds.empty()) @@ -490,6 +491,8 @@ bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatCh GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); break; } + default: + break; } GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 25)); } @@ -510,9 +513,11 @@ bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatCha } case ChatChannelSource::SRC_GENERAL: { - //may reply to the same channel or whisper - GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL); - GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); + //may reply to the same channel 80% or whisper + if (urand(0, 100) < 80) + GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL); + else + GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); break; } case ChatChannelSource::SRC_TRADE: diff --git a/src/strategy/actions/TalkToQuestGiverAction.cpp b/src/strategy/actions/TalkToQuestGiverAction.cpp index f6e62bb0..772d3ff7 100644 --- a/src/strategy/actions/TalkToQuestGiverAction.cpp +++ b/src/strategy/actions/TalkToQuestGiverAction.cpp @@ -11,6 +11,7 @@ #include "Object.h" #include "Playerbots.h" #include "QuestDef.h" +#include "StatsWeightCalculator.h" #include "WorldPacket.h" #include "BroadcastHelper.h" @@ -176,8 +177,20 @@ void TalkToQuestGiverAction::RewardMultipleItem(Quest const* quest, Object* ques bestIds = BestRewards(quest); if (!bestIds.empty()) { - ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[*bestIds.begin()]); - bot->RewardQuest(quest, *bestIds.begin(), questGiver, true); + StatsWeightCalculator calc(bot); + uint32 best = 0; + float bestScore = 0; + for (uint32 id : bestIds) + { + float score = calc.CalculateItem(quest->RewardChoiceItemId[id]); + if (score > bestScore) + { + bestScore = score; + best = id; + } + } + ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[best]); + bot->RewardQuest(quest, best, questGiver, true); out << "Rewarded " << ChatHelper::FormatItem(item); } else diff --git a/src/strategy/actions/TellLosAction.cpp b/src/strategy/actions/TellLosAction.cpp index 159fb845..03dd3569 100644 --- a/src/strategy/actions/TellLosAction.cpp +++ b/src/strategy/actions/TellLosAction.cpp @@ -130,10 +130,10 @@ bool TellAuraAction::Execute(Event event) return true; } -bool TellExpectedDpsAction::Execute(Event event) +bool TellEstimatedDpsAction::Execute(Event event) { - float dps = AI_VALUE(float, "expected group dps"); - botAI->TellMaster("Expected Group DPS: " + std::to_string(dps)); + float dps = AI_VALUE(float, "estimated group dps"); + botAI->TellMaster("Estimated Group DPS: " + std::to_string(dps)); return true; } diff --git a/src/strategy/actions/TellLosAction.h b/src/strategy/actions/TellLosAction.h index 1adc56d0..988564cf 100644 --- a/src/strategy/actions/TellLosAction.h +++ b/src/strategy/actions/TellLosAction.h @@ -30,10 +30,10 @@ public: virtual bool Execute(Event event); }; -class TellExpectedDpsAction : public Action +class TellEstimatedDpsAction : public Action { public: - TellExpectedDpsAction(PlayerbotAI* ai) : Action(ai, "tell expected dps") {} + TellEstimatedDpsAction(PlayerbotAI* ai) : Action(ai, "tell estimated dps") {} virtual bool Execute(Event event); }; diff --git a/src/strategy/actions/TellTargetAction.cpp b/src/strategy/actions/TellTargetAction.cpp index d1d05384..f6f05639 100644 --- a/src/strategy/actions/TellTargetAction.cpp +++ b/src/strategy/actions/TellTargetAction.cpp @@ -29,13 +29,14 @@ bool TellAttackersAction::Execute(Event event) botAI->TellMaster("--- Attackers ---"); GuidVector attackers = context->GetValue("attackers")->Get(); + int32 count = 0; for (ObjectGuid const guid : attackers) { Unit* unit = botAI->GetUnit(guid); if (!unit || !unit->IsAlive()) continue; - botAI->TellMaster(unit->GetName()); + botAI->TellMaster(std::to_string(++count) + std::string(".") + unit->GetName()); } botAI->TellMaster("--- Threat ---"); diff --git a/src/strategy/actions/UseMeetingStoneAction.cpp b/src/strategy/actions/UseMeetingStoneAction.cpp index 0c0b7285..f5d61b01 100644 --- a/src/strategy/actions/UseMeetingStoneAction.cpp +++ b/src/strategy/actions/UseMeetingStoneAction.cpp @@ -165,16 +165,10 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player) bool SummonAction::Teleport(Player* summoner, Player* player) { - Player* master = GetMaster(); - // if (master->GetMap() && master->GetMap()->IsDungeon()) { - // InstanceMap* map = master->GetMap()->ToInstanceMap(); - // if (map) { - // if (map->CannotEnter(player, true) == Map::CANNOT_ENTER_MAX_PLAYERS) { - // botAI->TellError("I can not enter this dungeon"); - // return false; - // } - // } - // } + // Player* master = GetMaster(); + if (!summoner) + return false; + if (player->GetVehicle()) { botAI->TellError("You cannot summon me while I'm on a vehicle"); @@ -197,13 +191,13 @@ bool SummonAction::Teleport(Player* summoner, Player* player) ->botRepairWhenSummon) // .conf option to repair bot gear when summoned 0 = off, 1 = on bot->DurabilityRepairAll(false, 1.0f, false); - if (master->IsInCombat() && !sPlayerbotAIConfig->allowSummonInCombat) + if (summoner->IsInCombat() && !sPlayerbotAIConfig->allowSummonInCombat) { botAI->TellError("You cannot summon me while you're in combat"); return false; } - if (!master->IsAlive() && !sPlayerbotAIConfig->allowSummonWhenMasterIsDead) + if (!summoner->IsAlive() && !sPlayerbotAIConfig->allowSummonWhenMasterIsDead) { botAI->TellError("You cannot summon me while you're dead"); return false; @@ -218,7 +212,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player) bool revive = sPlayerbotAIConfig->reviveBotWhenSummoned == 2 || - (sPlayerbotAIConfig->reviveBotWhenSummoned == 1 && !master->IsInCombat() && master->IsAlive()); + (sPlayerbotAIConfig->reviveBotWhenSummoned == 1 && !summoner->IsInCombat() && summoner->IsAlive()); if (bot->isDead() && revive) { diff --git a/src/strategy/deathknight/BloodDKStrategy.cpp b/src/strategy/deathknight/BloodDKStrategy.cpp index f76d6795..03bc454c 100644 --- a/src/strategy/deathknight/BloodDKStrategy.cpp +++ b/src/strategy/deathknight/BloodDKStrategy.cpp @@ -100,9 +100,12 @@ void BloodDKStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr))); triggers.push_back( - new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 5), - new NextAction("vampiric blood", ACTION_HIGH + 4), + new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4), new NextAction("death strike", ACTION_HIGH + 3), nullptr))); - // triggers.push_back(new TriggerNode("army of the dead", NextAction::array(0, new NextAction("army of the dead", - // ACTION_HIGH + 6), nullptr))); + triggers.push_back( + new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr))); + triggers.push_back( + new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr))); + triggers.push_back(new TriggerNode( + "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr))); } diff --git a/src/strategy/deathknight/DKActions.h b/src/strategy/deathknight/DKActions.h index 2fe9c708..355a5c52 100644 --- a/src/strategy/deathknight/DKActions.h +++ b/src/strategy/deathknight/DKActions.h @@ -165,7 +165,7 @@ public: class CastGhoulFrenzyAction : public CastBuffSpellAction { public: - CastGhoulFrenzyAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ghoul frenzy") {} + CastGhoulFrenzyAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ghoul frenzy", false, 5000) {} std::string const GetTargetName() override { return "pet target"; } }; @@ -242,7 +242,7 @@ class CastDeathAndDecayAction : public CastSpellAction { public: CastDeathAndDecayAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "death and decay") {} - ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } + // ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } }; class CastHornOfWinterAction : public CastSpellAction diff --git a/src/strategy/deathknight/DKAiObjectContext.cpp b/src/strategy/deathknight/DKAiObjectContext.cpp index 9023f8ac..45d4b65f 100644 --- a/src/strategy/deathknight/DKAiObjectContext.cpp +++ b/src/strategy/deathknight/DKAiObjectContext.cpp @@ -10,6 +10,7 @@ #include "DKTriggers.h" #include "FrostDKStrategy.h" #include "GenericDKNonCombatStrategy.h" +#include "GenericTriggers.h" #include "Playerbots.h" #include "PullStrategy.h" #include "UnholyDKStrategy.h" @@ -73,10 +74,14 @@ public: creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike; creators["plague strike on attacker"] = &DeathKnightTriggerFactoryInternal::plague_strike_on_attacker; creators["icy touch"] = &DeathKnightTriggerFactoryInternal::icy_touch; + creators["icy touch 8s"] = &DeathKnightTriggerFactoryInternal::icy_touch_8s; + creators["dd cd and icy touch 8s"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_icy_touch_8s; creators["death coil"] = &DeathKnightTriggerFactoryInternal::death_coil; creators["icy touch on attacker"] = &DeathKnightTriggerFactoryInternal::icy_touch_on_attacker; creators["improved icy talons"] = &DeathKnightTriggerFactoryInternal::improved_icy_talons; creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike; + creators["plague strike 8s"] = &DeathKnightTriggerFactoryInternal::plague_strike_8s; + creators["dd cd and plague strike 8s"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_plague_strike_8s; creators["horn of winter"] = &DeathKnightTriggerFactoryInternal::horn_of_winter; creators["mind freeze"] = &DeathKnightTriggerFactoryInternal::mind_freeze; creators["mind freeze on enemy healer"] = &DeathKnightTriggerFactoryInternal::mind_freeze_on_enemy_healer; @@ -87,8 +92,11 @@ public: creators["chains of ice"] = &DeathKnightTriggerFactoryInternal::chains_of_ice; creators["unbreakable armor"] = &DeathKnightTriggerFactoryInternal::unbreakable_armor; creators["high blood rune"] = &DeathKnightTriggerFactoryInternal::high_blood_rune; + creators["high frost rune"] = &DeathKnightTriggerFactoryInternal::high_frost_rune; + creators["high unholy rune"] = &DeathKnightTriggerFactoryInternal::high_unholy_rune; creators["freezing fog"] = &DeathKnightTriggerFactoryInternal::freezing_fog; creators["no desolation"] = &DeathKnightTriggerFactoryInternal::no_desolation; + creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation; creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown; creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead; } @@ -98,11 +106,15 @@ private: static Trigger* pestilence_glyph(PlayerbotAI* botAI) { return new PestilenceGlyphTrigger(botAI); } static Trigger* blood_strike(PlayerbotAI* botAI) { return new BloodStrikeTrigger(botAI); } static Trigger* plague_strike(PlayerbotAI* botAI) { return new PlagueStrikeDebuffTrigger(botAI); } + static Trigger* plague_strike_8s(PlayerbotAI* botAI) { return new PlagueStrike8sDebuffTrigger(botAI); } + static Trigger* dd_cd_and_plague_strike_8s(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "plague strike 8s"); } static Trigger* plague_strike_on_attacker(PlayerbotAI* botAI) { return new PlagueStrikeDebuffOnAttackerTrigger(botAI); } static Trigger* icy_touch(PlayerbotAI* botAI) { return new IcyTouchDebuffTrigger(botAI); } + static Trigger* icy_touch_8s(PlayerbotAI* botAI) { return new IcyTouch8sDebuffTrigger(botAI); } + static Trigger* dd_cd_and_icy_touch_8s(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "icy touch 8s"); } static Trigger* death_coil(PlayerbotAI* botAI) { return new DeathCoilTrigger(botAI); } static Trigger* icy_touch_on_attacker(PlayerbotAI* botAI) { return new IcyTouchDebuffOnAttackerTrigger(botAI); } static Trigger* improved_icy_talons(PlayerbotAI* botAI) { return new ImprovedIcyTalonsTrigger(botAI); } @@ -122,8 +134,11 @@ private: static Trigger* chains_of_ice(PlayerbotAI* botAI) { return new ChainsOfIceSnareTrigger(botAI); } static Trigger* unbreakable_armor(PlayerbotAI* botAI) { return new UnbreakableArmorTrigger(botAI); } static Trigger* high_blood_rune(PlayerbotAI* botAI) { return new HighBloodRuneTrigger(botAI); } + static Trigger* high_frost_rune(PlayerbotAI* botAI) { return new HighFrostRuneTrigger(botAI); } + static Trigger* high_unholy_rune(PlayerbotAI* botAI) { return new HighUnholyRuneTrigger(botAI); } static Trigger* freezing_fog(PlayerbotAI* botAI) { return new FreezingFogTrigger(botAI); } static Trigger* no_desolation(PlayerbotAI* botAI) { return new DesolationTrigger(botAI); } + static Trigger* dd_cd_and_no_desolation(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "no desolation"); } static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); } static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); } }; diff --git a/src/strategy/deathknight/DKTriggers.cpp b/src/strategy/deathknight/DKTriggers.cpp index 281ed586..2d1cbb15 100644 --- a/src/strategy/deathknight/DKTriggers.cpp +++ b/src/strategy/deathknight/DKTriggers.cpp @@ -39,14 +39,21 @@ bool PestilenceGlyphTrigger::IsActive() bool HighBloodRuneTrigger::IsActive() { - // bot->Say(std::to_string(bot->GetBaseRune(0)) + "_" + std::to_string(bot->GetRuneCooldown(0)) + " " + - // std::to_string(bot->GetBaseRune(1)) + "_" + std::to_string(bot->GetRuneCooldown(1)), LANG_UNIVERSAL); return !bot->GetRuneCooldown(0) && !bot->GetRuneCooldown(1); } +bool HighFrostRuneTrigger::IsActive() +{ + return !bot->GetRuneCooldown(2) && !bot->GetRuneCooldown(3); +} + +bool HighUnholyRuneTrigger::IsActive() +{ + return !bot->GetRuneCooldown(4) && !bot->GetRuneCooldown(5); +} bool DesolationTrigger::IsActive() { - return bot->HasAura(66817) && !botAI->HasAura("desolation", GetTarget(), false, true, -1, true); + return bot->HasAura(66817) && BuffTrigger::IsActive(); } bool DeathAndDecayCooldownTrigger::IsActive() @@ -54,6 +61,6 @@ bool DeathAndDecayCooldownTrigger::IsActive() uint32 spellId = AI_VALUE2(uint32, "spell id", name); if (!spellId) return true; - - return bot->HasSpellCooldown(spellId); + + return bot->GetSpellCooldownDelay(spellId) >= 3000; } \ No newline at end of file diff --git a/src/strategy/deathknight/DKTriggers.h b/src/strategy/deathknight/DKTriggers.h index ecbf7964..ef82394b 100644 --- a/src/strategy/deathknight/DKTriggers.h +++ b/src/strategy/deathknight/DKTriggers.h @@ -17,14 +17,26 @@ BUFF_TRIGGER(ImprovedIcyTalonsTrigger, "improved icy talons"); class PlagueStrikeDebuffTrigger : public DebuffTrigger { public: - PlagueStrikeDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", true, .0f) {} + PlagueStrikeDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", 1, true, .0f) {} +}; + +class PlagueStrike8sDebuffTrigger : public DebuffTrigger +{ +public: + PlagueStrike8sDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", 1, true, .0f, 3000) {} }; // DEBUFF_CHECKISOWNER_TRIGGER(IcyTouchDebuffTrigger, "frost fever"); class IcyTouchDebuffTrigger : public DebuffTrigger { public: - IcyTouchDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", true, .0f) {} + IcyTouchDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", 1, true, .0f) {} +}; + +class IcyTouch8sDebuffTrigger : public DebuffTrigger +{ +public: + IcyTouch8sDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", 1, true, .0f, 3000) {} }; BUFF_TRIGGER(UnbreakableArmorTrigger, "unbreakable armor"); @@ -139,6 +151,20 @@ public: bool IsActive() override; }; +class HighFrostRuneTrigger : public Trigger +{ +public: + HighFrostRuneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high frost rune") {} + bool IsActive() override; +}; + +class HighUnholyRuneTrigger : public Trigger +{ +public: + HighUnholyRuneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high unholy rune") {} + bool IsActive() override; +}; + class FreezingFogTrigger : public HasAuraTrigger { public: @@ -148,7 +174,7 @@ public: class DesolationTrigger : public BuffTrigger { public: - DesolationTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "desolation") {} + DesolationTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "desolation", 1, false, true, 10000) {} bool IsActive() override; }; diff --git a/src/strategy/deathknight/FrostDKStrategy.cpp b/src/strategy/deathknight/FrostDKStrategy.cpp index 87d12b17..6ec84558 100644 --- a/src/strategy/deathknight/FrostDKStrategy.cpp +++ b/src/strategy/deathknight/FrostDKStrategy.cpp @@ -26,7 +26,7 @@ public: // creators["icebound fortitude"] = &icebound_fortitude; // creators["mind freeze"] = &mind_freeze; // creators["hungering cold"] = &hungering_cold; - // creators["unbreakable armor"] = &unbreakable_armor; + creators["unbreakable armor"] = &unbreakable_armor; // creators["improved icy talons"] = &improved_icy_talons; } @@ -70,6 +70,13 @@ private: /*A*/ nullptr, /*C*/ nullptr); } + static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("unbreakable armor", + /*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr), + /*A*/ nullptr, + /*C*/ nullptr); + } }; FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI) @@ -80,24 +87,32 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI) NextAction** FrostDKStrategy::getDefaultActions() { return NextAction::array( - 0, new NextAction("obliterate", ACTION_DEFAULT + 0.5f), new NextAction("frost strike", ACTION_DEFAULT + 0.4f), - // new NextAction("death strike", ACTION_NORMAL + 3), - new NextAction("empower rune weapon", ACTION_DEFAULT + 0.2f), + 0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f), + new NextAction("frost strike", ACTION_DEFAULT + 0.4f), + new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL); } void FrostDKStrategy::InitTriggers(std::vector& triggers) { GenericDKStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr))); + + triggers.push_back(new TriggerNode( + "freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_DEFAULT + 0.5f), nullptr))); + + triggers.push_back(new TriggerNode( + "high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.2f), nullptr))); + triggers.push_back(new TriggerNode( "army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr))); - triggers.push_back(new TriggerNode( - "unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_NORMAL + 4), nullptr))); - triggers.push_back(new TriggerNode( - "high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 1), nullptr))); - triggers.push_back(new TriggerNode( - "freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 1), nullptr))); + triggers.push_back( + new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr))); + triggers.push_back(new TriggerNode( + "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr))); // triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune // weapon", ACTION_NORMAL + 4), nullptr))); } diff --git a/src/strategy/deathknight/GenericDKStrategy.cpp b/src/strategy/deathknight/GenericDKStrategy.cpp index 23693bcf..55146a56 100644 --- a/src/strategy/deathknight/GenericDKStrategy.cpp +++ b/src/strategy/deathknight/GenericDKStrategy.cpp @@ -135,7 +135,7 @@ private: { return new ActionNode("death and decay", /*P*/ nullptr, - /*A*/ NextAction::array(0, new NextAction("blood tap"), nullptr), + /*A*/ nullptr, /*C*/ nullptr); } @@ -176,33 +176,14 @@ void GenericDKStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("mind freeze on enemy healer", NextAction::array(0, new NextAction("mind freeze on enemy healer", ACTION_HIGH + 1), nullptr))); - triggers.push_back(new TriggerNode( - "bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_NORMAL + 1), nullptr))); triggers.push_back(new TriggerNode( "horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr))); - // triggers.push_back(new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("reach melee", - // ACTION_NORMAL + 8), nullptr))); + triggers.push_back(new TriggerNode("critical health", + NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr))); + triggers.push_back( new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5), new NextAction("rune tap", ACTION_HIGH + 4), nullptr))); - triggers.push_back(new TriggerNode("medium health", - NextAction::array(0, new NextAction("rune tap", ACTION_NORMAL + 4), - new NextAction("death strike", ACTION_NORMAL + 3), nullptr))); - triggers.push_back( - new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr))); - triggers.push_back( - new TriggerNode("icy touch on attacker", - NextAction::array(0, new NextAction("icy touch on attacker", ACTION_HIGH + 1), nullptr))); - triggers.push_back(new TriggerNode( - "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr))); - triggers.push_back( - new TriggerNode("plague strike on attacker", - NextAction::array(0, new NextAction("plague strike on attacker", ACTION_HIGH + 1), nullptr))); - // triggers.push_back(new TriggerNode("high aoe", - // NextAction::array(0, - // new NextAction("death and decay", ACTION_NORMAL + 5), - // new NextAction("pestilence", ACTION_NORMAL + 4), - // new NextAction("blood boil", ACTION_NORMAL + 3), nullptr))); triggers.push_back( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_HIGH + 9), new NextAction("pestilence", ACTION_NORMAL + 4), diff --git a/src/strategy/deathknight/UnholyDKStrategy.cpp b/src/strategy/deathknight/UnholyDKStrategy.cpp index a5174f36..fb1baca4 100644 --- a/src/strategy/deathknight/UnholyDKStrategy.cpp +++ b/src/strategy/deathknight/UnholyDKStrategy.cpp @@ -26,7 +26,7 @@ public: // creators["summon gargoyle"] = &army of the dead; // creators["anti magic shell"] = &anti_magic_shell; // creators["anti magic zone"] = &anti_magic_zone; - // creators["ghoul frenzy"] = &ghoul_frenzy; + creators["ghoul frenzy"] = &ghoul_frenzy; creators["corpse explosion"] = &corpse_explosion; creators["icy touch"] = &icy_touch; } @@ -35,15 +35,21 @@ private: static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("death strike", - /*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr), + /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr), + /*A*/ nullptr, + /*C*/ nullptr); + } + static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("ghoul frenzy", + /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } - static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("corpse explosion", - /*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr), + /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } @@ -51,14 +57,14 @@ private: static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("scourge strike", - /*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr), + /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("icy touch", - /*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr), + /*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } @@ -72,29 +78,57 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI NextAction** UnholyDKStrategy::getDefaultActions() { return NextAction::array( - 0, new NextAction("death and decay", ACTION_DEFAULT + 0.5f), - new NextAction("horn of winter", ACTION_DEFAULT + 0.4f), - new NextAction("summon gargoyle", ACTION_DEFAULT + 0.3f), new NextAction("death coil", ACTION_DEFAULT + 0.2f), - new NextAction("scourge strike", ACTION_NORMAL + 0.1f), new NextAction("melee", ACTION_DEFAULT), nullptr); + 0, new NextAction("death and decay", ACTION_HIGH + 5), + new NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f), + new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f), + new NextAction("horn of winter", ACTION_DEFAULT + 0.2f), + new NextAction("death coil", ACTION_DEFAULT + 0.1f), + new NextAction("melee", ACTION_DEFAULT), nullptr); } void UnholyDKStrategy::InitTriggers(std::vector& triggers) { GenericDKStrategy::InitTriggers(triggers); + triggers.push_back(new TriggerNode( + "death and decay cooldown", NextAction::array(0, + new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f), + new NextAction("scourge strike", ACTION_DEFAULT + 0.8f), + new NextAction("blood boil", ACTION_DEFAULT + 0.7f), + new NextAction("icy touch", ACTION_DEFAULT + 0.6f), + new NextAction("plague strike", ACTION_DEFAULT + 0.5f), + nullptr))); + + triggers.push_back(new TriggerNode("dd cd and no desolation", + NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.75f), nullptr))); + + // triggers.push_back( + // new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr))); + // triggers.push_back(new TriggerNode( + // "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr))); + + triggers.push_back(new TriggerNode( + "high frost rune", NextAction::array(0, + new NextAction("icy touch", ACTION_NORMAL + 3), nullptr))); + + triggers.push_back(new TriggerNode( + "high unholy rune", NextAction::array(0, + new NextAction("plague strike", ACTION_NORMAL + 2), nullptr))); + + triggers.push_back(new TriggerNode( + "high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 1), nullptr))); + + triggers.push_back( + new TriggerNode("dd cd and plague strike 8s", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr))); + + triggers.push_back( + new TriggerNode("dd cd and icy touch 8s", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 1), nullptr))); + // triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction(, ACTION_NORMAL + 2), nullptr))); triggers.push_back(new TriggerNode( "army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr))); - triggers.push_back(new TriggerNode("critical health", - NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr))); - triggers.push_back(new TriggerNode("no desolation", - NextAction::array(0, new NextAction("blood strike", ACTION_HIGH + 4), nullptr))); - triggers.push_back(new TriggerNode( - "death and decay cooldown", NextAction::array(0, new NextAction("ghoul frenzy", ACTION_NORMAL + 5.0f), - new NextAction("scourge strike", ACTION_NORMAL + 4.0f), - new NextAction("blood boil", ACTION_NORMAL + 3.0f), - new NextAction("icy touch", ACTION_NORMAL + 2.0f), - new NextAction("plague strike", ACTION_NORMAL + 1.0f), nullptr))); + triggers.push_back( + new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_HIGH + 1), nullptr))); } void UnholyDKAoeStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/druid/CatDpsDruidStrategy.cpp b/src/strategy/druid/CatDpsDruidStrategy.cpp index 80120330..e640cebd 100644 --- a/src/strategy/druid/CatDpsDruidStrategy.cpp +++ b/src/strategy/druid/CatDpsDruidStrategy.cpp @@ -70,7 +70,7 @@ private: { return new ActionNode("mangle (cat)", /*P*/ nullptr, - /*A*/ NextAction::array(0, new NextAction("claw"), nullptr), + /*A*/ nullptr, /*C*/ nullptr); } @@ -122,38 +122,58 @@ CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrateg NextAction** CatDpsDruidStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.1f), nullptr); + return NextAction::array(0, new NextAction("tiger's fury", ACTION_DEFAULT + 0.1f), nullptr); } void CatDpsDruidStrategy::InitTriggers(std::vector& triggers) { FeralDruidStrategy::InitTriggers(triggers); + // Default priority + triggers.push_back(new TriggerNode("almost full energy available", + NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr))); + triggers.push_back(new TriggerNode("combo points not full", + NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr))); + triggers.push_back(new TriggerNode("almost full energy available", + NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr))); + triggers.push_back(new TriggerNode("combo points not full and high energy", + NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr))); + triggers.push_back(new TriggerNode("almost full energy available", + NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr))); + triggers.push_back(new TriggerNode("combo points not full and high energy", + NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr))); triggers.push_back( - new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 2), nullptr))); + new TriggerNode("faerie fire (feral)", + NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f), nullptr))); + + // Main spell triggers.push_back( - new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_NORMAL + 5), nullptr))); + new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr))); + triggers.push_back( + new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr))); + triggers.push_back(new TriggerNode("combo points available", + NextAction::array(0, new NextAction("rip", ACTION_HIGH + 6), nullptr))); triggers.push_back(new TriggerNode( - "combo points available", NextAction::array(0, new NextAction("ferocious bite", ACTION_NORMAL + 9), nullptr))); + "ferocious bite time", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 5), nullptr))); triggers.push_back( - new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_EMERGENCY + 1), nullptr))); + new TriggerNode("target with combo points almost dead", + NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr))); + triggers.push_back(new TriggerNode("mangle (cat)", + NextAction::array(0, new NextAction("mangle (cat)", ACTION_HIGH + 3), nullptr))); + triggers.push_back(new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_HIGH + 2), nullptr))); + triggers.push_back( + new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_HIGH + 1), nullptr))); + + // AOE + triggers.push_back( + new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr))); triggers.push_back(new TriggerNode( - "faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH), nullptr))); + "light aoe", NextAction::array(0, new NextAction("rake on attacker", ACTION_HIGH + 2), nullptr))); + // Reach target triggers.push_back(new TriggerNode( - "tiger's fury", NextAction::array(0, new NextAction("tiger's fury", ACTION_EMERGENCY + 1), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9), nullptr))); triggers.push_back( - new TriggerNode("behind target", NextAction::array(0, new NextAction("pounce", ACTION_HIGH + 1), nullptr))); - // triggers.push_back(new TriggerNode("player has no flag", NextAction::array(0, new NextAction("prowl", - // ACTION_HIGH), nullptr))); triggers.push_back(new TriggerNode("enemy out of melee", NextAction::array(0, new - // NextAction("prowl", ACTION_INTERRUPT + 1), nullptr))); - triggers.push_back( - new TriggerNode("player has flag", NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY), nullptr))); - triggers.push_back(new TriggerNode("enemy flagcarrier near", - NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY), nullptr))); + new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("dash", ACTION_HIGH + 8), nullptr))); } -void CatAoeDruidStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 2), nullptr))); -} +void CatAoeDruidStrategy::InitTriggers(std::vector& triggers) {} diff --git a/src/strategy/druid/DruidAiObjectContext.cpp b/src/strategy/druid/DruidAiObjectContext.cpp index b1181ae2..04652487 100644 --- a/src/strategy/druid/DruidAiObjectContext.cpp +++ b/src/strategy/druid/DruidAiObjectContext.cpp @@ -83,6 +83,8 @@ public: creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire; creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp; creators["tiger's fury"] = &DruidTriggerFactoryInternal::tigers_fury; + creators["berserk"] = &DruidTriggerFactoryInternal::berserk; + creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar; creators["rake"] = &DruidTriggerFactoryInternal::rake; creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild; creators["mark of the wild on party"] = &DruidTriggerFactoryInternal::mark_of_the_wild_on_party; @@ -101,6 +103,8 @@ public: creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse; creators["eclipse (solar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_solar_cooldown; creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown; + creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat; + creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time; } private: @@ -117,6 +121,8 @@ private: static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); } static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); } static Trigger* tigers_fury(PlayerbotAI* botAI) { return new TigersFuryTrigger(botAI); } + static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); } + static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); } static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); } static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); } static Trigger* mark_of_the_wild_on_party(PlayerbotAI* botAI) { return new MarkOfTheWildOnPartyTrigger(botAI); } @@ -133,6 +139,8 @@ private: static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); } static Trigger* eclipse_solar_cooldown(PlayerbotAI* ai) { return new EclipseSolarCooldownTrigger(ai); } static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); } + static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); } + static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); } }; class DruidAiObjectContextInternal : public NamedObjectContext @@ -174,6 +182,7 @@ public: creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat; creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat; creators["rake"] = &DruidAiObjectContextInternal::rake; + creators["rake on attacker"] = &DruidAiObjectContextInternal::rake_on_attacker; creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite; creators["rip"] = &DruidAiObjectContextInternal::rip; creators["cower"] = &DruidAiObjectContextInternal::cower; @@ -188,6 +197,7 @@ public: creators["abolish poison on party"] = &DruidAiObjectContextInternal::abolish_poison_on_party; creators["berserk"] = &DruidAiObjectContextInternal::berserk; creators["tiger's fury"] = &DruidAiObjectContextInternal::tigers_fury; + creators["savage roar"] = &DruidAiObjectContextInternal::savage_roar; creators["mark of the wild"] = &DruidAiObjectContextInternal::mark_of_the_wild; creators["mark of the wild on party"] = &DruidAiObjectContextInternal::mark_of_the_wild_on_party; creators["regrowth"] = &DruidAiObjectContextInternal::regrowth; @@ -257,6 +267,7 @@ private: static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); } static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); } static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); } + static Action* rake_on_attacker(PlayerbotAI* botAI) { return new CastRakeOnMeleeAttackersAction(botAI); } static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); } static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); } static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); } @@ -271,6 +282,7 @@ private: static Action* abolish_poison_on_party(PlayerbotAI* botAI) { return new CastAbolishPoisonOnPartyAction(botAI); } static Action* berserk(PlayerbotAI* botAI) { return new CastBerserkAction(botAI); } static Action* tigers_fury(PlayerbotAI* botAI) { return new CastTigersFuryAction(botAI); } + static Action* savage_roar(PlayerbotAI* botAI) { return new CastSavageRoarAction(botAI); } static Action* mark_of_the_wild(PlayerbotAI* botAI) { return new CastMarkOfTheWildAction(botAI); } static Action* mark_of_the_wild_on_party(PlayerbotAI* botAI) { return new CastMarkOfTheWildOnPartyAction(botAI); } static Action* regrowth(PlayerbotAI* botAI) { return new CastRegrowthAction(botAI); } diff --git a/src/strategy/druid/DruidCatActions.cpp b/src/strategy/druid/DruidCatActions.cpp new file mode 100644 index 00000000..03571a8e --- /dev/null +++ b/src/strategy/druid/DruidCatActions.cpp @@ -0,0 +1 @@ +#include "DruidCatActions.h" diff --git a/src/strategy/druid/DruidCatActions.h b/src/strategy/druid/DruidCatActions.h index 579bdd00..cf4c6c35 100644 --- a/src/strategy/druid/DruidCatActions.h +++ b/src/strategy/druid/DruidCatActions.h @@ -35,10 +35,23 @@ public: CastTigersFuryAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "tiger's fury") {} }; +class CastSavageRoarAction : public CastBuffSpellAction +{ +public: + CastSavageRoarAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "savage roar") {} + std::string const GetTargetName() override { return "current target"; } +}; + class CastRakeAction : public CastDebuffSpellAction { public: - CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake") {} + CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {} +}; + +class CastRakeOnMeleeAttackersAction : public CastDebuffSpellOnMeleeAttackerAction +{ +public: + CastRakeOnMeleeAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnMeleeAttackerAction(botAI, "rake", true, 6.0f) {} }; class CastClawAction : public CastMeleeSpellAction @@ -65,10 +78,10 @@ public: CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {} }; -class CastRipAction : public CastMeleeSpellAction +class CastRipAction : public CastMeleeDebuffSpellAction { public: - CastRipAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rip") {} + CastRipAction(PlayerbotAI* botAI) : CastMeleeDebuffSpellAction(botAI, "rip", true, 12.0f) {} }; class CastShredAction : public CastMeleeSpellAction diff --git a/src/strategy/druid/DruidTriggers.cpp b/src/strategy/druid/DruidTriggers.cpp index d839f973..a083ade1 100644 --- a/src/strategy/druid/DruidTriggers.cpp +++ b/src/strategy/druid/DruidTriggers.cpp @@ -22,8 +22,6 @@ bool ThornsOnPartyTrigger::IsActive() return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("thorns", GetTarget()); } -bool MoonfireTrigger::IsActive() { return DebuffTrigger::IsActive() && !GetTarget()->HasUnitState(UNIT_STATE_ROOT); } - bool EntanglingRootsKiteTrigger::IsActive() { return DebuffTrigger::IsActive() && AI_VALUE(uint8, "attacker count") < 3 && !GetTarget()->GetPower(POWER_MANA); diff --git a/src/strategy/druid/DruidTriggers.h b/src/strategy/druid/DruidTriggers.h index 392b487e..50020835 100644 --- a/src/strategy/druid/DruidTriggers.h +++ b/src/strategy/druid/DruidTriggers.h @@ -9,6 +9,8 @@ #include "CureTriggers.h" #include "GenericTriggers.h" #include "Player.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" #include "SharedDefines.h" class PlayerbotAI; @@ -73,8 +75,6 @@ class MoonfireTrigger : public DebuffTrigger { public: MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {} - - bool IsActive() override; }; class FaerieFireTrigger : public DebuffTrigger @@ -95,10 +95,22 @@ public: BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {} }; -class TigersFuryTrigger : public BoostTrigger +class TigersFuryTrigger : public BuffTrigger { public: - TigersFuryTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "tiger's fury") {} + TigersFuryTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "tiger's fury") {} +}; + +class BerserkTrigger : public BoostTrigger +{ +public: + BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {} +}; + +class SavageRoarTrigger : public BuffTrigger +{ +public: + SavageRoarTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "savage roar") {} }; class NaturesGraspTrigger : public BoostTrigger @@ -212,4 +224,43 @@ public: bool IsActive() override { return bot->HasSpellCooldown(48518); } }; +class MangleCatTrigger : public DebuffTrigger +{ +public: + MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {} + bool IsActive() override + { + return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true) + && !botAI->HasAura("trauma", GetTarget(), false, false, -1, true); + } +}; + +class FerociousBiteTimeTrigger : public Trigger +{ +public: + FerociousBiteTimeTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite time") {} + bool IsActive() override + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) + return false; + + uint8 cp = AI_VALUE2(uint8, "combo", "current target"); + if (cp < 5) + return false; + + Aura* roar = botAI->GetAura("savage roar", bot); + bool roarCheck = !roar || roar->GetDuration() > 10000; + if (!roarCheck) + return false; + + Aura* rip = botAI->GetAura("rip", target, true); + bool ripCheck = !rip || rip->GetDuration() > 10000; + if (!ripCheck) + return false; + + return true; + } +}; + #endif diff --git a/src/strategy/druid/FeralDruidStrategy.cpp b/src/strategy/druid/FeralDruidStrategy.cpp index ed4b030b..91e315b2 100644 --- a/src/strategy/druid/FeralDruidStrategy.cpp +++ b/src/strategy/druid/FeralDruidStrategy.cpp @@ -112,4 +112,6 @@ void FeralDruidStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr))); triggers.push_back(new TriggerNode("enemy flagcarrier near", NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr))); + triggers.push_back( + new TriggerNode("berserk", NextAction::array(0, new NextAction("berserk", ACTION_HIGH + 6), nullptr))); } diff --git a/src/strategy/generic/CastTimeStrategy.cpp b/src/strategy/generic/CastTimeStrategy.cpp index 7d1b4545..483e2b8c 100644 --- a/src/strategy/generic/CastTimeStrategy.cpp +++ b/src/strategy/generic/CastTimeStrategy.cpp @@ -47,7 +47,7 @@ float CastTimeMultiplier::GetValue(Action* action) return 1.0f; } - if (castTime > (1000 * target->GetHealth() / AI_VALUE(float, "expected group dps"))) + if (castTime > (1000 * target->GetHealth() / AI_VALUE(float, "estimated group dps"))) { return 0.1f; } diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 78c5aef7..58db48f5 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -89,7 +89,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger triggers.push_back( new TriggerNode("bwl", NextAction::array(0, new NextAction("bwl chat shortcut", relevance), NULL))); triggers.push_back( - new TriggerNode("dps", NextAction::array(0, new NextAction("tell expected dps", relevance), NULL))); + new TriggerNode("dps", NextAction::array(0, new NextAction("tell estimated dps", relevance), NULL))); triggers.push_back( new TriggerNode("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL))); } diff --git a/src/strategy/generic/UseFoodStrategy.cpp b/src/strategy/generic/UseFoodStrategy.cpp index c0c29b51..04841e4a 100644 --- a/src/strategy/generic/UseFoodStrategy.cpp +++ b/src/strategy/generic/UseFoodStrategy.cpp @@ -5,12 +5,19 @@ #include "UseFoodStrategy.h" +#include "PlayerbotAIConfig.h" #include "Playerbots.h" void UseFoodStrategy::InitTriggers(std::vector& triggers) { Strategy::InitTriggers(triggers); + if (sPlayerbotAIConfig->freeFood) + triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); + else + triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); - triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr))); - triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); + if (sPlayerbotAIConfig->freeFood) + triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); + else + triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr))); } diff --git a/src/strategy/hunter/DpsHunterStrategy.cpp b/src/strategy/hunter/DpsHunterStrategy.cpp index 325f3b93..3ba171a2 100644 --- a/src/strategy/hunter/DpsHunterStrategy.cpp +++ b/src/strategy/hunter/DpsHunterStrategy.cpp @@ -41,12 +41,13 @@ DpsHunterStrategy::DpsHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy NextAction** DpsHunterStrategy::getDefaultActions() { return NextAction::array( - 0, new NextAction("kill shot", ACTION_DEFAULT + 0.6f), new NextAction("chimera shot", ACTION_DEFAULT + 0.5f), - new NextAction("explosive shot", ACTION_DEFAULT + 0.4f), new NextAction("aimed shot", ACTION_DEFAULT + 0.3f), - /*new NextAction("arcane shot", ACTION_DEFAULT + 0.2f),*/ new NextAction("steady shot", ACTION_DEFAULT + 0.1f), + 0, new NextAction("kill shot", ACTION_DEFAULT + 0.8f), new NextAction("chimera shot", ACTION_DEFAULT + 0.7f), + new NextAction("explosive shot", ACTION_DEFAULT + 0.6f), new NextAction("aimed shot", ACTION_DEFAULT + 0.5f), + new NextAction("silencing shot", ACTION_DEFAULT + 0.4f), + new NextAction("kill command", ACTION_DEFAULT + 0.3f), + // new NextAction("arcane shot", ACTION_DEFAULT + 0.2f), + new NextAction("steady shot", ACTION_DEFAULT + 0.1f), new NextAction("auto shot", ACTION_DEFAULT), nullptr); - // return NextAction::array(0, new NextAction("explosive shot", 11.0f), new NextAction("auto shot", 10.0f), new - // NextAction("auto attack", 9.0f), nullptr); } void DpsHunterStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/hunter/GenericHunterStrategy.cpp b/src/strategy/hunter/GenericHunterStrategy.cpp index e59df4cd..0974e2d4 100644 --- a/src/strategy/hunter/GenericHunterStrategy.cpp +++ b/src/strategy/hunter/GenericHunterStrategy.cpp @@ -95,7 +95,7 @@ void GenericHunterStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("enemy too close for auto shot", NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr))); triggers.push_back( - new TriggerNode("misdirection on main tank", + new TriggerNode("low tank threat", NextAction::array(0, new NextAction("misdirection on main tank", ACTION_HIGH + 7), NULL))); triggers.push_back( new TriggerNode("low health", NextAction::array(0, new NextAction("deterrence", ACTION_HIGH + 5), nullptr))); diff --git a/src/strategy/hunter/HunterActions.h b/src/strategy/hunter/HunterActions.h index 868905c6..87595b3b 100644 --- a/src/strategy/hunter/HunterActions.h +++ b/src/strategy/hunter/HunterActions.h @@ -78,6 +78,9 @@ END_SPELL_ACTION() BEGIN_RANGED_SPELL_ACTION(CastKillShotAction, "kill shot") END_SPELL_ACTION() +BEGIN_RANGED_SPELL_ACTION(CastSilencingShotAction, "silencing shot") +END_SPELL_ACTION() + BEGIN_RANGED_SPELL_ACTION(CastTranquilizingShotAction, "tranquilizing shot") END_SPELL_ACTION() @@ -139,6 +142,14 @@ public: std::string const GetTargetName() override { return "pet target"; } }; +class CastKillCommandAction : public CastAuraSpellAction +{ +public: + CastKillCommandAction(PlayerbotAI* botAI) : CastAuraSpellAction(botAI, "kill command") {} + + std::string const GetTargetName() override { return "pet target"; } +}; + class CastRevivePetAction : public CastBuffSpellAction { public: diff --git a/src/strategy/hunter/HunterAiObjectContext.cpp b/src/strategy/hunter/HunterAiObjectContext.cpp index 32548aad..dbfb6a54 100644 --- a/src/strategy/hunter/HunterAiObjectContext.cpp +++ b/src/strategy/hunter/HunterAiObjectContext.cpp @@ -143,6 +143,7 @@ public: creators["scorpid sting"] = &HunterAiObjectContextInternal::scorpid_sting; creators["hunter's mark"] = &HunterAiObjectContextInternal::hunters_mark; creators["mend pet"] = &HunterAiObjectContextInternal::mend_pet; + creators["kill command"] = &HunterAiObjectContextInternal::kill_command; creators["revive pet"] = &HunterAiObjectContextInternal::revive_pet; creators["call pet"] = &HunterAiObjectContextInternal::call_pet; creators["black arrow"] = &HunterAiObjectContextInternal::black_arrow; @@ -171,6 +172,7 @@ public: creators["steady shot"] = &HunterAiObjectContextInternal::steady_shot; creators["kill shot"] = &HunterAiObjectContextInternal::kill_shot; creators["misdirection on main tank"] = &HunterAiObjectContextInternal::misdirection_on_main_tank; + creators["silencing shot"] = &HunterAiObjectContextInternal::silencing_shot; } private: @@ -196,6 +198,7 @@ private: static Action* scorpid_sting(PlayerbotAI* botAI) { return new CastScorpidStingAction(botAI); } static Action* hunters_mark(PlayerbotAI* botAI) { return new CastHuntersMarkAction(botAI); } static Action* mend_pet(PlayerbotAI* botAI) { return new CastMendPetAction(botAI); } + static Action* kill_command(PlayerbotAI* botAI) { return new CastKillCommandAction(botAI); } static Action* revive_pet(PlayerbotAI* botAI) { return new CastRevivePetAction(botAI); } static Action* call_pet(PlayerbotAI* botAI) { return new CastCallPetAction(botAI); } static Action* black_arrow(PlayerbotAI* botAI) { return new CastBlackArrow(botAI); } @@ -217,6 +220,8 @@ private: static Action* steady_shot(PlayerbotAI* ai) { return new CastSteadyShotAction(ai); } static Action* kill_shot(PlayerbotAI* ai) { return new CastKillShotAction(ai); } static Action* misdirection_on_main_tank(PlayerbotAI* ai) { return new CastMisdirectionOnMainTankAction(ai); } + static Action* silencing_shot(PlayerbotAI* ai) { return new CastSilencingShotAction(ai); } + }; HunterAiObjectContext::HunterAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI) diff --git a/src/strategy/paladin/DpsPaladinStrategy.cpp b/src/strategy/paladin/DpsPaladinStrategy.cpp index ecbe3af7..efd55a6a 100644 --- a/src/strategy/paladin/DpsPaladinStrategy.cpp +++ b/src/strategy/paladin/DpsPaladinStrategy.cpp @@ -82,23 +82,32 @@ DpsPaladinStrategy::DpsPaladinStrategy(PlayerbotAI* botAI) : GenericPaladinStrat NextAction** DpsPaladinStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("crusader strike", ACTION_DEFAULT + 0.4f), - new NextAction("judgement of wisdom", ACTION_DEFAULT + 0.3f), - new NextAction("divine storm", ACTION_DEFAULT + 0.2f), + return NextAction::array(0, + new NextAction("hammer of wrath", ACTION_DEFAULT + 0.6f), + new NextAction("crusader strike", ACTION_DEFAULT + 0.5f), + new NextAction("judgement of wisdom", ACTION_DEFAULT + 0.4f), + new NextAction("divine storm", ACTION_DEFAULT + 0.3f), new NextAction("consecration", ACTION_DEFAULT + 0.1f), - new NextAction("melee", ACTION_DEFAULT), NULL); + new NextAction("melee", ACTION_DEFAULT), nullptr); } void DpsPaladinStrategy::InitTriggers(std::vector& triggers) { GenericPaladinStrategy::InitTriggers(triggers); + // triggers.push_back(new TriggerNode( + // "enough mana", NextAction::array(0, new NextAction("consecration", ACTION_DEFAULT + 0.2f), nullptr))); + triggers.push_back( + new TriggerNode("art of war", NextAction::array(0, new NextAction("exorcism", ACTION_DEFAULT + 0.2f), nullptr))); triggers.push_back( new TriggerNode("seal", NextAction::array(0, new NextAction("seal of corruption", ACTION_HIGH), NULL))); // triggers.push_back(new TriggerNode("seal", NextAction::array(0, new NextAction("seal of command", 90.0f), // nullptr))); triggers.push_back( new TriggerNode("low mana", NextAction::array(0, new NextAction("seal of wisdom", ACTION_HIGH + 5), nullptr))); + + triggers.push_back(new TriggerNode( + "avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr))); // triggers.push_back(new TriggerNode("sanctity aura", NextAction::array(0, new NextAction("sanctity aura", 90.0f), // nullptr))); triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("repentance or // shield", ACTION_CRITICAL_HEAL + 3), new NextAction("holy light", ACTION_CRITICAL_HEAL + 2), nullptr))); @@ -112,11 +121,11 @@ void DpsPaladinStrategy::InitTriggers(std::vector& triggers) // triggers.push_back(new TriggerNode("repentance", NextAction::array(0, new NextAction("repentance", // ACTION_INTERRUPT + 2), nullptr))); triggers.push_back(new TriggerNode( - "medium aoe", NextAction::array(0, new NextAction("consecration", ACTION_HIGH + 3), nullptr))); - triggers.push_back( - new TriggerNode("art of war", NextAction::array(0, new NextAction("exorcism", ACTION_HIGH + 2), nullptr))); - triggers.push_back(new TriggerNode("target critical health", - NextAction::array(0, new NextAction("hammer of wrath", ACTION_HIGH), nullptr))); + "medium aoe", NextAction::array(0, + new NextAction("divine storm", ACTION_HIGH + 4), + new NextAction("consecration", ACTION_HIGH + 3), nullptr))); + // triggers.push_back(new TriggerNode("target critical health", + // NextAction::array(0, new NextAction("hammer of wrath", ACTION_HIGH), nullptr))); // triggers.push_back(new TriggerNode( // "not facing target", // NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL))); diff --git a/src/strategy/paladin/GenericPaladinStrategy.cpp b/src/strategy/paladin/GenericPaladinStrategy.cpp index 676a6159..d25a7518 100644 --- a/src/strategy/paladin/GenericPaladinStrategy.cpp +++ b/src/strategy/paladin/GenericPaladinStrategy.cpp @@ -37,7 +37,7 @@ void GenericPaladinStrategy::InitTriggers(std::vector& triggers) "protect party member", NextAction::array(0, new NextAction("blessing of protection on party", ACTION_EMERGENCY + 2), nullptr))); triggers.push_back( - new TriggerNode("medium mana", NextAction::array(0, new NextAction("divine plea", ACTION_HIGH), NULL))); + new TriggerNode("high mana", NextAction::array(0, new NextAction("divine plea", ACTION_HIGH), NULL))); } void PaladinCureStrategy::InitTriggers(std::vector& triggers) @@ -61,8 +61,7 @@ void PaladinCureStrategy::InitTriggers(std::vector& triggers) void PaladinBoostStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr))); + // triggers.push_back(new TriggerNode("divine favor", NextAction::array(0, new NextAction("divine favor", // ACTION_HIGH + 1), nullptr))); } diff --git a/src/strategy/paladin/HealPaladinStrategy.cpp b/src/strategy/paladin/HealPaladinStrategy.cpp index dd1edf23..70c461ce 100644 --- a/src/strategy/paladin/HealPaladinStrategy.cpp +++ b/src/strategy/paladin/HealPaladinStrategy.cpp @@ -50,7 +50,9 @@ void HealPaladinStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("medium group heal occasion", - NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5), nullptr))); + NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5), + new NextAction("avenging wrath", ACTION_HIGH + 4), + nullptr))); triggers.push_back( new TriggerNode("party member critical health", diff --git a/src/strategy/paladin/TankPaladinStrategy.cpp b/src/strategy/paladin/TankPaladinStrategy.cpp index 649fe21e..553d2a7d 100644 --- a/src/strategy/paladin/TankPaladinStrategy.cpp +++ b/src/strategy/paladin/TankPaladinStrategy.cpp @@ -98,6 +98,8 @@ void TankPaladinStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("holy shield", ACTION_HIGH + 4), nullptr))); // triggers.push_back(new TriggerNode("blessing", NextAction::array(0, new NextAction("blessing of sanctuary", // ACTION_HIGH + 9), nullptr))); + triggers.push_back(new TriggerNode( + "avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr))); triggers.push_back( new TriggerNode("target critical health", NextAction::array(0, new NextAction("hammer of wrath", ACTION_CRITICAL_HEAL), nullptr))); diff --git a/src/strategy/raids/RaidStrategyContext.h b/src/strategy/raids/RaidStrategyContext.h index 29e997d2..a60ed12d 100644 --- a/src/strategy/raids/RaidStrategyContext.h +++ b/src/strategy/raids/RaidStrategyContext.h @@ -6,6 +6,7 @@ #include "RaidBwlStrategy.h" #include "RaidNaxxStrategy.h" #include "RaidMcStrategy.h" +#include "RaidAq20Strategy.h" class RaidStrategyContext : public NamedObjectContext { @@ -19,6 +20,7 @@ public: creators["bwl"] = &RaidStrategyContext::bwl; creators["uld"] = &RaidStrategyContext::uld; creators["mc"] = &RaidStrategyContext::mc; + creators["aq20"] = &RaidStrategyContext::aq20; } private: @@ -26,6 +28,7 @@ private: static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } + static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } }; -#endif \ No newline at end of file +#endif diff --git a/src/strategy/raids/aq20/RaidAq20ActionContext.h b/src/strategy/raids/aq20/RaidAq20ActionContext.h new file mode 100644 index 00000000..ea3afcf4 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20ActionContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H +#define _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "RaidAq20Actions.h" + +class RaidAq20ActionContext : public NamedObjectContext +{ +public: + RaidAq20ActionContext() + { + creators["aq20 use crystal"] = &RaidAq20ActionContext::use_crystal; + } + +private: + static Action* use_crystal(PlayerbotAI* ai) { return new Aq20UseCrystalAction(ai); } +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Actions.cpp b/src/strategy/raids/aq20/RaidAq20Actions.cpp new file mode 100644 index 00000000..9b7e4065 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Actions.cpp @@ -0,0 +1,49 @@ +#include "RaidAq20Actions.h" + +#include "Playerbots.h" +#include "RaidAq20Utils.h" + + +bool Aq20UseCrystalAction::Execute(Event event) +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred")) + { + if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss)) + { + float botDist = bot->GetDistance(crystal); + if (botDist > INTERACTION_DISTANCE) + return MoveTo(bot->GetMapId(), + crystal->GetPositionX() + frand(-3.5f, 3.5f), + crystal->GetPositionY() + frand(-3.5f, 3.5f), + crystal->GetPositionZ()); + + // if we're already in range just wait here until it's time to activate crystal + SetNextMovementDelay(500); + + // don't activate crystal if boss too far or its already been activated + if (boss->GetDistance(crystal) > 25.0f || + crystal->HasGameObjectFlag(GO_FLAG_IN_USE)) + return false; + + // don't activate crystal if boss doesn't have buff yet AND isn't going to have it soon + // (though ideally bot should activate it ~5 seconds early due to time it takes for + // crystal to activate and remove buff) + if (!RaidAq20Utils::IsOssirianBuffActive(boss) && + RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss) > 5000) + return false; + + // this makes crystal do its animation (then disappear after) + WorldPacket data1(CMSG_GAMEOBJ_USE); + data1 << crystal->GetGUID(); + bot->GetSession()->HandleGameObjectUseOpcode(data1); + + // this makes crystal actually remove the buff and put on debuff (took a while to figure that out) + WorldPacket data2(CMSG_GAMEOBJ_USE); + data2 << crystal->GetGUID(); + bot->GetSession()->HandleGameobjectReportUse(data2); + + return true; + } + } + return false; +} diff --git a/src/strategy/raids/aq20/RaidAq20Actions.h b/src/strategy/raids/aq20/RaidAq20Actions.h new file mode 100644 index 00000000..59a9b437 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Actions.h @@ -0,0 +1,14 @@ +#ifndef _PLAYERBOT_RAIDAQ20ACTIONS_H +#define _PLAYERBOT_RAIDAQ20ACTIONS_H + +#include "MovementActions.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" + +class Aq20UseCrystalAction : public MovementAction +{ +public: + Aq20UseCrystalAction(PlayerbotAI* botAI, std::string const name = "aq20 use crystal") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Strategy.cpp b/src/strategy/raids/aq20/RaidAq20Strategy.cpp new file mode 100644 index 00000000..2b8cbe8c --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Strategy.cpp @@ -0,0 +1,11 @@ +#include "RaidAq20Strategy.h" + +#include "Strategy.h" + +void RaidAq20Strategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode("aq20 move to crystal", + NextAction::array(0, new NextAction("aq20 use crystal", ACTION_RAID), nullptr))); + +} diff --git a/src/strategy/raids/aq20/RaidAq20Strategy.h b/src/strategy/raids/aq20/RaidAq20Strategy.h new file mode 100644 index 00000000..97ff7453 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Strategy.h @@ -0,0 +1,17 @@ +#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H +#define _PLAYERBOT_RAIDAQ20STRATEGY_H + +#include "AiObjectContext.h" +#include "Multiplier.h" +#include "Strategy.h" + +class RaidAq20Strategy : public Strategy +{ +public: + RaidAq20Strategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "aq20"; } + virtual void InitTriggers(std::vector& triggers) override; + // virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20TriggerContext.h b/src/strategy/raids/aq20/RaidAq20TriggerContext.h new file mode 100644 index 00000000..b49ae1c6 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20TriggerContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H + +#include "AiObjectContext.h" +#include "NamedObjectContext.h" +#include "RaidAq20Triggers.h" + +class RaidAq20TriggerContext : public NamedObjectContext +{ +public: + RaidAq20TriggerContext() + { + creators["aq20 move to crystal"] = &RaidAq20TriggerContext::move_to_crystal; + } + +private: + static Trigger* move_to_crystal(PlayerbotAI* ai) { return new Aq20MoveToCrystalTrigger(ai); } +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Triggers.cpp b/src/strategy/raids/aq20/RaidAq20Triggers.cpp new file mode 100644 index 00000000..fbda881a --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Triggers.cpp @@ -0,0 +1,37 @@ +#include "RaidAq20Triggers.h" + +#include "SharedDefines.h" +#include "RaidAq20Utils.h" + + +bool Aq20MoveToCrystalTrigger::IsActive() +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred")) + { + if (boss->IsInCombat()) + { + // if buff is active move to crystal + if (RaidAq20Utils::IsOssirianBuffActive(boss)) + return true; + + // if buff is not active a debuff will be, buff becomes active once debuff expires + // so move to crystal when debuff almost done, or based debuff time left and + // distance bot is from crystal (ie: start moving early enough to make it) + int32 debuffTimeRemaining = RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss); + if (debuffTimeRemaining < 5000) + return true; + if (debuffTimeRemaining < 30000) + { + if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss)) + { + float botDist = bot->GetDistance(crystal); + float timeToReach = botDist / bot->GetSpeed(MOVE_RUN); + // bot should ideally activate crystal a ~5 seconds early (due to time it takes for crystal + // to activate) so aim to get there in time to do so + return debuffTimeRemaining - 5000 < timeToReach * 1000.0f; + } + } + } + } + return false; +} diff --git a/src/strategy/raids/aq20/RaidAq20Triggers.h b/src/strategy/raids/aq20/RaidAq20Triggers.h new file mode 100644 index 00000000..219de8f4 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Triggers.h @@ -0,0 +1,14 @@ +#ifndef _PLAYERBOT_RAIDAQ20TRIGGERS_H +#define _PLAYERBOT_RAIDAQ20TRIGGERS_H + +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "Trigger.h" + +class Aq20MoveToCrystalTrigger : public Trigger +{ +public: + Aq20MoveToCrystalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aq20 move to crystal") {} + bool IsActive() override; +}; +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Utils.cpp b/src/strategy/raids/aq20/RaidAq20Utils.cpp new file mode 100644 index 00000000..41637af7 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Utils.cpp @@ -0,0 +1,38 @@ +#include "RaidAq20Utils.h" + +#include "SpellAuras.h" + +uint32 const OSSIRIAN_BUFF = 25176; +uint32 const OSSIRIAN_DEBUFFS[] = {25177, 25178, 25180, 25181, 25183}; +uint32 const OSSIRIAN_CRYSTAL_GO_ENTRY = 180619; + +bool RaidAq20Utils::IsOssirianBuffActive(Unit* ossirian) +{ + return ossirian && ossirian->HasAura(OSSIRIAN_BUFF); +} + +int32 RaidAq20Utils::GetOssirianDebuffTimeRemaining(Unit* ossirian) +{ + int32 retVal = 0xffffff; + if (ossirian) + { + for (uint32 debuff : OSSIRIAN_DEBUFFS) + { + if (AuraApplication* auraApplication = ossirian->GetAuraApplication(debuff)) + { + if (Aura* aura = auraApplication->GetBase()) + { + int32 duration = aura->GetDuration(); + if (retVal > duration) + retVal = duration; + } + } + } + } + return retVal; +} + +GameObject* RaidAq20Utils::GetNearestCrystal(Unit* ossirian) +{ + return ossirian ? ossirian->FindNearestGameObject(OSSIRIAN_CRYSTAL_GO_ENTRY, 200.0f) : nullptr; +} diff --git a/src/strategy/raids/aq20/RaidAq20Utils.h b/src/strategy/raids/aq20/RaidAq20Utils.h new file mode 100644 index 00000000..b78340d0 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Utils.h @@ -0,0 +1,15 @@ +#ifndef _PLAYERBOT_RAIDAQ20UTILS_H +#define _PLAYERBOT_RAIDAQ20UTILS_H + +#include "GameObject.h" +#include "Unit.h" + +class RaidAq20Utils +{ +public: + static bool IsOssirianBuffActive(Unit* ossirian); + static int32 GetOssirianDebuffTimeRemaining(Unit* ossirian); + static GameObject* GetNearestCrystal(Unit* ossirian); +}; + +#endif diff --git a/src/strategy/raids/naxxramas/RaidNaxxActions.h b/src/strategy/raids/naxxramas/RaidNaxxActions.h index 4c877510..b065ba74 100644 --- a/src/strategy/raids/naxxramas/RaidNaxxActions.h +++ b/src/strategy/raids/naxxramas/RaidNaxxActions.h @@ -64,6 +64,7 @@ public: this->prev_phase = 0; this->prev_erupt = 0; this->prev_timer = 0; + ResetSafe(); waypoints.push_back(std::make_pair(2793.58f, -3665.93f)); waypoints.push_back(std::make_pair(2775.49f, -3674.43f)); waypoints.push_back(std::make_pair(2762.30f, -3684.59f)); diff --git a/src/strategy/rogue/AssassinationRogueStrategy.cpp b/src/strategy/rogue/AssassinationRogueStrategy.cpp index 380ac292..5f3d2211 100644 --- a/src/strategy/rogue/AssassinationRogueStrategy.cpp +++ b/src/strategy/rogue/AssassinationRogueStrategy.cpp @@ -83,7 +83,7 @@ void AssassinationRogueStrategy::InitTriggers(std::vector& trigger new TriggerNode("medium aoe", NextAction::array(0, new NextAction("fan of knives", ACTION_NORMAL + 5), NULL))); triggers.push_back(new TriggerNode( - "tricks of the trade on main tank", + "low tank threat", NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), NULL))); triggers.push_back(new TriggerNode( diff --git a/src/strategy/rogue/DpsRogueStrategy.cpp b/src/strategy/rogue/DpsRogueStrategy.cpp index 91cc8044..0fd0d003 100644 --- a/src/strategy/rogue/DpsRogueStrategy.cpp +++ b/src/strategy/rogue/DpsRogueStrategy.cpp @@ -141,7 +141,7 @@ void DpsRogueStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("expose armor", ACTION_HIGH + 3), nullptr))); triggers.push_back(new TriggerNode( - "tricks of the trade on main tank", + "low tank threat", NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), nullptr))); } diff --git a/src/strategy/rogue/RogueAiObjectContext.cpp b/src/strategy/rogue/RogueAiObjectContext.cpp index 355c8953..351c7c12 100644 --- a/src/strategy/rogue/RogueAiObjectContext.cpp +++ b/src/strategy/rogue/RogueAiObjectContext.cpp @@ -77,8 +77,6 @@ public: creators["tricks of the trade on main tank"] = &RogueTriggerFactoryInternal::tricks_of_the_trade_on_main_tank; creators["adrenaline rush"] = &RogueTriggerFactoryInternal::adrenaline_rush; creators["blade fury"] = &RogueTriggerFactoryInternal::blade_fury; - creators["target with combo points almost dead"] = - &RogueTriggerFactoryInternal::target_with_combo_points_almost_dead; } private: @@ -102,10 +100,6 @@ private: { return new TricksOfTheTradeOnMainTankTrigger(ai); } - static Trigger* target_with_combo_points_almost_dead(PlayerbotAI* ai) - { - return new TargetWithComboPointsLowerHealTrigger(ai, 3, 3.0f); - } }; class RogueAiObjectContextInternal : public NamedObjectContext diff --git a/src/strategy/rogue/RogueTriggers.cpp b/src/strategy/rogue/RogueTriggers.cpp index 31e1a390..d4b89443 100644 --- a/src/strategy/rogue/RogueTriggers.cpp +++ b/src/strategy/rogue/RogueTriggers.cpp @@ -124,14 +124,3 @@ bool OffHandWeaponNoEnchantTrigger::IsActive() return false; return true; } - -bool TargetWithComboPointsLowerHealTrigger::IsActive() -{ - Unit* target = AI_VALUE(Unit*, "current target"); - if (!target || !target->IsAlive() || !target->IsInWorld()) - { - return false; - } - return ComboPointsAvailableTrigger::IsActive() && - (target->GetHealth() / AI_VALUE(float, "expected group dps")) <= lifeTime; -} \ No newline at end of file diff --git a/src/strategy/rogue/RogueTriggers.h b/src/strategy/rogue/RogueTriggers.h index 467bd9fc..48ce772e 100644 --- a/src/strategy/rogue/RogueTriggers.h +++ b/src/strategy/rogue/RogueTriggers.h @@ -127,17 +127,6 @@ public: TricksOfTheTradeOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "tricks of the trade", true) {} }; -class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger -{ -public: - TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f) - : ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime) - { - } - bool IsActive() override; -private: - float lifeTime; -}; #endif diff --git a/src/strategy/shaman/CasterShamanStrategy.cpp b/src/strategy/shaman/CasterShamanStrategy.cpp index 88e3ca06..746abf86 100644 --- a/src/strategy/shaman/CasterShamanStrategy.cpp +++ b/src/strategy/shaman/CasterShamanStrategy.cpp @@ -51,8 +51,8 @@ void CasterShamanStrategy::InitTriggers(std::vector& triggers) // triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", // ACTION_NORMAL + 9), nullptr))); triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new // NextAction("flametongue weapon", 23.0f), nullptr))); - triggers.push_back(new TriggerNode( - "enough mana", NextAction::array(0, new NextAction("chain lightning", ACTION_DEFAULT + 0.1f), nullptr))); + // triggers.push_back(new TriggerNode( + // "enough mana", NextAction::array(0, new NextAction("chain lightning", ACTION_DEFAULT + 0.1f), nullptr))); triggers.push_back(new TriggerNode("main hand weapon no imbue", NextAction::array(0, new NextAction("flametongue weapon", 22.0f), nullptr))); @@ -66,7 +66,9 @@ void CasterShamanStrategy::InitTriggers(std::vector& triggers) // shock", 21.0f), nullptr))); triggers.push_back( new TriggerNode("no fire totem", NextAction::array(0, new NextAction("totem of wrath", 15.0f), NULL))); - + triggers.push_back(new TriggerNode("fire elemental totem", + NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr))); + triggers.push_back( new TriggerNode("medium mana", NextAction::array(0, new NextAction("thunderstorm", ACTION_HIGH + 1), nullptr))); diff --git a/src/strategy/shaman/HealShamanStrategy.cpp b/src/strategy/shaman/HealShamanStrategy.cpp index 7fda4651..ff305559 100644 --- a/src/strategy/shaman/HealShamanStrategy.cpp +++ b/src/strategy/shaman/HealShamanStrategy.cpp @@ -104,6 +104,11 @@ void HealShamanStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("medium mana", NextAction::array(0, new NextAction("mana tide totem", ACTION_HIGH + 5), NULL))); + triggers.push_back( + new TriggerNode("no fire totem", NextAction::array(0, new NextAction("flametongue totem", 7.0f), + new NextAction("searing totem", 6.0f), nullptr))); + triggers.push_back(new TriggerNode("fire elemental totem", + NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr))); triggers.push_back(new TriggerNode( "party member to heal out of spell range", NextAction::array(0, new NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 1), nullptr))); diff --git a/src/strategy/shaman/MeleeShamanStrategy.cpp b/src/strategy/shaman/MeleeShamanStrategy.cpp index 0764f8a6..d38c163a 100644 --- a/src/strategy/shaman/MeleeShamanStrategy.cpp +++ b/src/strategy/shaman/MeleeShamanStrategy.cpp @@ -72,11 +72,8 @@ void MeleeShamanStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr))); triggers.push_back( - new TriggerNode("maelstrom weapon", NextAction::array(0, new NextAction("lightning bolt", 25.0f), nullptr))); - triggers.push_back(new TriggerNode("not facing target", - NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr))); - // triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of - // enemy contact", ACTION_NORMAL + 8), nullptr))); + new TriggerNode("maelstrom weapon 4", NextAction::array(0, new NextAction("lightning bolt", 25.0f), nullptr))); + triggers.push_back(new TriggerNode( "medium aoe", NextAction::array(0, new NextAction("strength of earth totem", ACTION_LIGHT_HEAL), nullptr))); triggers.push_back(new TriggerNode( @@ -86,9 +83,8 @@ void MeleeShamanStrategy::InitTriggers(std::vector& triggers) "no fire totem", NextAction::array(0, new NextAction("reach melee", 23.0f), new NextAction("magma totem", 22.0f), nullptr))); - triggers.push_back(new TriggerNode("fire elemental totem", - NextAction::array(0, new NextAction("reach melee", 33.0f), - new NextAction("fire elemental totem", 32.0f), nullptr))); + triggers.push_back(new TriggerNode( + "fire elemental totem", NextAction::array(0, new NextAction("fire elemental totem melee", 32.0f), nullptr))); triggers.push_back( new TriggerNode("no air totem", NextAction::array(0, new NextAction("windfury totem", 20.0f), nullptr))); diff --git a/src/strategy/shaman/ShamanActions.cpp b/src/strategy/shaman/ShamanActions.cpp index 7394c320..9ce29240 100644 --- a/src/strategy/shaman/ShamanActions.cpp +++ b/src/strategy/shaman/ShamanActions.cpp @@ -17,7 +17,7 @@ bool CastTotemAction::isUseful() { return false; } - float dps = AI_VALUE(float, "expected group dps"); + float dps = AI_VALUE(float, "estimated group dps"); if (target->GetHealth() / dps < needLifeTime) { return false; @@ -51,11 +51,14 @@ bool CastMagmaTotemAction::isUseful() { } bool CastFireNovaAction::isUseful() { + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) + return false; Creature* fireTotem = bot->GetMap()->GetCreature(bot->m_SummonSlot[1]); if (!fireTotem) return false; - if (bot->GetDistance(fireTotem) > 8.0f) + if (target->GetDistance(fireTotem) > 8.0f) return false; return CastMeleeSpellAction::isUseful(); diff --git a/src/strategy/shaman/ShamanActions.h b/src/strategy/shaman/ShamanActions.h index 2568cd11..c4f6ce38 100644 --- a/src/strategy/shaman/ShamanActions.h +++ b/src/strategy/shaman/ShamanActions.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_SHAMANACTIONS_H #include "GenericSpellActions.h" +#include "Playerbots.h" #include "SharedDefines.h" class PlayerbotAI; @@ -426,6 +427,20 @@ public: virtual bool isUseful() override { return CastTotemAction::isUseful(); } }; +class CastFireElementalTotemMeleeAction : public CastTotemAction +{ +public: + CastFireElementalTotemMeleeAction(PlayerbotAI* ai) : CastTotemAction(ai, "fire elemental totem", "", 0.0f) {} + virtual std::string const GetTargetName() override { return "self target"; } + virtual bool isUseful() override + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target || !bot->IsWithinMeleeRange(target)) + return false; + return CastTotemAction::isUseful(); + } +}; + class CastWrathOfAirTotemAction : public CastTotemAction { public: diff --git a/src/strategy/shaman/ShamanAiObjectContext.cpp b/src/strategy/shaman/ShamanAiObjectContext.cpp index f4a821ce..e67f4230 100644 --- a/src/strategy/shaman/ShamanAiObjectContext.cpp +++ b/src/strategy/shaman/ShamanAiObjectContext.cpp @@ -110,13 +110,17 @@ public: creators["no water totem"] = &ShamanATriggerFactoryInternal::no_water_totem; creators["no air totem"] = &ShamanATriggerFactoryInternal::no_air_totem; creators["earth shield on main tank"] = &ShamanATriggerFactoryInternal::earth_shield_on_main_tank; - creators["maelstrom weapon"] = &ShamanATriggerFactoryInternal::maelstrom_weapon; + creators["maelstrom weapon 3"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_3; + creators["maelstrom weapon 4"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_4; + creators["maelstrom weapon 5"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_5; creators["flame shock"] = &ShamanATriggerFactoryInternal::flame_shock; creators["wrath of air totem"] = &ShamanATriggerFactoryInternal::wrath_of_air_totem; } private: - static Trigger* maelstrom_weapon(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI); } + static Trigger* maelstrom_weapon_3(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 3); } + static Trigger* maelstrom_weapon_4(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 4); } + static Trigger* maelstrom_weapon_5(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 5); } static Trigger* heroism(PlayerbotAI* botAI) { return new HeroismTrigger(botAI); } static Trigger* bloodlust(PlayerbotAI* botAI) { return new BloodlustTrigger(botAI); } static Trigger* elemental_mastery(PlayerbotAI* botAI) { return new ElementalMasteryTrigger(botAI); } @@ -234,6 +238,7 @@ public: creators["lava burst"] = &ShamanAiObjectContextInternal::lava_burst; creators["earth shield on main tank"] = &ShamanAiObjectContextInternal::earth_shield_on_main_tank; creators["fire elemental totem"] = &ShamanAiObjectContextInternal::fire_elemental_totem; + creators["fire elemental totem melee"] = &ShamanAiObjectContextInternal::fire_elemental_totem_melee; creators["totem of wrath"] = &ShamanAiObjectContextInternal::totem_of_wrath; creators["wrath of air totem"] = &ShamanAiObjectContextInternal::wrath_of_air_totem; creators["shamanistic rage"] = &ShamanAiObjectContextInternal::shamanistic_rage; @@ -314,6 +319,7 @@ private: static Action* earth_shield_on_main_tank(PlayerbotAI* ai) { return new CastEarthShieldOnMainTankAction(ai); } static Action* totem_of_wrath(PlayerbotAI* ai) { return new CastTotemOfWrathAction(ai); } static Action* fire_elemental_totem(PlayerbotAI* ai) { return new CastFireElementalTotemAction(ai); } + static Action* fire_elemental_totem_melee(PlayerbotAI* ai) { return new CastFireElementalTotemMeleeAction(ai); } static Action* wrath_of_air_totem(PlayerbotAI* ai) { return new CastWrathOfAirTotemAction(ai); } static Action* shamanistic_rage(PlayerbotAI* ai) { return new CastShamanisticRageAction(ai); } static Action* feral_spirit(PlayerbotAI* ai) { return new CastFeralSpiritAction(ai); } diff --git a/src/strategy/shaman/ShamanTriggers.h b/src/strategy/shaman/ShamanTriggers.h index 97e9ec03..2ab651a1 100644 --- a/src/strategy/shaman/ShamanTriggers.h +++ b/src/strategy/shaman/ShamanTriggers.h @@ -241,7 +241,7 @@ public: class MaelstromWeaponTrigger : public HasAuraStackTrigger { public: - MaelstromWeaponTrigger(PlayerbotAI* botAI) : HasAuraStackTrigger(botAI, "maelstrom weapon", 5) {} + MaelstromWeaponTrigger(PlayerbotAI* botAI, int stack = 5) : HasAuraStackTrigger(botAI, "maelstrom weapon", stack) {} }; class WindShearInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger diff --git a/src/strategy/shaman/TotemsShamanStrategy.cpp b/src/strategy/shaman/TotemsShamanStrategy.cpp index cfa0c3be..f3346a5c 100644 --- a/src/strategy/shaman/TotemsShamanStrategy.cpp +++ b/src/strategy/shaman/TotemsShamanStrategy.cpp @@ -13,20 +13,13 @@ void TotemsShamanStrategy::InitTriggers(std::vector& triggers) { GenericShamanStrategy::InitTriggers(triggers); - triggers.push_back(new TriggerNode("fire elemental totem", - NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr))); - triggers.push_back( - new TriggerNode("no air totem", NextAction::array(0, new NextAction("wrath of air totem", 8.0f), NULL))); + new TriggerNode("no air totem", NextAction::array(0, new NextAction("wrath of air totem", 8.0f), nullptr))); triggers.push_back( new TriggerNode("no water totem", NextAction::array(0, new NextAction("mana spring totem", 7.0f), new NextAction("healing stream totem", 6.0f), nullptr))); - triggers.push_back( - new TriggerNode("no fire totem", NextAction::array(0, new NextAction("flametongue totem", 7.0f), - new NextAction("searing totem", 6.0f), nullptr))); - triggers.push_back(new TriggerNode("strength of earth totem", - NextAction::array(0, new NextAction("strength of earth totem", 6.0f), NULL))); + NextAction::array(0, new NextAction("strength of earth totem", 6.0f), nullptr))); } diff --git a/src/strategy/triggers/GenericTriggers.cpp b/src/strategy/triggers/GenericTriggers.cpp index c1f80eb7..f700e472 100644 --- a/src/strategy/triggers/GenericTriggers.cpp +++ b/src/strategy/triggers/GenericTriggers.cpp @@ -10,9 +10,11 @@ #include "BattlegroundWS.h" #include "CreatureAI.h" #include "ObjectGuid.h" +#include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "SharedDefines.h" #include "TemporarySummon.h" +#include "ThreatMgr.h" #include "Timer.h" bool LowManaTrigger::IsActive() @@ -64,7 +66,7 @@ bool PetAttackTrigger::IsActive() bool HighManaTrigger::IsActive() { - return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < 65; + return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig->highMana; } bool AlmostFullManaTrigger::IsActive() @@ -83,6 +85,19 @@ bool EnergyAvailable::IsActive() { return AI_VALUE2(uint8, "energy", "self targe bool ComboPointsAvailableTrigger::IsActive() { return AI_VALUE2(uint8, "combo", "current target") >= amount; } +bool ComboPointsNotFullTrigger::IsActive() { return AI_VALUE2(uint8, "combo", "current target") < amount; } + +bool TargetWithComboPointsLowerHealTrigger::IsActive() +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target || !target->IsAlive() || !target->IsInWorld()) + { + return false; + } + return ComboPointsAvailableTrigger::IsActive() && + (target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime; +} + bool LoseAggroTrigger::IsActive() { return !AI_VALUE2(bool, "has aggro", "current target"); } bool HasAggroTrigger::IsActive() { return AI_VALUE2(bool, "has aggro", "current target"); } @@ -137,7 +152,16 @@ bool OutNumberedTrigger::IsActive() bool BuffTrigger::IsActive() { Unit* target = GetTarget(); - return SpellTrigger::IsActive() && !botAI->HasAura(spell, target, false, checkIsOwner); + if (!target) + return false; + if (!SpellTrigger::IsActive()) + return false; + Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration); + if (!aura) + return true; + if (beforeDuration && aura->GetDuration() < beforeDuration) + return true; + return false; } Value* BuffOnPartyTrigger::GetTargetValue() @@ -171,6 +195,22 @@ bool MyAttackerCountTrigger::IsActive() return AI_VALUE2(bool, "combat", "self target") && AI_VALUE(uint8, "my attacker count") >= amount; } +bool LowTankThreatTrigger::IsActive() +{ + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + return false; + + Unit* current_target = AI_VALUE(Unit*, "current target"); + if (!current_target) + return false; + + ThreatMgr& mgr = current_target->GetThreatMgr(); + float threat = mgr.GetThreat(bot); + float tankThreat = mgr.GetThreat(mt); + return tankThreat == 0.0f || threat > tankThreat * 0.5f; +} + bool AoeTrigger::IsActive() { Unit* current_target = AI_VALUE(Unit*, "current target"); @@ -221,7 +261,7 @@ bool DebuffTrigger::IsActive() { return false; } - return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "expected group dps")) >= needLifeTime; + return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime; } bool DebuffOnBossTrigger::IsActive() diff --git a/src/strategy/triggers/GenericTriggers.h b/src/strategy/triggers/GenericTriggers.h index 35a49e3f..e9990c3e 100644 --- a/src/strategy/triggers/GenericTriggers.h +++ b/src/strategy/triggers/GenericTriggers.h @@ -10,6 +10,7 @@ #include "HealthTriggers.h" #include "RangeTriggers.h" +#include "Trigger.h" class PlayerbotAI; class Unit; @@ -113,6 +114,30 @@ public: bool IsActive() override; }; +class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger +{ +public: + TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f) + : ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime) + { + } + bool IsActive() override; + +private: + float lifeTime; +}; + +class ComboPointsNotFullTrigger : public StatAvailable +{ +public: + ComboPointsNotFullTrigger(PlayerbotAI* botAI, int32 amount = 5, std::string const name = "combo points not full") + : StatAvailable(botAI, amount, name) + { + } + + bool IsActive() override; +}; + class LoseAggroTrigger : public Trigger { public: @@ -224,6 +249,13 @@ public: MediumThreatTrigger(PlayerbotAI* botAI) : MyAttackerCountTrigger(botAI, 2) {} }; +class LowTankThreatTrigger : public Trigger +{ +public: + LowTankThreatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "low tank threat") {} + bool IsActive() override; +}; + class AoeTrigger : public AttackerCountTrigger { public: @@ -276,10 +308,12 @@ public: class BuffTrigger : public SpellTrigger { public: - BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false) + BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0) : SpellTrigger(botAI, spell, checkInterval) { this->checkIsOwner = checkIsOwner; + this->checkDuration = checkDuration; + this->beforeDuration = beforeDuration; } public: @@ -288,6 +322,8 @@ public: protected: bool checkIsOwner; + bool checkDuration; + uint32 beforeDuration; }; class BuffOnPartyTrigger : public BuffTrigger @@ -347,8 +383,8 @@ class DebuffTrigger : public BuffTrigger { public: DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, - float needLifeTime = 8.0f) - : BuffTrigger(botAI, spell, checkInterval, checkIsOwner), needLifeTime(needLifeTime) + float needLifeTime = 8.0f, uint32 beforeDuration = 0) + : BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime) { } diff --git a/src/strategy/triggers/TriggerContext.h b/src/strategy/triggers/TriggerContext.h index 788b7743..860ee682 100644 --- a/src/strategy/triggers/TriggerContext.h +++ b/src/strategy/triggers/TriggerContext.h @@ -64,6 +64,7 @@ public: creators["light energy available"] = &TriggerContext::LightEnergyAvailable; creators["medium energy available"] = &TriggerContext::MediumEnergyAvailable; creators["high energy available"] = &TriggerContext::HighEnergyAvailable; + creators["almost full energy available"] = &TriggerContext::AlmostFullEnergyAvailable; creators["loot available"] = &TriggerContext::LootAvailable; creators["no attackers"] = &TriggerContext::NoAttackers; @@ -96,8 +97,12 @@ public: creators["combo points available"] = &TriggerContext::ComboPointsAvailable; creators["combo points 3 available"] = &TriggerContext::ComboPoints3Available; + creators["target with combo points almost dead"] = &TriggerContext::target_with_combo_points_almost_dead; + creators["combo points not full"] = &TriggerContext::ComboPointsNotFull; + creators["combo points not full and high energy"] = &TriggerContext::ComboPointsNotFullAndHighEnergy; creators["medium threat"] = &TriggerContext::MediumThreat; + creators["low tank threat"] = &TriggerContext::low_tank_threat; creators["dead"] = &TriggerContext::Dead; creators["corpse near"] = &TriggerContext::corpse_near; @@ -279,6 +284,7 @@ private: static Trigger* LightEnergyAvailable(PlayerbotAI* botAI) { return new LightEnergyAvailableTrigger(botAI); } static Trigger* MediumEnergyAvailable(PlayerbotAI* botAI) { return new MediumEnergyAvailableTrigger(botAI); } static Trigger* HighEnergyAvailable(PlayerbotAI* botAI) { return new HighEnergyAvailableTrigger(botAI); } + static Trigger* AlmostFullEnergyAvailable(PlayerbotAI* botAI) { return new EnergyAvailable(botAI, 90); } static Trigger* LootAvailable(PlayerbotAI* botAI) { return new LootAvailableTrigger(botAI); } static Trigger* NoAttackers(PlayerbotAI* botAI) { return new NoAttackersTrigger(botAI); } static Trigger* TankAssist(PlayerbotAI* botAI) { return new TankAssistTrigger(botAI); } @@ -309,7 +315,15 @@ private: } static Trigger* ComboPointsAvailable(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI); } static Trigger* ComboPoints3Available(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI, 3); } + static Trigger* target_with_combo_points_almost_dead(PlayerbotAI* ai) + { + return new TargetWithComboPointsLowerHealTrigger(ai, 3, 3.0f); + } + static Trigger* ComboPointsNotFull(PlayerbotAI* botAI) { return new ComboPointsNotFullTrigger(botAI); } + static Trigger* ComboPointsNotFullAndHighEnergy(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "combo points not full", "high energy available"); } static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); } + static Trigger* low_tank_threat(PlayerbotAI* botAI) { return new LowTankThreatTrigger(botAI); } + // static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); } static Trigger* Dead(PlayerbotAI* botAI) { return new DeadTrigger(botAI); } static Trigger* corpse_near(PlayerbotAI* botAI) { return new CorpseNearTrigger(botAI); } static Trigger* PartyMemberDead(PlayerbotAI* botAI) { return new PartyMemberDeadTrigger(botAI); } diff --git a/src/strategy/values/AoeValues.cpp b/src/strategy/values/AoeValues.cpp index 069acc73..67508f66 100644 --- a/src/strategy/values/AoeValues.cpp +++ b/src/strategy/values/AoeValues.cpp @@ -145,6 +145,9 @@ Aura* AreaDebuffValue::Calculate() { continue; } + // float radius = dynOwner->GetRadius(); + // if (radius > 12.0f) + // continue; return aura; } } diff --git a/src/strategy/values/Arrow.cpp b/src/strategy/values/Arrow.cpp index f52dc670..0a8afb50 100644 --- a/src/strategy/values/Arrow.cpp +++ b/src/strategy/values/Arrow.cpp @@ -46,14 +46,14 @@ WorldLocation ArrowFormation::GetLocationInternal() float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); - float z = master->GetPositionZ(); + float z = master->GetPositionZ() + master->GetHoverHeight(); if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) { x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); z = master->GetPositionZ() + master->GetHoverHeight(); - z = master->GetMapHeight(x, y, z); + master->UpdateAllowedPositionZ(x, y, z); } return WorldLocation(master->GetMapId(), x, y, z); } diff --git a/src/strategy/values/AttackerWithoutAuraTargetValue.cpp b/src/strategy/values/AttackerWithoutAuraTargetValue.cpp index a3945e64..99489af4 100644 --- a/src/strategy/values/AttackerWithoutAuraTargetValue.cpp +++ b/src/strategy/values/AttackerWithoutAuraTargetValue.cpp @@ -36,3 +36,36 @@ Unit* AttackerWithoutAuraTargetValue::Calculate() return result; } + +Unit* MeleeAttackerWithoutAuraTargetValue::Calculate() +{ + GuidVector attackers = botAI->GetAiObjectContext()->GetValue("attackers")->Get(); + // Unit* target = botAI->GetAiObjectContext()->GetValue("current target")->Get(); + uint32 max_health = 0; + Unit* result = nullptr; + for (ObjectGuid const guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!bot->IsWithinMeleeRange(unit)) + continue; + + if (checkArc && !bot->HasInArc(CAST_ANGLE_IN_FRONT, unit)) + continue; + + if (unit->GetHealth() < max_health) + { + continue; + } + + if (!botAI->HasAura(qualifier, unit, false, true)) + { + max_health = unit->GetHealth(); + result = unit; + } + } + + return result; +} diff --git a/src/strategy/values/AttackerWithoutAuraTargetValue.h b/src/strategy/values/AttackerWithoutAuraTargetValue.h index 73867f9d..7d7d0666 100644 --- a/src/strategy/values/AttackerWithoutAuraTargetValue.h +++ b/src/strategy/values/AttackerWithoutAuraTargetValue.h @@ -28,7 +28,9 @@ protected: class MeleeAttackerWithoutAuraTargetValue : public AttackerWithoutAuraTargetValue { public: - MeleeAttackerWithoutAuraTargetValue(PlayerbotAI* botAI) : AttackerWithoutAuraTargetValue(botAI, "melee") {} + MeleeAttackerWithoutAuraTargetValue(PlayerbotAI* botAI, bool checkArc = true) : AttackerWithoutAuraTargetValue(botAI, "melee"), checkArc(checkArc) {} + Unit* Calculate() override; + bool checkArc; }; #endif diff --git a/src/strategy/values/DpsTargetValue.cpp b/src/strategy/values/DpsTargetValue.cpp index e2e12cb3..88c52ccf 100644 --- a/src/strategy/values/DpsTargetValue.cpp +++ b/src/strategy/values/DpsTargetValue.cpp @@ -50,6 +50,7 @@ public: CasterFindTargetSmartStrategy(PlayerbotAI* botAI, float dps) : FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000) { + result = nullptr; } void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override @@ -86,13 +87,15 @@ public: { float new_time = new_unit->GetHealth() / dps_; float old_time = old_unit->GetHealth() / dps_; - // [5-20] > (5-0] > (20-inf) - if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit)) + // [5-30] > (5-0] > (20-inf) + int new_level = GetIntervalLevel(new_unit); + int old_level = GetIntervalLevel(old_unit); + if (new_level != old_level) { - return true; + return new_level > old_level; } - int32_t level = GetIntervalLevel(new_unit); - if (level % 10 == 2 || level % 10 == 1) + int32_t level = new_level; + if (level % 10 == 2 || level % 10 == 0) { return new_time < old_time; } @@ -116,15 +119,15 @@ public: botAI->IsRanged(botAI->GetBot()) ? sPlayerbotAIConfig->spellDistance : sPlayerbotAIConfig->meleeDistance; attackRange += 5.0f; int level = dis < attackRange ? 10 : 0; - if (time >= 3 && time <= 20) + if (time >= 5 && time <= 30) { return level + 2; } - if (time > 20) + if (time > 30) { - return level + 1; + return level; } - return level; + return level + 1; } protected: @@ -176,12 +179,14 @@ public: float new_time = new_unit->GetHealth() / dps_; float old_time = old_unit->GetHealth() / dps_; // [5-20] > (5-0] > (20-inf) - if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit)) + int new_level = GetIntervalLevel(new_unit); + int old_level = GetIntervalLevel(old_unit); + if (new_level != old_level) { - return true; + return new_level > old_level; } // attack enemy in range and with lowest health - int level = GetIntervalLevel(new_unit); + int level = new_level; if (level == 10) { return new_time < old_time; @@ -249,12 +254,14 @@ public: float new_time = new_unit->GetHealth() / dps_; float old_time = old_unit->GetHealth() / dps_; // [5-20] > (5-0] > (20-inf) - if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit)) + int new_level = GetIntervalLevel(new_unit); + int old_level = GetIntervalLevel(old_unit); + if (new_level != old_level) { - return true; + return new_level > old_level; } // attack enemy in range and with lowest health - int level = GetIntervalLevel(new_unit); + int level = new_level; Player* bot = botAI->GetBot(); if (level == 10) { @@ -291,7 +298,7 @@ Unit* DpsTargetValue::Calculate() return rti; // FindLeastHpTargetStrategy strategy(botAI); - float dps = AI_VALUE(float, "expected group dps"); + float dps = AI_VALUE(float, "estimated group dps"); if (botAI->IsCaster(bot)) { CasterFindTargetSmartStrategy strategy(botAI, dps); diff --git a/src/strategy/values/EstimatedLifetimeValue.cpp b/src/strategy/values/EstimatedLifetimeValue.cpp new file mode 100644 index 00000000..b69861c4 --- /dev/null +++ b/src/strategy/values/EstimatedLifetimeValue.cpp @@ -0,0 +1,145 @@ +#include "EstimatedLifetimeValue.h" + +#include "AiFactory.h" +#include "PlayerbotAI.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotFactory.h" +#include "Playerbots.h" +#include "SharedDefines.h" + +float EstimatedLifetimeValue::Calculate() +{ + Unit* target = AI_VALUE(Unit*, qualifier); + if (!target || !target->IsAlive()) + { + return 0.0f; + } + float dps = AI_VALUE(float, "estimated group dps"); + bool aoePenalty = AI_VALUE(uint8, "attacker count") >= 3; + if (aoePenalty) + dps *= 0.75; + float res = target->GetHealth() / dps; + // bot->Say(target->GetName() + " lifetime: " + std::to_string(res), LANG_UNIVERSAL); + return res; +} + +float EstimatedGroupDpsValue::Calculate() +{ + float totalDps; + + std::vector groupPlayer = {bot}; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (member == bot) // calculated + continue; + + if (!member || !member->IsInWorld()) + continue; + + if (member->GetMapId() != bot->GetMapId()) + continue; + + if (member->GetExactDist(bot) > sPlayerbotAIConfig->sightDistance) + continue; + + groupPlayer.push_back(member); + } + } + for (Player* player : groupPlayer) + { + float roleMultiplier; + if (botAI->IsTank(player)) + roleMultiplier = 0.3f; + else if (botAI->IsHeal(player)) + roleMultiplier = 0.1f; + else + roleMultiplier = 1.0f; + float basicDps = GetBasicDps(player->GetLevel()); + float basicGs = GetBasicGs(player->GetLevel()); + uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(player, true, false, 12); + float gs_modifier = (float)mixedGearScore / basicGs; + // bonus for wotlk epic gear + if (mixedGearScore >= 300) + { + gs_modifier *= 1 + (mixedGearScore - 300) * 0.01; + } + if (gs_modifier < 0.75) + gs_modifier = 0.75; + if (gs_modifier > 4) + gs_modifier = 4; + totalDps += basicDps * roleMultiplier * gs_modifier; + } + // Group buff bonus + if (groupPlayer.size() >= 25) + totalDps *= 1.2; + else if (groupPlayer.size() >= 10) + totalDps *= 1.1; + else if (groupPlayer.size() >= 5) + totalDps *= 1.05; + return totalDps; +} + +float EstimatedGroupDpsValue::GetBasicDps(uint32 level) +{ + float basic_dps; + + if (level <= 15) + { + basic_dps = 5 + level * 1; + } + else if (level <= 25) + { + basic_dps = 20 + (level - 15) * 2; + } + else if (level <= 45) + { + basic_dps = 40 + (level - 25) * 3; + } + else if (level <= 55) + { + basic_dps = 100 + (level - 45) * 20; + } + else if (level <= 60) + { + basic_dps = 300 + (level - 55) * 50; + } + else if (level <= 70) + { + basic_dps = 550 + (level - 60) * 65; + } + else + { + basic_dps = 1200 + (level - 70) * 200; + } + return basic_dps; +} + +float EstimatedGroupDpsValue::GetBasicGs(uint32 level) +{ + float basic_gs; + + if (level <= 8) + { + basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_NORMAL); + } + else if (level <= 15) + { + basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_UNCOMMON); + } + else if (level <= 60) + { + basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_RARE); + } + else if (level <= 70) + { + basic_gs = PlayerbotFactory::CalcMixedGearScore(85 + (level - 60) * 3, ITEM_QUALITY_RARE); + } + else + { + basic_gs = PlayerbotFactory::CalcMixedGearScore(155 + (level - 70) * 4, ITEM_QUALITY_RARE); + } + return basic_gs; +} \ No newline at end of file diff --git a/src/strategy/values/ExpectedLifetimeValue.h b/src/strategy/values/EstimatedLifetimeValue.h similarity index 51% rename from src/strategy/values/ExpectedLifetimeValue.h rename to src/strategy/values/EstimatedLifetimeValue.h index 2afcece8..f050ac71 100644 --- a/src/strategy/values/ExpectedLifetimeValue.h +++ b/src/strategy/values/EstimatedLifetimeValue.h @@ -3,8 +3,8 @@ * and/or modify it under version 2 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_EXPECTEDLIFETIMEVALUE_H -#define _PLAYERBOT_EXPECTEDLIFETIMEVALUE_H +#ifndef _PLAYERBOT_EstimatedLifetimeValue_H +#define _PLAYERBOT_EstimatedLifetimeValue_H #include "NamedObjectContext.h" #include "PossibleTargetsValue.h" @@ -15,22 +15,26 @@ class PlayerbotAI; class Unit; // [target health] / [expected group single target dps] = [expected lifetime] -class ExpectedLifetimeValue : public FloatCalculatedValue, public Qualified +class EstimatedLifetimeValue : public FloatCalculatedValue, public Qualified { public: - ExpectedLifetimeValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "expected lifetime") {} + EstimatedLifetimeValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "estimated lifetime") {} public: float Calculate() override; }; -class ExpectedGroupDpsValue : public FloatCalculatedValue +class EstimatedGroupDpsValue : public FloatCalculatedValue { public: - ExpectedGroupDpsValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "expected group dps", 20 * 1000) {} + EstimatedGroupDpsValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "estimated group dps", 20 * 1000) {} public: float Calculate() override; + +protected: + float GetBasicDps(uint32 level); + float GetBasicGs(uint32 level); }; #endif diff --git a/src/strategy/values/ExpectedLifetimeValue.cpp b/src/strategy/values/ExpectedLifetimeValue.cpp deleted file mode 100644 index a8a554b6..00000000 --- a/src/strategy/values/ExpectedLifetimeValue.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "ExpectedLifetimeValue.h" - -#include "AiFactory.h" -#include "PlayerbotAI.h" -#include "Playerbots.h" -#include "SharedDefines.h" - -float ExpectedLifetimeValue::Calculate() -{ - Unit* target = AI_VALUE(Unit*, qualifier); - if (!target || !target->IsAlive()) - { - return 0.0f; - } - float dps = AI_VALUE(float, "expected group dps"); - float res = target->GetHealth() / dps; - // bot->Say(target->GetName() + " lifetime: " + std::to_string(res), LANG_UNIVERSAL); - return res; -} - -float ExpectedGroupDpsValue::Calculate() -{ - float dps_num; - Group* group = bot->GetGroup(); - if (!group) - { - dps_num = 0.7; - } - else - { - dps_num = group->GetMembersCount() * 0.7; - } - uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(bot, true, false, 12); - // efficiency record based on rare gear level, is there better calculation method? - // float dps_efficiency = 1; - float basic_dps; - int32 basic_gs; - int32 level = bot->GetLevel(); - - if (level <= 15) - { - basic_dps = 5 + level * 1; - } - else if (level <= 25) - { - basic_dps = 20 + (level - 15) * 2; - } - else if (level <= 40) - { - basic_dps = 40 + (level - 30) * 4; - } - else if (level <= 55) - { - basic_dps = 100 + (level - 45) * 20; - } - else if (level <= 60) - { - basic_dps = 300 + (level - 55) * 50; - } - else if (level <= 70) - { - basic_dps = 450 + (level - 60) * 40; - } - else - { - basic_dps = 750 + (level - 70) * 175; - } - - if (level <= 8) - { - basic_gs = (level + 5) * 2; - } - else if (level <= 15) - { - basic_gs = (level + 5) * 3; - } - else if (level <= 60) - { - basic_gs = (level + 5) * 4; - } - else if (level <= 70) - { - basic_gs = (85 + (level - 60) * 3) * 4; - } - else - { - basic_gs = (155 + (level - 70) * 4) * 4; - } - float gap = mixedGearScore - basic_gs; - float gs_modifier = (float)mixedGearScore / basic_gs - 1; - gs_modifier = gs_modifier * 3 + 1; - - if (gs_modifier < 0.75) - gs_modifier = 0.75; - if (gs_modifier > 4) - gs_modifier = 4; - - return dps_num * basic_dps * gs_modifier; -} \ No newline at end of file diff --git a/src/strategy/values/Formations.cpp b/src/strategy/values/Formations.cpp index 9854b420..8c918c56 100644 --- a/src/strategy/values/Formations.cpp +++ b/src/strategy/values/Formations.cpp @@ -88,7 +88,7 @@ public: float angle = GetFollowAngle(); float x = master->GetPositionX() + cos(angle) * range; float y = master->GetPositionY() + sin(angle) * range; - float z = master->GetPositionZ(); + float z = master->GetPositionZ() + master->GetHoverHeight(); if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) { @@ -138,15 +138,14 @@ public: float x = master->GetPositionX() + cos(angle) * range + dx; float y = master->GetPositionY() + sin(angle) * range + dy; - float z = master->GetPositionZ(); - z = bot->GetMapHeight(x, y, z + 5.0f); + float z = master->GetPositionZ() + master->GetHoverHeight(); if (!master->GetMap()->CheckCollisionAndGetValidCoords( master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) { x = master->GetPositionX() + cos(angle) * range + dx; y = master->GetPositionY() + sin(angle) * range + dy; z = master->GetPositionZ() + master->GetHoverHeight(); - z = master->GetMapHeight(x, y, z); + master->UpdateAllowedPositionZ(x, y, z); } // bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), // bot->GetPositionZ(), x, y, z); @@ -155,15 +154,14 @@ public: float x = master->GetPositionX() + cos(angle) * range + dx; float y = master->GetPositionY() + sin(angle) * range + dy; - float z = master->GetPositionZ(); - z = bot->GetMapHeight(x, y, z + 5.0f); + float z = master->GetPositionZ() + master->GetHoverHeight(); if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) { x = master->GetPositionX() + cos(angle) * range + dx; y = master->GetPositionY() + sin(angle) * range + dy; z = master->GetPositionZ() + master->GetHoverHeight(); - z = master->GetMapHeight(x, y, z); + master->UpdateAllowedPositionZ(x, y, z); } return WorldLocation(master->GetMapId(), x, y, z); } @@ -221,8 +219,8 @@ public: { x = target->GetPositionX() + cos(angle) * range; y = target->GetPositionY() + sin(angle) * range; - z = target->GetPositionZ() + target->GetHoverHeight(); - z = target->GetMapHeight(x, y, z); + z = target->GetPositionZ(); + target->UpdateAllowedPositionZ(x, y, z); } return WorldLocation(bot->GetMapId(), x, y, z); } @@ -389,7 +387,7 @@ public: x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; z = master->GetPositionZ() + master->GetHoverHeight(); - z = master->GetMapHeight(x, y, z); + master->UpdateAllowedPositionZ(x, y, z); } return WorldLocation(bot->GetMapId(), minX, minY, z); } @@ -403,7 +401,7 @@ public: x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; z = master->GetPositionZ() + master->GetHoverHeight(); - z = master->GetMapHeight(x, y, z); + master->UpdateAllowedPositionZ(x, y, z); } return WorldLocation(bot->GetMapId(), x, y, z); } diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index 9bdf206d..ca688a2d 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -166,7 +166,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) return ITEM_USAGE_NONE; uint16 dest; - InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, false); + InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, true); pItem->RemoveFromUpdateQueueOf(bot); delete pItem; diff --git a/src/strategy/values/NearestGameObjects.cpp b/src/strategy/values/NearestGameObjects.cpp index 29b3e45a..a600bc65 100644 --- a/src/strategy/values/NearestGameObjects.cpp +++ b/src/strategy/values/NearestGameObjects.cpp @@ -77,7 +77,7 @@ GuidVector NearestTrapWithDamageValue::Calculate() continue; } const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); - if (spellInfo->IsPositive()) + if (!spellInfo || spellInfo->IsPositive()) { continue; } diff --git a/src/strategy/values/ValueContext.h b/src/strategy/values/ValueContext.h index 6fb93fdf..f8f4a8fe 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -26,7 +26,7 @@ #include "DuelTargetValue.h" #include "EnemyHealerTargetValue.h" #include "EnemyPlayerValue.h" -#include "ExpectedLifetimeValue.h" +#include "EstimatedLifetimeValue.h" #include "Formations.h" #include "GrindTargetValue.h" #include "GroupValues.h" @@ -299,8 +299,8 @@ public: creators["boss target"] = &ValueContext::boss_target; creators["nearest triggers"] = &ValueContext::nearest_triggers; creators["neglect threat"] = &ValueContext::neglect_threat; - creators["expected lifetime"] = &ValueContext::expected_lifetime; - creators["expected group dps"] = &ValueContext::expected_group_dps; + creators["estimated lifetime"] = &ValueContext::expected_lifetime; + creators["estimated group dps"] = &ValueContext::expected_group_dps; creators["area debuff"] = &ValueContext::area_debuff; creators["nearest trap with damage"] = &ValueContext::nearest_trap_with_damange; creators["disperse distance"] = &ValueContext::disperse_distance; @@ -538,8 +538,8 @@ private: static UntypedValue* boss_target(PlayerbotAI* ai) { return new BossTargetValue(ai); } static UntypedValue* nearest_triggers(PlayerbotAI* ai) { return new NearestTriggersValue(ai); } static UntypedValue* neglect_threat(PlayerbotAI* ai) { return new NeglectThreatResetValue(ai); } - static UntypedValue* expected_lifetime(PlayerbotAI* ai) { return new ExpectedLifetimeValue(ai); } - static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(ai); } + static UntypedValue* expected_lifetime(PlayerbotAI* ai) { return new EstimatedLifetimeValue(ai); } + static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new EstimatedGroupDpsValue(ai); } static UntypedValue* area_debuff(PlayerbotAI* ai) { return new AreaDebuffValue(ai); } static UntypedValue* nearest_trap_with_damange(PlayerbotAI* ai) { return new NearestTrapWithDamageValue(ai); } static UntypedValue* disperse_distance(PlayerbotAI* ai) { return new DisperseDistanceValue(ai); } diff --git a/src/strategy/warrior/ArmsWarriorStrategy.cpp b/src/strategy/warrior/ArmsWarriorStrategy.cpp index bf56e891..c77e25c2 100644 --- a/src/strategy/warrior/ArmsWarriorStrategy.cpp +++ b/src/strategy/warrior/ArmsWarriorStrategy.cpp @@ -34,8 +34,9 @@ ArmsWarriorStrategy::ArmsWarriorStrategy(PlayerbotAI* botAI) : GenericWarriorStr NextAction** ArmsWarriorStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("bladestorm", ACTION_DEFAULT + 0.1f), - new NextAction("melee", ACTION_DEFAULT), nullptr); + return NextAction::array(0, new NextAction("bladestorm", ACTION_DEFAULT + 0.2f), + new NextAction("mortal strike", ACTION_DEFAULT + 0.1f), + new NextAction("melee", ACTION_DEFAULT), nullptr); } void ArmsWarriorStrategy::InitTriggers(std::vector& triggers) @@ -63,9 +64,11 @@ void ArmsWarriorStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "victory rush", NextAction::array(0, new NextAction("victory rush", ACTION_INTERRUPT), nullptr))); triggers.push_back(new TriggerNode( - "medium rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 10), nullptr))); - /*triggers.push_back(new TriggerNode("high rage available", NextAction::array(0, new NextAction("slam", ACTION_HIGH - * + 1), nullptr)));*/ + "high rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 10), nullptr))); + triggers.push_back(new TriggerNode("medium rage available", + NextAction::array(0, new NextAction("slam", ACTION_HIGH + 1), + new NextAction("thunder clap", ACTION_HIGH), + nullptr))); triggers.push_back( new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 2), nullptr))); triggers.push_back( @@ -75,6 +78,6 @@ void ArmsWarriorStrategy::InitTriggers(std::vector& triggers) "rend on attacker", NextAction::array(0, new NextAction("rend on attacker", ACTION_HIGH + 5), nullptr))); triggers.push_back(new TriggerNode( "critical health", NextAction::array(0, new NextAction("intimidating shout", ACTION_EMERGENCY), nullptr))); - triggers.push_back(new TriggerNode("medium rage available", - NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 1), nullptr))); + triggers.push_back(new TriggerNode("medium aoe", + NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 2), nullptr))); }