/* * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it * and/or modify it under version 3 of the License, or (at your option), any later version. */ #include "GenericBuffUtils.h" #include "AiObjectContext.h" #include "GameTime.h" #include "Group.h" #include "Player.h" #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" #include "SpellMgr.h" #include "Unit.h" #include "Value.h" namespace ai::buff { namespace { // Prevents bots from immediately casting already-present buffs upon logging in constexpr uint32 POST_LOGIN_BUFF_GRACE_MS = 5 * IN_MILLISECONDS; bool IsWithinPostLoginBuffGrace(Player* player) { if (!player) return false; return getMSTimeDiff( player->GetInGameTime(), GameTime::GetGameTimeMS().count()) < POST_LOGIN_BUFF_GRACE_MS; } } static bool HasEnoughSameMapMissingPlayersForGroupVariant( Player* bot, PlayerbotAI* botAI, std::string const& baseName, std::string const& groupName, uint32 requiredCount = 3) { Group* group = bot->GetGroup(); if (!group) return false; uint32 missingCount = 0; for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); if (!member || !member->IsInWorld() || !member->IsAlive() || member->GetMap() != bot->GetMap()) { continue; } if (botAI->HasAura(baseName, member) || botAI->HasAura(groupName, member)) continue; if (++missingCount >= requiredCount) return true; } return false; } static bool IsEligibleGroupForPartyBuffs(Group const* group) { if (!group) return false; switch (sPlayerbotAIConfig.autoPartyBuffs) { case AutoPartyBuffMode::RAID_ONLY: return group->isRaidGroup(); case AutoPartyBuffMode::GROUP_OR_RAID: return true; case AutoPartyBuffMode::DISABLED: return false; } return false; } bool IsGroupVariantEnabled(Player* bot, std::string const& name) { if (!IsEligibleGroupForPartyBuffs(bot->GetGroup())) return false; return !GroupVariantFor(name).empty(); } std::string MakeAuraQualifierForBuff(std::string const& name) { // Paladin if (name == "blessing of kings") return "blessing of kings,greater blessing of kings"; if (name == "blessing of might") return "blessing of might,greater blessing of might"; if (name == "blessing of wisdom") return "blessing of wisdom,greater blessing of wisdom"; if (name == "blessing of sanctuary") return "blessing of sanctuary,greater blessing of sanctuary"; // Druid if (name == "mark of the wild") return "mark of the wild,gift of the wild"; // Mage if (name == "arcane intellect") return "arcane intellect,arcane brilliance"; // Priest if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude"; if (name == "divine spirit") return "divine spirit,prayer of spirit"; if (name == "shadow protection") return "shadow protection,prayer of shadow protection"; return name; } std::string GroupVariantFor(std::string const& name) { // Druid if (name == "mark of the wild") return "gift of the wild"; // Mage if (name == "arcane intellect") return "arcane brilliance"; // Priest if (name == "power word: fortitude") return "prayer of fortitude"; if (name == "divine spirit") return "prayer of spirit"; if (name == "shadow protection") return "prayer of shadow protection"; // Paladin blessings are intentionally not included here because they are // coordinated by the auto greater blessing system instead. return std::string(); } bool NeedsPostLoginBuffGrace(std::string const& name) { static char const* const trackedBuffs[] = { "mark of the wild", "arcane intellect", "power word: fortitude", "prayer of fortitude", "divine spirit", "prayer of spirit", "shadow protection", "prayer of shadow protection", "blessing of kings", "blessing of might", "blessing of wisdom", "blessing of sanctuary" }; for (char const* trackedBuff : trackedBuffs) { if (name.find(trackedBuff) != std::string::npos) return true; } return false; } bool ShouldDeferPartyBuffEvaluationForRecentLogin( Player* bot, Unit* target, std::string const& spell) { if (!NeedsPostLoginBuffGrace(spell)) return false; if (IsWithinPostLoginBuffGrace(bot)) return true; Player* playerTarget = target ? target->ToPlayer() : nullptr; return IsWithinPostLoginBuffGrace(playerTarget); } bool ShouldDeferGreaterBlessingAssignmentForRecentLogin(Player* bot) { if (IsWithinPostLoginBuffGrace(bot)) return true; Group* group = bot->GetGroup(); if (!group) return false; for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); if (!member || !member->IsInWorld()) continue; if (IsWithinPostLoginBuffGrace(member)) return true; } return false; } bool HasRequiredReagents(Player* bot, uint32 spellId) { if (!spellId) return false; if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) { for (int i = 0; i < MAX_SPELL_REAGENTS; ++i) { if (info->Reagent[i] > 0) { uint32 const itemId = info->Reagent[i]; int32 const need = info->ReagentCount[i]; if ((int32)bot->GetItemCount(itemId, false) < need) return false; } } return true; } return false; } std::string UpgradeToGroupIfAppropriate( Player* bot, PlayerbotAI* botAI, std::string const& baseName) { if (!IsGroupVariantEnabled(bot, baseName)) return baseName; std::string const groupName = GroupVariantFor(baseName); if (groupName.empty()) return baseName; // Prefer singles until at least three living, in-world group members on the bot's map // are missing both the single-target buff and its group variant. if (!HasEnoughSameMapMissingPlayersForGroupVariant(bot, botAI, baseName, groupName)) return baseName; uint32 const groupSpellId = botAI->GetAiObjectContext() ->GetValue("spell id", groupName)->Get(); if (groupSpellId && HasRequiredReagents(bot, groupSpellId)) return groupName; return baseName; } } namespace ai::spell { bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId) { if (bot->HasSpellCooldown(spellId)) return true; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return false; uint32 category = spellInfo->GetCategory(); if (!category) return false; for (auto const& [cooldownSpellId, cooldown] : bot->GetSpellCooldownMap()) { if (cooldown.category == category && bot->GetSpellCooldownDelay(cooldownSpellId) > 0) return true; } return false; } }