diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 25afccfb..acf13ba6 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -6,9 +6,9 @@ #include "PlayerbotAI.h" #include +#include #include #include -#include #include "AiFactory.h" #include "BudgetValues.h" @@ -18,6 +18,7 @@ #include "EmoteAction.h" #include "Engine.h" #include "ExternalEventHelper.h" +#include "GameTime.h" #include "GuildMgr.h" #include "GuildTaskMgr.h" #include "LFGMgr.h" @@ -51,7 +52,6 @@ #include "Unit.h" #include "UpdateTime.h" #include "Vehicle.h" -#include "GameTime.h" std::vector PlayerbotAI::dispel_whitelist = { "mutating injection", @@ -232,11 +232,13 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) nextAICheckDelay = 0; // Early return if bot is in invalid state - if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) + if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() || + bot->IsDuringRemoveFromWorld()) return; // Handle cheat options (set bot health and power if cheats are enabled) - if (bot->IsAlive() && (static_cast(GetCheat()) > 0 || static_cast(sPlayerbotAIConfig->botCheatMask) > 0)) + if (bot->IsAlive() && + (static_cast(GetCheat()) > 0 || static_cast(sPlayerbotAIConfig->botCheatMask) > 0)) { if (HasCheat(BotCheatMask::health)) bot->SetFullHealth(); @@ -287,8 +289,10 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) isHeal = true; // Check if spell is single-target - if ((spellInfo->Effects[i].TargetA.GetTarget() && spellInfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY) || - (spellInfo->Effects[i].TargetB.GetTarget() && spellInfo->Effects[i].TargetB.GetTarget() != TARGET_UNIT_TARGET_ALLY)) + if ((spellInfo->Effects[i].TargetA.GetTarget() && + spellInfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY) || + (spellInfo->Effects[i].TargetB.GetTarget() && + spellInfo->Effects[i].TargetB.GetTarget() != TARGET_UNIT_TARGET_ALLY)) { isSingleTarget = false; } @@ -303,7 +307,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) } // Ensure bot is facing target if necessary - if (spellTarget && !bot->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget) && (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT)) + if (spellTarget && !bot->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget) && + (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT)) { sServerFacade->SetFacingTo(bot, spellTarget); } @@ -761,7 +766,7 @@ void PlayerbotAI::Reset(bool full) bot->GetMotionMaster()->Clear(); InterruptSpell(); - + if (full) { for (uint8 i = 0; i < BOT_STATE_MAX; i++) @@ -1060,7 +1065,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) if (message.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) && (GetChatHelper()->ExtractAllItemIds(message).size() > 0 || - GetChatHelper()->ExtractAllQuestIds(message).size() > 0) && + GetChatHelper()->ExtractAllQuestIds(message).size() > 0) && sPlayerbotAIConfig->toxicLinksRepliesChance) { if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig->toxicLinksRepliesChance) @@ -1069,7 +1074,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) } } else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) && - sPlayerbotAIConfig->thunderfuryRepliesChance)) + sPlayerbotAIConfig->thunderfuryRepliesChance)) { if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig->thunderfuryRepliesChance) { @@ -1344,7 +1349,8 @@ void PlayerbotAI::DoNextAction(bool min) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (!member || member == bot || member == newMaster || !member->IsInWorld() || !member->IsInSameRaidWith(bot)) + if (!member || member == bot || member == newMaster || !member->IsInWorld() || + !member->IsInSameRaidWith(bot)) continue; PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); @@ -1480,64 +1486,64 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) strategyName = "naxx"; break; case 574: - strategyName = "wotlk-uk"; // Utgarde Keep + strategyName = "wotlk-uk"; // Utgarde Keep break; case 575: - strategyName = "wotlk-up"; // Utgarde Pinnacle + strategyName = "wotlk-up"; // Utgarde Pinnacle break; case 576: - strategyName = "wotlk-nex"; // The Nexus + strategyName = "wotlk-nex"; // The Nexus break; case 578: - strategyName = "wotlk-occ"; // The Oculus + strategyName = "wotlk-occ"; // The Oculus break; case 595: - strategyName = "wotlk-cos"; // The Culling of Stratholme + strategyName = "wotlk-cos"; // The Culling of Stratholme break; case 599: - strategyName = "wotlk-hos"; // Halls of Stone + strategyName = "wotlk-hos"; // Halls of Stone break; case 600: - strategyName = "wotlk-dtk"; // Drak'Tharon Keep + strategyName = "wotlk-dtk"; // Drak'Tharon Keep break; case 601: - strategyName = "wotlk-an"; // Azjol-Nerub + strategyName = "wotlk-an"; // Azjol-Nerub break; case 602: - strategyName = "wotlk-hol"; // Halls of Lightning + strategyName = "wotlk-hol"; // Halls of Lightning break; case 603: strategyName = "uld"; break; case 604: - strategyName = "wotlk-gd"; // Gundrak + strategyName = "wotlk-gd"; // Gundrak break; case 608: - strategyName = "wotlk-vh"; // Violet Hold + strategyName = "wotlk-vh"; // Violet Hold break; case 615: - strategyName = "wotlk-os"; // Obsidian Sanctum + strategyName = "wotlk-os"; // Obsidian Sanctum break; case 616: - strategyName = "wotlk-eoe"; // Eye Of Eternity + strategyName = "wotlk-eoe"; // Eye Of Eternity break; case 619: - strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom + strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom break; case 631: strategyName = "icc"; break; case 632: - strategyName = "wotlk-fos"; // The Forge of Souls + strategyName = "wotlk-fos"; // The Forge of Souls break; case 650: - strategyName = "wotlk-toc"; // Trial of the Champion + strategyName = "wotlk-toc"; // Trial of the Champion break; case 658: - strategyName = "wotlk-pos"; // Pit of Saron + strategyName = "wotlk-pos"; // Pit of Saron break; case 668: - strategyName = "wotlk-hor"; // Halls of Reflection + strategyName = "wotlk-hor"; // Halls of Reflection break; default: break; @@ -1699,7 +1705,10 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec) bool PlayerbotAI::IsMelee(Player* player, bool bySpec) { return !IsRanged(player, bySpec); } -bool PlayerbotAI::IsCaster(Player* player, bool bySpec) { return IsRanged(player, bySpec) && 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 bySpec) { @@ -1720,15 +1729,15 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) Group::MemberSlotList const& slots = group->GetMemberSlots(); int counter = 0; - + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - - if (IsHeal(member)) // Check if the member is a healer + + if (IsHeal(member)) // Check if the member is a healer { bool isAssistant = group->IsAssistant(member->GetGUID()); - + // Check if the index matches for both assistant and non-assistant healers if ((isAssistant && index == counter) || (!isAssistant && index == counter)) { @@ -1752,15 +1761,15 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index) Group::MemberSlotList const& slots = group->GetMemberSlots(); int counter = 0; - + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - - if (IsRangedDps(member)) // Check if the member is a ranged DPS + + if (IsRangedDps(member)) // Check if the member is a ranged DPS { bool isAssistant = group->IsAssistant(member->GetGUID()); - + // Check the index for both assistant and non-assistant ranges if ((isAssistant && index == counter) || (!isAssistant && index == counter)) { @@ -1838,7 +1847,7 @@ int32 PlayerbotAI::GetRangedIndex(Player* player) return 0; } -int32 PlayerbotAI::GetClassIndex(Player* player, uint8_t cls) +int32 PlayerbotAI::GetClassIndex(Player* player, uint8 cls) { if (player->getClass() != cls) { @@ -3276,10 +3285,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) // } // WaitForSpellCast(spell); - - aiObjectContext->GetValue("last spell cast") - ->Get() - .Set(spellId, target->GetGUID(), time(nullptr)); + + aiObjectContext->GetValue("last spell cast")->Get().Set(spellId, target->GetGUID(), time(nullptr)); aiObjectContext->GetValue("position")->Get()["random"].Reset(); @@ -4017,17 +4024,17 @@ inline bool ZoneHasRealPlayers(Player* bot) { return false; } - + for (Player* player : sRandomPlayerbotMgr->GetPlayers()) { if (player->GetMapId() != bot->GetMapId()) continue; - + if (player->IsGameMaster() && !player->IsVisible()) { continue; } - + if (player->GetZoneId() == bot->GetZoneId()) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); @@ -4057,7 +4064,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) return true; } } - + // only keep updating till initializing time has completed, // which prevents unneeded expensive GameTime calls. if (_isBotInitializing) @@ -4082,7 +4089,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) { return true; } - + // bot map has active players. if (sPlayerbotAIConfig->BotActiveAloneForceWhenInMap) { @@ -4220,7 +4227,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) { return false; } - + // ####################################################################################### // All mandatory conditations are checked to be active or not, from here the remaining // situations are usable for scaling when enabled. @@ -4267,12 +4274,18 @@ uint32 PlayerbotAI::AutoScaleActivity(uint32 mod) double spreadSize = (double)(diffLimitCeiling - diffLimitFloor) / 6; // apply scaling - if (maxDiff > diffLimitCeiling) return 0; - if (maxDiff > diffLimitFloor + (4 * spreadSize)) return (mod * 1) / 10; - if (maxDiff > diffLimitFloor + (3 * spreadSize)) return (mod * 3) / 10; - if (maxDiff > diffLimitFloor + (2 * spreadSize)) return (mod * 5) / 10; - if (maxDiff > diffLimitFloor + (1 * spreadSize)) return (mod * 7) / 10; - if (maxDiff > diffLimitFloor) return (mod * 9) / 10; + if (maxDiff > diffLimitCeiling) + return 0; + if (maxDiff > diffLimitFloor + (4 * spreadSize)) + return (mod * 1) / 10; + if (maxDiff > diffLimitFloor + (3 * spreadSize)) + return (mod * 3) / 10; + if (maxDiff > diffLimitFloor + (2 * spreadSize)) + return (mod * 5) / 10; + if (maxDiff > diffLimitFloor + (1 * spreadSize)) + return (mod * 7) / 10; + if (maxDiff > diffLimitFloor) + return (mod * 9) / 10; return mod; } @@ -4497,7 +4510,8 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vectorInventoryType; - uint32 level = mixed ? proto->ItemLevel * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(proto->Quality)) : proto->ItemLevel; + uint32 level = mixed ? proto->ItemLevel * PlayerbotAI::GetItemScoreMultiplier(ItemQualities(proto->Quality)) + : proto->ItemLevel; switch (type) { @@ -4849,25 +4863,27 @@ Item* PlayerbotAI::FindItemInInventory(std::function // Find Poison Item* PlayerbotAI::FindPoison() const { - return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool { - return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6; - }); + return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool + { return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6; }); } // Find Consumable Item* PlayerbotAI::FindConsumable(uint32 displayId) const { - return FindItemInInventory([displayId](ItemTemplate const* pItemProto) -> bool { - return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && pItemProto->DisplayInfoID == displayId; - }); + return FindItemInInventory( + [displayId](ItemTemplate const* pItemProto) -> bool + { + return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && + pItemProto->DisplayInfoID == displayId; + }); } // Find Bandage Item* PlayerbotAI::FindBandage() const { - return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool { - return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE; - }); + return FindItemInInventory( + [](ItemTemplate const* pItemProto) -> bool + { return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE; }); } static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID, @@ -5292,13 +5308,13 @@ uint32 PlayerbotAI::GetBuffedCount(Player* player, std::string const spellname) int32 PlayerbotAI::GetNearGroupMemberCount(float dis) { - int count = 1; // yourself + 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 + if (member == bot) // calculated continue; if (!member || !member->IsInWorld()) @@ -5306,7 +5322,7 @@ int32 PlayerbotAI::GetNearGroupMemberCount(float dis) if (member->GetMapId() != bot->GetMapId()) continue; - + if (member->GetExactDist(bot) > dis) continue; @@ -5323,9 +5339,8 @@ bool PlayerbotAI::CanMove() return false; if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || - bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || - bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() || - bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) + bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() || + bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) return false; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; @@ -5988,3 +6003,80 @@ float PlayerbotAI::GetItemScoreMultiplier(ItemQualities quality) } return 1.0f; } + +bool PlayerbotAI::IsHealingSpell(uint32 spellFamilyName, flag96 spellFalimyFlags) +{ + if (!spellFamilyName) + return false; + flag96 healingFlags; + switch (spellFamilyName) + { + case SPELLFAMILY_DRUID: + { + uint32 healingFlagsA = 0x10 | 0x40 | 0x20 | 0x80; // rejuvenation | regrowth | healing touch | tranquility + uint32 healingFlagsB = 0x4000000 | 0x2000000 | 0x2 | 0x10; // wild growth | nourish | swiftmend | lifebloom + uint32 healingFlagsC = 0x0; + healingFlags = {healingFlagsA, healingFlagsB, healingFlagsC}; + break; + } + case SPELLFAMILY_PALADIN: + { + uint32 healingFlagsA = 0x80000000 | 0x40000000 | 0x8000 | + 0x80000; // holy light | flash of light | lay on hands | judgement of light + uint32 healingFlagsB = 0x10000; // holy shock + uint32 healingFlagsC = 0x0; + healingFlags = {healingFlagsA, healingFlagsB, healingFlagsC}; + break; + } + case SPELLFAMILY_SHAMAN: + { + uint32 healingFlagsA = 0x80 | 0x40 | 0x100; // lesser healing wave | healing wave | chain heal + uint32 healingFlagsB = 0x400; // earth shield + uint32 healingFlagsC = 0x10; // riptide + healingFlags = {healingFlagsA, healingFlagsB, healingFlagsC}; + break; + } + case SPELLFAMILY_PRIEST: + { + uint32 healingFlagsA = 0x40 | 0x200 | 0x40000 | 0x1000 | 0x800 | 0x400 | + 0x10000000; // renew | prayer of healing | lesser heal | greater heal | flash heal | + // heal | circle of healing + uint32 healingFlagsB = 0x800000 | 0x20 | 0x4; // penance | prayer of mending | binding heal + uint32 healingFlagsC = 0x0; + healingFlags = {healingFlagsA, healingFlagsB, healingFlagsC}; + break; + } + default: + break; + } + return spellFalimyFlags & healingFlags; +} + + +SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls) { + switch (cls) { + case CLASS_WARRIOR: + return SPELLFAMILY_WARRIOR; + case CLASS_PALADIN: + return SPELLFAMILY_PALADIN; + case CLASS_HUNTER: + return SPELLFAMILY_HUNTER; + case CLASS_ROGUE: + return SPELLFAMILY_ROGUE; + case CLASS_PRIEST: + return SPELLFAMILY_PRIEST; + case CLASS_DEATH_KNIGHT: + return SPELLFAMILY_DEATHKNIGHT; + case CLASS_SHAMAN: + return SPELLFAMILY_SHAMAN; + case CLASS_MAGE: + return SPELLFAMILY_MAGE; + case CLASS_WARLOCK: + return SPELLFAMILY_WARLOCK; + case CLASS_DRUID: + return SPELLFAMILY_DRUID; + default: + break; + } + return SPELLFAMILY_GENERIC; +} diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 2be8030e..d3e680cc 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -15,13 +15,14 @@ #include "Common.h" #include "Event.h" #include "Item.h" +#include "NewRpgStrategy.h" #include "PlayerbotAIBase.h" #include "PlayerbotAIConfig.h" #include "PlayerbotSecurity.h" #include "PlayerbotTextMgr.h" #include "SpellAuras.h" +#include "Util.h" #include "WorldPacket.h" -#include "NewRpgStrategy.h" class AiObjectContext; class Creature; @@ -419,7 +420,7 @@ public: bool HasAggro(Unit* unit); int32 GetGroupSlotIndex(Player* player); int32 GetRangedIndex(Player* player); - int32 GetClassIndex(Player* player, uint8_t cls); + int32 GetClassIndex(Player* player, uint8 cls); int32 GetRangedDpsIndex(Player* player); int32 GetMeleeIndex(Player* player); @@ -573,6 +574,8 @@ public: std::set GetCurrentIncompleteQuestIds(); void PetFollow(); static float GetItemScoreMultiplier(ItemQualities quality); + static bool IsHealingSpell(uint32 spellFamilyName, flag96 spelFalimyFlags); + static SpellFamilyNames Class2SpellFamilyName(uint8 cls); NewRpgInfo rpgInfo; private: diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index ff9930a7..c4ecd4a2 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -38,8 +38,6 @@ #include "StatsWeightCalculator.h" #include "World.h" -#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, @@ -79,14 +77,16 @@ void PlayerbotFactory::Init() if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10) continue; - + if (quest->GetRewSpellCast() > 0) { int32 spellId = quest->GetRewSpellCast(); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; - } else if (quest->GetRewSpell() > 0) { + } + else if (quest->GetRewSpell() > 0) + { int32 spellId = quest->GetRewSpell(); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) @@ -271,7 +271,8 @@ void PlayerbotFactory::Randomize(bool incremental) pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Talents"); LOG_DEBUG("playerbots", "Initializing talents..."); - if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) + if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || + bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) { InitTalentsTree(); } @@ -306,7 +307,8 @@ void PlayerbotFactory::Randomize(bool incremental) pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Equip"); LOG_DEBUG("playerbots", "Initializing equipmemt..."); - if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) + if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || + bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) { InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit); } @@ -1559,27 +1561,16 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) // int tab = AiFactory::GetPlayerSpecTab(bot); uint32 blevel = bot->GetLevel(); - int32 delta = 2; - if (blevel < 15) - delta = std::min(blevel, 15u); - else if (blevel < 40) - delta = 10; - else if (blevel < 60) - delta = 6; - else if (blevel < 70) - delta = 9; - else if (blevel < 80) - delta = 9; - else if (blevel == 80) - delta = 9; + int32 delta = std::min(blevel, 10u); StatsWeightCalculator calculator(bot); - for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) + // Reverse order may work better + for (int32 slot = (int32)EQUIPMENT_SLOT_TABARD; slot >= (int32)EQUIPMENT_SLOT_START; slot--) { if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) continue; - if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) + if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) continue; if (level < 30 && slot == EQUIPMENT_SLOT_NECK) @@ -1670,8 +1661,6 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) items[slot].push_back(itemId); } } - if (items[slot].size() >= 25) - break; } } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_NORMAL); @@ -1719,8 +1708,6 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) if (bestScoreForSlot < 1.2f * old_score) continue; } - - if (oldItem) { uint8 bagIndex = oldItem->GetBagSlot(); @@ -1742,19 +1729,19 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance) // if (newItem) // { // newItem->AddToWorld(); - // newItem->AddToUpdateQueueOf(bot); + // 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) + for (int32 slot = (int32)EQUIPMENT_SLOT_TABARD; slot >= (int32)EQUIPMENT_SLOT_START; slot--) { if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) continue; - if (level < 40 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) + if (level < 50 && (slot == EQUIPMENT_SLOT_TRINKET1 || slot == EQUIPMENT_SLOT_TRINKET2)) continue; if (level < 30 && slot == EQUIPMENT_SLOT_NECK) @@ -2087,7 +2074,7 @@ bool PlayerbotFactory::CanEquipUnseenItem(uint8 slot, uint16& dest, uint32 item) if (Item* pItem = Item::CreateItem(item, 1, bot, false, 0, true)) { InventoryResult result = botAI ? botAI->CanEquipItem(slot, dest, pItem, true, true) - : bot->CanEquipItem(slot, dest, pItem, true, true); + : bot->CanEquipItem(slot, dest, pItem, true, true); pItem->RemoveFromUpdateQueueOf(bot); delete pItem; return result == EQUIP_ERR_OK; @@ -2177,18 +2164,15 @@ void PlayerbotFactory::InitSkills() uint32 maxValue = level * 5; bot->UpdateSkillsForLevel(); - uint16 step = bot->GetSkillValue(SKILL_RIDING) ? bot->GetSkillStep(SKILL_RIDING) : 1; - - if (bot->GetLevel() >= 70) - bot->SetSkill(SKILL_RIDING, step, 300, 300); - else if (bot->GetLevel() >= 60) - bot->SetSkill(SKILL_RIDING, step, 225, 225); - else if (bot->GetLevel() >= 40) - bot->SetSkill(SKILL_RIDING, step, 150, 150); - else if (bot->GetLevel() >= 20) - bot->SetSkill(SKILL_RIDING, step, 75, 75); - else - bot->SetSkill(SKILL_RIDING, 0, 0, 0); + bot->SetSkill(SKILL_RIDING, 0, 0, 0); + if (bot->GetLevel() >= sPlayerbotAIConfig->useGroundMountAtMinLevel) + bot->learnSpell(33388); + if (bot->GetLevel() >= sPlayerbotAIConfig->useFastGroundMountAtMinLevel) + bot->learnSpell(33391); + if (bot->GetLevel() >= sPlayerbotAIConfig->useFlyMountAtMinLevel) + bot->learnSpell(34090); + if (bot->GetLevel() >= sPlayerbotAIConfig->useFastFlyMountAtMinLevel) + bot->learnSpell(34091); uint32 skillLevel = bot->GetLevel() < 40 ? 0 : 1; uint32 dualWieldLevel = bot->GetLevel() < 20 ? 0 : 1; @@ -2360,8 +2344,6 @@ void PlayerbotFactory::InitAvailableSpells() trainerIdCache.push_back(trainerId); } } - // uint32 learnedCounter = 0; - // uint32 oktest = 0; for (uint32 trainerId : trainerIdCache) { TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId); @@ -2379,7 +2361,7 @@ void PlayerbotFactory::InitAvailableSpells() if (!tSpell) continue; - if (!tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0])) + if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0])) continue; TrainerSpellState state = bot->GetTrainerSpellState(tSpell); @@ -2394,7 +2376,8 @@ void PlayerbotFactory::InitAvailableSpells() continue; if (spellInfo->Effects[j].Effect == SPELL_EFFECT_PROFICIENCY || - spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP || + (spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP && + spellInfo->Effects[j].MiscValue != SKILL_RIDING) || spellInfo->Effects[j].Effect == SPELL_EFFECT_DUAL_WIELD) { learn = false; @@ -2405,19 +2388,12 @@ void PlayerbotFactory::InitAvailableSpells() { continue; } - // oktest++; - if (tSpell->learnedSpell[0]) - { + + if (tSpell->IsCastable()) + bot->CastSpell(bot, tSpell->spell, true); + else bot->learnSpell(tSpell->learnedSpell[0], false); - } - // else - // { - // botAI->CastSpell(tSpell->spell, bot); - // } } - // LOG_INFO("playerbots", "C: {}, ok: {}", ++learnedCounter, oktest); - // if (++learnedCounter > 20) - // break; } } @@ -2460,6 +2436,8 @@ void PlayerbotFactory::InitClassSpells() // to leave DK starting area bot->learnSpell(53428, false); bot->learnSpell(50977, false); + bot->learnSpell(49142, false); + bot->learnSpell(48778, false); break; case CLASS_HUNTER: bot->learnSpell(2973, true); @@ -3866,7 +3844,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) float bestGemScore[4] = {0, 0, 0, 0}; std::vector curCount = GetCurrentGemsCount(); uint8 jewelersCount = 0; - int requiredActive = bot->GetLevel() <= 70 ? 2 : 1; + int requiredActive = 2; std::vector availableGems; for (const uint32& enchantGem : enchantGemIdCache) { @@ -4007,7 +3985,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) bool isJewelersGem = gemTemplate->ItemLimitCategory == 2; if (isJewelersGem && jewelersCount >= 3) continue; - + const GemPropertiesEntry* gemProperties = sGemPropertiesStore.LookupEntry(gemTemplate->GemProperties); if (!gemProperties) continue; diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 50bfc968..e2834923 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -5,11 +5,14 @@ #include "DBCStores.h" #include "ItemTemplate.h" #include "ObjectMgr.h" +#include "PlayerbotAI.h" +#include "PlayerbotAIAware.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "UpdateFields.h" +#include "Util.h" StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); } @@ -52,12 +55,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0); break; case ITEM_SPELLTRIGGER_CHANCE_ON_HIT: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) { if (proto->Spells[j].SpellPPMRate > 0.01f) CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate); else - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8 + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8 } break; default: @@ -67,7 +70,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) if (proto->socketBonus) { - if (const SpellItemEnchantmentEntry *enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus)) + if (const SpellItemEnchantmentEntry* enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus)) CollectEnchantStats(enchant); } } @@ -94,34 +97,50 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s procFlags = eventEntry->procFlags; else procFlags = spellInfo->ProcFlags; - + if (eventEntry && eventEntry->customChance) procChance = eventEntry->customChance; else procChance = spellInfo->ProcChance; - bool lowChance = procChance <= 5; - + bool lowChance = procChance <= 5; + if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags))) canNextTrigger = false; if (spellInfo->StackAmount) { // Heuristic multiplier for spell with stackAmount since high stackAmount may not be available - if (spellInfo->StackAmount <= 10) - multiplier *= spellInfo->StackAmount * 0.6; + if (spellInfo->StackAmount <= 1) + multiplier *= spellInfo->StackAmount * 1; + else if (spellInfo->StackAmount <= 5) + multiplier *= 1 + (spellInfo->StackAmount - 1) * 0.75; + else if (spellInfo->StackAmount <= 10) + multiplier *= 4 + (spellInfo->StackAmount - 5) * 0.6; else if (spellInfo->StackAmount <= 20) - multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4; + multiplier *= 7 + (spellInfo->StackAmount - 10) * 0.4; else - multiplier *= 10; + multiplier *= 11; } - + for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; + if (!effectInfo.Effect) + continue; switch (effectInfo.Effect) { case SPELL_EFFECT_APPLY_AURA: { + if (spellInfo->SpellFamilyName && /*effectInfo.ApplyAuraName != SPELL_AURA_DUMMY &&*/ + effectInfo.ApplyAuraName != SPELL_AURA_PROC_TRIGGER_SPELL) + { + if (!CheckSpellValidation(spellInfo->SpellFamilyName, effectInfo.SpellClassMask)) + return; + + // Some dummy effects cannot be recognized, make some bonus to identify + stats[STATS_TYPE_BONUS] += 1; + } + /// @todo Handle negative spell if (!spellInfo->IsPositive()) break; @@ -130,7 +149,8 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1) coverage = 1.0f; else - coverage = std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown)); + coverage = + std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown)); multiplier *= coverage; HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown); @@ -167,12 +187,12 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s break; float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); int32 val = AverageValue(effectInfo); - if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) { float transfer_multiplier = 1; stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; } - else if (type_ == CollectorType::SPELL_DMG) + else if (type_ & CollectorType::SPELL_DMG) { float transfer_multiplier = 0.5; stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; @@ -200,7 +220,7 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan { case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: { - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) CollectSpellStats(enchant_spell_id, 0.25f); break; } @@ -225,55 +245,60 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan } /// @todo Special case for some spell that hard to calculate, like trinket, relic, etc. -bool StatsCollector::SpecialSpellFilter(uint32 spellId) { +bool StatsCollector::SpecialSpellFilter(uint32 spellId) +{ // trinket switch (spellId) { - case 27521: // Insightful Earthstorm Diamond + case 60764: // Totem of Splintering + if (type_ & (CollectorType::SPELL)) + return true; + break; + case 27521: // Insightful Earthstorm Diamond stats[STATS_TYPE_MANA_REGENERATION] += 20; return true; - case 55381: // Insightful Earthsiege Diamond + case 55381: // Insightful Earthsiege Diamond stats[STATS_TYPE_MANA_REGENERATION] += 40; return true; - case 39442: // Darkmoon Card: Wrath - if (type_ != CollectorType::SPELL_HEAL) + case 39442: // Darkmoon Card: Wrath + if (!(type_ & CollectorType::SPELL_HEAL)) stats[STATS_TYPE_CRIT] += 50; return true; - case 59620: // Berserk - if (type_ == CollectorType::MELEE) + case 59620: // Berserk + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_ATTACK_POWER] += 120; return true; - case 67702: // Death's Verdict + case 67702: // Death's Verdict stats[STATS_TYPE_ATTACK_POWER] += 225; return true; - case 67771: // Death's Verdict (heroic) + case 67771: // Death's Verdict (heroic) stats[STATS_TYPE_ATTACK_POWER] += 260; return true; - case 71406: // Tiny Abomination in a Jar + 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) + 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 + return true; + case 71519: // Deathbringer's Will stats[STATS_TYPE_ATTACK_POWER] += 350; return true; - case 71562: // Deathbringer's Will (heroic) + case 71562: // Deathbringer's Will (heroic) stats[STATS_TYPE_ATTACK_POWER] += 400; return true; - case 71602: // Dislodged Foreign Object + case 71602: // Dislodged Foreign Object /// @todo The item can be triggered by heal spell, which mismatch with it's description /// Noticing that heroic item can not be triggered, probably a bug to report to AC - if (type_ == CollectorType::SPELL_HEAL) + if (type_ & CollectorType::SPELL_HEAL) return true; break; - case 71903: // Shadowmourne + case 71903: // Shadowmourne stats[STATS_TYPE_STRENGTH] += 200; return true; default: @@ -295,26 +320,26 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId) switch (enchantSpellId) { case 64440: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) { stats[STATS_TYPE_PARRY] += 50; } return true; - case 53365: // Rune of the Fallen Crusader - if (type_ == CollectorType::MELEE) + case 53365: // Rune of the Fallen Crusader + if (type_ & CollectorType::MELEE) { stats[STATS_TYPE_STRENGTH] += 75; } return true; - case 62157: // Rune of the Stoneskin Gargoyle - if (type_ == CollectorType::MELEE) + case 62157: // Rune of the Stoneskin Gargoyle + if (type_ & CollectorType::MELEE) { stats[STATS_TYPE_DEFENSE] += 25; stats[STATS_TYPE_STAMINA] += 50; } return true; - case 64571: // Blood draining - if (type_ == CollectorType::MELEE) + case 64571: // Blood draining + if (type_ & CollectorType::MELEE) { stats[STATS_TYPE_STAMINA] += 50; } @@ -325,18 +350,34 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId) return false; } -bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags) +bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict) { const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); - uint32 spellFamilyName = eventEntry ? eventEntry->spellFamilyName : 0; + uint32 spellFamilyName = 0; + if (eventEntry) + { + spellFamilyName = eventEntry->spellFamilyName; + flag96 spellFamilyMask = eventEntry->spellFamilyMask; + if (spellFamilyName != 0) + { + if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict)) + return false; + } + } - if (spellFamilyName != 0) - /// @todo Check specific trigger spell by spellFamilyMask - return true; - - uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask - switch (type_) { - case CollectorType::MELEE: + uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask + switch (type_) + { + case CollectorType::MELEE_DMG: + { + triggerMask |= MELEE_PROC_FLAG_MASK; + triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PROC_FLAG_DONE_PERIODIC; + if (procFlags & triggerMask) + return true; + break; + } + case CollectorType::MELEE_TANK: { triggerMask |= MELEE_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK; @@ -349,7 +390,7 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro { triggerMask |= RANGED_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK; - triggerMask |= PERIODIC_PROC_FLAG_MASK; + triggerMask |= PROC_FLAG_DONE_PERIODIC; if (procFlags & triggerMask) return true; break; @@ -357,6 +398,7 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro case CollectorType::SPELL_DMG: { triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PROC_FLAG_DONE_PERIODIC; // Healing spell cannot trigger triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; @@ -367,10 +409,13 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro case CollectorType::SPELL_HEAL: { triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PROC_FLAG_DONE_PERIODIC; // Dmg spell should not trigger triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; - triggerMask &= ~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell + if (!spellFamilyName) + triggerMask &= + ~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell if (procFlags & triggerMask) return true; break; @@ -419,39 +464,39 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) stats[STATS_TYPE_BLOCK_RATING] += val; break; case ITEM_MOD_HIT_MELEE_RATING: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_HIT] += val; break; case ITEM_MOD_HIT_RANGED_RATING: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_HIT] += val; break; case ITEM_MOD_HIT_SPELL_RATING: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_HIT] += val; break; case ITEM_MOD_CRIT_MELEE_RATING: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_CRIT] += val; break; case ITEM_MOD_CRIT_RANGED_RATING: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_CRIT] += val; break; case ITEM_MOD_CRIT_SPELL_RATING: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_CRIT] += val; break; case ITEM_MOD_HASTE_MELEE_RATING: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_HASTE] += val; break; case ITEM_MOD_HASTE_RANGED_RATING: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_HASTE] += val; break; case ITEM_MOD_HASTE_SPELL_RATING: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_HASTE] += val; break; case ITEM_MOD_HIT_RATING: @@ -502,13 +547,14 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) } } -void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown) +void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, + uint32 triggerCooldown) { if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) return; - + int32 val = AverageValue(effectInfo); - + switch (effectInfo.ApplyAuraName) { case SPELL_AURA_MOD_DAMAGE_DONE: @@ -534,11 +580,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu break; } case SPELL_AURA_MOD_ATTACK_POWER: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; break; case SPELL_AURA_MOD_RANGED_ATTACK_POWER: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; break; case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: @@ -564,7 +610,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu case STAT_SPIRIT: stats[STATS_TYPE_SPIRIT] += val * multiplier; break; - case -1: // Stat all + case -1: // Stat all stats[STATS_TYPE_STRENGTH] += val * multiplier; stats[STATS_TYPE_AGILITY] += val * multiplier; stats[STATS_TYPE_STAMINA] += val * multiplier; @@ -579,7 +625,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu case SPELL_AURA_MOD_RESISTANCE: { int32 statType = effectInfo.MiscValue; - if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical + if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical stats[STATS_TYPE_ARMOR] += val * multiplier; break; } @@ -604,39 +650,39 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu stats[STATS_TYPE_BLOCK_RATING] += val * multiplier; break; case CR_HIT_MELEE: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_HIT] += val * multiplier; break; case CR_HIT_RANGED: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_HIT] += val * multiplier; break; case CR_HIT_SPELL: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_HIT] += val * multiplier; break; case CR_CRIT_MELEE: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_CRIT] += val * multiplier; break; case CR_CRIT_RANGED: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_CRIT] += val * multiplier; break; case CR_CRIT_SPELL: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_CRIT] += val * multiplier; break; case CR_HASTE_MELEE: - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) stats[STATS_TYPE_HASTE] += val * multiplier; break; case CR_HASTE_RANGED: - if (type_ == CollectorType::RANGED) + if (type_ & CollectorType::RANGED) stats[STATS_TYPE_HASTE] += val * multiplier; break; case CR_HASTE_SPELL: - if (type_ == CollectorType::SPELL) + if (type_ & CollectorType::SPELL) stats[STATS_TYPE_HASTE] += val * multiplier; break; case CR_EXPERTISE: @@ -675,12 +721,18 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown); break; } + case SPELL_AURA_ADD_TARGET_TRIGGER: + { + if (canNextTrigger) + CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown); + break; + } case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS: { if (type_ != CollectorType::SPELL_HEAL) { int32 statType = effectInfo.MiscValue; - if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical + if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical stats[STATS_TYPE_CRIT] += 30 * val * multiplier; } break; @@ -709,4 +761,73 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo) break; } return basePoints; +} + +bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict) +{ + if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName) + return false; + + bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags); + // strict to healer + if (strict && (type_ & CollectorType::SPELL_HEAL)) + { + return isHealingSpell; + } + + if (!(type_ & CollectorType::SPELL_HEAL) && isHealingSpell) + return false; + + // spells for caster/melee/tank are ambiguous + if (cls_ == CLASS_DRUID && spellFamilyName == SPELLFAMILY_DRUID && (type_ & CollectorType::MELEE)) + { + uint32 castingFlagsA = 0x4 | 0x2 | 0x1 | 0x200000; // starfire | moonfire | wrath | insect swarm + uint32 castingFlagsB = 0x0; + uint32 castingFlagsC = 0x0; + flag96 invalidFlags = {castingFlagsA, castingFlagsB, castingFlagsC}; + if (spelFalimyFlags & invalidFlags) + return false; + } + + if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_TANK)) + { + uint32 retributionFlagsA = 0x0; + uint32 retributionFlagsB = 0x8000; // crusader strike + uint32 retributionFlagsC = 0x0; + flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC}; + if (spelFalimyFlags & invalidFlags) + return false; + } + + if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_DMG)) + { + uint32 retributionFlagsA = 0x0; + uint32 retributionFlagsB = 0x100000; // shield of righteouness + uint32 retributionFlagsC = 0x0; + flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC}; + if (spelFalimyFlags & invalidFlags) + return false; + } + + if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::SPELL_DMG)) + { + uint32 meleeFlagsA = 0x0; + uint32 meleeFlagsB = 0x1000010; // stromstrike + uint32 meleeFlagsC = 0x4; // lava lash + flag96 invalidFlags = {meleeFlagsA, meleeFlagsB, meleeFlagsC}; + if (spelFalimyFlags & invalidFlags) + return false; + } + + if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::MELEE_DMG)) + { + uint32 casterFlagsA = 0x0; + uint32 casterFlagsB = 0x1000; // lava burst + uint32 casterFlagsC = 0x0; + flag96 invalidFlags = {casterFlagsA, casterFlagsB, casterFlagsC}; + if (spelFalimyFlags & invalidFlags) + return false; + } + + return true; } \ No newline at end of file diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h index b51dddf9..99f100ec 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -42,15 +42,19 @@ enum StatsType : uint8 // Stats for weapon dps STATS_TYPE_MELEE_DPS, STATS_TYPE_RANGED_DPS, - STATS_TYPE_MAX = 25 + // Bonus for unrecognized stats + STATS_TYPE_BONUS, + STATS_TYPE_MAX = 26 }; enum CollectorType : uint8 { - MELEE = 1, - RANGED = 2, - SPELL_DMG = 4, - SPELL_HEAL = 8, + MELEE_DMG = 1, + MELEE_TANK = 2, + RANGED = 4, + SPELL_DMG = 8, + SPELL_HEAL = 16, + MELEE = MELEE_DMG | MELEE_TANK, SPELL = SPELL_DMG | SPELL_HEAL }; @@ -63,19 +67,21 @@ public: void CollectItemStats(ItemTemplate const* proto); void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); + bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict = true); + bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true); public: int32 stats[STATS_TYPE_MAX]; private: - bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags); void CollectByItemStatType(uint32 itemStatType, int32 val); bool SpecialSpellFilter(uint32 spellId); bool SpecialEnchantFilter(uint32 enchantSpellId); - void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown); + void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, + uint32 triggerCooldown); int32 AverageValue(const SpellEffectInfo& effectInfo); - + private: CollectorType type_; uint32 cls_; diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index e77ac102..371f510a 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -14,6 +14,8 @@ #include "PlayerbotAI.h" #include "PlayerbotFactory.h" #include "SharedDefines.h" +#include "SpellAuraDefines.h" +#include "SpellMgr.h" #include "StatsCollector.h" #include "Unit.h" @@ -23,15 +25,16 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) type_ = CollectorType::SPELL_HEAL; else if (PlayerbotAI::IsCaster(player)) type_ = CollectorType::SPELL_DMG; + else if (PlayerbotAI::IsTank(player)) + type_ = CollectorType::MELEE_TANK; else if (PlayerbotAI::IsMelee(player)) - type_ = CollectorType::MELEE; + type_ = CollectorType::MELEE_DMG; else type_ = CollectorType::RANGED; 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) @@ -79,7 +82,7 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId) CalculateItemTypePenalty(proto); if (enable_item_set_bonus_) - CalculateItemSetBonus(player_, proto); + CalculateItemSetMod(player_, proto); CalculateSocketBonus(player_, proto); @@ -125,6 +128,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) // Basic weights stats_weights_[STATS_TYPE_STAMINA] += 0.01f; stats_weights_[STATS_TYPE_ARMOR] += 0.001f; + stats_weights_[STATS_TYPE_BONUS] += 1.0f; if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) { @@ -259,8 +263,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f; } else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) || - (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow - (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance + (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow + (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance { stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; stats_weights_[STATS_TYPE_SPIRIT] += 0.6f; @@ -311,7 +315,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_DEFENSE] += 2.5f; stats_weights_[STATS_TYPE_PARRY] += 2.0f; stats_weights_[STATS_TYPE_DODGE] += 2.0f; - stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + // stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f; stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f; @@ -330,7 +334,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_DEFENSE] += 3.5f; stats_weights_[STATS_TYPE_PARRY] += 2.0f; stats_weights_[STATS_TYPE_DODGE] += 2.0f; - stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + // stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_HIT] += 2.0f; stats_weights_[STATS_TYPE_CRIT] += 0.5f; @@ -347,7 +351,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; stats_weights_[STATS_TYPE_DEFENSE] += 0.3f; stats_weights_[STATS_TYPE_DODGE] += 0.7f; - stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f; + // stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_HIT] += 3.0f; stats_weights_[STATS_TYPE_CRIT] += 1.3f; @@ -380,7 +384,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) } } -void StatsWeightCalculator::CalculateItemSetBonus(Player* player, ItemTemplate const* proto) +void StatsWeightCalculator::CalculateItemSetMod(Player* player, ItemTemplate const* proto) { uint32 itemSet = proto->ItemSet; if (!itemSet) @@ -466,7 +470,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) // enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield if (isDoubleHand && ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) || - (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab != DEATHKNIGHT_TAB_BLOOD) || + (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION))) @@ -486,14 +490,12 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) weight_ *= 0.1; } // fury with titan's grip - if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) && + if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || + proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) && (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip())) { weight_ *= 0.1; } - } - if (proto->Class == ITEM_CLASS_WEAPON) - { if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN) { weight_ *= 0.1; @@ -512,6 +514,10 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) { weight_ *= 1.1; } + if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand) + { + weight_ *= 1.3; + } bool slowDelay = proto->Delay > 2500; if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay) weight_ *= 1.1; @@ -540,11 +546,10 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) { float hit_current, hit_overflow; float validPoints; - // m_modMeleeHitChance = (float)GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); - // m_modMeleeHitChance += GetRatingBonusValue(CR_HIT_MELEE); - if (hitOverflowType_ == CollectorType::SPELL) + if (hitOverflowType_ & CollectorType::SPELL) { hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE); + hit_current += player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176) hit_current += player->GetRatingBonusValue(CR_HIT_SPELL); hit_overflow = SPELL_HIT_OVERFLOW; if (hit_overflow > hit_current) @@ -552,7 +557,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) else validPoints = 0; } - else if (hitOverflowType_ == CollectorType::MELEE) + else if (hitOverflowType_ & CollectorType::MELEE) { hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); hit_current += player->GetRatingBonusValue(CR_HIT_MELEE); @@ -576,7 +581,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) } { - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) { float expertise_current, expertise_overflow; expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE); @@ -589,12 +594,13 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) else validPoints = 0; - collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints); + collector_->stats[STATS_TYPE_EXPERTISE] = + std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints); } } { - if (type_ == CollectorType::MELEE) + if (type_ & CollectorType::MELEE) { float defense_current, defense_overflow; defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL); @@ -611,7 +617,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) } { - if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) { float armor_penetration_current, armor_penetration_overflow; armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); @@ -619,11 +625,13 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) float validPoints; if (armor_penetration_overflow > armor_penetration_current) - validPoints = (armor_penetration_overflow - armor_penetration_current) / player->GetRatingMultiplier(CR_ARMOR_PENETRATION); + 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); + collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = + std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints); } } } @@ -631,7 +639,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) void StatsWeightCalculator::ApplyWeightFinetune(Player* player) { { - if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) { float armor_penetration_current, armor_penetration_overflow; armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); diff --git a/src/factory/StatsWeightCalculator.h b/src/factory/StatsWeightCalculator.h index d65966ff..d978b9c8 100644 --- a/src/factory/StatsWeightCalculator.h +++ b/src/factory/StatsWeightCalculator.h @@ -40,7 +40,7 @@ private: void GenerateBasicWeights(Player* player); void GenerateAdditionalWeights(Player* player); - void CalculateItemSetBonus(Player* player, ItemTemplate const* proto); + void CalculateItemSetMod(Player* player, ItemTemplate const* proto); void CalculateSocketBonus(Player* player, ItemTemplate const* proto); void CalculateItemTypePenalty(ItemTemplate const* proto); diff --git a/src/strategy/druid/CasterDruidStrategy.cpp b/src/strategy/druid/CasterDruidStrategy.cpp index 27080efe..8182702d 100644 --- a/src/strategy/druid/CasterDruidStrategy.cpp +++ b/src/strategy/druid/CasterDruidStrategy.cpp @@ -115,7 +115,9 @@ CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrat NextAction** CasterDruidStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("starfall", ACTION_DEFAULT + 0.2f), + return NextAction::array(0, + new NextAction("starfall", ACTION_HIGH + 1.0f), + new NextAction("force of nature", ACTION_DEFAULT + 1.0f), new NextAction("wrath", ACTION_DEFAULT + 0.1f), // new NextAction("starfire", ACTION_NORMAL), nullptr); @@ -127,20 +129,19 @@ void CasterDruidStrategy::InitTriggers(std::vector& triggers) // triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", // ACTION_MOVE), nullptr))); + triggers.push_back(new TriggerNode("eclipse (lunar) cooldown", + NextAction::array(0, new NextAction("starfire", ACTION_DEFAULT + 0.2f), nullptr))); + triggers.push_back(new TriggerNode("eclipse (solar) cooldown", + NextAction::array(0, new NextAction("wrath", ACTION_DEFAULT + 0.2f), nullptr))); + triggers.push_back(new TriggerNode( "insect swarm", NextAction::array(0, new NextAction("insect swarm", ACTION_NORMAL + 5), nullptr))); triggers.push_back( new TriggerNode("moonfire", NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), nullptr))); triggers.push_back( new TriggerNode("eclipse (solar)", NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 6), nullptr))); - triggers.push_back(new TriggerNode("eclipse (lunar) cooldown", - NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 2), nullptr))); triggers.push_back(new TriggerNode("eclipse (lunar)", NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 6), nullptr))); - triggers.push_back(new TriggerNode("eclipse (solar) cooldown", - NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 2), nullptr))); - triggers.push_back( - new TriggerNode("moonfire", NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), nullptr))); triggers.push_back( new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), NULL))); triggers.push_back(new TriggerNode("enemy too close for spell", @@ -152,8 +153,7 @@ void CasterDruidAoeStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode( - "light aoe", NextAction::array(0, new NextAction("starfall", ACTION_NORMAL + 5), - new NextAction("insect swarm on attacker", ACTION_NORMAL + 3), + "light aoe", NextAction::array(0, new NextAction("insect swarm on attacker", ACTION_NORMAL + 3), new NextAction("moonfire on attacker", ACTION_NORMAL + 3), NULL))); } diff --git a/src/strategy/druid/DruidActions.h b/src/strategy/druid/DruidActions.h index ee90788a..124fef6c 100644 --- a/src/strategy/druid/DruidActions.h +++ b/src/strategy/druid/DruidActions.h @@ -323,4 +323,10 @@ public: Unit* GetTarget() override; }; +class CastForceOfNatureAction : public CastSpellAction +{ +public: + CastForceOfNatureAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "force of nature") {} +}; + #endif diff --git a/src/strategy/druid/DruidAiObjectContext.cpp b/src/strategy/druid/DruidAiObjectContext.cpp index e0d8c7de..631824b4 100644 --- a/src/strategy/druid/DruidAiObjectContext.cpp +++ b/src/strategy/druid/DruidAiObjectContext.cpp @@ -233,6 +233,7 @@ public: creators["insect swarm on attacker"] = &DruidAiObjectContextInternal::insect_swarm_on_attacker; creators["moonfire on attacker"] = &DruidAiObjectContextInternal::moonfire_on_attacker; creators["enrage"] = &DruidAiObjectContextInternal::enrage; + creators["force of nature"] = &DruidAiObjectContextInternal::force_of_nature; } private: @@ -317,6 +318,7 @@ private: static Action* insect_swarm_on_attacker(PlayerbotAI* ai) { return new CastInsectSwarmOnAttackerAction(ai); } static Action* moonfire_on_attacker(PlayerbotAI* ai) { return new CastMoonfireOnAttackerAction(ai); } static Action* enrage(PlayerbotAI* ai) { return new CastEnrageAction(ai); } + static Action* force_of_nature(PlayerbotAI* ai) { return new CastForceOfNatureAction(ai); } }; DruidAiObjectContext::DruidAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI) diff --git a/src/strategy/paladin/DpsPaladinStrategy.cpp b/src/strategy/paladin/DpsPaladinStrategy.cpp index 4b6bf78c..62692a62 100644 --- a/src/strategy/paladin/DpsPaladinStrategy.cpp +++ b/src/strategy/paladin/DpsPaladinStrategy.cpp @@ -84,8 +84,8 @@ NextAction** DpsPaladinStrategy::getDefaultActions() { 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("judgement of wisdom", ACTION_DEFAULT + 0.5f), + new NextAction("crusader strike", ACTION_DEFAULT + 0.4f), new NextAction("divine storm", ACTION_DEFAULT + 0.3f), new NextAction("consecration", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), nullptr);