diff --git a/.travis.yml b/.travis.yml index 65b841de4..b454f565b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ # language: cpp +cache: ccache # reduce clone time by only getting the latest commit and not the whole history (default for travis is 100) git: @@ -33,15 +34,20 @@ branches: os: - linux + - osx compiler: - clang - gcc env: - matrix: - - BUILD_TYPE=Release - - BUILD_TYPE=Release PCH_FLAG=ON + - BUILD_TYPE=Release + - BUILD_TYPE=Release PCH_FLAG=ON + +matrix: + exclude: + - os: osx + compiler: gcc addons: apt: @@ -60,18 +66,25 @@ addons: - libboost-system1.55-dev - libboost-program-options1.55-dev - libboost-thread1.55-dev - + # overwrite GCC version for GCC build only before_install: - if [ $CC = "gcc" ] ; then export CC=gcc-4.8 CXX=g++-4.8 ; fi -script: - - test -d _build || mkdir _build - - test -d _install || mkdir _install - - cd _build - - cmake -DCMAKE_INSTALL_PREFIX=../_install -DBUILD_EXTRACTOR=ON -DBUILD_VMAP_EXTRACTOR=ON -DBUILD_MMAP_EXTRACTOR=ON -DPCH=$PCH_FLAG .. - - make -j4 - - make install +install: |- + if [ "$TRAVIS_OS_NAME" == "osx" ] + then + brew update + brew install mysql++ + fi + +script: |- + mkdir _build + mkdir _install + cd _build + cmake -DCMAKE_INSTALL_PREFIX=../_install -DBUILD_EXTRACTOR=ON -DBUILD_VMAP_EXTRACTOR=ON -DBUILD_MMAP_EXTRACTOR=ON -DPCH=$PCH_FLAG .. + make -j4 + make install # if this configuration file is not in one of the offical CMaNGOS repositories at http://github.com/cmangos/, PLEASE remove the notifications or point them to another IRC channel! diff --git a/CMakeLists.txt b/CMakeLists.txt index 385992834..f499ea194 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,16 @@ if(UNIX) find_package(MySQL REQUIRED) endif() + # If OpenSSL path isn't specified on mac we set the one that homebrew uses + # since that's what most people will be using. + if (APPLE) + if (NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR /usr/local/opt/openssl/) + endif() + if (NOT OPENSSL_INCLUDE_DIR) + set(OPENSSL_INCLUDE_DIR /usr/local/opt/openssl/include) + endif() + endif() find_package(OpenSSL REQUIRED) find_package(ZLIB REQUIRED) endif() diff --git a/contrib/mmap/CMakeLists.txt b/contrib/mmap/CMakeLists.txt index c0af56039..1b46226b9 100644 --- a/contrib/mmap/CMakeLists.txt +++ b/contrib/mmap/CMakeLists.txt @@ -43,7 +43,12 @@ add_library(vmaplib ../../src/game/vmap/ModelInstance.cpp ) -target_link_libraries(vmaplib g3dlite zlib) +IF(APPLE) + FIND_LIBRARY(CORE_SERVICES CoreServices ) + SET(EXTRA_LIBS ${CORE_SERVICES}) +ENDIF (APPLE) + +target_link_libraries(vmaplib g3dlite zlib ${EXTRA_LIBS}) set(SOURCES ./src/IntermediateValues.cpp diff --git a/contrib/mmap/src/MMapCommon.h b/contrib/mmap/src/MMapCommon.h index a7759e083..69bf9d8e0 100644 --- a/contrib/mmap/src/MMapCommon.h +++ b/contrib/mmap/src/MMapCommon.h @@ -32,6 +32,7 @@ #ifndef WIN32 #include #include +#include #endif using namespace std; diff --git a/contrib/vmap_assembler/CMakeLists.txt b/contrib/vmap_assembler/CMakeLists.txt index 04b9073bc..a9260f341 100644 --- a/contrib/vmap_assembler/CMakeLists.txt +++ b/contrib/vmap_assembler/CMakeLists.txt @@ -37,7 +37,12 @@ add_library(vmap ../../src/game/vmap/ModelInstance.cpp ) -target_link_libraries(vmap g3dlite z) +IF(APPLE) + FIND_LIBRARY(CORE_SERVICES CoreServices ) + SET(EXTRA_LIBS ${CORE_SERVICES}) +ENDIF (APPLE) + +target_link_libraries(vmap g3dlite z ${EXTRA_LIBS}) add_executable(${EXECUTABLE_NAME} vmap_assembler.cpp) target_link_libraries(${EXECUTABLE_NAME} vmap) diff --git a/contrib/vmap_extractor/CMakeLists.txt b/contrib/vmap_extractor/CMakeLists.txt index 725d57c5f..441897178 100644 --- a/contrib/vmap_extractor/CMakeLists.txt +++ b/contrib/vmap_extractor/CMakeLists.txt @@ -19,6 +19,14 @@ ADD_DEFINITIONS("-Wall") ADD_DEFINITIONS("-ggdb") ADD_DEFINITIONS("-O3") +if (APPLE) + add_definitions("-Dfopen64=fopen") + add_definitions("-Dfseeko64=fseeko") + add_definitions("-Dfseek64=fseek") + add_definitions("-Dftell64=ftell") + add_definitions("-Dftello64=ftello") +endif() + include_directories(../../dep/libmpq) add_subdirectory(vmapextract) diff --git a/contrib/vmap_extractor/vmapextract/loadlib/loadlib.h b/contrib/vmap_extractor/vmapextract/loadlib/loadlib.h index ea301b0fc..6ecffceb5 100644 --- a/contrib/vmap_extractor/vmapextract/loadlib/loadlib.h +++ b/contrib/vmap_extractor/vmapextract/loadlib/loadlib.h @@ -19,31 +19,16 @@ #ifndef LOAD_LIB_H #define LOAD_LIB_H -#ifdef WIN32 -typedef __int64 int64; -typedef __int32 int32; -typedef __int16 int16; -typedef __int8 int8; -typedef unsigned __int64 uint64; -typedef unsigned __int32 uint32; -typedef unsigned __int16 uint16; -typedef unsigned __int8 uint8; -#else -#include -#ifndef uint64_t - #ifndef _WIN32 - #include - #endif -#endif -typedef int64_t int64; -typedef int32_t int32; -typedef int16_t int16; -typedef int8_t int8; -typedef uint64_t uint64; -typedef uint32_t uint32; -typedef uint16_t uint16; -typedef uint8_t uint8; -#endif +#include + +typedef std::int64_t int64; +typedef std::int32_t int32; +typedef std::int16_t int16; +typedef std::int8_t int8; +typedef std::uint64_t uint64; +typedef std::uint32_t uint32; +typedef std::uint16_t uint16; +typedef std::uint8_t uint8; #define FILE_FORMAT_VERSION 18 diff --git a/sql/base/mangos.sql b/sql/base/mangos.sql index 0cf2836d4..e67794a1a 100644 --- a/sql/base/mangos.sql +++ b/sql/base/mangos.sql @@ -2670,7 +2670,7 @@ VALUES (565,0,65,0,25,0,'',0), (566,0,10,0,50,0,'',0), (568,0,68,70,10,0,'',1), - (572,0,10,0,50,0,''), + (572,0,10,0,50,0,'',1), (580,0,70,0,25,0,'',1), (585,0,65,0,5,0,'',0); /*!40000 ALTER TABLE `instance_template` ENABLE KEYS */; diff --git a/sql/playerbotai/playerbotai_characters_r6_update.sql b/sql/playerbotai/playerbotai_characters_r6_update.sql new file mode 100644 index 000000000..e1aff9572 --- /dev/null +++ b/sql/playerbotai/playerbotai_characters_r6_update.sql @@ -0,0 +1,2 @@ +ALTER TABLE `playerbot_saved_data` DROP COLUMN `bot_secondary_order`; +ALTER TABLE playerbot_saved_data CHANGE COLUMN `bot_primary_order` `combat_order` int(11); diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index 3562b9fdf..77f22c2ba 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -19,6 +19,7 @@ set(LIBRARY_NAME game) add_definitions(-DDT_POLYREF64) +add_definitions(-D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=0) # include external dependencies libs headers (warnings disabled) include_directories(SYSTEM diff --git a/src/game/Chat.cpp b/src/game/Chat.cpp index d331c5c49..06f7f2eb9 100644 --- a/src/game/Chat.cpp +++ b/src/game/Chat.cpp @@ -780,7 +780,7 @@ ChatCommand* ChatHandler::getCommandTable() { "repairitems", SEC_GAMEMASTER, true, &ChatHandler::HandleRepairitemsCommand, "", nullptr }, { "stable", SEC_ADMINISTRATOR, false, &ChatHandler::HandleStableCommand, "", nullptr }, { "waterwalk", SEC_GAMEMASTER, false, &ChatHandler::HandleWaterwalkCommand, "", nullptr }, - //Playerbot mod + // Playerbot mod { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", nullptr }, { "quit", SEC_CONSOLE, true, &ChatHandler::HandleQuitCommand, "", nullptr }, { "mmap", SEC_GAMEMASTER, false, nullptr, "", mmapCommandTable }, diff --git a/src/game/Group.cpp b/src/game/Group.cpp index f21dbcb58..89eb20fb3 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -314,7 +314,7 @@ bool Group::AddMember(ObjectGuid guid, const char* name) uint32 Group::RemoveMember(ObjectGuid guid, uint8 method) { - //Playerbot mod - if master leaves group, all bots leave group + // Playerbot mod - if master leaves group, all bots leave group { Player* const player = sObjectMgr.GetPlayer(guid); if (player && player->GetPlayerbotMgr()) diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 2bf6ae9a3..8b57ecb39 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -13625,7 +13625,7 @@ void Player::ItemAddedQuestCheck(uint32 entry, uint32 count) if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED; - SendQuestUpdateAddItem(qInfo, j, additemcount); + SendQuestUpdateAddItem(qInfo, j, curitemcount, additemcount); } if (CanCompleteQuest(questid)) CompleteQuest(questid); @@ -14087,24 +14087,33 @@ void Player::SendPushToPartyResponse(Player* pPlayer, uint32 msg) const } } -void Player::SendQuestUpdateAddItem(Quest const* pQuest, uint32 item_idx, uint32 count) const +void Player::SendQuestUpdateAddItem(Quest const* pQuest, uint32 item_idx, uint32 current, uint32 count) { + MANGOS_ASSERT(count < 256 && "Quest slot count store is limited to 8 bits 2^8 = 256 (0..255)"); + + // Update quest watcher and fire QUEST_WATCH_UPDATE DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_ADD_ITEM"); WorldPacket data(SMSG_QUESTUPDATE_ADD_ITEM, (4 + 4)); data << pQuest->ReqItemId[item_idx]; data << count; GetSession()->SendPacket(data); + + // Update player field and fire UNIT_QUEST_LOG_CHANGED for self + uint16 slot = FindQuestSlot(pQuest->GetQuestId()); + if (slot < MAX_QUEST_LOG_SIZE) + SetQuestSlotCounter(slot, uint8(item_idx), uint8(current + count)); } void Player::SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid, uint32 creatureOrGO_idx, uint32 count) { - MANGOS_ASSERT(count < 256 && "mob/GO count store in 8 bits 2^8 = 256 (0..256)"); + MANGOS_ASSERT(count < 256 && "Quest slot count store is limited to 8 bits 2^8 = 256 (0..255)"); int32 entry = pQuest->ReqCreatureOrGOId[ creatureOrGO_idx ]; if (entry < 0) // client expected gameobject template id in form (id|0x80000000) entry = (-entry) | 0x80000000; + // Update quest watcher and fire QUEST_WATCH_UPDATE WorldPacket data(SMSG_QUESTUPDATE_ADD_KILL, (4 * 4 + 8)); DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_ADD_KILL"); data << uint32(pQuest->GetQuestId()); @@ -14114,9 +14123,10 @@ void Player::SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid data << guid; GetSession()->SendPacket(data); - uint16 log_slot = FindQuestSlot(pQuest->GetQuestId()); - if (log_slot < MAX_QUEST_LOG_SIZE) - SetQuestSlotCounter(log_slot, creatureOrGO_idx, count); + // Update player field and fire UNIT_QUEST_LOG_CHANGED for self + uint16 slot = FindQuestSlot(pQuest->GetQuestId()); + if (slot < MAX_QUEST_LOG_SIZE) + SetQuestSlotCounter(slot, uint8(creatureOrGO_idx), uint8(count)); } void Player::SendQuestGiverStatusMultiple() const diff --git a/src/game/Player.h b/src/game/Player.h index 236be5b81..f4ab4800d 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -1327,7 +1327,7 @@ class MANGOS_DLL_SPEC Player : public Unit void SendCanTakeQuestResponse(uint32 msg) const; void SendQuestConfirmAccept(Quest const* pQuest, Player* pReceiver) const; void SendPushToPartyResponse(Player* pPlayer, uint32 msg) const; - void SendQuestUpdateAddItem(Quest const* pQuest, uint32 item_idx, uint32 count) const; + void SendQuestUpdateAddItem(Quest const* pQuest, uint32 item_idx, uint32 current, uint32 count); void SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid, uint32 creatureOrGO_idx, uint32 count); void SendQuestGiverStatusMultiple() const; @@ -1342,14 +1342,16 @@ class MANGOS_DLL_SPEC Player : public Unit void AddTimedQuest(uint32 quest_id) { m_timedquests.insert(quest_id); } void RemoveTimedQuest(uint32 quest_id) { m_timedquests.erase(quest_id); } + PlayerMails::reverse_iterator GetMailRBegin() { return m_mail.rbegin();} + PlayerMails::reverse_iterator GetMailREnd() { return m_mail.rend();} + void UpdateMail(); + // Playerbot mod void chompAndTrim(std::string& str); bool getNextQuestId(const std::string& pString, unsigned int& pStartPos, unsigned int& pId); void skill(std::list& m_spellsToLearn); bool requiredQuests(const char* pQuestIdString); - PlayerMails::reverse_iterator GetMailRBegin() { return m_mail.rbegin();} - PlayerMails::reverse_iterator GetMailREnd() { return m_mail.rend();} - void UpdateMail(); + uint32 GetSpec(); /*********************************************************/ /*** LOAD SYSTEM ***/ diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 2cffec241..a22faecbd 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -3477,13 +3477,14 @@ void Spell::finish(bool ok) { switch (ihit->missCondition) { - case SPELL_MISS_DEFLECT: + case SPELL_MISS_MISS: + case SPELL_MISS_DODGE: + if (m_spellInfo->powerType == POWER_RAGE) // For Warriors only refund on parry/deflect, for rogues on all 4 + break; case SPELL_MISS_PARRY: - { - if (m_caster->GetCharmerOrOwnerOrOwnGuid().IsPlayer()) - m_caster->ModifyPower(Powers(m_spellInfo->powerType), int32(m_powerCost * 0.8)); + case SPELL_MISS_DEFLECT: + m_caster->ModifyPower(Powers(m_spellInfo->powerType), int32(m_powerCost * 0.8)); break; - } } } } diff --git a/src/game/WorldSession.h b/src/game/WorldSession.h index 50ec5452a..ba7faa7f3 100644 --- a/src/game/WorldSession.h +++ b/src/game/WorldSession.h @@ -176,7 +176,7 @@ class MANGOS_DLL_SPEC WorldSession Player* GetPlayer() const { return _player; } char const* GetPlayerName() const; void SetSecurity(AccountTypes security) { _security = security; } - //playerbot mod: player connected without socket are bot + // Playerbot mod: player connected without socket are bot const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "bot"; } void SetPlayer(Player* plr) { _player = plr; } uint8 Expansion() const { return m_expansion; } diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 849767778..3d639c6fa 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -130,10 +130,10 @@ Player* PlayerbotAI::GetMaster() const bool PlayerbotAI::CanReachWithSpellAttack(Unit* target) { - bool inrange=false; - float dist = m_bot->GetCombatDistance(target, true); + bool inrange = false; + float dist = m_bot->GetCombatDistance(target, false); - for (SpellRanges::iterator itr = m_spellRangeMap.begin(); itr != m_spellRangeMap.end(); ++itr) + for (SpellRanges::iterator itr = m_spellRangeMap.begin(); itr != m_spellRangeMap.end(); ++itr) { uint32 spellId = itr->first; @@ -155,9 +155,9 @@ bool PlayerbotAI::CanReachWithSpellAttack(Unit* target) float maxrange = itr->second; - // DEBUG_LOG("[%s] spell (%s) : dist (%f) < maxrange (%f)",m_bot->GetName(),spellInfo->SpellName[0],dist,maxrange); + // DEBUG_LOG("[%s] spell (%s) : dist (%f) < maxrange (%f)", m_bot->GetName(), spellInfo->SpellName[0], dist, maxrange); - if ( dist < maxrange) + if (dist < maxrange) { inrange = true; break; @@ -172,14 +172,16 @@ bool PlayerbotAI::In_Reach(Unit* Target, uint32 spellId) return false; float range = 0; - float dist = m_bot->GetCombatDistance(Target, true); + float dist = m_bot->GetCombatDistance(Target, false); SpellRanges::iterator it; it = m_spellRangeMap.find(spellId); (it != m_spellRangeMap.end()) ? range = it->second : range = 0; - // DEBUG_LOG("spell (%u) : range (%f)",spellId, range); - if ( dist > range) + // DEBUG_LOG("spell (%u) : range (%f)", spellId, range); + + if (dist > range) return false; + return true; } @@ -248,7 +250,6 @@ uint32 PlayerbotAI::getSpellId(const char* args, bool master) const return foundSpellId; } - uint32 PlayerbotAI::getPetSpellId(const char* args) const { if (!*args) @@ -312,7 +313,6 @@ uint32 PlayerbotAI::getPetSpellId(const char* args) const return foundSpellId; } - uint32 PlayerbotAI::initSpell(uint32 spellId) { // Check if bot knows this spell @@ -354,7 +354,6 @@ uint32 PlayerbotAI::initSpell(uint32 spellId) return (next == 0) ? spellId : next; } - // Pet spells do not form chains like player spells. // One of the options to initialize a spell is to use spell icon id uint32 PlayerbotAI::initPetSpell(uint32 spellIconId) @@ -459,9 +458,9 @@ void PlayerbotAI::SendNotEquipList(Player& /*player*/) ChatHandler ch(GetMaster()); const std::string descr[] = { "head", "neck", "shoulders", "body", "chest", - "waist", "legs", "feet", "wrists", "hands", "finger1", "finger2", - "trinket1", "trinket2", "back", "mainhand", "offhand", "ranged", - "tabard" }; + "waist", "legs", "feet", "wrists", "hands", "finger1", "finger2", + "trinket1", "trinket2", "back", "mainhand", "offhand", "ranged", + "tabard" }; // now send client all items that can be equipped by slot for (uint8 equipSlot = 0; equipSlot < 19; ++equipSlot) @@ -1234,80 +1233,79 @@ bool PlayerbotAI::IsItemUseful(uint32 itemid) switch (pProto->Class) { - case ITEM_CLASS_WEAPON: - if (pProto->SubClass >= MAX_ITEM_SUBCLASS_WEAPON) - return false; - else - return m_bot->HasSkill(item_weapon_skills[pProto->SubClass]); - break; - case ITEM_CLASS_ARMOR: - if (pProto->SubClass >= MAX_ITEM_SUBCLASS_ARMOR) - return false; - else - return (m_bot->HasSkill(item_armor_skills[pProto->SubClass]) && !m_bot->HasSkill(item_armor_skills[pProto->SubClass + 1])); - break; - case ITEM_CLASS_QUEST: - if (!HasCollectFlag(COLLECT_FLAG_QUEST)) - break; - case ITEM_CLASS_KEY: - return true; - case ITEM_CLASS_GEM: - if (m_bot->HasSkill(SKILL_BLACKSMITHING) || - m_bot->HasSkill(SKILL_ENGINEERING) || - m_bot->HasSkill(SKILL_JEWELCRAFTING)) - return true; - break; - case ITEM_CLASS_TRADE_GOODS: - if (!HasCollectFlag(COLLECT_FLAG_PROFESSION)) - break; - - switch (pProto->SubClass) - { - case ITEM_SUBCLASS_PARTS: - case ITEM_SUBCLASS_EXPLOSIVES: - case ITEM_SUBCLASS_DEVICES: - if (m_bot->HasSkill(SKILL_ENGINEERING)) - return true; - break; - case ITEM_SUBCLASS_JEWELCRAFTING: - if (m_bot->HasSkill(SKILL_JEWELCRAFTING)) - return true; - break; - case ITEM_SUBCLASS_CLOTH: - if (m_bot->HasSkill(SKILL_TAILORING)) - return true; + case ITEM_CLASS_WEAPON: + if (pProto->SubClass >= MAX_ITEM_SUBCLASS_WEAPON) + return false; + else + return m_bot->HasSkill(item_weapon_skills[pProto->SubClass]); break; - case ITEM_SUBCLASS_LEATHER: - if (m_bot->HasSkill(SKILL_LEATHERWORKING)) - return true; + case ITEM_CLASS_ARMOR: + if (pProto->SubClass >= MAX_ITEM_SUBCLASS_ARMOR) + return false; + else + return (m_bot->HasSkill(item_armor_skills[pProto->SubClass]) && !m_bot->HasSkill(item_armor_skills[pProto->SubClass + 1])); break; - case ITEM_SUBCLASS_METAL_STONE: + case ITEM_CLASS_QUEST: + if (!HasCollectFlag(COLLECT_FLAG_QUEST)) + break; + case ITEM_CLASS_KEY: + return true; + case ITEM_CLASS_GEM: if (m_bot->HasSkill(SKILL_BLACKSMITHING) || m_bot->HasSkill(SKILL_ENGINEERING) || - m_bot->HasSkill(SKILL_MINING)) + m_bot->HasSkill(SKILL_JEWELCRAFTING)) return true; break; - case ITEM_SUBCLASS_MEAT: - if (m_bot->HasSkill(SKILL_COOKING)) - return true; - break; - case ITEM_SUBCLASS_HERB: - if (m_bot->HasSkill(SKILL_HERBALISM) || - m_bot->HasSkill(SKILL_ALCHEMY)) - return true; - break; - case ITEM_SUBCLASS_ELEMENTAL: - return true; // pretty much every profession uses these a bit - case ITEM_SUBCLASS_ENCHANTING: - if (m_bot->HasSkill(SKILL_ENCHANTING)) - return true; - break; - default: + case ITEM_CLASS_TRADE_GOODS: + if (!HasCollectFlag(COLLECT_FLAG_PROFESSION)) + break; + + switch (pProto->SubClass) + { + case ITEM_SUBCLASS_PARTS: + case ITEM_SUBCLASS_EXPLOSIVES: + case ITEM_SUBCLASS_DEVICES: + if (m_bot->HasSkill(SKILL_ENGINEERING)) + return true; + break; + case ITEM_SUBCLASS_JEWELCRAFTING: + if (m_bot->HasSkill(SKILL_JEWELCRAFTING)) + return true; + break; + case ITEM_SUBCLASS_CLOTH: + if (m_bot->HasSkill(SKILL_TAILORING)) + return true; + break; + case ITEM_SUBCLASS_LEATHER: + if (m_bot->HasSkill(SKILL_LEATHERWORKING)) + return true; + break; + case ITEM_SUBCLASS_METAL_STONE: + if (m_bot->HasSkill(SKILL_BLACKSMITHING) || + m_bot->HasSkill(SKILL_ENGINEERING) || + m_bot->HasSkill(SKILL_MINING)) + return true; + break; + case ITEM_SUBCLASS_MEAT: + if (m_bot->HasSkill(SKILL_COOKING)) + return true; + break; + case ITEM_SUBCLASS_HERB: + if (m_bot->HasSkill(SKILL_HERBALISM) || + m_bot->HasSkill(SKILL_ALCHEMY)) + return true; + break; + case ITEM_SUBCLASS_ELEMENTAL: + return true; // pretty much every profession uses these a bit + case ITEM_SUBCLASS_ENCHANTING: + if (m_bot->HasSkill(SKILL_ENCHANTING)) + return true; + break; + default: + break; + } break; - } - break; - case ITEM_CLASS_RECIPE: - { + case ITEM_CLASS_RECIPE: if (!HasCollectFlag(COLLECT_FLAG_PROFESSION)) break; @@ -1317,52 +1315,51 @@ bool PlayerbotAI::IsItemUseful(uint32 itemid) switch (pProto->SubClass) { - case ITEM_SUBCLASS_LEATHERWORKING_PATTERN: - if (m_bot->HasSkill(SKILL_LEATHERWORKING)) - return true; - break; - case ITEM_SUBCLASS_TAILORING_PATTERN: - if (m_bot->HasSkill(SKILL_TAILORING)) - return true; - break; - case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC: - if (m_bot->HasSkill(SKILL_ENGINEERING)) - return true; - break; - case ITEM_SUBCLASS_BLACKSMITHING: - if (m_bot->HasSkill(SKILL_BLACKSMITHING)) - return true; - break; - case ITEM_SUBCLASS_COOKING_RECIPE: - if (m_bot->HasSkill(SKILL_COOKING)) - return true; - break; - case ITEM_SUBCLASS_ALCHEMY_RECIPE: - if (m_bot->HasSkill(SKILL_ALCHEMY)) - return true; - break; - case ITEM_SUBCLASS_FIRST_AID_MANUAL: - if (m_bot->HasSkill(SKILL_FIRST_AID)) - return true; - break; - case ITEM_SUBCLASS_ENCHANTING_FORMULA: - if (m_bot->HasSkill(SKILL_ENCHANTING)) - return true; - break; - case ITEM_SUBCLASS_FISHING_MANUAL: - if (m_bot->HasSkill(SKILL_FISHING)) - return true; - break; - case ITEM_SUBCLASS_JEWELCRAFTING_RECIPE: - if (m_bot->HasSkill(SKILL_JEWELCRAFTING)) - return true; - break; - default: - break; + case ITEM_SUBCLASS_LEATHERWORKING_PATTERN: + if (m_bot->HasSkill(SKILL_LEATHERWORKING)) + return true; + break; + case ITEM_SUBCLASS_TAILORING_PATTERN: + if (m_bot->HasSkill(SKILL_TAILORING)) + return true; + break; + case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC: + if (m_bot->HasSkill(SKILL_ENGINEERING)) + return true; + break; + case ITEM_SUBCLASS_BLACKSMITHING: + if (m_bot->HasSkill(SKILL_BLACKSMITHING)) + return true; + break; + case ITEM_SUBCLASS_COOKING_RECIPE: + if (m_bot->HasSkill(SKILL_COOKING)) + return true; + break; + case ITEM_SUBCLASS_ALCHEMY_RECIPE: + if (m_bot->HasSkill(SKILL_ALCHEMY)) + return true; + break; + case ITEM_SUBCLASS_FIRST_AID_MANUAL: + if (m_bot->HasSkill(SKILL_FIRST_AID)) + return true; + break; + case ITEM_SUBCLASS_ENCHANTING_FORMULA: + if (m_bot->HasSkill(SKILL_ENCHANTING)) + return true; + break; + case ITEM_SUBCLASS_FISHING_MANUAL: + if (m_bot->HasSkill(SKILL_FISHING)) + return true; + break; + case ITEM_SUBCLASS_JEWELCRAFTING_RECIPE: + if (m_bot->HasSkill(SKILL_JEWELCRAFTING)) + return true; + break; + default: + break; } - } - default: - break; + default: + break; } return false; } @@ -1393,7 +1390,12 @@ void PlayerbotAI::ReloadAI() break; case CLASS_SHAMAN: if (m_classAI) delete m_classAI; - m_combatStyle = COMBAT_MELEE; + if (m_bot->GetSpec() == SHAMAN_SPEC_ENHANCEMENT) + { + m_combatStyle = COMBAT_MELEE; + } + else + m_combatStyle = COMBAT_RANGED; m_classAI = (PlayerbotClassAI *) new PlayerbotShamanAI(GetMaster(), m_bot, this); break; case CLASS_PALADIN: @@ -1408,7 +1410,12 @@ void PlayerbotAI::ReloadAI() break; case CLASS_DRUID: if (m_classAI) delete m_classAI; - m_combatStyle = COMBAT_MELEE; + if (m_bot->GetSpec() == DRUID_SPEC_FERAL) + { + m_combatStyle = COMBAT_MELEE; + } + else + m_combatStyle = COMBAT_RANGED; m_classAI = (PlayerbotClassAI *) new PlayerbotDruidAI(GetMaster(), m_bot, this); break; case CLASS_HUNTER: @@ -1439,12 +1446,23 @@ void PlayerbotAI::SendOrders(Player& /*player*/) out << "I HEAL and WON'T DISPEL"; else if (m_combatOrder & ORDERS_PASSIVE) out << "I'M PASSIVE"; - if ((m_combatOrder & ORDERS_PRIMARY) && (m_combatOrder & ORDERS_SECONDARY)) + if ((m_combatOrder & ORDERS_PRIMARY) && (m_combatOrder & (ORDERS_PROTECT | ORDERS_RESIST))) + { out << " and "; - if (m_combatOrder & ORDERS_PROTECT) - out << "I PROTECT " << (m_targetProtect ? m_targetProtect->GetName() : "unknown"); - else if (m_combatOrder & ORDERS_RESIST) - out << "I RESIST " << m_resistType; + if (m_combatOrder & ORDERS_PROTECT) + out << "I PROTECT " << (m_targetProtect ? m_targetProtect->GetName() : "unknown"); + if (m_combatOrder & ORDERS_RESIST) + { + if (m_combatOrder & ORDERS_RESIST_FIRE) + out << "I RESIST FIRE"; + if (m_combatOrder & ORDERS_RESIST_NATURE) + out << "I RESIST NATURE"; + if (m_combatOrder & ORDERS_RESIST_FROST) + out << "I RESIST FROST"; + if (m_combatOrder & ORDERS_RESIST_SHADOW) + out << "I RESIST SHADOW"; + } + } out << "."; if (m_mgr->m_confDebugWhisper) @@ -1477,7 +1495,8 @@ void PlayerbotAI::SendOrders(Player& /*player*/) } TellMaster(out.str().c_str()); - TellMaster("My combat delay is '%u'", m_DelayAttack); + if (m_DelayAttack) + TellMaster("My combat delay is '%u'", m_DelayAttack); } // handle outgoing packets the server would send to the client @@ -1485,12 +1504,12 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) { switch (packet.GetOpcode()) { - case SMSG_DUEL_WINNER: + case SMSG_DUEL_WINNER: { m_bot->HandleEmoteCommand(EMOTE_ONESHOT_APPLAUD); return; } - case SMSG_DUEL_COMPLETE: + case SMSG_DUEL_COMPLETE: { SetIgnoreUpdateTime(4); m_ScenarioType = SCENARIO_PVE; @@ -1498,12 +1517,12 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) m_bot->GetMotionMaster()->Clear(true); return; } - case SMSG_DUEL_OUTOFBOUNDS: + case SMSG_DUEL_OUTOFBOUNDS: { m_bot->HandleEmoteCommand(EMOTE_ONESHOT_CHICKEN); return; } - case SMSG_DUEL_REQUESTED: + case SMSG_DUEL_REQUESTED: { SetIgnoreUpdateTime(0); WorldPacket p(packet); @@ -1534,7 +1553,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_PET_TAME_FAILURE: + case SMSG_PET_TAME_FAILURE: { // DEBUG_LOG("SMSG_PET_TAME_FAILURE"); WorldPacket p(packet); @@ -1583,7 +1602,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_BUY_FAILED: + case SMSG_BUY_FAILED: { WorldPacket p(packet); // 8+4+4+1 ObjectGuid vendorguid; @@ -1596,35 +1615,35 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) switch(msg) { - case BUY_ERR_CANT_FIND_ITEM: - break; - case BUY_ERR_ITEM_ALREADY_SOLD: - break; - case BUY_ERR_NOT_ENOUGHT_MONEY: - { - Announce(CANT_AFFORD); + case BUY_ERR_CANT_FIND_ITEM: break; - } - case BUY_ERR_SELLER_DONT_LIKE_YOU: - break; - case BUY_ERR_DISTANCE_TOO_FAR: - break; - case BUY_ERR_ITEM_SOLD_OUT: - break; - case BUY_ERR_CANT_CARRY_MORE: - { - Announce(INVENTORY_FULL); + case BUY_ERR_ITEM_ALREADY_SOLD: + break; + case BUY_ERR_NOT_ENOUGHT_MONEY: + { + Announce(CANT_AFFORD); + break; + } + case BUY_ERR_SELLER_DONT_LIKE_YOU: + break; + case BUY_ERR_DISTANCE_TOO_FAR: + break; + case BUY_ERR_ITEM_SOLD_OUT: + break; + case BUY_ERR_CANT_CARRY_MORE: + { + Announce(INVENTORY_FULL); + break; + } + case BUY_ERR_RANK_REQUIRE: + break; + case BUY_ERR_REPUTATION_REQUIRE: break; - } - case BUY_ERR_RANK_REQUIRE: - break; - case BUY_ERR_REPUTATION_REQUIRE: - break; } return; } - case SMSG_AUCTION_COMMAND_RESULT: + case SMSG_AUCTION_COMMAND_RESULT: { uint32 auctionId, Action, ErrorCode; std::string action[3] = {"Creating", "Cancelling", "Bidding"}; @@ -1638,7 +1657,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) switch (ErrorCode) { - case AUCTION_OK: + case AUCTION_OK: { out << "|cff1eff00|h" << action[Action] << " was successful|h|r"; break; @@ -1653,17 +1672,17 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) out << "|cffff0000|hWhile" << action[Action] << ", an internal error occured|h|r"; break; } - case AUCTION_ERR_NOT_ENOUGH_MONEY: + case AUCTION_ERR_NOT_ENOUGH_MONEY: { out << "|cffff0000|hWhile " << action[Action] << ", I didn't have enough money|h|r"; break; } - case AUCTION_ERR_ITEM_NOT_FOUND: + case AUCTION_ERR_ITEM_NOT_FOUND: { out << "|cffff0000|hItem was not found!|h|r"; break; } - case AUCTION_ERR_BID_OWN: + case AUCTION_ERR_BID_OWN: { out << "|cffff0000|hI cannot bid on my own auctions!|h|r"; break; @@ -1675,7 +1694,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_INVENTORY_CHANGE_FAILURE: + case SMSG_INVENTORY_CHANGE_FAILURE: { WorldPacket p(packet); uint8 err; @@ -1685,61 +1704,61 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) { switch (err) { - case EQUIP_ERR_CANT_CARRY_MORE_OF_THIS: - TellMaster("I can't carry anymore of those."); - return; - case EQUIP_ERR_MISSING_REAGENT: - TellMaster("I'm missing some reagents for that."); - return; - case EQUIP_ERR_ITEM_LOCKED: - TellMaster("That item is locked."); - return; - case EQUIP_ERR_ALREADY_LOOTED: - TellMaster("That is already looted."); - return; - case EQUIP_ERR_INVENTORY_FULL: - { - if (m_lootCurrent.IsGameObject()) - if (GameObject* go = m_bot->GetMap()->GetGameObject(m_lootCurrent)) - m_collectObjects.remove(go->GetEntry()); + case EQUIP_ERR_CANT_CARRY_MORE_OF_THIS: + TellMaster("I can't carry anymore of those."); + return; + case EQUIP_ERR_MISSING_REAGENT: + TellMaster("I'm missing some reagents for that."); + return; + case EQUIP_ERR_ITEM_LOCKED: + TellMaster("That item is locked."); + return; + case EQUIP_ERR_ALREADY_LOOTED: + TellMaster("That is already looted."); + return; + case EQUIP_ERR_INVENTORY_FULL: + { + if (m_lootCurrent.IsGameObject()) + if (GameObject* go = m_bot->GetMap()->GetGameObject(m_lootCurrent)) + m_collectObjects.remove(go->GetEntry()); - if (m_inventory_full) - return; + if (m_inventory_full) + return; - TellMaster("My inventory is full."); - m_inventory_full = true; + TellMaster("My inventory is full."); + m_inventory_full = true; + return; + } + case EQUIP_ERR_NOT_IN_COMBAT: + TellMaster("I can't use that in combat."); + return; + case EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW: + TellMaster("I can't get that now."); + return; + case EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE: + TellMaster("I can only have one of those equipped."); + return; + case EQUIP_ERR_BANK_FULL: + TellMaster("My bank is full."); + return; + case EQUIP_ERR_ITEM_NOT_FOUND: + TellMaster("I can't find the item."); + return; + case EQUIP_ERR_TOO_FAR_AWAY_FROM_BANK: + TellMaster("I'm too far from the bank."); + return; + case EQUIP_ERR_NONE: + TellMaster("I can't use it on that"); + return; + default: + TellMaster("I can't use that."); + DEBUG_LOG ("[PlayerbotAI]: HandleBotOutgoingPacket - SMSG_INVENTORY_CHANGE_FAILURE: %u", err); return; - } - case EQUIP_ERR_NOT_IN_COMBAT: - TellMaster("I can't use that in combat."); - return; - case EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW: - TellMaster("I can't get that now."); - return; - case EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE: - TellMaster("I can only have one of those equipped."); - return; - case EQUIP_ERR_BANK_FULL: - TellMaster("My bank is full."); - return; - case EQUIP_ERR_ITEM_NOT_FOUND: - TellMaster("I can't find the item."); - return; - case EQUIP_ERR_TOO_FAR_AWAY_FROM_BANK: - TellMaster("I'm too far from the bank."); - return; - case EQUIP_ERR_NONE: - TellMaster("I can't use it on that"); - return; - default: - TellMaster("I can't use that."); - DEBUG_LOG ("[PlayerbotAI]: HandleBotOutgoingPacket - SMSG_INVENTORY_CHANGE_FAILURE: %u", err); - return; } } } - case SMSG_CAST_FAILED: + case SMSG_CAST_FAILED: { WorldPacket p(packet); uint8 castCount; @@ -1842,7 +1861,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_SPELL_FAILURE: + case SMSG_SPELL_FAILURE: { WorldPacket p(packet); uint8 castCount; @@ -1864,7 +1883,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // if a change in speed was detected for the master // make sure we have the same mount status - case SMSG_FORCE_RUN_SPEED_CHANGE: + case SMSG_FORCE_RUN_SPEED_CHANGE: { WorldPacket p(packet); ObjectGuid guid; @@ -1923,7 +1942,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } // handle flying acknowledgement - case SMSG_MOVE_SET_CAN_FLY: + case SMSG_MOVE_SET_CAN_FLY: { WorldPacket p(packet); ObjectGuid guid; @@ -1937,7 +1956,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } // handle dismount flying acknowledgement - case SMSG_MOVE_UNSET_CAN_FLY: + case SMSG_MOVE_UNSET_CAN_FLY: { WorldPacket p(packet); ObjectGuid guid; @@ -1952,7 +1971,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // If the leader role was given to the bot automatically give it to the master // if the master is in the group, otherwise leave group - case SMSG_GROUP_SET_LEADER: + case SMSG_GROUP_SET_LEADER: { WorldPacket p(packet); std::string name; @@ -1975,7 +1994,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } // If the master leaves the group, then the bot leaves too - case SMSG_PARTY_COMMAND_RESULT: + case SMSG_PARTY_COMMAND_RESULT: { WorldPacket p(packet); uint32 operation; @@ -1992,7 +2011,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } // Handle Group invites (auto accept if master is in group, otherwise decline & send message - case SMSG_GROUP_INVITE: + case SMSG_GROUP_INVITE: { if (m_bot->GetGroupInvite()) { @@ -2039,7 +2058,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // Handle when another player opens the trade window with the bot // also sends list of tradable items bot can trade if bot is allowed to obey commands from - case SMSG_TRADE_STATUS: + case SMSG_TRADE_STATUS: { if (m_bot->GetTrader() == nullptr) break; @@ -2163,9 +2182,8 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_SPELL_START: + case SMSG_SPELL_START: { - WorldPacket p(packet); ObjectGuid castItemGuid; @@ -2197,7 +2215,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_SPELL_GO: + case SMSG_SPELL_GO: { WorldPacket p(packet); @@ -2217,7 +2235,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } // if someone tries to resurrect, then accept - case SMSG_RESURRECT_REQUEST: + case SMSG_RESURRECT_REQUEST: { if (!m_bot->isAlive()) { @@ -2237,7 +2255,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_LOOT_RESPONSE: + case SMSG_LOOT_RESPONSE: { WorldPacket p(packet); // (8+1+4+1+1+4+4+4+4+4+1) ObjectGuid guid; @@ -2256,20 +2274,28 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // Pickup money if (loot->GetGoldAmount()) - loot->SendGold(m_bot); + loot->SendGold(m_bot); // Pick up the items // Get the list of items first and iterate it LootItemList lootList; loot->GetLootItemsListFor(m_bot, lootList); + bool lootableItemsPresent = false; for (LootItemList::const_iterator lootItr = lootList.begin(); lootItr != lootList.end(); ++lootItr) { - LootItem* lootItem = *lootItr; + LootItem* lootItem = *lootItr; + + // sLog.outError("PlayerbotAI: attempting to loot item %u with slot type %d", lootItem->itemId, lootItem->GetSlotTypeForSharedLoot(m_bot, loot)); // Skip non lootable items - if (lootItem->GetSlotTypeForSharedLoot(m_bot, loot) != LOOT_SLOT_NORMAL) + if (lootItem->GetSlotTypeForSharedLoot(m_bot, loot) != LOOT_SLOT_NORMAL && lootItem->GetSlotTypeForSharedLoot(m_bot, loot) != LOOT_SLOT_OWNER) + { + // sLog.outError("PlayerbotAI: skipping non lootable item"); continue; + } + + lootableItemsPresent = true; // If bot is skinning or has collect all orders: autostore all items // else bot has order to only loot quest or useful items @@ -2294,7 +2320,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) ObjectGuid const& lguid = loot->GetLootGuid(); - // Check that bot has either equiped or received the item + // Check that bot has either equipped or received the item // then change item's loot state if (result == EQUIP_ERR_OK && lguid.IsItem()) { @@ -2304,13 +2330,21 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } } + if (!lootableItemsPresent) { + // if previous is current, clear + if (m_lootPrev == m_lootCurrent) + m_lootPrev = ObjectGuid(); + + // clear current target + m_lootCurrent = ObjectGuid(); + } + // release loot loot->Release(m_bot); - return; } - case SMSG_LOOT_RELEASE_RESPONSE: + case SMSG_LOOT_RELEASE_RESPONSE: { WorldPacket p(packet); ObjectGuid guid; @@ -2319,16 +2353,21 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) if (guid == m_lootCurrent) { + // sLog.outError("PlayerbotAI: SMSG_LOOT_RELEASE_RESPONSE we have an active guid to loot"); Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); if (c && c->GetCreatureInfo()->SkinningLootId && !c->GetLootStatus() != CREATURE_LOOT_STATUS_LOOTED) { + // sLog.outError("PlayerbotAI: SMSG_LOOT_RELEASE_RESPONSE creature is skinnable and has not been looted"); + uint32 reqSkill = c->GetCreatureInfo()->GetRequiredLootSkill(); // check if it is a leather skin and if it is to be collected (could be ore or herb) if (m_bot->HasSkill(reqSkill) && ((reqSkill != SKILL_SKINNING) || (HasCollectFlag(COLLECT_FLAG_SKIN) && reqSkill == SKILL_SKINNING))) { + // sLog.outError("PlayerbotAI: SMSG_LOOT_RELEASE_RESPONSE attempting to skin creature"); + // calculate skill requirement uint32 skillValue = m_bot->GetPureSkillValue(reqSkill); uint32 targetLevel = c->getLevel(); @@ -2358,10 +2397,11 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) SetIgnoreUpdateTime(0); } + // sLog.outError("PlayerbotAI: SMSG_LOOT_RELEASE_RESPONSE returning"); return; } - case SMSG_LOOT_ROLL_WON: + case SMSG_LOOT_ROLL_WON: { WorldPacket p(packet); // (8+4+4+4+4+8+1+1) ObjectGuid guid; @@ -2381,7 +2421,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) SetState(BOTSTATE_DELAYED); -/* ItemPrototype const *pProto = ObjectMgr::GetItemPrototype(itemid); + /* ItemPrototype const *pProto = ObjectMgr::GetItemPrototype(itemid); if(pProto) { std::ostringstream out; @@ -2394,14 +2434,14 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_PARTYKILLLOG: + case SMSG_PARTYKILLLOG: { // reset AI delay so bots immediately respond to next combat target & or looting/skinning SetIgnoreUpdateTime(0); return; } - case SMSG_ITEM_PUSH_RESULT: + case SMSG_ITEM_PUSH_RESULT: { WorldPacket p(packet); // (8+4+4+4+1+4+4+4+4+4+4) ObjectGuid guid; @@ -2451,35 +2491,34 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) } /* uncomment this and your bots will tell you all their outgoing packet opcode names - case SMSG_MONSTER_MOVE: - case SMSG_UPDATE_WORLD_STATE: - case SMSG_COMPRESSED_UPDATE_OBJECT: - case MSG_MOVE_SET_FACING: - case MSG_MOVE_STOP: - case MSG_MOVE_HEARTBEAT: - case MSG_MOVE_STOP_STRAFE: - case MSG_MOVE_START_STRAFE_LEFT: - case SMSG_UPDATE_OBJECT: - case MSG_MOVE_START_FORWARD: - case MSG_MOVE_START_STRAFE_RIGHT: - case SMSG_DESTROY_OBJECT: - case MSG_MOVE_START_BACKWARD: - case SMSG_AURA_UPDATE_ALL: - case MSG_MOVE_FALL_LAND: - case MSG_MOVE_JUMP: - return; + case SMSG_MONSTER_MOVE: + case SMSG_UPDATE_WORLD_STATE: + case SMSG_COMPRESSED_UPDATE_OBJECT: + case MSG_MOVE_SET_FACING: + case MSG_MOVE_STOP: + case MSG_MOVE_HEARTBEAT: + case MSG_MOVE_STOP_STRAFE: + case MSG_MOVE_START_STRAFE_LEFT: + case SMSG_UPDATE_OBJECT: + case MSG_MOVE_START_FORWARD: + case MSG_MOVE_START_STRAFE_RIGHT: + case SMSG_DESTROY_OBJECT: + case MSG_MOVE_START_BACKWARD: + case SMSG_AURA_UPDATE_ALL: + case MSG_MOVE_FALL_LAND: + case MSG_MOVE_JUMP: + return;*/ default: { - const char* oc = LookupOpcodeName(packet.GetOpcode()); + /*const char* oc = LookupOpcodeName(packet.GetOpcode()); - std::ostringstream out; - out << "botout: " << oc; - sLog.outError(out.str().c_str()); + std::ostringstream out; + out << "botout: " << oc; + sLog.outError(out.str().c_str()); - //TellMaster(oc); + TellMaster(oc);*/ } - */ } } @@ -2723,7 +2762,7 @@ Item* PlayerbotAI::FindBandage() const if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) continue; - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) + if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_FOOD) return pItem; } } @@ -2742,15 +2781,15 @@ Item* PlayerbotAI::FindBandage() const if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) continue; - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) + if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_FOOD) return pItem; } } } return nullptr; } -//Find Poison ...Natsukawa -Item* PlayerbotAI::FindPoison() const + +Item* PlayerbotAI::FindConsumable(uint32 displayId) const { // list out items in main backpack for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) @@ -2763,7 +2802,7 @@ Item* PlayerbotAI::FindPoison() const if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) continue; - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6) + if ((pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_SUBCLASS_BANDAGE) && pItemProto->DisplayInfoID == displayId) return pItem; } } @@ -2782,7 +2821,7 @@ Item* PlayerbotAI::FindPoison() const if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) continue; - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6) + if ((pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_SUBCLASS_BANDAGE) && pItemProto->DisplayInfoID == displayId) return pItem; } } @@ -2790,44 +2829,135 @@ Item* PlayerbotAI::FindPoison() const return nullptr; } -Item* PlayerbotAI::FindConsumable(uint32 displayId) const +static const uint32 uPriorizedSharpStoneIds[6] = { - // list out items in main backpack - for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + ELEMENTAL_SHARPENING_DISPLAYID, DENSE_SHARPENING_DISPLAYID, SOLID_SHARPENING_DISPLAYID, + HEAVY_SHARPENING_DISPLAYID, COARSE_SHARPENING_DISPLAYID, ROUGH_SHARPENING_DISPLAYID +}; + +static const uint32 uPriorizedWeightStoneIds[5] = +{ + DENSE_WEIGHTSTONE_DISPLAYID, SOLID_WEIGHTSTONE_DISPLAYID, HEAVY_WEIGHTSTONE_DISPLAYID, + COARSE_WEIGHTSTONE_DISPLAYID, ROUGH_WEIGHTSTONE_DISPLAYID +}; + +/** + * FindStoneFor() + * return Item* Returns sharpening/weight stone item eligible to enchant a bot weapon + * + * params:weapon Item* the weapĂ´n the function should search and return a enchanting item for + * return nullptr if no relevant item is found in bot inventory, else return a sharpening or weight + * stone based on the weapon subclass + * + */ +Item* PlayerbotAI::FindStoneFor(Item* weapon) const +{ + Item* stone; + ItemPrototype const* pProto = weapon->GetProto(); + if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 + || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 + || pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)) { - Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); - if (pItem) + for (uint8 i = 0; i < countof(uPriorizedSharpStoneIds); ++i) { - const ItemPrototype* const pItemProto = pItem->GetProto(); + stone = FindConsumable(uPriorizedSharpStoneIds[i]); + if (stone) + return stone; + } + } + else if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)) + { + for (uint8 i = 0; i < countof(uPriorizedWeightStoneIds); ++i) + { + stone = FindConsumable(uPriorizedWeightStoneIds[i]); + if (stone) + return stone; + } + } - if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; + return nullptr; +} - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->DisplayInfoID == displayId) - return pItem; +static const uint32 uPriorizedManaPotionIds[8] = +{ + MAJOR_MANA_POTION, MAJOR_REJUVENATION_POTION, SUPERIOR_MANA_POTION, + GREATER_MANA_POTION, MANA_POTION, LESSER_MANA_POTION, + MINOR_MANA_POTION, MINOR_REJUVENATION_POTION +}; + +/** + * FindManaRegenItem() + * return Item* Returns items like runes or potion that can help the bot to instantly resplenish some of its mana + * + * return nullptr if no relevant item is found in bot inventory, else return a consumable item providing mana + * + */ +Item* PlayerbotAI::FindManaRegenItem() const +{ + Item* manaRegen; + // If bot has enough health, try to use a Demonic or Dark Rune + // to avoid triggering the health potion cooldown with a mana potion + if (m_bot->GetHealth() > 1500) + { + // First try a Demonic Rune as they are BoP + manaRegen = FindConsumable(DEMONIC_RUNE); + if (manaRegen) + return manaRegen; + else + { + manaRegen = FindConsumable(DARK_RUNE); + if (manaRegen) + return manaRegen; } } - // list out items in other removable backpacks - for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + // Else use mana potion (and knowingly trigger the health potion cooldown) + for (uint8 i = 0; i < countof(uPriorizedManaPotionIds); ++i) { - const Bag* const pBag = (Bag *) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); - if (pBag) - for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + manaRegen = FindConsumable(uPriorizedManaPotionIds[i]); + if (manaRegen) + return manaRegen; + } + + return nullptr; +} + +bool PlayerbotAI::FindAmmo() const +{ + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item* pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + if (pItemProto->Class == ITEM_CLASS_PROJECTILE && m_bot->CheckAmmoCompatibility(pItemProto)) + { + m_bot->SetAmmo(pItem->GetEntry()); + return true; + } + } + } + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) { - Item* const pItem = m_bot->GetItemByPos(bag, slot); + Item* pItem = m_bot->GetItemByPos(i, j); if (pItem) { const ItemPrototype* const pItemProto = pItem->GetProto(); - if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->DisplayInfoID == displayId) - return pItem; + if (pItemProto->Class == ITEM_CLASS_PROJECTILE && m_bot->CheckAmmoCompatibility(pItemProto)) + { + m_bot->SetAmmo(pItem->GetEntry()); + return true; + } } } + } } - return nullptr; + return false; } void PlayerbotAI::InterruptCurrentCastingSpell() @@ -2840,12 +2970,36 @@ void PlayerbotAI::InterruptCurrentCastingSpell() m_bot->GetSession()->QueuePacket(std::move(packet)); } -void PlayerbotAI::Feast() +// intelligently sets a reasonable combat order for this bot +// based on its class / level / etc +void PlayerbotAI::Attack(Unit* forcedTarget) { - // stand up if we are done feasting - if (!(m_bot->GetHealth() < m_bot->GetMaxHealth() || (m_bot->GetPowerType() == POWER_MANA && m_bot->GetPower(POWER_MANA) < m_bot->GetMaxPower(POWER_MANA)))) + // set combat state, and clear looting, etc... + if (m_botState != BOTSTATE_COMBAT) { - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + SetState(BOTSTATE_COMBAT); + m_lootCurrent = ObjectGuid(); + m_targetCombat = 0; + m_DelayAttackInit = CurrentTime(); // Combat started, new start time to check CombatDelay for. + } + + GetCombatTarget(forcedTarget); + + if (!m_targetCombat) + return; + + m_bot->Attack(m_targetCombat, true); + + // add thingToAttack to loot list + m_lootTargets.push_back(m_targetCombat->GetObjectGuid()); +} + +void PlayerbotAI::Feast() +{ + // stand up if we are done feasting + if (!(m_bot->GetHealth() < m_bot->GetMaxHealth() || (m_bot->GetPowerType() == POWER_MANA && m_bot->GetPower(POWER_MANA) < m_bot->GetMaxPower(POWER_MANA)))) + { + m_bot->SetStandState(UNIT_STAND_STATE_STAND); return; } @@ -2909,7 +3063,7 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) UpdateAttackerInfo(); // check for attackers on protected unit, and make it a forcedTarget if any - if (!forcedTarget && (m_combatOrder & ORDERS_PROTECT) && m_targetProtect != 0) + if (!forcedTarget && (m_combatOrder & ORDERS_PROTECT) && m_targetProtect) { Unit *newTarget = FindAttacker((ATTACKERINFOTYPE) (AIT_VICTIMNOTSELF | AIT_HIGHESTTHREAT), m_targetProtect); if (newTarget && newTarget != m_targetCombat) @@ -2924,13 +3078,17 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) { if (m_mgr->m_confDebugWhisper) TellMaster("Changing target to %s by force!", forcedTarget->GetName()); - m_targetType = (m_combatOrder == ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); + m_targetType = (m_combatOrder & ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); } // we already have a target and we are not forced to change it if (m_targetCombat && !forcedTarget) return; + // forced to change target to current target == null operation + if (forcedTarget && forcedTarget == m_targetCombat) + return; + // are we forced on a target? if (forcedTarget) { @@ -2938,19 +3096,19 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) m_targetChanged = true; } // do we have to assist someone? - if (!m_targetCombat && (m_combatOrder & ORDERS_ASSIST) && m_targetAssist != 0) + if (!m_targetCombat && (m_combatOrder & ORDERS_ASSIST) && m_targetAssist) { m_targetCombat = FindAttacker((ATTACKERINFOTYPE) (AIT_VICTIMNOTSELF | AIT_LOWESTTHREAT), m_targetAssist); if (m_mgr->m_confDebugWhisper && m_targetCombat) TellMaster("Attacking %s to assist %s", m_targetCombat->GetName(), m_targetAssist->GetName()); - m_targetType = (m_combatOrder == ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); + m_targetType = (m_combatOrder & ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); m_targetChanged = true; } // are there any other attackers? if (!m_targetCombat) { m_targetCombat = FindAttacker(); - m_targetType = (m_combatOrder == ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); + m_targetType = (m_combatOrder & ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); m_targetChanged = true; } // no attacker found anyway @@ -2966,12 +3124,12 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) // prevents bot from helping if (m_targetCombat->GetTypeId() == TYPEID_PLAYER && dynamic_cast (m_targetCombat)->duel) { - m_ignoreAIUpdatesUntilTime = time(0) + 6; + SetIgnoreUpdateTime(6); return; } m_bot->SetSelectionGuid((m_targetCombat->GetObjectGuid())); - m_ignoreAIUpdatesUntilTime = time(0) + 1; + SetIgnoreUpdateTime(1); if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) m_bot->SetStandState(UNIT_STAND_STATE_STAND); @@ -3000,14 +3158,17 @@ void PlayerbotAI::GetDuelTarget(Unit* forcedTarget) void PlayerbotAI::DoNextCombatManeuver() { + if (!GetClassAI()) + return; // error, error... + if (m_combatOrder == ORDERS_PASSIVE) return; // check for new targets if (m_ScenarioType == SCENARIO_PVP_DUEL) - GetDuelTarget(GetMaster()); + GetDuelTarget(GetMaster()); // TODO: Wow... wait... what? So not right. else - GetCombatTarget(); + Attack(); // check if we have a target - fixes crash reported by rrtn (kill hunter's pet bug) // if current target for attacks doesn't make sense anymore @@ -3022,24 +3183,51 @@ void PlayerbotAI::DoNextCombatManeuver() m_targetChanged = false; m_targetType = TARGET_NORMAL; SetQuestNeedCreatures(); - ClearCombatOrder(ORDERS_PULL); + if (GetCombatOrder() & ORDERS_TEMP) + { + if (GetCombatOrder() & ORDERS_TEMP_WAIT_TANKAGGRO) + TellMaster("I was still waiting for the tank to gain aggro, but that doesn't make sense anymore..."); + if (GetCombatOrder() & ORDERS_TEMP_WAIT_OOC) + TellMaster("I was still waiting OOC but that was way off..."); + ClearCombatOrder(ORDERS_TEMP); + } return; } - // do opening moves, if we changed target + // new target -> DoFirstCombatManeuver if (m_targetChanged) { - if (GetClassAI()) - m_targetChanged = GetClassAI()->DoFirstCombatManeuver(m_targetCombat); - else - m_targetChanged = false; + switch (GetClassAI()->DoFirstCombatManeuver(m_targetCombat)) + { + case RETURN_CONTINUE: // true needed for rogue stealth attack + break; + + case RETURN_NO_ACTION_ERROR: + TellMaster("FirstCombatManeuver: No action performed due to error. Heading onto NextCombatManeuver."); + case RETURN_FINISHED_FIRST_MOVES: // false default + case RETURN_NO_ACTION_UNKNOWN: + case RETURN_NO_ACTION_OK: + default: // assume no action -> no return + m_targetChanged = false; + } } // do normal combat movement DoCombatMovement(); - if (GetClassAI() && !m_targetChanged) - (GetClassAI())->DoNextCombatManeuver(m_targetCombat); + if (!m_targetChanged) + { + // if m_targetChanged = false + switch (GetClassAI()->DoNextCombatManeuver(m_targetCombat)) + { + case RETURN_NO_ACTION_UNKNOWN: + case RETURN_NO_ACTION_OK: + case RETURN_CONTINUE: + case RETURN_NO_ACTION_ERROR: + default: + return; + } + } } void PlayerbotAI::DoCombatMovement() @@ -3048,13 +3236,18 @@ void PlayerbotAI::DoCombatMovement() bool meleeReach = m_bot->CanReachWithMeleeAttack(m_targetCombat); - if (m_combatStyle == COMBAT_MELEE && !m_bot->hasUnitState(UNIT_STAT_CHASE) && ((m_movementOrder == MOVEMENT_STAY && meleeReach) || (m_movementOrder != MOVEMENT_STAY))) + if (m_combatStyle == COMBAT_MELEE + && !m_bot->hasUnitState(UNIT_STAT_CHASE) + && ((m_movementOrder == MOVEMENT_STAY && meleeReach) || m_movementOrder != MOVEMENT_STAY) + && GetClassAI()->GetWaitUntil() == 0 ) // Not waiting { // melee combat - chase target if in range or if we are not forced to stay m_bot->GetMotionMaster()->Clear(false); m_bot->GetMotionMaster()->MoveChase(m_targetCombat); } - else if (m_combatStyle == COMBAT_RANGED && m_movementOrder != MOVEMENT_STAY) + else if (m_combatStyle == COMBAT_RANGED + && m_movementOrder != MOVEMENT_STAY + && GetClassAI()->GetWaitUntil() == 0 ) // Not waiting { // ranged combat - just move within spell range if bot does not have heal orders if (!CanReachWithSpellAttack(m_targetCombat) && !IsHealer()) @@ -3103,7 +3296,7 @@ Player* PlayerbotAI::GetGroupTank() { Player* groupMember = sObjectMgr.GetPlayer(itr->guid); if (!groupMember || !groupMember->GetPlayerbotAI()) - return nullptr; + continue; if (groupMember->GetPlayerbotAI()->IsTank()) return groupMember; } @@ -3112,6 +3305,254 @@ Player* PlayerbotAI::GetGroupTank() return nullptr; } +void PlayerbotAI::SetGroupCombatOrder(CombatOrderType co) +{ + if (!m_bot) return; + + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI()) + continue; + groupMember->GetPlayerbotAI()->SetCombatOrder(co); + } + } + else + SetCombatOrder(co); +} + +void PlayerbotAI::ClearGroupCombatOrder(CombatOrderType co) +{ + if (!m_bot) return; + + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI()) + continue; + groupMember->GetPlayerbotAI()->ClearCombatOrder(co); + } + } + else + ClearCombatOrder(co); +} + +void PlayerbotAI::SetGroupIgnoreUpdateTime(uint8 t) +{ + if (!m_bot) return; + + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI()) + continue; + groupMember->GetPlayerbotAI()->SetIgnoreUpdateTime(t); + } + } + else + SetIgnoreUpdateTime(t); +} + +bool PlayerbotAI::GroupHoTOnTank() +{ + if (!m_bot) return false; + + bool bReturn = false; + + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI()) + continue; + if (groupMember->GetPlayerbotAI()->GetClassAI()->CastHoTOnTank()) + bReturn = true; + } + + if (bReturn) + { + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI()) + continue; + groupMember->GetPlayerbotAI()->SetIgnoreUpdateTime(1); + } + } + } + else // No group + { + if (GetClassAI()->CastHoTOnTank()) + { + SetIgnoreUpdateTime(1); + return true; + } + } + + return bReturn; +} + +bool PlayerbotAI::CanPull(Player &fromPlayer) +{ + if (!m_bot) return false; + if (!GetClassAI()) return false; + + if (!m_bot->GetGroup() || fromPlayer.GetGroup() != m_bot->GetGroup()) + { + SendWhisper("I can't pull - we're not in the same group.", fromPlayer); + return false; + } + + if (IsGroupInCombat()) // TODO: add raid support + { + SendWhisper("Unable to pull - the group is already in combat", fromPlayer); + return false; + } + + if ((GetCombatOrder() & ORDERS_TANK) == 0) + { + SendWhisper("I cannot pull as I do not have combat orders to tank.", fromPlayer); + return false; + } + + switch (m_bot->getClass()) + { + case CLASS_PALADIN: + if ( ((PlayerbotPaladinAI*)GetClassAI())->CanPull() == false) + { + SendWhisper("I cannot pull, I do not have the proper spell or it's not ready yet.", fromPlayer); + return false; + } + break; + + case CLASS_DRUID: + if ( ((PlayerbotDruidAI*)GetClassAI())->CanPull() == false) + { + SendWhisper("I cannot pull, I do not have the proper spell or it's not ready yet.", fromPlayer); + return false; + } + break; + + case CLASS_WARRIOR: + if ( ((PlayerbotWarriorAI*)GetClassAI())->CanPull() == false) + { + SendWhisper("I cannot pull, I do not have the proper weapon and/or ammo.", fromPlayer); + return false; + } + break; + + default: + SendWhisper("I cannot pull, I am not a tanking class.", fromPlayer); + return false; + } + + return true; +} + +// This function assumes a "CanPull()" call was preceded (not doing so will result in odd behavior) +bool PlayerbotAI::CastPull() +{ + if (!m_bot) return false; + if (!GetClassAI()) return false; + if (!GetCurrentTarget()) return false; + + if ((GetCombatOrder() & ORDERS_TANK) == 0) return false; + + switch (m_bot->getClass()) + { + case CLASS_PALADIN: + return ((PlayerbotPaladinAI*)GetClassAI())->Pull(); + + case CLASS_DRUID: + return ((PlayerbotDruidAI*)GetClassAI())->Pull(); + + case CLASS_WARRIOR: + return ((PlayerbotWarriorAI*)GetClassAI())->Pull(); + + default: + return false; + } + + return false; +} + +bool PlayerbotAI::GroupTankHoldsAggro() +{ + if (!m_bot) return false; + + // update attacker info now + UpdateAttackerInfo(); + + if (m_bot->GetGroup()) + { + Unit* newTarget = FindAttacker((ATTACKERINFOTYPE) (AIT_VICTIMNOTSELF), GetGroupTank()); + if (newTarget) + { + return false; + } + } + else + return false; // no group -> no group tank to hold aggro + + return true; +} + +// Wrapper for the UpdateAI cast subfunction +// Each bot class neutralize function will return a spellId +// depending on the creatureType of the target +bool PlayerbotAI::CastNeutralize() +{ + if (!m_bot) return false; + if (!GetClassAI()) return false; + if (!m_targetGuidCommand) return false; + + Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, m_targetGuidCommand); + if (!pTarget) return false; + + Creature * pCreature = (Creature*) pTarget; + if (!pCreature) return false; + + // Define the target's creature type, so the bot AI will now if + // it can neutralize it + uint8 creatureType = 0; + creatureType = pCreature->GetCreatureInfo()->CreatureType; + + switch (m_bot->getClass()) + { + case CLASS_DRUID: + m_spellIdCommand = ((PlayerbotDruidAI*)GetClassAI())->Neutralize(creatureType); + break; + case CLASS_PRIEST: + m_spellIdCommand = ((PlayerbotPriestAI*)GetClassAI())->Neutralize(creatureType); + break; + case CLASS_MAGE: + m_spellIdCommand = ((PlayerbotMageAI*)GetClassAI())->Neutralize(creatureType); + break; + case CLASS_WARLOCK: + m_spellIdCommand = ((PlayerbotWarlockAI*)GetClassAI())->Neutralize(creatureType); + break; + default: + return false; + } + + // A spellId was found + if (m_spellIdCommand != 0) + return true; + + return false; +} + void PlayerbotAI::SetQuestNeedCreatures() { // reset values first @@ -3243,7 +3684,7 @@ uint32 PlayerbotAI::GetFreeBagSpace() const void PlayerbotAI::DoFlight() { - // DEBUG_LOG("[PlayerbotAI]: DoFlight - %s : %s", m_bot->GetName(), m_taxiMaster.GetString().c_str()); + DEBUG_LOG("[PlayerbotAI]: DoFlight - %s : %s", m_bot->GetName(), m_taxiMaster.GetString().c_str()); Creature *npc = m_bot->GetNPCIfCanInteractWith(m_taxiMaster, UNIT_NPC_FLAG_FLIGHTMASTER); if (!npc) @@ -3304,7 +3745,7 @@ void PlayerbotAI::DoLoot() // not a lootable creature, clear it if (!c->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE) && (!c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE) || - (c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE) && !m_bot->HasSkill(skillId)))) + (c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE) && !m_bot->HasSkill(skillId)))) { m_lootCurrent = ObjectGuid(); // clear movement target, take next target on next update @@ -3333,12 +3774,14 @@ void PlayerbotAI::DoLoot() { if (c->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE) && !c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) { + // sLog.outError("PlayerbotAI: DoLoot() sending CMSG_LOOT and returning"); + // loot the creature std::unique_ptr packet(new WorldPacket(CMSG_LOOT, 8)); *packet << m_lootCurrent; m_bot->GetSession()->QueuePacket(std::move(packet)); return; // no further processing is needed - // m_lootCurrent is reset in SMSG_LOOT_RELEASE_RESPONSE after checking for skinloot + // m_lootCurrent is reset in SMSG_LOOT_RESPONSE/SMSG_LOOT_RELEASE_RESPONSE } else if (c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) // not all creature skins are leather, some are ore or herb @@ -3350,14 +3793,13 @@ void PlayerbotAI::DoLoot() reqSkillValue = targetLevel < 10 ? 0 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; } - // creatures cannot be unlocked or forced open - keyFailed = true; - forceFailed = true; + // creatures cannot be unlocked or forced open + keyFailed = true; + forceFailed = true; } if (go) // object { - // add this GO to our collection list if active and is chest/ore/herb if (go && HasCollectFlag(COLLECT_FLAG_NEAROBJECT) && go->GetGoType() == GAMEOBJECT_TYPE_CHEST) { @@ -3556,13 +3998,14 @@ void PlayerbotAI::DoLoot() if (keyFailed && skillFailed && forceFailed) { DEBUG_LOG ("[PlayerbotAI]: DoLoot attempts failed on [%s]", - go ? go->GetGOInfo()->name : c->GetCreatureInfo()->Name); + go ? go->GetGOInfo()->name : c->GetCreatureInfo()->Name); m_lootCurrent = ObjectGuid(); // remove this GO from our list using the same settings that it was added with earlier if (go && HasCollectFlag(COLLECT_FLAG_NEAROBJECT) && go->GetGoType() == GAMEOBJECT_TYPE_CHEST) m_collectObjects.remove(go->GetEntry()); } + // clear movement target, take next target on next update m_bot->GetMotionMaster()->Clear(false); m_bot->GetMotionMaster()->MoveIdle(); @@ -3612,20 +4055,20 @@ void PlayerbotAI::AcceptQuest(Quest const *qInfo, Player *pGiver) break; } - // build needed creatures if quest contains any - for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) - if (qInfo->ReqCreatureOrGOCount[i] > 0) - { - SetQuestNeedCreatures(); - break; - } + // build needed creatures if quest contains any + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + if (qInfo->ReqCreatureOrGOCount[i] > 0) + { + SetQuestNeedCreatures(); + break; + } - // Runsttren: did not add typeid switch from WorldSession::HandleQuestgiverAcceptQuestOpcode! - // I think it's not needed, cause typeid should be TYPEID_PLAYER - and this one is not handled - // there and there is no default case also. + // Runsttren: did not add typeid switch from WorldSession::HandleQuestgiverAcceptQuestOpcode! + // I think it's not needed, cause typeid should be TYPEID_PLAYER - and this one is not handled + // there and there is no default case also. - if (qInfo->GetSrcSpell() > 0) - m_bot->CastSpell(m_bot, qInfo->GetSrcSpell(), TRIGGERED_OLD_TRIGGERED); + if (qInfo->GetSrcSpell() > 0) + m_bot->CastSpell(m_bot, qInfo->GetSrcSpell(), TRIGGERED_OLD_TRIGGERED); } } @@ -3693,10 +4136,10 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) } else out << "|cffff0000Unable to turn quest in:|r " - << "|cff808080|Hquest:" << questID << ':' - << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r" - << " reward: |cffffffff|Hitem:" - << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + << "|cff808080|Hquest:" << questID << ':' + << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r" + << " reward: |cffffffff|Hitem:" + << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; } // else multiple rewards - let master pick @@ -3717,11 +4160,11 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) else if (status == QUEST_STATUS_INCOMPLETE) out << "|cffff0000Quest incomplete:|r " - << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; else if (status == QUEST_STATUS_AVAILABLE) out << "|cff00ff00Quest available:|r " - << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; if (!out.str().empty()) TellMaster(out.str()); @@ -3754,6 +4197,23 @@ bool PlayerbotAI::IsInCombat() return inCombat; } +bool PlayerbotAI::IsRegenerating() +{ + Unit::SpellAuraHolderMap& auras = m_bot->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator aura = auras.begin(); aura != auras.end(); aura++) + { + SpellEntry const* spell = aura->second->GetSpellProto(); + if (!spell) + continue; + if (spell->Category == 59 || spell->Category == 11){ + return true; + } + } + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + return false; +} + void PlayerbotAI::UpdateAttackersForTarget(Unit *victim) { HostileReference *ref = victim->getHostileRefManager().getFirst(); @@ -3926,7 +4386,7 @@ Unit* PlayerbotAI::FindAttacker(ATTACKERINFOTYPE ait, Unit *victim) if (!(ait & (AIT_LOWESTTHREAT | AIT_HIGHESTTHREAT))) { a = itr->second.attacker; - itr = m_attackerInfo.end(); + itr = m_attackerInfo.end(); // == break; } else { @@ -3977,7 +4437,7 @@ void PlayerbotAI::BotDataRestore() */ void PlayerbotAI::CombatOrderRestore() { - QueryResult* result = CharacterDatabase.PQuery("SELECT bot_primary_order,bot_secondary_order,primary_target,secondary_target,pname,sname,combat_delay,auto_follow FROM playerbot_saved_data WHERE guid = '%u'", m_bot->GetGUIDLow()); + QueryResult* result = CharacterDatabase.PQuery("SELECT combat_order,primary_target,secondary_target,pname,sname,combat_delay,auto_follow FROM playerbot_saved_data WHERE guid = '%u'", m_bot->GetGUIDLow()); if (!result) { @@ -3986,27 +4446,23 @@ void PlayerbotAI::CombatOrderRestore() TellMaster("I have no orders"); return; } - else - { - Field* fields = result->Fetch(); - gPrimOrder = fields[0].GetUInt8(); - gSecOrder = fields[1].GetUInt8(); - ObjectGuid PrimtargetGUID = ObjectGuid(fields[2].GetUInt64()); - ObjectGuid SectargetGUID = ObjectGuid(fields[3].GetUInt64()); - std::string pname = fields[4].GetString(); - std::string sname = fields[5].GetString(); - m_DelayAttack = fields[6].GetUInt8(); - m_FollowAutoGo = fields[7].GetUInt8(); - //if (gPrimtarget > 0) - gPrimtarget = ObjectAccessor::GetUnit(*m_bot->GetMap()->GetWorldObject(PrimtargetGUID), PrimtargetGUID); - //if (gSectarget > 0) - gSectarget = ObjectAccessor::GetUnit(*m_bot->GetMap()->GetWorldObject(SectargetGUID), SectargetGUID); - delete result; - } - Unit *gtarget = nullptr; - ObjectGuid NotargetGUID = m_bot->GetObjectGuid(); - gtarget = ObjectAccessor::GetUnit(*m_bot, NotargetGUID); - CombatOrderType co; + + Field* fields = result->Fetch(); + CombatOrderType combatOrders = (CombatOrderType)fields[0].GetUInt32(); + ObjectGuid PrimtargetGUID = ObjectGuid(fields[1].GetUInt64()); + ObjectGuid SectargetGUID = ObjectGuid(fields[2].GetUInt64()); + std::string pname = fields[3].GetString(); + std::string sname = fields[4].GetString(); + m_DelayAttack = fields[5].GetUInt8(); + m_FollowAutoGo = fields[6].GetUInt8(); + gPrimtarget = ObjectAccessor::GetUnit(*m_bot->GetMap()->GetWorldObject(PrimtargetGUID), PrimtargetGUID); + gSectarget = ObjectAccessor::GetUnit(*m_bot->GetMap()->GetWorldObject(SectargetGUID), SectargetGUID); + delete result; + + //Unit *target = nullptr; + //ObjectGuid NotargetGUID = m_bot->GetObjectGuid(); + //target = ObjectAccessor::GetUnit(*m_bot, NotargetGUID); + if (m_FollowAutoGo == FOLLOWAUTOGO_OFF) { DistOverRide = 0; //set initial adjustable follow settings @@ -4015,82 +4471,30 @@ void PlayerbotAI::CombatOrderRestore() gTempDist2 = 1.0f; SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); } - if (gPrimOrder > 0) - { - if (gPrimOrder == 1) co = ORDERS_TANK; - else if (gPrimOrder == 2) co = ORDERS_ASSIST; - else if (gPrimOrder == 3) co = ORDERS_HEAL; - SetCombatOrder(co, gPrimtarget); - } - if (gSecOrder > 0) - { - if (gSecOrder == 1) co = ORDERS_PROTECT; - else if (gSecOrder == 2) co = ORDERS_NODISPEL; - else if (gSecOrder == 3) { - co = ORDERS_RESIST; - m_resistType = SCHOOL_FROST; - gSecOrder = 3; - } - else if (gSecOrder == 4) { - co = ORDERS_RESIST; - m_resistType = SCHOOL_NATURE; - gSecOrder = 4; - } - else if (gSecOrder == 5) { - co = ORDERS_RESIST; - m_resistType = SCHOOL_FIRE; - gSecOrder = 5; - } - else if (gSecOrder == 6) { - co = ORDERS_RESIST; - m_resistType = SCHOOL_SHADOW; - gSecOrder = 6; - } - SetCombatOrder(co, gSectarget); - } - if (gPrimOrder == 0 && gSecOrder == 0) - SetCombatOrder(co, gtarget); + + if (combatOrders & ORDERS_PRIMARY) SetCombatOrder(combatOrders, gPrimtarget); + if (combatOrders & ORDERS_SECONDARY) SetCombatOrder(combatOrders, gSectarget); } void PlayerbotAI::SetCombatOrderByStr(std::string str, Unit *target) { + // TellMaster("SetCombatOrderByStr: order %s", str); + CombatOrderType co; - if (str == "tank") co = ORDERS_TANK; - else if (str == "assist") co = ORDERS_ASSIST; - else if (str == "heal") co = ORDERS_HEAL; - else if (str == "protect") co = ORDERS_PROTECT; - else if (str == "passive") co = ORDERS_PASSIVE; - else if (str == "pull") co = ORDERS_PULL; - else if (str == "nodispel") co = ORDERS_NODISPEL; - else if (str == "resistfrost") { - co = ORDERS_RESIST; - m_resistType = SCHOOL_FROST; - gSecOrder = 3; - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_secondary_order = '%u' WHERE guid = '%u'", gSecOrder, m_bot->GetGUIDLow()); - } - else if (str == "resistnature") { - co = ORDERS_RESIST; - m_resistType = SCHOOL_NATURE; - gSecOrder = 4; - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_secondary_order = '%u' WHERE guid = '%u'", gSecOrder, m_bot->GetGUIDLow()); - } - else if (str == "resistfire") { - co = ORDERS_RESIST; - m_resistType = SCHOOL_FIRE; - gSecOrder = 5; - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_secondary_order = '%u' WHERE guid = '%u'", gSecOrder, m_bot->GetGUIDLow()); - } - else if (str == "resistshadow") { - co = ORDERS_RESIST; - m_resistType = SCHOOL_SHADOW; - gSecOrder = 6; - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_secondary_order = '%u' WHERE guid = '%u'", gSecOrder, m_bot->GetGUIDLow()); - } - else - co = ORDERS_RESET; + if (str == "tank") co = ORDERS_TANK; + else if (str == "assist") co = ORDERS_ASSIST; + else if (str == "heal") co = ORDERS_HEAL; + else if (str == "protect") co = ORDERS_PROTECT; + else if (str == "passive") co = ORDERS_PASSIVE; + else if (str == "pull") co = ORDERS_TEMP_WAIT_TANKAGGRO; + else if (str == "nodispel") co = ORDERS_NODISPEL; + else if (str == "resistfrost") co = ORDERS_RESIST_FROST; + else if (str == "resistnature") co = ORDERS_RESIST_NATURE; + else if (str == "resistfire") co = ORDERS_RESIST_FIRE; + else if (str == "resistshadow") co = ORDERS_RESIST_SHADOW; + else co = ORDERS_RESET; + SetCombatOrder(co, target); - if (m_FollowAutoGo != FOLLOWAUTOGO_OFF) - m_FollowAutoGo = FOLLOWAUTOGO_INIT; } void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) @@ -4102,6 +4506,7 @@ void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) gTempTarget = target->GetGUIDLow(); gname = target->GetName(); } + // reset m_combatOrder after ORDERS_PASSIVE if (m_combatOrder == ORDERS_PASSIVE) { @@ -4112,91 +4517,60 @@ void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) switch (co) { - case ORDERS_TANK: // 1(1) - { - gPrimOrder = 1; - break; - } case ORDERS_ASSIST: // 2(10) - { - gPrimOrder = 2; - if (!target) { - TellMaster("The assist command requires a target."); - return; + if (!target) + { + TellMaster("The assist command requires a target."); + return; + } + else m_targetAssist = target; + break; } - else - m_targetAssist = target; - break; - } - case ORDERS_HEAL: // 4(100) - { - gPrimOrder = 3; - break; - } - case ORDERS_NODISPEL: // 8(1000) - { - gSecOrder = 2; - break; - } case ORDERS_PROTECT: // 10(10000) - { - gSecOrder = 1; - if (!target) { - TellMaster("The protect command requires a target."); - return; + if (!target) + { + TellMaster("The protect command requires a target."); + return; + } + else m_targetProtect = target; + break; } - else - m_targetProtect = target; - break; - } case ORDERS_PASSIVE: // 20(100000) - { - m_combatOrder = ORDERS_PASSIVE; - SendOrders(*GetMaster()); - return; - } - case ORDERS_PULL: // 80(10000000) - { - if (!target) { - TellMaster("The pull command requires a target."); + m_combatOrder = ORDERS_PASSIVE; + m_targetAssist = 0; + m_targetProtect = 0; return; } - else - m_targetProtect = target; - break; - } case ORDERS_RESET: // FFFF(11111111) - { - m_combatOrder = ORDERS_NONE; - m_targetAssist = 0; - m_targetProtect = 0; - m_resistType = SCHOOL_NONE; - TellMaster("Orders are cleaned!"); - gPrimOrder = 0; - gSecOrder = 0; - m_DelayAttackInit = CurrentTime(); - m_DelayAttack = 0; - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_primary_order = 0, bot_secondary_order = 0, primary_target = 0, secondary_target = 0, pname = '',sname = '', combat_delay = 0 WHERE guid = '%u'", m_bot->GetGUIDLow()); - return; - } + { + m_combatOrder = ORDERS_NONE; + m_targetAssist = 0; + m_targetProtect = 0; + m_DelayAttackInit = CurrentTime(); + m_DelayAttack = 0; + CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET combat_order = 0, primary_target = 0, secondary_target = 0, pname = '',sname = '', combat_delay = 0 WHERE guid = '%u'", m_bot->GetGUIDLow()); + TellMaster("Orders are cleaned!"); + return; + } + default: + break; } - // DEBUG_LOG("SetCombatOrder co = (%x) gPrimOrder = (%u) gSecOrder = (%u)", co, gPrimOrder, gSecOrder); - // Do your magic if ((co & ORDERS_PRIMARY)) { m_combatOrder = (CombatOrderType) (((uint32) m_combatOrder & (uint32) ORDERS_SECONDARY) | (uint32) co); - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_primary_order = '%u', primary_target = '%u', pname = '%s' WHERE guid = '%u'", (gPrimOrder & (uint8) ORDERS_PRIMARY), gTempTarget, gname.c_str(), m_bot->GetGUIDLow()); + if (target) + CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET combat_order = '%u', primary_target = '%u', pname = '%s' WHERE guid = '%u'", (m_combatOrder & ~ORDERS_TEMP), gTempTarget, gname.c_str(), m_bot->GetGUIDLow()); } else { - m_combatOrder = (CombatOrderType) ((uint32) m_combatOrder | (uint32) co); - if (co != ORDERS_PULL) - CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET bot_secondary_order = '%u', secondary_target = '%u', sname = '%s' WHERE guid = '%u'", (gSecOrder & (uint8) ORDERS_SECONDARY), gTempTarget, gname.c_str(), m_bot->GetGUIDLow()); + m_combatOrder = (CombatOrderType)((uint32)m_combatOrder | (uint32)co); + if (target) + CharacterDatabase.DirectPExecute("UPDATE playerbot_saved_data SET combat_order = '%u', secondary_target = '%u', sname = '%s' WHERE guid = '%u'", (m_combatOrder & ~ORDERS_TEMP), gTempTarget, gname.c_str(), m_bot->GetGUIDLow()); } } @@ -4217,13 +4591,6 @@ void PlayerbotAI::ClearCombatOrder(CombatOrderType co) SetCombatOrder(ORDERS_RESET); return; - case ORDERS_NODISPEL: - case ORDERS_PROTECT: - case ORDERS_RESIST: - ; - return; - - case ORDERS_PULL: default: return; } @@ -4240,6 +4607,7 @@ void PlayerbotAI::MovementReset() { // stop moving... MovementClear(); + if (m_movementOrder == MOVEMENT_FOLLOW) { if (!m_followTarget) @@ -4534,7 +4902,9 @@ void PlayerbotAI::UpdateAI(const uint32 /*p_time*/) } // resurrect now // DEBUG_LOG ("[PlayerbotAI]: UpdateAI - Reviving %s to corpse...", m_bot->GetName() ); - m_ignoreAIUpdatesUntilTime = time(0) + 6; + + SetIgnoreUpdateTime(6); + PlayerbotChatHandler ch(GetMaster()); if (!ch.revive(*m_bot)) { @@ -4627,7 +4997,11 @@ void PlayerbotAI::UpdateAI(const uint32 /*p_time*/) { Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, m_targetGuidCommand); if (pTarget) + { + // Face the target to avoid facing casting error + FaceTarget(pTarget); CastSpell(m_spellIdCommand, *pTarget); + }; m_spellIdCommand = 0; m_targetGuidCommand = ObjectGuid(); @@ -4689,10 +5063,10 @@ void PlayerbotAI::UpdateAI(const uint32 /*p_time*/) if (m_DelayAttackInit + m_DelayAttack > CurrentTime()) return SetIgnoreUpdateTime(1); // short bursts of delay - DoNextCombatManeuver(); + return DoNextCombatManeuver(); } else - SetIgnoreUpdateTime(0); // It's better to update AI more frequently during combat + return SetIgnoreUpdateTime(0); // It's better to update AI more frequently during combat } return; @@ -4701,6 +5075,14 @@ void PlayerbotAI::UpdateAI(const uint32 /*p_time*/) // bot was in combat recently - loot now if (m_botState == BOTSTATE_COMBAT) { + if (GetCombatOrder() & ORDERS_TEMP) + { + if (GetCombatOrder() & ORDERS_TEMP_WAIT_TANKAGGRO) + TellMaster("I was still waiting for the tank to gain aggro, but that doesn't make sense anymore..."); + if (GetCombatOrder() & ORDERS_TEMP_WAIT_OOC) + TellMaster("I was still waiting OOC but I just got out of combat..."); + ClearCombatOrder(ORDERS_TEMP); + } SetState(BOTSTATE_LOOTING); m_attackerInfo.clear(); if (HasCollectFlag(COLLECT_FLAG_COMBAT)) @@ -4729,7 +5111,7 @@ void PlayerbotAI::UpdateAI(const uint32 /*p_time*/) return MovementReset(); // do class specific non combat actions - if (GetClassAI() && !m_bot->IsMounted()) + if (GetClassAI() && !m_bot->IsMounted() && !IsRegenerating()) { GetClassAI()->DoNonCombatActions(); @@ -4795,7 +5177,7 @@ bool PlayerbotAI::In_Range(Unit* Target, uint32 spellId) if (!TempRange) return false; - if (TempRange->minRange == (TempRange->maxRange == 0.0f)) + if (TempRange->minRange == 0.0f && TempRange->maxRange == 0.0f) return true; //Unit is out of range of this spell @@ -4826,6 +5208,7 @@ bool PlayerbotAI::CheckBotCast(const SpellEntry *sInfo ) } SpellCastResult res = tmp_spell->CheckCast(false); + // DEBUG_LOG("CheckBotCast SpellCastResult(%u)",res); switch(res) { case SPELL_CAST_OK: @@ -4875,6 +5258,13 @@ bool PlayerbotAI::CastSpell(uint32 spellId) return false; } + // for AI debug purpose: uncomment the following line and bot will tell Master of every spell they attempt to cast + // TellMaster("I'm trying to cast %s (spellID %u)", pSpellInfo->SpellName[0], spellId); + + // Power check (stolen from: CreatureAI.cpp - CreatureAI::CanCastSpell) + if (m_bot->GetPower((Powers)pSpellInfo->powerType) < Spell::CalculatePowerCost(pSpellInfo, m_bot)) + return false; + // set target ObjectGuid targetGUID = m_bot->GetSelectionGuid(); Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, targetGUID); @@ -4904,7 +5294,6 @@ bool PlayerbotAI::CastSpell(uint32 spellId) { CastTime = (castTimeEntry->CastTime / 1000); DEBUG_LOG ("[PlayerbotAI]: CastSpell - Bot movement reset for casting %s (%u)", pSpellInfo->SpellName[0], spellId); - // m_bot->clearUnitState(UNIT_STAT_MOVING); m_bot->StopMoving(); } @@ -4976,12 +5365,14 @@ bool PlayerbotAI::CastSpell(uint32 spellId) return false; if (IsAutoRepeatRangedSpell(pSpellInfo)) - m_bot->CastSpell(pTarget, pSpellInfo, TRIGGERED_OLD_TRIGGERED); // cast triggered spell + m_bot->CastSpell(pTarget, pSpellInfo, TRIGGERED_OLD_TRIGGERED); // cast triggered spell else - m_bot->CastSpell(pTarget, pSpellInfo, TRIGGERED_NONE); // uni-cast spell + m_bot->CastSpell(pTarget, pSpellInfo, TRIGGERED_NONE); // uni-cast spell } - SetIgnoreUpdateTime(CastTime + 1); + // Some casting times are negative so set ignore update time to 1 sec to avoid stucking the bot AI + SetIgnoreUpdateTime(std::max(CastTime, 0.0f) + 1); + return true; } @@ -5072,7 +5463,7 @@ bool PlayerbotAI::Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player * sameOrBetterAuraFound = true; break; } - willBenefitFromSpell = willBenefitFromSpell || !sameOrBetterAuraFound; + willBenefitFromSpell = willBenefitFromSpell || !sameOrBetterAuraFound; } if (!willBenefitFromSpell) @@ -5418,10 +5809,10 @@ bool PlayerbotAI::PickPocket(Unit* pTarget) copper -= (silver * 100); out << "|r|cff009900" << m_bot->GetName() << " loots: " << "|h|cffffffff[|r|cff00ff00" << gold - << "|r|cfffffc00g|r|cff00ff00" << silver - << "|r|cffcdcdcds|r|cff00ff00" << copper - << "|r|cff993300c" - << "|h|cffffffff]"; + << "|r|cfffffc00g|r|cff00ff00" << silver + << "|r|cffcdcdcds|r|cff00ff00" << copper + << "|r|cff993300c" + << "|h|cffffffff]"; TellMaster(out.str().c_str()); } @@ -5431,7 +5822,7 @@ bool PlayerbotAI::PickPocket(Unit* pTarget) } if (!loot->AutoStore(m_bot, false, NULL_BAG, NULL_SLOT)) - sLog.outDebug("PLAYERBOT Debug: Failed to get loot from pickpocketed NPC"); + sLog.outDebug("PLAYERBOT Debug: Failed to get loot from pickpocketed NPC"); // release the loot whatever happened loot->Release(m_bot); @@ -5811,44 +6202,6 @@ void PlayerbotAI::extractSpellIdList(const std::string& text, BotEntryList& m_sp } } -void PlayerbotAI::extractGOinfo(const std::string& text, BotObjectList& m_lootTargets) const -{ - - // Link format - // |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << "|h[" << gInfo->name << "]|h|r"; - // |cFFFFFF00|Hfound:9582:1731|h[Copper Vein]|h|r - - uint8 pos = 0; - while (true) - { - // extract GO guid - int i = text.find("Hfound:", pos); // base H = 11 - if (i == -1) // break if error - break; - - pos = i + 7; //start of window in text 11 + 7 = 18 - int endPos = text.find(':', pos); // end of window in text 22 - if (endPos == -1) //break if error - break; - std::string guidC = text.substr(pos, endPos - pos); // get string within window i.e guid 22 - 18 = 4 - uint32 guid = atol(guidC.c_str()); // convert ascii to long int - - // extract GO entry - pos = endPos + 1; - endPos = text.find(':', pos); // end of window in text - if (endPos == -1) //break if error - break; - - std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry - uint32 entry = atol(entryC.c_str()); // convert ascii to float - - ObjectGuid lootCurrent = ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid); - - if (guid) - m_lootTargets.push_back(lootCurrent); - } -} - void PlayerbotAI::extractTalentIds(const std::string &text, std::list &talentIds) const { // Link format: @@ -5887,6 +6240,44 @@ void PlayerbotAI::extractTalentIds(const std::string &text, std::listname << "]|h|r"; + // |cFFFFFF00|Hfound:9582:1731|h[Copper Vein]|h|r + + uint8 pos = 0; + while (true) + { + // extract GO guid + int i = text.find("Hfound:", pos); // base H = 11 + if (i == -1) // break if error + break; + + pos = i + 7; //start of window in text 11 + 7 = 18 + int endPos = text.find(':', pos); // end of window in text 22 + if (endPos == -1) //break if error + break; + std::string guidC = text.substr(pos, endPos - pos); // get string within window i.e guid 22 - 18 = 4 + uint32 guid = atol(guidC.c_str()); // convert ascii to long int + + // extract GO entry + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + break; + + std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry + uint32 entry = atol(entryC.c_str()); // convert ascii to float + + ObjectGuid lootCurrent = ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid); + + if (guid) + m_lootTargets.push_back(lootCurrent); + } +} + // extracts currency in #g#s#c format uint32 PlayerbotAI::extractMoney(const std::string& text) const { @@ -6109,6 +6500,8 @@ void PlayerbotAI::findNearbyCreature() if (!(it->second.npc_option_npcflag & npcflags)) continue; + DEBUG_LOG("GOSSIP_OPTION_ (%u)",it->second.option_id); + switch (it->second.option_id) { case GOSSIP_OPTION_AUCTIONEER: @@ -6242,12 +6635,83 @@ void PlayerbotAI::findNearbyCreature() m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); } } + itr = m_findNPC.erase(itr); // all done lets go home m_bot->GetMotionMaster()->Clear(false); m_bot->GetMotionMaster()->MoveIdle(); } } } +/** + * IsElite() + * Playerbot wrapper to know if a target is elite or not. This is used by the AI to switch from one action to another + * return bool Returns true if bot's target is a creature with elite rank (elite rare, elite, worldboss) + * + * params:pTarget Unit* the target to check if it is elite + * params:isWorldBoss bool if true, the function will return true only if the target is a worldboss. This allow to enable specific code if the target is a worldboss + * return false if the target is not elite/rare elite/worldboss or if isWorldBoss was provided as true and that the target is not a worldboss + * + */ +bool PlayerbotAI::IsElite(Unit* pTarget, bool isWorldBoss) const +{ + if (!pTarget) + return false; + + if (Creature * pCreature = (Creature*) pTarget) + { + if (isWorldBoss) + return pCreature->IsWorldBoss(); + else + return (pCreature->IsElite() || pCreature->IsWorldBoss()); + } + + return false; +} + +// Check if bot target has one of the following auras: Sap, Polymorph, Shackle Undead, Banish, Seduction, Freezing Trap, Hibernate +// This is used by the AI to prevent bots from attacking crowd control targets + +static const uint32 uAurasIds[21] = +{ + 118, 12824, 12825, 12826, // polymorph + 28272, 28271, // polymorph pig, turtle + 9484, 9485, 10955, // shackle + 6358, // seduction + 710, 18647, // banish + 6770, 2070, 11297, // sap + 3355, 14308, 14309, // freezing trap (effect auras IDs, not spell IDs) + 2637, 18657, 18658 // hibernate +}; + +bool PlayerbotAI::IsNeutralized(Unit* pTarget) +{ + if (!pTarget) + return false; + + for (uint8 i = 0; i < countof(uAurasIds); ++i) + { + if (pTarget->HasAura(uAurasIds[i], EFFECT_INDEX_0)) + return true; + } + + return false; +} + +// Utility function to make the bots face their target +// Useful to ensure bots can cast spells/abilities +// without getting facing target errors +void PlayerbotAI::FaceTarget(Unit* pTarget) +{ + if (!pTarget) + return; + + // Only update orientation if not already facing target + if (!m_bot->HasInArc(M_PI_F, pTarget)) + m_bot->SetFacingTo(m_bot->GetAngle(pTarget)); + + return; +} + bool PlayerbotAI::CanStore() { uint32 totalused = 0; @@ -6387,6 +6851,52 @@ void PlayerbotAI::UseItem(Item *item, uint32 targetFlag, ObjectGuid targetGUID) m_bot->GetSession()->QueuePacket(std::move(packet)); } +static const uint32 uPriorizedHealingItemIds[14] = +{ + HEALTHSTONE_DISPLAYID, MAJOR_HEALING_POTION, WHIPPER_ROOT_TUBER, NIGHT_DRAGON_BREATH, LIMITED_INVULNERABILITY_POTION, GREATER_DREAMLESS_SLEEP_POTION, + SUPERIOR_HEALING_POTION, CRYSTAL_RESTORE, DREAMLESS_SLEEP_POTION, GREATER_HEALING_POTION, HEALING_POTION, LESSER_HEALING_POTION, DISCOLORED_HEALING_POTION, MINOR_HEALING_POTION, +}; + +/** + * TryEmergency() + * Playerbot function to select an item that the bot will use to heal itself on low health without waiting for a heal from a healer + * + * params:pAttacker Unit* the creature that is attacking the bot + * return nothing + */ +void PlayerbotAI::TryEmergency(Unit* pAttacker) +{ + // Do not use consumable if bot can heal self + if (IsHealer() && GetManaPercent() > 20) + return; + + // If bot does not have aggro: use bandage instead of potion/stone/crystal + if (!pAttacker && !m_bot->HasAura(11196)) // Recently bandaged + { + Item* bandage = FindBandage(); + if (bandage) + { + SetIgnoreUpdateTime(8); + UseItem(bandage); + return; + } + } + + // Else loop over the list of health consumable to pick one + Item* healthItem; + for (uint8 i = 0; i < countof(uPriorizedHealingItemIds); ++i) + { + healthItem = FindConsumable(uPriorizedHealingItemIds[i]); + if (healthItem) + { + UseItem(healthItem); + return; + } + } + + return; +} + // submits packet to use an item void PlayerbotAI::EquipItem(Item* src_Item) { @@ -6464,12 +6974,12 @@ void PlayerbotAI::EquipItem(Item* src_Item) // 'Will not be traded' slot. bool PlayerbotAI::TradeItem(const Item& item, int8 slot) { - // DEBUG_LOG ("[PlayerbotAI]: TradeItem - slot=%d, hasTrader=%d, itemInTrade=%d, itemTradeable=%d", - // slot, - // (m_bot->GetTrader() ? 1 : 0), - // (item.IsInTrade() ? 1 : 0), - // (item.CanBeTraded() ? 1 : 0) - // ); + DEBUG_LOG ("[PlayerbotAI]: TradeItem - slot=%d, hasTrader=%d, itemInTrade=%d, itemTradeable=%d", + slot, + (m_bot->GetTrader() ? 1 : 0), + (item.IsInTrade() ? 1 : 0), + (item.CanBeTraded() ? 1 : 0) + ); if (!m_bot->GetTrader() || item.IsInTrade() || (!item.CanBeTraded() && slot != TRADE_SLOT_NONTRADED)) return false; @@ -6490,13 +7000,13 @@ bool PlayerbotAI::TradeItem(const Item& item, int8 slot) } } - if (tradeSlot == -1) return false; + if (tradeSlot == -1) return false; - std::unique_ptr packet(new WorldPacket(CMSG_SET_TRADE_ITEM, 3)); - *packet << (uint8) tradeSlot << (uint8) item.GetBagSlot() + std::unique_ptr packet(new WorldPacket(CMSG_SET_TRADE_ITEM, 3)); + *packet << (uint8) tradeSlot << (uint8) item.GetBagSlot() << (uint8) item.GetSlot(); - m_bot->GetSession()->QueuePacket(std::move(packet)); - return true; + m_bot->GetSession()->QueuePacket(std::move(packet)); + return true; } // submits packet to trade copper (trade window must be open) @@ -7074,13 +7584,13 @@ bool PlayerbotAI::AddQuest(const uint32 entry, WorldObject * questgiver) break; } - // build needed creatures if quest contains any - for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) - if (qInfo->ReqCreatureOrGOCount[i] > 0) - { - SetQuestNeedCreatures(); - break; - } + // build needed creatures if quest contains any + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + if (qInfo->ReqCreatureOrGOCount[i] > 0) + { + SetQuestNeedCreatures(); + break; + } if (qInfo->GetSrcSpell() > 0) m_bot->CastSpell(m_bot, qInfo->GetSrcSpell(), TRIGGERED_OLD_TRIGGERED); @@ -7245,6 +7755,7 @@ void PlayerbotAI::Sell(const uint32 itemid) break; } } + m_bot->MoveItemFromInventory(pItem->GetBagSlot(), pItem->GetSlot(), true); m_bot->AddItemToBuyBackSlot(pItem, cost); m_bot->ModifyMoney(cost); @@ -7495,6 +8006,9 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) else if (ExtractCommand("pull", input)) _HandleCommandPull(input, fromPlayer); + else if (ExtractCommand("neutralize", input) || ExtractCommand("neutral", input)) + _HandleCommandNeutralize(input, fromPlayer); + else if (ExtractCommand("cast", input, true)) // true -> "cast" OR "c" _HandleCommandCast(input, fromPlayer); @@ -7690,6 +8204,10 @@ void PlayerbotAI::_HandleCommandReset(std::string &text, Player &fromPlayer) void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) { + std::string msg = "_HandleCommandOrders processing order: "; + msg += text; + SendWhisper(msg, fromPlayer); + if (ExtractCommand("delay", text)) { uint32 gdelay; @@ -7713,18 +8231,18 @@ void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) if (text == "") { - SendWhisper("|cffff0000Syntax error:|cffffffff orders combat [targetPlayer]", fromPlayer); + SendWhisper("|cffff0000Syntax error:|cffffffff orders combat ", fromPlayer); return; } QueryResult *resultlvl = CharacterDatabase.PQuery("SELECT guid FROM playerbot_saved_data WHERE guid = '%u'", m_bot->GetObjectGuid().GetCounter()); if (!resultlvl) - CharacterDatabase.DirectPExecute("INSERT INTO playerbot_saved_data (guid,bot_primary_order,bot_secondary_order,primary_target,secondary_target,pname,sname,combat_delay,auto_follow,autoequip) VALUES ('%u',0,0,0,0,'','',0,0,false)", m_bot->GetObjectGuid().GetCounter()); + CharacterDatabase.DirectPExecute("INSERT INTO playerbot_saved_data (guid,combat_order,primary_target,secondary_target,pname,sname,combat_delay,auto_follow,autoequip) VALUES ('%u',0,0,0,'','',0,0,false)", m_bot->GetObjectGuid().GetCounter()); else delete resultlvl; - int protect = text.find("protect"); - int assist = text.find("assist"); + size_t protect = text.find("protect"); + size_t assist = text.find("assist"); if (ExtractCommand("protect", text) || ExtractCommand("assist", text)) { @@ -7742,19 +8260,15 @@ void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) } target = ObjectAccessor::GetUnit(fromPlayer, targetGUID); if (!target) - { - SendWhisper("|cffff0000Invalid target for combat order protect or assist!", fromPlayer); - return; - } + return SendWhisper("|cffff0000Invalid target for combat order protect or assist!", fromPlayer); if (protect != std::string::npos) - SetCombatOrderByStr("protect", target); + SetCombatOrder(ORDERS_PROTECT, target); else if (assist != std::string::npos) - SetCombatOrderByStr("assist", target); - return; + SetCombatOrder(ORDERS_ASSIST, target); } - SetCombatOrderByStr(text, target); - return; + else + SetCombatOrderByStr(text, target); } else if (text != "") { @@ -7891,16 +8405,15 @@ void PlayerbotAI::_HandleCommandAttack(std::string &text, Player &fromPlayer) ObjectGuid attackOnGuid = fromPlayer.GetSelectionGuid(); if (attackOnGuid) { - if (Unit * thingToAttack = ObjectAccessor::GetUnit(*m_bot, attackOnGuid)) + if (Unit* thingToAttack = ObjectAccessor::GetUnit(*m_bot, attackOnGuid)) { - if (!m_bot->IsFriendlyTo(thingToAttack) && !m_bot->IsWithinLOSInMap(thingToAttack)) + if (!m_bot->IsFriendlyTo(thingToAttack)) { - DoTeleport(*m_followTarget); + if (!m_bot->IsWithinLOSInMap(thingToAttack)) + DoTeleport(*m_followTarget); if (m_bot->IsWithinLOSInMap(thingToAttack)) - GetCombatTarget(thingToAttack); + Attack(thingToAttack); } - else if (!m_bot->IsFriendlyTo(thingToAttack) && m_bot->IsWithinLOSInMap(thingToAttack)) - GetCombatTarget(thingToAttack); } } else @@ -7912,61 +8425,169 @@ void PlayerbotAI::_HandleCommandAttack(std::string &text, Player &fromPlayer) void PlayerbotAI::_HandleCommandPull(std::string &text, Player &fromPlayer) { - if (text != "") + bool bReadyCheck = false; + + if (!m_bot) return; + + if (ExtractCommand("test", text)) // switch to automatic follow distance + { + if (CanPull(fromPlayer)) + SendWhisper("Looks like I am capable of pulling. Ask me 'pull ready' with a target for a more precise check.", fromPlayer); + return; + } + if (ExtractCommand("ready", text)) // switch to automatic follow distance { - SendWhisper("pull cannot have a subcommand.", fromPlayer); + bReadyCheck = true; + } + else if (text != "") + { + SendWhisper("See 'help pull' for details on using the pull command.", fromPlayer); return; } - if (fromPlayer.GetGroup() != m_bot->GetGroup()) + // This function also takes care of error reporting + if (!CanPull(fromPlayer)) + return; + + // Check for valid target + m_bot->SetSelectionGuid(fromPlayer.GetSelectionGuid()); + ObjectGuid attackOnGuid = m_bot->GetSelectionGuid(); + if (!attackOnGuid) { - SendWhisper("I can't pull - we're not in the same group.", fromPlayer); + SendWhisper("No target is selected.", fromPlayer); return; } - if (IsGroupInCombat()) // TODO: add raid support + Unit* thingToAttack = ObjectAccessor::GetUnit(*m_bot, attackOnGuid); + if (!thingToAttack) { - SendWhisper("Unable to pull - the group is already in combat", fromPlayer); + SendWhisper("No target is selected.", fromPlayer); return; } - // This does not allow for the eventuality that a player is the tank, but assuming the player being a tank - // knows how to pull (which is not our job anyway) there is little lost here - if (!GetGroupTank()) // TODO: can't this work with: m_bot->GetGroup()->GetMainTankGUID() (which, by-the-by, is raid-proof) + if (m_bot->IsFriendlyTo(thingToAttack)) + { + SendWhisper("Where I come from we don't attack our friends.", fromPlayer); + return; + } + // TODO: Okay, this one should actually be fixable. InMap should return, but LOS (Line of Sight) should result in moving, well, into LoS. + if (!m_bot->IsWithinLOSInMap(thingToAttack)) + { + SendWhisper("I can't see that target!", fromPlayer); + return; + } + GetCombatTarget(thingToAttack); + if (!GetCurrentTarget()) { - SendWhisper("This group has no playerbot tank to perform the pull. Either give someone the combat order 'tank' or pull yourself.", fromPlayer); + SendWhisper("Failed to set target, cause unknown.", fromPlayer); + return; + } + if (bReadyCheck) + { + SendWhisper("All checks have been passed and I am ready to pull! ... Are you sure you wouldn't like a smaller target?", fromPlayer); return; } - //(3) if tank does not have the proper pulling method (shoot + gun/bow, spell, ...) -> report failure - //(4) else - //(4a) if tank, wait a second (if healer class with HoT is present), pull (based on class), deactivate any attack (such as 'shoot (bow/gun)' for warriors), wait until in melee range, attack - //(4b) if dps, wait (see (4+5) in first post) - //(4c) if healer, do a HoT on the tank if class has a HoT. else do healing checks - //(5) when target is in melee range of tank, wait 2 seconds (healers continue to do heal checks), then return to normal functioning + // All healers which have it available will cast any applicable HoT (Heal over Time) spell on the tank + GroupHoTOnTank(); - /* - ObjectGuid attackOnGuid = fromPlayer.GetSelectionGuid(); - if (attackOnGuid) + /* Technically the tank should wait a bit if/until the HoT has been applied + but the above function immediately casts it rather than wait for an UpdateAI tick + + There is no need to take into account that GroupHoTOnTank() may fail due to global cooldown. Either you're prepared for a difficult + pull in which case it won't fail due to global cooldown, or you're chaining easy pulls in which case you don't care. + */ + /* So have the group wait for the tank to take action (and aggro) - this way it will be easy to see if tank has aggro or not without having to + worry about tank not being the first to have UpdateAI() called + */ + + // Need to have a group and a tank, both checked in "CanPull()" call above + //if (!(GetGroupTank()->GetPlayerbotAI()->GetClassAI()->Pull())) + // I've been told to pull and a check was done above whether I'm actually a tank, so *I* will try to pull: + if (!CastPull()) + { + SendWhisper("I did my best but I can't actually pull. How odd.", fromPlayer); + return; + } + + // Sets Combat Orders to PULL + SetGroupCombatOrder(ORDERS_TEMP_WAIT_TANKAGGRO); + + SetGroupIgnoreUpdateTime(2); + + // Set all group members (save this tank) to wait 10 seconds. They will wait until the tank says so, until any non-tank gains aggro or 10 seconds - whichever is shortest + if (m_bot->GetGroup()) // one last sanity check, should be unnecessary { - if (Unit * thingToAttack = ObjectAccessor::GetUnit(*m_bot, attackOnGuid)) + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) { - if (!m_bot->IsFriendlyTo(thingToAttack) && !m_bot->IsWithinLOSInMap(thingToAttack)) - { - DoTeleport(*m_followTarget); - if (m_bot->IsWithinLOSInMap(thingToAttack)) - GetCombatTarget(thingToAttack); - } - else if (!m_bot->IsFriendlyTo(thingToAttack) && m_bot->IsWithinLOSInMap(thingToAttack)) - GetCombatTarget(thingToAttack); + Player* groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->GetPlayerbotAI() || groupMember == m_bot) + continue; + groupMember->GetPlayerbotAI()->GetClassAI()->SetWait(10); } } - else + + //(4a) if tank, deactivate any attack (such as 'shoot (bow/gun)' for warriors), wait until in melee range, attack + //(4b) if dps, wait until the target is in melee range of the tank +2seconds or until tank no longer holds aggro + //(4c) if healer, do healing checks + //(5) when target is in melee range of tank, wait 2 seconds (healers continue to do group heal checks, all do self-heal checks), then return to normal functioning +} + +void PlayerbotAI::_HandleCommandNeutralize(std::string &text, Player &fromPlayer) +{ + if (!m_bot) return; + + if (text != "") + { + SendWhisper("See 'help neutralize' for details on using the neutralize command.", fromPlayer); + return; + } + + // Check for valid target + m_bot->SetSelectionGuid(fromPlayer.GetSelectionGuid()); + ObjectGuid selectOnGuid = m_bot->GetSelectionGuid(); + if (!selectOnGuid) { SendWhisper("No target is selected.", fromPlayer); - m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + return; + } + + Unit* thingToNeutralize = ObjectAccessor::GetUnit(*m_bot, selectOnGuid); + if (!thingToNeutralize) + { + SendWhisper("No valid target is selected.", fromPlayer); + return; + } + + if (m_bot->IsFriendlyTo(thingToNeutralize)) + { + SendWhisper("I can't neutralize that target: this is a friend to me.", fromPlayer); + return; + } + + if (!m_bot->IsWithinLOSInMap(thingToNeutralize)) + { + SendWhisper("I can't see that target!", fromPlayer); + return; + } + + if (IsNeutralized(thingToNeutralize)) + { + SendWhisper("Target is already neutralized.", fromPlayer); + return; + } + + m_targetGuidCommand = selectOnGuid; + + // All checks passed: call the Neutralize function of each bot class + // to define what spellid to use if available and if creature type is correct + // m_spellIdCommand will be defined there and UpdateAI will then handle the cast + if (!CastNeutralize()) + { + SendWhisper("Something went wrong: I can't neutralize that target.", fromPlayer); + return; } - */ } void PlayerbotAI::_HandleCommandCast(std::string &text, Player &fromPlayer) @@ -7997,14 +8618,11 @@ void PlayerbotAI::_HandleCommandCast(std::string &text, Player &fromPlayer) } ObjectGuid castOnGuid = fromPlayer.GetSelectionGuid(); - if (spellId != 0 && m_bot->HasSpell(spellId)) + if (spellId != 0 && castOnGuid && m_bot->HasSpell(spellId)) { m_spellIdCommand = spellId; - if (castOnGuid) - m_targetGuidCommand = castOnGuid; - else - m_targetGuidCommand = m_bot->GetObjectGuid(); - } + m_targetGuidCommand = castOnGuid; + } } // _HandleCommandSell: Handle selling items @@ -9340,7 +9958,10 @@ void PlayerbotAI::_HandleCommandQuest(std::string &text, Player &fromPlayer) std::list questIds; extractQuestIds(text, questIds); for (std::list::iterator it = questIds.begin(); it != questIds.end(); it++) + { m_tasks.push_back(std::pair(TAKE_QUEST, *it)); + DEBUG_LOG(" questid (%u)",*it); + } m_findNPC.push_back(UNIT_NPC_FLAG_QUESTGIVER); } else if (ExtractCommand("drop", text, true)) // true -> "quest drop" OR "quest d" @@ -9483,14 +10104,14 @@ void PlayerbotAI::_HandleCommandPet(std::string &text, Player &fromPlayer) std::string state; switch (pet->GetCharmInfo()->GetReactState()) { - case REACT_AGGRESSIVE: - SendWhisper("My pet is aggressive.", fromPlayer); - break; - case REACT_DEFENSIVE: - SendWhisper("My pet is defensive.", fromPlayer); - break; - case REACT_PASSIVE: - SendWhisper("My pet is passive.", fromPlayer); + case REACT_AGGRESSIVE: + SendWhisper("My pet is aggressive.", fromPlayer); + break; + case REACT_DEFENSIVE: + SendWhisper("My pet is defensive.", fromPlayer); + break; + case REACT_PASSIVE: + SendWhisper("My pet is passive.", fromPlayer); } } else if (ExtractCommand("cast", text)) @@ -9583,19 +10204,19 @@ void PlayerbotAI::_HandleCommandPet(std::string &text, Player &fromPlayer) std::string color; switch (itr->second.active) { - case ACT_ENABLED: - color = "cff35d22d"; // Some flavor of green - break; - default: - color = "cffffffff"; + case ACT_ENABLED: + color = "cff35d22d"; // Some flavor of green + break; + default: + color = "cffffffff"; } if (IsPositiveSpell(spellId)) posOut << " |" << color << "|Hspell:" << spellId << "|h[" - << pSpellInfo->SpellName[loc] << "]|h|r"; + << pSpellInfo->SpellName[loc] << "]|h|r"; else negOut << " |" << color << "|Hspell:" << spellId << "|h[" - << pSpellInfo->SpellName[loc] << "]|h|r"; + << pSpellInfo->SpellName[loc] << "]|h|r"; } ChatHandler ch(&fromPlayer); @@ -9619,7 +10240,7 @@ void PlayerbotAI::_HandleCommandSpells(std::string &text, Player &fromPlayer) std::string spellName; uint32 ignoredSpells[] = {1843, 5019, 2479, 6603, 3365, 8386, 21651, 21652, 6233, 6246, 6247, - 61437, 22810, 22027, 45927, 7266, 7267, 6477, 6478, 7355, 68398}; + 61437, 22810, 22027, 45927, 7266, 7267, 6477, 6478, 7355, 68398}; uint32 ignoredSpellsCount = sizeof(ignoredSpells) / sizeof(uint32); for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) diff --git a/src/game/playerbot/PlayerbotAI.h b/src/game/playerbot/PlayerbotAI.h index a19197572..9a1ce8f04 100644 --- a/src/game/playerbot/PlayerbotAI.h +++ b/src/game/playerbot/PlayerbotAI.h @@ -26,7 +26,6 @@ enum RacialTraits BLOOD_FURY_WARLOCK = 33702, BLOOD_FURY_SHAMAN = 33697, ESCAPE_ARTIST_ALL = 20589, - EVERY_MAN_FOR_HIMSELF_ALL = 59752, GIFT_OF_THE_NAARU_DEATH_KNIGHT = 59545, GIFT_OF_THE_NAARU_HUNTER = 59543, GIFT_OF_THE_NAARU_MAGE = 59548, @@ -74,6 +73,107 @@ enum NotableItems ELEMENTAL_SEAFORIUM_CHARGE = 23819 }; +enum SharpeningStoneDisplayId +{ + ROUGH_SHARPENING_DISPLAYID = 24673, + COARSE_SHARPENING_DISPLAYID = 24674, + HEAVY_SHARPENING_DISPLAYID = 24675, + SOLID_SHARPENING_DISPLAYID = 24676, + DENSE_SHARPENING_DISPLAYID = 24677, + CONSECRATED_SHARPENING_DISPLAYID = 24674, // will not be used because bot can not know if it will face undead targets + ELEMENTAL_SHARPENING_DISPLAYID = 21072 +}; + +enum WeightStoneDisplayId +{ + ROUGH_WEIGHTSTONE_DISPLAYID = 24683, + COARSE_WEIGHTSTONE_DISPLAYID = 24684, + HEAVY_WEIGHTSTONE_DISPLAYID = 24685, + SOLID_WEIGHTSTONE_DISPLAYID = 24686, + DENSE_WEIGHTSTONE_DISPLAYID = 24687 +}; + +enum ManaPotionsId +{ + MINOR_MANA_POTION = 15715, + LESSER_MANA_POTION = 15716, + MANA_POTION = 15717, + GREATER_MANA_POTION = 15718, + SUPERIOR_MANA_POTION = 24151, + MAJOR_MANA_POTION = 21672, + MINOR_REJUVENATION_POTION = 2345, + MAJOR_REJUVENATION_POTION = 18253 +}; + +enum ManaRunesId +{ + DEMONIC_RUNE = 22952, + DARK_RUNE = 32905 +}; + +enum HealingItemDisplayId +{ + MAJOR_HEALING_POTION = 24152, + WHIPPER_ROOT_TUBER = 21974, + NIGHT_DRAGON_BREATH = 21975, + LIMITED_INVULNERABILITY_POTION = 24213, + GREATER_DREAMLESS_SLEEP_POTION = 17403, + SUPERIOR_HEALING_POTION = 15714, + CRYSTAL_RESTORE = 2516, + DREAMLESS_SLEEP_POTION = 17403, + GREATER_HEALING_POTION = 15713, + HEALING_POTION = 15712, + LESSER_HEALING_POTION = 15711, + DISCOLORED_HEALING_POTION = 15736, + MINOR_HEALING_POTION = 15710 +}; + +enum MainSpec +{ + MAGE_SPEC_FIRE = 41, + MAGE_SPEC_FROST = 61, + MAGE_SPEC_ARCANE = 81, + WARRIOR_SPEC_ARMS = 161, + WARRIOR_SPEC_PROTECTION = 163, + WARRIOR_SPEC_FURY = 164, + ROGUE_SPEC_COMBAT = 181, + ROGUE_SPEC_ASSASSINATION = 182, + ROGUE_SPEC_SUBTELTY = 183, + PRIEST_SPEC_DISCIPLINE = 201, + PRIEST_SPEC_HOLY = 202, + PRIEST_SPEC_SHADOW = 203, + SHAMAN_SPEC_ELEMENTAL = 261, + SHAMAN_SPEC_RESTORATION = 262, + SHAMAN_SPEC_ENHANCEMENT = 263, + DRUID_SPEC_FERAL = 281, + DRUID_SPEC_RESTORATION = 282, + DRUID_SPEC_BALANCE = 283, + WARLOCK_SPEC_DESTRUCTION = 301, + WARLOCK_SPEC_AFFLICTION = 302, + WARLOCK_SPEC_DEMONOLOGY = 303, + HUNTER_SPEC_BEASTMASTERY = 361, + HUNTER_SPEC_SURVIVAL = 362, + HUNTER_SPEC_MARKSMANSHIP = 363, + PALADIN_SPEC_RETRIBUTION = 381, + PALADIN_SPEC_HOLY = 382, + PALADIN_SPEC_PROTECTION = 383 +}; + +enum CombatManeuverReturns +{ + // TODO: RETURN_NO_ACTION_UNKNOWN is not part of ANY_OK or ANY_ERROR. It's also bad form and should be eliminated ASAP. + RETURN_NO_ACTION_OK = 0x01, // No action taken during this combat maneuver, as intended (just wait, etc...) + RETURN_NO_ACTION_UNKNOWN = 0x02, // No action taken during this combat maneuver, unknown reason + RETURN_NO_ACTION_ERROR = 0x04, // No action taken due to error + RETURN_NO_ACTION_INVALIDTARGET = 0x08, // No action taken - invalid target + RETURN_FINISHED_FIRST_MOVES = 0x10, // Last action of first-combat-maneuver finished, continue onto next-combat-maneuver + RETURN_CONTINUE = 0x20, // Continue first moves; normal return value for next-combat-maneuver + RETURN_NO_ACTION_INSUFFICIENT_POWER = 0x40, // No action taken due to insufficient power (rage, focus, mana, runes) + RETURN_ANY_OK = 0x31, // All the OK values bitwise OR'ed + RETURN_ANY_ACTION = 0x30, // All returns that result in action (which should also be 'OK') + RETURN_ANY_ERROR = 0x4C // All the ERROR values bitwise OR'ed +}; + enum AutoEquipEnum { AUTOEQUIP_OFF = 0, @@ -114,29 +214,28 @@ class MANGOS_DLL_SPEC PlayerbotAI // the master will auto set the target of the bot enum CombatOrderType { - ORDERS_NONE = 0x0000, // no special orders given - ORDERS_TANK = 0x0001, // bind attackers by gaining threat - ORDERS_ASSIST = 0x0002, // assist someone (dps type) - ORDERS_HEAL = 0x0004, // concentrate on healing (no attacks, only self defense) - ORDERS_NODISPEL = 0x0008, // Dont dispel anything - ORDERS_PROTECT = 0x0010, // combinable state: check if protectee is attacked - ORDERS_PASSIVE = 0x0020, // bots do nothing - ORDERS_RESIST = 0x0040, // resist a magic school(see below for types) - ORDERS_PULL = 0x0080, // Command to pull was given (expect bots to turn this off themselves) + ORDERS_NONE = 0x0000, // no special orders given + ORDERS_TANK = 0x0001, // bind attackers by gaining threat + ORDERS_ASSIST = 0x0002, // assist someone (dps type) + ORDERS_HEAL = 0x0004, // concentrate on healing (no attacks, only self defense) + ORDERS_NODISPEL = 0x0008, // Dont dispel anything + ORDERS_PROTECT = 0x0010, // combinable state: check if protectee is attacked + ORDERS_PASSIVE = 0x0020, // bots do nothing + ORDERS_TEMP_WAIT_TANKAGGRO = 0x0040, // Wait on tank to build aggro - expect healing to continue, disable setting when tank loses focus + ORDERS_TEMP_WAIT_OOC = 0x0080, // Wait but only while OOC - wait only - combat will resume healing, dps, tanking, ... + ORDERS_RESIST_FIRE = 0x0100, // resist fire + ORDERS_RESIST_NATURE = 0x0200, // resist nature + ORDERS_RESIST_FROST = 0x0400, // resist frost + ORDERS_RESIST_SHADOW = 0x0800, // resist shadow + + // Cumulative orders ORDERS_PRIMARY = 0x0007, - ORDERS_SECONDARY = 0x00F8, + ORDERS_SECONDARY = 0x0F78, + ORDERS_RESIST = 0x0F00, + ORDERS_TEMP = 0x00C0, // All orders NOT to be saved, turned off by bots (or logoff, reset, ...) ORDERS_RESET = 0xFFFF }; - enum ResistType - { - SCHOOL_NONE = 0, - SCHOOL_FIRE = 1, - SCHOOL_NATURE = 2, - SCHOOL_FROST = 3, - SCHOOL_SHADOW = 4 - }; - enum CombatTargetType { TARGET_NORMAL = 0x00, @@ -220,15 +319,17 @@ class MANGOS_DLL_SPEC PlayerbotAI AIT_VICTIMSELF = 0x04, AIT_VICTIMNOTSELF = 0x08 // !!! must use victim param in FindAttackers }; + struct AttackerInfo { - Unit* attacker; // reference to the attacker - Unit* victim; // combatant's current victim + Unit* attacker; // reference to the attacker + Unit* victim; // combatant's current victim float threat; // own threat on this combatant float threat2; // highest threat not caused by bot uint32 count; // number of units attacking uint32 source; // 1=bot, 2=master, 3=group }; + typedef std::map AttackerInfoList; typedef std::map SpellRanges; @@ -325,6 +426,11 @@ class MANGOS_DLL_SPEC PlayerbotAI void findNearbyGO(); // finds nearby creatures, whose UNIT_NPC_FLAGS match the flags specified in item list m_itemIds void findNearbyCreature(); + bool IsElite(Unit* pTarget, bool isWorldBoss = false) const; + // Used by bots to check if their target is neutralized (polymorph, shackle or the like). Useful to avoid breaking crowd control + bool IsNeutralized(Unit* pTarget); + // Make the bots face their target + void FaceTarget(Unit* pTarget); void MakeSpellLink(const SpellEntry *sInfo, std::ostringstream &out); void MakeWeaponSkillLink(const SpellEntry *sInfo, std::ostringstream &out, uint32 skillid); @@ -342,8 +448,8 @@ class MANGOS_DLL_SPEC PlayerbotAI bool CanReceiveSpecificSpell(uint8 spec, Unit* target) const; - bool HasTool(uint32 TC); bool PickPocket(Unit* pTarget); + bool HasTool(uint32 TC); bool HasSpellReagents(uint32 spellId); void ItemCountInInv(uint32 itemid, uint32 &count); uint32 GetSpellCharges(uint32 spellId); @@ -364,13 +470,15 @@ class MANGOS_DLL_SPEC PlayerbotAI Item* FindFood() const; Item* FindDrink() const; Item* FindBandage() const; - Item* FindPoison() const; Item* FindMount(uint32 matchingRidingSkill) const; Item* FindItem(uint32 ItemId, bool Equipped_too = false); Item* FindItemInBank(uint32 ItemId); Item* FindKeyForLockValue(uint32 reqSkillValue); Item* FindBombForLockValue(uint32 reqSkillValue); Item* FindConsumable(uint32 displayId) const; + Item* FindStoneFor(Item* weapon) const; + Item* FindManaRegenItem() const; + bool FindAmmo() const; uint8 _findItemSlot(Item* target); bool CanStore(); @@ -395,6 +503,8 @@ class MANGOS_DLL_SPEC PlayerbotAI void UseItem(Item *item, Unit *target); void UseItem(Item *item); + void TryEmergency(Unit* pAttacker); + void PlaySound(uint32 soundid); void Announce(AnnounceFlags msg); @@ -426,6 +536,7 @@ class MANGOS_DLL_SPEC PlayerbotAI bool ItemStatComparison(const ItemPrototype *pProto, const ItemPrototype *pProto2); void Feast(); void InterruptCurrentCastingSpell(); + void Attack(Unit* forcedTarget = nullptr); void GetCombatTarget(Unit* forcedTarged = 0); void GetDuelTarget(Unit* forcedTarget); Unit* GetCurrentTarget() { return m_targetCombat; }; @@ -468,8 +579,17 @@ class MANGOS_DLL_SPEC PlayerbotAI bool AddQuest(const uint32 entry, WorldObject* questgiver); bool IsInCombat(); + bool IsRegenerating(); bool IsGroupInCombat(); Player* GetGroupTank(); // TODO: didn't want to pollute non-playerbot code but this should really go in group.cpp + void SetGroupCombatOrder(CombatOrderType co); + void ClearGroupCombatOrder(CombatOrderType co); + void SetGroupIgnoreUpdateTime(uint8 t); + bool GroupHoTOnTank(); + bool CanPull(Player &fromPlayer); + bool CastPull(); + bool GroupTankHoldsAggro(); + bool CastNeutralize(); void UpdateAttackerInfo(); Unit* FindAttacker(ATTACKERINFOTYPE ait = AIT_NONE, Unit *victim = 0); uint32 GetAttackerCount() { return m_attackerInfo.size(); }; @@ -481,13 +601,14 @@ class MANGOS_DLL_SPEC PlayerbotAI bool IsHealer() { return (m_combatOrder & ORDERS_HEAL) ? true : false; } bool IsDPS() { return (m_combatOrder & ORDERS_ASSIST) ? true : false; } bool Impulse() { srand ( time(nullptr) ); return(((rand() % 100) > 50) ? true : false); } - ResistType GetResistType() { return this->m_resistType; } void SetMovementOrder(MovementOrderType mo, Unit *followTarget = 0); MovementOrderType GetMovementOrder() { return this->m_movementOrder; } void MovementReset(); void MovementClear(); bool IsMoving(); + void SetInFront(const Unit* obj); + void ItemLocalization(std::string& itemName, const uint32 itemID) const; void QuestLocalization(std::string& questTitle, const uint32 questID) const; void CreatureLocalization(std::string& creatureName, const uint32 entry) const; @@ -514,11 +635,13 @@ class MANGOS_DLL_SPEC PlayerbotAI bool ExtractCommand(const std::string sLookingFor, std::string &text, bool bUseShort = false); // outsource commands for code clarity void _HandleCommandReset(std::string &text, Player &fromPlayer); + void _HandleCommandReport(std::string &text, Player &fromPlayer); void _HandleCommandOrders(std::string &text, Player &fromPlayer); void _HandleCommandFollow(std::string &text, Player &fromPlayer); void _HandleCommandStay(std::string &text, Player &fromPlayer); void _HandleCommandAttack(std::string &text, Player &fromPlayer); void _HandleCommandPull(std::string &text, Player &fromPlayer); + void _HandleCommandNeutralize(std::string &text, Player &fromPlayer); void _HandleCommandCast(std::string &text, Player &fromPlayer); void _HandleCommandSell(std::string &text, Player &fromPlayer); void _HandleCommandBuy(std::string &text, Player &fromPlayer); @@ -575,7 +698,7 @@ class MANGOS_DLL_SPEC PlayerbotAI CombatStyle m_combatStyle; CombatOrderType m_combatOrder; - ResistType m_resistType; + MovementOrderType m_movementOrder; ScenarioType m_ScenarioType; @@ -623,15 +746,12 @@ class MANGOS_DLL_SPEC PlayerbotAI bool m_targetChanged; CombatTargetType m_targetType; - Unit *m_targetCombat; // current combat target - Unit *m_targetAssist; // get new target by checking attacker list of assisted player - Unit *m_targetProtect; // check + Unit* m_targetCombat; // current combat target + Unit* m_targetAssist; // get new target by checking attacker list of assisted player + Unit* m_targetProtect; // check Unit *m_followTarget; // whom to follow in non combat situation? - uint8 gPrimOrder; - uint8 gSecOrder; - uint32 FISHING, HERB_GATHERING, MINING, diff --git a/src/game/playerbot/PlayerbotClassAI.cpp b/src/game/playerbot/PlayerbotClassAI.cpp index 0309c1b64..b507b24e2 100644 --- a/src/game/playerbot/PlayerbotClassAI.cpp +++ b/src/game/playerbot/PlayerbotClassAI.cpp @@ -1,18 +1,678 @@ #include "PlayerbotClassAI.h" #include "Common.h" -PlayerbotClassAI::PlayerbotClassAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : m_master(master), m_bot(bot), m_ai(ai) {} +#include "Cell.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" + +PlayerbotClassAI::PlayerbotClassAI(Player* const master, Player* const bot, PlayerbotAI* const ai) +{ + m_master = master; + m_bot = bot; + m_ai = ai; + + m_MinHealthPercentTank = 80; + m_MinHealthPercentHealer = 60; + m_MinHealthPercentDPS = 30; + m_MinHealthPercentMaster = m_MinHealthPercentDPS; + + ClearWait(); +} PlayerbotClassAI::~PlayerbotClassAI() {} -bool PlayerbotClassAI::DoFirstCombatManeuver(Unit *) +CombatManeuverReturns PlayerbotClassAI::DoFirstCombatManeuver(Unit *) { return RETURN_NO_ACTION_OK; } +CombatManeuverReturns PlayerbotClassAI::DoNextCombatManeuver(Unit *) { return RETURN_NO_ACTION_OK; } + +CombatManeuverReturns PlayerbotClassAI::DoFirstCombatManeuverPVE(Unit *) { return RETURN_NO_ACTION_OK; } +CombatManeuverReturns PlayerbotClassAI::DoNextCombatManeuverPVE(Unit *) { return RETURN_NO_ACTION_OK; } +CombatManeuverReturns PlayerbotClassAI::DoFirstCombatManeuverPVP(Unit *) { return RETURN_NO_ACTION_OK; } +CombatManeuverReturns PlayerbotClassAI::DoNextCombatManeuverPVP(Unit *) { return RETURN_NO_ACTION_OK; } + +void PlayerbotClassAI::DoNonCombatActions() +{ + DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::DoNonCombatActions() rather than class specific function"); +} + +bool PlayerbotClassAI::EatDrinkBandage(bool bMana, unsigned char foodPercent, unsigned char drinkPercent, unsigned char bandagePercent) +{ + Item* drinkItem = nullptr; + Item* foodItem = nullptr; + if (bMana && m_ai->GetManaPercent() < drinkPercent) + drinkItem = m_ai->FindDrink(); + if (m_ai->GetHealthPercent() < foodPercent) + foodItem = m_ai->FindFood(); + if (drinkItem || foodItem) + { + if (drinkItem) + { + m_ai->TellMaster("I could use a drink."); + m_ai->UseItem(drinkItem); + } + if (foodItem) + { + m_ai->TellMaster("I could use some food."); + m_ai->UseItem(foodItem); + } + return true; + } + + if (m_ai->GetHealthPercent() < bandagePercent && !m_bot->HasAura(RECENTLY_BANDAGED)) + { + Item* bandageItem = m_ai->FindBandage(); + if (bandageItem) + { + m_ai->TellMaster("I could use first aid."); + m_ai->UseItem(bandageItem); + return true; + } + } + + return false; +} + +bool PlayerbotClassAI::CanPull() +{ + DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::CanPull() rather than class specific function"); + return false; +} + +bool PlayerbotClassAI::CastHoTOnTank() { - // return false, if done with opening moves/spells + DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::CastHoTOnTank() rather than class specific function"); return false; } -void PlayerbotClassAI::DoNextCombatManeuver(Unit *) {} -void PlayerbotClassAI::DoNonCombatActions(){} +CombatManeuverReturns PlayerbotClassAI::HealPlayer(Player* target) { + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (!target) return RETURN_NO_ACTION_INVALIDTARGET; + if (target->IsInDuel()) return RETURN_NO_ACTION_INVALIDTARGET; + + return RETURN_NO_ACTION_OK; +} + +// Please note that job_type JOB_MANAONLY is a cumulative restriction. JOB_TANK | JOB_HEAL means both; JOB_TANK | JOB_MANAONLY means tanks with powertype MANA (paladins, druids) +CombatManeuverReturns PlayerbotClassAI::Buff(bool (*BuffHelper)(PlayerbotAI*, uint32, Unit*), uint32 spellId, uint32 type, bool bMustBeOOC) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + if (!m_bot->isAlive() || m_bot->IsInDuel()) return RETURN_NO_ACTION_ERROR; + if (bMustBeOOC && m_bot->isInCombat()) return RETURN_NO_ACTION_ERROR; + + if (spellId == 0) return RETURN_NO_ACTION_OK; + + // First, fill the list of targets + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->isAlive() || groupMember->IsInDuel()) + continue; + JOB_TYPE job = GetTargetJob(groupMember); + if (job & type && (!(job & JOB_MANAONLY) || groupMember->getClass() == CLASS_DRUID || groupMember->GetPowerType() == POWER_MANA)) + { + if (BuffHelper(m_ai, spellId, groupMember)) + return RETURN_CONTINUE; + } + } + } + else + { + if (m_master && !m_master->IsInDuel() + && (!(GetTargetJob(m_master) & JOB_MANAONLY) || m_master->getClass() == CLASS_DRUID || m_master->GetPowerType() == POWER_MANA)) + if (BuffHelper(m_ai, spellId, m_master)) + return RETURN_CONTINUE; + // Do not check job or power type - any buff you have is always useful to self + if (BuffHelper(m_ai, spellId, m_bot)) + return RETURN_CONTINUE; + } + + return RETURN_NO_ACTION_OK; +} + +/** + * NeedGroupBuff() + * return boolean Returns true if more than two targets in the bot's group need the group buff. + * + * params:groupBuffSpellId uint32 the spell ID of the group buff like Arcane Brillance + * params:singleBuffSpellId uint32 the spell ID of the single target buff equivalent of the group buff like Arcane Intellect for group buff Arcane Brillance + * return false if false is returned, the bot is expected to perform a buff check for the single target buff of the group buff. + * + */ +bool PlayerbotClassAI::NeedGroupBuff(uint32 groupBuffSpellId, uint32 singleBuffSpellId) +{ + if (!m_bot) return false; + + uint8 numberOfGroupTargets = 0; + // Check group players to avoid using regeant and mana with an expensive group buff + // when only two players or less need it + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->isAlive()) + continue; + // Check if group member needs buff + if (!groupMember->HasAura(groupBuffSpellId, EFFECT_INDEX_0) && !groupMember->HasAura(singleBuffSpellId, EFFECT_INDEX_0)) + numberOfGroupTargets++; + // Don't forget about pet + Pet * pet = groupMember->GetPet(); + if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && (pet->HasAura(groupBuffSpellId, EFFECT_INDEX_0) || pet->HasAura(singleBuffSpellId, EFFECT_INDEX_0))) + numberOfGroupTargets++; + } + // treshold set to 2 targets because beyond that value, the group buff cost is cheaper in mana + if (numberOfGroupTargets < 3) + return false; + + // In doubt, buff everyone + return true; + } + else + return false; // no group, no group buff +} + +/** + * GetHealTarget() + * return Unit* Returns unit to be healed. First checks 'critical' Healer(s), next Tank(s), next Master (if different from:), next DPS. + * If none of the healths are low enough (or multiple valid targets) against these checks, the lowest health is healed. Having a target + * returned does not guarantee it's worth healing, merely that the target does not have 100% health. + * + * return NULL If NULL is returned, no healing is required. At all. + * + * Will need extensive re-write for co-operation amongst multiple healers. As it stands, multiple healers would all pick the same 'ideal' + * healing target. + */ +Player* PlayerbotClassAI::GetHealTarget(JOB_TYPE type) +{ + if (!m_ai) return nullptr; + if (!m_bot) return nullptr; + if (!m_bot->isAlive() || m_bot->IsInDuel()) return nullptr; + + // define seperately for sorting purposes - DO NOT CHANGE ORDER! + std::vector targets; + + // First, fill the list of targets + if (m_bot->GetGroup()) + { + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->isAlive() || groupMember->IsInDuel()) + continue; + JOB_TYPE job = GetTargetJob(groupMember); + if (job & type) + targets.push_back( heal_priority(groupMember, (groupMember->GetHealth() * 100 / groupMember->GetMaxHealth()), job) ); + } + } + else + { + targets.push_back( heal_priority(m_bot, m_bot->GetHealthPercent(), GetTargetJob(m_bot)) ); + if (m_master && !m_master->IsInDuel()) + targets.push_back( heal_priority(m_master, (m_master->GetHealth() * 100 / m_master->GetMaxHealth()), GetTargetJob(m_master)) ); + } + + // Sorts according to type: Healers first, tanks next, then master followed by DPS, thanks to the order of the TYPE enum + std::sort(targets.begin(), targets.end()); + + uint8 uCount = 0,i = 0; + // x is used as 'target found' variable; i is used as the targets iterator throughout all 4 types. + int16 x = -1; + + // Try to find a healer in need of healing (if multiple, the lowest health one) + while (true) + { + // This works because we sorted it above + if ( (uCount + i) >= targets.size() || !(targets.at(uCount).type & JOB_HEAL)) break; + uCount++; + } + + // We have uCount healers in the targets, check if any qualify for priority healing + for (; uCount > 0; uCount--, i++) + { + if (targets.at(i).hp <= m_MinHealthPercentHealer) + if (x == -1 || targets.at(x).hp > targets.at(i).hp) + x = i; + } + if (x > -1) return targets.at(x).p; + + // Try to find a tank in need of healing (if multiple, the lowest health one) + while (true) + { + if ( (uCount + i) >= targets.size() || !(targets.at(uCount).type & JOB_TANK)) break; + uCount++; + } + + for (; uCount > 0; uCount--, i++) + { + if (targets.at(i).hp <= m_MinHealthPercentTank) + if (x == -1 || targets.at(x).hp > targets.at(i).hp) + x = i; + } + if (x > -1) return targets.at(x).p; + + // Try to find master in need of healing (lowest health one first) + if (m_MinHealthPercentMaster != m_MinHealthPercentDPS) + { + while (true) + { + if ( (uCount + i) >= targets.size() || !(targets.at(uCount).type & JOB_MASTER)) break; + uCount++; + } + + for (; uCount > 0; uCount--, i++) + { + if (targets.at(i).hp <= m_MinHealthPercentMaster) + if (x == -1 || targets.at(x).hp > targets.at(i).hp) + x = i; + } + if (x > -1) return targets.at(x).p; + } + + // Try to find anyone else in need of healing (lowest health one first) + while (true) + { + if ( (uCount + i) >= targets.size() ) break; + uCount++; + } + + for (; uCount > 0; uCount--, i++) + { + if (targets.at(i).hp <= m_MinHealthPercentDPS) + if (x == -1 || targets.at(x).hp > targets.at(i).hp) + x = i; + } + if (x > -1) return targets.at(x).p; + + // Nobody is critical, find anyone hurt at all, return lowest (let the healer sort out if it's worth healing or not) + for (i = 0, uCount = targets.size(); uCount > 0; uCount--, i++) + { + if (targets.at(i).hp < 100) + if (x == -1 || targets.at(x).hp > targets.at(i).hp) + x = i; + } + if (x > -1) return targets.at(x).p; + + return nullptr; +} + +/** + * FleeFromAoEIfCan() + * return boolean Check if the bot can move out of the hostile AoE spell then try to find a proper destination and move towards it + * The AoE is assumed to be centered on the current bot location (this is the case most of the time) + * + * params: spellId uint32 the spell ID of the hostile AoE the bot is supposed to move from. It is used to find the radius of the AoE spell + * params: pTarget Unit* the creature or gameobject the bot will use to define one of the prefered direction in which to flee + * + * return true if bot has found a proper destination, false if none was found + */ +bool PlayerbotClassAI::FleeFromAoEIfCan(uint32 spellId, Unit* pTarget) +{ + if (!m_bot) return false; + if (!spellId) return false; + + // Step 1: Get radius from hostile AoE spell + float radius = 0; + SpellEntry const* spellproto = sSpellTemplate.LookupEntry(spellId); + if (spellproto) + radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellproto->EffectRadiusIndex[EFFECT_INDEX_0])); + + // Step 2: Get current bot position to move from it + float curr_x, curr_y, curr_z; + m_bot->GetPosition(curr_x, curr_y, curr_z); + return FleeFromPointIfCan(radius, pTarget, curr_x, curr_y, curr_z); +} + +/** + * FleeFromTrapGOIfCan() + * return boolean Check if the bot can move from a hostile nearby trap, then try to find a proper destination and move towards it + * + * params: goEntry uint32 the ID of the hostile trap the bot is supposed to move from. It is used to find the radius of the trap + * params: pTarget Unit* the creature or gameobject the bot will use to define one of the prefered direction in which to flee + * + * return true if bot has found a proper destination, false if none was found + */ +bool PlayerbotClassAI::FleeFromTrapGOIfCan(uint32 goEntry, Unit* pTarget) +{ + if (!m_bot) return false; + if (!goEntry) return false; + + // Step 1: check if the GO exists and find its trap radius + GameObjectInfo const* trapInfo = sGOStorage.LookupEntry(goEntry); + if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP) + return false; + + float trapRadius = float(trapInfo->trap.radius); + + // Step 2: find a GO in the range around player + GameObject* pGo = nullptr; + + MaNGOS::NearestGameObjectEntryInObjectRangeCheck go_check(*m_bot, goEntry, trapRadius); + MaNGOS::GameObjectLastSearcher searcher(pGo, go_check); + + Cell::VisitGridObjects(m_bot, searcher, trapRadius); + + if (!pGo) + return false; + + return FleeFromPointIfCan(trapRadius, pTarget, pGo->GetPositionX(), pGo->GetPositionY(), pGo->GetPositionZ()); +} + +/** + * FleeFromNpcWithAuraIfCan() + * return boolean Check if the bot can move from a creature having a specific aura, then try to find a proper destination and move towards it + * + * params: goEntry uint32 the ID of the hostile trap the bot is supposed to move from. It is used to find the radius of the trap + * params: spellId uint32 the spell ID of the aura the creature is supposed to have (directly or from triggered spell). It is used to find the radius of the aura + * params: pTarget Unit* the creature or gameobject the bot will use to define one of the prefered direction in which to flee + * + * return true if bot has found a proper destination, false if none was found + */ +bool PlayerbotClassAI::FleeFromNpcWithAuraIfCan(uint32 NpcEntry, uint32 spellId, Unit* pTarget) +{ + if (!m_bot) return false; + if (!NpcEntry) return false; + if (!spellId) return false; + + // Step 1: Get radius from hostile aura spell + float radius = 0; + SpellEntry const* spellproto = sSpellTemplate.LookupEntry(spellId); + if (spellproto) + radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellproto->EffectRadiusIndex[EFFECT_INDEX_0])); + + if (radius == 0) + return false; + + // Step 2: find a close creature with the right entry: + Creature* pCreature = nullptr; + + MaNGOS::NearestCreatureEntryWithLiveStateInObjectRangeCheck creature_check(*m_bot, NpcEntry, false, false, radius, true); + MaNGOS::CreatureLastSearcher searcher(pCreature, creature_check); + + Cell::VisitGridObjects(m_bot, searcher, radius); + + if (!pCreature) + return false; + + // Force to flee on a direction opposite to the position of the creature (fleeing from it, not only avoiding it) + return FleeFromPointIfCan(radius, pTarget, pCreature->GetPositionX(), pCreature->GetPositionY(), pCreature->GetPositionZ(), M_PI_F); +} + +/** + * FleeFromPointIfCan() + * return boolean Check if the bot can move from a provided point (x, y, z) to given distance radius + * + * params: radius uint32 the minimal radius (distance) used by the bot to look for a destination from the provided position + * params: pTarget Unit* the creature or gameobject the bot will use to define one of the prefered direction in which to flee + * params: x0, y0, z0 float the coordinates of the origin point used to calculate the destination point + * params: forcedAngle float (optional) when iterating to find a proper point in world to move the bot to, this angle will be prioritly used over other angles if it is provided + * + * return true if bot has found a proper destination, false if none was found + */ +bool PlayerbotClassAI::PlayerbotClassAI::FleeFromPointIfCan(uint32 radius, Unit* pTarget, float x0, float y0, float z0, float forcedAngle /* = 0.0f */) +{ + if (!m_bot) return false; + if (!m_ai) return false; + + // Get relative position to current target + // the bot will try to move on a tangential axis from it + float dist_from_target, angle_to_target; + if (pTarget) + { + dist_from_target = pTarget->GetDistance(m_bot); + if (dist_from_target > 0.2f) + angle_to_target = pTarget->GetAngle(m_bot); + else + angle_to_target = frand(0, 2 * M_PI_F); + } + else + { + dist_from_target = 0.0f; + angle_to_target = frand(0, 2 * M_PI_F); + } + + // Find coords to move to + // The bot will move for a distance equal to the spell radius + 1 yard for more safety + float dist = radius + 1.0f; + + float moveAngles[3] = {- M_PI_F / 2, M_PI_F / 2, 0.0f}; + float angle, x, y, z; + bool foundCoords; + for (uint8 i = 0; i < 3; i++) + { + // define an angle tangential to target's direction + angle = angle_to_target + moveAngles[i]; + // if an angle was provided, use it instead but only for the first iteration in case this does not lead to a valid point + if (forcedAngle != 0.0f) + { + angle = forcedAngle; + forcedAngle = 0.0f; + } + foundCoords = true; + + x = x0 + dist * cos(angle); + y = y0 + dist * sin(angle); + z = z0 + 0.5f; + + // try to fix z + if (!m_bot->GetMap()->GetHeightInRange(x, y, z)) + foundCoords = false; + + // check any collision + float testZ = z + 0.5f; // needed to avoid some false positive hit detection of terrain or passable little object + if (m_bot->GetMap()->GetHitPosition(x0, y0, z0 + 0.5f, x, y, testZ, -0.1f)) + { + z = testZ; + if (!m_bot->GetMap()->GetHeightInRange(x, y, z)) + foundCoords = false; + } + + if (foundCoords) + { + m_ai->InterruptCurrentCastingSpell(); + m_bot->GetMotionMaster()->MovePoint(0, x, y, z); + m_ai->SetIgnoreUpdateTime(2); + return true; + } + } -bool PlayerbotClassAI::BuffPlayer(Player* target) { return false; } + +/** + * GetDispelTarget() + * return Unit* Returns unit to be dispelled. First checks 'critical' Healer(s), next Tank(s), next Master (if different from:), next DPS. + * + * return NULL If NULL is returned, no healing is required. At all. + * + * Will need extensive re-write for co-operation amongst multiple healers. As it stands, multiple healers would all pick the same 'ideal' + * healing target. + */ +Player* PlayerbotClassAI::GetDispelTarget(DispelType dispelType, JOB_TYPE type, bool bMustBeOOC) +{ + if (!m_ai) return nullptr; + if (!m_bot) return nullptr; + if (!m_bot->isAlive() || m_bot->IsInDuel()) return nullptr; + if (bMustBeOOC && m_bot->isInCombat()) return nullptr; + + // First, fill the list of targets + if (m_bot->GetGroup()) + { + // define seperately for sorting purposes - DO NOT CHANGE ORDER! + std::vector targets; + + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->isAlive()) + continue; + JOB_TYPE job = GetTargetJob(groupMember); + if (job & type) + { + uint32 dispelMask = GetDispellMask(dispelType); + Unit::SpellAuraHolderMap const& auras = groupMember->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellAuraHolder *holder = itr->second; + // Only return group members with negative magic effect + if (dispelType == DISPEL_MAGIC && holder->IsPositive()) + continue; + // poison, disease and curse are always negative: return everyone + if ((1 << holder->GetSpellProto()->Dispel) & dispelMask) + targets.push_back( heal_priority(groupMember, 0, job) ); + } + } + } + + // Sorts according to type: Healers first, tanks next, then master followed by DPS, thanks to the order of the TYPE enum + std::sort(targets.begin(), targets.end()); + + if (targets.size()) + return targets.at(0).p; + } + + return nullptr; +} + +Player* PlayerbotClassAI::GetResurrectionTarget(JOB_TYPE type, bool bMustBeOOC) +{ + if (!m_ai) return nullptr; + if (!m_bot) return nullptr; + if (!m_bot->isAlive() || m_bot->IsInDuel()) return nullptr; + if (bMustBeOOC && m_bot->isInCombat()) return nullptr; + + // First, fill the list of targets + if (m_bot->GetGroup()) + { + // define seperately for sorting purposes - DO NOT CHANGE ORDER! + std::vector targets; + + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || groupMember->isAlive()) + continue; + JOB_TYPE job = GetTargetJob(groupMember); + if (job & type) + targets.push_back( heal_priority(groupMember, 0, job) ); + } + + // Sorts according to type: Healers first, tanks next, then master followed by DPS, thanks to the order of the TYPE enum + std::sort(targets.begin(), targets.end()); + + if (targets.size()) + return targets.at(0).p; + } + else if (!m_master->isAlive()) + return m_master; + + return nullptr; +} + +JOB_TYPE PlayerbotClassAI::GetTargetJob(Player* target) +{ + // is a bot + if (target->GetPlayerbotAI()) + { + if (target->GetPlayerbotAI()->IsHealer()) + return JOB_HEAL; + if (target->GetPlayerbotAI()->IsTank()) + return JOB_TANK; + return JOB_DPS; + } + + // figure out what to do with human players - i.e. figure out if they're tank, DPS or healer + uint32 uSpec = target->GetSpec(); + switch (target->getClass()) + { + case CLASS_PALADIN: + if (uSpec == PALADIN_SPEC_HOLY) + return JOB_HEAL; + if (uSpec == PALADIN_SPEC_PROTECTION) + return JOB_TANK; + return (m_master == target) ? JOB_MASTER : JOB_DPS; + case CLASS_DRUID: + if (uSpec == DRUID_SPEC_RESTORATION) + return JOB_HEAL; + // Feral can be used for both Tank or DPS... play it safe and assume tank. If not... he best be good at threat management or he'll ravage the healer's mana + else if (uSpec == DRUID_SPEC_FERAL) + return JOB_TANK; + return (m_master == target) ? JOB_MASTER : JOB_DPS; + case CLASS_PRIEST: + // Since Discipline can be used for both healer or DPS assume DPS + if (uSpec == PRIEST_SPEC_HOLY) + return JOB_HEAL; + return (m_master == target) ? JOB_MASTER : JOB_DPS; + case CLASS_SHAMAN: + if (uSpec == SHAMAN_SPEC_RESTORATION) + return JOB_HEAL; + return (m_master == target) ? JOB_MASTER : JOB_DPS; + case CLASS_WARRIOR: + if (uSpec == WARRIOR_SPEC_PROTECTION) + return JOB_TANK; + return (m_master == target) ? JOB_MASTER : JOB_DPS; + case CLASS_MAGE: + case CLASS_WARLOCK: + case CLASS_ROGUE: + case CLASS_HUNTER: + default: + return (m_master == target) ? JOB_MASTER : JOB_DPS; + } +} + +CombatManeuverReturns PlayerbotClassAI::CastSpellNoRanged(uint32 nextAction, Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (nextAction == 0) + return RETURN_NO_ACTION_OK; // Asked to do nothing so... yeh... Dooone. + + if (pTarget != nullptr) + return (m_ai->CastSpell(nextAction, *pTarget) ? RETURN_CONTINUE : RETURN_NO_ACTION_ERROR); + else + return (m_ai->CastSpell(nextAction) ? RETURN_CONTINUE : RETURN_NO_ACTION_ERROR); +} + +CombatManeuverReturns PlayerbotClassAI::CastSpellWand(uint32 nextAction, Unit *pTarget, uint32 SHOOT) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (SHOOT > 0 && m_bot->FindCurrentSpellBySpellId(SHOOT) && m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true)) + { + if (nextAction == SHOOT) + // At this point we're already shooting and are asked to shoot. Don't cause a global cooldown by stopping to shoot! Leave it be. + return RETURN_CONTINUE; // ... We're asked to shoot and are already shooting so... Task accomplished? + + // We are shooting but wish to cast a spell. Stop 'casting' shoot. + m_bot->InterruptNonMeleeSpells(true, SHOOT); + // ai->TellMaster("Interrupting auto shot."); + } + + // We've stopped ranged (if applicable), if no nextAction just return + if (nextAction == 0) + return RETURN_CONTINUE; // Asked to do nothing so... yeh... Dooone. + + if (nextAction == SHOOT) + { + if (SHOOT > 0 && m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED && !m_bot->FindCurrentSpellBySpellId(SHOOT) && m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true)) + return (m_ai->CastSpell(SHOOT, *pTarget) ? RETURN_CONTINUE : RETURN_NO_ACTION_ERROR); + else + // Do Melee attack + return RETURN_NO_ACTION_UNKNOWN; // We're asked to shoot and aren't. + } + + if (pTarget != nullptr) + return (m_ai->CastSpell(nextAction, *pTarget) ? RETURN_CONTINUE : RETURN_NO_ACTION_ERROR); + else + return (m_ai->CastSpell(nextAction) ? RETURN_CONTINUE : RETURN_NO_ACTION_ERROR); +} diff --git a/src/game/playerbot/PlayerbotClassAI.h b/src/game/playerbot/PlayerbotClassAI.h index c6dfe06a7..9260df90e 100644 --- a/src/game/playerbot/PlayerbotClassAI.h +++ b/src/game/playerbot/PlayerbotClassAI.h @@ -14,6 +14,26 @@ class Player; class PlayerbotAI; +enum JOB_TYPE +{ + JOB_HEAL = 0x01, + JOB_TANK = 0x02, + JOB_MASTER = 0x04, // Not a fan of this distinction but user (or rather, admin) choice + JOB_DPS = 0x08, + JOB_ALL = 0x0F, // all of the above + JOB_MANAONLY = 0x10 // for buff checking (NOTE: this means any with powertype mana AND druids (who may be shifted but still have mana) +}; + +struct heal_priority +{ + Player* p; + uint8 hp; + JOB_TYPE type; + heal_priority(Player* pin, uint8 hpin, JOB_TYPE t) : p(pin), hp(hpin), type(t) {} + // overriding the operator like this is not recommended for general use - however we won't use this struct for anything else + bool operator<(const heal_priority& a) const { return type < a.type; } +}; + class MANGOS_DLL_SPEC PlayerbotClassAI { public: @@ -21,25 +41,61 @@ class MANGOS_DLL_SPEC PlayerbotClassAI virtual ~PlayerbotClassAI(); // all combat actions go here - virtual bool DoFirstCombatManeuver(Unit*); - virtual void DoNextCombatManeuver(Unit*); + virtual CombatManeuverReturns DoFirstCombatManeuver(Unit*); + virtual CombatManeuverReturns DoNextCombatManeuver(Unit*); + bool Pull() { DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::Pull() rather than class specific function"); return false; } + bool Neutralize() { DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::Neutralize() rather than class specific function"); return false; } // all non combat actions go here, ex buffs, heals, rezzes virtual void DoNonCombatActions(); - - // buff a specific player, usually a real PC who is not in group - virtual bool BuffPlayer(Player* target); + bool EatDrinkBandage(bool bMana = true, unsigned char foodPercent = 50, unsigned char drinkPercent = 50, unsigned char bandagePercent = 70); // Utilities Player* GetMaster () { return m_master; } Player* GetPlayerBot() { return m_bot; } - PlayerbotAI* GetAI (){ return m_ai; }; + PlayerbotAI* GetAI() { return m_ai; } + bool CanPull(); + bool CastHoTOnTank(); + time_t GetWaitUntil() { return m_WaitUntil; } + void SetWait(uint8 t) { m_WaitUntil = m_ai->CurrentTime() + t; } + void ClearWait() { m_WaitUntil = 0; } + //void SetWaitUntil(time_t t) { m_WaitUntil = t; } + +protected: + virtual CombatManeuverReturns DoFirstCombatManeuverPVE(Unit*); + virtual CombatManeuverReturns DoNextCombatManeuverPVE(Unit*); + virtual CombatManeuverReturns DoFirstCombatManeuverPVP(Unit*); + virtual CombatManeuverReturns DoNextCombatManeuverPVP(Unit*); + + CombatManeuverReturns CastSpellNoRanged(uint32 nextAction, Unit *pTarget); + CombatManeuverReturns CastSpellWand(uint32 nextAction, Unit *pTarget, uint32 SHOOT); + virtual CombatManeuverReturns HealPlayer(Player* target); + CombatManeuverReturns Buff(bool (*BuffHelper)(PlayerbotAI*, uint32, Unit*), uint32 spellId, uint32 type = JOB_ALL, bool bMustBeOOC = true); + bool NeedGroupBuff(uint32 groupBuffSpellId, uint32 singleBuffSpellId); + Player* GetHealTarget(JOB_TYPE type = JOB_ALL); + Player* GetDispelTarget(DispelType dispelType, JOB_TYPE type = JOB_ALL, bool bMustBeOOC = false); + Player* GetResurrectionTarget(JOB_TYPE type = JOB_ALL, bool bMustBeOOC = true); + JOB_TYPE GetTargetJob(Player* target); + bool FleeFromAoEIfCan(uint32 spellId, Unit* pTarget); + bool FleeFromTrapGOIfCan(uint32 goEntry, Unit* pTarget); + bool FleeFromNpcWithAuraIfCan(uint32 NpcEntry, uint32 spellId, Unit* pTarget); + bool FleeFromPointIfCan(uint32 radius, Unit* pTarget, float x0, float y0, float z0, float forcedAngle = 0.0f); + + // These values are used in GetHealTarget and can be overridden per class (to accomodate healing spell health checks) + uint8 m_MinHealthPercentTank; + uint8 m_MinHealthPercentHealer; + uint8 m_MinHealthPercentDPS; + uint8 m_MinHealthPercentMaster; + + time_t m_WaitUntil; -private: Player* m_master; Player* m_bot; PlayerbotAI* m_ai; + + // first aid + uint32 RECENTLY_BANDAGED; }; #endif diff --git a/src/game/playerbot/PlayerbotDruidAI.cpp b/src/game/playerbot/PlayerbotDruidAI.cpp index 56a7faec7..b0445e8f1 100644 --- a/src/game/playerbot/PlayerbotDruidAI.cpp +++ b/src/game/playerbot/PlayerbotDruidAI.cpp @@ -3,6 +3,8 @@ Complete: maybe around 33% Authors : rrtn, Natsukawa Version : 0.42 + * + * @todo reintroduce TBC spells/abilities to AI () */ #include "PlayerbotDruidAI.h" #include "../SpellAuras.h" @@ -11,670 +13,677 @@ class PlayerbotAI; PlayerbotDruidAI::PlayerbotDruidAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - MOONFIRE = ai->initSpell(MOONFIRE_1); // attacks - STARFIRE = ai->initSpell(STARFIRE_1); - STARFALL = ai->initSpell(STARFALL_1); - WRATH = ai->initSpell(WRATH_1); - ROOTS = ai->initSpell(ENTANGLING_ROOTS_1); - INSECT_SWARM = ai->initSpell(INSECT_SWARM_1); - FORCE_OF_NATURE = ai->initSpell(FORCE_OF_NATURE_1); - HURRICANE = ai->initSpell(HURRICANE_1); - MARK_OF_THE_WILD = ai->initSpell(MARK_OF_THE_WILD_1); // buffs - GIFT_OF_THE_WILD = ai->initSpell(GIFT_OF_THE_WILD_1); - THORNS = ai->initSpell(THORNS_1); - BARKSKIN = ai->initSpell(BARKSKIN_1); - INNERVATE = ai->initSpell(INNERVATE_1); - FAERIE_FIRE = ai->initSpell(FAERIE_FIRE_1); // debuffs - REJUVENATION = ai->initSpell(REJUVENATION_1); // heals - REGROWTH = ai->initSpell(REGROWTH_1); - WILD_GROWTH = ai->initSpell(WILD_GROWTH_1); - LIFEBLOOM = ai->initSpell(LIFEBLOOM_1); - NOURISH = ai->initSpell(NOURISH_1); - HEALING_TOUCH = ai->initSpell(HEALING_TOUCH_1); - SWIFTMEND = ai->initSpell(SWIFTMEND_1); - TRANQUILITY = ai->initSpell(TRANQUILITY_1); - REVIVE = ai->initSpell(REVIVE_1); - REMOVE_CURSE = ai->initSpell(REMOVE_CURSE_DRUID_1); - ABOLISH_POISON = ai->initSpell(ABOLISH_POISON_1); + MOONFIRE = m_ai->initSpell(MOONFIRE_1); // attacks + STARFIRE = m_ai->initSpell(STARFIRE_1); + STARFALL = m_ai->initSpell(STARFALL_1); + WRATH = m_ai->initSpell(WRATH_1); + ROOTS = m_ai->initSpell(ENTANGLING_ROOTS_1); + INSECT_SWARM = m_ai->initSpell(INSECT_SWARM_1); + FORCE_OF_NATURE = m_ai->initSpell(FORCE_OF_NATURE_1); + HURRICANE = m_ai->initSpell(HURRICANE_1); + MARK_OF_THE_WILD = m_ai->initSpell(MARK_OF_THE_WILD_1); // buffs + GIFT_OF_THE_WILD = m_ai->initSpell(GIFT_OF_THE_WILD_1); + THORNS = m_ai->initSpell(THORNS_1); + BARKSKIN = m_ai->initSpell(BARKSKIN_1); + INNERVATE = m_ai->initSpell(INNERVATE_1); + FAERIE_FIRE = m_ai->initSpell(FAERIE_FIRE_1); // debuffs + FAERIE_FIRE_FERAL = m_ai->initSpell(FAERIE_FIRE_FERAL_1); + REJUVENATION = m_ai->initSpell(REJUVENATION_1); // heals + REGROWTH = m_ai->initSpell(REGROWTH_1); + WILD_GROWTH = m_ai->initSpell(WILD_GROWTH_1); + LIFEBLOOM = m_ai->initSpell(LIFEBLOOM_1); + NOURISH = m_ai->initSpell(NOURISH_1); + HEALING_TOUCH = m_ai->initSpell(HEALING_TOUCH_1); + SWIFTMEND = m_ai->initSpell(SWIFTMEND_1); + TRANQUILITY = m_ai->initSpell(TRANQUILITY_1); + REVIVE = m_ai->initSpell(REVIVE_1); + REMOVE_CURSE = m_ai->initSpell(REMOVE_CURSE_DRUID_1); + ABOLISH_POISON = m_ai->initSpell(ABOLISH_POISON_1); // Druid Forms - MOONKIN_FORM = ai->initSpell(MOONKIN_FORM_1); - DIRE_BEAR_FORM = ai->initSpell(DIRE_BEAR_FORM_1); - BEAR_FORM = ai->initSpell(BEAR_FORM_1); - CAT_FORM = ai->initSpell(CAT_FORM_1); - TREE_OF_LIFE = ai->initSpell(TREE_OF_LIFE_1); - TRAVEL_FORM = ai->initSpell(TRAVEL_FORM_1); + MOONKIN_FORM = m_ai->initSpell(MOONKIN_FORM_1); + DIRE_BEAR_FORM = m_ai->initSpell(DIRE_BEAR_FORM_1); + BEAR_FORM = m_ai->initSpell(BEAR_FORM_1); + CAT_FORM = m_ai->initSpell(CAT_FORM_1); + TREE_OF_LIFE = m_ai->initSpell(TREE_OF_LIFE_1); + TRAVEL_FORM = m_ai->initSpell(TRAVEL_FORM_1); // Cat Attack type's - RAKE = ai->initSpell(RAKE_1); - CLAW = ai->initSpell(CLAW_1); // 45 - COWER = ai->initSpell(COWER_1); // 20 - MANGLE = ai->initSpell(MANGLE_1); // 45 - TIGERS_FURY = ai->initSpell(TIGERS_FURY_1); + RAKE = m_ai->initSpell(RAKE_1); + CLAW = m_ai->initSpell(CLAW_1); // 45 + COWER = m_ai->initSpell(COWER_1); // 20 + MANGLE = m_ai->initSpell(MANGLE_1); // 45 + TIGERS_FURY = m_ai->initSpell(TIGERS_FURY_1); // Cat Finishing Move's - RIP = ai->initSpell(RIP_1); // 30 - FEROCIOUS_BITE = ai->initSpell(FEROCIOUS_BITE_1); // 35 - MAIM = ai->initSpell(MAIM_1); // 35 + RIP = m_ai->initSpell(RIP_1); // 30 + FEROCIOUS_BITE = m_ai->initSpell(FEROCIOUS_BITE_1); // 35 + MAIM = m_ai->initSpell(MAIM_1); // 35 // Bear/Dire Bear Attacks & Buffs - BASH = ai->initSpell(BASH_1); - MAUL = ai->initSpell(MAUL_1); // 15 - SWIPE = ai->initSpell(SWIPE_BEAR_1); // 20 - DEMORALIZING_ROAR = ai->initSpell(DEMORALIZING_ROAR_1); // 10 - CHALLENGING_ROAR = ai->initSpell(CHALLENGING_ROAR_1); - ENRAGE = ai->initSpell(ENRAGE_1); - GROWL = ai->initSpell(GROWL_1); + BASH = m_ai->initSpell(BASH_1); + MAUL = m_ai->initSpell(MAUL_1); // 15 + SWIPE = m_ai->initSpell(SWIPE_BEAR_1); // 20 + DEMORALIZING_ROAR = m_ai->initSpell(DEMORALIZING_ROAR_1); // 10 + CHALLENGING_ROAR = m_ai->initSpell(CHALLENGING_ROAR_1); + ENRAGE = m_ai->initSpell(ENRAGE_1); + GROWL = m_ai->initSpell(GROWL_1); RECENTLY_BANDAGED = 11196; // first aid check // racial - SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); - WAR_STOMP = ai->initSpell(WAR_STOMP_ALL); // tauren + SHADOWMELD = m_ai->initSpell(SHADOWMELD_ALL); + WAR_STOMP = m_ai->initSpell(WAR_STOMP_ALL); // tauren } PlayerbotDruidAI::~PlayerbotDruidAI() {} -bool PlayerbotDruidAI::HealTarget(Unit *target) +CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - uint8 hp = target->GetHealth() * 100 / target->GetMaxHealth(); - - //If spell exists and orders say we should be dispelling - if ((REMOVE_CURSE > 0 || ABOLISH_POISON > 0) && ai->GetCombatOrder() != PlayerbotAI::ORDERS_NODISPEL) + bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) { - //This does something important(lol) - uint32 dispelMask = GetDispellMask(DISPEL_CURSE); - uint32 dispelMask2 = GetDispellMask(DISPEL_POISON); - //Get a list of all the targets auras(spells affecting target) - Unit::SpellAuraHolderMap const& auras = target->GetSpellAuraHolderMap(); - //Iterate through the auras - for(Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) { - SpellAuraHolder *holder = itr->second; - //I dont know what this does but it doesn't work without it - if ((1<GetSpellProto()->Dispel) & dispelMask) - { - //If the spell is dispellable and we can dispel it, do so - if((holder->GetSpellProto()->Dispel == DISPEL_CURSE) & (REMOVE_CURSE > 0)) - ai->CastSpell(REMOVE_CURSE, *target); - return false; - } - else if ((1<GetSpellProto()->Dispel) & dispelMask2) + if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder()) { - if((holder->GetSpellProto()->Dispel == DISPEL_POISON) & (ABOLISH_POISON > 0)) - ai->CastSpell(ABOLISH_POISON, *target); - return false; + if (meleeReach) + { + // Set everyone's UpdateAI() waiting to 2 seconds + m_ai->SetGroupIgnoreUpdateTime(2); + // Clear their TEMP_WAIT_TANKAGGRO flag + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + // Start attacking, force target on current target + m_ai->Attack(m_ai->GetCurrentTarget()); + + // While everyone else is waiting 2 second, we need to build up aggro, so don't return + } + else + { + // TODO: add check if target is ranged + return RETURN_NO_ACTION_OK; // wait for target to get nearer + } } + else if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) + return _DoNextPVECombatManeuverHeal(); + else + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); } } - if (hp >= 70) - return false; + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } - // Reset form if needed - GoBuffForm(GetPlayerBot()); + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + } - if (hp < 70 && REJUVENATION > 0 && ai->In_Reach(target,REJUVENATION) && !target->HasAura(REJUVENATION) && ai->CastSpell(REJUVENATION, *target)) - return true; + return RETURN_NO_ACTION_ERROR; +} - if (hp < 60 && LIFEBLOOM > 0 && ai->In_Reach(target,LIFEBLOOM) && !target->HasAura(LIFEBLOOM) && ai->CastSpell(LIFEBLOOM, *target)) - return true; +CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - if (hp < 55 && REGROWTH > 0 && ai->In_Reach(target,REGROWTH) && !target->HasAura(REGROWTH) && ai->CastSpell(REGROWTH, *target)) - return true; +CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - if (hp < 50 && SWIFTMEND > 0 && ai->In_Reach(target,SWIFTMEND) && (target->HasAura(REJUVENATION) || target->HasAura(REGROWTH)) && ai->CastSpell(SWIFTMEND, *target)) - return true; +CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuver(Unit* pTarget) +{ + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoNextCombatManeuverPVE(pTarget); + } - if (hp < 45 && WILD_GROWTH > 0 && ai->In_Reach(target,WILD_GROWTH) && !target->HasAura(WILD_GROWTH) && ai->CastSpell(WILD_GROWTH, *target)) - return true; + return RETURN_NO_ACTION_ERROR; +} - if (hp < 30 && NOURISH > 0 && ai->In_Reach(target,NOURISH) && ai->CastSpell(NOURISH, *target)) - return true; +CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuverPVE(Unit* pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - if (hp < 25 && HEALING_TOUCH > 0 && ai->In_Reach(target,HEALING_TOUCH) && ai->CastSpell(HEALING_TOUCH, *target)) - return true; + bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); - return false; -} // end HealTarget + //uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); -void PlayerbotDruidAI::DoNextCombatManeuver(Unit *pTarget) -{ - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + uint32 spec = m_bot->GetSpec(); + if (spec == 0) // default to spellcasting or healing for healer + spec = (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder() ? DRUID_SPEC_RESTORATION : DRUID_SPEC_BALANCE); + + // Make sure healer stays put, don't even melee (aggro) if in range: only melee if feral spec AND not healer + if (!m_ai->IsHealer() && spec == DRUID_SPEC_FERAL && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + else // ranged combat in all other cases + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + + //Unit* pVictim = pTarget->getVictim(); + uint32 BEAR = (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM); - switch (ai->GetScenarioType()) + // TODO: do something to allow emergency heals for non-healers? + switch (CheckForms()) { - case PlayerbotAI::SCENARIO_PVP_DUEL: - ai->CastSpell(MOONFIRE); - return; + case RETURN_OK_SHIFTING: + return RETURN_CONTINUE; + + case RETURN_FAIL: + case RETURN_OK_CANNOTSHIFT: + if (spec == DRUID_SPEC_FERAL) + spec = DRUID_SPEC_BALANCE; // Can't shift, force spellcasting + break; // rest functions without form + + //case RETURN_OK_NOCHANGE: // great! + //case RETURN_FAIL_WAITINGONSELFBUFF: // This is war dammit! No time for silly buffs during combat... default: break; } - uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); - - Player *m_bot = GetPlayerBot(); - Unit* pVictim = pTarget->getVictim(); + // Low mana and bot is a caster/healer: cast Innervate on self + // TODO add group check to also cast on low mana healers or master + if (m_ai->GetManaPercent() < 15 && ((m_ai->IsHealer() || spec == DRUID_SPEC_RESTORATION))) + if (INNERVATE > 0 && !m_bot->HasAura(INNERVATE, EFFECT_INDEX_0) && CastSpell(INNERVATE, m_bot)) + return RETURN_CONTINUE; - if (ai->IsTank()) - SpellSequence = DruidTank; - else if (ai->IsDPS()) - SpellSequence = DruidSpell; - else if (ai->IsHealer()) - SpellSequence = DruidHeal; - else - SpellSequence = DruidCombat; - - switch (SpellSequence) + //Used to determine if this bot is highest on threat + Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + if (newTarget && !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank { - case DruidTank: // Its now a tank druid! - //ai->TellMaster("DruidTank"); + if (HealPlayer(m_bot) == RETURN_CONTINUE) + return RETURN_CONTINUE; - if (!m_bot->HasInArc(M_PI_F, pTarget)) - { - if (pVictim) - pVictim->Attack(pTarget, true); - } - if (m_bot->HasAura(CAT_FORM, EFFECT_INDEX_0)) - m_bot->RemoveAurasDueToSpell(768); - //ai->TellMaster("FormClearCat"); - if (MOONKIN_FORM > 0 && !m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) - ai->CastSpell (MOONKIN_FORM); - else if (DIRE_BEAR_FORM > 0 && !m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && !m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0)) - ai->CastSpell (DIRE_BEAR_FORM); - else if (BEAR_FORM > 0 && !m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && !m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) && !m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) - ai->CastSpell (BEAR_FORM); - else if (DEMORALIZING_ROAR > 0 && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && !m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && !pTarget->HasAura(DEMORALIZING_ROAR, EFFECT_INDEX_0) && ai->GetRageAmount() >= 10) - ai->CastSpell(DEMORALIZING_ROAR, *pTarget); - if (FAERIE_FIRE > 0 && ai->In_Reach(pTarget,FAERIE_FIRE) && DruidSpellCombat < 1 && !pTarget->HasAura(FAERIE_FIRE, EFFECT_INDEX_0)) - { - ai->CastSpell(FAERIE_FIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (MOONFIRE > 0 && ai->In_Reach(pTarget,MOONFIRE) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 2 && !pTarget->HasAura(MOONFIRE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 24) - { - ai->CastSpell(MOONFIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (ROOTS > 0 && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 3 && !pTarget->HasAura(ROOTS, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(ROOTS, *pTarget); - DruidSpellCombat++; - break; - } - else if (HURRICANE > 0 && ai->In_Reach(pTarget,HURRICANE) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && ai->GetAttackerCount() >= 5 && DruidSpellCombat < 4 && ai->GetManaPercent() >= 91) - { - //ai->TellMaster("casting hurricane!"); - ai->CastSpell(HURRICANE, *pTarget); - ai->SetIgnoreUpdateTime(10); - DruidSpellCombat++; - break; - } - else if (WRATH > 0 && ai->In_Reach(pTarget,WRATH) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 5 && ai->GetManaPercent() >= 13) - { - ai->CastSpell(WRATH, *pTarget); - DruidSpellCombat++; - break; - } - else if (INSECT_SWARM > 0 && ai->In_Reach(pTarget,INSECT_SWARM) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 6 && !pTarget->HasAura(INSECT_SWARM, EFFECT_INDEX_0) && ai->GetManaPercent() >= 9) - { - ai->CastSpell(INSECT_SWARM, *pTarget); - DruidSpellCombat++; - break; - } - else if (STARFIRE > 0 && ai->In_Reach(pTarget,STARFIRE) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 7 && ai->GetManaPercent() >= 18) - { - ai->CastSpell(STARFIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (FORCE_OF_NATURE > 0 && ai->In_Reach(pTarget,FORCE_OF_NATURE) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 8 && ai->GetManaPercent() >= 12) - { - //ai->TellMaster("summoning treants."); - ai->CastSpell(FORCE_OF_NATURE); - DruidSpellCombat++; - break; - } - else if (STARFALL > 0 && ai->In_Reach(pTarget,STARFALL) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && !m_bot->HasAura(STARFALL, EFFECT_INDEX_0) && ai->GetAttackerCount() >= 3 && DruidSpellCombat < 9 && ai->GetManaPercent() >= 39) - { - ai->CastSpell(STARFALL, *pTarget); - DruidSpellCombat++; - break; - } - else if (BARKSKIN > 0 && pVictim == m_bot && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && ai->GetHealthPercent() < 75 && DruidSpellCombat < 10 && !m_bot->HasAura(BARKSKIN, EFFECT_INDEX_0)) - { - ai->CastSpell(BARKSKIN, *m_bot); - DruidSpellCombat++; - break; - } - else if (INNERVATE > 0 && ai->In_Reach(pTarget,INNERVATE) && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && ai->GetManaPercent() < 50 && DruidSpellCombat < 11 && !m_bot->HasAura(INNERVATE, EFFECT_INDEX_0)) - { - ai->CastSpell(INNERVATE, *m_bot); - DruidSpellCombat++; - break; - } - else if (ENRAGE > 0 && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && DruidSpellCombat < 2 && !m_bot->HasAura(ENRAGE, EFFECT_INDEX_0)) - { - ai->CastSpell(ENRAGE, *m_bot); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (SWIPE > 0 && ai->In_Reach(pTarget,SWIPE) && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && DruidSpellCombat < 4 && ai->GetRageAmount() >= 20) - { - ai->CastSpell(SWIPE, *pTarget); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (MAUL > 0 && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && DruidSpellCombat < 6 && ai->GetRageAmount() >= 15) - { - ai->CastSpell(MAUL, *pTarget); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (BASH > 0 && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && !pTarget->HasAura(BASH, EFFECT_INDEX_0) && DruidSpellCombat < 8 && ai->GetRageAmount() >= 10) - { - ai->CastSpell(BASH, *pTarget); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (CHALLENGING_ROAR > 0 && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && pVictim != m_bot && DruidSpellCombat < 10 && !pTarget->HasAura(CHALLENGING_ROAR, EFFECT_INDEX_0) && !pTarget->HasAura(GROWL, EFFECT_INDEX_0) && ai->GetRageAmount() >= 15) - { - ai->CastSpell(CHALLENGING_ROAR, *pTarget); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (GROWL > 0 && ai->In_Reach(pTarget,GROWL) && (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0) || m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) && pVictim != m_bot && DruidSpellCombat < 12 && !pTarget->HasAura(CHALLENGING_ROAR, EFFECT_INDEX_0) && !pTarget->HasAura(GROWL, EFFECT_INDEX_0)) - { - ai->CastSpell(GROWL, *pTarget); - DruidSpellCombat = DruidSpellCombat + 2; - break; - } - else if (DruidSpellCombat > 13) - { - DruidSpellCombat = 0; - break; - } - else + // Aggroed by an elite that came in melee range + if (m_ai->IsElite(newTarget) && meleeReach) + { + // protect the bot with barkskin: the increased casting time is meaningless + // because bot will then avoid to cast to not angry mob further + if (m_ai->IsHealer() || spec == DRUID_SPEC_RESTORATION || spec == DRUID_SPEC_BALANCE) { - DruidSpellCombat = 0; - break; - } - break; + if (BARKSKIN > 0 && !m_bot->HasAura(BARKSKIN, EFFECT_INDEX_0) && CastSpell(BARKSKIN, m_bot)) + return RETURN_CONTINUE; - case DruidSpell: - //ai->TellMaster("DruidSpell"); - if (m_bot->HasAura(CAT_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(768); - //ai->TellMaster("FormClearCat"); - break; - } - if (m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(5487); - //ai->TellMaster("FormClearBear"); - break; - } - if (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(9634); - //ai->TellMaster("FormClearDireBear"); - break; + return RETURN_NO_ACTION_OK; } - if (m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(24858); - //ai->TellMaster("FormClearMoonkin"); - break; - } - if (FAERIE_FIRE > 0 && ai->In_Reach(pTarget,FAERIE_FIRE) && DruidSpellCombat < 1 && !pTarget->HasAura(FAERIE_FIRE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(FAERIE_FIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (MOONFIRE > 0 && ai->In_Reach(pTarget,MOONFIRE) && DruidSpellCombat < 2 && !pTarget->HasAura(MOONFIRE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 24) - { - ai->CastSpell(MOONFIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (ROOTS > 0 && DruidSpellCombat < 3 && !pTarget->HasAura(ROOTS, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(ROOTS, *pTarget); - DruidSpellCombat++; - break; - } - else if (HURRICANE > 0 && ai->In_Reach(pTarget,HURRICANE) && ai->GetAttackerCount() >= 5 && DruidSpellCombat < 4 && ai->GetManaPercent() >= 91) - { - //ai->TellMaster("casting hurricane!"); - ai->CastSpell(HURRICANE, *pTarget); - ai->SetIgnoreUpdateTime(10); - DruidSpellCombat++; - break; - } - else if (WRATH > 0 && ai->In_Reach(pTarget,WRATH) && DruidSpellCombat < 5 && ai->GetManaPercent() >= 13) - { - ai->CastSpell(WRATH, *pTarget); - DruidSpellCombat++; - break; - } - else if (INSECT_SWARM > 0 && ai->In_Reach(pTarget,INSECT_SWARM) && DruidSpellCombat < 6 && !pTarget->HasAura(INSECT_SWARM, EFFECT_INDEX_0) && ai->GetManaPercent() >= 9) - { - ai->CastSpell(INSECT_SWARM, *pTarget); - DruidSpellCombat++; - break; - } - else if (STARFIRE > 0 && ai->In_Reach(pTarget,STARFIRE) && DruidSpellCombat < 7 && ai->GetManaPercent() >= 18) - { - ai->CastSpell(STARFIRE, *pTarget); - DruidSpellCombat++; - break; - } - else if (FORCE_OF_NATURE > 0 && ai->In_Reach(pTarget,FORCE_OF_NATURE) && DruidSpellCombat < 8 && ai->GetManaPercent() >= 12) - { - //ai->TellMaster("summoning treants."); - ai->CastSpell(FORCE_OF_NATURE); - DruidSpellCombat++; - break; - } - else if (STARFALL > 0 && ai->In_Reach(pTarget,STARFALL) && !m_bot->HasAura(STARFALL, EFFECT_INDEX_0) && ai->GetAttackerCount() >= 3 && DruidSpellCombat < 9 && ai->GetManaPercent() >= 39) - { - ai->CastSpell(STARFALL, *pTarget); - DruidSpellCombat++; - break; - } - else if (BARKSKIN > 0 && pVictim == m_bot && ai->GetHealthPercent() < 75 && DruidSpellCombat < 10 && !m_bot->HasAura(BARKSKIN, EFFECT_INDEX_0)) - { - ai->CastSpell(BARKSKIN, *m_bot); - DruidSpellCombat++; - break; - } - else if (INNERVATE > 0 && ai->In_Reach(pTarget,INNERVATE) && ai->GetManaPercent() < 50 && DruidSpellCombat < 11 && !m_bot->HasAura(INNERVATE, EFFECT_INDEX_0)) - { - ai->CastSpell(INNERVATE, *m_bot); - DruidSpellCombat++; - break; - } - else if (DruidSpellCombat > 13) - { - DruidSpellCombat = 0; - break; - } - else - { - DruidSpellCombat = 0; - break; - } - break; + //no other cases: cats have cower in the damage rotation and bears can tank + } + } - case DruidHeal: - //ai->TellMaster("DruidHeal"); - if (m_bot->HasAura(CAT_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(768); - //ai->TellMaster("FormClearCat"); - break; - } - if (m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(5487); - //ai->TellMaster("FormClearBear"); - break; - } - if (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(9634); - //ai->TellMaster("FormClearDireBear"); - break; - } - if (m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(24858); - //ai->TellMaster("FormClearMoonkin"); - break; - } - if (ai->GetHealthPercent() <= 40) - { - HealTarget (m_bot); - break; - } - if (masterHP <= 40) - { - HealTarget (GetMaster()); - break; - } - else - { - DruidSpellCombat = 0; - break; - } - break; + if (m_ai->IsHealer()) + if (_DoNextPVECombatManeuverHeal() & RETURN_CONTINUE) + return RETURN_CONTINUE; - case DruidCombat: - //ai->TellMaster("DruidCombat"); - if (!m_bot->HasInArc(M_PI_F, pTarget)) - { - if (pVictim) - pVictim->Attack(pTarget, true); - } - if (m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(5487); - //ai->TellMaster("FormClearBear"); - break; - } - if (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(9634); - //ai->TellMaster("FormClearDireBear"); - break; - } - if (m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) - { - m_bot->RemoveAurasDueToSpell(24858); - //ai->TellMaster("FormClearMoonkin"); - break; - } - if (CAT_FORM > 0 && !m_bot->HasAura(CAT_FORM, EFFECT_INDEX_0)) - ai->CastSpell (CAT_FORM); - if (MAIM > 0 && m_bot->GetComboPoints() >= 1 && pTarget->IsNonMeleeSpellCasted(true)) - { - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("SpellPreventing Maim"); - break; - } + switch (spec) + { + case DRUID_SPEC_FERAL: + if (BEAR > 0 && m_bot->HasAura(BEAR)) + return _DoNextPVECombatManeuverBear(pTarget); + if (CAT_FORM > 0 && m_bot->HasAura(CAT_FORM)) + return _DoNextPVECombatManeuverCat(pTarget); + // NO break - failover to DRUID_SPEC_BALANCE + + case DRUID_SPEC_RESTORATION: // There is no Resto DAMAGE rotation. If you insist, go Balance... + case DRUID_SPEC_BALANCE: + if (m_bot->HasAura(BEAR) || m_bot->HasAura(CAT_FORM)) + return RETURN_NO_ACTION_UNKNOWN; // Didn't shift out of inappropriate form + + return _DoNextPVECombatManeuverSpellDPS(pTarget); + + /*if (BASH > 0 && !pTarget->HasAura(BASH, EFFECT_INDEX_0) && DruidSpellCombat < 5 && CastSpell(BASH, pTarget)) + return RETURN_CONTINUE; + if (CHALLENGING_ROAR > 0 && pVictim != m_bot && !pTarget->HasAura(CHALLENGING_ROAR, EFFECT_INDEX_0) && !pTarget->HasAura(GROWL, EFFECT_INDEX_0) && CastSpell(CHALLENGING_ROAR, pTarget)) + return RETURN_CONTINUE; + if (ROOTS > 0 && !pTarget->HasAura(ROOTS, EFFECT_INDEX_0) && CastSpell(ROOTS, pTarget)) + return RETURN_CONTINUE; + */ + } - if (RAKE > 0 && m_bot->GetComboPoints() <= 1 && ai->GetEnergyAmount() >= 40) - { - ai->CastSpell (RAKE, *pTarget); - //ai->TellMaster("Rake"); - break; - } - else if (CLAW > 0 && m_bot->GetComboPoints() <= 2 && ai->GetEnergyAmount() >= 45) - { - ai->CastSpell (CLAW, *pTarget); - //ai->TellMaster("Claw"); - break; - } - else if (MANGLE > 0 && m_bot->GetComboPoints() <= 3 && ai->GetEnergyAmount() >= 45) - { - ai->CastSpell (MANGLE, *pTarget); - //ai->TellMaster("Mangle"); - break; - } - else if (CLAW > 0 && m_bot->GetComboPoints() <= 4 && ai->GetEnergyAmount() >= 45) - { - ai->CastSpell (CLAW, *pTarget); - //ai->TellMaster("Claw2"); - break; - } + return RETURN_NO_ACTION_UNKNOWN; +} // end DoNextCombatManeuver - if (m_bot->GetComboPoints() == 5) - { - if (RIP > 0 && pTarget->getClass() == CLASS_ROGUE && ai->GetEnergyAmount() >= 30) - ai->CastSpell(RIP, *pTarget); - //ai->TellMaster("Rogue Rip"); - else if (MAIM > 0 && pTarget->getClass() == CLASS_DRUID && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Druid Maim"); - else if (MAIM > 0 && pTarget->getClass() == CLASS_SHAMAN && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Shaman Maim"); - else if (MAIM > 0 && pTarget->getClass() == CLASS_WARLOCK && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Warlock Maim"); - else if (FEROCIOUS_BITE > 0 && pTarget->getClass() == CLASS_HUNTER && ai->GetEnergyAmount() >= 35) - ai->CastSpell(FEROCIOUS_BITE, *pTarget); - //ai->TellMaster("Hunter Ferocious Bite"); - else if (FEROCIOUS_BITE > 0 && pTarget->getClass() == CLASS_WARRIOR && ai->GetEnergyAmount() >= 35) - ai->CastSpell(FEROCIOUS_BITE, *pTarget); - //ai->TellMaster("Warrior Ferocious Bite"); - else if (FEROCIOUS_BITE > 0 && pTarget->getClass() == CLASS_PALADIN && ai->GetEnergyAmount() >= 35) - ai->CastSpell(FEROCIOUS_BITE, *pTarget); - //ai->TellMaster("Paladin Ferocious Bite"); - else if (MAIM > 0 && pTarget->getClass() == CLASS_MAGE && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Mage Maim"); - else if (MAIM > 0 && pTarget->getClass() == CLASS_PRIEST && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Priest Maim"); - else if (MAIM > 0 && ai->GetEnergyAmount() >= 35) - ai->CastSpell(MAIM, *pTarget); - //ai->TellMaster("Else Maim"); - break; - } - else +CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + if (m_ai->CastSpell(MOONFIRE)) + return RETURN_CONTINUE; + + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} + +CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverBear(Unit* pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (!m_bot->HasAura( (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM) )) return RETURN_NO_ACTION_ERROR; + + // Used to determine if this bot is highest on threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + + // Face enemy, make sure you're attacking + m_ai->FaceTarget(pTarget); + + if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder() && !newTarget && GROWL > 0 && !m_bot->HasSpellCooldown(GROWL)) + if (CastSpell(GROWL, pTarget)) + return RETURN_CONTINUE; + + if (FAERIE_FIRE_FERAL > 0 && m_ai->In_Reach(pTarget,FAERIE_FIRE_FERAL) && !pTarget->HasAura(FAERIE_FIRE_FERAL, EFFECT_INDEX_0)) + if (CastSpell(FAERIE_FIRE_FERAL, pTarget)) + return RETURN_CONTINUE; + + if (SWIPE > 0 && m_ai->In_Reach(pTarget,SWIPE) && m_ai->GetAttackerCount() >= 2 && CastSpell(SWIPE, pTarget)) + return RETURN_CONTINUE; + + if (ENRAGE > 0 && !m_bot->HasSpellCooldown(ENRAGE) && CastSpell(ENRAGE, m_bot)) + return RETURN_CONTINUE; + + if (DEMORALIZING_ROAR > 0 && !pTarget->HasAura(DEMORALIZING_ROAR, EFFECT_INDEX_0) && CastSpell(DEMORALIZING_ROAR, pTarget)) + return RETURN_CONTINUE; + + if (MAUL > 0 && CastSpell(MAUL, pTarget)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} + +CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverCat(Unit* pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (!m_bot->HasAura(CAT_FORM)) return RETURN_NO_ACTION_UNKNOWN; + + //Used to determine if this bot is highest on threat + Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + + // Face enemy, make sure you're attacking + m_ai->FaceTarget(pTarget); + + // Attempt to do a finishing move + if (m_bot->GetComboPoints() >= 5) + { + if (RIP > 0 && !pTarget->HasAura(RIP, EFFECT_INDEX_0)) + { + if (CastSpell(RIP, pTarget)) + return RETURN_CONTINUE; + } + // 35 Energy + else if (FEROCIOUS_BITE > 0) + { + if (CastSpell(FEROCIOUS_BITE, pTarget)) + return RETURN_CONTINUE; + } + } // End 5 ComboPoints + + if (newTarget && COWER > 0 && !m_bot->HasSpellCooldown(COWER) && CastSpell(COWER, pTarget)) + return RETURN_CONTINUE; + + if (SHRED > 0 && !pTarget->HasInArc(M_PI_F, m_bot) && m_ai->CastSpell(SHRED, *pTarget)) + return RETURN_CONTINUE; + + if (FAERIE_FIRE_FERAL > 0 && m_ai->In_Reach(pTarget,FAERIE_FIRE_FERAL) && !pTarget->HasAura(FAERIE_FIRE_FERAL, EFFECT_INDEX_0) && CastSpell(FAERIE_FIRE_FERAL, pTarget)) + return RETURN_CONTINUE; + + if (TIGERS_FURY > 0 && !m_bot->HasSpellCooldown(TIGERS_FURY) && !m_bot->HasAura(TIGERS_FURY, EFFECT_INDEX_0) && CastSpell(TIGERS_FURY)) + return RETURN_CONTINUE; + + if (RAKE > 0 && !pTarget->HasAura(RAKE) && CastSpell(RAKE, pTarget)) + return RETURN_CONTINUE; + + if (CLAW > 0 && CastSpell(CLAW, pTarget)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} + +CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverSpellDPS(Unit* pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + uint32 NATURE = (STARFIRE > 0 ? STARFIRE : WRATH); + + // Face enemy, make sure you're attacking + m_ai->FaceTarget(pTarget); + + if (FAERIE_FIRE > 0 && m_ai->In_Reach(pTarget,FAERIE_FIRE) && !pTarget->HasAura(FAERIE_FIRE, EFFECT_INDEX_0) && CastSpell(FAERIE_FIRE, pTarget)) + return RETURN_CONTINUE; + + if (INSECT_SWARM > 0 && m_ai->In_Reach(pTarget,INSECT_SWARM) && !pTarget->HasAura(INSECT_SWARM, EFFECT_INDEX_0) && CastSpell(INSECT_SWARM, pTarget)) + return RETURN_CONTINUE; + + // Healer? Don't waste more mana on DPS + if (m_ai->IsHealer()) + return RETURN_NO_ACTION_OK; + + if (MOONFIRE > 0 && m_ai->In_Reach(pTarget,MOONFIRE) && !pTarget->HasAura(MOONFIRE, EFFECT_INDEX_0) && CastSpell(MOONFIRE, pTarget)) + return RETURN_CONTINUE; + + if (NATURE > 0 && CastSpell(NATURE, pTarget)) + return RETURN_CONTINUE; + + if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE) + m_bot->Attack(pTarget, true); + else + m_bot->AttackStop(); + + return RETURN_NO_ACTION_UNKNOWN; +} + +CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverHeal() +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} + +CombatManeuverReturns PlayerbotDruidAI::HealPlayer(Player* target) +{ + CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target); + if (r != RETURN_NO_ACTION_OK) + return r; + + if (!target->isAlive()) + { + if (m_bot->isInCombat()) + { + if (REBIRTH && m_ai->In_Reach(target,REBIRTH) && !m_bot->HasSpellCooldown(REBIRTH) && m_ai->CastSpell(REBIRTH, *target)) { - DruidSpellCombat = 0; - break; + std::string msg = "Resurrecting "; + msg += target->GetName(); + m_bot->Say(msg, LANG_UNIVERSAL); + return RETURN_CONTINUE; } - break; - } -} // end DoNextCombatManeuver + } -void PlayerbotDruidAI::DoNonCombatActions() -{ - Player * m_bot = GetPlayerBot(); - Player * master = GetMaster(); - if (!m_bot || !master) - return; + return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM + } - PlayerbotAI* ai = GetAI(); + // Remove curse on group members if orders allow bot to do so + if (Player* pCursedTarget = GetDispelTarget(DISPEL_CURSE)) + { + if (REMOVE_CURSE > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && CastSpell(REMOVE_CURSE, pCursedTarget)) + return RETURN_CONTINUE; + } + + // Remove poison on group members if orders allow bot to do so + if (Player* pPoisonedTarget = GetDispelTarget(DISPEL_POISON)) + { + uint32 cure = ABOLISH_POISON > 0 ? ABOLISH_POISON : CURE_POISON; + if (cure > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && CastSpell(cure, pPoisonedTarget)) + return RETURN_CONTINUE; + } - // mana check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + uint8 hp = target->GetHealthPercent(); - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); + // Define a tank bot will look at + Unit* pMainTank = GetHealTarget(JOB_TANK); - if (pItem != nullptr && ai->GetManaPercent() < 30) + // If target is out of range (40 yards) and is a tank: move towards it + // Other classes have to adjust their position to the healers + // TODO: This code should be common to all healers and will probably + // move to a more suitable place + if (pMainTank && !m_ai->In_Reach(pMainTank, HEALING_TOUCH)) { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); - return; + m_bot->GetMotionMaster()->MoveFollow(target, 39.0f, m_bot->GetOrientation()); + return RETURN_CONTINUE; } - else if (!pItem && INNERVATE > 0 && !m_bot->HasAura(INNERVATE) && ai->GetManaPercent() <= 20 && ai->CastSpell(INNERVATE, *m_bot)) - return; - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + // Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check) + if (hp >= 80) + return RETURN_NO_ACTION_OK; - pItem = ai->FindFood(); + // Start heals. Do lowest HP checks at the top - if (pItem != nullptr && ai->GetHealthPercent() < 30) + // Emergency heal: target needs to be healed NOW! + if ((target == pMainTank && hp < 10) || (target != pMainTank && hp < 15)) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); - return; + // first try Nature's Swiftness + Healing Touch: instant heal + if (NATURES_SWIFTNESS > 0 && !m_bot->HasSpellCooldown(NATURES_SWIFTNESS) && CastSpell(NATURES_SWIFTNESS, m_bot)) + return RETURN_CONTINUE; + + if (HEALING_TOUCH > 0 && m_bot->HasAura(NATURES_SWIFTNESS, EFFECT_INDEX_0) && m_ai->In_Reach(target,HEALING_TOUCH) && CastSpell(HEALING_TOUCH, target)) + return RETURN_CONTINUE; + + // Else try to Swiftmend the target if druid HoT is active on it + if (SWIFTMEND > 0 && !m_bot->HasSpellCooldown(SWIFTMEND) && m_ai->In_Reach(target,SWIFTMEND) && (target->HasAura(REJUVENATION) || target->HasAura(REGROWTH)) && CastSpell(SWIFTMEND, target)) + return RETURN_CONTINUE; } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + + // Urgent heal: target won't die next second, but first bot needs to gain some time to cast Healing Touch safely + if ((target == pMainTank && hp < 15) || (target != pMainTank && hp < 25)) { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); - return; + if (REGROWTH > 0 && m_ai->In_Reach(target,REGROWTH) && !target->HasAura(REGROWTH) && CastSpell(REGROWTH, target)) + return RETURN_CONTINUE; + if (REJUVENATION > 0 && m_ai->In_Reach(target,REJUVENATION) && target->HasAura(REGROWTH) && !target->HasAura(REJUVENATION) && CastSpell(REJUVENATION, target)) + return RETURN_CONTINUE; + if (SWIFTMEND > 0 && !m_bot->HasSpellCooldown(SWIFTMEND) && m_ai->In_Reach(target,SWIFTMEND) && (target->HasAura(REJUVENATION) || target->HasAura(REGROWTH)) && CastSpell(SWIFTMEND, target)) + return RETURN_CONTINUE; } - // buff and heal master's group - if (master->GetGroup()) + if (hp < 60 && HEALING_TOUCH > 0 && m_ai->In_Reach(target,HEALING_TOUCH) && CastSpell(HEALING_TOUCH, target)) + return RETURN_CONTINUE; + + if (hp < 80 && REJUVENATION > 0 && m_ai->In_Reach(target,REJUVENATION) && !target->HasAura(REJUVENATION) && CastSpell(REJUVENATION, target)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} // end HealTarget + +/** +* CheckForms() +* +* Returns bool - Value indicates success - shape was shifted, already shifted, no need to shift. +*/ +uint8 PlayerbotDruidAI::CheckForms() +{ + if (!m_ai) return RETURN_FAIL; + if (!m_bot) return RETURN_FAIL; + + uint32 spec = m_bot->GetSpec(); + uint32 BEAR = (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM); + + // if bot has healing orders always shift to humanoid form + // regardless of spec + if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) || spec == DRUID_SPEC_RESTORATION) { - // Buff master with group buff - if (master->isAlive() && GIFT_OF_THE_WILD && ai->HasSpellReagents(GIFT_OF_THE_WILD) && ai->Buff(GIFT_OF_THE_WILD, master)) - return; + if (m_bot->HasAura(CAT_FORM, EFFECT_INDEX_0)) + { + m_bot->RemoveAurasDueToSpell(CAT_FORM_1); + //m_ai->TellMaster("FormClearCat"); + return RETURN_OK_SHIFTING; + } + if (m_bot->HasAura(BEAR_FORM, EFFECT_INDEX_0)) + { + m_bot->RemoveAurasDueToSpell(BEAR_FORM_1); + //m_ai->TellMaster("FormClearBear"); + return RETURN_OK_SHIFTING; + } + if (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_INDEX_0)) + { + m_bot->RemoveAurasDueToSpell(DIRE_BEAR_FORM_1); + //m_ai->TellMaster("FormClearDireBear"); + return RETURN_OK_SHIFTING; + } + // spellcasting form, but disables healing spells so it's got to go + if (m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) + { + m_bot->RemoveAurasDueToSpell(MOONKIN_FORM_1); + //m_ai->TellMaster("FormClearMoonkin"); + return RETURN_OK_SHIFTING; + } + + return RETURN_OK_NOCHANGE; + } + + if (spec == DRUID_SPEC_BALANCE) + { + if (m_bot->HasAura(MOONKIN_FORM)) + return RETURN_OK_NOCHANGE; + + if (!MOONKIN_FORM) + return RETURN_OK_CANNOTSHIFT; + + if (CastSpell(MOONKIN_FORM)) + return RETURN_OK_SHIFTING; + else + return RETURN_FAIL; + } - Group::MemberSlotList const& groupSlot = GetMaster()->GetGroup()->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + if (spec == DRUID_SPEC_FERAL) + { + // Use Bear form only if we are told we're a tank and have thorns up + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) { - Player *tPlayer = sObjectMgr.GetPlayer(itr->guid); - if (!tPlayer || tPlayer == m_bot) - continue; + if (m_bot->HasAura(BEAR)) + return RETURN_OK_NOCHANGE; + + if (!BEAR) + return RETURN_OK_CANNOTSHIFT; + + if (!m_bot->HasAura(THORNS)) + return RETURN_FAIL_WAITINGONSELFBUFF; - // Resurrect member if needed - if (!tPlayer->isAlive()) + if (CastSpell(BEAR)) + return RETURN_OK_SHIFTING; + else + return RETURN_FAIL; + } + else // No tank orders - try to go kitty or at least bear + { + if (CAT_FORM > 0) { - if (ai->CastSpell(REVIVE, *tPlayer)) - { - std::string msg = "Resurrecting "; - msg += tPlayer->GetName(); - m_bot->Say(msg, LANG_UNIVERSAL); - return; - } + if (m_bot->HasAura(CAT_FORM)) + return RETURN_OK_NOCHANGE; + + if (CastSpell(CAT_FORM)) + return RETURN_OK_SHIFTING; else - continue; + return RETURN_FAIL; } - else + + if (BEAR > 0) { - // buff and heal - if (BuffPlayer(tPlayer)) - return; + if (m_bot->HasAura(BEAR)) + return RETURN_OK_NOCHANGE; - if (HealTarget(tPlayer)) - return; + if (CastSpell(BEAR)) + return RETURN_OK_SHIFTING; + else + return RETURN_FAIL; } + + return RETURN_OK_CANNOTSHIFT; } } + + // Unknown Spec + return RETURN_FAIL; +} + +void PlayerbotDruidAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; + + if (!m_bot->isAlive() || m_bot->IsInDuel()) return; + + // Revive + if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE) + return; + + // Heal + if (m_ai->IsHealer()) + { + if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; + } else { - if (master->isAlive()) - { - if (BuffPlayer(master)) - return; - if (HealTarget(master)) - return; - } - else - if (ai->CastSpell(REVIVE, *master)) - ai->TellMaster("Resurrecting you, Master."); + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; } - BuffPlayer(m_bot); + // Buff group + // the check for group targets is performed by NeedGroupBuff (if group is found for bots by the function) + if (NeedGroupBuff(GIFT_OF_THE_WILD, MARK_OF_THE_WILD) && m_ai->HasSpellReagents(GIFT_OF_THE_WILD)) + { + if (Buff(&PlayerbotDruidAI::BuffHelper, GIFT_OF_THE_WILD) & RETURN_CONTINUE) + return; + } + else if (Buff(&PlayerbotDruidAI::BuffHelper, MARK_OF_THE_WILD) & RETURN_CONTINUE) + return; + if (Buff(&PlayerbotDruidAI::BuffHelper, THORNS, (m_bot->GetGroup() ? JOB_TANK : JOB_ALL)) & RETURN_CONTINUE) + return; + if (OMEN_OF_CLARITY && !m_bot->HasAura(OMEN_OF_CLARITY) && CastSpell(OMEN_OF_CLARITY, m_bot)) + return; + + // hp/mana check + if (EatDrinkBandage()) + return; + + if (INNERVATE && m_ai->In_Reach(m_bot,INNERVATE) && !m_bot->HasAura(INNERVATE) && m_ai->GetManaPercent() <= 20 && CastSpell(INNERVATE, m_bot)) + return; + + // Return to fighting form AFTER reviving, healing, buffing + CheckForms(); + + // Nothing else to do, Night Elves will cast Shadowmeld to reduce their aggro versus patrols or nearby mobs + if (SHADOWMELD && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && m_ai->CastSpell(SHADOWMELD, *m_bot)) + return; } // end DoNonCombatActions -bool PlayerbotDruidAI::BuffPlayer(Player* target) +bool PlayerbotDruidAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit* target) { - PlayerbotAI * ai = GetAI(); + if (!ai) return false; + if (spellId == 0) return false; + if (!target) return false; Pet * pet = target->GetPet(); - if (pet) - { - if (ai->Buff(MARK_OF_THE_WILD, pet, &(PlayerbotDruidAI::GoBuffForm))) - return true; - else if (ai->Buff(THORNS, pet, &(PlayerbotDruidAI::GoBuffForm))) - return true; - } - - if (ai->Buff(MARK_OF_THE_WILD, target, &(PlayerbotDruidAI::GoBuffForm))) + if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet, &(PlayerbotDruidAI::GoBuffForm))) return true; - else if (ai->Buff(THORNS, target, &(PlayerbotDruidAI::GoBuffForm))) + + if (ai->Buff(spellId, target, &(PlayerbotDruidAI::GoBuffForm))) return true; - else - return false; + + return false; } -void PlayerbotDruidAI::GoBuffForm(Player *self) +void PlayerbotDruidAI::GoBuffForm(Player* self) { // RANK_1 spell ids used because this is a static method which does not have access to instance. // There is only one rank for these spells anyway. @@ -689,3 +698,55 @@ void PlayerbotDruidAI::GoBuffForm(Player *self) if (self->HasAura(TRAVEL_FORM_1)) self->RemoveAurasDueToSpell(TRAVEL_FORM_1); } + +// Match up with "Pull()" below +bool PlayerbotDruidAI::CanPull() +{ + if (BEAR_FORM && FAERIE_FIRE_FERAL) + return true; + + return false; +} + +// Match up with "CanPull()" above +bool PlayerbotDruidAI::Pull() +{ + if (BEAR_FORM && (CastSpell(FAERIE_FIRE_FERAL) & RETURN_CONTINUE)) + return true; + + return false; +} + +bool PlayerbotDruidAI::CastHoTOnTank() +{ + if (!m_ai) return false; + + if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false; + + // Druid HoTs: Rejuvenation, Regrowth, Tranquility (channeled, AoE) + if (REJUVENATION) + return (RETURN_CONTINUE & CastSpell(REJUVENATION, m_ai->GetGroupTank())); + + return false; +} + +// Return to UpdateAI the spellId usable to neutralize a target with creaturetype +uint32 PlayerbotDruidAI::Neutralize(uint8 creatureType) +{ + if (!m_bot) return 0; + if (!m_ai) return 0; + if (!creatureType) return 0; + + if (creatureType != CREATURE_TYPE_DRAGONKIN && creatureType != CREATURE_TYPE_BEAST) + { + m_ai->TellMaster("I can't make that target hibernate."); + return 0; + } + + if (HIBERNATE) + return HIBERNATE; + else + return 0; + + return 0; +} diff --git a/src/game/playerbot/PlayerbotDruidAI.h b/src/game/playerbot/PlayerbotDruidAI.h index 1476eae1f..8c2b68920 100644 --- a/src/game/playerbot/PlayerbotDruidAI.h +++ b/src/game/playerbot/PlayerbotDruidAI.h @@ -98,20 +98,49 @@ class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI virtual ~PlayerbotDruidAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + bool Pull(); + uint32 Neutralize(uint8 creatureType); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); - // buff a specific player, usually a real PC who is not in group - bool BuffPlayer(Player *target); + // Utility Functions + bool CanPull(); + bool CastHoTOnTank(); private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + + CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = nullptr) { return CastSpellNoRanged(nextAction, pTarget); } + + // Combat Maneuver helper functions + CombatManeuverReturns _DoNextPVECombatManeuverBear(Unit* pTarget); + CombatManeuverReturns _DoNextPVECombatManeuverCat(Unit* pTarget); + CombatManeuverReturns _DoNextPVECombatManeuverSpellDPS(Unit* pTarget); + CombatManeuverReturns _DoNextPVECombatManeuverHeal(); + // Heals the target based off its hps - bool HealTarget (Unit *target); + CombatManeuverReturns HealPlayer (Player* target); + + static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target); // Callback method to reset shapeshift forms blocking buffs and heals static void GoBuffForm(Player *self); + //Assumes form based on spec + uint8 CheckForms(); + enum CheckForms_ReturnValues { + RETURN_FAIL = 0, + RETURN_FAIL_WAITINGONSELFBUFF, + RETURN_OK_NOCHANGE, + RETURN_OK_SHIFTING, + RETURN_OK_CANNOTSHIFT + }; + // druid cat/bear/dire bear/moonkin/tree of life forms uint32 CAT_FORM, BEAR_FORM, @@ -126,6 +155,7 @@ class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI TIGERS_FURY, RAKE, RIP, + SHRED, FEROCIOUS_BITE, MAIM, MANGLE; @@ -137,13 +167,16 @@ class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI DEMORALIZING_ROAR, CHALLENGING_ROAR, GROWL, - ENRAGE; + ENRAGE, + FAERIE_FIRE_FERAL; // druid attacks & debuffs uint32 MOONFIRE, ROOTS, WRATH, + OMEN_OF_CLARITY, STARFALL, + HIBERNATE, STARFIRE, INSECT_SWARM, FAERIE_FIRE, @@ -155,6 +188,7 @@ class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI GIFT_OF_THE_WILD, THORNS, INNERVATE, + NATURES_SWIFTNESS, BARKSKIN; // druid heals @@ -166,8 +200,10 @@ class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI WILD_GROWTH, SWIFTMEND, TRANQUILITY, + REBIRTH, REVIVE, REMOVE_CURSE, + CURE_POISON, ABOLISH_POISON; // first aid diff --git a/src/game/playerbot/PlayerbotHunterAI.cpp b/src/game/playerbot/PlayerbotHunterAI.cpp index 08c50ba6b..81f61a814 100644 --- a/src/game/playerbot/PlayerbotHunterAI.cpp +++ b/src/game/playerbot/PlayerbotHunterAI.cpp @@ -8,13 +8,13 @@ class PlayerbotAI; PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { // PET CTRL - PET_SUMMON = ai->initSpell(CALL_PET_1); - PET_DISMISS = ai->initSpell(DISMISS_PET_1); - PET_REVIVE = ai->initSpell(REVIVE_PET_1); - PET_MEND = ai->initSpell(MEND_PET_1); + PET_SUMMON = m_ai->initSpell(CALL_PET_1); + PET_DISMISS = m_ai->initSpell(DISMISS_PET_1); + PET_REVIVE = m_ai->initSpell(REVIVE_PET_1); + PET_MEND = m_ai->initSpell(MEND_PET_1); PET_FEED = 1539; - INTIMIDATION = ai->initSpell(INTIMIDATION_1); // (generic) + INTIMIDATION = m_ai->initSpell(INTIMIDATION_1); // (generic) // PET SKILLS must be initialized by pets SONIC_BLAST = 0; // bat @@ -23,57 +23,60 @@ PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, Pl NETHER_SHOCK = 0; // RANGED COMBAT - AUTO_SHOT = ai->initSpell(AUTO_SHOT_1); - HUNTERS_MARK = ai->initSpell(HUNTERS_MARK_1); - ARCANE_SHOT = ai->initSpell(ARCANE_SHOT_1); - CONCUSSIVE_SHOT = ai->initSpell(CONCUSSIVE_SHOT_1); - DISTRACTING_SHOT = ai->initSpell(DISTRACTING_SHOT_1); - MULTI_SHOT = ai->initSpell(MULTISHOT_1); - EXPLOSIVE_SHOT = ai->initSpell(EXPLOSIVE_SHOT_1); - SERPENT_STING = ai->initSpell(SERPENT_STING_1); - SCORPID_STING = ai->initSpell(SCORPID_STING_1); - WYVERN_STING = ai->initSpell(WYVERN_STING_1); - VIPER_STING = ai->initSpell(VIPER_STING_1); - AIMED_SHOT = ai->initSpell(AIMED_SHOT_1); - STEADY_SHOT = ai->initSpell(STEADY_SHOT_1); - CHIMERA_SHOT = ai->initSpell(CHIMERA_SHOT_1); - VOLLEY = ai->initSpell(VOLLEY_1); - BLACK_ARROW = ai->initSpell(BLACK_ARROW_1); - KILL_SHOT = ai->initSpell(KILL_SHOT_1); + AUTO_SHOT = m_ai->initSpell(AUTO_SHOT_1); + HUNTERS_MARK = m_ai->initSpell(HUNTERS_MARK_1); + ARCANE_SHOT = m_ai->initSpell(ARCANE_SHOT_1); + CONCUSSIVE_SHOT = m_ai->initSpell(CONCUSSIVE_SHOT_1); + DISTRACTING_SHOT = m_ai->initSpell(DISTRACTING_SHOT_1); + MULTI_SHOT = m_ai->initSpell(MULTISHOT_1); + EXPLOSIVE_SHOT = m_ai->initSpell(EXPLOSIVE_SHOT_1); + SERPENT_STING = m_ai->initSpell(SERPENT_STING_1); + SCORPID_STING = m_ai->initSpell(SCORPID_STING_1); + WYVERN_STING = m_ai->initSpell(WYVERN_STING_1); + VIPER_STING = m_ai->initSpell(VIPER_STING_1); + AIMED_SHOT = m_ai->initSpell(AIMED_SHOT_1); + STEADY_SHOT = m_ai->initSpell(STEADY_SHOT_1); + CHIMERA_SHOT = m_ai->initSpell(CHIMERA_SHOT_1); + VOLLEY = m_ai->initSpell(VOLLEY_1); + BLACK_ARROW = m_ai->initSpell(BLACK_ARROW_1); + KILL_SHOT = m_ai->initSpell(KILL_SHOT_1); + TRANQUILIZING_SHOT = m_ai->initSpell(TRANQUILIZING_SHOT_1); // MELEE - RAPTOR_STRIKE = ai->initSpell(RAPTOR_STRIKE_1); - WING_CLIP = ai->initSpell(WING_CLIP_1); - MONGOOSE_BITE = ai->initSpell(MONGOOSE_BITE_1); - DISENGAGE = ai->initSpell(DISENGAGE_1); - MISDIRECTION = ai->initSpell(MISDIRECTION_1); - DETERRENCE = ai->initSpell(DETERRENCE_1); + RAPTOR_STRIKE = m_ai->initSpell(RAPTOR_STRIKE_1); + WING_CLIP = m_ai->initSpell(WING_CLIP_1); + MONGOOSE_BITE = m_ai->initSpell(MONGOOSE_BITE_1); + DISENGAGE = m_ai->initSpell(DISENGAGE_1); + DETERRENCE = m_ai->initSpell(DETERRENCE_1); + FEIGN_DEATH = m_ai->initSpell(FEIGN_DEATH_1); + MISDIRECTION = m_ai->initSpell(MISDIRECTION_1); + DETERRENCE = m_ai->initSpell(DETERRENCE_1); // TRAPS BEAR_TRAP = 0; // non-player spell - FREEZING_TRAP = ai->initSpell(FREEZING_TRAP_1); - IMMOLATION_TRAP = ai->initSpell(IMMOLATION_TRAP_1); - FROST_TRAP = ai->initSpell(FROST_TRAP_1); - EXPLOSIVE_TRAP = ai->initSpell(EXPLOSIVE_TRAP_1); + FREEZING_TRAP = m_ai->initSpell(FREEZING_TRAP_1); + IMMOLATION_TRAP = m_ai->initSpell(IMMOLATION_TRAP_1); + FROST_TRAP = m_ai->initSpell(FROST_TRAP_1); + EXPLOSIVE_TRAP = m_ai->initSpell(EXPLOSIVE_TRAP_1); ARCANE_TRAP = 0; // non-player spell - SNAKE_TRAP = ai->initSpell(SNAKE_TRAP_1); + SNAKE_TRAP = m_ai->initSpell(SNAKE_TRAP_1); // BUFFS - ASPECT_OF_THE_HAWK = ai->initSpell(ASPECT_OF_THE_HAWK_1); - ASPECT_OF_THE_MONKEY = ai->initSpell(ASPECT_OF_THE_MONKEY_1); - RAPID_FIRE = ai->initSpell(RAPID_FIRE_1); - TRUESHOT_AURA = ai->initSpell(TRUESHOT_AURA_1); + ASPECT_OF_THE_HAWK = m_ai->initSpell(ASPECT_OF_THE_HAWK_1); + ASPECT_OF_THE_MONKEY = m_ai->initSpell(ASPECT_OF_THE_MONKEY_1); + RAPID_FIRE = m_ai->initSpell(RAPID_FIRE_1); + TRUESHOT_AURA = m_ai->initSpell(TRUESHOT_AURA_1); RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_HUNTER); // draenei - STONEFORM = ai->initSpell(STONEFORM_ALL); // dwarf - SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); - BLOOD_FURY = ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc - WAR_STOMP = ai->initSpell(WAR_STOMP_ALL); // tauren - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_HUNTER); // draenei + STONEFORM = m_ai->initSpell(STONEFORM_ALL); // dwarf + SHADOWMELD = m_ai->initSpell(SHADOWMELD_ALL); + BLOOD_FURY = m_ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc + WAR_STOMP = m_ai->initSpell(WAR_STOMP_ALL); // tauren + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll m_petSummonFailed = false; m_rangedCombat = true; @@ -81,258 +84,362 @@ PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, Pl PlayerbotHunterAI::~PlayerbotHunterAI() {} -bool PlayerbotHunterAI::DoFirstCombatManeuver(Unit* /*pTarget*/) +CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuver(Unit* pTarget) { Player *m_bot = GetPlayerBot(); m_has_ammo = m_bot->HasItemCount( m_bot->GetUInt32Value(PLAYER_AMMO_ID), 1 ); + DEBUG_LOG("current ammo (%u)",m_bot->GetUInt32Value(PLAYER_AMMO_ID)); m_bot->setAttackTimer(RANGED_ATTACK,0); - return false; + if (!m_has_ammo) + { + m_ai->FindAmmo(); + //DEBUG_LOG("new ammo (%u)",m_bot->GetUInt32Value(PLAYER_AMMO_ID)); + m_has_ammo = m_bot->HasItemCount( m_bot->GetUInt32Value(PLAYER_AMMO_ID), 1 ); + } + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) + { + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + } + } + + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } + + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + break; + } + + return RETURN_NO_ACTION_ERROR; } -void PlayerbotHunterAI::DoNextCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + return RETURN_NO_ACTION_OK; +} + +bool PlayerbotHunterAI::HasPet(Player* bot) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT * FROM character_pet WHERE owner = '%u' AND (slot = '%u' OR slot = '%u')", bot->GetGUIDLow(), PET_SAVE_AS_CURRENT, PET_SAVE_NOT_IN_SLOT); + + if (result) + return true; //hunter has current pet + else + return false; //hunter either has no pet or stabled +} // end HasPet + +CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - switch (ai->GetScenarioType()) +CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - ai->CastSpell(RAPTOR_STRIKE); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoNextCombatManeuverPVE(pTarget); break; } - // ------- Non Duel combat ---------- + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + if (!pTarget) return RETURN_NO_ACTION_ERROR; - // Hunter - Player *m_bot = GetPlayerBot(); Unit* pVictim = pTarget->getVictim(); // check for pet and heal if neccessary Pet *pet = m_bot->GetPet(); - if ((pet) - && (((float) pet->GetHealth() / (float) pet->GetMaxHealth()) < 0.5f) - && (PET_MEND > 0 && !pet->getDeathState() != ALIVE && pVictim != m_bot && !pet->HasAura(PET_MEND, EFFECT_INDEX_0) && ai->GetManaPercent() >= 13 && ai->CastSpell(PET_MEND, *m_bot))) + // TODO: clarify/simplify: !pet->getDeathState() != ALIVE + if (pet && PET_MEND > 0 && pet->isAlive() && pet->GetHealthPercent() < 50 && pVictim != m_bot && !pet->HasAura(PET_MEND, EFFECT_INDEX_0) && m_ai->CastSpell(PET_MEND, *m_bot)) { - ai->TellMaster("healing pet."); - return; + m_ai->TellMaster("healing pet."); + return RETURN_CONTINUE; } - else if ((pet) - && (INTIMIDATION > 0 && pVictim == pet && !pet->HasAura(INTIMIDATION, EFFECT_INDEX_0) && ai->CastSpell(INTIMIDATION, *m_bot))) - //ai->TellMaster( "casting intimidation." ); // if pet has aggro :) - return; + else if (pet && INTIMIDATION > 0 && pVictim == pet && !pet->HasAura(INTIMIDATION, EFFECT_INDEX_0) && m_ai->CastSpell(INTIMIDATION, *m_bot)) + return RETURN_CONTINUE; - // racial traits + /*// racial traits if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_INDEX_0)) - ai->CastSpell(BLOOD_FURY, *m_bot); - //ai->TellMaster( "Blood Fury." ); + m_ai->CastSpell(BLOOD_FURY, *m_bot); else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0)) - ai->CastSpell(BERSERKING, *m_bot); - //ai->TellMaster( "Berserking." ); - - // check if ranged combat is possible (set m_rangedCombat and switch auras + m_ai->CastSpell(BERSERKING, *m_bot); + */ + // check if ranged combat is possible: by default chose ranged combat bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); - if (meleeReach || !m_has_ammo) + if (!meleeReach && m_has_ammo) + { + // switch to ranged combat + m_rangedCombat = true; + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + + // increase ranged attack power... + if (ASPECT_OF_THE_HAWK > 0 && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0)) + m_ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot); + } + else { // switch to melee combat (target in melee range, out of ammo) m_rangedCombat = false; - ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + if (!m_bot->GetUInt32Value(PLAYER_AMMO_ID)) + m_ai->TellMaster("Out of ammo!"); // become monkey (increases dodge chance)... if (ASPECT_OF_THE_MONKEY > 0 && !m_bot->HasAura(ASPECT_OF_THE_MONKEY, EFFECT_INDEX_0)) - ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot); + m_ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot); } - else if (!meleeReach) + + if (TRANQUILIZING_SHOT > 0 && IsTargetEnraged(pTarget) && !m_bot->HasSpellCooldown(TRANQUILIZING_SHOT) && m_ai->CastSpell(TRANQUILIZING_SHOT, *pTarget)) { - // switch to ranged combat - m_rangedCombat = true; - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + m_ai->TellMaster("Casting TRANQUILIZING SHOT onto %s", pTarget->GetName()); + return RETURN_CONTINUE; + } - // increase ranged attack power... - if (ASPECT_OF_THE_HAWK > 0 && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0)) - ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot); + //Used to determine if this bot has highest threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + // Aggro management + if (newTarget && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank + { + // Aggroed by an elite + if (m_ai->IsElite(newTarget)) + { + // Try to disengage + if (DISENGAGE > 0 && !m_bot->HasSpellCooldown(DISENGAGE) && m_ai->In_Reach(newTarget, DISENGAGE) && m_ai->CastSpell(DISENGAGE, *newTarget)) + return RETURN_CONTINUE; + // Increase dodge and parry chance + if (DETERRENCE > 0 && !m_bot->HasSpellCooldown(DETERRENCE) && !m_bot->HasAura(DETERRENCE, EFFECT_INDEX_0) && m_ai->CastSpell(DETERRENCE, *m_bot)) + return RETURN_CONTINUE; + // Else feign death if low on health or attacked by a worldboss + if (FEIGN_DEATH > 0 && (m_ai->GetHealthPercent() <= 20 || m_ai->IsElite(pTarget, true)) && !m_bot->HasSpellCooldown(FEIGN_DEATH) && !m_bot->HasAura(FEIGN_DEATH, EFFECT_INDEX_0) && m_ai->CastSpell(FEIGN_DEATH, *m_bot)) + return RETURN_CONTINUE; + } + } - // ai->TellMaster("target dist %f",m_bot->GetCombatDistance(pTarget, true)); + // Distance management: avoid to be in the dead zone where neither melee nor range can be used: keep distance whenever possible + // If not in range: come closer + // Do not do it if passive or stay orders. + if (pTarget && !m_ai->In_Reach(pTarget, AUTO_SHOT) && + !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_PASSIVE) && + (m_bot->GetPlayerbotAI()->GetMovementOrder() != PlayerbotAI::MOVEMENT_STAY)) + { + m_ai->InterruptCurrentCastingSpell(); + m_bot->GetMotionMaster()->MoveFollow(pTarget, 39.0f, m_bot->GetOrientation()); + return RETURN_CONTINUE; + } + + // If below ranged combat distance and bot is not attacked by target + // make it flee from target for a few seconds to get in ranged distance again + // Do not do it if passive or stay orders. + if (pVictim != m_bot && m_bot->GetCombatDistance(pTarget, true) <= 8.0f && + !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_PASSIVE) && + (m_bot->GetPlayerbotAI()->GetMovementOrder() != PlayerbotAI::MOVEMENT_STAY)) + { + m_ai->InterruptCurrentCastingSpell(); + m_ai->SetIgnoreUpdateTime(2); + m_bot->GetMotionMaster()->Clear(false); + m_bot->GetMotionMaster()->MoveFleeing(pTarget, 2); + return RETURN_CONTINUE; + } + + // damage spells + if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED) + { + // Debuff target + if (HUNTERS_MARK > 0 && m_ai->In_Reach(pTarget,HUNTERS_MARK) && !pTarget->HasAura(HUNTERS_MARK, EFFECT_INDEX_0) && m_ai->CastSpell(HUNTERS_MARK, *pTarget)) + return RETURN_CONTINUE; + // Buff self + if (RAPID_FIRE > 0 && !m_bot->HasAura(RAPID_FIRE, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(RAPID_FIRE) && m_ai->CastSpell(RAPID_FIRE, *m_bot)) + return RETURN_CONTINUE; + if (ARCANE_SHOT > 0 && !m_bot->HasSpellCooldown(ARCANE_SHOT) && m_ai->In_Range(pTarget,ARCANE_SHOT) && m_ai->CastSpell(ARCANE_SHOT, *pTarget)) + return RETURN_CONTINUE; + // Stings: only use Viper and Serpent sting. Stats decrease (Scorpid Sting) is useless in PvE + // and as the bot is obviously not alone and assisting someone, no need to put the target to sleep (Wyvern Sting) + // Viper sting for non-worldboss mana users + if (pTarget->GetPower(POWER_MANA) > 0 && !m_ai->IsElite(pTarget, true)) + { + if (VIPER_STING > 0 && m_ai->In_Range(pTarget,VIPER_STING) && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && m_ai->CastSpell(VIPER_STING, *pTarget)) + return RETURN_CONTINUE; + } + // Serpent sting for everyone else + else + { + if (SERPENT_STING > 0 && m_ai->In_Range(pTarget,SERPENT_STING) && !pTarget->HasAura(SERPENT_STING, EFFECT_INDEX_0) && m_ai->CastSpell(SERPENT_STING, *pTarget)) + return RETURN_CONTINUE; + } + if (CONCUSSIVE_SHOT > 0 && m_ai->In_Range(pTarget,CONCUSSIVE_SHOT) && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CONCUSSIVE_SHOT, *pTarget)) + return RETURN_CONTINUE; + if (BLACK_ARROW > 0 && m_ai->In_Range(pTarget,BLACK_ARROW) && !pTarget->HasAura(BLACK_ARROW, EFFECT_INDEX_0) && m_ai->CastSpell(BLACK_ARROW, *pTarget)) + return RETURN_CONTINUE; + if (AIMED_SHOT > 0 && !m_bot->HasSpellCooldown(AIMED_SHOT) && m_ai->In_Range(pTarget,AIMED_SHOT) && m_ai->CastSpell(AIMED_SHOT, *pTarget)) + return RETURN_CONTINUE; + + //if (MULTI_SHOT > 0 && !m_bot->HasSpellCooldown(MULTI_SHOT) && m_ai->In_Range(pTarget,MULTI_SHOT) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(MULTI_SHOT, *pTarget)) + // return RETURN_CONTINUE; + //if (VOLLEY > 0 && m_ai->In_Range(pTarget,VOLLEY) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(VOLLEY, *pTarget)) + // return RETURN_CONTINUE; + + // Auto shot + // m_ai->TellMaster("target dist %f",m_bot->GetCombatDistance(pTarget,true)); if (AUTO_SHOT > 0) { if (m_bot->isAttackReady(RANGED_ATTACK)) - ai->CastSpell(AUTO_SHOT, *pTarget); - - m_bot->setAttackTimer(RANGED_ATTACK,500); + m_bot->CastSpell(pTarget, AUTO_SHOT, TRIGGERED_OLD_TRIGGERED); const SpellEntry* spellInfo = sSpellTemplate.LookupEntry(AUTO_SHOT); if (!spellInfo) - return; + return RETURN_CONTINUE; - if (ai->CheckBotCast(spellInfo) != SPELL_CAST_OK) + if (m_ai->CheckBotCast(spellInfo) != SPELL_CAST_OK) m_bot->InterruptNonMeleeSpells(true, AUTO_SHOT); + + return RETURN_CONTINUE; } - } - // damage spells - std::ostringstream out; - if (m_rangedCombat) - { - out << "Case Ranged"; - if (HUNTERS_MARK > 0 && ai->In_Reach(pTarget,HUNTERS_MARK) && ai->GetManaPercent() >= 3 && !pTarget->HasAura(HUNTERS_MARK, EFFECT_INDEX_0) && ai->CastSpell(HUNTERS_MARK, *pTarget)) - out << " > Hunter's Mark"; - else if (RAPID_FIRE > 0 && ai->GetManaPercent() >= 3 && !m_bot->HasAura(RAPID_FIRE, EFFECT_INDEX_0) && ai->CastSpell(RAPID_FIRE, *m_bot)) - out << " > Rapid Fire"; - else if (MULTI_SHOT > 0 && ai->In_Reach(pTarget,MULTI_SHOT) && ai->GetManaPercent() >= 13 && ai->GetAttackerCount() >= 3 && ai->CastSpell(MULTI_SHOT, *pTarget)) - out << " > Multi-Shot"; - else if (ARCANE_SHOT > 0 && ai->In_Reach(pTarget,ARCANE_SHOT) && ai->GetManaPercent() >= 7 && ai->CastSpell(ARCANE_SHOT, *pTarget)) - out << " > Arcane Shot"; - else if (CONCUSSIVE_SHOT > 0 && ai->In_Reach(pTarget,CONCUSSIVE_SHOT) && ai->GetManaPercent() >= 6 && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_INDEX_0) && ai->CastSpell(CONCUSSIVE_SHOT, *pTarget)) - out << " > Concussive Shot"; - else if (EXPLOSIVE_SHOT > 0 && ai->In_Reach(pTarget,EXPLOSIVE_SHOT) && ai->GetManaPercent() >= 10 && !pTarget->HasAura(EXPLOSIVE_SHOT, EFFECT_INDEX_0) && ai->CastSpell(EXPLOSIVE_SHOT, *pTarget)) - out << " > Explosive Shot"; - else if (VIPER_STING > 0 && ai->In_Reach(pTarget,VIPER_STING) && ai->GetManaPercent() >= 8 && pTarget->GetPower(POWER_MANA) > 0 && ai->GetManaPercent() < 70 && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && ai->CastSpell(VIPER_STING, *pTarget)) - out << " > Viper Sting"; - else if (SERPENT_STING > 0 && ai->In_Reach(pTarget,SERPENT_STING) && ai->GetManaPercent() >= 13 && !pTarget->HasAura(SERPENT_STING, EFFECT_INDEX_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_INDEX_0) && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && ai->CastSpell(SERPENT_STING, *pTarget)) - out << " > Serpent Sting"; - else if (SCORPID_STING > 0 && ai->In_Reach(pTarget,SCORPID_STING) && ai->GetManaPercent() >= 11 && !pTarget->HasAura(WYVERN_STING, EFFECT_INDEX_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_INDEX_0) && !pTarget->HasAura(SERPENT_STING, EFFECT_INDEX_0) && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && ai->CastSpell(SCORPID_STING, *pTarget)) - out << " > Scorpid Sting"; - else if (CHIMERA_SHOT > 0 && ai->In_Reach(pTarget,CHIMERA_SHOT) && ai->GetManaPercent() >= 12 && ai->CastSpell(CHIMERA_SHOT, *pTarget)) - out << " > Chimera Shot"; - else if (VOLLEY > 0 && ai->In_Reach(pTarget,VOLLEY) && ai->GetManaPercent() >= 24 && ai->GetAttackerCount() >= 3 && ai->CastSpell(VOLLEY, *pTarget)) - out << " > Volley"; - else if (BLACK_ARROW > 0 && ai->In_Reach(pTarget,BLACK_ARROW) && ai->GetManaPercent() >= 6 && !pTarget->HasAura(BLACK_ARROW, EFFECT_INDEX_0) && ai->CastSpell(BLACK_ARROW, *pTarget)) - out << " > Black Arrow"; - else if (AIMED_SHOT > 0 && ai->In_Reach(pTarget,AIMED_SHOT) && ai->GetManaPercent() >= 12 && ai->CastSpell(AIMED_SHOT, *pTarget)) - out << " > Aimed Shot"; - else if (STEADY_SHOT > 0 && ai->In_Reach(pTarget,STEADY_SHOT) && ai->GetManaPercent() >= 5 && ai->CastSpell(STEADY_SHOT, *pTarget)) - out << " > Steady Shot"; - else if (KILL_SHOT > 0 && ai->In_Reach(pTarget,KILL_SHOT) && ai->GetManaPercent() >= 7 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.2 && ai->CastSpell(KILL_SHOT, *pTarget)) - out << " > Kill Shot!"; - else - out << " NONE!"; + + return RETURN_NO_ACTION_OK; } else { - out << "Case Melee"; - if (RAPTOR_STRIKE > 0 && ai->In_Reach(pTarget,RAPTOR_STRIKE) && ai->Impulse() && ai->CastSpell(RAPTOR_STRIKE, *pTarget)) - out << " > Raptor Strike"; - else if (EXPLOSIVE_TRAP > 0 && ai->GetManaPercent() >= 27 && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && ai->CastSpell(EXPLOSIVE_TRAP, *pTarget)) - out << " > Explosive Trap"; - else if (WING_CLIP > 0 && ai->In_Reach(pTarget,WING_CLIP) && ai->GetManaPercent() >= 6 && !pTarget->HasAura(WING_CLIP, EFFECT_INDEX_0) && ai->CastSpell(WING_CLIP, *pTarget)) - out << " > Wing Clip"; - else if (IMMOLATION_TRAP > 0 && ai->GetManaPercent() >= 13 && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && ai->CastSpell(IMMOLATION_TRAP, *pTarget)) - out << " > Immolation Trap"; - else if (MONGOOSE_BITE > 0 && ai->Impulse() && ai->CastSpell(MONGOOSE_BITE, *pTarget)) - out << " > Mongoose Bite"; - else if (FROST_TRAP > 0 && ai->GetManaPercent() >= 2 && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && ai->CastSpell(FROST_TRAP, *pTarget)) - out << " > Frost Trap"; - else if (ARCANE_TRAP > 0 && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && ai->CastSpell(ARCANE_TRAP, *pTarget)) - out << " > Arcane Trap"; - else if (DETERRENCE > 0 && pVictim == m_bot && m_bot->GetHealth() < m_bot->GetMaxHealth() * 0.5 && !m_bot->HasAura(DETERRENCE, EFFECT_INDEX_0) && ai->CastSpell(DETERRENCE, *m_bot)) - out << " > Deterrence"; - else if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && ai->CastSpell(WAR_STOMP, *pTarget)) - out << " > War Stomp"; - else if (m_bot->getRace() == RACE_NIGHTELF && pVictim == m_bot && ai->GetHealthPercent() < 25 && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && ai->CastSpell(SHADOWMELD, *m_bot)) - out << " > Shadowmeld"; - else if ((pet && !pet->getDeathState() != ALIVE) - && (MISDIRECTION > 0 && ai->In_Reach(pTarget,MISDIRECTION) && pVictim == m_bot && !m_bot->HasAura(MISDIRECTION, EFFECT_INDEX_0) && ai->GetManaPercent() >= 9 && ai->CastSpell(MISDIRECTION, *pet))) - out << " > Misdirection"; // give threat to pet - /*else if( FREEZING_TRAP>0 && ai->GetManaPercent()>=5 && !pTarget->HasAura(FREEZING_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && ai->CastSpell(FREEZING_TRAP,*pTarget) ) + if (MONGOOSE_BITE > 0 && m_bot->RollMeleeOutcomeAgainst(pTarget, BASE_ATTACK, SPELL_SCHOOL_MASK_NORMAL) == MELEE_HIT_DODGE && m_ai->CastSpell(MONGOOSE_BITE, *pTarget)) + return RETURN_CONTINUE; + if (RAPTOR_STRIKE > 0 && !m_bot->HasSpellCooldown(RAPTOR_STRIKE) && m_ai->In_Reach(pTarget,RAPTOR_STRIKE) && m_ai->CastSpell(RAPTOR_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (EXPLOSIVE_TRAP > 0 && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && m_ai->CastSpell(EXPLOSIVE_TRAP, *pTarget)) + return RETURN_CONTINUE; + if (WING_CLIP > 0 && !m_bot->HasSpellCooldown(WING_CLIP) && m_ai->In_Reach(pTarget,WING_CLIP) && !pTarget->HasAura(WING_CLIP, EFFECT_INDEX_0) && m_ai->CastSpell(WING_CLIP, *pTarget)) + return RETURN_CONTINUE; + if (IMMOLATION_TRAP > 0 && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && m_ai->CastSpell(IMMOLATION_TRAP, *pTarget)) + return RETURN_CONTINUE; + if (FROST_TRAP > 0 && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && m_ai->CastSpell(FROST_TRAP, *pTarget)) + return RETURN_CONTINUE; + + //if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && m_ai->CastSpell(WAR_STOMP, *pTarget)) + // return RETURN_CONTINUE; + //else if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && m_ai->CastSpell(STONEFORM, *m_bot)) + // return RETURN_CONTINUE; + + /*else if(FREEZING_TRAP > 0 && !pTarget->HasAura(FREEZING_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && m_ai->CastSpell(FREEZING_TRAP,*pTarget) ) out << " > Freezing Trap"; // this can trap your bots too - else if( BEAR_TRAP>0 && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && ai->CastSpell(BEAR_TRAP,*pTarget) ) - out << " > Bear Trap"; // this was just too annoying :) - else if( DISENGAGE>0 && pVictim && ai->GetManaPercent()>=5 && ai->CastSpell(DISENGAGE,*pTarget) ) - out << " > Disengage!"; // attempt to return to ranged combat*/ - else - out << " NONE!"; + */ } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster(out.str().c_str()); + + return RETURN_NO_ACTION_OK; } // end DoNextCombatManeuver -void PlayerbotHunterAI::DoNonCombatActions() +CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVP(Unit* pTarget) { - PlayerbotAI *ai = GetAI(); - if (!ai) - return; + if (m_ai->CastSpell(RAPTOR_STRIKE)) + return RETURN_CONTINUE; - Player * m_bot = GetPlayerBot(); - if (!m_bot) - return; + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} - // reset ranged combat state - if (!m_rangedCombat || ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE) +bool PlayerbotHunterAI::IsTargetEnraged(Unit* pTarget) +{ + if (!m_ai) return false; + if (!m_bot) return false; + if (!pTarget) return false; + + Unit::SpellAuraHolderMap const& auras = pTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { - m_rangedCombat = true; - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + SpellAuraHolder *holder = itr->second; + // Return true is target unit has aura with DISPEL_ENRAGE dispel type + if ((1 << holder->GetSpellProto()->Dispel) & GetDispellMask(DISPEL_ENRAGE)) + return true; } - // buff group - if (TRUESHOT_AURA > 0) - (!m_bot->HasAura(TRUESHOT_AURA, EFFECT_INDEX_0) && ai->CastSpell (TRUESHOT_AURA, *m_bot)); - - // buff myself - if (ASPECT_OF_THE_HAWK > 0) - (!m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0) && ai->CastSpell (ASPECT_OF_THE_HAWK, *m_bot)); - - // mana check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + return false; +} - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); +void PlayerbotHunterAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; - if (pItem != nullptr && ai->GetManaPercent() < 30) + if (!m_rangedCombat || m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE) { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); - return; + m_rangedCombat = true; + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); } - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + // buff group + if (TRUESHOT_AURA > 0 && !m_bot->HasAura(TRUESHOT_AURA, EFFECT_INDEX_0)) + m_ai->CastSpell(TRUESHOT_AURA, *m_bot); - pItem = ai->FindFood(); + // buff myself + if (ASPECT_OF_THE_HAWK > 0 && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0)) + m_ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot); - if (pItem != nullptr && ai->GetHealthPercent() < 30) - { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); + // hp/mana check + if (EatDrinkBandage()) return; - } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) - { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); - return; - } // check for pet - if (PET_SUMMON > 0 && !m_petSummonFailed && m_bot->GetPetGuid()) + if (PET_SUMMON > 0 && !m_petSummonFailed && HasPet(m_bot)) { // we can summon pet, and no critical summon errors before Pet *pet = m_bot->GetPet(); if (!pet) { // summon pet - if (PET_SUMMON > 0 && ai->CastSpell(PET_SUMMON, *m_bot)) - ai->TellMaster("summoning pet."); + if (PET_SUMMON > 0 && m_ai->CastSpell(PET_SUMMON, *m_bot)) + m_ai->TellMaster("summoning pet."); else { m_petSummonFailed = true; - ai->TellMaster("summon pet failed!"); + m_ai->TellMaster("summon pet failed!"); } } - else if (pet->getDeathState() != ALIVE) + else if (!(pet->isAlive())) { - // revive pet - if (PET_REVIVE > 0 && ai->GetManaPercent() >= 80 && ai->CastSpell(PET_REVIVE, *m_bot)) - ai->TellMaster("reviving pet."); + if (PET_REVIVE > 0 && m_ai->CastSpell(PET_REVIVE, *m_bot)) + m_ai->TellMaster("reviving pet."); } - else if (((float) pet->GetHealth() / (float) pet->GetMaxHealth()) < 0.5f) + else if (pet->GetHealthPercent() < 50) { - // heal pet when health lower 50% - if (PET_MEND > 0 && !pet->getDeathState() != ALIVE && !pet->HasAura(PET_MEND, EFFECT_INDEX_0) && ai->GetManaPercent() >= 13 && ai->CastSpell(PET_MEND, *m_bot)) - ai->TellMaster("healing pet."); + if (PET_MEND > 0 && pet->isAlive() && !pet->HasAura(PET_MEND, EFFECT_INDEX_0) && m_ai->CastSpell(PET_MEND, *m_bot)) + m_ai->TellMaster("healing pet."); } else if (pet->GetHappinessState() != HAPPY) // if pet is hungry { @@ -350,14 +457,13 @@ void PlayerbotHunterAI::DoNonCombatActions() if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet { // DEBUG_LOG ("[PlayerbotHunterAI]: DoNonCombatActions - Food for pet: %s",pItemProto->Name1); - // caster->CastSpell(caster, 51284, true); // pet feed visual + caster->CastSpell(caster, 23355, TRIGGERED_OLD_TRIGGERED); // pet feed visual uint32 count = 1; // number of items used - int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel)*15; // nutritional value of food - DEBUG_LOG("FEED_PET benefit (%i)",benefit); + int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food m_bot->DestroyItemCount(pItem, count, true); // remove item from inventory m_bot->CastCustomSpell(m_bot, PET_FEED, &benefit, nullptr, nullptr, TRIGGERED_OLD_TRIGGERED); // feed pet - ai->TellMaster("feeding pet."); - ai->SetIgnoreUpdateTime(10); + m_ai->TellMaster("feeding pet."); + m_ai->SetIgnoreUpdateTime(10); return; } } @@ -379,21 +485,25 @@ void PlayerbotHunterAI::DoNonCombatActions() if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet { // DEBUG_LOG ("[PlayerbotHunterAI]: DoNonCombatActions - Food for pet: %s",pItemProto->Name1); - // caster->CastSpell(caster, 51284, true); // pet feed visual + caster->CastSpell(caster, 23355, TRIGGERED_OLD_TRIGGERED); // pet feed visual uint32 count = 1; // number of items used - int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel)*15; // nutritional value of food + int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food m_bot->DestroyItemCount(pItem, count, true); // remove item from inventory m_bot->CastCustomSpell(m_bot, PET_FEED, &benefit, nullptr, nullptr, TRIGGERED_OLD_TRIGGERED); // feed pet - ai->TellMaster("feeding pet."); - ai->SetIgnoreUpdateTime(10); + m_ai->TellMaster("feeding pet."); + m_ai->SetIgnoreUpdateTime(10); return; } } } } if (pet->HasAura(PET_MEND, EFFECT_INDEX_0) && !pet->HasAura(PET_FEED, EFFECT_INDEX_0)) - ai->TellMaster("..no pet food!"); - ai->SetIgnoreUpdateTime(7); + m_ai->TellMaster("..no pet food!"); + m_ai->SetIgnoreUpdateTime(7); } } + + // Nothing else to do, Night Elves will cast Shadowmeld to reduce their aggro versus patrols or nearby mobs + if (SHADOWMELD && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && m_ai->CastSpell(SHADOWMELD, *m_bot)) + return; } // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotHunterAI.h b/src/game/playerbot/PlayerbotHunterAI.h index 223711d64..5be418911 100644 --- a/src/game/playerbot/PlayerbotHunterAI.h +++ b/src/game/playerbot/PlayerbotHunterAI.h @@ -89,10 +89,11 @@ class MANGOS_DLL_SPEC PlayerbotHunterAI : PlayerbotClassAI public: PlayerbotHunterAI(Player * const master, Player * const bot, PlayerbotAI * const ai); virtual ~PlayerbotHunterAI(); + bool HasPet(Player* bot); // all combat actions go here - bool DoFirstCombatManeuver(Unit*); - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); @@ -101,22 +102,80 @@ class MANGOS_DLL_SPEC PlayerbotHunterAI : PlayerbotClassAI //void BuffPlayer(Player *target); private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + // Hunter + bool IsTargetEnraged(Unit* pTarget); bool m_petSummonFailed; bool m_rangedCombat; bool m_has_ammo; - uint32 PET_SUMMON, PET_DISMISS, PET_REVIVE, PET_MEND, PET_FEED, BAD_ATTITUDE, SONIC_BLAST, NETHER_SHOCK, DEMORALIZING_SCREECH, INTIMIDATION; - uint32 AUTO_SHOT, HUNTERS_MARK, ARCANE_SHOT, CONCUSSIVE_SHOT, DISTRACTING_SHOT, MULTI_SHOT, EXPLOSIVE_SHOT, SERPENT_STING, SCORPID_STING, VIPER_STING, WYVERN_STING, AIMED_SHOT, STEADY_SHOT, CHIMERA_SHOT, VOLLEY, BLACK_ARROW, KILL_SHOT; - uint32 RAPTOR_STRIKE, WING_CLIP, MONGOOSE_BITE, DISENGAGE, DETERRENCE; - uint32 BEAR_TRAP, FREEZING_TRAP, IMMOLATION_TRAP, FROST_TRAP, EXPLOSIVE_TRAP, ARCANE_TRAP, SNAKE_TRAP; - uint32 ASPECT_OF_THE_HAWK, ASPECT_OF_THE_MONKEY, RAPID_FIRE, TRUESHOT_AURA, MISDIRECTION; + uint32 PET_SUMMON, + PET_DISMISS, + PET_REVIVE, + PET_MEND, + PET_FEED, + BAD_ATTITUDE, + SONIC_BLAST, + NETHER_SHOCK, + DEMORALIZING_SCREECH, + INTIMIDATION; + + uint32 AUTO_SHOT, + HUNTERS_MARK, + ARCANE_SHOT, + CONCUSSIVE_SHOT, + DISTRACTING_SHOT, + MULTI_SHOT, + EXPLOSIVE_SHOT, + SERPENT_STING, + SCORPID_STING, + VIPER_STING, + WYVERN_STING, + AIMED_SHOT, + STEADY_SHOT, + CHIMERA_SHOT, + VOLLEY, + BLACK_ARROW, + KILL_SHOT, + TRANQUILIZING_SHOT; + + uint32 RAPTOR_STRIKE, + WING_CLIP, + MONGOOSE_BITE, + DISENGAGE, + DETERRENCE, + FEIGN_DEATH; + + uint32 BEAR_TRAP, + FREEZING_TRAP, + IMMOLATION_TRAP, + FROST_TRAP, + EXPLOSIVE_TRAP, + ARCANE_TRAP, + SNAKE_TRAP; + + uint32 ASPECT_OF_THE_HAWK, + ASPECT_OF_THE_MONKEY, + RAPID_FIRE, + TRUESHOT_AURA, + MISDIRECTION; // first aid uint32 RECENTLY_BANDAGED; // racial - uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN; + uint32 ARCANE_TORRENT, + GIFT_OF_THE_NAARU, + STONEFORM, + ESCAPE_ARTIST, + SHADOWMELD, + BLOOD_FURY, WAR_STOMP, + BERSERKING, + WILL_OF_THE_FORSAKEN; }; #endif diff --git a/src/game/playerbot/PlayerbotMageAI.cpp b/src/game/playerbot/PlayerbotMageAI.cpp index b89cda476..db58ac94d 100644 --- a/src/game/playerbot/PlayerbotMageAI.cpp +++ b/src/game/playerbot/PlayerbotMageAI.cpp @@ -5,469 +5,501 @@ class PlayerbotAI; PlayerbotMageAI::PlayerbotMageAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - ARCANE_MISSILES = ai->initSpell(ARCANE_MISSILES_1); - ARCANE_EXPLOSION = ai->initSpell(ARCANE_EXPLOSION_1); - COUNTERSPELL = ai->initSpell(COUNTERSPELL_1); - SLOW = ai->initSpell(SLOW_1); - ARCANE_BARRAGE = ai->initSpell(ARCANE_BARRAGE_1); - ARCANE_BLAST = ai->initSpell(ARCANE_BLAST_1); - ARCANE_POWER = ai->initSpell(ARCANE_POWER_1); - DAMPEN_MAGIC = ai->initSpell(DAMPEN_MAGIC_1); - AMPLIFY_MAGIC = ai->initSpell(AMPLIFY_MAGIC_1); - MAGE_ARMOR = ai->initSpell(MAGE_ARMOR_1); - MIRROR_IMAGE = ai->initSpell(MIRROR_IMAGE_1); - ARCANE_INTELLECT = ai->initSpell(ARCANE_INTELLECT_1); - ARCANE_BRILLIANCE = ai->initSpell(ARCANE_BRILLIANCE_1); - DALARAN_INTELLECT = ai->initSpell(DALARAN_INTELLECT_1); - DALARAN_BRILLIANCE = ai->initSpell(DALARAN_BRILLIANCE_1); - MANA_SHIELD = ai->initSpell(MANA_SHIELD_1); - CONJURE_WATER = ai->initSpell(CONJURE_WATER_1); - CONJURE_FOOD = ai->initSpell(CONJURE_FOOD_1); - FIREBALL = ai->initSpell(FIREBALL_1); - FIRE_BLAST = ai->initSpell(FIRE_BLAST_1); - FLAMESTRIKE = ai->initSpell(FLAMESTRIKE_1); - SCORCH = ai->initSpell(SCORCH_1); - PYROBLAST = ai->initSpell(PYROBLAST_1); - BLAST_WAVE = ai->initSpell(BLAST_WAVE_1); - COMBUSTION = ai->initSpell(COMBUSTION_1); - DRAGONS_BREATH = ai->initSpell(DRAGONS_BREATH_1); - LIVING_BOMB = ai->initSpell(LIVING_BOMB_1); - FROSTFIRE_BOLT = ai->initSpell(FROSTFIRE_BOLT_1); - FIRE_WARD = ai->initSpell(FIRE_WARD_1); - MOLTEN_ARMOR = ai->initSpell(MOLTEN_ARMOR_1); - ICY_VEINS = ai->initSpell(ICY_VEINS_1); - DEEP_FREEZE = ai->initSpell(DEEP_FREEZE_1); - FROSTBOLT = ai->initSpell(FROSTBOLT_1); - FROST_NOVA = ai->initSpell(FROST_NOVA_1); - BLIZZARD = ai->initSpell(BLIZZARD_1); - CONE_OF_COLD = ai->initSpell(CONE_OF_COLD_1); - ICE_BARRIER = ai->initSpell(ICE_BARRIER_1); - SUMMON_WATER_ELEMENTAL = ai->initSpell(SUMMON_WATER_ELEMENTAL_1); - FROST_WARD = ai->initSpell(FROST_WARD_1); - ICE_LANCE = ai->initSpell(ICE_LANCE_1); - FROST_ARMOR = ai->initSpell(FROST_ARMOR_1); - ICE_ARMOR = ai->initSpell(ICE_ARMOR_1); - ICE_BLOCK = ai->initSpell(ICE_BLOCK_1); - COLD_SNAP = ai->initSpell(COLD_SNAP_1); + ARCANE_MISSILES = m_ai->initSpell(ARCANE_MISSILES_1); + ARCANE_EXPLOSION = m_ai->initSpell(ARCANE_EXPLOSION_1); + COUNTERSPELL = m_ai->initSpell(COUNTERSPELL_1); + SLOW = m_ai->initSpell(SLOW_1); + ARCANE_BARRAGE = m_ai->initSpell(ARCANE_BARRAGE_1); + ARCANE_BLAST = m_ai->initSpell(ARCANE_BLAST_1); + ARCANE_POWER = m_ai->initSpell(ARCANE_POWER_1); + DAMPEN_MAGIC = m_ai->initSpell(DAMPEN_MAGIC_1); + AMPLIFY_MAGIC = m_ai->initSpell(AMPLIFY_MAGIC_1); + MAGE_ARMOR = m_ai->initSpell(MAGE_ARMOR_1); + MIRROR_IMAGE = m_ai->initSpell(MIRROR_IMAGE_1); + ARCANE_INTELLECT = m_ai->initSpell(ARCANE_INTELLECT_1); + ARCANE_BRILLIANCE = m_ai->initSpell(ARCANE_BRILLIANCE_1); + DALARAN_INTELLECT = m_ai->initSpell(DALARAN_INTELLECT_1); + DALARAN_BRILLIANCE = m_ai->initSpell(DALARAN_BRILLIANCE_1); + MANA_SHIELD = m_ai->initSpell(MANA_SHIELD_1); + CONJURE_WATER = m_ai->initSpell(CONJURE_WATER_1); + CONJURE_FOOD = m_ai->initSpell(CONJURE_FOOD_1); + FIREBALL = m_ai->initSpell(FIREBALL_1); + FIRE_BLAST = m_ai->initSpell(FIRE_BLAST_1); + FLAMESTRIKE = m_ai->initSpell(FLAMESTRIKE_1); + SCORCH = m_ai->initSpell(SCORCH_1); + POLYMORPH = m_ai->initSpell(POLYMORPH_1); + PRESENCE_OF_MIND = m_ai->initSpell(PRESENCE_OF_MIND_1); + PYROBLAST = m_ai->initSpell(PYROBLAST_1); + BLAST_WAVE = m_ai->initSpell(BLAST_WAVE_1); + COMBUSTION = m_ai->initSpell(COMBUSTION_1); + DRAGONS_BREATH = m_ai->initSpell(DRAGONS_BREATH_1); + LIVING_BOMB = m_ai->initSpell(LIVING_BOMB_1); + FROSTFIRE_BOLT = m_ai->initSpell(FROSTFIRE_BOLT_1); + FIRE_WARD = m_ai->initSpell(FIRE_WARD_1); + MOLTEN_ARMOR = m_ai->initSpell(MOLTEN_ARMOR_1); + ICY_VEINS = m_ai->initSpell(ICY_VEINS_1); + DEEP_FREEZE = m_ai->initSpell(DEEP_FREEZE_1); + FROSTBOLT = m_ai->initSpell(FROSTBOLT_1); + FROST_NOVA = m_ai->initSpell(FROST_NOVA_1); + BLIZZARD = m_ai->initSpell(BLIZZARD_1); + CONE_OF_COLD = m_ai->initSpell(CONE_OF_COLD_1); + ICE_BARRIER = m_ai->initSpell(ICE_BARRIER_1); + SUMMON_WATER_ELEMENTAL = m_ai->initSpell(SUMMON_WATER_ELEMENTAL_1); + FROST_WARD = m_ai->initSpell(FROST_WARD_1); + ICE_LANCE = m_ai->initSpell(ICE_LANCE_1); + FROST_ARMOR = m_ai->initSpell(FROST_ARMOR_1); + ICE_ARMOR = m_ai->initSpell(ICE_ARMOR_1); + ICE_BLOCK = m_ai->initSpell(ICE_BLOCK_1); + COLD_SNAP = m_ai->initSpell(COLD_SNAP_1); + MAGE_REMOVE_CURSE = m_ai->initSpell(REMOVE_CURSE_MAGE_1); + + // TALENTS + IMPROVED_SCORCH = 0; + for (uint8 i = 0; i < 3; i++) + { + if (m_ai->initSpell(uiImprovedScorch[i])) + IMPROVED_SCORCH = m_ai->initSpell(uiImprovedScorch[i]); + } + FIRE_VULNERABILITY = 22959; // RANGED COMBAT - SHOOT = ai->initSpell(SHOOT_2); + SHOOT = m_ai->initSpell(SHOOT_2); RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); // blood elf - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_MAGE); // draenei - ESCAPE_ARTIST = ai->initSpell(ESCAPE_ARTIST_ALL); // gnome - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll - WILL_OF_THE_FORSAKEN = ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); // blood elf + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_MAGE); // draenei + ESCAPE_ARTIST = m_ai->initSpell(ESCAPE_ARTIST_ALL); // gnome + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll + WILL_OF_THE_FORSAKEN = m_ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead } PlayerbotMageAI::~PlayerbotMageAI() {} -void PlayerbotMageAI::DoNextCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) + { + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + } + } + + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } - switch (ai->GetScenarioType()) + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - if (FIREBALL > 0) - ai->CastSpell(FIREBALL); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + break; + } + + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoNextCombatManeuverPVE(pTarget); + break; } - // ------- Non Duel combat ---------- + return RETURN_NO_ACTION_ERROR; +} - //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob +CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - // Damage Spells (primitive example) - Player *m_bot = GetPlayerBot(); Unit* pVictim = pTarget->getVictim(); bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); - if (!meleeReach && ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED) + uint32 spec = m_bot->GetSpec(); + + if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && !meleeReach) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + // switch to melee if in melee range AND can't shoot OR have no ranged (wand) equipped + else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE + && meleeReach + && (SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true))) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + + //Used to determine if this bot is highest on threat + Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + + // Remove curse on group members + if (Player* pCursedTarget = GetDispelTarget(DISPEL_CURSE)) { - // switch to ranged combat - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + if (MAGE_REMOVE_CURSE > 0 && CastSpell(MAGE_REMOVE_CURSE, pCursedTarget)) + return RETURN_CONTINUE; } - if (SHOOT > 0 && ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED && !m_bot->FindCurrentSpellBySpellId(SHOOT)) - ai->CastSpell(SHOOT, *pTarget); - //ai->TellMaster( "started auto shot." ); - else if (SHOOT > 0 && m_bot->FindCurrentSpellBySpellId(SHOOT)) - m_bot->InterruptNonMeleeSpells(true, SHOOT); - switch (SpellSequence) + if (newTarget && !m_ai->IsNeutralized(newTarget)) // Bot has aggro and the mob is not already crowd controled { - case SPELL_FROST: - if (ICY_VEINS > 0 && !m_bot->HasAura(ICY_VEINS, EFFECT_INDEX_0) && LastSpellFrost < 1 && ai->GetManaPercent() >= 3) - { - ai->CastSpell(ICY_VEINS, *m_bot); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (FROSTBOLT > 0 && LastSpellFrost < 2 && !pTarget->HasAura(FROSTBOLT, EFFECT_INDEX_0) && ai->GetManaPercent() >= 16) - { - ai->CastSpell(FROSTBOLT, *pTarget); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (FROST_WARD > 0 && LastSpellFrost < 3 && !m_bot->HasAura(FROST_WARD, EFFECT_INDEX_0) && ai->GetManaPercent() >= 19) - { - ai->CastSpell(FROST_WARD, *m_bot); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (FROST_NOVA > 0 && LastSpellFrost < 4 && meleeReach && !pTarget->HasAura(FROST_NOVA, EFFECT_INDEX_0) && ai->GetManaPercent() >= 10) - { - ai->CastSpell(FROST_NOVA, *pTarget); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (ICE_LANCE > 0 && LastSpellFrost < 5 && ai->GetManaPercent() >= 7) - { - ai->CastSpell(ICE_LANCE, *pTarget); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (BLIZZARD > 0 && LastSpellFrost < 6 && ai->GetAttackerCount() >= 5 && ai->GetManaPercent() >= 89) - { - ai->CastSpell(BLIZZARD, *pTarget); - ai->SetIgnoreUpdateTime(8); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (CONE_OF_COLD > 0 && LastSpellFrost < 7 && meleeReach && !pTarget->HasAura(CONE_OF_COLD, EFFECT_INDEX_0) && ai->GetManaPercent() >= 35) - { - ai->CastSpell(CONE_OF_COLD, *pTarget); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (DEEP_FREEZE > 0 && LastSpellFrost < 8 && pTarget->HasAura(AURA_STATE_FROZEN, EFFECT_INDEX_0) && !pTarget->HasAura(DEEP_FREEZE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 9) - { - ai->CastSpell(DEEP_FREEZE, *pTarget); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (ICE_BARRIER > 0 && LastSpellFrost < 9 && pVictim == m_bot && !m_bot->HasAura(ICE_BARRIER, EFFECT_INDEX_0) && ai->GetHealthPercent() < 50 && ai->GetManaPercent() >= 30) - { - ai->CastSpell(ICE_BARRIER, *m_bot); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (SUMMON_WATER_ELEMENTAL > 0 && LastSpellFrost < 10 && ai->GetManaPercent() >= 16) - { - ai->CastSpell(SUMMON_WATER_ELEMENTAL); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (ICE_BLOCK > 0 && LastSpellFrost < 11 && pVictim == m_bot && !m_bot->HasAura(ICE_BLOCK, EFFECT_INDEX_0) && ai->GetHealthPercent() < 30) - { - ai->CastSpell(ICE_BLOCK, *m_bot); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - else if (COLD_SNAP > 0 && LastSpellFrost < 12) + if (newTarget->GetHealthPercent() > 25) + { + // If elite + if (m_ai->IsElite(newTarget)) { - ai->CastSpell(COLD_SNAP, *m_bot); - SpellSequence = SPELL_FIRE; - LastSpellFrost = LastSpellFrost + 1; - break; - } - LastSpellFrost = 0; - //SpellSequence = SPELL_FIRE; - //break; + // If the attacker is a beast or humanoid, let's the bot give it a form more suited to the low intellect of something fool enough to attack a mage + Creature * pCreature = (Creature*) newTarget; + if (pCreature && (pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_HUMANOID || pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_BEAST)) + { + if (POLYMORPH > 0 && CastSpell(POLYMORPH, newTarget)) + return RETURN_CONTINUE; + } - case SPELL_FIRE: - if (FIRE_WARD > 0 && !m_bot->HasAura(FIRE_WARD, EFFECT_INDEX_0) && LastSpellFire < 1 && ai->GetManaPercent() >= 3) - { - ai->CastSpell(FIRE_WARD, *m_bot); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (COMBUSTION > 0 && !m_bot->HasAura(COMBUSTION, EFFECT_INDEX_0) && LastSpellFire < 2) - { - ai->CastSpell(COMBUSTION, *m_bot); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (FIREBALL > 0 && LastSpellFire < 3 && ai->GetManaPercent() >= 23) - { - ai->CastSpell(FIREBALL, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (FIRE_BLAST > 0 && LastSpellFire < 4 && ai->GetManaPercent() >= 25) - { - ai->CastSpell(FIRE_BLAST, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (FLAMESTRIKE > 0 && LastSpellFire < 5 && ai->GetManaPercent() >= 35) - { - ai->CastSpell(FLAMESTRIKE, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (SCORCH > 0 && LastSpellFire < 6 && ai->GetManaPercent() >= 10) - { - ai->CastSpell(SCORCH, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (PYROBLAST > 0 && LastSpellFire < 7 && !pTarget->HasAura(PYROBLAST, EFFECT_INDEX_0) && ai->GetManaPercent() >= 27) - { - ai->CastSpell(PYROBLAST, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (BLAST_WAVE > 0 && LastSpellFire < 8 && ai->GetAttackerCount() >= 3 && meleeReach && ai->GetManaPercent() >= 34) - { - ai->CastSpell(BLAST_WAVE, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (DRAGONS_BREATH > 0 && LastSpellFire < 9 && meleeReach && ai->GetManaPercent() >= 37) - { - ai->CastSpell(DRAGONS_BREATH, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (LIVING_BOMB > 0 && LastSpellFire < 10 && !pTarget->HasAura(LIVING_BOMB, EFFECT_INDEX_0) && ai->GetManaPercent() >= 27) - { - ai->CastSpell(LIVING_BOMB, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - else if (FROSTFIRE_BOLT > 0 && LastSpellFire < 11 && !pTarget->HasAura(FROSTFIRE_BOLT, EFFECT_INDEX_0) && ai->GetManaPercent() >= 14) - { - ai->CastSpell(FROSTFIRE_BOLT, *pTarget); - SpellSequence = SPELL_ARCANE; - LastSpellFire = LastSpellFire + 1; - break; - } - LastSpellFire = 0; - //SpellSequence = SPELL_ARCANE; - //break; + // Things are getting dire: cast Ice block + if (ICE_BLOCK > 0 && !m_bot->HasSpellCooldown(ICE_BLOCK) && m_ai->GetHealthPercent() < 30 && !m_bot->HasAura(ICE_BLOCK, EFFECT_INDEX_0) && m_ai->CastSpell(ICE_BLOCK)) + return RETURN_CONTINUE; - case SPELL_ARCANE: - if (ARCANE_POWER > 0 && LastSpellArcane < 1 && ai->GetManaPercent() >= 37) - { - ai->CastSpell(ARCANE_POWER, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (ARCANE_MISSILES > 0 && LastSpellArcane < 2 && ai->GetManaPercent() >= 37) - { - ai->CastSpell(ARCANE_MISSILES, *pTarget); - ai->SetIgnoreUpdateTime(3); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (ARCANE_EXPLOSION > 0 && LastSpellArcane < 3 && ai->GetAttackerCount() >= 3 && meleeReach && ai->GetManaPercent() >= 27) - { - ai->CastSpell(ARCANE_EXPLOSION, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (COUNTERSPELL > 0 && pTarget->IsNonMeleeSpellCasted(true) && LastSpellArcane < 4 && ai->GetManaPercent() >= 9) - { - ai->CastSpell(COUNTERSPELL, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (SLOW > 0 && LastSpellArcane < 5 && !pTarget->HasAura(SLOW, EFFECT_INDEX_0) && ai->GetManaPercent() >= 12) - { - ai->CastSpell(SLOW, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (ARCANE_BARRAGE > 0 && LastSpellArcane < 6 && ai->GetManaPercent() >= 27) - { - ai->CastSpell(ARCANE_BARRAGE, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (ARCANE_BLAST > 0 && LastSpellArcane < 7 && ai->GetManaPercent() >= 8) - { - ai->CastSpell(ARCANE_BLAST, *pTarget); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (MIRROR_IMAGE > 0 && LastSpellArcane < 8 && ai->GetManaPercent() >= 10) - { - ai->CastSpell(MIRROR_IMAGE); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; - } - else if (MANA_SHIELD > 0 && LastSpellArcane < 9 && ai->GetHealthPercent() < 70 && pVictim == m_bot && !m_bot->HasAura(MANA_SHIELD, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(MANA_SHIELD, *m_bot); - SpellSequence = SPELL_FROST; - LastSpellArcane = LastSpellArcane + 1; - break; + // Cast Ice Barrier if health starts to goes low + if (ICE_BARRIER > 0 && !m_bot->HasSpellCooldown(ICE_BARRIER) && m_ai->GetHealthPercent() < 50 && !m_bot->HasAura(ICE_BARRIER) && m_ai->SelfBuff(ICE_BARRIER)) + return RETURN_CONTINUE; + + // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on. + return CastSpell(SHOOT, pTarget); } - else + else // not elite { - LastSpellArcane = 0; - SpellSequence = SPELL_FROST; + // Cast mana shield if no shield is already up + if (MANA_SHIELD > 0 && m_ai->GetHealthPercent() < 70 && !m_bot->HasAura(MANA_SHIELD) && !m_bot->HasAura(ICE_BARRIER) && m_ai->SelfBuff(MANA_SHIELD)) + return RETURN_CONTINUE; } + } } + + // Mana check and replenishment + if (EVOCATION && m_ai->GetManaPercent() <= 10 && !m_bot->HasSpellCooldown(EVOCATION) && !newTarget && m_ai->SelfBuff(EVOCATION)) + return RETURN_CONTINUE; + if (m_ai->GetManaPercent() <= 20) + { + Item* gem = FindManaGem(); + if (gem) + m_ai->UseItem(gem); + } + + // If bot has frost/fire resist order use Frost/Fire Ward when available + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FROST && FROST_WARD && !m_bot->HasSpellCooldown(FROST_WARD) && m_ai->SelfBuff(FROST_WARD)) + return RETURN_CONTINUE; + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FIRE && FIRE_WARD && !m_bot->HasSpellCooldown(FIRE_WARD) && m_ai->SelfBuff(FIRE_WARD)) + return RETURN_CONTINUE; + + if (COUNTERSPELL > 0 && !m_bot->HasSpellCooldown(COUNTERSPELL) && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(COUNTERSPELL, pTarget)) + return RETURN_CONTINUE; + + // If Clearcasting is active, cast arcane missiles + // Bot could also cast flamestrike or blizzard for free, but the AoE could break some crowd control + // or add threat on mobs ignoring the bot currently, so only focus on the bot's current target + if (m_bot->HasAura(CLEARCASTING_1) && ARCANE_MISSILES > 0 && CastSpell(ARCANE_MISSILES, pTarget)) + { + m_ai->SetIgnoreUpdateTime(3); + return RETURN_CONTINUE; + } + + switch (spec) + { + case MAGE_SPEC_FROST: + if (COLD_SNAP && !m_bot->HasSpellCooldown(COLD_SNAP) && CheckFrostCooldowns() > 2 && m_ai->SelfBuff(COLD_SNAP)) // Clear frost spell cooldowns if bot has more than 2 active + return RETURN_CONTINUE; + if (CONE_OF_COLD > 0 && !m_bot->HasSpellCooldown(CONE_OF_COLD) && meleeReach) + { + // Cone of Cold does not require a target, so ensure that the bot faces the current one before casting + m_ai->FaceTarget(pTarget); + if (m_ai->CastSpell(CONE_OF_COLD)) + return RETURN_CONTINUE; + } + if (FROSTBOLT > 0 && m_ai->In_Reach(pTarget,FROSTBOLT) && !pTarget->HasAura(FROSTBOLT, EFFECT_INDEX_0) && CastSpell(FROSTBOLT, pTarget)) + return RETURN_CONTINUE; + if (FROST_NOVA > 0 && !m_bot->HasSpellCooldown(FROST_NOVA) && meleeReach && !pTarget->HasAura(FROST_NOVA, EFFECT_INDEX_0) && CastSpell(FROST_NOVA, pTarget)) + return RETURN_CONTINUE; + // Default frost spec action + if (FROSTBOLT > 0 && m_ai->In_Reach(pTarget,FROSTBOLT)) + return CastSpell(FROSTBOLT, pTarget); + /* + if (BLIZZARD > 0 && m_ai->In_Reach(pTarget,BLIZZARD) && m_ai->GetAttackerCount() >= 5 && CastSpell(BLIZZARD, pTarget)) + { + m_ai->SetIgnoreUpdateTime(8); + return RETURN_CONTINUE; + } + */ + break; + + case MAGE_SPEC_FIRE: + if (COMBUSTION > 0 && m_ai->SelfBuff(COMBUSTION)) + return RETURN_CONTINUE; + if (BLAST_WAVE > 0 && m_ai->GetAttackerCount() >= 3 && meleeReach && CastSpell(BLAST_WAVE, pTarget)) + return RETURN_CONTINUE; + // Try to have 3 scorch stacks to let tank build aggro while getting a nice crit% bonus + if (IMPROVED_SCORCH > 0 && SCORCH > 0) + { + if (!pTarget->HasAura(FIRE_VULNERABILITY, EFFECT_INDEX_0) && CastSpell(SCORCH, pTarget)) // no stacks: cast it + return RETURN_CONTINUE; + else + { + SpellAuraHolder* holder = pTarget->GetSpellAuraHolder(FIRE_VULNERABILITY); + if (holder && (holder->GetStackAmount() < 3) && CastSpell(SCORCH, pTarget)) + return RETURN_CONTINUE; + } + } + // At least 3 stacks of Scorch: cast an opening fireball + if (FIREBALL > 0 && !pTarget->HasAura(FIREBALL, EFFECT_INDEX_1) && CastSpell(FIREBALL, pTarget)) + return RETURN_CONTINUE; + // 3 stacks of Scorch and fireball DoT: use fire blast if available + if (FIRE_BLAST > 0 && !m_bot->HasSpellCooldown(FIRE_BLAST) && CastSpell(FIRE_BLAST, pTarget)) + return RETURN_CONTINUE; + // All DoTs, cooldowns used, try to maximise scorch stacks (5) to get a even nicer crit% bonus + if (IMPROVED_SCORCH > 0 && SCORCH > 0) + { + SpellAuraHolder* holder = pTarget->GetSpellAuraHolder(FIRE_VULNERABILITY); + if (holder && (holder->GetStackAmount() < 5) && CastSpell(SCORCH, pTarget)) + return RETURN_CONTINUE; + } + // Default fire spec action + if (FIREBALL > 0 && m_ai->In_Reach(pTarget,FIREBALL)) + return CastSpell(FIREBALL, pTarget); + /* + if (FLAMESTRIKE > 0 && m_ai->In_Reach(pTarget,FLAMESTRIKE) && CastSpell(FLAMESTRIKE, pTarget)) + return RETURN_CONTINUE; + */ + break; + + case MAGE_SPEC_ARCANE: + if (ARCANE_POWER > 0 && !m_bot->HasSpellCooldown(ARCANE_POWER) && m_ai->IsElite(pTarget) && m_ai->CastSpell(ARCANE_POWER)) // Do not waste Arcane Power on normal NPCs as the bot is likely in a group + return RETURN_CONTINUE; + if (PRESENCE_OF_MIND > 0 && !m_bot->HasAura(PRESENCE_OF_MIND) && !m_bot->HasSpellCooldown(PRESENCE_OF_MIND) && m_ai->IsElite(pTarget) && m_ai->SelfBuff(PRESENCE_OF_MIND)) + return RETURN_CONTINUE; + // If bot has presence of mind active, cast long casting time spells + if (PRESENCE_OF_MIND && m_bot->HasAura(PRESENCE_OF_MIND)) + { + // Instant Pyroblast, yeah! Tanks will probably hate this, but what do they know about power? Nothing... + if (PYROBLAST > 0 && CastSpell(PYROBLAST, pTarget)) + return RETURN_CONTINUE; + if (FIREBALL > 0 && CastSpell(FIREBALL, pTarget)) + return RETURN_CONTINUE; + } + if (ARCANE_EXPLOSION > 0 && m_ai->GetAttackerCount() >= 3 && meleeReach && CastSpell(ARCANE_EXPLOSION, pTarget)) + return RETURN_CONTINUE; + // Default arcane spec actions (yes, two fire spells) + if (FIRE_BLAST > 0 && !m_bot->HasSpellCooldown(FIRE_BLAST) && CastSpell(FIRE_BLAST, pTarget)) + return RETURN_CONTINUE; + if (FIREBALL > 0 && m_ai->In_Reach(pTarget,FIREBALL)) + return CastSpell(FIREBALL, pTarget); + // If no fireball, arcane missiles + if (ARCANE_MISSILES > 0 && CastSpell(ARCANE_MISSILES, pTarget)) + { + m_ai->SetIgnoreUpdateTime(3); + return RETURN_CONTINUE; + } + break; + } + + // No spec due to low level OR no spell found yet + if (FROSTBOLT > 0 && m_ai->In_Reach(pTarget,FROSTBOLT) && !pTarget->HasAura(FROSTBOLT, EFFECT_INDEX_0) && CastSpell(FROSTBOLT, pTarget)) + return RETURN_CONTINUE; + if (FIREBALL > 0 && m_ai->In_Reach(pTarget,FIREBALL) && CastSpell(FIREBALL, pTarget)) // Very low levels + return RETURN_CONTINUE; + + // Default: shoot with wand + return CastSpell(SHOOT, pTarget); + + return RETURN_NO_ACTION_ERROR; // What? Not even Fireball or wand are available? } // end DoNextCombatManeuver +CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + if (FIREBALL && m_ai->In_Reach(pTarget,FIREBALL) && m_ai->CastSpell(FIREBALL)) + return RETURN_CONTINUE; + + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} + +// Function to keep track of active frost cooldowns to clear with Cold Snap +uint8 PlayerbotMageAI::CheckFrostCooldowns() +{ + uint8 uiFrostActiveCooldown = 0; + if (FROST_NOVA && m_bot->HasSpellCooldown(FROST_NOVA)) + uiFrostActiveCooldown++; + if (ICE_BARRIER && m_bot->HasSpellCooldown(ICE_BARRIER)) + uiFrostActiveCooldown++; + if (CONE_OF_COLD && m_bot->HasSpellCooldown(CONE_OF_COLD)) + uiFrostActiveCooldown++; + if (ICE_BLOCK && m_bot->HasSpellCooldown(ICE_BLOCK)) + uiFrostActiveCooldown++; + if (FROST_WARD && m_bot->HasSpellCooldown(FROST_WARD)) + uiFrostActiveCooldown++; + + return uiFrostActiveCooldown; +} + +static const uint32 uPriorizedManaGemIds[4] = +{ + MANA_RUBY_DISPLAYID, MANA_CITRINE_DISPLAYID, MANA_AGATE_DISPLAYID, MANA_JADE_DISPLAYID +}; + +// Return a mana gem Item based on the priorized list +Item* PlayerbotMageAI::FindManaGem() const +{ + Item* gem; + for (uint8 i = 0; i < countof(uPriorizedManaGemIds); ++i) + { + gem = m_ai->FindConsumable(uPriorizedManaGemIds[i]); + if (gem) + return gem; + } + return nullptr; +} + void PlayerbotMageAI::DoNonCombatActions() { - Player * m_bot = GetPlayerBot(); - Player * master = GetMaster(); + Player* master = GetMaster(); if (!m_bot || !master) return; - SpellSequence = SPELL_FROST; - PlayerbotAI* ai = GetAI(); - - // Buff armor - if (MOLTEN_ARMOR) + // Remove curse on group members if orders allow bot to do so + if (Player* pCursedTarget = GetDispelTarget(DISPEL_CURSE)) { - if (ai->SelfBuff(MOLTEN_ARMOR)) + if (MAGE_REMOVE_CURSE > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && CastSpell(MAGE_REMOVE_CURSE, pCursedTarget)) return; } - else if (MAGE_ARMOR) + + // Buff armor + if (MAGE_ARMOR) { - if (ai->SelfBuff(MAGE_ARMOR)) + if (m_ai->SelfBuff(MAGE_ARMOR)) return; } else if (ICE_ARMOR) { - if (ai->SelfBuff(ICE_ARMOR)) + if (m_ai->SelfBuff(ICE_ARMOR)) return; } else if (FROST_ARMOR) - if (ai->SelfBuff(FROST_ARMOR)) - return; - - // buff master's group - if (master->GetGroup()) { - // Buff master with group buff... - if (ARCANE_BRILLIANCE && ai->HasSpellReagents(ARCANE_BRILLIANCE)) - if (ai->Buff(ARCANE_BRILLIANCE, master)) - return; - - // ...and check group for new members joined or resurrected, or just buff everyone if no group buff available - Group::MemberSlotList const& groupSlot = GetMaster()->GetGroup()->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) - { - Player *tPlayer = sObjectMgr.GetPlayer(itr->guid); - if (!tPlayer || !tPlayer->isAlive() || tPlayer == m_bot) - continue; - // buff - if (BuffPlayer(tPlayer)) - return; - } - + if (m_ai->SelfBuff(FROST_ARMOR)) + return; } - // There is no group, buff master - else if (master->isAlive() && BuffPlayer(master)) - return; - // Buff self finally - if (BuffPlayer(m_bot)) + if (COMBUSTION && !m_bot->HasSpellCooldown(COMBUSTION) && m_ai->SelfBuff(COMBUSTION)) return; - // conjure food & water - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); - - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); - - if (pItem == nullptr && CONJURE_WATER && ai->GetBaseManaPercent() >= 48) + // buff group + // the check for group targets is performed by NeedGroupBuff (if group is found for bots by the function) + if (NeedGroupBuff(ARCANE_BRILLIANCE, ARCANE_INTELLECT) && m_ai->HasSpellReagents(ARCANE_BRILLIANCE)) { - ai->TellMaster("I'm conjuring some water."); - ai->CastSpell(CONJURE_WATER, *m_bot); - ai->SetIgnoreUpdateTime(3); - return; + if (Buff(&PlayerbotMageAI::BuffHelper, ARCANE_BRILLIANCE) & RETURN_CONTINUE) + return; } - else if (pItem != nullptr && ai->GetManaPercent() < 30) - { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); + else if (Buff(&PlayerbotMageAI::BuffHelper, ARCANE_INTELLECT, JOB_MANAONLY) & RETURN_CONTINUE) return; - } - - pItem = ai->FindFood(); - if (pItem == nullptr && CONJURE_FOOD && ai->GetBaseManaPercent() >= 48) + Item* gem = FindManaGem(); + if (!gem && CONJURE_MANA_GEM && m_ai->CastSpell(CONJURE_MANA_GEM, *m_bot)) { - ai->TellMaster("I'm conjuring some food."); - ai->CastSpell(CONJURE_FOOD, *m_bot); - ai->SetIgnoreUpdateTime(3); + m_ai->SetIgnoreUpdateTime(3); + return; } - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); - - pItem = ai->FindFood(); - - if (pItem != nullptr && ai->GetHealthPercent() < 30) + // TODO: The beauty of a mage is not only its ability to supply itself with water, but to share its water + // So, conjure at *least* 1.25 stacks, ready to trade a stack and still have some left for self + if (m_ai->FindDrink() == nullptr && CONJURE_WATER && m_ai->CastSpell(CONJURE_WATER, *m_bot)) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); + m_ai->TellMaster("I'm conjuring some water."); + m_ai->SetIgnoreUpdateTime(3); return; } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + if (m_ai->FindFood() == nullptr && CONJURE_FOOD && m_ai->CastSpell(CONJURE_FOOD, *m_bot)) { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); + m_ai->TellMaster("I'm conjuring some food."); + m_ai->SetIgnoreUpdateTime(3); return; } + if (EatDrinkBandage()) + return; } // end DoNonCombatActions -bool PlayerbotMageAI::BuffPlayer(Player* target) +// TODO: this and priest's BuffHelper are identical and thus could probably go in PlayerbotClassAI.cpp somewhere +bool PlayerbotMageAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target) { - PlayerbotAI * ai = GetAI(); - Pet * pet = target->GetPet(); + if (!ai) return false; + if (spellId == 0) return false; + if (!target) return false; - if (pet && pet->GetPowerType() == POWER_MANA && ai->Buff(ARCANE_INTELLECT, pet)) + Pet* pet = target->GetPet(); + if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet)) return true; - if (ARCANE_INTELLECT) - return ai->Buff(ARCANE_INTELLECT, target); + if (ai->Buff(spellId, target)) + return true; + + return false; +} + +// Return to UpdateAI the spellId usable to neutralize a target with creaturetype +uint32 PlayerbotMageAI::Neutralize(uint8 creatureType) +{ + if (!m_bot) return 0; + if (!m_ai) return 0; + if (!creatureType) return 0; + + if (creatureType != CREATURE_TYPE_HUMANOID && creatureType != CREATURE_TYPE_BEAST) + { + m_ai->TellMaster("I can't polymorph that target."); + return 0; + } + + if (POLYMORPH) + return POLYMORPH; else - return false; + return 0; + + return 0; } diff --git a/src/game/playerbot/PlayerbotMageAI.h b/src/game/playerbot/PlayerbotMageAI.h index d1be13610..b232d848a 100644 --- a/src/game/playerbot/PlayerbotMageAI.h +++ b/src/game/playerbot/PlayerbotMageAI.h @@ -10,6 +10,14 @@ enum SPELL_ARCANE }; +enum ManaGemIds +{ + MANA_RUBY_DISPLAYID = 7045, + MANA_CITRINE_DISPLAYID = 6496, + MANA_AGATE_DISPLAYID = 6851, + MANA_JADE_DISPLAYID = 7393 +}; + enum MageSpells { AMPLIFY_MAGIC_1 = 1008, @@ -23,6 +31,7 @@ enum MageSpells BLAST_WAVE_1 = 11113, BLINK_1 = 1953, BLIZZARD_1 = 10, + CLEARCASTING_1 = 12536, COLD_SNAP_1 = 11958, COMBUSTION_1 = 11129, CONE_OF_COLD_1 = 120, @@ -58,6 +67,7 @@ enum MageSpells MANA_SHIELD_1 = 1463, MIRROR_IMAGE_1 = 55342, MOLTEN_ARMOR_1 = 30482, + POLYMORPH_1 = 118, PRESENCE_OF_MIND_1 = 12043, PYROBLAST_1 = 11366, REMOVE_CURSE_MAGE_1 = 475, @@ -69,6 +79,19 @@ enum MageSpells SPELLSTEAL_1 = 30449, SUMMON_WATER_ELEMENTAL_1 = 31687 }; + +enum MageTalents +{ + IMPROVED_SCORCH_1 = 11095, + IMPROVED_SCORCH_2 = 12872, + IMPROVED_SCORCH_3 = 12873 +}; + +static const uint32 uiImprovedScorch[3] = +{ + IMPROVED_SCORCH_1, IMPROVED_SCORCH_2, IMPROVED_SCORCH_3 +}; + //class Player; class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI @@ -78,24 +101,39 @@ class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI virtual ~PlayerbotMageAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + uint32 Neutralize(uint8 creatureType); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); - // buff a specific player, usually a real PC who is not in group - bool BuffPlayer(Player *target); - private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + Item* FindManaGem() const; + + CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = nullptr) { return CastSpellWand(nextAction, pTarget, SHOOT); } + + static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target); + + uint8 CheckFrostCooldowns(); + // ARCANE uint32 ARCANE_MISSILES, ARCANE_EXPLOSION, COUNTERSPELL, + EVOCATION, + POLYMORPH, + PRESENCE_OF_MIND, SLOW, ARCANE_BARRAGE, ARCANE_BLAST, MIRROR_IMAGE, ARCANE_POWER; + // ranged uint32 SHOOT; @@ -104,6 +142,8 @@ class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI FIRE_BLAST, FLAMESTRIKE, SCORCH, + FIRE_VULNERABILITY, + IMPROVED_SCORCH, PYROBLAST, BLAST_WAVE, COMBUSTION, @@ -137,17 +177,14 @@ class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI DALARAN_BRILLIANCE, MANA_SHIELD, DAMPEN_MAGIC, - AMPLIFY_MAGIC; - - // first aid - uint32 RECENTLY_BANDAGED; + AMPLIFY_MAGIC, + MAGE_REMOVE_CURSE; // racial uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, - EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, @@ -159,7 +196,8 @@ class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI LastSpellFire, LastSpellFrost, CONJURE_WATER, - CONJURE_FOOD; + CONJURE_FOOD, + CONJURE_MANA_GEM; }; #endif diff --git a/src/game/playerbot/PlayerbotMgr.cpp b/src/game/playerbot/PlayerbotMgr.cpp index ebbf28ca9..731336ac4 100644 --- a/src/game/playerbot/PlayerbotMgr.cpp +++ b/src/game/playerbot/PlayerbotMgr.cpp @@ -111,7 +111,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_ACTIVATETAXI: + case CMSG_ACTIVATETAXI: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -120,9 +120,10 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) std::vector nodes; nodes.resize(2); uint8 delay = 9; + p >> guid >> nodes[0] >> nodes[1]; - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_ACTIVATETAXI from %d to %d", nodes[0], nodes[1]); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_ACTIVATETAXI from %d to %d", nodes[0], nodes[1]); for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -147,7 +148,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_ACTIVATETAXIEXPRESS: + case CMSG_ACTIVATETAXIEXPRESS: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -155,11 +156,13 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) ObjectGuid guid; uint32 node_count; uint8 delay = 9; + p >> guid; p.read_skip(); p >> node_count; std::vector nodes; + for (uint32 i = 0; i < node_count; ++i) { uint32 node; @@ -170,7 +173,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (nodes.empty()) return; - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_ACTIVATETAXIEXPRESS from %d to %d", nodes.front(), nodes.back()); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_ACTIVATETAXIEXPRESS from %d to %d", nodes.front(), nodes.back()); for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -179,9 +182,11 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) Player* const bot = it->second; if (!bot) return; + Group* group = bot->GetGroup(); if (!group) continue; + Unit *target = ObjectAccessor::GetUnit(*bot, guid); bot->GetPlayerbotAI()->SetIgnoreUpdateTime(delay); @@ -193,9 +198,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_MOVE_SPLINE_DONE: + case CMSG_MOVE_SPLINE_DONE: { - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE"); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE"); WorldPacket p(packet); p.rpos(0); // reset reader @@ -257,30 +262,30 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) bot->GetSession()->SendPacket(data); } - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE Taxi has to go from %u to %u", sourcenode, destinationnode); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE Taxi has to go from %u to %u", sourcenode, destinationnode); - uint32 mountDisplayId = sObjectMgr.GetTaxiMountDisplayId(sourcenode, bot->GetTeam()); + uint32 mountDisplayId = sObjectMgr.GetTaxiMountDisplayId(sourcenode, bot->GetTeam()); - uint32 path, cost; - sObjectMgr.GetTaxiPath(sourcenode, destinationnode, path, cost); + uint32 path, cost; + sObjectMgr.GetTaxiPath(sourcenode, destinationnode, path, cost); - if (path && mountDisplayId) - bot->GetSession()->SendDoFlight(mountDisplayId, path, 1); // skip start fly node - else - bot->m_taxi.ClearTaxiDestinations(); // clear problematic path and next + if (path && mountDisplayId) + bot->GetSession()->SendDoFlight(mountDisplayId, path, 1); // skip start fly node + else + bot->m_taxi.ClearTaxiDestinations(); // clear problematic path and next } else /* std::ostringstream out; - out << "Destination reached" << bot->GetName(); - ChatHandler ch(m_master); - ch.SendSysMessage(out.str().c_str()); */ + out << "Destination reached" << bot->GetName(); + ChatHandler ch(m_master); + ch.SendSysMessage(out.str().c_str()); */ bot->m_taxi.ClearTaxiDestinations(); // Destination, clear source node } return; } // if master is logging out, log out all bots - case CMSG_LOGOUT_REQUEST: + case CMSG_LOGOUT_REQUEST: { LogoutAllBots(); return; @@ -288,7 +293,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) // If master inspects one of his bots, give the master useful info in chat window // such as inventory that can be equipped - case CMSG_INSPECT: + case CMSG_INSPECT: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -301,7 +306,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) // handle emotes from the master //case CMSG_EMOTE: - case CMSG_TEXT_EMOTE: + case CMSG_TEXT_EMOTE: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -309,13 +314,13 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) p >> emoteNum; /* std::ostringstream out; - out << "emote is: " << emoteNum; - ChatHandler ch(m_master); - ch.SendSysMessage(out.str().c_str()); */ + out << "emote is: " << emoteNum; + ChatHandler ch(m_master); + ch.SendSysMessage(out.str().c_str()); */ switch (emoteNum) { - case TEXTEMOTE_BOW: + case TEXTEMOTE_BOW: { // Buff anyone who bows before me. Useful for players not in bot's group // How do I get correct target??? @@ -325,53 +330,53 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } /* - case TEXTEMOTE_BONK: - { - Player* const pPlayer = GetPlayerBot(m_master->GetSelection()); - if (!pPlayer || !pPlayer->GetPlayerbotAI()) - return; - PlayerbotAI* const pBot = pPlayer->GetPlayerbotAI(); + case TEXTEMOTE_BONK: + { + Player* const pPlayer = GetPlayerBot(m_master->GetSelection()); + if (!pPlayer || !pPlayer->GetPlayerbotAI()) + return; + PlayerbotAI* const pBot = pPlayer->GetPlayerbotAI(); - ChatHandler ch(m_master); - { - std::ostringstream out; - out << "CurrentTime: " << CurrentTime() - << " m_ignoreAIUpdatesUntilTime: " << pBot->m_ignoreAIUpdatesUntilTime; - ch.SendSysMessage(out.str().c_str()); - } - { - std::ostringstream out; - out << "m_TimeDoneEating: " << pBot->m_TimeDoneEating - << " m_TimeDoneDrinking: " << pBot->m_TimeDoneDrinking; - ch.SendSysMessage(out.str().c_str()); - } - { - std::ostringstream out; - out << "m_CurrentlyCastingSpellId: " << pBot->m_CurrentlyCastingSpellId; - ch.SendSysMessage(out.str().c_str()); - } - { - std::ostringstream out; - out << "IsBeingTeleported() " << pBot->GetPlayer()->IsBeingTeleported(); - ch.SendSysMessage(out.str().c_str()); - } - { - std::ostringstream out; - bool tradeActive = (pBot->GetPlayer()->GetTrader()) ? true : false; - out << "tradeActive: " << tradeActive; - ch.SendSysMessage(out.str().c_str()); - } - { - std::ostringstream out; - out << "IsCharmed() " << pBot->getPlayer()->isCharmed(); - ch.SendSysMessage(out.str().c_str()); - } - return; - } - */ + ChatHandler ch(m_master); + { + std::ostringstream out; + out << "CurrentTime: " << CurrentTime() + << " m_ignoreAIUpdatesUntilTime: " << pBot->m_ignoreAIUpdatesUntilTime; + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + out << "m_TimeDoneEating: " << pBot->m_TimeDoneEating + << " m_TimeDoneDrinking: " << pBot->m_TimeDoneDrinking; + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + out << "m_CurrentlyCastingSpellId: " << pBot->m_CurrentlyCastingSpellId; + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + out << "IsBeingTeleported() " << pBot->GetPlayer()->IsBeingTeleported(); + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + bool tradeActive = (pBot->GetPlayer()->GetTrader()) ? true : false; + out << "tradeActive: " << tradeActive; + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + out << "IsCharmed() " << pBot->getPlayer()->isCharmed(); + ch.SendSysMessage(out.str().c_str()); + } + return; + } + */ - case TEXTEMOTE_EAT: - case TEXTEMOTE_DRINK: + case TEXTEMOTE_EAT: + case TEXTEMOTE_DRINK: { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -382,7 +387,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) } // emote to attack selected target - case TEXTEMOTE_POINT: + case TEXTEMOTE_POINT: { ObjectGuid attackOnGuid = m_master->GetSelectionGuid(); if (!attackOnGuid) @@ -395,20 +400,19 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) { bot = itr->second; - if (!bot->IsFriendlyTo(thingToAttack) && !bot->IsWithinLOSInMap(thingToAttack)) + if (!bot->IsFriendlyTo(thingToAttack)) { - bot->GetPlayerbotAI()->DoTeleport(*m_master); + if (!bot->IsWithinLOSInMap(thingToAttack)) + bot->GetPlayerbotAI()->DoTeleport(*m_master); if (bot->IsWithinLOSInMap(thingToAttack)) - bot->GetPlayerbotAI()->GetCombatTarget(thingToAttack); + bot->GetPlayerbotAI()->Attack(thingToAttack); } - else if (!bot->IsFriendlyTo(thingToAttack) && bot->IsWithinLOSInMap(thingToAttack)) - bot->GetPlayerbotAI()->GetCombatTarget(thingToAttack); } return; } // emote to stay - case TEXTEMOTE_STAND: + case TEXTEMOTE_STAND: { Player* const bot = GetPlayerBot(m_master->GetSelectionGuid()); if (bot) @@ -419,13 +423,13 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) Player* const bot = it->second; bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY); } - return; + return; } // 324 is the followme emote (not defined in enum) // if master has bot selected then only bot follows, else all bots follow - case 324: - case TEXTEMOTE_WAVE: + case 324: + case TEXTEMOTE_WAVE: { Player* const bot = GetPlayerBot(m_master->GetSelectionGuid()); if (bot) @@ -436,7 +440,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) Player* const bot = it->second; bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, m_master); } - return; + return; } } return; @@ -444,6 +448,8 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) case CMSG_GAMEOBJ_USE: // not sure if we still need this one { + DEBUG_LOG("PlayerbotMgr: CMSG_GAMEOBJ_USE"); + WorldPacket p(packet); p.rpos(0); // reset reader ObjectGuid objGUID; @@ -458,20 +464,36 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) Player* const bot = it->second; bot->GetPlayerbotAI()->FollowAutoReset(); - if (obj->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER) - bot->GetPlayerbotAI()->TurnInQuests(obj); // add other go types here, i.e.: // GAMEOBJECT_TYPE_CHEST - loot quest items of chest + if (obj->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER) + { + bot->GetPlayerbotAI()->TurnInQuests(obj); + + // auto accept every available quest this NPC has + bot->PrepareQuestMenu(objGUID); + QuestMenu& questMenu = bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + uint32 questID = qItem.m_qId; + if (!bot->GetPlayerbotAI()->AddQuest(questID, obj)) + DEBUG_LOG("Couldn't take quest"); + } + } } } break; - case CMSG_QUESTGIVER_HELLO: + case CMSG_QUESTGIVER_HELLO: { + DEBUG_LOG("PlayerbotMgr: CMSG_QUESTGIVER_HELLO"); + WorldPacket p(packet); p.rpos(0); // reset reader ObjectGuid npcGUID; p >> npcGUID; + WorldObject* pNpc = m_master->GetMap()->GetWorldObject(npcGUID); if (!pNpc) return; @@ -488,7 +510,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) } // if master accepts a quest, bots should also try to accept quest - case CMSG_QUESTGIVER_ACCEPT_QUEST: + case CMSG_QUESTGIVER_ACCEPT_QUEST: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -527,25 +549,25 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) // build needed items if quest contains any for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++) - if (qInfo->ReqItemCount[i]>0) + if (qInfo->ReqItemCount[i] > 0) { bot->GetPlayerbotAI()->SetQuestNeedItems(); break; } - // build needed creatures if quest contains any - for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) - if (qInfo->ReqCreatureOrGOCount[i] > 0) - { - bot->GetPlayerbotAI()->SetQuestNeedCreatures(); - break; - } + // build needed creatures if quest contains any + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + if (qInfo->ReqCreatureOrGOCount[i] > 0) + { + bot->GetPlayerbotAI()->SetQuestNeedCreatures(); + break; + } } } - return; + return; } - case CMSG_AREATRIGGER: + case CMSG_AREATRIGGER: { WorldPacket p(packet); @@ -564,7 +586,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_QUESTGIVER_COMPLETE_QUEST: + case CMSG_QUESTGIVER_COMPLETE_QUEST: { WorldPacket p(packet); p.rpos(0); // reset reader @@ -572,7 +594,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) ObjectGuid npcGUID; p >> npcGUID >> quest; - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_QUESTGIVER_COMPLETE_QUEST npc = %s, quest = %u", npcGUID.GetString().c_str(), quest); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_QUESTGIVER_COMPLETE_QUEST npc = %s, quest = %u", npcGUID.GetString().c_str(), quest); WorldObject* pNpc = m_master->GetMap()->GetWorldObject(npcGUID); if (!pNpc) @@ -588,7 +610,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_LOOT_ROLL: + case CMSG_LOOT_ROLL: { WorldPacket p(packet); //WorldPacket packet for CMSG_LOOT_ROLL, (8+4+1) ObjectGuid Guid; @@ -646,15 +668,17 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) } return; } + // Handle GOSSIP activate actions, prior to GOSSIP select menu actions - case CMSG_GOSSIP_HELLO: + case CMSG_GOSSIP_HELLO: { - // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO"); + DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO"); WorldPacket p(packet); //WorldPacket packet for CMSG_GOSSIP_HELLO, (8) ObjectGuid guid; p.rpos(0); //reset packet pointer p >> guid; + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { Player* const bot = it->second; @@ -678,19 +702,19 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) switch (itr->second.option_id) { - case GOSSIP_OPTION_TAXIVENDOR: + case GOSSIP_OPTION_TAXIVENDOR: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_TAXIVENDOR"); bot->GetSession()->SendLearnNewTaxiNode(pCreature); break; } - case GOSSIP_OPTION_QUESTGIVER: + case GOSSIP_OPTION_QUESTGIVER: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_QUESTGIVER"); bot->GetPlayerbotAI()->TurnInQuests(pCreature); break; } - case GOSSIP_OPTION_VENDOR: + case GOSSIP_OPTION_VENDOR: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_VENDOR"); if (!botConfig.GetBoolDefault("PlayerbotAI.SellGarbage", true)) @@ -700,22 +724,22 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) bot->GetPlayerbotAI()->SellGarbage(*bot); break; } - case GOSSIP_OPTION_STABLEPET: + case GOSSIP_OPTION_STABLEPET: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_STABLEPET"); break; } - case GOSSIP_OPTION_AUCTIONEER: + case GOSSIP_OPTION_AUCTIONEER: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_AUCTIONEER"); break; } - case GOSSIP_OPTION_BANKER: + case GOSSIP_OPTION_BANKER: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_BANKER"); break; } - case GOSSIP_OPTION_INNKEEPER: + case GOSSIP_OPTION_INNKEEPER: { // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_INNKEEPER"); break; @@ -726,7 +750,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_SPIRIT_HEALER_ACTIVATE: + case CMSG_SPIRIT_HEALER_ACTIVATE: { // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_SPIRIT_HEALER_ACTIVATE SpiritHealer is resurrecting the Player %s",m_master->GetName()); for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) @@ -739,7 +763,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; } - case CMSG_LIST_INVENTORY: + case CMSG_LIST_INVENTORY: { if (!botConfig.GetBoolDefault("PlayerbotAI.SellGarbage", true)) return; @@ -754,7 +778,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) return; // for all master's bots - for(PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { Player* const bot = it->second; if (!bot->IsInMap(static_cast(pNpc))) @@ -788,51 +812,50 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) case CMSG_GAMEOBJECT_QUERY: case MSG_MOVE_JUMP: case MSG_MOVE_FALL_LAND: - return; + return;*/ - default: + default: { - const char* oc = LookupOpcodeName(packet.GetOpcode()); - // ChatHandler ch(m_master); - // ch.SendSysMessage(oc); + /*const char* oc = LookupOpcodeName(packet.GetOpcode()); + // ChatHandler ch(m_master); + // ch.SendSysMessage(oc); - std::ostringstream out; - out << "masterin: " << oc; - sLog.outError(out.str().c_str()); + std::ostringstream out; + out << "masterin: " << oc; + sLog.outError(out.str().c_str()); */ } - */ } } void PlayerbotMgr::HandleMasterOutgoingPacket(const WorldPacket& packet) { /* - switch (packet.GetOpcode()) - { - // maybe our bots should only start looting after the master loots? - //case SMSG_LOOT_RELEASE_RESPONSE: {} - case SMSG_NAME_QUERY_RESPONSE: - case SMSG_MONSTER_MOVE: - case SMSG_COMPRESSED_UPDATE_OBJECT: - case SMSG_DESTROY_OBJECT: - case SMSG_UPDATE_OBJECT: - case SMSG_STANDSTATE_UPDATE: - case MSG_MOVE_HEARTBEAT: - case SMSG_QUERY_TIME_RESPONSE: - case SMSG_AURA_UPDATE_ALL: - case SMSG_CREATURE_QUERY_RESPONSE: - case SMSG_GAMEOBJECT_QUERY_RESPONSE: - return; - default: - { - const char* oc = LookupOpcodeName(packet.GetOpcode()); + switch (packet.GetOpcode()) + { + // maybe our bots should only start looting after the master loots? + //case SMSG_LOOT_RELEASE_RESPONSE: {} + case SMSG_NAME_QUERY_RESPONSE: + case SMSG_MONSTER_MOVE: + case SMSG_COMPRESSED_UPDATE_OBJECT: + case SMSG_DESTROY_OBJECT: + case SMSG_UPDATE_OBJECT: + case SMSG_STANDSTATE_UPDATE: + case MSG_MOVE_HEARTBEAT: + case SMSG_QUERY_TIME_RESPONSE: + case SMSG_AURA_UPDATE_ALL: + case SMSG_CREATURE_QUERY_RESPONSE: + case SMSG_GAMEOBJECT_QUERY_RESPONSE: + return; + default: + { + const char* oc = LookupOpcodeName(packet.GetOpcode()); - std::ostringstream out; - out << "masterout: " << oc; - sLog.outError(out.str().c_str()); - } - } - */ + std::ostringstream out; + out << "masterout: " << oc; + sLog.outError(out.str().c_str()); + } + } + */ } void PlayerbotMgr::LogoutAllBots() @@ -847,8 +870,6 @@ void PlayerbotMgr::LogoutAllBots() RemoveAllBotsFromGroup(); } - - void PlayerbotMgr::Stay() { for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) @@ -858,7 +879,6 @@ void PlayerbotMgr::Stay() } } - // Playerbot mod: logs out a Playerbot. void PlayerbotMgr::LogoutPlayerBot(ObjectGuid guid) { @@ -892,7 +912,7 @@ void PlayerbotMgr::OnBotLogin(Player * const bot) // have bot leave their group if (bot->GetGroup() && (m_master->GetGroup() == nullptr || - m_master->GetGroup()->IsMember(bot->GetObjectGuid()) == false)) + m_master->GetGroup()->IsMember(bot->GetObjectGuid()) == false)) bot->RemoveFromGroup(); // sometimes master can lose leadership, pass leadership to master check @@ -900,16 +920,16 @@ void PlayerbotMgr::OnBotLogin(Player * const bot) if (m_master->GetGroup() && !m_master->GetGroup()->IsLeader(masterGuid)) { - // But only do so if one of the master's bots is leader - for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); itr++) - { - Player* bot = itr->second; - if ( m_master->GetGroup()->IsLeader(bot->GetObjectGuid()) ) - { - m_master->GetGroup()->ChangeLeader(masterGuid); - break; - } - } + // But only do so if one of the master's bots is leader + for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); itr++) + { + Player* bot = itr->second; + if ( m_master->GetGroup()->IsLeader(bot->GetObjectGuid()) ) + { + m_master->GetGroup()->ChangeLeader(masterGuid); + break; + } + } } } @@ -1038,6 +1058,49 @@ bool Player::requiredQuests(const char* pQuestIdString) return false; } +//See MainSpec enum in PlayerbotAI.h for details on class return values +uint32 Player::GetSpec() +{ + uint32 row = 0, spec = 0; + Player* player = m_session->GetPlayer(); + uint32 classMask = player->getClassMask(); + + for (unsigned int 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 ((classMask & talentTabInfo->ClassMask) == 0) + continue; + + uint32 curtalent_maxrank = 0; + for (int32 k = MAX_TALENT_RANK - 1; k > -1; --k) + { + if (talentInfo->RankID[k] && HasSpell(talentInfo->RankID[k])) + { + if (row == 0 && spec == 0) + spec = talentInfo->TalentTab; + + if (talentInfo->Row > row) + { + row = talentInfo->Row; + spec = talentInfo->TalentTab; + } + } + } + } + + //Return the tree with the deepest talent + return spec; +} + void Player::UpdateMail() { // save money,items and mail to prevent cheating @@ -1058,117 +1121,119 @@ bool ChatHandler::HandlePlayerbotCommand(char* args) return false; } - if (!m_session) - { - PSendSysMessage("|cffff0000You may only add bots from an active session"); - SetSentErrorMessage(true); - return false; - } + if (!m_session) + { + PSendSysMessage("|cffff0000You may only add bots from an active session"); + SetSentErrorMessage(true); + return false; + } - if (!*args) - { - PSendSysMessage("|cffff0000usage: add PLAYERNAME or remove PLAYERNAME"); - SetSentErrorMessage(true); - return false; - } + if (!*args) + { + PSendSysMessage("|cffff0000usage: add PLAYERNAME or remove PLAYERNAME"); + SetSentErrorMessage(true); + return false; + } - char *cmd = strtok ((char *) args, " "); - char *charname = strtok (nullptr, " "); - if (!cmd || !charname) - { - PSendSysMessage("|cffff0000usage: add PLAYERNAME or remove PLAYERNAME"); - SetSentErrorMessage(true); - return false; - } + char *cmd = strtok ((char *) args, " "); + char *charname = strtok (nullptr, " "); - std::string cmdStr = cmd; - std::string charnameStr = charname; + if (!cmd || !charname) + { + PSendSysMessage("|cffff0000usage: add PLAYERNAME or remove PLAYERNAME"); + SetSentErrorMessage(true); + return false; + } - if (!normalizePlayerName(charnameStr)) - return false; + std::string cmdStr = cmd; + std::string charnameStr = charname; - ObjectGuid guid = sObjectMgr.GetPlayerGuidByName(charnameStr.c_str()); - if (guid == ObjectGuid() || (guid == m_session->GetPlayer()->GetObjectGuid())) - { - SendSysMessage(LANG_PLAYER_NOT_FOUND); - SetSentErrorMessage(true); - return false; - } + if (!normalizePlayerName(charnameStr)) + return false; - uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid); - if (accountId != m_session->GetAccountId()) - { - PSendSysMessage("|cffff0000You may only add bots from the same account."); - SetSentErrorMessage(true); - return false; - } + ObjectGuid guid = sObjectMgr.GetPlayerGuidByName(charnameStr.c_str()); + if (guid == ObjectGuid() || (guid == m_session->GetPlayer()->GetObjectGuid())) + { + SendSysMessage(LANG_PLAYER_NOT_FOUND); + SetSentErrorMessage(true); + return false; + } - // create the playerbot manager if it doesn't already exist - PlayerbotMgr* mgr = m_session->GetPlayer()->GetPlayerbotMgr(); - if (!mgr) - { - mgr = new PlayerbotMgr(m_session->GetPlayer()); - m_session->GetPlayer()->SetPlayerbotMgr(mgr); - } + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (accountId != m_session->GetAccountId()) + { + PSendSysMessage("|cffff0000You may only add bots from the same account."); + SetSentErrorMessage(true); + return false; + } - QueryResult *resultchar = CharacterDatabase.PQuery("SELECT COUNT(*) FROM characters WHERE online = '1' AND account = '%u'", m_session->GetAccountId()); - if (resultchar) - { - Field *fields = resultchar->Fetch(); - int acctcharcount = fields[0].GetUInt32(); - int maxnum = botConfig.GetIntDefault("PlayerbotAI.MaxNumBots", 9); - if (!(m_session->GetSecurity() > SEC_PLAYER)) - if (acctcharcount > maxnum && (cmdStr == "add" || cmdStr == "login")) - { - PSendSysMessage("|cffff0000You cannot summon anymore bots.(Current Max: |cffffffff%u)", maxnum); - SetSentErrorMessage(true); - delete resultchar; - return false; - } - delete resultchar; - } + // create the playerbot manager if it doesn't already exist + PlayerbotMgr* mgr = m_session->GetPlayer()->GetPlayerbotMgr(); + if (!mgr) + { + mgr = new PlayerbotMgr(m_session->GetPlayer()); + m_session->GetPlayer()->SetPlayerbotMgr(mgr); + } - QueryResult *resultlvl = CharacterDatabase.PQuery("SELECT level,name FROM characters WHERE guid = '%u'", guid.GetCounter()); - if (resultlvl) - { - Field *fields = resultlvl->Fetch(); - int charlvl = fields[0].GetUInt32(); - int maxlvl = botConfig.GetIntDefault("PlayerbotAI.RestrictBotLevel", 80); - if (!(m_session->GetSecurity() > SEC_PLAYER)) - if (charlvl > maxlvl) - { - PSendSysMessage("|cffff0000You cannot summon |cffffffff[%s]|cffff0000, it's level is too high.(Current Max:lvl |cffffffff%u)", fields[1].GetString(), maxlvl); - SetSentErrorMessage(true); - delete resultlvl; - return false; - } - delete resultlvl; - } - // end of gmconfig patch - if (cmdStr == "add" || cmdStr == "login") - { - if (mgr->GetPlayerBot(guid)) + QueryResult *resultchar = CharacterDatabase.PQuery("SELECT COUNT(*) FROM characters WHERE online = '1' AND account = '%u'", m_session->GetAccountId()); + if (resultchar) + { + Field *fields = resultchar->Fetch(); + int acctcharcount = fields[0].GetUInt32(); + int maxnum = botConfig.GetIntDefault("PlayerbotAI.MaxNumBots", 9); + if (!(m_session->GetSecurity() > SEC_PLAYER)) + if (acctcharcount > maxnum && (cmdStr == "add" || cmdStr == "login")) { - PSendSysMessage("Bot already exists in world."); + PSendSysMessage("|cffff0000You cannot summon anymore bots.(Current Max: |cffffffff%u)", maxnum); SetSentErrorMessage(true); + delete resultchar; return false; } - CharacterDatabase.DirectPExecute("UPDATE characters SET online = 1 WHERE guid = '%u'", guid.GetCounter()); - mgr->LoginPlayerBot(guid); - PSendSysMessage("Bot added successfully."); - } - else if (cmdStr == "remove" || cmdStr == "logout") - { - if (!mgr->GetPlayerBot(guid)) + delete resultchar; + } + + QueryResult *resultlvl = CharacterDatabase.PQuery("SELECT level,name FROM characters WHERE guid = '%u'", guid.GetCounter()); + if (resultlvl) + { + Field *fields = resultlvl->Fetch(); + int charlvl = fields[0].GetUInt32(); + int maxlvl = botConfig.GetIntDefault("PlayerbotAI.RestrictBotLevel", 80); + if (!(m_session->GetSecurity() > SEC_PLAYER)) + if (charlvl > maxlvl) { - PSendSysMessage("|cffff0000Bot can not be removed because bot does not exist in world."); + PSendSysMessage("|cffff0000You cannot summon |cffffffff[%s]|cffff0000, it's level is too high.(Current Max:lvl |cffffffff%u)", fields[1].GetString(), maxlvl); SetSentErrorMessage(true); + delete resultlvl; return false; } - CharacterDatabase.DirectPExecute("UPDATE characters SET online = 0 WHERE guid = '%u'", guid.GetCounter()); - mgr->LogoutPlayerBot(guid); - PSendSysMessage("Bot removed successfully."); + delete resultlvl; + } + + // end of gmconfig patch + if (cmdStr == "add" || cmdStr == "login") + { + if (mgr->GetPlayerBot(guid)) + { + PSendSysMessage("Bot already exists in world."); + SetSentErrorMessage(true); + return false; + } + CharacterDatabase.DirectPExecute("UPDATE characters SET online = 1 WHERE guid = '%u'", guid.GetCounter()); + mgr->LoginPlayerBot(guid); + PSendSysMessage("Bot added successfully."); + } + else if (cmdStr == "remove" || cmdStr == "logout") + { + if (!mgr->GetPlayerBot(guid)) + { + PSendSysMessage("|cffff0000Bot can not be removed because bot does not exist in world."); + SetSentErrorMessage(true); + return false; } + CharacterDatabase.DirectPExecute("UPDATE characters SET online = 0 WHERE guid = '%u'", guid.GetCounter()); + mgr->LogoutPlayerBot(guid); + PSendSysMessage("Bot removed successfully."); + } - return true; + return true; } diff --git a/src/game/playerbot/PlayerbotPaladinAI.cpp b/src/game/playerbot/PlayerbotPaladinAI.cpp index cd4e5b23f..ac0127be9 100644 --- a/src/game/playerbot/PlayerbotPaladinAI.cpp +++ b/src/game/playerbot/PlayerbotPaladinAI.cpp @@ -13,54 +13,55 @@ class PlayerbotAI; PlayerbotPaladinAI::PlayerbotPaladinAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - RETRIBUTION_AURA = ai->initSpell(RETRIBUTION_AURA_1); - CRUSADER_AURA = ai->initSpell(CRUSADER_AURA_1); - CRUSADER_STRIKE = ai->initSpell(CRUSADER_STRIKE_1); - SEAL_OF_COMMAND = ai->initSpell(SEAL_OF_COMMAND_1); - JUDGEMENT_OF_LIGHT = ai->initSpell(JUDGEMENT_OF_LIGHT_1); - JUDGEMENT_OF_WISDOM = ai->initSpell(JUDGEMENT_OF_WISDOM_1); - JUDGEMENT_OF_JUSTICE = ai->initSpell(JUDGEMENT_OF_JUSTICE_1); - DIVINE_STORM = ai->initSpell(DIVINE_STORM_1); - BLESSING_OF_MIGHT = ai->initSpell(BLESSING_OF_MIGHT_1); - GREATER_BLESSING_OF_MIGHT = ai->initSpell(GREATER_BLESSING_OF_MIGHT_1); - HAMMER_OF_WRATH = ai->initSpell(HAMMER_OF_WRATH_1); - FLASH_OF_LIGHT = ai->initSpell(FLASH_OF_LIGHT_1); // Holy - HOLY_LIGHT = ai->initSpell(HOLY_LIGHT_1); - HOLY_SHOCK = ai->initSpell(HOLY_SHOCK_1); - HOLY_WRATH = ai->initSpell(HOLY_WRATH_1); - DIVINE_FAVOR = ai->initSpell(DIVINE_FAVOR_1); - CONCENTRATION_AURA = ai->initSpell(CONCENTRATION_AURA_1); - BLESSING_OF_WISDOM = ai->initSpell(BLESSING_OF_WISDOM_1); - GREATER_BLESSING_OF_WISDOM = ai->initSpell(GREATER_BLESSING_OF_WISDOM_1); - CONSECRATION = ai->initSpell(CONSECRATION_1); - AVENGING_WRATH = ai->initSpell(AVENGING_WRATH_1); - LAY_ON_HANDS = ai->initSpell(LAY_ON_HANDS_1); - EXORCISM = ai->initSpell(EXORCISM_1); - SACRED_SHIELD = ai->initSpell(SACRED_SHIELD_1); - DIVINE_PLEA = ai->initSpell(DIVINE_PLEA_1); - BLESSING_OF_KINGS = ai->initSpell(BLESSING_OF_KINGS_1); - GREATER_BLESSING_OF_KINGS = ai->initSpell(GREATER_BLESSING_OF_KINGS_1); - BLESSING_OF_SANCTUARY = ai->initSpell(BLESSING_OF_SANCTUARY_1); - GREATER_BLESSING_OF_SANCTUARY = ai->initSpell(GREATER_BLESSING_OF_SANCTUARY_1); - HAMMER_OF_JUSTICE = ai->initSpell(HAMMER_OF_JUSTICE_1); - RIGHTEOUS_FURY = ai->initSpell(RIGHTEOUS_FURY_1); - RIGHTEOUS_DEFENSE = ai->initSpell(RIGHTEOUS_DEFENSE_1); - SHADOW_RESISTANCE_AURA = ai->initSpell(SHADOW_RESISTANCE_AURA_1); - DEVOTION_AURA = ai->initSpell(DEVOTION_AURA_1); - FIRE_RESISTANCE_AURA = ai->initSpell(FIRE_RESISTANCE_AURA_1); - FROST_RESISTANCE_AURA = ai->initSpell(FROST_RESISTANCE_AURA_1); - HAND_OF_PROTECTION = ai->initSpell(HAND_OF_PROTECTION_1); - DIVINE_PROTECTION = ai->initSpell(DIVINE_PROTECTION_1); - DIVINE_INTERVENTION = ai->initSpell(DIVINE_INTERVENTION_1); - DIVINE_SACRIFICE = ai->initSpell(DIVINE_SACRIFICE_1); - DIVINE_SHIELD = ai->initSpell(DIVINE_SHIELD_1); - HOLY_SHIELD = ai->initSpell(HOLY_SHIELD_1); - AVENGERS_SHIELD = ai->initSpell(AVENGERS_SHIELD_1); - HAND_OF_SACRIFICE = ai->initSpell(HAND_OF_SACRIFICE_1); - SHIELD_OF_RIGHTEOUSNESS = ai->initSpell(SHIELD_OF_RIGHTEOUSNESS_1); - REDEMPTION = ai->initSpell(REDEMPTION_1); - PURIFY = ai->initSpell(PURIFY_1); - CLEANSE = ai->initSpell(CLEANSE_1); + RETRIBUTION_AURA = m_ai->initSpell(RETRIBUTION_AURA_1); + CRUSADER_AURA = m_ai->initSpell(CRUSADER_AURA_1); + CRUSADER_STRIKE = m_ai->initSpell(CRUSADER_STRIKE_1); + SEAL_OF_COMMAND = m_ai->initSpell(SEAL_OF_COMMAND_1); + JUDGEMENT = m_ai->initSpell(JUDGEMENT_1); + JUDGEMENT_OF_LIGHT = m_ai->initSpell(JUDGEMENT_OF_LIGHT_1); + JUDGEMENT_OF_WISDOM = m_ai->initSpell(JUDGEMENT_OF_WISDOM_1); + JUDGEMENT_OF_JUSTICE = m_ai->initSpell(JUDGEMENT_OF_JUSTICE_1); + DIVINE_STORM = m_ai->initSpell(DIVINE_STORM_1); + BLESSING_OF_MIGHT = m_ai->initSpell(BLESSING_OF_MIGHT_1); + GREATER_BLESSING_OF_MIGHT = m_ai->initSpell(GREATER_BLESSING_OF_MIGHT_1); + HAMMER_OF_WRATH = m_ai->initSpell(HAMMER_OF_WRATH_1); + FLASH_OF_LIGHT = m_ai->initSpell(FLASH_OF_LIGHT_1); // Holy + HOLY_LIGHT = m_ai->initSpell(HOLY_LIGHT_1); + HOLY_SHOCK = m_ai->initSpell(HOLY_SHOCK_1); + HOLY_WRATH = m_ai->initSpell(HOLY_WRATH_1); + DIVINE_FAVOR = m_ai->initSpell(DIVINE_FAVOR_1); + CONCENTRATION_AURA = m_ai->initSpell(CONCENTRATION_AURA_1); + BLESSING_OF_WISDOM = m_ai->initSpell(BLESSING_OF_WISDOM_1); + GREATER_BLESSING_OF_WISDOM = m_ai->initSpell(GREATER_BLESSING_OF_WISDOM_1); + CONSECRATION = m_ai->initSpell(CONSECRATION_1); + AVENGING_WRATH = m_ai->initSpell(AVENGING_WRATH_1); + LAY_ON_HANDS = m_ai->initSpell(LAY_ON_HANDS_1); + EXORCISM = m_ai->initSpell(EXORCISM_1); + SACRED_SHIELD = m_ai->initSpell(SACRED_SHIELD_1); + DIVINE_PLEA = m_ai->initSpell(DIVINE_PLEA_1); + BLESSING_OF_KINGS = m_ai->initSpell(BLESSING_OF_KINGS_1); + GREATER_BLESSING_OF_KINGS = m_ai->initSpell(GREATER_BLESSING_OF_KINGS_1); + BLESSING_OF_SANCTUARY = m_ai->initSpell(BLESSING_OF_SANCTUARY_1); + GREATER_BLESSING_OF_SANCTUARY = m_ai->initSpell(GREATER_BLESSING_OF_SANCTUARY_1); + HAMMER_OF_JUSTICE = m_ai->initSpell(HAMMER_OF_JUSTICE_1); + RIGHTEOUS_FURY = m_ai->initSpell(RIGHTEOUS_FURY_1); + RIGHTEOUS_DEFENSE = m_ai->initSpell(RIGHTEOUS_DEFENSE_1); + SHADOW_RESISTANCE_AURA = m_ai->initSpell(SHADOW_RESISTANCE_AURA_1); + DEVOTION_AURA = m_ai->initSpell(DEVOTION_AURA_1); + FIRE_RESISTANCE_AURA = m_ai->initSpell(FIRE_RESISTANCE_AURA_1); + FROST_RESISTANCE_AURA = m_ai->initSpell(FROST_RESISTANCE_AURA_1); + HAND_OF_PROTECTION = m_ai->initSpell(HAND_OF_PROTECTION_1); + DIVINE_PROTECTION = m_ai->initSpell(DIVINE_PROTECTION_1); + DIVINE_INTERVENTION = m_ai->initSpell(DIVINE_INTERVENTION_1); + DIVINE_SACRIFICE = m_ai->initSpell(DIVINE_SACRIFICE_1); + DIVINE_SHIELD = m_ai->initSpell(DIVINE_SHIELD_1); + HOLY_SHIELD = m_ai->initSpell(HOLY_SHIELD_1); + AVENGERS_SHIELD = m_ai->initSpell(AVENGERS_SHIELD_1); + HAND_OF_SACRIFICE = m_ai->initSpell(HAND_OF_SACRIFICE_1); + SHIELD_OF_RIGHTEOUSNESS = m_ai->initSpell(SHIELD_OF_RIGHTEOUSNESS_1); + REDEMPTION = m_ai->initSpell(REDEMPTION_1); + PURIFY = m_ai->initSpell(PURIFY_1); + CLEANSE = m_ai->initSpell(CLEANSE_1); // Warrior auras DEFENSIVE_STANCE = 71; //Def Stance @@ -72,480 +73,706 @@ PlayerbotPaladinAI::PlayerbotPaladinAI(Player* const master, Player* const bot, RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_PALADIN); // draenei - STONEFORM = ai->initSpell(STONEFORM_ALL); // dwarf - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_PALADIN); // draenei + STONEFORM = m_ai->initSpell(STONEFORM_ALL); // dwarf + + //The check doesn't work for now + //PRAYER_OF_SHADOW_PROTECTION = m_ai->initSpell(PriestSpells::PRAYER_OF_SHADOW_PROTECTION_1); } PlayerbotPaladinAI::~PlayerbotPaladinAI() {} -bool PlayerbotPaladinAI::HealTarget(Unit *target) +CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - uint8 hp = target->GetHealth() * 100 / target->GetMaxHealth(); - - if (hp < 25 && ai->CastSpell(LAY_ON_HANDS, *target)) - return true; - - if (hp < 30 && ai->CastSpell(FLASH_OF_LIGHT, *target)) - return true; - - if (hp < 35 && ai->CastSpell(HOLY_SHOCK, *target)) - return true; - - if (hp < 40 && ai->CastSpell(HOLY_LIGHT, *target)) - return true; - - if (PURIFY > 0 && ai->GetCombatOrder() != PlayerbotAI::ORDERS_NODISPEL) + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) { - uint32 DISPEL = CLEANSE > 0 ? CLEANSE : PURIFY; - uint32 dispelMask = GetDispellMask(DISPEL_DISEASE); - uint32 dispelMask2 = GetDispellMask(DISPEL_POISON); - uint32 dispelMask3 = GetDispellMask(DISPEL_MAGIC); - Unit::SpellAuraHolderMap const& auras = target->GetSpellAuraHolderMap(); - for(Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) { - SpellAuraHolder *holder = itr->second; - if ((1<GetSpellProto()->Dispel) & dispelMask) + if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder()) { - if(holder->GetSpellProto()->Dispel == DISPEL_DISEASE) - ai->CastSpell(DISPEL, *target); - return false; - } - else if ((1<GetSpellProto()->Dispel) & dispelMask2) - { - if(holder->GetSpellProto()->Dispel == DISPEL_POISON) - ai->CastSpell(DISPEL, *target); - return false; - } - else if ((1<GetSpellProto()->Dispel) & dispelMask3 & (DISPEL == CLEANSE)) - { - if(holder->GetSpellProto()->Dispel == DISPEL_MAGIC) - ai->CastSpell(DISPEL, *target); - return false; + if (m_bot->CanReachWithMeleeAttack(pTarget)) + { + // Set everyone's UpdateAI() waiting to 2 seconds + m_ai->SetGroupIgnoreUpdateTime(2); + // Clear their TEMP_WAIT_TANKAGGRO flag + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + // Start attacking, force target on current target + m_ai->Attack(m_ai->GetCurrentTarget()); + + // While everyone else is waiting 2 second, we need to build up aggro, so don't return + } + else + { + // TODO: add check if target is ranged + return RETURN_NO_ACTION_OK; // wait for target to get nearer + } } + else if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) + return HealPlayer(GetHealTarget()); + else + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); } } - return false; -} // end HealTarget + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } -void PlayerbotPaladinAI::DoNextCombatManeuver(Unit *pTarget) -{ - Unit* pVictim = pTarget->getVictim(); - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + // Check if bot needs to cast seal on self + m_CurrentSeal = 0; + m_CurrentJudgement = 0; - switch (ai->GetScenarioType()) + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - if (HAMMER_OF_JUSTICE > 0) - ai->CastSpell(HAMMER_OF_JUSTICE); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoFirstCombatManeuverPVE(pTarget); break; } - // damage spells - Player *m_bot = GetPlayerBot(); - Group *m_group = m_bot->GetGroup(); - bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); - std::ostringstream out; + return RETURN_NO_ACTION_ERROR; +} - //Shield master if low hp. - uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); +CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - if (GetMaster()->isAlive()) - if (masterHP < 25 && HAND_OF_PROTECTION > 0 && !GetMaster()->HasAura(FORBEARANCE, EFFECT_INDEX_0) && !GetMaster()->HasAura(HAND_OF_PROTECTION, EFFECT_INDEX_0) && !GetMaster()->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) && !GetMaster()->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0)) - ai->CastSpell(HAND_OF_PROTECTION, *GetMaster()); +CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - // heal group inside combat, but do not heal if tank - if (m_group && pVictim != m_bot) // possible tank - { - Group::MemberSlotList const& groupSlot = m_group->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) - { - Player *m_groupMember = sObjectMgr.GetPlayer(itr->guid); - if (!m_groupMember || !m_groupMember->isAlive()) - continue; +CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); - uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); - if (memberHP < 40 && ai->GetManaPercent() >= 40) // do not heal bots without plenty of mana for master & self - if (HealTarget(m_groupMember)) - return; - } + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoNextCombatManeuverPVE(pTarget); + break; } - if (RIGHTEOUS_FURY > 0 && !m_bot->HasAura(RIGHTEOUS_FURY, EFFECT_INDEX_0) && ai->GetCombatOrder() == PlayerbotAI::ORDERS_TANK) - ai->CastSpell (RIGHTEOUS_FURY, *m_bot); - - if (SHADOW_RESISTANCE_AURA > 0 && !m_bot->HasAura(SHADOW_RESISTANCE_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_WARLOCK) - ai->CastSpell (SHADOW_RESISTANCE_AURA, *m_bot); + return RETURN_NO_ACTION_ERROR; +} - if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_WARRIOR) - ai->CastSpell (DEVOTION_AURA, *m_bot); +CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + if (!pTarget) return RETURN_NO_ACTION_INVALIDTARGET; - if (FIRE_RESISTANCE_AURA > 0 && !m_bot->HasAura(FIRE_RESISTANCE_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_MAGE) - ai->CastSpell (FIRE_RESISTANCE_AURA, *m_bot); + // damage spells + uint32 spec = m_bot->GetSpec(); + std::ostringstream out; - if (RETRIBUTION_AURA > 0 && !m_bot->HasAura(RETRIBUTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_PRIEST) - ai->CastSpell (RETRIBUTION_AURA, *m_bot); + // Make sure healer stays put, don't even melee (aggro) if in range. + if (m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + else if (!m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); - if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_SHAMAN) - ai->CastSpell (DEVOTION_AURA, *m_bot); + // Emergency check: bot is about to die: use Divine Shield (first) + // Use Divine Protection if Divine Shield is not available and bot is not tanking because of the pacify effect + // TODO adjust treshold (may be too low) + if (m_ai->GetHealthPercent() < 8) + { + if (DIVINE_SHIELD > 0 && !m_bot->HasSpellCooldown(DIVINE_SHIELD) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DIVINE_SHIELD, *m_bot)) + return RETURN_CONTINUE; - if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_ROGUE) - ai->CastSpell (DEVOTION_AURA, *m_bot); + if (DIVINE_PROTECTION > 0 && !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) && !m_bot->HasSpellCooldown(DIVINE_PROTECTION) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DIVINE_PROTECTION, *m_bot)) + return RETURN_CONTINUE; + } - if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_PALADIN) - ai->CastSpell (DEVOTION_AURA, *m_bot); + // Check if bot needs to cast a seal on self or judge the target + if (CheckSealAndJudgement(pTarget)) + return RETURN_CONTINUE; - if (ai->GetHealthPercent() <= 40 || GetMaster()->GetHealth() <= GetMaster()->GetMaxHealth() * 0.4) - SpellSequence = Healing; + // Heal + if (m_ai->IsHealer()) + { + if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE)) + return RETURN_CONTINUE; + } else - SpellSequence = Combat; + { + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE)) + return RETURN_CONTINUE; + } - switch (SpellSequence) + //Used to determine if this bot has highest threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + if (newTarget && !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank { - case Combat: - if (JUDGEMENT_OF_LIGHT > 0 && !pTarget->HasAura(JUDGEMENT_OF_LIGHT, EFFECT_INDEX_0) && CombatCounter < 1 && ai->GetManaPercent() >= 5) - { - ai->CastSpell (JUDGEMENT_OF_LIGHT, *pTarget); - out << " Judgement of Light"; - CombatCounter++; - break; - } - else if (SEAL_OF_COMMAND > 0 && !m_bot->HasAura(SEAL_OF_COMMAND, EFFECT_INDEX_0) && CombatCounter < 2 && ai->GetManaPercent() >= 14) - { - ai->CastSpell (SEAL_OF_COMMAND, *m_bot); - out << " Seal of Command"; - CombatCounter++; - break; - } - else if (HAMMER_OF_JUSTICE > 0 && !pTarget->HasAura(HAMMER_OF_JUSTICE, EFFECT_INDEX_0) && CombatCounter < 3 && ai->GetManaPercent() >= 3) - { - ai->CastSpell (HAMMER_OF_JUSTICE, *pTarget); - out << " Hammer of Justice"; - CombatCounter++; - break; - } - else if (CRUSADER_STRIKE > 0 && CombatCounter < 4 && ai->GetManaPercent() >= 5) - { - ai->CastSpell (CRUSADER_STRIKE, *pTarget); - out << " Crusader Strike"; - CombatCounter++; - break; - } - else if (AVENGING_WRATH > 0 && CombatCounter < 5 && !m_bot->HasAura(AVENGING_WRATH, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - { - ai->CastSpell (AVENGING_WRATH, *m_bot); - out << " Avenging Wrath"; - CombatCounter++; - break; - } - else if (SACRED_SHIELD > 0 && CombatCounter < 6 && pVictim == m_bot && ai->GetHealthPercent() < 70 && !m_bot->HasAura(SACRED_SHIELD, EFFECT_INDEX_0) && ai->GetManaPercent() >= 12) - { - ai->CastSpell (SACRED_SHIELD, *m_bot); - out << " Sacred Shield"; - CombatCounter++; - break; - } - else if (DIVINE_STORM > 0 && CombatCounter < 7 && ai->GetAttackerCount() >= 3 && meleeReach && ai->GetManaPercent() >= 12) - { - ai->CastSpell (DIVINE_STORM, *pTarget); - out << " Divine Storm"; - CombatCounter++; - break; - } - else if (HAMMER_OF_WRATH > 0 && CombatCounter < 8 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && ai->GetManaPercent() >= 14) - { - ai->CastSpell (HAMMER_OF_WRATH, *pTarget); - out << " Hammer of Wrath"; - CombatCounter++; - break; - } - else if (HOLY_WRATH > 0 && CombatCounter < 9 && ai->GetAttackerCount() >= 3 && meleeReach && ai->GetManaPercent() >= 24) - { - ai->CastSpell (HOLY_WRATH, *pTarget); - out << " Holy Wrath"; - CombatCounter++; - break; - } - else if (HAND_OF_SACRIFICE > 0 && pVictim == GetMaster() && !GetMaster()->HasAura(HAND_OF_SACRIFICE, EFFECT_INDEX_0) && CombatCounter < 10 && ai->GetManaPercent() >= 6) - { - ai->CastSpell (HAND_OF_SACRIFICE, *GetMaster()); - out << " Hand of Sacrifice"; - CombatCounter++; - break; - } - else if (DIVINE_PROTECTION > 0 && pVictim == m_bot && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && ai->GetHealthPercent() < 30 && CombatCounter < 11 && ai->GetManaPercent() >= 3) - { - ai->CastSpell (DIVINE_PROTECTION, *m_bot); - out << " Divine Protection"; - CombatCounter++; - break; - } - else if (RIGHTEOUS_DEFENSE > 0 && pVictim != m_bot && ai->GetHealthPercent() > 70 && CombatCounter < 12) - { - ai->CastSpell (RIGHTEOUS_DEFENSE, *pTarget); - out << " Righteous Defense"; - CombatCounter++; - break; - } - else if (DIVINE_PLEA > 0 && !m_bot->HasAura(DIVINE_PLEA, EFFECT_INDEX_0) && ai->GetManaPercent() < 50 && CombatCounter < 13) - { - ai->CastSpell (DIVINE_PLEA, *m_bot); - out << " Divine Plea"; - CombatCounter++; - break; - } - else if (DIVINE_FAVOR > 0 && !m_bot->HasAura(DIVINE_FAVOR, EFFECT_INDEX_0) && CombatCounter < 14) - { - ai->CastSpell (DIVINE_FAVOR, *m_bot); - out << " Divine Favor"; - CombatCounter++; - break; - } - else if (CombatCounter > 15) - { - CombatCounter = 0; - //ai->TellMaster("CombatCounter Reset"); - break; - } - else - { - CombatCounter = 0; - //ai->TellMaster("Counter = 0"); - break; - } + if (HealPlayer(m_bot) == RETURN_CONTINUE) + return RETURN_CONTINUE; - case Healing: - if (ai->GetHealthPercent() <= 40) - { - HealTarget (m_bot); - out << " ...healing bot"; - break; - } - if (masterHP <= 40) - { - HealTarget (GetMaster()); - out << " ...healing master"; - break; - } - else + // Aggroed by an elite + if (m_ai->IsElite(newTarget)) + { + // Try to stun the mob + if (HAMMER_OF_JUSTICE > 0 && m_ai->In_Reach(newTarget, HAMMER_OF_JUSTICE) && !m_bot->HasSpellCooldown(HAMMER_OF_JUSTICE) && !newTarget->HasAura(HAMMER_OF_JUSTICE) && m_ai->CastSpell(HAMMER_OF_JUSTICE, *newTarget)) + return RETURN_CONTINUE; + + // Bot has low life: use divine powers to protect him/herself + if (m_ai->GetHealthPercent() < 15) { - CombatCounter = 0; - //ai->TellMaster("Counter = 0"); - break; + if (DIVINE_SHIELD > 0 && !m_bot->HasSpellCooldown(DIVINE_SHIELD) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DIVINE_SHIELD, *m_bot)) + return RETURN_CONTINUE; + + if (DIVINE_PROTECTION > 0 && !m_bot->HasSpellCooldown(DIVINE_PROTECTION) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DIVINE_PROTECTION, *m_bot)) + return RETURN_CONTINUE; } + + // Else: do nothing and pray for tank to pick aggro from mob + return RETURN_NO_ACTION_OK; + } } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster(out.str().c_str()); - if (AVENGING_WRATH > 0 && !m_bot->HasAura(AVENGING_WRATH, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) - ai->CastSpell(AVENGING_WRATH, *m_bot); + // Damage rotation + switch (spec) + { + case PALADIN_SPEC_HOLY: + if (m_ai->IsHealer()) + return RETURN_NO_ACTION_OK; + // else: DPS (retribution, NEVER protection) + + case PALADIN_SPEC_RETRIBUTION: + if (HAMMER_OF_WRATH > 0 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && m_ai->CastSpell (HAMMER_OF_WRATH, *pTarget)) + return RETURN_CONTINUE; + return RETURN_CONTINUE; + /*if (HAMMER_OF_JUSTICE > 0 && !pTarget->HasAura(HAMMER_OF_JUSTICE, EFFECT_INDEX_0) && m_ai->CastSpell (HAMMER_OF_JUSTICE, *pTarget)) + return RETURN_CONTINUE;*/ + /*if (HOLY_WRATH > 0 && m_ai->GetAttackerCount() >= 3 && meleeReach && m_ai->CastSpell (HOLY_WRATH, *pTarget)) + return RETURN_CONTINUE;*/ + /*if (BLESSING_OF_SACRIFICE > 0 && pVictim == GetMaster() && !GetMaster()->HasAura(BLESSING_OF_SACRIFICE, EFFECT_INDEX_0) && m_ai->CastSpell (BLESSING_OF_SACRIFICE, *GetMaster())) + return RETURN_CONTINUE;*/ + /*if (DIVINE_FAVOR > 0 && !m_bot->HasAura(DIVINE_FAVOR, EFFECT_INDEX_0) && m_ai->CastSpell (DIVINE_FAVOR, *m_bot)) + return RETURN_CONTINUE;*/ + return RETURN_NO_ACTION_OK; + + case PALADIN_SPEC_PROTECTION: + //Taunt if orders specify + if (CONSECRATION > 0 && !m_bot->HasSpellCooldown(CONSECRATION) && m_ai->CastSpell(CONSECRATION, *pTarget)) + return RETURN_CONTINUE; + if (HOLY_SHIELD > 0 && !m_bot->HasAura(HOLY_SHIELD) && m_ai->CastSpell(HOLY_SHIELD, *m_bot)) + return RETURN_CONTINUE; + if (SHIELD_OF_RIGHTEOUSNESS > 0 && !m_bot->HasSpellCooldown(SHIELD_OF_RIGHTEOUSNESS) && m_ai->CastSpell(SHIELD_OF_RIGHTEOUSNESS, *pTarget)) + return RETURN_CONTINUE; + return RETURN_NO_ACTION_OK; + } - if (DIVINE_SHIELD > 0 && ai->GetHealthPercent() < 30 && pVictim == m_bot && !m_bot->HasAura(FORBEARANCE, EFFECT_INDEX_0) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) && ai->GetManaPercent() >= 3) - ai->CastSpell(DIVINE_SHIELD, *m_bot); + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + if (m_ai->CastSpell(HAMMER_OF_JUSTICE)) + return RETURN_CONTINUE; - if (DIVINE_SACRIFICE > 0 && ai->GetHealthPercent() > 50 && pVictim != m_bot && !m_bot->HasAura(DIVINE_SACRIFICE, EFFECT_INDEX_0)) - ai->CastSpell(DIVINE_SACRIFICE, *m_bot); + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative } -void PlayerbotPaladinAI::DoNonCombatActions() +CombatManeuverReturns PlayerbotPaladinAI::HealPlayer(Player* target) { - PlayerbotAI* ai = GetAI(); - Player * m_bot = GetPlayerBot(); - if (!m_bot) - return; + CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target); + if (r != RETURN_NO_ACTION_OK) + return r; - // Buff myself - if (ai->GetCombatOrder() == ai->ORDERS_TANK) - ai->SelfBuff(RIGHTEOUS_FURY); - BuffPlayer(m_bot); + if (!target->isAlive()) + { + if (REDEMPTION && m_ai->CastSpell(REDEMPTION, *target)) + { + std::string msg = "Resurrecting "; + msg += target->GetName(); + m_bot->Say(msg, LANG_UNIVERSAL); + return RETURN_CONTINUE; + } + return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM + } - // Buff master - BuffPlayer(ai->GetMaster()); + uint32 dispel = CLEANSE > 0 ? CLEANSE : PURIFY; + // Remove negative magic on group members if orders allow bot to do so + if (Player* pCursedTarget = GetDispelTarget(DISPEL_MAGIC)) + { + if (dispel > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && m_ai->CastSpell(dispel, *pCursedTarget)) + return RETURN_CONTINUE; + } + // Remove poison on group members if orders allow bot to do so + if (Player* pPoisonedTarget = GetDispelTarget(DISPEL_POISON)) + { + m_ai->TellMaster("Has poison %s :",pPoisonedTarget->GetName()); + if (dispel > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && m_ai->CastSpell(dispel, *pPoisonedTarget)) + return RETURN_CONTINUE; + } - // mana check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + // Remove disease on group members if orders allow bot to do so + if (Player* pDiseasedTarget = GetDispelTarget(DISPEL_DISEASE)) + { + if (dispel > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && m_ai->CastSpell(dispel, *pDiseasedTarget)) + return RETURN_CONTINUE; + } - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); + // Define a tank bot will look at + Unit* pMainTank = GetHealTarget(JOB_TANK); - if (pItem != nullptr && ai->GetManaPercent() < 40) + // If target is out of range (40 yards) and is a tank: move towards it + // Other classes have to adjust their position to the healers + // TODO: This code should be common to all healers and will probably + // move to a more suitable place + if (pMainTank && !m_ai->In_Reach(pMainTank, FLASH_OF_LIGHT)) { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); - return; + m_bot->GetMotionMaster()->MoveFollow(target, 39.0f, m_bot->GetOrientation()); + return RETURN_CONTINUE; } - // hp check original - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + uint8 hp = target->GetHealthPercent(); - pItem = ai->FindFood(); + // Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check) + if (hp >= 90) + return RETURN_NO_ACTION_OK; - if (pItem != nullptr && ai->GetHealthPercent() < 40) + if (hp < 10 && LAY_ON_HANDS && !m_bot->HasSpellCooldown(LAY_ON_HANDS) && m_ai->In_Reach(target,LAY_ON_HANDS) && m_ai->CastSpell(LAY_ON_HANDS, *target)) + return RETURN_CONTINUE; + + // Target is a moderately wounded healer or a badly wounded not tank? Blessing of Protection! + if (BLESSING_OF_PROTECTION > 0 + && ((hp < 25 && (GetTargetJob(target) & JOB_HEAL)) || (hp < 15 && !(GetTargetJob(target) & JOB_TANK))) + && !m_bot->HasSpellCooldown(BLESSING_OF_PROTECTION) && m_ai->In_Reach(target,BLESSING_OF_PROTECTION) + && !target->HasAura(FORBEARANCE, EFFECT_INDEX_0) + && !target->HasAura(BLESSING_OF_PROTECTION, EFFECT_INDEX_0) && !target->HasAura(DIVINE_PROTECTION, EFFECT_INDEX_0) + && !target->HasAura(DIVINE_SHIELD, EFFECT_INDEX_0) + && m_ai->CastSpell(BLESSING_OF_PROTECTION, *target)) + return RETURN_CONTINUE; + + // Low HP : activate Divine Favor to make next heal a critical heal + if (hp < 25 && DIVINE_FAVOR > 0 && !m_bot->HasAura(DIVINE_FAVOR, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(DIVINE_FAVOR) && m_ai->CastSpell (DIVINE_FAVOR, *m_bot)) + return RETURN_CONTINUE; + + if (hp < 40 && FLASH_OF_LIGHT && m_ai->In_Reach(target,FLASH_OF_LIGHT) && m_ai->CastSpell(FLASH_OF_LIGHT, *target)) + return RETURN_CONTINUE; + + if (hp < 60 && HOLY_SHOCK && m_ai->In_Reach(target,HOLY_SHOCK) && m_ai->CastSpell(HOLY_SHOCK, *target)) + return RETURN_CONTINUE; + + if (hp < 90 && HOLY_LIGHT && m_ai->In_Reach(target,HOLY_LIGHT) && m_ai->CastSpell(HOLY_LIGHT, *target)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} // end HealTarget + +void PlayerbotPaladinAI::CheckAuras() +{ + if (!m_ai) return; + if (!m_bot) return; + + uint32 spec = m_bot->GetSpec(); + + // If we have resist orders, adjust accordingly + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FROST) + { + if (!m_bot->HasAura(FROST_RESISTANCE_AURA) && FROST_RESISTANCE_AURA > 0 && !m_bot->HasAura(FROST_RESISTANCE_AURA)) + m_ai->CastSpell(FROST_RESISTANCE_AURA); + return; + } + else if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FIRE) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); + if (!m_bot->HasAura(FIRE_RESISTANCE_AURA) && FIRE_RESISTANCE_AURA > 0 && !m_bot->HasAura(FIRE_RESISTANCE_AURA)) + m_ai->CastSpell(FIRE_RESISTANCE_AURA); return; } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + else if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_SHADOW) { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); + // Shadow protection check is broken, they stack! + if (!m_bot->HasAura(SHADOW_RESISTANCE_AURA) && SHADOW_RESISTANCE_AURA > 0 && !m_bot->HasAura(SHADOW_RESISTANCE_AURA)) // /*&& !m_bot->HasAura(PRAYER_OF_SHADOW_PROTECTION)*/ /*&& !m_bot->HasAura(PRAYER_OF_SHADOW_PROTECTION)*/ + m_ai->CastSpell(SHADOW_RESISTANCE_AURA); return; } - // heal and buff group - if (GetMaster()->GetGroup()) + // if there is a tank in the group, use concentration aura + bool tankInGroup = false; + if (m_bot->GetGroup()) { - Group::MemberSlotList const& groupSlot = GetMaster()->GetGroup()->GetMemberSlots(); + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) { - Player *tPlayer = sObjectMgr.GetPlayer(itr->guid); - if (!tPlayer) + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember) continue; - if (!tPlayer->isAlive()) + if (GetTargetJob(groupMember) & JOB_TANK) { - if (ai->CastSpell(REDEMPTION, *tPlayer)) - { - std::string msg = "Resurrecting "; - msg += tPlayer->GetName(); - m_bot->Say(msg, LANG_UNIVERSAL); - return; - } - else - continue; + tankInGroup = true; + break; } + } + } + + // If we have no resist orders, adjust aura based on spec or tank + if (spec == PALADIN_SPEC_PROTECTION || tankInGroup) + { + if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA)) + m_ai->CastSpell(DEVOTION_AURA); + return; + } + else if (spec == PALADIN_SPEC_HOLY) + { + if (CONCENTRATION_AURA > 0 && !m_bot->HasAura(CONCENTRATION_AURA)) + m_ai->CastSpell(CONCENTRATION_AURA); + return; + } + else if (spec == PALADIN_SPEC_RETRIBUTION) + { + if (RETRIBUTION_AURA > 0 && !m_bot->HasAura(RETRIBUTION_AURA)) + m_ai->CastSpell(RETRIBUTION_AURA); + return; + } +} + +// Check if the paladin bot needs to cast/refresh a seal on him/herself +// also check if the paladin bot needs to judge its target and first buff +// him/herself with the relevant seal +// TODO: handle other paladins in group/raid, for example to cast Seal/Judgement of Light +bool PlayerbotPaladinAI::CheckSealAndJudgement(Unit* pTarget) +{ + if (!m_ai) return false; + if (!m_bot) return false; + if (!pTarget) return false; + + Creature * pCreature = (Creature*) pTarget; + + // Prevent low health humanoid from fleeing by judging them with Seal of Justice + if (pCreature && pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_HUMANOID && pTarget->GetHealthPercent() < 20 && !pCreature->IsWorldBoss()) + { + if (SEAL_OF_JUSTICE > 0 && !m_bot->HasAura(SEAL_OF_JUSTICE, EFFECT_INDEX_0) && m_ai->CastSpell(SEAL_OF_JUSTICE, *m_bot)) + { + m_CurrentSeal = SEAL_OF_JUSTICE; + m_CurrentJudgement = 0; + return true; + } + } + + // Bot already defined a seal and a judgement and each is active on bot and target: don't waste time to go further + if (m_CurrentSeal > 0 && m_bot->HasAura(m_CurrentSeal, EFFECT_INDEX_0) && m_CurrentJudgement > 0 && pTarget->HasAura(m_CurrentJudgement, EFFECT_INDEX_0)) + return false; + + // Refresh judgement if needed by forcing paladin bot to cast seal and judgement anew + // But first, unleash current seal if bot can do extra damage to the target in the process + if (m_CurrentJudgement > 0 && !pTarget->HasAura(m_CurrentJudgement, EFFECT_INDEX_0)) + { + if (m_bot->HasAura(SEAL_OF_COMMAND, EFFECT_INDEX_0) || m_bot->HasAura(SEAL_OF_RIGHTEOUSNESS, EFFECT_INDEX_0)) + if (JUDGEMENT > 0 && !m_bot->HasSpellCooldown(JUDGEMENT) && m_ai->In_Reach(pTarget, JUDGEMENT)) + m_ai->CastSpell(JUDGEMENT, *pTarget); + + m_CurrentJudgement = 0; + m_CurrentSeal = 0; + return false; + } + + // Judgement is still active on target: refresh seal on bot if needed + if (m_CurrentJudgement > 0 && m_CurrentSeal > 0 && !m_bot->HasAura(m_CurrentSeal, EFFECT_INDEX_0)) + if (m_CurrentSeal > 0 && !m_bot->HasAura(m_CurrentSeal, EFFECT_INDEX_0) && m_ai->CastSpell(m_CurrentSeal, *m_bot)) + return true; - if (HealTarget(tPlayer)) - return; + // No judgement on target but bot has seal active: time to judge the target + if (m_CurrentJudgement == 0 && m_CurrentSeal > 0 && m_bot->HasAura(m_CurrentSeal, EFFECT_INDEX_0)) + { + if (JUDGEMENT > 0 && !m_bot->HasSpellCooldown(JUDGEMENT) && m_ai->In_Reach(pTarget, JUDGEMENT) && m_ai->CastSpell(JUDGEMENT, *pTarget)) + { + if (m_CurrentSeal == SEAL_OF_JUSTICE) + m_CurrentJudgement = JUDGEMENT_OF_JUSTICE; + else if (m_CurrentSeal == SEAL_OF_WISDOM) + m_CurrentJudgement = JUDGEMENT_OF_WISDOM; + else if (m_CurrentSeal == SEAL_OF_THE_CRUSADER) + m_CurrentJudgement = JUDGEMENT_OF_THE_CRUSADER; + else + return false; - if (tPlayer != m_bot && tPlayer != GetMaster()) - if (BuffPlayer(tPlayer)) - return; + // Set current seal to 0 to force the bot to seal him/herself for combat now that the target is judged + m_CurrentSeal = 0; + return true; } + + return false; } + + // Now bot casts seal on him/herself + // No judgement on target: look for best seal to judge target next + // Target already judged: bot will buff him/herself for combat according to spec/orders + uint32 spec = m_bot->GetSpec(); + + // Bypass spec if combat orders were given + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_HEAL) spec = PALADIN_SPEC_HOLY; + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) spec = PALADIN_SPEC_PROTECTION; + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST) spec = PALADIN_SPEC_RETRIBUTION; + + if (m_CurrentJudgement == 0) + { + if (spec == PALADIN_SPEC_HOLY || m_ai->IsHealer()) + m_CurrentSeal = SEAL_OF_WISDOM; + else + m_CurrentSeal = SEAL_OF_THE_CRUSADER; + } + else + { + if (spec == PALADIN_SPEC_HOLY) + m_CurrentSeal = SEAL_OF_WISDOM; + else if (spec == PALADIN_SPEC_PROTECTION) + m_CurrentSeal = SEAL_OF_RIGHTEOUSNESS; + else if (spec == PALADIN_SPEC_RETRIBUTION && SEAL_OF_COMMAND > 0) + m_CurrentSeal = SEAL_OF_COMMAND; + // no spec: try Seal of Righteouness + else + m_CurrentSeal = SEAL_OF_RIGHTEOUSNESS; + } + + if (m_CurrentSeal > 0 && !m_bot->HasAura(m_CurrentSeal, EFFECT_INDEX_0) && m_ai->CastSpell(m_CurrentSeal, *m_bot)) + return true; + + return false; } -bool PlayerbotPaladinAI::BuffPlayer(Player* target) +void PlayerbotPaladinAI::DoNonCombatActions() { - PlayerbotAI * ai = GetAI(); - uint8 SPELL_BLESSING = 2; // See SpellSpecific enum in SpellMgr.h + if (!m_ai) return; + if (!m_bot) return; - Pet * pet = target->GetPet(); - bool petCanBeBlessed = false; - if (pet) - petCanBeBlessed = ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet); + if (!m_bot->isAlive() || m_bot->IsInDuel()) return; - if (!ai->CanReceiveSpecificSpell(SPELL_BLESSING, target) && !petCanBeBlessed) - return false; + CheckAuras(); + + //Put up RF if tank + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) + m_ai->SelfBuff(RIGHTEOUS_FURY); + //Disable RF if not tank + else if (m_bot->HasAura(RIGHTEOUS_FURY)) + m_bot->RemoveAurasDueToSpell(RIGHTEOUS_FURY); + + // Revive + if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE) + return; + + // Heal + if (m_ai->IsHealer()) + { + if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; + } + else + { + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; + } + + // buff group + if (Buff(&PlayerbotPaladinAI::BuffHelper, 1) & RETURN_CONTINUE) // Paladin's BuffHelper takes care of choosing the specific Blessing so just pass along a non-zero value + return; + // hp/mana check + if (EatDrinkBandage()) + return; + + // Search and apply stones to weapons + // Mainhand ... + Item * stone, * weapon; + weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) + { + stone = m_ai->FindStoneFor(weapon); + if (stone) + { + m_ai->UseItem(stone, EQUIPMENT_SLOT_MAINHAND); + m_ai->SetIgnoreUpdateTime(5); + } + } +} + +/** + * BuffHelper + * BuffHelper is a static function, takes an AI, spellId (ignored for paladin) and a target and attempts to buff them as well as their pets as + * best as possible. + * + * Return bool - returns true if a buff took place. + */ +bool PlayerbotPaladinAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target) +{ + if (!ai) return false; + if (spellId == 0) return false; + if (!target) return false; + + PlayerbotPaladinAI* c = (PlayerbotPaladinAI*) ai->GetClassAI(); + uint32 bigSpellId = 0; + + Pet* pet = target->GetPet(); + uint32 petSpellId = 0, petBigSpellId = 0; + + // See which buff is appropriate according to class + // TODO: take into account other paladins in the group switch (target->getClass()) { case CLASS_DRUID: case CLASS_SHAMAN: case CLASS_PALADIN: - if (Bless(BLESSING_OF_MIGHT, target)) - return true; - if (Bless(BLESSING_OF_KINGS, target)) - return true; - if (Bless(BLESSING_OF_WISDOM, target)) - return true; - if (Bless(BLESSING_OF_SANCTUARY, target)) - return true; - else - return false; + spellId = c->BLESSING_OF_MIGHT; + if (!spellId) + { + spellId = c->BLESSING_OF_KINGS; + if (!spellId) + { + spellId = c->BLESSING_OF_WISDOM; + if (!spellId) + { + spellId = c->BLESSING_OF_SANCTUARY; + if (!spellId) + return false; + } + } + } + break; case CLASS_HUNTER: - if (petCanBeBlessed) - if (Bless(BLESSING_OF_MIGHT, pet)) - return true; - if (Bless(BLESSING_OF_KINGS, pet)) - return true; - if (Bless(BLESSING_OF_SANCTUARY, pet)) - return true; + if (pet && ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet) && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE)) + { + petSpellId = c->BLESSING_OF_MIGHT; + if (!petSpellId) + { + petSpellId = c->BLESSING_OF_KINGS; + if (!petSpellId) + petSpellId = c->BLESSING_OF_SANCTUARY; + } + } case CLASS_ROGUE: case CLASS_WARRIOR: - if (Bless(BLESSING_OF_MIGHT, target)) - return true; - if (Bless(BLESSING_OF_KINGS, target)) - return true; - if (Bless(BLESSING_OF_SANCTUARY, target)) - return true; - else - return false; + spellId = c->BLESSING_OF_MIGHT; + if (!spellId) + { + spellId = c->BLESSING_OF_KINGS; + if (!spellId) + { + spellId = c->BLESSING_OF_SANCTUARY; + if (!spellId) + return false; + } + } + break; case CLASS_WARLOCK: - if (petCanBeBlessed) + if (pet && ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet) && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE)) { if (pet->GetPowerType() == POWER_MANA) + petSpellId = c->BLESSING_OF_WISDOM; + else + petSpellId = c->BLESSING_OF_MIGHT; + + if (!petSpellId) { - if (Bless(BLESSING_OF_WISDOM, pet)) - return true; + petSpellId = c->BLESSING_OF_KINGS; + if (!petSpellId) + petSpellId = c->BLESSING_OF_SANCTUARY; } - else if (Bless(BLESSING_OF_MIGHT, pet)) - return true; - if (Bless(BLESSING_OF_KINGS, pet)) - return true; - if (Bless(BLESSING_OF_SANCTUARY, pet)) - return true; } case CLASS_PRIEST: case CLASS_MAGE: - if (Bless(BLESSING_OF_WISDOM, target)) - return true; - if (Bless(BLESSING_OF_KINGS, target)) - return true; - if (Bless(BLESSING_OF_SANCTUARY, target)) - return true; - else - return false; + spellId = c->BLESSING_OF_WISDOM; + if (!spellId) + { + spellId = c->BLESSING_OF_KINGS; + if (!spellId) + { + spellId = c->BLESSING_OF_SANCTUARY; + if (!spellId) + return false; + } + } + break; } + + if (petSpellId == c->BLESSING_OF_MIGHT) + petBigSpellId = c->GREATER_BLESSING_OF_MIGHT; + else if (petSpellId == c->BLESSING_OF_WISDOM) + petBigSpellId = c->GREATER_BLESSING_OF_WISDOM; + else if (petSpellId == c->BLESSING_OF_KINGS) + petBigSpellId = c->GREATER_BLESSING_OF_KINGS; + else if (petSpellId == c->BLESSING_OF_SANCTUARY) + petBigSpellId = c->GREATER_BLESSING_OF_SANCTUARY; + + if (spellId == c->BLESSING_OF_MIGHT) + bigSpellId = c->GREATER_BLESSING_OF_MIGHT; + else if (spellId == c->BLESSING_OF_WISDOM) + bigSpellId = c->GREATER_BLESSING_OF_WISDOM; + else if (spellId == c->BLESSING_OF_KINGS) + bigSpellId = c->GREATER_BLESSING_OF_KINGS; + else if (spellId == c->BLESSING_OF_SANCTUARY) + bigSpellId = c->GREATER_BLESSING_OF_SANCTUARY; + + if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->HasSpellReagents(petBigSpellId) && ai->Buff(petBigSpellId, pet)) + return true; + if (ai->HasSpellReagents(bigSpellId) && ai->Buff(bigSpellId, target)) + return true; + if ((pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(petSpellId, pet)) || ai->Buff(spellId, target)) + return true; return false; } -bool PlayerbotPaladinAI::Bless(uint32 spellId, Unit *target) +// Match up with "Pull()" below +bool PlayerbotPaladinAI::CanPull() { - if (spellId == 0) - return false; + if (HAND_OF_RECKONING && !m_bot->HasSpellCooldown(HAND_OF_RECKONING)) + return true; + if (EXORCISM && !m_bot->HasSpellCooldown(EXORCISM)) + return true; - PlayerbotAI * ai = GetAI(); + return false; +} - if (spellId == BLESSING_OF_MIGHT) - { - if (GREATER_BLESSING_OF_MIGHT && ai->HasSpellReagents(GREATER_BLESSING_OF_MIGHT) && ai->Buff(GREATER_BLESSING_OF_MIGHT, target)) - return true; - else - return ai->Buff(spellId, target); - } - else if (spellId == BLESSING_OF_WISDOM) - { - if (GREATER_BLESSING_OF_WISDOM && ai->HasSpellReagents(GREATER_BLESSING_OF_WISDOM) && ai->Buff(GREATER_BLESSING_OF_WISDOM, target)) - return true; - else - return ai->Buff(spellId, target); - } - else if (spellId == BLESSING_OF_KINGS) - { - if (GREATER_BLESSING_OF_KINGS && ai->HasSpellReagents(GREATER_BLESSING_OF_KINGS) && ai->Buff(GREATER_BLESSING_OF_KINGS, target)) - return true; - else - return ai->Buff(spellId, target); - } - else if (spellId == BLESSING_OF_SANCTUARY) - { - if (GREATER_BLESSING_OF_SANCTUARY && ai->HasSpellReagents(GREATER_BLESSING_OF_SANCTUARY) && ai->Buff(GREATER_BLESSING_OF_SANCTUARY, target)) - return true; - else - return ai->Buff(spellId, target); - } +// Match up with "CanPull()" above +bool PlayerbotPaladinAI::Pull() +{ + if (EXORCISM && m_ai->CastSpell(EXORCISM)) + return true; + + return false; +} + +bool PlayerbotPaladinAI::CastHoTOnTank() +{ + if (!m_ai) return false; + + if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false; + + // Paladin: Sheath of Light (with talents), Flash of Light (with Infusion of Light talent and only on a target with the Sacred Shield buff), + // Holy Shock (with Tier 8 set bonus) + // None of these are HoTs to cast before pulling (I think) - // Should not happen, but let it be here return false; } diff --git a/src/game/playerbot/PlayerbotPaladinAI.h b/src/game/playerbot/PlayerbotPaladinAI.h index fa6d6f27d..167a698b7 100644 --- a/src/game/playerbot/PlayerbotPaladinAI.h +++ b/src/game/playerbot/PlayerbotPaladinAI.h @@ -53,6 +53,7 @@ enum PaladinSpells HOLY_SHIELD_1 = 20925, HOLY_SHOCK_1 = 20473, HOLY_WRATH_1 = 2812, + JUDGEMENT_1 = 20271, JUDGEMENT_OF_JUSTICE_1 = 53407, JUDGEMENT_OF_LIGHT_1 = 20271, JUDGEMENT_OF_WISDOM_1 = 53408, @@ -74,7 +75,12 @@ enum PaladinSpells SENSE_UNDEAD_1 = 5502, SHADOW_RESISTANCE_AURA_1 = 19876, SHIELD_OF_RIGHTEOUSNESS_1 = 53600, - TURN_EVIL_1 = 10326 + TURN_EVIL_1 = 10326, + + // Judgement auras on target + JUDGEMENT_OF_WISDOM = 20355, // rank 2: 20354, rank 1: 20186 + JUDGEMENT_OF_JUSTICE = 20184, + JUDGEMENT_OF_THE_CRUSADER = 20303 // rank 5: 20302, rank 4: 20301, rank 3: 20300, rank 2: 20188, rank 1: 21183 }; //class Player; @@ -85,24 +91,42 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI virtual ~PlayerbotPaladinAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + bool Pull(); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); - // buff a specific player, usually a real PC who is not in group - bool BuffPlayer(Player *target); + // Utility Functions + bool CanPull(); + bool CastHoTOnTank(); private: +CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + // Heals the target based off its hps - bool HealTarget (Unit *target); - // Bless target using greater blessing if possible - bool Bless(uint32 spellId, Unit *target); + CombatManeuverReturns HealPlayer(Player* target); + + //Changes aura according to spec/orders + void CheckAuras(); + //Changes Seal according to spec + bool CheckSealAndJudgement(Unit* target); + uint32 m_CurrentSeal; + uint32 m_CurrentJudgement; + + static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target); + // make this public so the static function can access it. Either that or make an accessor function for each +public: // Retribution uint32 RETRIBUTION_AURA, SEAL_OF_COMMAND, JUDGEMENT_OF_LIGHT, + JUDGEMENT_OF_THE_CRUSADER, JUDGEMENT_OF_WISDOM, GREATER_BLESSING_OF_WISDOM, GREATER_BLESSING_OF_MIGHT, @@ -110,6 +134,7 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI BLESSING_OF_MIGHT, HAMMER_OF_JUSTICE, RIGHTEOUS_FURY, + JUDGEMENT, CRUSADER_AURA, CRUSADER_STRIKE, AVENGING_WRATH, @@ -137,12 +162,14 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI SEAL_OF_RIGHTEOUSNESS, SEAL_OF_VENGEANCE, SEAL_OF_WISDOM, + SEAL_OF_THE_CRUSADER, PURIFY, CLEANSE; // Protection uint32 GREATER_BLESSING_OF_KINGS, BLESSING_OF_KINGS, + BLESSING_OF_PROTECTION, HAND_OF_PROTECTION, SHADOW_RESISTANCE_AURA, DEVOTION_AURA, @@ -160,7 +187,9 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI BLESSING_OF_SANCTUARY, GREATER_BLESSING_OF_SANCTUARY, HAND_OF_SACRIFICE, - SHIELD_OF_RIGHTEOUSNESS; + SHIELD_OF_RIGHTEOUSNESS, + HAND_OF_RECKONING, + HAMMER_OF_THE_RIGHTEOUS; // cannot be protected uint32 FORBEARANCE; @@ -173,13 +202,16 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, - EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN; + //Non-Stacking buffs + uint32 PRAYER_OF_SHADOW_PROTECTION; + +private: uint32 SpellSequence, CombatCounter, HealCounter; }; diff --git a/src/game/playerbot/PlayerbotPriestAI.cpp b/src/game/playerbot/PlayerbotPriestAI.cpp index 372b0cca4..e205428c4 100644 --- a/src/game/playerbot/PlayerbotPriestAI.cpp +++ b/src/game/playerbot/PlayerbotPriestAI.cpp @@ -1,4 +1,3 @@ - #include "PlayerbotPriestAI.h" #include "../SpellAuras.h" @@ -6,507 +5,559 @@ class PlayerbotAI; PlayerbotPriestAI::PlayerbotPriestAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - RENEW = ai->initSpell(RENEW_1); - HEAL = ai->initSpell(HEAL_1); - LESSER_HEAL = ai->initSpell(LESSER_HEAL_1); - GREATER_HEAL = ai->initSpell(GREATER_HEAL_1); - FLASH_HEAL = ai->initSpell(FLASH_HEAL_1); - RESURRECTION = ai->initSpell(RESURRECTION_1); - SMITE = ai->initSpell(SMITE_1); - MANA_BURN = ai->initSpell(MANA_BURN_1); - HOLY_NOVA = ai->initSpell(HOLY_NOVA_1); - HOLY_FIRE = ai->initSpell(HOLY_FIRE_1); - DESPERATE_PRAYER = ai->initSpell(DESPERATE_PRAYER_1); - PRAYER_OF_HEALING = ai->initSpell(PRAYER_OF_HEALING_1); - CIRCLE_OF_HEALING = ai->initSpell(CIRCLE_OF_HEALING_1); - BINDING_HEAL = ai->initSpell(BINDING_HEAL_1); - PRAYER_OF_MENDING = ai->initSpell(PRAYER_OF_MENDING_1); - CURE_DISEASE = ai->initSpell(CURE_DISEASE_1); + RENEW = m_ai->initSpell(RENEW_1); + LESSER_HEAL = m_ai->initSpell(LESSER_HEAL_1); + FLASH_HEAL = m_ai->initSpell(FLASH_HEAL_1); + (FLASH_HEAL > 0) ? FLASH_HEAL : FLASH_HEAL = LESSER_HEAL; + HEAL = m_ai->initSpell(HEAL_1); + (HEAL > 0) ? HEAL : HEAL = FLASH_HEAL; + GREATER_HEAL = m_ai->initSpell(GREATER_HEAL_1); + (GREATER_HEAL > 0) ? GREATER_HEAL : GREATER_HEAL = HEAL; + RESURRECTION = m_ai->initSpell(RESURRECTION_1); + SMITE = m_ai->initSpell(SMITE_1); + MANA_BURN = m_ai->initSpell(MANA_BURN_1); + HOLY_NOVA = m_ai->initSpell(HOLY_NOVA_1); + HOLY_FIRE = m_ai->initSpell(HOLY_FIRE_1); + DESPERATE_PRAYER = m_ai->initSpell(DESPERATE_PRAYER_1); + PRAYER_OF_HEALING = m_ai->initSpell(PRAYER_OF_HEALING_1); + CIRCLE_OF_HEALING = m_ai->initSpell(CIRCLE_OF_HEALING_1); + BINDING_HEAL = m_ai->initSpell(BINDING_HEAL_1); + PRAYER_OF_MENDING = m_ai->initSpell(PRAYER_OF_MENDING_1); + CURE_DISEASE = m_ai->initSpell(CURE_DISEASE_1); // SHADOW - FADE = ai->initSpell(FADE_1); - SHADOW_WORD_PAIN = ai->initSpell(SHADOW_WORD_PAIN_1); - MIND_BLAST = ai->initSpell(MIND_BLAST_1); - SCREAM = ai->initSpell(PSYCHIC_SCREAM_1); - MIND_FLAY = ai->initSpell(MIND_FLAY_1); - DEVOURING_PLAGUE = ai->initSpell(DEVOURING_PLAGUE_1); - SHADOW_PROTECTION = ai->initSpell(SHADOW_PROTECTION_1); - VAMPIRIC_TOUCH = ai->initSpell(VAMPIRIC_TOUCH_1); - PRAYER_OF_SHADOW_PROTECTION = ai->initSpell(PRAYER_OF_SHADOW_PROTECTION_1); - SHADOWFIEND = ai->initSpell(SHADOWFIEND_1); - MIND_SEAR = ai->initSpell(MIND_SEAR_1); + FADE = m_ai->initSpell(FADE_1); + SHADOW_WORD_PAIN = m_ai->initSpell(SHADOW_WORD_PAIN_1); + MIND_BLAST = m_ai->initSpell(MIND_BLAST_1); + SCREAM = m_ai->initSpell(PSYCHIC_SCREAM_1); + MIND_FLAY = m_ai->initSpell(MIND_FLAY_1); + DEVOURING_PLAGUE = m_ai->initSpell(DEVOURING_PLAGUE_1); + SHADOW_PROTECTION = m_ai->initSpell(SHADOW_PROTECTION_1); + VAMPIRIC_TOUCH = m_ai->initSpell(VAMPIRIC_TOUCH_1); + PRAYER_OF_SHADOW_PROTECTION = m_ai->initSpell(PRAYER_OF_SHADOW_PROTECTION_1); + SHADOWFIEND = m_ai->initSpell(SHADOWFIEND_1); + MIND_SEAR = m_ai->initSpell(MIND_SEAR_1); + SHADOWFORM = m_ai->initSpell(SHADOWFORM_1); + VAMPIRIC_EMBRACE = m_ai->initSpell(VAMPIRIC_EMBRACE_1); + // RANGED COMBAT - SHOOT = ai->initSpell(SHOOT_1); + SHOOT = m_ai->initSpell(SHOOT_1); // DISCIPLINE - PENANCE = ai->initSpell(PENANCE_1); - INNER_FIRE = ai->initSpell(INNER_FIRE_1); - POWER_WORD_SHIELD = ai->initSpell(POWER_WORD_SHIELD_1); - POWER_WORD_FORTITUDE = ai->initSpell(POWER_WORD_FORTITUDE_1); - PRAYER_OF_FORTITUDE = ai->initSpell(PRAYER_OF_FORTITUDE_1); - FEAR_WARD = ai->initSpell(FEAR_WARD_1); - DIVINE_SPIRIT = ai->initSpell(DIVINE_SPIRIT_1); - PRAYER_OF_SPIRIT = ai->initSpell(PRAYER_OF_SPIRIT_1); - MASS_DISPEL = ai->initSpell(MASS_DISPEL_1); - POWER_INFUSION = ai->initSpell(POWER_INFUSION_1); - INNER_FOCUS = ai->initSpell(INNER_FOCUS_1); - - RECENTLY_BANDAGED = 11196; // first aid check + PENANCE = m_ai->initSpell(PENANCE_1); + INNER_FIRE = m_ai->initSpell(INNER_FIRE_1); + POWER_WORD_SHIELD = m_ai->initSpell(POWER_WORD_SHIELD_1); + POWER_WORD_FORTITUDE = m_ai->initSpell(POWER_WORD_FORTITUDE_1); + PRAYER_OF_FORTITUDE = m_ai->initSpell(PRAYER_OF_FORTITUDE_1); + FEAR_WARD = m_ai->initSpell(FEAR_WARD_1); + DIVINE_SPIRIT = m_ai->initSpell(DIVINE_SPIRIT_1); + PRAYER_OF_SPIRIT = m_ai->initSpell(PRAYER_OF_SPIRIT_1); + MASS_DISPEL = m_ai->initSpell(MASS_DISPEL_1); + POWER_INFUSION = m_ai->initSpell(POWER_INFUSION_1); + INNER_FOCUS = m_ai->initSpell(INNER_FOCUS_1); + PRIEST_DISPEL_MAGIC = m_ai->initSpell(DISPEL_MAGIC_1); + + RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_PRIEST); // draenei - STONEFORM = ai->initSpell(STONEFORM_ALL); // dwarf - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human - SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll - WILL_OF_THE_FORSAKEN = ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); + ELUNES_GRACE = m_ai->initSpell(ELUNES_GRACE_1); // night elf + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_PRIEST); // draenei + STONEFORM = m_ai->initSpell(STONEFORM_ALL); // dwarf + SHADOWMELD = m_ai->initSpell(SHADOWMELD_ALL); + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll + WILL_OF_THE_FORSAKEN = m_ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead } PlayerbotPriestAI::~PlayerbotPriestAI() {} -bool PlayerbotPriestAI::HealTarget(Unit* target) +CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - uint8 hp = target->GetHealth() * 100 / target->GetMaxHealth(); - - if (CURE_DISEASE > 0 && ai->GetCombatOrder() != PlayerbotAI::ORDERS_NODISPEL) + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) { - uint32 dispelMask = GetDispellMask(DISPEL_DISEASE); - Unit::SpellAuraHolderMap const& auras = target->GetSpellAuraHolderMap(); - for(Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) { - SpellAuraHolder *holder = itr->second; - if ((1<GetSpellProto()->Dispel) & dispelMask) - { - if(holder->GetSpellProto()->Dispel == DISPEL_DISEASE) - ai->CastSpell(CURE_DISEASE, *target); - return false; - } + if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) + return HealPlayer(GetHealTarget()); + else + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); } } - if (hp >= 80) - return false; - - if (hp < 25 && FLASH_HEAL && ai->CastSpell(FLASH_HEAL, *target)) - return true; - else if (hp < 30 && GREATER_HEAL > 0 && ai->CastSpell(GREATER_HEAL, *target)) - return true; - else if (hp < 33 && BINDING_HEAL > 0 && ai->CastSpell(BINDING_HEAL, *target)) - return true; - else if (hp < 40 && PRAYER_OF_HEALING > 0 && ai->CastSpell(PRAYER_OF_HEALING, *target)) - return true; - else if (hp < 50 && CIRCLE_OF_HEALING > 0 && ai->CastSpell(CIRCLE_OF_HEALING, *target)) - return true; - else if (hp < 60 && HEAL > 0 && ai->CastSpell(HEAL, *target)) - return true; - else if (hp < 80 && RENEW > 0 && !target->HasAura(RENEW) && ai->CastSpell(RENEW, *target)) - return true; - else - return false; -} // end HealTarget - -void PlayerbotPriestAI::DoNextCombatManeuver(Unit *pTarget) -{ - Unit* pVictim = pTarget->getVictim(); - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } - switch (ai->GetScenarioType()) + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - (ai->HasAura(SCREAM, *pTarget) && ai->GetHealthPercent() < 60 && ai->CastSpell(HEAL)) || - ai->CastSpell(SHADOW_WORD_PAIN) || - (ai->GetHealthPercent() < 80 && ai->CastSpell(RENEW)) || - (ai->GetPlayerBot()->GetDistance(pTarget) <= 5 && ai->CastSpell(SCREAM)) || - ai->CastSpell(MIND_BLAST) || - (ai->GetHealthPercent() < 50 && ai->CastSpell(GREATER_HEAL)) || - ai->CastSpell(SMITE); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoFirstCombatManeuverPVE(pTarget); break; } - // ------- Non Duel combat ---------- - Player *m_bot = GetPlayerBot(); - Group *m_group = m_bot->GetGroup(); - bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); + return RETURN_NO_ACTION_ERROR; +} - if (ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && !meleeReach) - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); - // if in melee range OR can't shoot OR have no ranged (wand) equipped - else if (ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE - && (meleeReach || SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true)) - && !ai->IsHealer()) - ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); - - if (SHOOT > 0 && ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED && !m_bot->FindCurrentSpellBySpellId(SHOOT)) - ai->CastSpell(SHOOT, *pTarget); - //ai->TellMaster( "started auto shot." ); - else if (SHOOT > 0 && m_bot->FindCurrentSpellBySpellId(SHOOT)) - m_bot->InterruptNonMeleeSpells(true, SHOOT); - - // Heal myself - if (ai->GetHealthPercent() < 15 && FADE > 0 && !m_bot->HasAura(FADE, EFFECT_INDEX_0)) - { - ai->TellMaster("I'm casting fade."); - ai->CastSpell(FADE, *m_bot); - } - else if (ai->GetHealthPercent() < 25 && POWER_WORD_SHIELD > 0 && !m_bot->HasAura(POWER_WORD_SHIELD, EFFECT_INDEX_0)) - { - ai->TellMaster("I'm casting pws on myself."); - ai->CastSpell(POWER_WORD_SHIELD); - } - else if (ai->GetHealthPercent() < 35 && DESPERATE_PRAYER > 0) +CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + if (m_ai->IsHealer()) { - ai->TellMaster("I'm casting desperate prayer."); - ai->CastSpell(DESPERATE_PRAYER, *m_bot); + // Cast renew on tank + if (CastHoTOnTank()) + return RETURN_FINISHED_FIRST_MOVES; } - else if (ai->GetHealthPercent() < 80) - HealTarget (m_bot); + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - // Heal master - uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); - if (GetMaster()->isAlive()) +CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) { - if (masterHP < 25 && POWER_WORD_SHIELD > 0 && !GetMaster()->HasAura(POWER_WORD_SHIELD, EFFECT_INDEX_0)) - ai->CastSpell(POWER_WORD_SHIELD, *(GetMaster())); - else if (masterHP < 80) - HealTarget (GetMaster()); + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoNextCombatManeuverPVE(pTarget); + break; } - // Heal group - if (m_group) + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); + uint32 spec = m_bot->GetSpec(); + + // Define a tank bot will look at + Unit* pMainTank = GetHealTarget(JOB_TANK); + + if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && !meleeReach) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + // switch to melee if in melee range AND can't shoot OR have no ranged (wand) equipped AND is not healer + else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE + && meleeReach + && (SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true)) + && !m_ai->IsHealer()) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + + // Dwarves priests will try to buff with Fear Ward + if (FEAR_WARD > 0 && !m_bot->HasSpellCooldown(FEAR_WARD)) { - Group::MemberSlotList const& groupSlot = m_group->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + // Buff tank first + if (pMainTank) { - Player *m_groupMember = sObjectMgr.GetPlayer(itr->guid); - if (!m_groupMember || !m_groupMember->isAlive()) - continue; - - uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); - if (memberHP < 25) - HealTarget(m_groupMember); + if (m_ai->In_Reach(pMainTank, FEAR_WARD) && !pMainTank->HasAura(FEAR_WARD, EFFECT_INDEX_0) && CastSpell(FEAR_WARD, pMainTank)) + return RETURN_CONTINUE; + } + // Else try to buff master + else if (GetMaster()) + { + if (m_ai->In_Reach(GetMaster(), FEAR_WARD) && !GetMaster()->HasAura(FEAR_WARD, EFFECT_INDEX_0) && CastSpell(FEAR_WARD, GetMaster())) + return RETURN_CONTINUE; } } - // Damage Spells - switch (SpellSequence) + //Used to determine if this bot is highest on threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + if (newTarget && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank { - case SPELL_HOLY: - if (SMITE > 0 && ai->In_Reach(pTarget,SMITE) && LastSpellHoly < 1 && !pTarget->HasAura(SMITE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 17) - { - ai->CastSpell(SMITE, *pTarget); - SpellSequence = SPELL_SHADOWMAGIC; - LastSpellHoly = LastSpellHoly + 1; - break; - } - else if (MANA_BURN > 0 && ai->In_Reach(pTarget,MANA_BURN) && LastSpellHoly < 2 && pTarget->GetPower(POWER_MANA) > 0 && ai->GetManaPercent() < 70 && ai->GetManaPercent() >= 14) - { - //ai->TellMaster("I'm casting mana burn."); - ai->CastSpell(MANA_BURN, *pTarget); - ai->SetIgnoreUpdateTime(3); - SpellSequence = SPELL_SHADOWMAGIC; - LastSpellHoly = LastSpellHoly + 1; - break; - } - else if (HOLY_NOVA > 0 && ai->In_Reach(pTarget,HOLY_NOVA) && LastSpellHoly < 3 && meleeReach && ai->GetManaPercent() >= 22) - { - //ai->TellMaster("I'm casting holy nova."); - ai->CastSpell(HOLY_NOVA); - SpellSequence = SPELL_SHADOWMAGIC; - LastSpellHoly = LastSpellHoly + 1; - break; - } - else if (HOLY_FIRE > 0 && ai->In_Reach(pTarget,HOLY_FIRE) && LastSpellHoly < 4 && !pTarget->HasAura(HOLY_FIRE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 13) - { - //ai->TellMaster("I'm casting holy fire."); - ai->CastSpell(HOLY_FIRE, *pTarget); - SpellSequence = SPELL_SHADOWMAGIC; - LastSpellHoly = LastSpellHoly + 1; - break; - } - else if (PRAYER_OF_MENDING > 0 && ai->In_Reach(pTarget,PRAYER_OF_MENDING) && LastSpellHoly < 5 && pVictim == GetMaster() && GetMaster()->GetHealth() <= GetMaster()->GetMaxHealth() * 0.7 && !GetMaster()->HasAura(PRAYER_OF_MENDING, EFFECT_INDEX_0) && ai->GetManaPercent() >= 15) - { - //ai->TellMaster("I'm casting prayer of mending on master."); - ai->CastSpell(PRAYER_OF_MENDING, *GetMaster()); - SpellSequence = SPELL_SHADOWMAGIC; - LastSpellHoly = LastSpellHoly + 1; - break; - } - else if (LastSpellHoly > 6) + if (FADE > 0 && !m_bot->HasAura(FADE, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(FADE)) + { + if (CastSpell(FADE, m_bot)) { - LastSpellHoly = 0; - SpellSequence = SPELL_SHADOWMAGIC; - break; + m_ai->TellMaster("I'm casting fade."); + return RETURN_CONTINUE; } - LastSpellHoly = LastSpellHoly + 1; - //SpellSequence = SPELL_SHADOWMAGIC; - //break; + else + m_ai->TellMaster("I have AGGRO."); + } - case SPELL_SHADOWMAGIC: - if (SHADOW_WORD_PAIN > 0 && ai->In_Reach(pTarget,SHADOW_WORD_PAIN) && LastSpellShadowMagic < 1 && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_INDEX_0) && ai->GetManaPercent() >= 25) - { - //ai->TellMaster("I'm casting pain."); - ai->CastSpell(SHADOW_WORD_PAIN, *pTarget); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (MIND_BLAST > 0 && ai->In_Reach(pTarget,MIND_BLAST) && LastSpellShadowMagic < 2 && ai->GetManaPercent() >= 19) - { - //ai->TellMaster("I'm casting mind blast."); - ai->CastSpell(MIND_BLAST, *pTarget); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (SCREAM > 0 && ai->In_Reach(pTarget,SCREAM) && LastSpellShadowMagic < 3 && ai->GetAttackerCount() >= 3 && ai->GetManaPercent() >= 15) + // Heal myself + // TODO: move to HealTarget code + if (m_ai->GetHealthPercent() < 35 && POWER_WORD_SHIELD > 0 && !m_bot->HasAura(POWER_WORD_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(WEAKENED_SOUL, EFFECT_INDEX_0)) + { + if (CastSpell(POWER_WORD_SHIELD) & RETURN_CONTINUE) { - ai->TellMaster("I'm casting scream."); - ai->CastSpell(SCREAM); - SpellSequence = SPELL_DISCIPLINE; - (LastSpellShadowMagic = LastSpellShadowMagic + 1); - break; + m_ai->TellMaster("I'm casting PW:S on myself."); + return RETURN_CONTINUE; } + else if (m_ai->IsHealer()) // Even if any other RETURN_ANY_OK - aside from RETURN_CONTINUE + m_ai->TellMaster("Your healer's about TO DIE. HELP ME."); + } + if (m_ai->GetHealthPercent() < 35 && DESPERATE_PRAYER > 0 && m_ai->In_Reach(m_bot,DESPERATE_PRAYER) && CastSpell(DESPERATE_PRAYER, m_bot) & RETURN_CONTINUE) + { + m_ai->TellMaster("I'm casting desperate prayer."); + return RETURN_CONTINUE; + } + // Night Elves priest bot can also cast Elune's Grace to improve his/her dodge rating + if (ELUNES_GRACE && !m_bot->HasAura(ELUNES_GRACE, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(ELUNES_GRACE) && CastSpell(ELUNES_GRACE, m_bot)) + return RETURN_CONTINUE; - else if (MIND_FLAY > 0 && LastSpellShadowMagic < 4 && !pTarget->HasAura(MIND_FLAY, EFFECT_INDEX_0) && ai->GetManaPercent() >= 10) - { - //ai->TellMaster("I'm casting mind flay."); - ai->CastSpell(MIND_FLAY, *pTarget); - ai->SetIgnoreUpdateTime(3); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (DEVOURING_PLAGUE > 0 && ai->In_Reach(pTarget,DEVOURING_PLAGUE) && LastSpellShadowMagic < 5 && !pTarget->HasAura(DEVOURING_PLAGUE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 28) - { - ai->CastSpell(DEVOURING_PLAGUE, *pTarget); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (SHADOW_PROTECTION > 0 && ai->In_Reach(pTarget,SHADOW_PROTECTION) && LastSpellShadowMagic < 6 && ai->GetManaPercent() >= 60) - { - ai->CastSpell(SHADOW_PROTECTION, *pTarget); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (VAMPIRIC_TOUCH > 0 && LastSpellShadowMagic < 7 && !pTarget->HasAura(VAMPIRIC_TOUCH, EFFECT_INDEX_0) && ai->GetManaPercent() >= 18) - { - ai->CastSpell(VAMPIRIC_TOUCH, *pTarget); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (SHADOWFIEND > 0 && ai->In_Reach(pTarget,SHADOWFIEND) && LastSpellShadowMagic < 8) - { - ai->CastSpell(SHADOWFIEND); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (MIND_SEAR > 0 && ai->In_Reach(pTarget,MIND_SEAR) && LastSpellShadowMagic < 9 && ai->GetAttackerCount() >= 3 && ai->GetManaPercent() >= 28) - { - ai->CastSpell(MIND_SEAR, *pTarget); - ai->SetIgnoreUpdateTime(5); - SpellSequence = SPELL_DISCIPLINE; - LastSpellShadowMagic = LastSpellShadowMagic + 1; - break; - } - else if (LastSpellShadowMagic > 10) - { - LastSpellShadowMagic = 0; - SpellSequence = SPELL_DISCIPLINE; - break; - } - LastSpellShadowMagic = LastSpellShadowMagic + 1; - //SpellSequence = SPELL_DISCIPLINE; - //break; + // If enemy comes in melee reach + if (meleeReach) + { + // Already healed self or tank. If healer, do nothing else to anger mob + if (m_ai->IsHealer()) + return RETURN_NO_ACTION_OK; // In a sense, mission accomplished. - case SPELL_DISCIPLINE: - if (FEAR_WARD > 0 && ai->In_Reach(pTarget,FEAR_WARD) && LastSpellDiscipline < 1 && ai->GetManaPercent() >= 3) + // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on. + if (newTarget->GetHealthPercent() > 25) { - //ai->TellMaster("I'm casting fear ward"); - ai->CastSpell(FEAR_WARD, *(GetMaster())); - SpellSequence = SPELL_HOLY; - LastSpellDiscipline = LastSpellDiscipline + 1; - break; - } - else if (POWER_INFUSION > 0 && LastSpellDiscipline < 2 && ai->GetManaPercent() >= 16) - { - //ai->TellMaster("I'm casting power infusion"); - ai->CastSpell(POWER_INFUSION, *(GetMaster())); - SpellSequence = SPELL_HOLY; - LastSpellDiscipline = LastSpellDiscipline + 1; - break; - } - else if (MASS_DISPEL > 0 && ai->In_Reach(pTarget,MASS_DISPEL) && LastSpellDiscipline < 3 && ai->GetManaPercent() >= 33) - { - //ai->TellMaster("I'm casting mass dispel"); - ai->CastSpell(MASS_DISPEL); - SpellSequence = SPELL_HOLY; - LastSpellDiscipline = LastSpellDiscipline + 1; - break; - } - else if (INNER_FOCUS > 0 && !m_bot->HasAura(INNER_FOCUS, EFFECT_INDEX_0) && LastSpellDiscipline < 4) - { - //ai->TellMaster("I'm casting inner focus"); - ai->CastSpell(INNER_FOCUS, *m_bot); - SpellSequence = SPELL_HOLY; - LastSpellDiscipline = LastSpellDiscipline + 1; - break; - } - else if (PENANCE > 0 && LastSpellDiscipline < 5 && ai->GetManaPercent() >= 16) - { - //ai->TellMaster("I'm casting PENANCE"); - ai->CastSpell(PENANCE); - SpellSequence = SPELL_HOLY; - LastSpellDiscipline = LastSpellDiscipline + 1; - break; - } - else if (LastSpellDiscipline > 6) - { - LastSpellDiscipline = 0; - SpellSequence = SPELL_HOLY; - break; + // If elite, do nothing and pray tank gets aggro off you + if (m_ai->IsElite(newTarget)) + return RETURN_NO_ACTION_OK; + + // Not an elite. You could insert PSYCHIC SCREAM here but in any PvE situation that's 90-95% likely + // to worsen the situation for the group. ... So please don't. + return CastSpell(SHOOT, pTarget); } - else + } + } + + // Damage tweaking for healers + if (m_ai->IsHealer()) + { + // Heal other players/bots first + if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE) + return RETURN_CONTINUE; + + // No one needs to be healed: do small damage instead + // If target is elite and not handled by MT: do nothing + if (m_ai->IsElite(pTarget) && pMainTank && pMainTank->getVictim() != pTarget) + return RETURN_NO_ACTION_OK; + + // Cast Shadow Word:Pain on current target and keep its up (if mana >= 40% or target HP < 15%) + if (SHADOW_WORD_PAIN > 0 && m_ai->In_Reach(pTarget,SHADOW_WORD_PAIN) && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_INDEX_0) && + (pTarget->GetHealthPercent() < 15 || m_ai->GetManaPercent() >= 40) && CastSpell(SHADOW_WORD_PAIN, pTarget)) + return RETURN_CONTINUE; + else // else shoot at it + return CastSpell(SHOOT, pTarget); + } + + // Damage Spells + switch (spec) + { + case PRIEST_SPEC_HOLY: + if (HOLY_FIRE > 0 && m_ai->In_Reach(pTarget,HOLY_FIRE) && !pTarget->HasAura(HOLY_FIRE, EFFECT_INDEX_0) && CastSpell(HOLY_FIRE, pTarget)) + return RETURN_CONTINUE; + if (SMITE > 0 && m_ai->In_Reach(pTarget,SMITE) && CastSpell(SMITE, pTarget)) + return RETURN_CONTINUE; + //if (HOLY_NOVA > 0 && m_ai->In_Reach(pTarget,HOLY_NOVA) && meleeReach && m_ai->CastSpell(HOLY_NOVA)) + // return RETURN_CONTINUE; + break; + + case PRIEST_SPEC_SHADOW: + if (DEVOURING_PLAGUE > 0 && m_ai->In_Reach(pTarget,DEVOURING_PLAGUE) && !pTarget->HasAura(DEVOURING_PLAGUE, EFFECT_INDEX_0) && CastSpell(DEVOURING_PLAGUE, pTarget)) + return RETURN_CONTINUE; + if (SHADOW_WORD_PAIN > 0 && m_ai->In_Reach(pTarget,SHADOW_WORD_PAIN) && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_INDEX_0) && CastSpell(SHADOW_WORD_PAIN, pTarget)) + return RETURN_CONTINUE; + if (MIND_BLAST > 0 && m_ai->In_Reach(pTarget,MIND_BLAST) && (!m_bot->HasSpellCooldown(MIND_BLAST)) && CastSpell(MIND_BLAST, pTarget)) + return RETURN_CONTINUE; + if (MIND_FLAY > 0 && m_ai->In_Reach(pTarget,MIND_FLAY) && CastSpell(MIND_FLAY, pTarget)) { - LastSpellDiscipline = LastSpellDiscipline + 1; - SpellSequence = SPELL_HOLY; + m_ai->SetIgnoreUpdateTime(3); + return RETURN_CONTINUE; } + if (SHADOWFORM == 0 && MIND_FLAY == 0 && SMITE > 0 && m_ai->In_Reach(pTarget,SMITE) && CastSpell(SMITE, pTarget)) // low levels + return RETURN_CONTINUE; + break; + + case PRIEST_SPEC_DISCIPLINE: + if (POWER_INFUSION > 0 && m_ai->In_Reach(GetMaster(),POWER_INFUSION) && CastSpell(POWER_INFUSION, GetMaster())) // TODO: just master? + return RETURN_CONTINUE; + if (INNER_FOCUS > 0 && m_ai->In_Reach(m_bot,INNER_FOCUS) && !m_bot->HasAura(INNER_FOCUS, EFFECT_INDEX_0) && CastSpell(INNER_FOCUS, m_bot)) + return RETURN_CONTINUE; + if (SMITE > 0 && m_ai->In_Reach(pTarget,SMITE) && CastSpell(SMITE, pTarget)) + return RETURN_CONTINUE; + break; + } + + // No spec due to low level OR no spell found yet + if (MIND_BLAST > 0 && m_ai->In_Reach(pTarget,MIND_BLAST) && (!m_bot->HasSpellCooldown(MIND_BLAST)) && CastSpell(MIND_BLAST, pTarget)) + return RETURN_CONTINUE; + if (SHADOW_WORD_PAIN > 0 && m_ai->In_Reach(pTarget,SHADOW_WORD_PAIN) && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_INDEX_0) && CastSpell(SHADOW_WORD_PAIN, pTarget)) + return RETURN_CONTINUE; + if (MIND_FLAY > 0 && m_ai->In_Reach(pTarget,MIND_FLAY) && CastSpell(MIND_FLAY, pTarget)) + { + m_ai->SetIgnoreUpdateTime(3); + return RETURN_CONTINUE; } + if (SHADOWFORM == 0 && SMITE > 0 && m_ai->In_Reach(pTarget,SMITE) && CastSpell(SMITE, pTarget)) + return RETURN_CONTINUE; + + // Default: shoot with wand + return CastSpell(SHOOT, pTarget); + + return RETURN_NO_ACTION_OK; } // end DoNextCombatManeuver -void PlayerbotPriestAI::DoNonCombatActions() +CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuverPVP(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - Player * m_bot = GetPlayerBot(); - Player * master = GetMaster(); - if (!m_bot || !master) - return; + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + // TODO: spec tweaking + if (m_ai->HasAura(SCREAM, *pTarget) && m_ai->GetHealthPercent() < 60 && HEAL && m_ai->In_Reach(pTarget,HEAL) && CastSpell(HEAL) & RETURN_ANY_OK) + return RETURN_CONTINUE; - SpellSequence = SPELL_HOLY; + if (SHADOW_WORD_PAIN && m_ai->In_Reach(pTarget,SHADOW_WORD_PAIN) && CastSpell(SHADOW_WORD_PAIN) & RETURN_ANY_OK) // TODO: Check whether enemy has it active yet + return RETURN_CONTINUE; - // selfbuff goes first - if (ai->SelfBuff(INNER_FIRE)) - return; + if (m_ai->GetHealthPercent() < 80 && RENEW && m_ai->In_Reach(pTarget,RENEW) && CastSpell(RENEW) & RETURN_ANY_OK) // TODO: Check whether you have renew active on you + return RETURN_CONTINUE; - // mana check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + if (SCREAM && m_ai->In_Reach(pTarget,SCREAM) && CastSpell(SCREAM) & RETURN_ANY_OK) // TODO: Check for cooldown + return RETURN_CONTINUE; - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); + if (MIND_BLAST && m_ai->In_Reach(pTarget,MIND_BLAST) && CastSpell(MIND_BLAST) & RETURN_ANY_OK) // TODO: Check for cooldown + return RETURN_CONTINUE; - if (pItem != nullptr && ai->GetManaPercent() < 30) - { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); - return; + if (m_ai->GetHealthPercent() < 50 && GREATER_HEAL && m_ai->In_Reach(pTarget,GREATER_HEAL) && CastSpell(GREATER_HEAL) & RETURN_ANY_OK) + return RETURN_CONTINUE; + + if (SMITE && m_ai->In_Reach(pTarget,SMITE) && CastSpell(SMITE) & RETURN_ANY_OK) + return RETURN_CONTINUE; + + m_ai->TellMaster("Couldn't find a spell to cast while dueling"); + default: + break; } - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} - pItem = ai->FindFood(); +CombatManeuverReturns PlayerbotPriestAI::HealPlayer(Player* target) +{ + CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target); + if (r != RETURN_NO_ACTION_OK) + return r; - if (pItem != nullptr && ai->GetHealthPercent() < 30) + if (!target->isAlive()) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); - return; + if (RESURRECTION && m_ai->In_Reach(target,RESURRECTION) && m_ai->CastSpell(RESURRECTION, *target)) + { + std::string msg = "Resurrecting "; + msg += target->GetName(); + m_bot->Say(msg, LANG_UNIVERSAL); + return RETURN_CONTINUE; + } + return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + + // Remove negative magic on group members if orders allow bot to do so + if (Player* pCursedTarget = GetDispelTarget(DISPEL_MAGIC)) { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); - return; + if (PRIEST_DISPEL_MAGIC > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && CastSpell(PRIEST_DISPEL_MAGIC, pCursedTarget)) + return RETURN_CONTINUE; } - // buff and heal master's group - if (master->GetGroup()) + // Remove disease on group members if orders allow bot to do so + if (Player* pDiseasedTarget = GetDispelTarget(DISPEL_DISEASE)) { - // Buff master with group buffs - if (master->isAlive()) - { - if (PRAYER_OF_FORTITUDE && ai->HasSpellReagents(PRAYER_OF_FORTITUDE) && ai->Buff(PRAYER_OF_FORTITUDE, master)) - return; + uint32 cure = ABOLISH_DISEASE > 0 ? ABOLISH_DISEASE : CURE_DISEASE; + // uint32 poison = ABOLISH_POISON ? ABOLISH_POISON : CURE_POISON; + if (cure > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && CastSpell(cure, pDiseasedTarget)) + return RETURN_CONTINUE; + } - if (PRAYER_OF_SPIRIT && ai->HasSpellReagents(PRAYER_OF_SPIRIT) && ai->Buff(PRAYER_OF_SPIRIT, master)) - return; + uint8 hp = target->GetHealthPercent(); + uint8 hpSelf = m_ai->GetHealthPercent(); - if (PRAYER_OF_SHADOW_PROTECTION && ai->HasSpellReagents(PRAYER_OF_SHADOW_PROTECTION) && ai->Buff(PRAYER_OF_SHADOW_PROTECTION, master)) - return; - } + // Define a tank bot will look at + Unit* pMainTank = GetHealTarget(JOB_TANK); - Group::MemberSlotList const& groupSlot = GetMaster()->GetGroup()->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) - { - Player *tPlayer = sObjectMgr.GetPlayer(itr->guid); - if (!tPlayer || tPlayer == m_bot) - continue; + if (hp >= 90) + return RETURN_NO_ACTION_OK; - // first rezz em - if (!tPlayer->isAlive()) - { - if (ai->CastSpell(RESURRECTION, *tPlayer)) - { - std::string msg = "Resurrecting "; - msg += tPlayer->GetName(); - m_bot->Say(msg, LANG_UNIVERSAL); - return; - } - else - continue; - } - else - { - // buff and heal - if (BuffPlayer(tPlayer)) - return; + // If target is out of range (40 yards) and is a tank: move towards it + // Other classes have to adjust their position to the healers + // TODO: This code should be common to all healers and will probably + // move to a more suitable place + if (pMainTank && !m_ai->In_Reach(pMainTank, FLASH_HEAL)) + { + m_bot->GetMotionMaster()->MoveFollow(target, 39.0f, m_bot->GetOrientation()); + return RETURN_CONTINUE; + } - if (HealTarget(tPlayer)) - return; - } - } + // Get a free and more efficient heal if needed: low mana for bot or average health for target + if (m_ai->IsInCombat() && (hp < 50 || m_ai->GetManaPercent() < 40)) + if (INNER_FOCUS > 0 && !m_bot->HasSpellCooldown(INNER_FOCUS) && !m_bot->HasAura(INNER_FOCUS, EFFECT_INDEX_0) && CastSpell(INNER_FOCUS, m_bot)) + return RETURN_CONTINUE; + + if (hp < 25 && POWER_WORD_SHIELD > 0 && m_ai->In_Reach(target,POWER_WORD_SHIELD) && !m_bot->HasAura(POWER_WORD_SHIELD, EFFECT_INDEX_0) && !target->HasAura(WEAKENED_SOUL,EFFECT_INDEX_0) && m_ai->CastSpell(POWER_WORD_SHIELD, *target)) + return RETURN_CONTINUE; + if (hp < 35 && FLASH_HEAL > 0 && m_ai->In_Reach(target,FLASH_HEAL) && m_ai->CastSpell(FLASH_HEAL, *target)) + return RETURN_CONTINUE; + if (hp < 50 && GREATER_HEAL > 0 && m_ai->In_Reach(target,GREATER_HEAL) && m_ai->CastSpell(GREATER_HEAL, *target)) + return RETURN_CONTINUE; + if (hp < 70 && HEAL > 0 && m_ai->In_Reach(target,HEAL) && m_ai->CastSpell(HEAL, *target)) + return RETURN_CONTINUE; + if (hp < 90 && RENEW > 0 && m_ai->In_Reach(target,RENEW) && !target->HasAura(RENEW) && m_ai->CastSpell(RENEW, *target)) + return RETURN_CONTINUE; + + // Group heal. Not really useful until a group check is available? + //if (hp < 40 && PRAYER_OF_HEALING > 0 && m_ai->CastSpell(PRAYER_OF_HEALING, *target) & RETURN_CONTINUE) + // return RETURN_CONTINUE; + + return RETURN_NO_ACTION_OK; +} // end HealTarget + +void PlayerbotPriestAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; + + if (!m_bot->isAlive() || m_bot->IsInDuel()) return; + + uint32 spec = m_bot->GetSpec(); + + // selfbuff goes first + if (m_ai->SelfBuff(INNER_FIRE)) + return; + + // Revive + if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE) + return; + + // After revive + if (spec == PRIEST_SPEC_SHADOW && SHADOWFORM > 0) + m_ai->SelfBuff(SHADOWFORM); + if (VAMPIRIC_EMBRACE > 0) + m_ai->SelfBuff(VAMPIRIC_EMBRACE); + + // Heal + if (m_ai->IsHealer()) + { + if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; } else { - if (master->isAlive()) - { - if (BuffPlayer(master)) - return; - if (HealTarget(master)) - return; - } - else - if (ai->CastSpell(RESURRECTION, *master)) - ai->TellMaster("Resurrecting you, Master."); + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; + } + + // Buffing + // the check for group targets is performed by NeedGroupBuff (if group is found for bots by the function) + if (NeedGroupBuff(PRAYER_OF_FORTITUDE, POWER_WORD_FORTITUDE) && m_ai->HasSpellReagents(PRAYER_OF_FORTITUDE)) + { + if (Buff(&PlayerbotPriestAI::BuffHelper, PRAYER_OF_FORTITUDE) & RETURN_CONTINUE) + return; + } + else if (Buff(&PlayerbotPriestAI::BuffHelper, POWER_WORD_FORTITUDE) & RETURN_CONTINUE) + return; + + if (NeedGroupBuff(PRAYER_OF_SPIRIT, DIVINE_SPIRIT) && m_ai->HasSpellReagents(PRAYER_OF_FORTITUDE)) + { + if (Buff(&PlayerbotPriestAI::BuffHelper, PRAYER_OF_SPIRIT) & RETURN_CONTINUE) + return; + } + else if (Buff(&PlayerbotPriestAI::BuffHelper, DIVINE_SPIRIT, (JOB_ALL | JOB_MANAONLY)) & RETURN_CONTINUE) + return; + + if (NeedGroupBuff(PRAYER_OF_SHADOW_PROTECTION, SHADOW_PROTECTION) && m_ai->HasSpellReagents(PRAYER_OF_FORTITUDE)) + { + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_SHADOW && Buff(&PlayerbotPriestAI::BuffHelper, PRAYER_OF_SHADOW_PROTECTION) & RETURN_CONTINUE) + return; } + else if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_SHADOW && Buff(&PlayerbotPriestAI::BuffHelper, SHADOW_PROTECTION) & RETURN_CONTINUE) + return; - BuffPlayer(m_bot); + if (EatDrinkBandage()) + return; + + // Nothing else to do, Night Elves will cast Shadowmeld to reduce their aggro versus patrols or nearby mobs + if (SHADOWMELD && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && m_ai->CastSpell(SHADOWMELD, *m_bot)) + return; } // end DoNonCombatActions -bool PlayerbotPriestAI::BuffPlayer(Player* target) +// TODO: this and mage's BuffHelper are identical and thus could probably go in PlayerbotClassAI.cpp somewhere +bool PlayerbotPriestAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target) { - PlayerbotAI * ai = GetAI(); - Pet * pet = target->GetPet(); + if (!ai) return false; + if (spellId == 0) return false; + if (!target) return false; - if (pet && ai->Buff(POWER_WORD_FORTITUDE, pet)) + Pet * pet = target->GetPet(); + if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet)) return true; - if (ai->Buff(POWER_WORD_FORTITUDE, target)) + if (ai->Buff(spellId, target)) return true; - if ((target->getClass() == CLASS_DRUID || target->GetPowerType() == POWER_MANA) && ai->Buff(DIVINE_SPIRIT, target)) - return true; + return false; +} + +bool PlayerbotPriestAI::CastHoTOnTank() +{ + if (!m_ai) return false; + + if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false; + + // Priest HoTs: Renew, Penance (with talents, channeled) + if (RENEW && m_ai->In_Reach(m_ai->GetGroupTank(),RENEW)) + return (RETURN_CONTINUE & CastSpell(RENEW, m_ai->GetGroupTank())); return false; } + +// Return to UpdateAI the spellId usable to neutralize a target with creaturetype +uint32 PlayerbotPriestAI::Neutralize(uint8 creatureType) +{ + if (!m_bot) return 0; + if (!m_ai) return 0; + if (!creatureType) return 0; + + if (creatureType != CREATURE_TYPE_UNDEAD) + { + m_ai->TellMaster("I can't shackle that target."); + return 0; + } + + if (SHACKLE_UNDEAD) + return SHACKLE_UNDEAD; + else + return 0; + + return 0; +} diff --git a/src/game/playerbot/PlayerbotPriestAI.h b/src/game/playerbot/PlayerbotPriestAI.h index c7d3db346..66dd9dd47 100644 --- a/src/game/playerbot/PlayerbotPriestAI.h +++ b/src/game/playerbot/PlayerbotPriestAI.h @@ -23,6 +23,7 @@ enum PriestSpells DISPERSION_1 = 47585, DIVINE_HYMN_1 = 64843, DIVINE_SPIRIT_1 = 14752, + ELUNES_GRACE_1 = 2651, FADE_1 = 586, FEAR_WARD_1 = 6346, FLASH_HEAL_1 = 2061, @@ -65,11 +66,12 @@ enum PriestSpells SHADOW_WORD_PAIN_1 = 589, SHADOWFIEND_1 = 34433, SHADOWFORM_1 = 15473, - SHOOT_1 = 5019, + SHOOT_1 = 5019, SILENCE_1 = 15487, SMITE_1 = 585, VAMPIRIC_EMBRACE_1 = 15286, - VAMPIRIC_TOUCH_1 = 34914 + VAMPIRIC_TOUCH_1 = 34914, + WEAKENED_SOUL = 6788 }; //class Player; @@ -80,17 +82,28 @@ class MANGOS_DLL_SPEC PlayerbotPriestAI : PlayerbotClassAI virtual ~PlayerbotPriestAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + uint32 Neutralize(uint8 creatureType); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); - // buff a specific player, usually a real PC who is not in group - bool BuffPlayer(Player *target); + // Utility Functions + bool CastHoTOnTank(); private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + + CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = nullptr) { return CastSpellWand(nextAction, pTarget, SHOOT); } + // Heals the target based off its hps - bool HealTarget (Unit* target); + CombatManeuverReturns HealPlayer(Player* target); + + static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target); // holy uint32 BINDING_HEAL, @@ -108,8 +121,12 @@ class MANGOS_DLL_SPEC PlayerbotPriestAI : PlayerbotClassAI PRAYER_OF_MENDING, RENEW, RESURRECTION, + SHACKLE_UNDEAD, SMITE, - CURE_DISEASE; + CURE_DISEASE, + ABOLISH_DISEASE, + PRIEST_DISPEL_MAGIC; + // ranged uint32 SHOOT; @@ -124,7 +141,9 @@ class MANGOS_DLL_SPEC PlayerbotPriestAI : PlayerbotClassAI VAMPIRIC_TOUCH, PRAYER_OF_SHADOW_PROTECTION, SHADOWFIEND, - MIND_SEAR; + MIND_SEAR, + SHADOWFORM, + VAMPIRIC_EMBRACE; // discipline uint32 POWER_WORD_SHIELD, @@ -139,15 +158,12 @@ class MANGOS_DLL_SPEC PlayerbotPriestAI : PlayerbotClassAI PRAYER_OF_SPIRIT, INNER_FOCUS; - // first aid - uint32 RECENTLY_BANDAGED; - // racial uint32 ARCANE_TORRENT, + ELUNES_GRACE, + ESCAPE_ARTIST, GIFT_OF_THE_NAARU, STONEFORM, - ESCAPE_ARTIST, - EVERY_MAN_FOR_HIMSELF, SHADOWMELD, WAR_STOMP, BERSERKING, diff --git a/src/game/playerbot/PlayerbotRogueAI.cpp b/src/game/playerbot/PlayerbotRogueAI.cpp index e3fd03807..cf1fb6377 100644 --- a/src/game/playerbot/PlayerbotRogueAI.cpp +++ b/src/game/playerbot/PlayerbotRogueAI.cpp @@ -10,98 +10,160 @@ class PlayerbotAI; PlayerbotRogueAI::PlayerbotRogueAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - SINISTER_STRIKE = ai->initSpell(SINISTER_STRIKE_1); - BACKSTAB = ai->initSpell(BACKSTAB_1); - KICK = ai->initSpell(KICK_1); - FEINT = ai->initSpell(FEINT_1); - FAN_OF_KNIVES = ai->initSpell(FAN_OF_KNIVES_1); - GOUGE = ai->initSpell(GOUGE_1); - SPRINT = ai->initSpell(SPRINT_1); - - SHADOWSTEP = ai->initSpell(SHADOWSTEP_1); - STEALTH = ai->initSpell(STEALTH_1); - VANISH = ai->initSpell(VANISH_1); - EVASION = ai->initSpell(EVASION_1); - CLOAK_OF_SHADOWS = ai->initSpell(CLOAK_OF_SHADOWS_1); - HEMORRHAGE = ai->initSpell(HEMORRHAGE_1); - GHOSTLY_STRIKE = ai->initSpell(GHOSTLY_STRIKE_1); - SHADOW_DANCE = ai->initSpell(SHADOW_DANCE_1); - BLIND = ai->initSpell(BLIND_1); - DISTRACT = ai->initSpell(DISTRACT_1); - PREPARATION = ai->initSpell(PREPARATION_1); - PREMEDITATION = ai->initSpell(PREMEDITATION_1); - PICK_POCKET = ai->initSpell(PICK_POCKET_1); - - EVISCERATE = ai->initSpell(EVISCERATE_1); - KIDNEY_SHOT = ai->initSpell(KIDNEY_SHOT_1); - SLICE_DICE = ai->initSpell(SLICE_AND_DICE_1); - GARROTE = ai->initSpell(GARROTE_1); - EXPOSE_ARMOR = ai->initSpell(EXPOSE_ARMOR_1); - RUPTURE = ai->initSpell(RUPTURE_1); - DISMANTLE = ai->initSpell(DISMANTLE_1); - CHEAP_SHOT = ai->initSpell(CHEAP_SHOT_1); - AMBUSH = ai->initSpell(AMBUSH_1); - MUTILATE = ai->initSpell(MUTILATE_1); + ADRENALINE_RUSH = m_ai->initSpell(ADRENALINE_RUSH_1); + SINISTER_STRIKE = m_ai->initSpell(SINISTER_STRIKE_1); + BACKSTAB = m_ai->initSpell(BACKSTAB_1); + KICK = m_ai->initSpell(KICK_1); + FEINT = m_ai->initSpell(FEINT_1); + FAN_OF_KNIVES = m_ai->initSpell(FAN_OF_KNIVES_1); + GOUGE = m_ai->initSpell(GOUGE_1); + SPRINT = m_ai->initSpell(SPRINT_1); + + SHADOWSTEP = m_ai->initSpell(SHADOWSTEP_1); + STEALTH = m_ai->initSpell(STEALTH_1); + VANISH = m_ai->initSpell(VANISH_1); + EVASION = m_ai->initSpell(EVASION_1); + CLOAK_OF_SHADOWS = m_ai->initSpell(CLOAK_OF_SHADOWS_1); + HEMORRHAGE = m_ai->initSpell(HEMORRHAGE_1); + GHOSTLY_STRIKE = m_ai->initSpell(GHOSTLY_STRIKE_1); + SHADOW_DANCE = m_ai->initSpell(SHADOW_DANCE_1); + BLIND = m_ai->initSpell(BLIND_1); + DISTRACT = m_ai->initSpell(DISTRACT_1); + PREPARATION = m_ai->initSpell(PREPARATION_1); + PREMEDITATION = m_ai->initSpell(PREMEDITATION_1); + PICK_POCKET = m_ai->initSpell(PICK_POCKET_1); + + EVISCERATE = m_ai->initSpell(EVISCERATE_1); + KIDNEY_SHOT = m_ai->initSpell(KIDNEY_SHOT_1); + SLICE_DICE = m_ai->initSpell(SLICE_AND_DICE_1); + GARROTE = m_ai->initSpell(GARROTE_1); + EXPOSE_ARMOR = m_ai->initSpell(EXPOSE_ARMOR_1); + RUPTURE = m_ai->initSpell(RUPTURE_1); + DISMANTLE = m_ai->initSpell(DISMANTLE_1); + CHEAP_SHOT = m_ai->initSpell(CHEAP_SHOT_1); + AMBUSH = m_ai->initSpell(AMBUSH_1); + MUTILATE = m_ai->initSpell(MUTILATE_1); RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_ROGUE); - STONEFORM = ai->initSpell(STONEFORM_ALL); // dwarf - ESCAPE_ARTIST = ai->initSpell(ESCAPE_ARTIST_ALL); // gnome - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human - SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); - BLOOD_FURY = ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll - WILL_OF_THE_FORSAKEN = ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_ROGUE); + STONEFORM = m_ai->initSpell(STONEFORM_ALL); // dwarf + ESCAPE_ARTIST = m_ai->initSpell(ESCAPE_ARTIST_ALL); // gnome + SHADOWMELD = m_ai->initSpell(SHADOWMELD_ALL); + BLOOD_FURY = m_ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll + WILL_OF_THE_FORSAKEN = m_ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead } PlayerbotRogueAI::~PlayerbotRogueAI() {} -bool PlayerbotRogueAI::DoFirstCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - Player * m_bot = GetPlayerBot(); + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) + { + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + } + } - if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && ai->CastSpell(STEALTH, *m_bot)) + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } + + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Stealth (%d)", STEALTH); + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + break; + } - m_bot->addUnitState(UNIT_STAT_CHASE); // ensure that the bot does not use MoveChase(), as this doesn't seem to work with STEALTH + return RETURN_NO_ACTION_ERROR; +} - return true; +CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuverPVE(Unit *pTarget) +{ + if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && m_ai->CastSpell(STEALTH, *m_bot)) + { + return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth } else if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) { - m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); - return false; + m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); // TODO: this isn't the place for movement code, is it? + return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth } - return false; + + // Not in stealth, can't cast stealth; Off to DoNextCombatManeuver + return RETURN_NO_ACTION_OK; } -void PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget) +// TODO: blatant copy of PVE for now, please PVP-port it +CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuverPVP(Unit *pTarget) { - if (!pTarget) - return; + if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && m_ai->CastSpell(STEALTH, *m_bot)) + { + return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth + } + else if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) + { + m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); // TODO: this isn't the place for movement code, is it? + return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth + } - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + // Not in stealth, can't cast stealth; Off to DoNextCombatManeuver + return RETURN_NO_ACTION_OK; +} - switch (ai->GetScenarioType()) +CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - { - if (SINISTER_STRIKE > 0) - ai->CastSpell(SINISTER_STRIKE); - return; - } + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoNextCombatManeuverPVE(pTarget); break; } - Player *m_bot = GetPlayerBot(); + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!pTarget) return RETURN_NO_ACTION_ERROR; + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + Unit* pVictim = pTarget->getVictim(); bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); @@ -109,235 +171,350 @@ void PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget) /*if (pVictim) { if( pVictim!=m_bot && !m_bot->hasUnitState(UNIT_STAT_FOLLOW) && !pTarget->isInBackInMap(m_bot,10) ) { - GetAI()->TellMaster( "getting behind target" ); + m_ai->TellMaster( "getting behind target" ); m_bot->GetMotionMaster()->Clear( true ); m_bot->GetMotionMaster()->MoveFollow( pTarget, 1, 2*M_PI ); } else if( pVictim==m_bot && m_bot->hasUnitState(UNIT_STAT_FOLLOW) ) { - GetAI()->TellMaster( "chasing attacking target" ); + m_ai->TellMaster( "chasing attacking target" ); m_bot->GetMotionMaster()->Clear( true ); m_bot->GetMotionMaster()->MoveChase( pTarget ); } }*/ - //Rouge like behaviour. ^^ -/* if (VANISH > 0 && GetMaster()->isDead()) { //Causes the server to crash :( removed for now. + // If bot is stealthed: pre-combat actions + if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) + { + if (PICK_POCKET > 0 && m_ai->In_Reach(pTarget,PICK_POCKET) && (pTarget->GetCreatureTypeMask() & CREATURE_TYPEMASK_HUMANOID_OR_UNDEAD) != 0 && m_ai->PickPocket(pTarget)) + return RETURN_CONTINUE; + if (PREMEDITATION > 0 && m_ai->CastSpell(PREMEDITATION, *pTarget)) + return RETURN_CONTINUE; + if (AMBUSH > 0 && !pTarget->HasInArc(M_PI_F, m_bot) && m_ai->CastSpell(AMBUSH, *pTarget)) + return RETURN_CONTINUE; + if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CHEAP_SHOT, *pTarget)) + return RETURN_CONTINUE; + if (GARROTE > 0 && !pTarget->HasInArc(M_PI_F, m_bot) && m_ai->CastSpell(GARROTE, *pTarget)) + return RETURN_CONTINUE; + + // No appropriate action found, remove stealth + m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + return RETURN_CONTINUE; + } + + //Used to determine if this bot has highest threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + if (newTarget && !(m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank + { + // Aggroed by an elite + if (m_ai->IsElite(newTarget)) + { + if (VANISH > 0 && m_ai->GetHealthPercent() <= 20 && !m_bot->HasSpellCooldown(VANISH) && !m_bot->HasAura(FEINT, EFFECT_INDEX_0) && m_ai->CastSpell(VANISH)) + { + m_ai->SetIgnoreUpdateTime(11); + return RETURN_CONTINUE; + } + if (BLIND > 0 && m_ai->GetHealthPercent() <= 30 && m_ai->HasSpellReagents(BLIND) && !newTarget->HasAura(BLIND, EFFECT_INDEX_0) && m_ai->CastSpell(BLIND, *newTarget)) + return RETURN_CONTINUE; + if (EVASION > 0 && m_ai->GetHealthPercent() <= 35 && !m_bot->HasSpellCooldown(EVASION) && !m_bot->HasAura(EVASION, EFFECT_INDEX_0) && m_ai->CastSpell(EVASION)) + return RETURN_CONTINUE; + if (FEINT > 0 && !m_bot->HasSpellCooldown(FEINT) && m_ai->CastSpell(FEINT, *newTarget)) + return RETURN_CONTINUE; + if (PREPARATION > 0 && !m_bot->HasSpellCooldown(PREPARATION) && (m_bot->HasSpellCooldown(EVASION) || m_bot->HasSpellCooldown(VANISH)) && m_ai->CastSpell(PREPARATION)) + return RETURN_CONTINUE; + } + + // Default: Gouge the target + if (GOUGE > 0 && !pTarget->HasAura(GOUGE, EFFECT_INDEX_0) && m_ai->CastSpell(GOUGE, *newTarget)) + return RETURN_CONTINUE; + } + + // Buff bot with cold blood if available + // This buff is done after the stealth and aggro management code because we don't want to give starting extra damage (= extra threat) to a bot + // as it is obviously not soloing his/her target + if (COLD_BLOOD > 0 && !m_bot->HasAura(COLD_BLOOD, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(COLD_BLOOD) && m_ai->CastSpell(COLD_BLOOD, *m_bot)) + return RETURN_CONTINUE; + + // Rogue like behaviour ^^ + /*if (VANISH > 0 && GetMaster()->isDead()) { //Causes the server to crash :( removed for now. m_bot->AttackStop(); m_bot->RemoveAllAttackers(); - ai->CastSpell(VANISH); - // m_bot->RemoveAllSpellCooldown(); - GetAI()->TellMaster("AttackStop, CombatStop, Vanish"); + m_ai->CastSpell(VANISH); + //m_bot->RemoveAllSpellCooldown(); + m_ai->TellMaster("AttackStop, CombatStop, Vanish"); }*/ - // decide what to do: - if (pVictim == m_bot && CLOAK_OF_SHADOWS > 0 && pVictim->HasAura(SPELL_AURA_PERIODIC_DAMAGE) && !m_bot->HasAura(CLOAK_OF_SHADOWS, EFFECT_INDEX_0) && ai->CastSpell(CLOAK_OF_SHADOWS)) + // we fight in melee, target is not in range, skip the next part! + if (!meleeReach) + return RETURN_CONTINUE; + + // If target is elite and wounded: use adrenaline rush to finish it quicker + if (ADRENALINE_RUSH > 0 && m_ai->IsElite(pTarget) && pTarget->GetHealthPercent() < 50 && !m_bot->HasAura(ADRENALINE_RUSH, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(ADRENALINE_RUSH) && m_ai->CastSpell(ADRENALINE_RUSH, *m_bot)) + return RETURN_CONTINUE; + + // Bot's target is casting a spell: try to interrupt it + if (pTarget->IsNonMeleeSpellCasted(true)) { - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("CoS!"); - return; + if (KIDNEY_SHOT > 0 && !m_bot->HasSpellCooldown(KIDNEY_SHOT) && m_bot->GetComboPoints() >= 1 && m_ai->CastSpell(KIDNEY_SHOT, *pTarget)) + return RETURN_CONTINUE; + else if (KICK > 0 && !m_bot->HasSpellCooldown(KICK) && m_ai->CastSpell(KICK, *pTarget)) + return RETURN_CONTINUE; } - else if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) + + // Finishing moves + // Bot will try to activate finishing move at 4 combos points (5 combos points case will be bonus) + // TODO : define combo points treshold depending on target rank and HP + if (m_bot->GetComboPoints() >= 4) + { + Creature * pCreature = (Creature*) pTarget; + // wait for energy + if (m_ai->GetEnergyAmount() < 25 && (KIDNEY_SHOT || SLICE_DICE || EXPOSE_ARMOR || RUPTURE)) + return RETURN_NO_ACTION_OK; + + // If target is elite Slice & Dice is a must have + if (SLICE_DICE > 0 && m_ai->IsElite(pTarget) && !m_bot->HasAura(SLICE_DICE, EFFECT_INDEX_1) && m_ai->CastSpell(SLICE_DICE, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + + // If target is a warrior or paladin type (high armor): expose its armor + if (EXPOSE_ARMOR > 0 && pCreature && pCreature->GetCreatureInfo()->UnitClass != 8 && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_INDEX_0) && m_ai->CastSpell(EXPOSE_ARMOR, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + + if (RUPTURE > 0 && !pTarget->HasAura(RUPTURE, EFFECT_INDEX_0) && m_ai->CastSpell(RUPTURE, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + + // default combo action or if other combo action is unavailable/failed + // wait for energy + if (m_ai->GetEnergyAmount() < 35 && EVISCERATE > 0) + return RETURN_NO_ACTION_OK; + if (EVISCERATE > 0 && m_ai->CastSpell(EVISCERATE, *pTarget)) + return RETURN_CONTINUE; + + // failed for some (non-energy related) reason, fall through to normal attacks to maximize DPS + } + + // Combo generating or damage increasing attacks + if (HEMORRHAGE > 0 && !pTarget->HasAura(HEMORRHAGE, EFFECT_INDEX_2) && m_ai->CastSpell(HEMORRHAGE, *pTarget)) + return RETURN_CONTINUE; + if (BACKSTAB > 0 && !pTarget->HasInArc(M_PI_F, m_bot) && m_ai->CastSpell(BACKSTAB, *pTarget)) + return RETURN_CONTINUE; + if (GHOSTLY_STRIKE > 0 && !m_bot->HasSpellCooldown(GHOSTLY_STRIKE) && m_ai->CastSpell(GHOSTLY_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (SINISTER_STRIKE > 0 && m_ai->CastSpell(SINISTER_STRIKE, *pTarget)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_OK; +} // end DoNextCombatManeuver + +CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + if (!pTarget) return RETURN_NO_ACTION_ERROR; + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + Unit* pVictim = pTarget->getVictim(); + bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); + + // decide what to do: + if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) SpellSequence = RogueStealth; else if (pTarget->IsNonMeleeSpellCasted(true)) SpellSequence = RogueSpellPreventing; - else if (pVictim == m_bot && ai->GetHealthPercent() < 40) + else if (pVictim == m_bot && m_ai->GetHealthPercent() < 40) SpellSequence = RogueThreat; else SpellSequence = RogueCombat; // we fight in melee, target is not in range, skip the next part! if (!meleeReach) - return; + return RETURN_CONTINUE; std::ostringstream out; switch (SpellSequence) { case RogueStealth: - out << "Case Stealth"; - if (PICK_POCKET > 0 && (pTarget->GetCreatureTypeMask() & CREATURE_TYPEMASK_HUMANOID_OR_UNDEAD) != 0 && ai->PickPocket(pTarget)) - out << " > Pick Pocket"; - else if (PREMEDITATION > 0 && ai->CastSpell(PREMEDITATION, *pTarget)) - out << " > Premeditation"; - else if (AMBUSH > 0 && ai->GetEnergyAmount() >= 60 && ai->CastSpell(AMBUSH, *pTarget)) - out << " > Ambush"; - else if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 60 && ai->CastSpell(CHEAP_SHOT, *pTarget)) - out << " > Cheap Shot"; - else if (GARROTE > 0 && ai->GetEnergyAmount() >= 50 && ai->CastSpell(GARROTE, *pTarget)) - out << " > Garrote"; - else - m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); - break; + if (PREMEDITATION > 0 && m_ai->CastSpell(PREMEDITATION, *pTarget)) + return RETURN_CONTINUE; + if (AMBUSH > 0 && m_ai->CastSpell(AMBUSH, *pTarget)) + return RETURN_CONTINUE; + if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CHEAP_SHOT, *pTarget)) + return RETURN_CONTINUE; + if (GARROTE > 0 && m_ai->CastSpell(GARROTE, *pTarget)) + return RETURN_CONTINUE; + + // No appropriate action found, remove stealth + m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + return RETURN_CONTINUE; + case RogueThreat: - out << "Case Threat"; - if (GOUGE > 0 && ai->GetEnergyAmount() >= 45 && !pTarget->HasAura(GOUGE, EFFECT_INDEX_0) && ai->CastSpell(GOUGE, *pTarget)) - out << " > Gouge"; - else if (EVASION > 0 && ai->GetHealthPercent() <= 35 && !m_bot->HasAura(EVASION, EFFECT_INDEX_0) && ai->CastSpell(EVASION)) - out << " > Evasion"; - else if (BLIND > 0 && ai->GetHealthPercent() <= 30 && !pTarget->HasAura(BLIND, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 30 && ai->CastSpell(BLIND, *pTarget)) - out << " > Blind"; - else if (FEINT > 0 && ai->GetHealthPercent() <= 25 && ai->GetEnergyAmount() >= 20 && ai->CastSpell(FEINT)) - out << " > Feint"; - else if (VANISH > 0 && ai->GetHealthPercent() <= 20 && !m_bot->HasAura(FEINT, EFFECT_INDEX_0) && ai->CastSpell(VANISH)) - out << " > Vanish"; - else if (PREPARATION > 0 && ai->CastSpell(PREPARATION)) - out << " > Preparation"; - else if (m_bot->getRace() == RACE_NIGHTELF && ai->GetHealthPercent() <= 15 && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && ai->CastSpell(SHADOWMELD, *m_bot)) - out << " > Shadowmeld"; - else - out << " NONE!"; + if (GOUGE > 0 && !pTarget->HasAura(GOUGE, EFFECT_INDEX_0) && m_ai->CastSpell(GOUGE, *pTarget)) + return RETURN_CONTINUE; + if (EVASION > 0 && m_ai->GetHealthPercent() <= 35 && !m_bot->HasAura(EVASION, EFFECT_INDEX_0) && m_ai->CastSpell(EVASION)) + return RETURN_CONTINUE; + if (BLIND > 0 && m_ai->GetHealthPercent() <= 30 && !pTarget->HasAura(BLIND, EFFECT_INDEX_0) && m_ai->CastSpell(BLIND, *pTarget)) + return RETURN_CONTINUE; + if (FEINT > 0 && m_ai->GetHealthPercent() <= 25 && m_ai->CastSpell(FEINT)) + return RETURN_CONTINUE; + if (VANISH > 0 && m_ai->GetHealthPercent() <= 20 && !m_bot->HasAura(FEINT, EFFECT_INDEX_0) && m_ai->CastSpell(VANISH)) + return RETURN_CONTINUE; + if (PREPARATION > 0 && m_ai->CastSpell(PREPARATION)) + return RETURN_CONTINUE; break; + case RogueSpellPreventing: - out << "Case Prevent"; - if (KIDNEY_SHOT > 0 && ai->GetEnergyAmount() >= 25 && m_bot->GetComboPoints() >= 2 && ai->CastSpell(KIDNEY_SHOT, *pTarget)) - out << " > Kidney Shot"; - else if (KICK > 0 && ai->GetEnergyAmount() >= 25 && ai->CastSpell(KICK, *pTarget)) - out << " > Kick"; - else - out << " NONE!"; - break; + if (KIDNEY_SHOT > 0 && m_bot->GetComboPoints() >= 2 && m_ai->CastSpell(KIDNEY_SHOT, *pTarget)) + return RETURN_CONTINUE; + else if (KICK > 0 && m_ai->CastSpell(KICK, *pTarget)) + return RETURN_CONTINUE; + // break; // No action? Go combat! + case RogueCombat: default: - out << "Case Combat"; - if (m_bot->GetComboPoints() <= 4) - { - if (SHADOW_DANCE > 0 && !m_bot->HasAura(SHADOW_DANCE, EFFECT_INDEX_0) && ai->CastSpell(SHADOW_DANCE, *m_bot)) - out << " > Shadow Dance"; - else if (CHEAP_SHOT > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_INDEX_0) && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 60 && ai->CastSpell(CHEAP_SHOT, *pTarget)) - out << " > Cheap Shot"; - else if (AMBUSH > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 60 && ai->CastSpell(AMBUSH, *pTarget)) - out << " > Ambush"; - else if (GARROTE > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 50 && ai->CastSpell(GARROTE, *pTarget)) - out << " > Garrote"; - else if (BACKSTAB > 0 && pTarget->isInBackInMap(m_bot, 1) && ai->GetEnergyAmount() >= 60 && ai->CastSpell(BACKSTAB, *pTarget)) - out << " > Backstab"; - else if (MUTILATE > 0 && ai->GetEnergyAmount() >= 60 && ai->CastSpell(MUTILATE, *pTarget)) - out << " > Mutilate"; - else if (SINISTER_STRIKE > 0 && ai->GetEnergyAmount() >= 45 && ai->CastSpell(SINISTER_STRIKE, *pTarget)) - out << " > Sinister Strike"; - else if (GHOSTLY_STRIKE > 0 && ai->GetEnergyAmount() >= 40 && ai->CastSpell(GHOSTLY_STRIKE, *pTarget)) - out << " > Ghostly Strike"; - else if (HEMORRHAGE > 0 && ai->GetEnergyAmount() >= 35 && ai->CastSpell(HEMORRHAGE, *pTarget)) - out << " > Hemorrhage"; - else if (DISMANTLE > 0 && !pTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED) && ai->GetEnergyAmount() >= 25 && ai->CastSpell(DISMANTLE, *pTarget)) - out << " > Dismantle"; - else if (SHADOWSTEP > 0 && ai->GetEnergyAmount() >= 10 && ai->CastSpell(SHADOWSTEP, *pTarget)) - out << " > Shadowstep"; - else if (m_bot->getRace() == RACE_BLOODELF && !pTarget->HasAura(ARCANE_TORRENT, EFFECT_INDEX_0) && ai->CastSpell(ARCANE_TORRENT, *pTarget)) - out << " > Arcane Torrent"; - else if ((m_bot->getRace() == RACE_HUMAN && m_bot->hasUnitState(UNIT_STAT_STUNNED)) || m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || (m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && ai->CastSpell(EVERY_MAN_FOR_HIMSELF, *m_bot))) - out << " > Every Man for Himself"; - else if ((m_bot->getRace() == RACE_UNDEAD && m_bot->HasAuraType(SPELL_AURA_MOD_FEAR)) || (m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && ai->CastSpell(WILL_OF_THE_FORSAKEN, *m_bot))) - out << " > Will of the Forsaken"; - else if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && ai->CastSpell(STONEFORM, *m_bot)) - out << " > Stoneform"; - else if ((m_bot->getRace() == RACE_GNOME && m_bot->hasUnitState(UNIT_STAT_STUNNED)) || (m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) && ai->CastSpell(ESCAPE_ARTIST, *m_bot))) - out << " > Escape Artist"; - else if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_INDEX_0) && ai->CastSpell(BLOOD_FURY, *m_bot)) - out << " > Blood Fury"; - else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0) && ai->CastSpell(BERSERKING, *m_bot)) - out << " > Berserking"; - else - out << " NONE!"; - } - else + if (m_bot->GetComboPoints() >= 5) { - if (EVISCERATE > 0 && pTarget->getClass() == CLASS_ROGUE && ai->GetEnergyAmount() >= 35 && ai->CastSpell(EVISCERATE, *pTarget)) - out << " > Rogue Eviscerate"; - else if (EVISCERATE > 0 && pTarget->getClass() == CLASS_DRUID && ai->GetEnergyAmount() >= 35 && ai->CastSpell(EVISCERATE, *pTarget)) - out << " > Druid Eviscerate"; - else if (KIDNEY_SHOT > 0 && pTarget->getClass() == CLASS_SHAMAN && ai->GetEnergyAmount() >= 25 && ai->CastSpell(KIDNEY_SHOT, *pTarget)) - out << " > Shaman Kidney"; - else if (SLICE_DICE > 0 && pTarget->getClass() == CLASS_WARLOCK && ai->GetEnergyAmount() >= 25 && ai->CastSpell(SLICE_DICE, *pTarget)) - out << " > Warlock Slice & Dice"; - else if (SLICE_DICE > 0 && pTarget->getClass() == CLASS_HUNTER && ai->GetEnergyAmount() >= 25 && ai->CastSpell(SLICE_DICE, *pTarget)) - out << " > Hunter Slice & Dice"; - else if (EXPOSE_ARMOR > 0 && pTarget->getClass() == CLASS_WARRIOR && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 25 && ai->CastSpell(EXPOSE_ARMOR, *pTarget)) - out << " > Warrior Expose Armor"; - else if (EXPOSE_ARMOR > 0 && pTarget->getClass() == CLASS_PALADIN && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 25 && ai->CastSpell(EXPOSE_ARMOR, *pTarget)) - out << " > Paladin Expose Armor"; - else if (RUPTURE > 0 && pTarget->getClass() == CLASS_MAGE && ai->GetEnergyAmount() >= 25 && ai->CastSpell(RUPTURE, *pTarget)) - out << " > Mage Rupture"; - else if (RUPTURE > 0 && pTarget->getClass() == CLASS_PRIEST && ai->GetEnergyAmount() >= 25 && ai->CastSpell(RUPTURE, *pTarget)) - out << " > Priest Rupture"; - else if (EVISCERATE > 0 && ai->GetEnergyAmount() >= 35 && ai->CastSpell(EVISCERATE, *pTarget)) - out << " > Eviscerate"; - else - out << " NONE!"; + // wait for energy + if (m_ai->GetEnergyAmount() < 25 && (KIDNEY_SHOT || SLICE_DICE || EXPOSE_ARMOR)) + return RETURN_NO_ACTION_OK; + + switch (pTarget->getClass()) + { + case CLASS_SHAMAN: + if (KIDNEY_SHOT > 0 && m_ai->CastSpell(KIDNEY_SHOT, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + break; + + case CLASS_WARLOCK: + case CLASS_HUNTER: + if (SLICE_DICE > 0 && m_ai->CastSpell(SLICE_DICE, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + break; + + case CLASS_WARRIOR: + case CLASS_PALADIN: + if (EXPOSE_ARMOR > 0 && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_INDEX_0) && m_ai->CastSpell(EXPOSE_ARMOR, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + break; + + + case CLASS_MAGE: + case CLASS_PRIEST: + if (RUPTURE > 0 && m_ai->CastSpell(RUPTURE, *pTarget)) // 25 energy (checked above) + return RETURN_CONTINUE; + break; + + case CLASS_ROGUE: + case CLASS_DRUID: + default: + break; // fall through to below + } + + // default combo action for rogue/druid or if other combo action is unavailable/failed + // wait for energy + if (m_ai->GetEnergyAmount() < 35 && EVISCERATE) + return RETURN_NO_ACTION_OK; + if (EVISCERATE > 0 && m_ai->CastSpell(EVISCERATE, *pTarget)) + return RETURN_CONTINUE; + + // failed for some (non-energy related) reason, fall through to normal attacks to maximize DPS } + + if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CHEAP_SHOT, *pTarget)) + return RETURN_CONTINUE; + if (AMBUSH > 0 && m_ai->CastSpell(AMBUSH, *pTarget)) + return RETURN_CONTINUE; + if (GARROTE > 0 && m_ai->CastSpell(GARROTE, *pTarget)) + return RETURN_CONTINUE; + if (BACKSTAB > 0 && pTarget->isInBackInMap(m_bot, 1) && m_ai->CastSpell(BACKSTAB, *pTarget)) + return RETURN_CONTINUE; + if (SINISTER_STRIKE > 0 && m_ai->CastSpell(SINISTER_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (GHOSTLY_STRIKE > 0 && m_ai->CastSpell(GHOSTLY_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (HEMORRHAGE > 0 && m_ai->CastSpell(HEMORRHAGE, *pTarget)) + return RETURN_CONTINUE; + break; } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster(out.str().c_str()); + + return RETURN_NO_ACTION_OK; } -// end DoNextCombatManeuver +// Note: in Classic, wound poison and crippling poison share the same display ID +// If bot has both in his/her inventory, the first one picked will be used, be it a wound poison or not +static const uint32 uPriorizedPoisonIds[3] = +{ + INSTANT_POISON_DISPLAYID, WOUND_POISON_DISPLAYID, DEADLY_POISON_DISPLAYID +}; -void PlayerbotRogueAI::DoNonCombatActions() +// Return a poison Item based +Item* PlayerbotRogueAI::FindPoison() const { - PlayerbotAI *ai = GetAI(); - if (!ai) - return; + Item* poison; + for (uint8 i = 0; i < countof(uPriorizedPoisonIds); ++i) + { + poison = m_ai->FindConsumable(uPriorizedPoisonIds[i]); + if (poison) + return poison; + } + return nullptr; +} - Player * m_bot = GetPlayerBot(); - if (!m_bot) - return; +void PlayerbotRogueAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; // remove stealth if (m_bot->HasAura(STEALTH)) m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); - - Item* pItem = ai->FindFood(); - Item* fItem = ai->FindBandage(); - - if (pItem != nullptr && ai->GetHealthPercent() < 30) - { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); - return; - } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) - { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); + if (EatDrinkBandage(false)) return; - } - // Search and apply poisons to weapons + // Search and apply poisons to weapons, if no poison found, try to apply a sharpening/weight stone // Mainhand ... - Item * poison, * weapon; + Item * poison, * stone, * weapon; weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) { - poison = ai->FindConsumable(INSTANT_POISON_DISPLAYID); - if (!poison) - poison = ai->FindConsumable(WOUND_POISON_DISPLAYID); - if (!poison) - poison = ai->FindConsumable(DEADLY_POISON_DISPLAYID); + poison = FindPoison(); if (poison) { - ai->UseItem(poison, EQUIPMENT_SLOT_MAINHAND); - ai->SetIgnoreUpdateTime(5); + m_ai->UseItem(poison, EQUIPMENT_SLOT_MAINHAND); + m_ai->SetIgnoreUpdateTime(5); + } + else + { + stone = m_ai->FindStoneFor(weapon); + if (stone) + { + m_ai->UseItem(stone, EQUIPMENT_SLOT_MAINHAND); + m_ai->SetIgnoreUpdateTime(5); + } } } //... and offhand weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) { - poison = ai->FindConsumable(DEADLY_POISON_DISPLAYID); - if (!poison) - poison = ai->FindConsumable(WOUND_POISON_DISPLAYID); - if (!poison) - poison = ai->FindConsumable(INSTANT_POISON_DISPLAYID); + poison = FindPoison(); if (poison) { - ai->UseItem(poison, EQUIPMENT_SLOT_OFFHAND); - ai->SetIgnoreUpdateTime(5); + m_ai->UseItem(poison, EQUIPMENT_SLOT_OFFHAND); + m_ai->SetIgnoreUpdateTime(5); + } + else + { + stone = m_ai->FindStoneFor(weapon); + if (stone) + { + m_ai->UseItem(stone, EQUIPMENT_SLOT_OFFHAND); + m_ai->SetIgnoreUpdateTime(5); + } } } + // Nothing else to do, Night Elves will cast Shadowmeld to reduce their aggro versus patrols or nearby mobs + if (SHADOWMELD && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && m_ai->CastSpell(SHADOWMELD, *m_bot)) + return; } // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotRogueAI.h b/src/game/playerbot/PlayerbotRogueAI.h index 19bc7ea92..619f3fd2c 100644 --- a/src/game/playerbot/PlayerbotRogueAI.h +++ b/src/game/playerbot/PlayerbotRogueAI.h @@ -14,9 +14,11 @@ enum enum RoguePoisonDisplayId { - DEADLY_POISON_DISPLAYID = 13707, - INSTANT_POISON_DISPLAYID = 13710, - WOUND_POISON_DISPLAYID = 37278 + DEADLY_POISON_DISPLAYID = 13707, + CRIPPLING_POISON_DISPLAYID = 13708, + MIND_NUMBLING_POISON_DISPLAYID = 13709, + INSTANT_POISON_DISPLAYID = 13710, + WOUND_POISON_DISPLAYID = 13708 }; enum RogueSpells @@ -69,34 +71,90 @@ enum RogueSpells class MANGOS_DLL_SPEC PlayerbotRogueAI : PlayerbotClassAI { +public: public: PlayerbotRogueAI(Player * const master, Player * const bot, PlayerbotAI * const ai); virtual ~PlayerbotRogueAI(); // all combat actions go here - bool DoFirstCombatManeuver(Unit*); - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); +private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + Item* FindPoison() const; + private: // COMBAT - uint32 SINISTER_STRIKE, BACKSTAB, GOUGE, EVASION, SPRINT, KICK, FEINT, SHIV, FAN_OF_KNIVES; + uint32 ADRENALINE_RUSH, + SINISTER_STRIKE, + BACKSTAB, + GOUGE, + EVASION, + SPRINT, + KICK, + FEINT, + SHIV, + FAN_OF_KNIVES; // SUBTLETY - uint32 SHADOWSTEP, STEALTH, VANISH, HEMORRHAGE, BLIND, SHADOW_DANCE, PICK_POCKET, CLOAK_OF_SHADOWS, TRICK_TRADE, CRIPPLING_POISON, DEADLY_POISON, MIND_NUMBING_POISON, GHOSTLY_STRIKE, DISTRACT, PREPARATION, PREMEDITATION; + uint32 SHADOWSTEP, + STEALTH, + VANISH, + HEMORRHAGE, + BLIND, + SHADOW_DANCE, + PICK_POCKET, + CLOAK_OF_SHADOWS, + TRICK_TRADE, + CRIPPLING_POISON, + DEADLY_POISON, + MIND_NUMBING_POISON, + GHOSTLY_STRIKE, + DISTRACT, + PREPARATION, + PREMEDITATION; // ASSASSINATION - uint32 EVISCERATE, SLICE_DICE, GARROTE, EXPOSE_ARMOR, AMBUSH, RUPTURE, DISMANTLE, CHEAP_SHOT, KIDNEY_SHOT, MUTILATE, ENVENOM, DEADLY_THROW; + uint32 COLD_BLOOD, + EVISCERATE, + SLICE_DICE, + GARROTE, + EXPOSE_ARMOR, + AMBUSH, + RUPTURE, + DISMANTLE, + CHEAP_SHOT, + KIDNEY_SHOT, + MUTILATE, + ENVENOM, + DEADLY_THROW; // first aid uint32 RECENTLY_BANDAGED; // racial - uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN; + uint32 ARCANE_TORRENT, + GIFT_OF_THE_NAARU, + STONEFORM, + ESCAPE_ARTIST, + SHADOWMELD, + BLOOD_FURY, + WAR_STOMP, + BERSERKING, + WILL_OF_THE_FORSAKEN; - uint32 SpellSequence, LastSpellCombat, LastSpellSubtlety, LastSpellAssassination, Aura; + uint32 SpellSequence, + LastSpellCombat, + LastSpellSubtlety, + LastSpellAssassination, + Aura; }; #endif diff --git a/src/game/playerbot/PlayerbotShamanAI.cpp b/src/game/playerbot/PlayerbotShamanAI.cpp index c0a667430..e31e08f94 100644 --- a/src/game/playerbot/PlayerbotShamanAI.cpp +++ b/src/game/playerbot/PlayerbotShamanAI.cpp @@ -1,553 +1,474 @@ - #include "PlayerbotShamanAI.h" #include "../SpellAuras.h" +#include "../Totem.h" class PlayerbotAI; PlayerbotShamanAI::PlayerbotShamanAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { // restoration - CHAIN_HEAL = ai->initSpell(CHAIN_HEAL_1); - HEALING_WAVE = ai->initSpell(HEALING_WAVE_1); - LESSER_HEALING_WAVE = ai->initSpell(LESSER_HEALING_WAVE_1); - RIPTIDE = ai->initSpell(RIPTIDE_1); - ANCESTRAL_SPIRIT = ai->initSpell(ANCESTRAL_SPIRIT_1); - EARTH_SHIELD = ai->initSpell(EARTH_SHIELD_1); - WATER_SHIELD = ai->initSpell(WATER_SHIELD_1); - EARTHLIVING_WEAPON = ai->initSpell(EARTHLIVING_WEAPON_1); - TREMOR_TOTEM = ai->initSpell(TREMOR_TOTEM_1); // totems - HEALING_STREAM_TOTEM = ai->initSpell(HEALING_STREAM_TOTEM_1); - MANA_SPRING_TOTEM = ai->initSpell(MANA_SPRING_TOTEM_1); - MANA_TIDE_TOTEM = ai->initSpell(MANA_TIDE_TOTEM_1); - CURE_TOXINS = ai->initSpell(CURE_TOXINS_1); - CLEANSE_SPIRIT = ai->initSpell(CLEANSE_SPIRIT_1); + CHAIN_HEAL = m_ai->initSpell(CHAIN_HEAL_1); + HEALING_WAVE = m_ai->initSpell(HEALING_WAVE_1); + LESSER_HEALING_WAVE = m_ai->initSpell(LESSER_HEALING_WAVE_1); + RIPTIDE = m_ai->initSpell(RIPTIDE_1); + ANCESTRAL_SPIRIT = m_ai->initSpell(ANCESTRAL_SPIRIT_1); + EARTH_SHIELD = m_ai->initSpell(EARTH_SHIELD_1); + WATER_SHIELD = m_ai->initSpell(WATER_SHIELD_1); + EARTHLIVING_WEAPON = m_ai->initSpell(EARTHLIVING_WEAPON_1); + TREMOR_TOTEM = m_ai->initSpell(TREMOR_TOTEM_1); // totems + HEALING_STREAM_TOTEM = m_ai->initSpell(HEALING_STREAM_TOTEM_1); + MANA_SPRING_TOTEM = m_ai->initSpell(MANA_SPRING_TOTEM_1); + MANA_TIDE_TOTEM = m_ai->initSpell(MANA_TIDE_TOTEM_1); + CURE_DISEASE_SHAMAN = m_ai->initSpell(CURE_DISEASE_SHAMAN_1); + CURE_POISON_SHAMAN = m_ai->initSpell(CURE_POISON_SHAMAN_1); + NATURES_SWIFTNESS_SHAMAN = m_ai->initSpell(NATURES_SWIFTNESS_SHAMAN_1); // enhancement FOCUSED = 0; // Focused what? - STORMSTRIKE = ai->initSpell(STORMSTRIKE_1); - LAVA_LASH = ai->initSpell(LAVA_LASH_1); - SHAMANISTIC_RAGE = ai->initSpell(SHAMANISTIC_RAGE_1); - BLOODLUST = ai->initSpell(BLOODLUST_1); - HEROISM = ai->initSpell(HEROISM_1); - FERAL_SPIRIT = ai->initSpell(FERAL_SPIRIT_1); - LIGHTNING_SHIELD = ai->initSpell(LIGHTNING_SHIELD_1); - ROCKBITER_WEAPON = ai->initSpell(ROCKBITER_WEAPON_1); - FLAMETONGUE_WEAPON = ai->initSpell(FLAMETONGUE_WEAPON_1); - FROSTBRAND_WEAPON = ai->initSpell(FROSTBRAND_WEAPON_1); - WINDFURY_WEAPON = ai->initSpell(WINDFURY_WEAPON_1); - STONESKIN_TOTEM = ai->initSpell(STONESKIN_TOTEM_1); // totems - STRENGTH_OF_EARTH_TOTEM = ai->initSpell(STRENGTH_OF_EARTH_TOTEM_1); - FROST_RESISTANCE_TOTEM = ai->initSpell(FROST_RESISTANCE_TOTEM_1); - FLAMETONGUE_TOTEM = ai->initSpell(FLAMETONGUE_TOTEM_1); - FIRE_RESISTANCE_TOTEM = ai->initSpell(FIRE_RESISTANCE_TOTEM_1); - GROUNDING_TOTEM = ai->initSpell(GROUNDING_TOTEM_1); - NATURE_RESISTANCE_TOTEM = ai->initSpell(NATURE_RESISTANCE_TOTEM_1); - WIND_FURY_TOTEM = ai->initSpell(WINDFURY_TOTEM_1); - STONESKIN_TOTEM = ai->initSpell(STONESKIN_TOTEM_1); - WRATH_OF_AIR_TOTEM = ai->initSpell(WRATH_OF_AIR_TOTEM_1); - EARTH_ELEMENTAL_TOTEM = ai->initSpell(EARTH_ELEMENTAL_TOTEM_1); + STORMSTRIKE = m_ai->initSpell(STORMSTRIKE_1); + LAVA_LASH = m_ai->initSpell(LAVA_LASH_1); + SHAMANISTIC_RAGE = m_ai->initSpell(SHAMANISTIC_RAGE_1); + BLOODLUST = m_ai->initSpell(BLOODLUST_1); + HEROISM = m_ai->initSpell(HEROISM_1); + FERAL_SPIRIT = m_ai->initSpell(FERAL_SPIRIT_1); + LIGHTNING_SHIELD = m_ai->initSpell(LIGHTNING_SHIELD_1); + ROCKBITER_WEAPON = m_ai->initSpell(ROCKBITER_WEAPON_1); + FLAMETONGUE_WEAPON = m_ai->initSpell(FLAMETONGUE_WEAPON_1); + FROSTBRAND_WEAPON = m_ai->initSpell(FROSTBRAND_WEAPON_1); + WINDFURY_WEAPON = m_ai->initSpell(WINDFURY_WEAPON_1); + STONESKIN_TOTEM = m_ai->initSpell(STONESKIN_TOTEM_1); // totems + STRENGTH_OF_EARTH_TOTEM = m_ai->initSpell(STRENGTH_OF_EARTH_TOTEM_1); + FROST_RESISTANCE_TOTEM = m_ai->initSpell(FROST_RESISTANCE_TOTEM_1); + FLAMETONGUE_TOTEM = m_ai->initSpell(FLAMETONGUE_TOTEM_1); + FIRE_RESISTANCE_TOTEM = m_ai->initSpell(FIRE_RESISTANCE_TOTEM_1); + GROUNDING_TOTEM = m_ai->initSpell(GROUNDING_TOTEM_1); + NATURE_RESISTANCE_TOTEM = m_ai->initSpell(NATURE_RESISTANCE_TOTEM_1); + WIND_FURY_TOTEM = m_ai->initSpell(WINDFURY_TOTEM_1); + STONESKIN_TOTEM = m_ai->initSpell(STONESKIN_TOTEM_1); + WRATH_OF_AIR_TOTEM = m_ai->initSpell(WRATH_OF_AIR_TOTEM_1); + EARTH_ELEMENTAL_TOTEM = m_ai->initSpell(EARTH_ELEMENTAL_TOTEM_1); // elemental - LIGHTNING_BOLT = ai->initSpell(LIGHTNING_BOLT_1); - EARTH_SHOCK = ai->initSpell(EARTH_SHOCK_1); - FLAME_SHOCK = ai->initSpell(FLAME_SHOCK_1); - PURGE = ai->initSpell(PURGE_1); + LIGHTNING_BOLT = m_ai->initSpell(LIGHTNING_BOLT_1); + EARTH_SHOCK = m_ai->initSpell(EARTH_SHOCK_1); + FLAME_SHOCK = m_ai->initSpell(FLAME_SHOCK_1); + PURGE = m_ai->initSpell(PURGE_1); WIND_SHOCK = 0; //NPC spell - FROST_SHOCK = ai->initSpell(FROST_SHOCK_1); - CHAIN_LIGHTNING = ai->initSpell(CHAIN_LIGHTNING_1); - LAVA_BURST = ai->initSpell(LAVA_BURST_1); - HEX = ai->initSpell(HEX_1); - STONECLAW_TOTEM = ai->initSpell(STONECLAW_TOTEM_1); // totems - SEARING_TOTEM = ai->initSpell(SEARING_TOTEM_1); + FROST_SHOCK = m_ai->initSpell(FROST_SHOCK_1); + CHAIN_LIGHTNING = m_ai->initSpell(CHAIN_LIGHTNING_1); + LAVA_BURST = m_ai->initSpell(LAVA_BURST_1); + HEX = m_ai->initSpell(HEX_1); + STONECLAW_TOTEM = m_ai->initSpell(STONECLAW_TOTEM_1); // totems + SEARING_TOTEM = m_ai->initSpell(SEARING_TOTEM_1); FIRE_NOVA_TOTEM = 0; // NPC only spell, check FIRE_NOVA_1 - MAGMA_TOTEM = ai->initSpell(MAGMA_TOTEM_1); - EARTHBIND_TOTEM = ai->initSpell(EARTHBIND_TOTEM_1); - TOTEM_OF_WRATH = ai->initSpell(TOTEM_OF_WRATH_1); - FIRE_ELEMENTAL_TOTEM = ai->initSpell(FIRE_ELEMENTAL_TOTEM_1); + MAGMA_TOTEM = m_ai->initSpell(MAGMA_TOTEM_1); + EARTHBIND_TOTEM = m_ai->initSpell(EARTHBIND_TOTEM_1); + TOTEM_OF_WRATH = m_ai->initSpell(TOTEM_OF_WRATH_1); + FIRE_ELEMENTAL_TOTEM = m_ai->initSpell(FIRE_ELEMENTAL_TOTEM_1); + ELEMENTAL_MASTERY = m_ai->initSpell(ELEMENTAL_MASTERY_1); RECENTLY_BANDAGED = 11196; // first aid check // racial - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_SHAMAN); // draenei - BLOOD_FURY = ai->initSpell(BLOOD_FURY_SHAMAN); // orc - WAR_STOMP = ai->initSpell(WAR_STOMP_ALL); // tauren - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_SHAMAN); // draenei + BLOOD_FURY = m_ai->initSpell(BLOOD_FURY_SHAMAN); // orc + WAR_STOMP = m_ai->initSpell(WAR_STOMP_ALL); // tauren + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll } PlayerbotShamanAI::~PlayerbotShamanAI() {} -void PlayerbotShamanAI::HealTarget(Unit &target, uint8 hp) +CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - Player *m_bot = GetPlayerBot(); - - if (hp < 30 && HEALING_WAVE > 0 && ai->GetManaPercent() >= 32) - ai->CastSpell(HEALING_WAVE, target); - else if (hp < 45 && LESSER_HEALING_WAVE > 0 && ai->GetManaPercent() >= 19) - ai->CastSpell(LESSER_HEALING_WAVE, target); - else if (hp < 55 && RIPTIDE > 0 && !target.HasAura(RIPTIDE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 21) - ai->CastSpell(RIPTIDE, target); - else if (hp < 70 && CHAIN_HEAL > 0 && ai->GetManaPercent() >= 24) - ai->CastSpell(CHAIN_HEAL, target); - if (CURE_TOXINS > 0 && ai->GetCombatOrder() != PlayerbotAI::ORDERS_NODISPEL) + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) { - uint32 DISPEL = CLEANSE_SPIRIT > 0 ? CLEANSE_SPIRIT : CURE_TOXINS; - uint32 dispelMask = GetDispellMask(DISPEL_POISON); - uint32 dispelMask2 = GetDispellMask(DISPEL_DISEASE); - uint32 dispelMask3 = GetDispellMask(DISPEL_CURSE); - Unit::SpellAuraHolderMap const& auras = target.GetSpellAuraHolderMap(); - for(Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) + return HealPlayer(GetHealTarget()); + else + return RETURN_NO_ACTION_OK; // wait it out + } + else { - SpellAuraHolder *holder = itr->second; - if ((1 << holder->GetSpellProto()->Dispel) & dispelMask) - { - if (holder->GetSpellProto()->Dispel == DISPEL_POISON) - ai->CastSpell(DISPEL, target); - } - else if ((1 << holder->GetSpellProto()->Dispel) & dispelMask2) - { - if (holder->GetSpellProto()->Dispel == DISPEL_DISEASE) - ai->CastSpell(DISPEL, target); - } - else if ((1 << holder->GetSpellProto()->Dispel) & dispelMask3 & (DISPEL == CLEANSE_SPIRIT)) - { - if (holder->GetSpellProto()->Dispel == DISPEL_CURSE) - ai->CastSpell(DISPEL, target); - } + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); } } - // end HealTarget + + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } + + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + break; + } + + return RETURN_NO_ACTION_ERROR; } -void PlayerbotShamanAI::DoNextCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} - switch (ai->GetScenarioType()) +CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - ai->CastSpell(LIGHTNING_BOLT); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoNextCombatManeuverPVE(pTarget); break; } - // ------- Non Duel combat ---------- + return RETURN_NO_ACTION_ERROR; +} - ai->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, GetMaster()); // dont want to melee mob <----changed +CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - Player *m_bot = GetPlayerBot(); - Group *m_group = m_bot->GetGroup(); + uint32 spec = m_bot->GetSpec(); - // Heal myself - if (ai->GetHealthPercent() < 30 && ai->GetManaPercent() >= 32) - ai->CastSpell(HEALING_WAVE); - else if (ai->GetHealthPercent() < 50 && ai->GetManaPercent() >= 19) - ai->CastSpell(LESSER_HEALING_WAVE); - else if (ai->GetHealthPercent() < 70) - HealTarget (*m_bot, ai->GetHealthPercent()); + // Make sure healer stays put, don't even melee (aggro) if in range. + if (m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + else if (!m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); - // Heal master - uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); - if (GetMaster()->isAlive()) + // Heal + if (m_ai->IsHealer()) + { + if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE)) + return RETURN_CONTINUE; + } + else + { + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & RETURN_CONTINUE) + return RETURN_CONTINUE; + } + + // Damage Spells + DropTotems(); + CheckShields(); + UseCooldowns(); + switch (spec) { - if (masterHP < 30 && ai->GetManaPercent() >= 32) - ai->CastSpell(HEALING_WAVE, *(GetMaster())); - else if (masterHP < 70) - HealTarget (*GetMaster(), masterHP); + case SHAMAN_SPEC_ENHANCEMENT: + if (STORMSTRIKE > 0 && (!m_bot->HasSpellCooldown(STORMSTRIKE)) && m_ai->CastSpell(STORMSTRIKE, *pTarget)) + return RETURN_CONTINUE; + if (FLAME_SHOCK > 0 && (!pTarget->HasAura(FLAME_SHOCK)) && m_ai->CastSpell(FLAME_SHOCK, *pTarget)) + return RETURN_CONTINUE; + if (EARTH_SHOCK > 0 && (!m_bot->HasSpellCooldown(EARTH_SHOCK)) && m_ai->CastSpell(EARTH_SHOCK, *pTarget)) + return RETURN_CONTINUE; + + /*if (FOCUSED > 0 && m_ai->CastSpell(FOCUSED, *pTarget)) + return RETURN_CONTINUE;*/ + break; + + case SHAMAN_SPEC_RESTORATION: + // fall through to elemental + + case SHAMAN_SPEC_ELEMENTAL: + if (FLAME_SHOCK > 0 && (!pTarget->HasAura(FLAME_SHOCK)) && m_ai->CastSpell(FLAME_SHOCK, *pTarget)) + return RETURN_CONTINUE; + if (LIGHTNING_BOLT > 0 && m_ai->CastSpell(LIGHTNING_BOLT, *pTarget)) + return RETURN_CONTINUE; + /*if (PURGE > 0 && m_ai->CastSpell(PURGE, *pTarget)) + return RETURN_CONTINUE;*/ + /*if (FROST_SHOCK > 0 && !pTarget->HasAura(FROST_SHOCK, EFFECT_INDEX_0) && m_ai->CastSpell(FROST_SHOCK, *pTarget)) + return RETURN_CONTINUE;*/ + /*if (CHAIN_LIGHTNING > 0 && m_ai->CastSpell(CHAIN_LIGHTNING, *pTarget)) + return RETURN_CONTINUE;*/ } - // Heal group - if (m_group) + return RETURN_NO_ACTION_OK; +} // end DoNextCombatManeuver + +CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + DropTotems(); + CheckShields(); + UseCooldowns(); + + Player* healTarget = (m_ai->GetScenarioType() == PlayerbotAI::SCENARIO_PVP_DUEL) ? GetHealTarget() : m_bot; + if (HealPlayer(healTarget) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE)) + return RETURN_CONTINUE; + if (m_ai->CastSpell(LIGHTNING_BOLT)) + return RETURN_CONTINUE; + + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} + +CombatManeuverReturns PlayerbotShamanAI::HealPlayer(Player* target) +{ + CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target); + if (r != RETURN_NO_ACTION_OK) + return r; + + if (!target->isAlive()) { - Group::MemberSlotList const& groupSlot = m_group->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + if (ANCESTRAL_SPIRIT && m_ai->CastSpell(ANCESTRAL_SPIRIT, *target)) { - Player *m_groupMember = sObjectMgr.GetPlayer(itr->guid); - if (!m_groupMember || !m_groupMember->isAlive()) - continue; + std::string msg = "Resurrecting "; + msg += target->GetName(); + m_bot->Say(msg, LANG_UNIVERSAL); + return RETURN_CONTINUE; + } + return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM + } + + // Remove poison on group members if orders allow bot to do so + if (Player* pPoisonedTarget = GetDispelTarget(DISPEL_POISON)) + { + if (CURE_POISON_SHAMAN > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && m_ai->CastSpell(CURE_POISON_SHAMAN, *pPoisonedTarget)) + return RETURN_CONTINUE; + } + + // Remove disease on group members if orders allow bot to do so + if (Player* pDiseasedTarget = GetDispelTarget(DISPEL_DISEASE)) + { + if (CURE_DISEASE_SHAMAN > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0 && m_ai->CastSpell(CURE_DISEASE_SHAMAN, *pDiseasedTarget)) + return RETURN_CONTINUE; + } + + // Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check) + if (target->GetHealthPercent() >= 80) + return RETURN_NO_ACTION_OK; + + // Technically the best rotation is CHAIN + LHW + LHW subbing in HW for trouble (bad mana efficiency) + if (target->GetHealthPercent() < 30 && HEALING_WAVE > 0 && m_ai->CastSpell(HEALING_WAVE, *target)) + return RETURN_CONTINUE; + if (target->GetHealthPercent() < 50 && LESSER_HEALING_WAVE > 0 && m_ai->CastSpell(LESSER_HEALING_WAVE, *target)) + return RETURN_CONTINUE; + if (target->GetHealthPercent() < 80 && CHAIN_HEAL > 0 && m_ai->CastSpell(CHAIN_HEAL, *target)) + return RETURN_CONTINUE; + + return RETURN_NO_ACTION_UNKNOWN; +} // end HealTarget + +void PlayerbotShamanAI::DropTotems() +{ + if (!m_ai) return; + if (!m_bot) return; + + uint32 spec = m_bot->GetSpec(); + + Totem* earth = m_bot->GetTotem(TOTEM_SLOT_EARTH); + Totem* fire = m_bot->GetTotem(TOTEM_SLOT_FIRE); + Totem* water = m_bot->GetTotem(TOTEM_SLOT_WATER); + Totem* air = m_bot->GetTotem(TOTEM_SLOT_AIR); + + // Earth Totems + if ((earth == nullptr) || (m_bot->GetDistance(earth) > 30)) + { + if (STRENGTH_OF_EARTH_TOTEM > 0 && m_ai->CastSpell(STRENGTH_OF_EARTH_TOTEM)) + return; + } + + // Fire Totems + if ((fire == nullptr) || (m_bot->GetDistance(fire) > 30)) + { + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FROST && FROST_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(FROST_RESISTANCE_TOTEM)) + return; + // If the spec didn't take totem of wrath, use flametongue + else if ((spec != SHAMAN_SPEC_ELEMENTAL) && FLAMETONGUE_TOTEM > 0 && m_ai->CastSpell(FLAMETONGUE_TOTEM)) + return; + } - uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); - if (memberHP < 30) - HealTarget(*m_groupMember, memberHP); + // Air totems + if ((air == nullptr) || (m_bot->GetDistance(air) > 30)) + { + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_NATURE && NATURE_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(NATURE_RESISTANCE_TOTEM)) + return; + else if (spec == SHAMAN_SPEC_ENHANCEMENT) + { + if (WIND_FURY_TOTEM > 0 /*&& !m_bot->HasAura(IMPROVED_ICY_TALONS)*/ && m_ai->CastSpell(WIND_FURY_TOTEM)) + return; + } + else + { + if (WRATH_OF_AIR_TOTEM > 0 && m_ai->CastSpell(WRATH_OF_AIR_TOTEM)) + return; } } - // Damage Spells + // Water Totems + if ((water == nullptr) || (m_bot->GetDistance(water) > 30)) + { + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FIRE && FIRE_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(FIRE_RESISTANCE_TOTEM)) + return; + else if (MANA_SPRING_TOTEM > 0 && m_ai->CastSpell(MANA_SPRING_TOTEM)) + return; + } + + /*if (EARTH_ELEMENTAL_TOTEM > 0 && m_ai->CastSpell(EARTH_ELEMENTAL_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (EARTHBIND_TOTEM > 0 && !pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(EARTHBIND_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (FIRE_ELEMENTAL_TOTEM > 0 && m_ai->CastSpell(FIRE_ELEMENTAL_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (FIRE_NOVA_TOTEM > 0 && m_ai->CastSpell(FIRE_NOVA_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (GROUNDING_TOTEM > 0 && !m_bot->HasAura(GROUNDING_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(WIND_FURY_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(GROUNDING_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (HEALING_STREAM_TOTEM > 0 && m_ai->GetHealthPercent() < 50 && !m_bot->HasAura(HEALING_STREAM_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(MANA_SPRING_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(HEALING_STREAM_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (MAGMA_TOTEM > 0 && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && m_ai->CastSpell(MAGMA_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (SEARING_TOTEM > 0 && !pTarget->HasAura(SEARING_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0) && m_ai->CastSpell(SEARING_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (STONECLAW_TOTEM > 0 && m_ai->GetHealthPercent() < 51 && !pTarget->HasAura(STONECLAW_TOTEM, EFFECT_INDEX_0) && !pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(STONECLAW_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (STONESKIN_TOTEM > 0 && !m_bot->HasAura(STONESKIN_TOTEM, EFFECT_INDEX_0) && !m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(STONESKIN_TOTEM)) + return RETURN_CONTINUE;*/ + /*if (TREMOR_TOTEM > 0 && !m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0) && m_ai->CastSpell(TREMOR_TOTEM)) + return RETURN_CONTINUE;*/ +} + +void PlayerbotShamanAI::CheckShields() +{ + if (!m_ai) return; + if (!m_bot) return; + + uint32 spec = m_bot->GetSpec(); + + if (spec == SHAMAN_SPEC_ENHANCEMENT && LIGHTNING_SHIELD > 0 && !m_bot->HasAura(LIGHTNING_SHIELD, EFFECT_INDEX_0)) + m_ai->CastSpell(LIGHTNING_SHIELD, *m_bot); + if (EARTH_SHIELD > 0 && !GetMaster()->HasAura(EARTH_SHIELD, EFFECT_INDEX_0)) + m_ai->CastSpell(EARTH_SHIELD, *(GetMaster())); +} + +void PlayerbotShamanAI::UseCooldowns() +{ + if (!m_ai) return; + if (!m_bot) return; + + uint32 spec = m_bot->GetSpec(); + + if (BLOODLUST > 0 && (!GetMaster()->HasAura(BLOODLUST, EFFECT_INDEX_0)) && m_ai->CastSpell(BLOODLUST)) + return; - switch (SpellSequence) + switch(spec) { - case SPELL_ENHANCEMENT: - if (STRENGTH_OF_EARTH_TOTEM > 0 && LastSpellEnhancement == 1 && (!m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 13) - { - ai->CastSpell(STRENGTH_OF_EARTH_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (STONESKIN_TOTEM > 0 && LastSpellEnhancement == 5 && (!m_bot->HasAura(STONESKIN_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 13) - { - ai->CastSpell(STONESKIN_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (FOCUSED > 0 && LastSpellEnhancement == 2) - { - ai->CastSpell(FOCUSED, *pTarget); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (FROST_RESISTANCE_TOTEM > 0 && LastSpellEnhancement == 10 && (!m_bot->HasAura(FROST_RESISTANCE_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && (!m_bot->HasAura(FLAMETONGUE_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 10) - { - ai->CastSpell(FROST_RESISTANCE_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (FLAMETONGUE_TOTEM > 0 && LastSpellEnhancement == 15 && (!m_bot->HasAura(FLAMETONGUE_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && (!m_bot->HasAura(FROST_RESISTANCE_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 14) - { - ai->CastSpell(FLAMETONGUE_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (FIRE_RESISTANCE_TOTEM > 0 && LastSpellEnhancement == 20 && (!m_bot->HasAura(FIRE_RESISTANCE_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(HEALING_STREAM_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(MANA_SPRING_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 10) - { - ai->CastSpell(FIRE_RESISTANCE_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (GROUNDING_TOTEM > 0 && LastSpellEnhancement == 25 && (!m_bot->HasAura(GROUNDING_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(WIND_FURY_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 5) - { - ai->CastSpell(GROUNDING_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (NATURE_RESISTANCE_TOTEM > 0 && LastSpellEnhancement == 30 && (!m_bot->HasAura(NATURE_RESISTANCE_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(GROUNDING_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(WIND_FURY_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 10) - { - ai->CastSpell(NATURE_RESISTANCE_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (WIND_FURY_TOTEM > 0 && LastSpellEnhancement == 35 && (!m_bot->HasAura(WIND_FURY_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(GROUNDING_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 11) - { - ai->CastSpell(WIND_FURY_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (STORMSTRIKE > 0 && LastSpellEnhancement == 4 && (!pTarget->HasAura(STORMSTRIKE, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(STORMSTRIKE, *pTarget); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (LAVA_LASH > 0 && LastSpellEnhancement == 6 && ai->GetManaPercent() >= 4) - { - ai->CastSpell(LAVA_LASH, *pTarget); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (FERAL_SPIRIT > 0 && LastSpellEnhancement == 7 && ai->GetManaPercent() >= 12) - { - ai->CastSpell(FERAL_SPIRIT); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (WRATH_OF_AIR_TOTEM > 0 && (!m_bot->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(GROUNDING_TOTEM, EFFECT_INDEX_0)) && LastSpellEnhancement == 40) - { - ai->CastSpell(WRATH_OF_AIR_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (EARTH_ELEMENTAL_TOTEM > 0 && LastSpellEnhancement == 45 && ai->GetManaPercent() >= 24) - { - ai->CastSpell(EARTH_ELEMENTAL_TOTEM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (BLOODLUST > 0 && LastSpellEnhancement == 8 && (!GetMaster()->HasAura(BLOODLUST, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 26) - { - ai->CastSpell(BLOODLUST); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (HEROISM > 0 && LastSpellEnhancement == 10 && (!GetMaster()->HasAura(HEROISM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 26) - { - ai->CastSpell(HEROISM); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (SHAMANISTIC_RAGE > 0 && (!m_bot->HasAura(SHAMANISTIC_RAGE, EFFECT_INDEX_0)) && LastSpellEnhancement == 11) - { - ai->CastSpell(SHAMANISTIC_RAGE, *m_bot); - SpellSequence = SPELL_RESTORATION; - LastSpellEnhancement = LastSpellEnhancement + 1; - break; - } - else if (LastSpellEnhancement > 50) - { - LastSpellEnhancement = 1; - SpellSequence = SPELL_RESTORATION; - break; - } - LastSpellEnhancement = LastSpellEnhancement + 1; - //SpellSequence = SPELL_RESTORATION; - //break; - - case SPELL_RESTORATION: - if (HEALING_STREAM_TOTEM > 0 && LastSpellRestoration < 3 && ai->GetHealthPercent() < 50 && (!m_bot->HasAura(HEALING_STREAM_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(MANA_SPRING_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 4) - { - ai->CastSpell(HEALING_STREAM_TOTEM); - SpellSequence = SPELL_ELEMENTAL; - LastSpellRestoration = LastSpellRestoration + 1; - break; - } - else if (MANA_SPRING_TOTEM > 0 && LastSpellRestoration < 4 && (!m_bot->HasAura(MANA_SPRING_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(HEALING_STREAM_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 5) - { - ai->CastSpell(MANA_SPRING_TOTEM); - SpellSequence = SPELL_ELEMENTAL; - LastSpellRestoration = LastSpellRestoration + 1; - break; - } - else if (MANA_TIDE_TOTEM > 0 && LastSpellRestoration < 5 && ai->GetManaPercent() < 50 && ai->GetManaPercent() >= 3) - { - ai->CastSpell(MANA_TIDE_TOTEM); - SpellSequence = SPELL_ELEMENTAL; - LastSpellRestoration = LastSpellRestoration + 1; - break; - } - /*else if (TREMOR_TOTEM > 0 && LastSpellRestoration < 6 && (!m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 2) - { - ai->CastSpell(TREMOR_TOTEM); - SpellSequence = SPELL_ELEMENTAL; - LastSpellRestoration = LastSpellRestoration +1; - break; - }*/ - else if (LastSpellRestoration > 6) - { - LastSpellRestoration = 0; - SpellSequence = SPELL_ELEMENTAL; - break; - } - LastSpellRestoration = LastSpellRestoration + 1; - //SpellSequence = SPELL_ELEMENTAL; - //break; - - case SPELL_ELEMENTAL: - if (LIGHTNING_BOLT > 0 && LastSpellElemental == 1 && ai->GetManaPercent() >= 13) - { - ai->CastSpell(LIGHTNING_BOLT, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (SEARING_TOTEM > 0 && LastSpellElemental == 2 && (!pTarget->HasAura(SEARING_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 9) - { - ai->CastSpell(SEARING_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (STONECLAW_TOTEM > 0 && ai->GetHealthPercent() < 51 && LastSpellElemental == 3 && (!pTarget->HasAura(STONECLAW_TOTEM, EFFECT_INDEX_0)) && (!pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 8) - { - ai->CastSpell(STONECLAW_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (FLAME_SHOCK > 0 && LastSpellElemental == 4 && (!pTarget->HasAura(FLAME_SHOCK, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 22) - { - ai->CastSpell(FLAME_SHOCK, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (LAVA_BURST > 0 && LastSpellElemental == 5 && (pTarget->HasAura(FLAME_SHOCK, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 10) - { - ai->CastSpell(LAVA_BURST, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (MAGMA_TOTEM > 0 && LastSpellElemental == 6 && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 35) - { - ai->CastSpell(MAGMA_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (EARTHBIND_TOTEM > 0 && LastSpellElemental == 7 && (!pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_INDEX_0)) && (!m_bot->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 5) - { - ai->CastSpell(EARTHBIND_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (EARTH_SHOCK > 0 && LastSpellElemental == 8 && ai->GetManaPercent() >= 23) - { - ai->CastSpell(EARTH_SHOCK, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (PURGE > 0 && LastSpellElemental == 9 && ai->GetManaPercent() >= 8) - { - ai->CastSpell(PURGE, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (WIND_SHOCK > 0 && LastSpellElemental == 10 && ai->GetManaPercent() >= 8) - { - ai->CastSpell(WIND_SHOCK, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (FIRE_NOVA_TOTEM > 0 && LastSpellElemental == 11 && ai->GetManaPercent() >= 33) - { - ai->CastSpell(FIRE_NOVA_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (FROST_SHOCK > 0 && LastSpellElemental == 12 && (!pTarget->HasAura(FROST_SHOCK, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 23) - { - ai->CastSpell(FROST_SHOCK, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (CHAIN_LIGHTNING > 0 && LastSpellElemental == 13 && ai->GetManaPercent() >= 33) - { - ai->CastSpell(CHAIN_LIGHTNING, *pTarget); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (TOTEM_OF_WRATH > 0 && LastSpellElemental == 14 && (!m_bot->HasAura(TOTEM_OF_WRATH, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 5) - { - ai->CastSpell(TOTEM_OF_WRATH); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - else if (FIRE_ELEMENTAL_TOTEM > 0 && LastSpellElemental == 15 && ai->GetManaPercent() >= 23) - { - ai->CastSpell(FIRE_ELEMENTAL_TOTEM); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - } - /*else if (HEX > 0 && LastSpellElemental == 16 && (!pTarget->HasAura(HEX, EFFECT_INDEX_0)) && ai->GetManaPercent() >= 3) - { - ai->CastSpell(HEX); - SpellSequence = SPELL_ENHANCEMENT; - LastSpellElemental = LastSpellElemental + 1; - break; - }*/ - else if (LastSpellElemental > 16) - { - LastSpellElemental = 1; - SpellSequence = SPELL_ENHANCEMENT; - break; - } - else - { - LastSpellElemental = LastSpellElemental + 1; - SpellSequence = SPELL_ENHANCEMENT; - } + case SHAMAN_SPEC_ENHANCEMENT: + break; + + case SHAMAN_SPEC_ELEMENTAL: + if (ELEMENTAL_MASTERY > 0 && m_ai->CastSpell(ELEMENTAL_MASTERY, *m_bot)) + return; + break; + + case SHAMAN_SPEC_RESTORATION: + if (MANA_TIDE_TOTEM > 0 && m_ai->GetManaPercent() < 50 && m_ai->CastSpell(MANA_TIDE_TOTEM)) + return; + else if (NATURES_SWIFTNESS_SHAMAN > 0 && m_ai->CastSpell(NATURES_SWIFTNESS_SHAMAN)) + return; + + default: + break; } -} // end DoNextCombatManeuver +} void PlayerbotShamanAI::DoNonCombatActions() { - PlayerbotAI* ai = GetAI(); - Player * m_bot = GetPlayerBot(); - if (!m_bot) - return; + if (!m_ai) return; + if (!m_bot) return; - SpellSequence = SPELL_ENHANCEMENT; + if (!m_bot->isAlive() || m_bot->IsInDuel()) return; - // buff master with EARTH_SHIELD - if (EARTH_SHIELD > 0) - (!GetMaster()->HasAura(EARTH_SHIELD, EFFECT_INDEX_0) && ai->CastSpell(EARTH_SHIELD, *(GetMaster()))); + uint32 spec = m_bot->GetSpec(); - // buff myself with WATER_SHIELD, LIGHTNING_SHIELD - if (WATER_SHIELD > 0) - (!m_bot->HasAura(WATER_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(LIGHTNING_SHIELD, EFFECT_INDEX_0) && ai->CastSpell(WATER_SHIELD, *m_bot)); - else if (LIGHTNING_SHIELD > 0) - (!m_bot->HasAura(LIGHTNING_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(WATER_SHIELD, EFFECT_INDEX_0) && ai->CastSpell(LIGHTNING_SHIELD, *m_bot)); + CheckShields(); /* // buff myself weapon if (ROCKBITER_WEAPON > 0) - (!m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && ai->CastSpell(ROCKBITER_WEAPON,*m_bot) ); + (!m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && m_ai->CastSpell(ROCKBITER_WEAPON,*m_bot) ); else if (EARTHLIVING_WEAPON > 0) - (!m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && ai->CastSpell(WINDFURY_WEAPON,*m_bot) ); + (!m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && m_ai->CastSpell(WINDFURY_WEAPON,*m_bot) ); else if (WINDFURY_WEAPON > 0) - (!m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && ai->CastSpell(WINDFURY_WEAPON,*m_bot) ); + (!m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && m_ai->CastSpell(WINDFURY_WEAPON,*m_bot) ); else if (FLAMETONGUE_WEAPON > 0) - (!m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && ai->CastSpell(FLAMETONGUE_WEAPON,*m_bot) ); + (!m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && m_ai->CastSpell(FLAMETONGUE_WEAPON,*m_bot) ); else if (FROSTBRAND_WEAPON > 0) - (!m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(EARTHLIVING_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && ai->CastSpell(FROSTBRAND_WEAPON,*m_bot) ); + (!m_bot->HasAura(FROSTBRAND_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(WINDFURY_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(FLAMETONGUE_WEAPON, EFFECT_INDEX_0) && !m_bot->HasAura(ROCKBITER_WEAPON, EFFECT_INDEX_0) && m_ai->CastSpell(FROSTBRAND_WEAPON,*m_bot) ); */ - // mana check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + // Mainhand + Item* weapon; + weapon = m_bot->GetItemByPos(EQUIPMENT_SLOT_MAINHAND); + if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ELEMENTAL) + m_ai->CastSpell(FLAMETONGUE_WEAPON, *m_bot); + else if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ENHANCEMENT) + m_ai->CastSpell(WINDFURY_WEAPON, *m_bot); - Item* pItem = ai->FindDrink(); - Item* fItem = ai->FindBandage(); + //Offhand + weapon = m_bot->GetItemByPos(EQUIPMENT_SLOT_OFFHAND); + if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ENHANCEMENT) + m_ai->CastSpell(FLAMETONGUE_WEAPON, *m_bot); - if (pItem != nullptr && ai->GetManaPercent() < 30) - { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); + // Revive + if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE) return; - } - - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); - pItem = ai->FindFood(); - - if (pItem != nullptr && ai->GetHealthPercent() < 30) + // Heal + if (m_ai->IsHealer()) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); - return; + if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + else { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); - return; + // Is this desirable? Debatable. + // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either) + if (HealPlayer(m_bot) & RETURN_CONTINUE) + return;// RETURN_CONTINUE; } - // heal master's group - if (GetMaster()->GetGroup()) - { - Group::MemberSlotList const& groupSlot = GetMaster()->GetGroup()->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) - { - Player *tPlayer = sObjectMgr.GetPlayer(itr->guid); - if (!tPlayer || !tPlayer->isAlive()) - continue; - - // heal - (HealTarget(*tPlayer, tPlayer->GetHealth() * 100 / tPlayer->GetMaxHealth())); - } - } + // hp/mana check + if (EatDrinkBandage()) + return; } // end DoNonCombatActions + +bool PlayerbotShamanAI::CastHoTOnTank() +{ + if (!m_ai) return false; + + if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false; + + // Shaman: Healing Stream Totem + // None of these are cast before Pulling + + return false; +} diff --git a/src/game/playerbot/PlayerbotShamanAI.h b/src/game/playerbot/PlayerbotShamanAI.h index 91ab76979..01fcf7ddc 100644 --- a/src/game/playerbot/PlayerbotShamanAI.h +++ b/src/game/playerbot/PlayerbotShamanAI.h @@ -23,7 +23,8 @@ enum CHAINED_HEAL_1 = 70809, CLEANSE_SPIRIT_1 = 51886, CLEANSING_TOTEM_1 = 8170, - CURE_TOXINS_1 = 526, + CURE_DISEASE_SHAMAN_1 = 2870, + CURE_POISON_SHAMAN_1 = 526, EARTH_ELEMENTAL_TOTEM_1 = 2062, EARTH_SHIELD_1 = 974, EARTH_SHOCK_1 = 8042, @@ -88,31 +89,106 @@ class MANGOS_DLL_SPEC PlayerbotShamanAI : PlayerbotClassAI virtual ~PlayerbotShamanAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); + // Utility Functions + bool CastHoTOnTank(); + private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + // Heals the target based off its hps - void HealTarget (Unit& target, uint8 hp); + CombatManeuverReturns HealPlayer(Player* target); + Player* GetHealTarget() { return PlayerbotClassAI::GetHealTarget(); } + void DropTotems(); + void CheckShields(); + void UseCooldowns(); // ENHANCEMENT - uint32 ROCKBITER_WEAPON, STONESKIN_TOTEM, LIGHTNING_SHIELD, FLAMETONGUE_WEAPON, STRENGTH_OF_EARTH_TOTEM, FOCUSED, FROSTBRAND_WEAPON, FROST_RESISTANCE_TOTEM, FLAMETONGUE_TOTEM, FIRE_RESISTANCE_TOTEM, WINDFURY_WEAPON, GROUNDING_TOTEM, NATURE_RESISTANCE_TOTEM, WIND_FURY_TOTEM, STORMSTRIKE, LAVA_LASH, SHAMANISTIC_RAGE, WRATH_OF_AIR_TOTEM, EARTH_ELEMENTAL_TOTEM, BLOODLUST, HEROISM, FERAL_SPIRIT; + uint32 ROCKBITER_WEAPON, + STONESKIN_TOTEM, + LIGHTNING_SHIELD, + FLAMETONGUE_WEAPON, + STRENGTH_OF_EARTH_TOTEM, + FOCUSED, + FROSTBRAND_WEAPON, + FROST_RESISTANCE_TOTEM, + FLAMETONGUE_TOTEM, + FIRE_RESISTANCE_TOTEM, + WINDFURY_WEAPON, + GROUNDING_TOTEM, + NATURE_RESISTANCE_TOTEM, + WIND_FURY_TOTEM, + STORMSTRIKE, + LAVA_LASH, + SHAMANISTIC_RAGE, + WRATH_OF_AIR_TOTEM, + EARTH_ELEMENTAL_TOTEM, + BLOODLUST, + HEROISM, + FERAL_SPIRIT; // RESTORATION - uint32 HEALING_WAVE, LESSER_HEALING_WAVE, ANCESTRAL_SPIRIT, TREMOR_TOTEM, HEALING_STREAM_TOTEM, MANA_SPRING_TOTEM, CHAIN_HEAL, MANA_TIDE_TOTEM, EARTH_SHIELD, WATER_SHIELD, EARTHLIVING_WEAPON, RIPTIDE, CURE_TOXINS, CLEANSE_SPIRIT; + uint32 HEALING_WAVE, + LESSER_HEALING_WAVE, + ANCESTRAL_SPIRIT, + TREMOR_TOTEM, + HEALING_STREAM_TOTEM, + MANA_SPRING_TOTEM, + CHAIN_HEAL, + MANA_TIDE_TOTEM, + EARTH_SHIELD, + WATER_SHIELD, + EARTHLIVING_WEAPON, + RIPTIDE, + CURE_DISEASE_SHAMAN, + CURE_POISON_SHAMAN, + NATURES_SWIFTNESS_SHAMAN; // ELEMENTAL - uint32 LIGHTNING_BOLT, EARTH_SHOCK, STONECLAW_TOTEM, FLAME_SHOCK, SEARING_TOTEM, PURGE, FIRE_NOVA_TOTEM, WIND_SHOCK, FROST_SHOCK, MAGMA_TOTEM, CHAIN_LIGHTNING, TOTEM_OF_WRATH, FIRE_ELEMENTAL_TOTEM, LAVA_BURST, EARTHBIND_TOTEM, HEX; + uint32 LIGHTNING_BOLT, + EARTH_SHOCK, + STONECLAW_TOTEM, + FLAME_SHOCK, + SEARING_TOTEM, + PURGE, + FIRE_NOVA_TOTEM, + WIND_SHOCK, + FROST_SHOCK, + MAGMA_TOTEM, + CHAIN_LIGHTNING, + TOTEM_OF_WRATH, + FIRE_ELEMENTAL_TOTEM, + LAVA_BURST, + EARTHBIND_TOTEM, + ELEMENTAL_MASTERY, + HEX; // first aid uint32 RECENTLY_BANDAGED; // racial - uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN; + uint32 ARCANE_TORRENT, + GIFT_OF_THE_NAARU, + STONEFORM, + ESCAPE_ARTIST, + SHADOWMELD, + BLOOD_FURY, + WAR_STOMP, + BERSERKING, + WILL_OF_THE_FORSAKEN; - uint32 SpellSequence, LastSpellEnhancement, LastSpellRestoration, LastSpellElemental; + uint32 SpellSequence, + LastSpellEnhancement, + LastSpellRestoration, + LastSpellElemental; }; #endif diff --git a/src/game/playerbot/PlayerbotWarlockAI.cpp b/src/game/playerbot/PlayerbotWarlockAI.cpp index 560854ce4..6b677508d 100644 --- a/src/game/playerbot/PlayerbotWarlockAI.cpp +++ b/src/game/playerbot/PlayerbotWarlockAI.cpp @@ -5,357 +5,541 @@ class PlayerbotAI; PlayerbotWarlockAI::PlayerbotWarlockAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { // DESTRUCTION - SHADOW_BOLT = ai->initSpell(SHADOW_BOLT_1); - IMMOLATE = ai->initSpell(IMMOLATE_1); - INCINERATE = ai->initSpell(INCINERATE_1); - SEARING_PAIN = ai->initSpell(SEARING_PAIN_1); - CONFLAGRATE = ai->initSpell(CONFLAGRATE_1); - SHADOWFURY = ai->initSpell(SHADOWFURY_1); - CHAOS_BOLT = ai->initSpell(CHAOS_BOLT_1); - SHADOWFLAME = ai->initSpell(SHADOWFLAME_1); - HELLFIRE = ai->initSpell(HELLFIRE_1); - RAIN_OF_FIRE = ai->initSpell(RAIN_OF_FIRE_1); - SOUL_FIRE = ai->initSpell(SOUL_FIRE_1); // soul shard spells - SHADOWBURN = ai->initSpell(SHADOWBURN_1); + SHADOW_BOLT = m_ai->initSpell(SHADOW_BOLT_1); + IMMOLATE = m_ai->initSpell(IMMOLATE_1); + INCINERATE = m_ai->initSpell(INCINERATE_1); + SEARING_PAIN = m_ai->initSpell(SEARING_PAIN_1); + CONFLAGRATE = m_ai->initSpell(CONFLAGRATE_1); + SHADOWFURY = m_ai->initSpell(SHADOWFURY_1); + CHAOS_BOLT = m_ai->initSpell(CHAOS_BOLT_1); + SHADOWFLAME = m_ai->initSpell(SHADOWFLAME_1); + HELLFIRE = m_ai->initSpell(HELLFIRE_1); + RAIN_OF_FIRE = m_ai->initSpell(RAIN_OF_FIRE_1); + SOUL_FIRE = m_ai->initSpell(SOUL_FIRE_1); // soul shard spells + SHADOWBURN = m_ai->initSpell(SHADOWBURN_1); // CURSE - CURSE_OF_WEAKNESS = ai->initSpell(CURSE_OF_WEAKNESS_1); - CURSE_OF_THE_ELEMENTS = ai->initSpell(CURSE_OF_THE_ELEMENTS_1); - CURSE_OF_AGONY = ai->initSpell(CURSE_OF_AGONY_1); - CURSE_OF_EXHAUSTION = ai->initSpell(CURSE_OF_EXHAUSTION_1); - CURSE_OF_TONGUES = ai->initSpell(CURSE_OF_TONGUES_1); - CURSE_OF_DOOM = ai->initSpell(CURSE_OF_DOOM_1); + CURSE_OF_WEAKNESS = m_ai->initSpell(CURSE_OF_WEAKNESS_1); + CURSE_OF_THE_ELEMENTS = m_ai->initSpell(CURSE_OF_THE_ELEMENTS_1); + CURSE_OF_AGONY = m_ai->initSpell(CURSE_OF_AGONY_1); + CURSE_OF_EXHAUSTION = m_ai->initSpell(CURSE_OF_EXHAUSTION_1); + CURSE_OF_TONGUES = m_ai->initSpell(CURSE_OF_TONGUES_1); + CURSE_OF_DOOM = m_ai->initSpell(CURSE_OF_DOOM_1); // AFFLICTION - CORRUPTION = ai->initSpell(CORRUPTION_1); - DRAIN_SOUL = ai->initSpell(DRAIN_SOUL_1); - DRAIN_LIFE = ai->initSpell(DRAIN_LIFE_1); - DRAIN_MANA = ai->initSpell(DRAIN_MANA_1); - LIFE_TAP = ai->initSpell(LIFE_TAP_1); - UNSTABLE_AFFLICTION = ai->initSpell(UNSTABLE_AFFLICTION_1); - HAUNT = ai->initSpell(HAUNT_1); - SEED_OF_CORRUPTION = ai->initSpell(SEED_OF_CORRUPTION_1); - DARK_PACT = ai->initSpell(DARK_PACT_1); - HOWL_OF_TERROR = ai->initSpell(HOWL_OF_TERROR_1); - FEAR = ai->initSpell(FEAR_1); + CORRUPTION = m_ai->initSpell(CORRUPTION_1); + DRAIN_SOUL = m_ai->initSpell(DRAIN_SOUL_1); + DRAIN_LIFE = m_ai->initSpell(DRAIN_LIFE_1); + DRAIN_MANA = m_ai->initSpell(DRAIN_MANA_1); + LIFE_TAP = m_ai->initSpell(LIFE_TAP_1); + UNSTABLE_AFFLICTION = m_ai->initSpell(UNSTABLE_AFFLICTION_1); + HAUNT = m_ai->initSpell(HAUNT_1); + SEED_OF_CORRUPTION = m_ai->initSpell(SEED_OF_CORRUPTION_1); + DARK_PACT = m_ai->initSpell(DARK_PACT_1); + HOWL_OF_TERROR = m_ai->initSpell(HOWL_OF_TERROR_1); + FEAR = m_ai->initSpell(FEAR_1); + SIPHON_LIFE = m_ai->initSpell(SIPHON_LIFE_1); // DEMONOLOGY - DEMON_SKIN = ai->initSpell(DEMON_SKIN_1); - DEMON_ARMOR = ai->initSpell(DEMON_ARMOR_1); - DEMONIC_EMPOWERMENT = ai->initSpell(DEMONIC_EMPOWERMENT_1); - FEL_ARMOR = ai->initSpell(FEL_ARMOR_1); - SHADOW_WARD = ai->initSpell(SHADOW_WARD_1); - SOULSHATTER = ai->initSpell(SOULSHATTER_1); - SOUL_LINK = ai->initSpell(SOUL_LINK_1); + BANISH = m_ai->initSpell(BANISH_1); + DEMON_SKIN = m_ai->initSpell(DEMON_SKIN_1); + DEMON_ARMOR = m_ai->initSpell(DEMON_ARMOR_1); + DEMONIC_EMPOWERMENT = m_ai->initSpell(DEMONIC_EMPOWERMENT_1); + FEL_ARMOR = m_ai->initSpell(FEL_ARMOR_1); + SHADOW_WARD = m_ai->initSpell(SHADOW_WARD_1); + SOULSHATTER = m_ai->initSpell(SOULSHATTER_1); + SOUL_LINK = m_ai->initSpell(SOUL_LINK_1); SOUL_LINK_AURA = 25228; // dummy aura applied, after spell SOUL_LINK - HEALTH_FUNNEL = ai->initSpell(HEALTH_FUNNEL_1); - DETECT_INVISIBILITY = ai->initSpell(DETECT_INVISIBILITY_1); - CREATE_FIRESTONE = ai->initSpell(CREATE_FIRESTONE_1); - CREATE_HEALTHSTONE = ai->initSpell(CREATE_HEALTHSTONE_1); - CREATE_SOULSTONE = ai->initSpell(CREATE_SOULSTONE_1); + HEALTH_FUNNEL = m_ai->initSpell(HEALTH_FUNNEL_1); + DETECT_INVISIBILITY = m_ai->initSpell(DETECT_INVISIBILITY_1); + CREATE_FIRESTONE = m_ai->initSpell(CREATE_FIRESTONE_1); + CREATE_HEALTHSTONE = m_ai->initSpell(CREATE_HEALTHSTONE_1); + CREATE_SOULSTONE = m_ai->initSpell(CREATE_SOULSTONE_1); + CREATE_SPELLSTONE = m_ai->initSpell(CREATE_SPELLSTONE_1); // demon summon - SUMMON_IMP = ai->initSpell(SUMMON_IMP_1); - SUMMON_VOIDWALKER = ai->initSpell(SUMMON_VOIDWALKER_1); - SUMMON_SUCCUBUS = ai->initSpell(SUMMON_SUCCUBUS_1); - SUMMON_FELHUNTER = ai->initSpell(SUMMON_FELHUNTER_1); - SUMMON_FELGUARD = ai->initSpell(SUMMON_FELGUARD_1); + SUMMON_IMP = m_ai->initSpell(SUMMON_IMP_1); + SUMMON_VOIDWALKER = m_ai->initSpell(SUMMON_VOIDWALKER_1); + SUMMON_SUCCUBUS = m_ai->initSpell(SUMMON_SUCCUBUS_1); + SUMMON_FELHUNTER = m_ai->initSpell(SUMMON_FELHUNTER_1); + SUMMON_FELGUARD = m_ai->initSpell(SUMMON_FELGUARD_1); // demon skills should be initialized on demons BLOOD_PACT = 0; // imp skill CONSUME_SHADOWS = 0; // voidwalker skill FEL_INTELLIGENCE = 0; // felhunter skill // RANGED COMBAT - SHOOT = ai->initSpell(SHOOT_3); + SHOOT = m_ai->initSpell(SHOOT_3); RECENTLY_BANDAGED = 11196; // first aid check // racial - ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); // blood elf - ESCAPE_ARTIST = ai->initSpell(ESCAPE_ARTIST_ALL); // gnome - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human - BLOOD_FURY = ai->initSpell(BLOOD_FURY_WARLOCK); // orc - WILL_OF_THE_FORSAKEN = ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead - - m_lastDemon = 0; - m_demonOfChoice = DEMON_IMP; - m_isTempImp = false; + ARCANE_TORRENT = m_ai->initSpell(ARCANE_TORRENT_MANA_CLASSES); // blood elf + ESCAPE_ARTIST = m_ai->initSpell(ESCAPE_ARTIST_ALL); // gnome + BLOOD_FURY = m_ai->initSpell(BLOOD_FURY_WARLOCK); // orc + WILL_OF_THE_FORSAKEN = m_ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead + + m_lastDemon = 0; + m_isTempImp = false; + m_CurrentCurse = 0; } PlayerbotWarlockAI::~PlayerbotWarlockAI() {} -void PlayerbotWarlockAI::DoNextCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuver(Unit* pTarget) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) + { + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + } + } + + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) + { + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); + } - switch (ai->GetScenarioType()) + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - if (SHADOW_BOLT > 0) - ai->CastSpell(SHADOW_BOLT); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoFirstCombatManeuverPVE(pTarget); break; } - // ------- Non Duel combat ---------- + return RETURN_NO_ACTION_ERROR; +} - //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob +CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/) +{ + m_CurrentCurse = 0; + return RETURN_NO_ACTION_OK; +} - Player *m_bot = GetPlayerBot(); - Unit* pVictim = pTarget->getVictim(); - Pet *pet = m_bot->GetPet(); +CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} + +CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + switch (m_ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoNextCombatManeuverPVE(pTarget); + break; + } - // Empower demon - if (pet && DEMONIC_EMPOWERMENT && !m_bot->HasSpellCooldown(DEMONIC_EMPOWERMENT)) - ai->CastSpell(DEMONIC_EMPOWERMENT); + return RETURN_NO_ACTION_ERROR; +} - // Use voidwalker sacrifice on low health if possible - if (ai->GetHealthPercent() < 50) - if (pet && pet->GetEntry() == DEMON_VOIDWALKER && SACRIFICE && !m_bot->HasAura(SACRIFICE)) - ai->CastPetSpell(SACRIFICE); +CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + //Unit* pVictim = pTarget->getVictim(); + bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); + Pet *pet = m_bot->GetPet(); + uint32 spec = m_bot->GetSpec(); + uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, nullptr); + + // Voidwalker is near death - sacrifice it for a shield + if (pet && pet->GetEntry() == DEMON_VOIDWALKER && SACRIFICE && !m_bot->HasAura(SACRIFICE) && pet->GetHealthPercent() < 10) + m_ai->CastPetSpell(SACRIFICE); // Use healthstone - if (ai->GetHealthPercent() < 30) + if (m_ai->GetHealthPercent() < 30) { - Item* healthStone = ai->FindConsumable(HEALTHSTONE_DISPLAYID); + Item* healthStone = m_ai->FindConsumable(HEALTHSTONE_DISPLAYID); if (healthStone) - ai->UseItem(healthStone); + m_ai->UseItem(healthStone); } - bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); - if (!meleeReach && ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED) + + // Voidwalker sacrifice gives shield - but you lose the pet (and it's DPS/tank) - use only as last resort for your own health! + if (m_ai->GetHealthPercent() < 20 && pet && pet->GetEntry() == DEMON_VOIDWALKER && SACRIFICE && !m_bot->HasAura(SACRIFICE)) + m_ai->CastPetSpell(SACRIFICE); + + if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && !meleeReach) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + // switch to melee if in melee range AND can't shoot OR have no ranged (wand) equipped + else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE + && meleeReach + && (SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true, true))) + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE); + + //Used to determine if this bot is highest on threat + Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); + if (newTarget && !m_ai->IsNeutralized(newTarget)) // TODO: && party has a tank { - // switch to ranged combat - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on. + if (newTarget->GetHealthPercent() > 25) + { + // If elite + if (m_ai->IsElite(newTarget)) + { + // let warlock pet handle it to win some time + Creature * pCreature = (Creature*) newTarget; + if (pet) + { + switch (pet->GetEntry()) + { + // taunt the elite and tank it + case DEMON_VOIDWALKER: + if (TORMENT && m_ai->CastPetSpell(TORMENT, newTarget)) + return RETURN_NO_ACTION_OK; + // maybe give it some love? + case DEMON_SUCCUBUS: + if (pCreature && pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_HUMANOID) + if (SEDUCTION && !newTarget->HasAura(SEDUCTION) && m_ai->CastPetSpell(SEDUCTION, newTarget)) + return RETURN_NO_ACTION_OK; + } + + } + // if aggroed mob is a demon or an elemental: banish it + if (pCreature && (pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_DEMON || pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_ELEMENTAL)) + { + if (BANISH && !newTarget->HasAura(BANISH) && CastSpell(BANISH, newTarget)) + return RETURN_CONTINUE; + } + + return RETURN_NO_ACTION_OK; // do nothing and pray tank gets aggro off you + } + + // Not an elite. You could insert FEAR here but in any PvE situation that's 90-95% likely + // to worsen the situation for the group. ... So please don't. + return CastSpell(SHOOT, pTarget); + } + } + + // Create soul shard (only on non-worldboss) + uint8 freeSpace = m_ai->GetFreeBagSpace(); + uint8 HPThreshold = (m_ai->IsElite(pTarget) ? 10 : 25); + if (DRAIN_SOUL && !m_ai->IsElite(pTarget, true) && pTarget->GetHealthPercent() < HPThreshold && m_ai->In_Reach(pTarget, DRAIN_SOUL) && + !pTarget->HasAura(DRAIN_SOUL) && (shardCount < MAX_SHARD_COUNT && freeSpace > 0) && CastSpell(DRAIN_SOUL, pTarget)) + { + m_ai->SetIgnoreUpdateTime(15); + return RETURN_CONTINUE; } - if (SHOOT > 0 && ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED && !m_bot->FindCurrentSpellBySpellId(SHOOT)) - ai->CastSpell(SHOOT, *pTarget); - //ai->TellMaster( "started auto shot." ); - else if (SHOOT > 0 && m_bot->FindCurrentSpellBySpellId(SHOOT)) - m_bot->InterruptNonMeleeSpells(true, SHOOT); + + if (pet && DARK_PACT && (100 * pet->GetPower(POWER_MANA) / pet->GetMaxPower(POWER_MANA)) > 10 && m_ai->GetManaPercent() <= 20) + if (m_ai->CastSpell(DARK_PACT, *m_bot)) + return RETURN_CONTINUE; + + // Mana check and replenishment + if (LIFE_TAP && m_ai->GetManaPercent() <= 20 && m_ai->GetHealthPercent() > 50) + if (m_ai->CastSpell(LIFE_TAP, *m_bot)) + return RETURN_CONTINUE; + + // HP, mana and aggro checks done + // Curse the target + if (CheckCurse(pTarget)) + return RETURN_CONTINUE; // Damage Spells - switch (SpellSequence) + if (spec) { - case SPELL_CURSES: - if (CURSE_OF_AGONY && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(SHADOWFLAME) && LastSpellCurse < 1) - { - ai->CastSpell(CURSE_OF_AGONY, *pTarget); - SpellSequence = SPELL_AFFLICTION; - ++LastSpellCurse; - break; - } - else if (CURSE_OF_THE_ELEMENTS && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_WEAKNESS) && LastSpellCurse < 2) - { - ai->CastSpell(CURSE_OF_THE_ELEMENTS, *pTarget); - SpellSequence = SPELL_AFFLICTION; - ++LastSpellCurse; - break; - } - else if (CURSE_OF_WEAKNESS && !pTarget->HasAura(CURSE_OF_WEAKNESS) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && LastSpellCurse < 3) - { - ai->CastSpell(CURSE_OF_WEAKNESS, *pTarget); - SpellSequence = SPELL_AFFLICTION; - ++LastSpellCurse; - break; - } - else if (CURSE_OF_TONGUES && !pTarget->HasAura(CURSE_OF_TONGUES) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_WEAKNESS) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && LastSpellCurse < 4) - { - ai->CastSpell(CURSE_OF_TONGUES, *pTarget); - SpellSequence = SPELL_AFFLICTION; - ++LastSpellCurse; + switch (spec) + { + case WARLOCK_SPEC_AFFLICTION: + if (CORRUPTION && m_ai->In_Reach(pTarget,CORRUPTION) && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget)) + return RETURN_CONTINUE; + if (IMMOLATE && m_ai->In_Reach(pTarget,IMMOLATE) && !pTarget->HasAura(IMMOLATE) && CastSpell(IMMOLATE, pTarget)) + return RETURN_CONTINUE; + if (SIPHON_LIFE > 0 && m_ai->In_Reach(pTarget,SIPHON_LIFE) && !pTarget->HasAura(SIPHON_LIFE) && CastSpell(SIPHON_LIFE, pTarget)) + return RETURN_CONTINUE; break; - } - LastSpellCurse = 0; - //SpellSequence = SPELL_AFFLICTION; - //break; - case SPELL_AFFLICTION: - if (LIFE_TAP && LastSpellAffliction < 1 && ai->GetManaPercent() <= 50 && ai->GetHealthPercent() > 50) - { - ai->CastSpell(LIFE_TAP, *m_bot); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (CORRUPTION && !pTarget->HasAura(CORRUPTION) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(SEED_OF_CORRUPTION) && LastSpellAffliction < 2) - { - ai->CastSpell(CORRUPTION, *pTarget); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (DRAIN_SOUL && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.40 && !pTarget->HasAura(DRAIN_SOUL) && LastSpellAffliction < 3) - { - ai->CastSpell(DRAIN_SOUL, *pTarget); - //ai->SetIgnoreUpdateTime(15); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (DRAIN_LIFE && LastSpellAffliction < 4 && !pTarget->HasAura(DRAIN_SOUL) && !pTarget->HasAura(SEED_OF_CORRUPTION) && !pTarget->HasAura(DRAIN_LIFE) && !pTarget->HasAura(DRAIN_MANA) && ai->GetHealthPercent() <= 70) - { - ai->CastSpell(DRAIN_LIFE, *pTarget); - //ai->SetIgnoreUpdateTime(5); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (UNSTABLE_AFFLICTION && LastSpellAffliction < 5 && !pTarget->HasAura(UNSTABLE_AFFLICTION) && !pTarget->HasAura(SHADOWFLAME)) - { - ai->CastSpell(UNSTABLE_AFFLICTION, *pTarget); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (HAUNT && LastSpellAffliction < 6 && !pTarget->HasAura(HAUNT)) - { - ai->CastSpell(HAUNT, *pTarget); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (SEED_OF_CORRUPTION && !pTarget->HasAura(SEED_OF_CORRUPTION) && LastSpellAffliction < 7) - { - ai->CastSpell(SEED_OF_CORRUPTION, *pTarget); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (HOWL_OF_TERROR && !pTarget->HasAura(HOWL_OF_TERROR) && ai->GetAttackerCount() > 3 && LastSpellAffliction < 8) - { - ai->CastSpell(HOWL_OF_TERROR, *pTarget); - ai->TellMaster("casting howl of terror!"); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - else if (FEAR && !pTarget->HasAura(FEAR) && pVictim == m_bot && ai->GetAttackerCount() >= 2 && LastSpellAffliction < 9) - { - ai->CastSpell(FEAR, *pTarget); - //ai->TellMaster("casting fear!"); - //ai->SetIgnoreUpdateTime(1.5); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; + case WARLOCK_SPEC_DEMONOLOGY: + if (CORRUPTION && m_ai->In_Reach(pTarget,CORRUPTION) && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget)) + return RETURN_CONTINUE; + if (IMMOLATE && m_ai->In_Reach(pTarget,IMMOLATE) && !pTarget->HasAura(IMMOLATE) && CastSpell(IMMOLATE, pTarget)) + return RETURN_CONTINUE; break; - } - else if ((pet) - && (DARK_PACT > 0 && ai->GetManaPercent() <= 50 && LastSpellAffliction < 10 && pet->GetPower(POWER_MANA) > 0)) - { - ai->CastSpell(DARK_PACT, *m_bot); - SpellSequence = SPELL_DESTRUCTION; - ++LastSpellAffliction; - break; - } - LastSpellAffliction = 0; - //SpellSequence = SPELL_DESTRUCTION; - //break; - case SPELL_DESTRUCTION: - if (SHADOWFURY && LastSpellDestruction < 1 && !pTarget->HasAura(SHADOWFURY)) - { - ai->CastSpell(SHADOWFURY, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (SHADOW_BOLT && LastSpellDestruction < 2) - { - ai->CastSpell(SHADOW_BOLT, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (RAIN_OF_FIRE && LastSpellDestruction < 3 && ai->GetAttackerCount() >= 3) - { - ai->CastSpell(RAIN_OF_FIRE, *pTarget); - //ai->TellMaster("casting rain of fire!"); - //ai->SetIgnoreUpdateTime(8); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (SHADOWFLAME && !pTarget->HasAura(SHADOWFLAME) && LastSpellDestruction < 4) - { - ai->CastSpell(SHADOWFLAME, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (IMMOLATE && !pTarget->HasAura(IMMOLATE) && !pTarget->HasAura(SHADOWFLAME) && LastSpellDestruction < 5) - { - ai->CastSpell(IMMOLATE, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (CONFLAGRATE && LastSpellDestruction < 6) - { - ai->CastSpell(CONFLAGRATE, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (INCINERATE && LastSpellDestruction < 7) - { - ai->CastSpell(INCINERATE, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (SEARING_PAIN && LastSpellDestruction < 8) - { - ai->CastSpell(SEARING_PAIN, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; + case WARLOCK_SPEC_DESTRUCTION: + if (SHADOWBURN && pTarget->GetHealthPercent() < (HPThreshold / 2.0) && m_ai->In_Reach(pTarget, SHADOWBURN) && !pTarget->HasAura(SHADOWBURN) && CastSpell(SHADOWBURN, pTarget)) + return RETURN_CONTINUE; + if (CORRUPTION && m_ai->In_Reach(pTarget,CORRUPTION) && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget)) + return RETURN_CONTINUE; + if (IMMOLATE && m_ai->In_Reach(pTarget,IMMOLATE) && !pTarget->HasAura(IMMOLATE) && CastSpell(IMMOLATE, pTarget)) + return RETURN_CONTINUE; + if (CONFLAGRATE && m_ai->In_Reach(pTarget,CONFLAGRATE) && pTarget->HasAura(IMMOLATE) && !m_bot->HasSpellCooldown(CONFLAGRATE) && CastSpell(CONFLAGRATE, pTarget)) + return RETURN_CONTINUE; break; - } - else if (SOUL_FIRE && LastSpellDestruction < 9) - { - ai->CastSpell(SOUL_FIRE, *pTarget); - //ai->SetIgnoreUpdateTime(6); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; - } - else if (CHAOS_BOLT && LastSpellDestruction < 10) + } + + // Shadow bolt is common to all specs + if (SHADOW_BOLT && m_ai->In_Reach(pTarget,SHADOW_BOLT) && CastSpell(SHADOW_BOLT, pTarget)) + return RETURN_CONTINUE; + + // Default: shoot with wand + return CastSpell(SHOOT, pTarget); + + return RETURN_NO_ACTION_OK; + + //if (DRAIN_LIFE && LastSpellAffliction < 4 && !pTarget->HasAura(DRAIN_SOUL) && !pTarget->HasAura(DRAIN_LIFE) && !pTarget->HasAura(DRAIN_MANA) && m_ai->GetHealthPercent() <= 70) + // m_ai->CastSpell(DRAIN_LIFE, *pTarget); + // //m_ai->SetIgnoreUpdateTime(5); + //else if (HOWL_OF_TERROR && !pTarget->HasAura(HOWL_OF_TERROR) && m_ai->GetAttackerCount() > 3 && LastSpellAffliction < 8) + // m_ai->CastSpell(HOWL_OF_TERROR, *pTarget); + // m_ai->TellMaster("casting howl of terror!"); + //else if (FEAR && !pTarget->HasAura(FEAR) && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && LastSpellAffliction < 9) + // m_ai->CastSpell(FEAR, *pTarget); + // //m_ai->TellMaster("casting fear!"); + // //m_ai->SetIgnoreUpdateTime(1.5); + //else if (RAIN_OF_FIRE && LastSpellDestruction < 3 && m_ai->GetAttackerCount() >= 3) + // m_ai->CastSpell(RAIN_OF_FIRE, *pTarget); + // //m_ai->TellMaster("casting rain of fire!"); + // //m_ai->SetIgnoreUpdateTime(8); + //else if (SEARING_PAIN && LastSpellDestruction < 8) + // m_ai->CastSpell(SEARING_PAIN, *pTarget); + //else if (SOUL_FIRE && LastSpellDestruction < 9) + // m_ai->CastSpell(SOUL_FIRE, *pTarget); + // //m_ai->SetIgnoreUpdateTime(6); + //else if (HELLFIRE && LastSpellDestruction < 12 && !m_bot->HasAura(HELLFIRE) && m_ai->GetAttackerCount() >= 5 && m_ai->GetHealthPercent() >= 50) + // m_ai->CastSpell(HELLFIRE); + // m_ai->TellMaster("casting hellfire!"); + // //m_ai->SetIgnoreUpdateTime(15); + } + + // No spec due to low level OR no spell found yet + if (CORRUPTION && m_ai->In_Reach(pTarget,CORRUPTION) && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget)) + return RETURN_CONTINUE; + if (IMMOLATE && m_ai->In_Reach(pTarget,IMMOLATE) && !pTarget->HasAura(IMMOLATE) && CastSpell(IMMOLATE, pTarget)) + return RETURN_CONTINUE; + if (SHADOW_BOLT && m_ai->In_Reach(pTarget,SHADOW_BOLT)) + return CastSpell(SHADOW_BOLT, pTarget); + + // Default: shoot with wand + return CastSpell(SHOOT, pTarget); + + return RETURN_NO_ACTION_OK; +} // end DoNextCombatManeuver + +CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + if (FEAR && m_ai->In_Reach(pTarget,FEAR) && m_ai->CastSpell(FEAR, *pTarget)) + return RETURN_CONTINUE; + if (SHADOW_BOLT && m_ai->In_Reach(pTarget,SHADOW_BOLT) && m_ai->CastSpell(SHADOW_BOLT)) + return RETURN_CONTINUE; + + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} + +// Decision tree for putting a curse on the current target +bool PlayerbotWarlockAI::CheckCurse(Unit* pTarget) +{ + Creature * pCreature = (Creature*) pTarget; + uint32 CurseToCast = 0; + + // Prevent low health humanoid from fleeing or fleeing too fast + // Curse of Exhaustion first to avoid increasing damage output on tank + if (pCreature && pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_HUMANOID && pTarget->GetHealthPercent() < 20 && !pCreature->IsWorldBoss()) + { + if (CURSE_OF_EXHAUSTION && m_ai->In_Reach(pTarget,CURSE_OF_EXHAUSTION) && !pTarget->HasAura(CURSE_OF_EXHAUSTION)) + { + if (AMPLIFY_CURSE && !m_bot->HasSpellCooldown(AMPLIFY_CURSE)) + CastSpell(AMPLIFY_CURSE, m_bot); + + if (CastSpell(CURSE_OF_EXHAUSTION, pTarget)) { - ai->CastSpell(CHAOS_BOLT, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; + m_CurrentCurse = CURSE_OF_EXHAUSTION; + return true; } - else if (SHADOWBURN && LastSpellDestruction < 11 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && !pTarget->HasAura(SHADOWBURN)) + } + else if (CURSE_OF_RECKLESSNESS && m_ai->In_Reach(pTarget,CURSE_OF_RECKLESSNESS) && !pTarget->HasAura(CURSE_OF_RECKLESSNESS) && !pTarget->HasAura(CURSE_OF_EXHAUSTION) && CastSpell(CURSE_OF_RECKLESSNESS, pTarget)) + { + m_CurrentCurse = CURSE_OF_RECKLESSNESS; + return true; + } + } + + // If bot already put a curse and curse is still active on target: no need to go further + if (m_CurrentCurse > 0 && pTarget->HasAura(m_CurrentCurse)) + return false; + + // No curse or effect worn off: choose again which curse to use + + // Target is a boss + if (pCreature && pCreature->IsWorldBoss()) + { + if (m_bot->GetGroup()) + { + uint8 mages = 0; + uint8 warlocks = 1; + Group::MemberSlotList const& groupSlot = m_bot->GetGroup()->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *groupMember = sObjectMgr.GetPlayer(itr->guid); + if (!groupMember || !groupMember->isAlive()) + continue; + switch (groupMember->getClass()) + { + case CLASS_WARLOCK: + warlocks++; + continue; + case CLASS_MAGE: + mages++; + continue; + } + } + if (warlocks > 1 && warlocks > mages) + CurseToCast = CURSE_OF_SHADOW; + else if (mages > warlocks) + CurseToCast = CURSE_OF_THE_ELEMENTS; + else + CurseToCast = CURSE_OF_AGONY; + } + // If target is not elite, no need to put a curse useful + // in the long run: go for direct damage + } else if (!m_ai->IsElite(pTarget)) + CurseToCast = CURSE_OF_AGONY; + // Enemy elite mages have low health but can cast dangerous spells: group safety before bot DPS + else if (pCreature && pCreature->GetCreatureInfo()->UnitClass == 8) + CurseToCast = CURSE_OF_TONGUES; + // Default case: Curse of Agony + else + CurseToCast = CURSE_OF_AGONY; + + // Try to curse the target with the selected curse + if (CurseToCast && m_ai->In_Reach(pTarget,CurseToCast) && !pTarget->HasAura(CurseToCast)) + { + if (CurseToCast == CURSE_OF_AGONY) + if (AMPLIFY_CURSE && !m_bot->HasSpellCooldown(AMPLIFY_CURSE)) + CastSpell(AMPLIFY_CURSE, m_bot); + + if (CastSpell(CurseToCast, pTarget)) + { + m_CurrentCurse = CurseToCast; + return true; + } + } + // else: go for Curse of Agony + else if (CURSE_OF_AGONY && m_ai->In_Reach(pTarget,CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_AGONY)) + { + if (AMPLIFY_CURSE && !m_bot->HasSpellCooldown(AMPLIFY_CURSE)) + CastSpell(AMPLIFY_CURSE, m_bot); + + if (CastSpell(CURSE_OF_AGONY, pTarget)) + { + m_CurrentCurse = CURSE_OF_AGONY; + return true; + } + } + // else: go for Curse of Weakness + else if (CURSE_OF_WEAKNESS && !pTarget->HasAura(CURSE_OF_WEAKNESS) && !pTarget->HasAura(CURSE_OF_AGONY)) + { + if (AMPLIFY_CURSE && !m_bot->HasSpellCooldown(AMPLIFY_CURSE)) + CastSpell(AMPLIFY_CURSE, m_bot); + + if (CastSpell(CURSE_OF_WEAKNESS, pTarget)) + { + m_CurrentCurse = CURSE_OF_WEAKNESS; + return true; + } + } + else + return false; +} + +void PlayerbotWarlockAI::CheckDemon() +{ + uint32 spec = m_bot->GetSpec(); + uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, nullptr); + Pet *pet = m_bot->GetPet(); + uint32 demonOfChoice; + + // If pet other than imp is active: return + if (pet && pet->GetEntry() != DEMON_IMP) + return; + + //Assign demon of choice + if (spec == WARLOCK_SPEC_AFFLICTION) + demonOfChoice = DEMON_FELHUNTER; + else if (spec == WARLOCK_SPEC_DEMONOLOGY) + demonOfChoice = DEMON_SUCCUBUS; + else if (spec == WARLOCK_SPEC_DESTRUCTION) + demonOfChoice = DEMON_IMP; + + // Summon demon + if (!pet || m_isTempImp) + { + uint32 summonSpellId; + if (demonOfChoice != DEMON_IMP && shardCount > 0) + { + switch (demonOfChoice) { - ai->CastSpell(SHADOWBURN, *pTarget); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; + case DEMON_VOIDWALKER: + summonSpellId = SUMMON_VOIDWALKER; + break; + + case DEMON_FELHUNTER: + summonSpellId = SUMMON_FELHUNTER; + break; + + case DEMON_SUCCUBUS: + summonSpellId = SUMMON_SUCCUBUS; + break; + + default: + summonSpellId = 0; } - else if (HELLFIRE && LastSpellDestruction < 12 && !m_bot->HasAura(HELLFIRE) && ai->GetAttackerCount() >= 5 && ai->GetHealthPercent() >= 50) + + if (summonSpellId && m_ai->CastSpell(summonSpellId)) { - ai->CastSpell(HELLFIRE); - ai->TellMaster("casting hellfire!"); - //ai->SetIgnoreUpdateTime(15); - SpellSequence = SPELL_CURSES; - ++LastSpellDestruction; - break; + //m_ai->TellMaster("Summoning favorite demon..."); + m_isTempImp = false; + return; } + } + + if (!pet && SUMMON_IMP && m_ai->CastSpell(SUMMON_IMP)) + { + if (demonOfChoice != DEMON_IMP) + m_isTempImp = true; else - { - LastSpellDestruction = 0; - SpellSequence = SPELL_CURSES; - } + m_isTempImp = false; + + //m_ai->TellMaster("Summoning Imp..."); + return; + } } -} // end DoNextCombatManeuver + + return; +} void PlayerbotWarlockAI::DoNonCombatActions() { - SpellSequence = SPELL_CURSES; - - PlayerbotAI *ai = GetAI(); - Player * m_bot = GetPlayerBot(); - if (!ai || !m_bot) - return; + if (!m_ai) return; + if (!m_bot) return; + //uint32 spec = m_bot->GetSpec(); Pet *pet = m_bot->GetPet(); // Initialize pet spells @@ -364,216 +548,164 @@ void PlayerbotWarlockAI::DoNonCombatActions() switch (pet->GetEntry()) { case DEMON_IMP: - { - BLOOD_PACT = ai->initPetSpell(BLOOD_PACT_ICON); - FIREBOLT = ai->initPetSpell(FIREBOLT_ICON); - FIRE_SHIELD = ai->initPetSpell(FIRE_SHIELD_ICON); + BLOOD_PACT = m_ai->initPetSpell(BLOOD_PACT_ICON); + FIREBOLT = m_ai->initPetSpell(FIREBOLT_ICON); + FIRE_SHIELD = m_ai->initPetSpell(FIRE_SHIELD_ICON); break; - } + case DEMON_VOIDWALKER: - { - CONSUME_SHADOWS = ai->initPetSpell(CONSUME_SHADOWS_ICON); - SACRIFICE = ai->initPetSpell(SACRIFICE_ICON); - SUFFERING = ai->initPetSpell(SUFFERING_ICON); - TORMENT = ai->initPetSpell(TORMENT_ICON); + CONSUME_SHADOWS = m_ai->initPetSpell(CONSUME_SHADOWS_ICON); + SACRIFICE = m_ai->initPetSpell(SACRIFICE_ICON); + SUFFERING = m_ai->initPetSpell(SUFFERING_ICON); + TORMENT = m_ai->initPetSpell(TORMENT_ICON); break; - } + case DEMON_SUCCUBUS: - { - LASH_OF_PAIN = ai->initPetSpell(LASH_OF_PAIN_ICON); - SEDUCTION = ai->initPetSpell(SEDUCTION_ICON); - SOOTHING_KISS = ai->initPetSpell(SOOTHING_KISS_ICON); + LASH_OF_PAIN = m_ai->initPetSpell(LASH_OF_PAIN_ICON); + SEDUCTION = m_ai->initPetSpell(SEDUCTION_ICON); + SOOTHING_KISS = m_ai->initPetSpell(SOOTHING_KISS_ICON); break; - } + case DEMON_FELHUNTER: - { - DEVOUR_MAGIC = ai->initPetSpell(DEVOUR_MAGIC_ICON); - FEL_INTELLIGENCE = ai->initPetSpell(FEL_INTELLIGENCE_ICON); - SHADOW_BITE = ai->initPetSpell(SHADOW_BITE_ICON); - SPELL_LOCK = ai->initPetSpell(SPELL_LOCK_ICON); + DEVOUR_MAGIC = m_ai->initPetSpell(DEVOUR_MAGIC_ICON); + SPELL_LOCK = m_ai->initPetSpell(SPELL_LOCK_ICON); break; - } - case DEMON_FELGUARD: - { - ANGUISH = ai->initPetSpell(ANGUISH_ICON); - CLEAVE = ai->initPetSpell(CLEAVE_ICON); - INTERCEPT = ai->initPetSpell(INTERCEPT_ICON); - break; - } } m_lastDemon = pet->GetEntry(); - - if (!m_isTempImp) - m_demonOfChoice = pet->GetEntry(); } // Destroy extra soul shards uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, nullptr); - uint8 freeSpace = ai->GetFreeBagSpace(); + uint8 freeSpace = m_ai->GetFreeBagSpace(); if (shardCount > MAX_SHARD_COUNT || (freeSpace == 0 && shardCount > 1)) m_bot->DestroyItemCount(SOUL_SHARD, shardCount > MAX_SHARD_COUNT ? shardCount - MAX_SHARD_COUNT : 1, true, false); - // buff myself DEMON_SKIN, DEMON_ARMOR, FEL_ARMOR - if (FEL_ARMOR) + // buff myself DEMON_SKIN, DEMON_ARMOR, FEL_ARMOR - Strongest one available is chosen + if (DEMON_ARMOR) { - if (ai->SelfBuff(FEL_ARMOR)) - return; - } - else if (DEMON_ARMOR) - { - if (ai->SelfBuff(DEMON_ARMOR)) + if (m_ai->SelfBuff(DEMON_ARMOR)) return; } else if (DEMON_SKIN) - if (ai->SelfBuff(DEMON_SKIN)) + if (m_ai->SelfBuff(DEMON_SKIN)) return; // healthstone creation if (CREATE_HEALTHSTONE && shardCount > 0) { - Item* const healthStone = ai->FindConsumable(HEALTHSTONE_DISPLAYID); - if (!healthStone && ai->CastSpell(CREATE_HEALTHSTONE)) + Item* const healthStone = m_ai->FindConsumable(HEALTHSTONE_DISPLAYID); + if (!healthStone && m_ai->CastSpell(CREATE_HEALTHSTONE)) return; } // soulstone creation and use if (CREATE_SOULSTONE) { - Item* soulStone = ai->FindConsumable(SOULSTONE_DISPLAYID); + Item* soulStone = m_ai->FindConsumable(SOULSTONE_DISPLAYID); if (!soulStone) { - if (shardCount > 0 && !m_bot->HasSpellCooldown(CREATE_SOULSTONE) && ai->CastSpell(CREATE_SOULSTONE)) + if (shardCount > 0 && !m_bot->HasSpellCooldown(CREATE_SOULSTONE) && m_ai->CastSpell(CREATE_SOULSTONE)) return; } else { uint32 soulStoneSpell = soulStone->GetProto()->Spells[0].SpellId; - Player * master = GetMaster(); + Player* master = GetMaster(); if (!master->HasAura(soulStoneSpell) && !m_bot->HasSpellCooldown(soulStoneSpell)) { - ai->UseItem(soulStone, master); + // TODO: first choice: healer. Second choice: anyone else with revive spell. Third choice: self or master. + m_ai->UseItem(soulStone, master); return; } } } - // firestone creation and use - Item* const weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) - { - Item* const stone = ai->FindConsumable(FIRESTONE_DISPLAYID); - if (!stone) - { - if (CREATE_FIRESTONE && shardCount > 0 && ai->CastSpell(CREATE_FIRESTONE)) - return; - } - else - { - ai->UseItem(stone, EQUIPMENT_SLOT_MAINHAND); - return; - } - } - - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); - - // mana check - if (pet && DARK_PACT && pet->GetPower(POWER_MANA) > 0 && ai->GetManaPercent() <= 50) - if (ai->CastSpell(DARK_PACT, *m_bot)) + // hp/mana check + if (pet && DARK_PACT && (100 * pet->GetPower(POWER_MANA) / pet->GetMaxPower(POWER_MANA)) > 40 && m_ai->GetManaPercent() <= 60) + if (m_ai->CastSpell(DARK_PACT, *m_bot)) return; - if (LIFE_TAP && ai->GetManaPercent() <= 50 && ai->GetHealthPercent() > 50) - if (ai->CastSpell(LIFE_TAP, *m_bot)) + if (LIFE_TAP && m_ai->GetManaPercent() <= 80 && m_ai->GetHealthPercent() > 50) + if (m_ai->CastSpell(LIFE_TAP, *m_bot)) return; - if (ai->GetManaPercent() < 25) + // Do not waste time/soul shards to create spellstone or firestone + // if two-handed weapon (staff) or off-hand item are already equipped + // Spellstone creation and use (Spellstone dominates firestone completely as I understand it) + Item* const weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + Item* const offweapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (weapon && !offweapon && weapon->GetProto()->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) { - Item* pItem = ai->FindDrink(); - if (pItem) + Item* const stone = m_ai->FindConsumable(SPELLSTONE_DISPLAYID); + Item* const stone2 = m_ai->FindConsumable(FIRESTONE_DISPLAYID); + uint8 spellstone_count = m_bot->GetItemCount(SPELLSTONE, false, nullptr); + if (spellstone_count == 0) + spellstone_count = m_bot->GetItemCount(GREATER_SPELLSTONE, false, nullptr); + if (spellstone_count == 0) + spellstone_count = m_bot->GetItemCount(MAJOR_SPELLSTONE, false, nullptr); + uint8 firestone_count = m_bot->GetItemCount(LESSER_FIRESTONE, false, nullptr); + if (firestone_count == 0) + firestone_count = m_bot->GetItemCount(FIRESTONE, false, nullptr); + if (firestone_count == 0) + firestone_count = m_bot->GetItemCount(GREATER_FIRESTONE, false, nullptr); + if (firestone_count == 0) + firestone_count = m_bot->GetItemCount(MAJOR_FIRESTONE, false, nullptr); + if (spellstone_count == 0 && firestone_count == 0) { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); - return; + if (CREATE_SPELLSTONE && shardCount > 0 && m_ai->CastSpell(CREATE_SPELLSTONE)) + return; + else if (CREATE_SPELLSTONE == 0 && CREATE_FIRESTONE > 0 && shardCount > 0 && m_ai->CastSpell(CREATE_FIRESTONE)) + return; } - } - - // hp check - if (ai->GetHealthPercent() < 30) - { - Item* pItem = ai->FindFood(); - if (pItem) + else if (stone) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); + m_ai->UseItem(stone, EQUIPMENT_SLOT_OFFHAND); return; } - } - - if (ai->GetHealthPercent() < 50 && !m_bot->HasAura(RECENTLY_BANDAGED)) - { - Item* fItem = ai->FindBandage(); - if (fItem) + else { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); + m_ai->UseItem(stone2, EQUIPMENT_SLOT_OFFHAND); return; } } + if (EatDrinkBandage()) + return; + //Heal Voidwalker if (pet && pet->GetEntry() == DEMON_VOIDWALKER && CONSUME_SHADOWS && pet->GetHealthPercent() < 75 && !pet->HasAura(CONSUME_SHADOWS)) - ai->CastPetSpell(CONSUME_SHADOWS); + m_ai->CastPetSpell(CONSUME_SHADOWS); - // Summon demon - if (!pet || m_isTempImp) - { - uint32 summonSpellId; - if (m_demonOfChoice != DEMON_IMP && shardCount > 0) - { - switch (m_demonOfChoice) - { - case DEMON_VOIDWALKER: - summonSpellId = SUMMON_VOIDWALKER; - break; - case DEMON_FELGUARD: - summonSpellId = SUMMON_FELGUARD; - break; - case DEMON_FELHUNTER: - summonSpellId = SUMMON_FELHUNTER; - break; - case DEMON_SUCCUBUS: - summonSpellId = SUMMON_SUCCUBUS; - break; - default: - summonSpellId = 0; - } - if (ai->CastSpell(summonSpellId)) - { - ai->TellMaster("Summoning favorite demon..."); - m_isTempImp = false; - return; - } - } - else if (!pet && SUMMON_IMP && ai->CastSpell(SUMMON_IMP)) - { - if (m_demonOfChoice != DEMON_IMP) - m_isTempImp = true; - - ai->TellMaster("Summoning Imp..."); - return; - } - } + CheckDemon(); // Soul link demon - if (pet && SOUL_LINK && !m_bot->HasAura(SOUL_LINK_AURA) && ai->CastSpell(SOUL_LINK, *m_bot)) + if (pet && SOUL_LINK && !m_bot->HasAura(SOUL_LINK_AURA) && m_ai->CastSpell(SOUL_LINK, *m_bot)) return; // Check demon buffs - if (pet && pet->GetEntry() == DEMON_IMP && BLOOD_PACT && !m_bot->HasAura(BLOOD_PACT) && ai->CastPetSpell(BLOOD_PACT)) + if (pet && pet->GetEntry() == DEMON_IMP && BLOOD_PACT && !m_bot->HasAura(BLOOD_PACT) && m_ai->CastPetSpell(BLOOD_PACT)) return; +} // end DoNonCombatActions - if (pet && pet->GetEntry() == DEMON_FELHUNTER && FEL_INTELLIGENCE && !m_bot->HasAura(FEL_INTELLIGENCE) && ai->CastPetSpell(FEL_INTELLIGENCE)) - return; +// Return to UpdateAI the spellId usable to neutralize a target with creaturetype +uint32 PlayerbotWarlockAI::Neutralize(uint8 creatureType) +{ + if (!m_bot) return 0; + if (!m_ai) return 0; + if (!creatureType) return 0; -} // end DoNonCombatActions + // TODO: add a way to handle spell cast by pet like Seduction + if (creatureType != CREATURE_TYPE_DEMON && creatureType != CREATURE_TYPE_ELEMENTAL) + { + m_ai->TellMaster("I can't banish that target."); + return 0; + } + + if (BANISH) + return BANISH; + else + return 0; + + return 0; +} \ No newline at end of file diff --git a/src/game/playerbot/PlayerbotWarlockAI.h b/src/game/playerbot/PlayerbotWarlockAI.h index 81a2240b4..cfae4ec36 100644 --- a/src/game/playerbot/PlayerbotWarlockAI.h +++ b/src/game/playerbot/PlayerbotWarlockAI.h @@ -4,7 +4,14 @@ #include "PlayerbotClassAI.h" #define SOUL_SHARD 6265 -#define MAX_SHARD_COUNT 4 // Maximum soul shard count bot should keep +#define SPELLSTONE 5522 +#define GREATER_SPELLSTONE 13602 +#define MAJOR_SPELLSTONE 13603 +#define LESSER_FIRESTONE 1254 +#define FIRESTONE 13699 +#define GREATER_FIRESTONE 13700 +#define MAJOR_FIRESTONE 13701 +#define MAX_SHARD_COUNT 15 // Maximum soul shard count bot should keep enum { @@ -115,7 +122,8 @@ enum WarlockSpells SHADOWBURN_1 = 17877, SHADOWFLAME_1 = 47897, SHADOWFURY_1 = 30283, - SHOOT_3 = 5019, + SHOOT_3 = 5019, + SIPHON_LIFE_1 = 18265, SOUL_FIRE_1 = 6353, SOUL_LINK_1 = 19028, SOULSHATTER_1 = 29858, @@ -136,7 +144,9 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI virtual ~PlayerbotWarlockAI(); // all combat actions go here - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + uint32 Neutralize(uint8 creatureType); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); @@ -145,20 +155,33 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI //void BuffPlayer(Player *target); private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + + CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = nullptr) { return CastSpellWand(nextAction, pTarget, SHOOT); } + + bool CheckCurse(Unit* pTarget); + void CheckDemon(); // CURSES uint32 CURSE_OF_WEAKNESS, CURSE_OF_AGONY, CURSE_OF_EXHAUSTION, + CURSE_OF_RECKLESSNESS, + CURSE_OF_SHADOW, CURSE_OF_TONGUES, CURSE_OF_THE_ELEMENTS, CURSE_OF_DOOM; + // ranged uint32 SHOOT; // AFFLICTION - uint32 CORRUPTION, - DRAIN_SOUL, + uint32 AMPLIFY_CURSE, + CORRUPTION, + DRAIN_SOUL, DRAIN_LIFE, DRAIN_MANA, LIFE_TAP, @@ -167,7 +190,8 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI SEED_OF_CORRUPTION, DARK_PACT, HOWL_OF_TERROR, - FEAR; + FEAR, + SIPHON_LIFE; // DESTRUCTION uint32 SHADOW_BOLT, @@ -184,19 +208,22 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI SHADOWBURN; // DEMONOLOGY - uint32 DEMON_SKIN, + uint32 BANISH, + DEMON_SKIN, DEMON_ARMOR, DEMONIC_EMPOWERMENT, SHADOW_WARD, FEL_ARMOR, SOULSHATTER, + ENSLAVE_DEMON, SOUL_LINK, SOUL_LINK_AURA, HEALTH_FUNNEL, DETECT_INVISIBILITY, CREATE_FIRESTONE, CREATE_SOULSTONE, - CREATE_HEALTHSTONE; + CREATE_HEALTHSTONE, + CREATE_SPELLSTONE; // DEMON SUMMON uint32 SUMMON_IMP, @@ -232,7 +259,6 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, - EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, @@ -245,8 +271,8 @@ class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI LastSpellDestruction; uint32 m_lastDemon; // Last demon entry used for spell initialization - uint32 m_demonOfChoice; // Preferred demon entry bool m_isTempImp; // True if imp summoned temporarily until soul shard acquired for demon of choice. + uint32 m_CurrentCurse; // Curse currently active on bot's target }; #endif diff --git a/src/game/playerbot/PlayerbotWarriorAI.cpp b/src/game/playerbot/PlayerbotWarriorAI.cpp index da743cba2..99bb2e624 100644 --- a/src/game/playerbot/PlayerbotWarriorAI.cpp +++ b/src/game/playerbot/PlayerbotWarriorAI.cpp @@ -10,334 +10,621 @@ class PlayerbotAI; PlayerbotWarriorAI::PlayerbotWarriorAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) { - BATTLE_STANCE = ai->initSpell(BATTLE_STANCE_1); //ARMS - CHARGE = ai->initSpell(CHARGE_1); //ARMS - OVERPOWER = ai->initSpell(OVERPOWER_1); // ARMS - HEROIC_STRIKE = ai->initSpell(HEROIC_STRIKE_1); //ARMS - REND = ai->initSpell(REND_1); //ARMS - THUNDER_CLAP = ai->initSpell(THUNDER_CLAP_1); //ARMS - HAMSTRING = ai->initSpell(HAMSTRING_1); //ARMS - MOCKING_BLOW = ai->initSpell(MOCKING_BLOW_1); //ARMS - RETALIATION = ai->initSpell(RETALIATION_1); //ARMS - SWEEPING_STRIKES = ai->initSpell(SWEEPING_STRIKES_1); //ARMS - MORTAL_STRIKE = ai->initSpell(MORTAL_STRIKE_1); //ARMS - BLADESTORM = ai->initSpell(BLADESTORM_1); //ARMS - HEROIC_THROW = ai->initSpell(HEROIC_THROW_1); //ARMS - SHATTERING_THROW = ai->initSpell(SHATTERING_THROW_1); //ARMS - BLOODRAGE = ai->initSpell(BLOODRAGE_1); //PROTECTION - DEFENSIVE_STANCE = ai->initSpell(DEFENSIVE_STANCE_1); //PROTECTION - DEVASTATE = ai->initSpell(DEVASTATE_1); //PROTECTION - SUNDER_ARMOR = ai->initSpell(SUNDER_ARMOR_1); //PROTECTION - TAUNT = ai->initSpell(TAUNT_1); //PROTECTION - SHIELD_BASH = ai->initSpell(SHIELD_BASH_1); //PROTECTION - REVENGE = ai->initSpell(REVENGE_1); //PROTECTION - SHIELD_BLOCK = ai->initSpell(SHIELD_BLOCK_1); //PROTECTION - DISARM = ai->initSpell(DISARM_1); //PROTECTION - SHIELD_WALL = ai->initSpell(SHIELD_WALL_1); //PROTECTION - SHIELD_SLAM = ai->initSpell(SHIELD_SLAM_1); //PROTECTION - VIGILANCE = ai->initSpell(VIGILANCE_1); //PROTECTION - DEVASTATE = ai->initSpell(DEVASTATE_1); //PROTECTION - SHOCKWAVE = ai->initSpell(SHOCKWAVE_1); //PROTECTION - CONCUSSION_BLOW = ai->initSpell(CONCUSSION_BLOW_1); //PROTECTION - SPELL_REFLECTION = ai->initSpell(SPELL_REFLECTION_1); //PROTECTION - LAST_STAND = ai->initSpell(LAST_STAND_1); //PROTECTION - BATTLE_SHOUT = ai->initSpell(BATTLE_SHOUT_1); //FURY - DEMORALIZING_SHOUT = ai->initSpell(DEMORALIZING_SHOUT_1); //FURY - CLEAVE = ai->initSpell(CLEAVE_1); //FURY - INTIMIDATING_SHOUT = ai->initSpell(INTIMIDATING_SHOUT_1); //FURY - EXECUTE = ai->initSpell(EXECUTE_1); //FURY - CHALLENGING_SHOUT = ai->initSpell(CHALLENGING_SHOUT_1); //FURY - SLAM = ai->initSpell(SLAM_1); //FURY - BERSERKER_STANCE = ai->initSpell(BERSERKER_STANCE_1); //FURY - INTERCEPT = ai->initSpell(INTERCEPT_1); //FURY - DEATH_WISH = ai->initSpell(DEATH_WISH_1); //FURY - BERSERKER_RAGE = ai->initSpell(BERSERKER_RAGE_1); //FURY - WHIRLWIND = ai->initSpell(WHIRLWIND_1); //FURY - PUMMEL = ai->initSpell(PUMMEL_1); //FURY - BLOODTHIRST = ai->initSpell(BLOODTHIRST_1); //FURY - RECKLESSNESS = ai->initSpell(RECKLESSNESS_1); //FURY + SHOOT_BOW = m_ai->initSpell(SHOOT_BOW_1); // GENERAL + SHOOT_GUN = m_ai->initSpell(SHOOT_GUN_1); // GENERAL + SHOOT_XBOW = m_ai->initSpell(SHOOT_XBOW_1); // GENERAL + + BATTLE_STANCE = m_ai->initSpell(BATTLE_STANCE_1); //ARMS + CHARGE = m_ai->initSpell(CHARGE_1); //ARMS + OVERPOWER = m_ai->initSpell(OVERPOWER_1); // ARMS + HEROIC_STRIKE = m_ai->initSpell(HEROIC_STRIKE_1); //ARMS + REND = m_ai->initSpell(REND_1); //ARMS + THUNDER_CLAP = m_ai->initSpell(THUNDER_CLAP_1); //ARMS + HAMSTRING = m_ai->initSpell(HAMSTRING_1); //ARMS + MOCKING_BLOW = m_ai->initSpell(MOCKING_BLOW_1); //ARMS + RETALIATION = m_ai->initSpell(RETALIATION_1); //ARMS + SWEEPING_STRIKES = m_ai->initSpell(SWEEPING_STRIKES_1); //ARMS + MORTAL_STRIKE = m_ai->initSpell(MORTAL_STRIKE_1); //ARMS + BLADESTORM = m_ai->initSpell(BLADESTORM_1); //ARMS + HEROIC_THROW = m_ai->initSpell(HEROIC_THROW_1); //ARMS + SHATTERING_THROW = m_ai->initSpell(SHATTERING_THROW_1); //ARMS + BLOODRAGE = m_ai->initSpell(BLOODRAGE_1); //PROTECTION + DEFENSIVE_STANCE = m_ai->initSpell(DEFENSIVE_STANCE_1); //PROTECTION + DEVASTATE = m_ai->initSpell(DEVASTATE_1); //PROTECTION + SUNDER_ARMOR = m_ai->initSpell(SUNDER_ARMOR_1); //PROTECTION + TAUNT = m_ai->initSpell(TAUNT_1); //PROTECTION + SHIELD_BASH = m_ai->initSpell(SHIELD_BASH_1); //PROTECTION + REVENGE = m_ai->initSpell(REVENGE_1); //PROTECTION + SHIELD_BLOCK = m_ai->initSpell(SHIELD_BLOCK_1); //PROTECTION + DISARM = m_ai->initSpell(DISARM_1); //PROTECTION + SHIELD_WALL = m_ai->initSpell(SHIELD_WALL_1); //PROTECTION + SHIELD_SLAM = m_ai->initSpell(SHIELD_SLAM_1); //PROTECTION + VIGILANCE = m_ai->initSpell(VIGILANCE_1); //PROTECTION + DEVASTATE = m_ai->initSpell(DEVASTATE_1); //PROTECTION + SHOCKWAVE = m_ai->initSpell(SHOCKWAVE_1); //PROTECTION + CONCUSSION_BLOW = m_ai->initSpell(CONCUSSION_BLOW_1); //PROTECTION + SPELL_REFLECTION = m_ai->initSpell(SPELL_REFLECTION_1); //PROTECTION + LAST_STAND = m_ai->initSpell(LAST_STAND_1); //PROTECTION + BATTLE_SHOUT = m_ai->initSpell(BATTLE_SHOUT_1); //FURY + DEMORALIZING_SHOUT = m_ai->initSpell(DEMORALIZING_SHOUT_1); //FURY + CLEAVE = m_ai->initSpell(CLEAVE_1); //FURY + INTIMIDATING_SHOUT = m_ai->initSpell(INTIMIDATING_SHOUT_1); //FURY + EXECUTE = m_ai->initSpell(EXECUTE_1); //FURY + CHALLENGING_SHOUT = m_ai->initSpell(CHALLENGING_SHOUT_1); //FURY + SLAM = m_ai->initSpell(SLAM_1); //FURY + BERSERKER_STANCE = m_ai->initSpell(BERSERKER_STANCE_1); //FURY + INTERCEPT = m_ai->initSpell(INTERCEPT_1); //FURY + DEATH_WISH = m_ai->initSpell(DEATH_WISH_1); //FURY + BERSERKER_RAGE = m_ai->initSpell(BERSERKER_RAGE_1); //FURY + WHIRLWIND = m_ai->initSpell(WHIRLWIND_1); //FURY + PUMMEL = m_ai->initSpell(PUMMEL_1); //FURY + BLOODTHIRST = m_ai->initSpell(BLOODTHIRST_1); //FURY + RECKLESSNESS = m_ai->initSpell(RECKLESSNESS_1); //FURY RAMPAGE = 0; // passive - HEROIC_FURY = ai->initSpell(HEROIC_FURY_1); //FURY - COMMANDING_SHOUT = ai->initSpell(COMMANDING_SHOUT_1); //FURY - ENRAGED_REGENERATION = ai->initSpell(ENRAGED_REGENERATION_1); //FURY - PIERCING_HOWL = ai->initSpell(PIERCING_HOWL_1); //FURY + HEROIC_FURY = m_ai->initSpell(HEROIC_FURY_1); //FURY + COMMANDING_SHOUT = m_ai->initSpell(COMMANDING_SHOUT_1); //FURY + ENRAGED_REGENERATION = m_ai->initSpell(ENRAGED_REGENERATION_1); //FURY + PIERCING_HOWL = m_ai->initSpell(PIERCING_HOWL_1); //FURY RECENTLY_BANDAGED = 11196; // first aid check // racial - GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_WARRIOR); // draenei - STONEFORM = ai->initSpell(STONEFORM_ALL); // dwarf - ESCAPE_ARTIST = ai->initSpell(ESCAPE_ARTIST_ALL); // gnome - EVERY_MAN_FOR_HIMSELF = ai->initSpell(EVERY_MAN_FOR_HIMSELF_ALL); // human - SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); // night elf - BLOOD_FURY = ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc - WAR_STOMP = ai->initSpell(WAR_STOMP_ALL); // tauren - BERSERKING = ai->initSpell(BERSERKING_ALL); // troll - WILL_OF_THE_FORSAKEN = ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead + GIFT_OF_THE_NAARU = m_ai->initSpell(GIFT_OF_THE_NAARU_WARRIOR); // draenei + STONEFORM = m_ai->initSpell(STONEFORM_ALL); // dwarf + ESCAPE_ARTIST = m_ai->initSpell(ESCAPE_ARTIST_ALL); // gnome + SHADOWMELD = m_ai->initSpell(SHADOWMELD_ALL); // night elf + BLOOD_FURY = m_ai->initSpell(BLOOD_FURY_MELEE_CLASSES); // orc + WAR_STOMP = m_ai->initSpell(WAR_STOMP_ALL); // tauren + BERSERKING = m_ai->initSpell(BERSERKING_ALL); // troll + WILL_OF_THE_FORSAKEN = m_ai->initSpell(WILL_OF_THE_FORSAKEN_ALL); // undead } PlayerbotWarriorAI::~PlayerbotWarriorAI() {} -bool PlayerbotWarriorAI::DoFirstCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuver(Unit* pTarget) { - Player *m_bot = GetPlayerBot(); - PlayerbotAI *ai = GetAI(); - float fTargetDist = m_bot->GetDistance(pTarget); + // There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway) + // Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO) + { + if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro()) + { + if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder()) + { + if (m_bot->GetCombatDistance(pTarget, true) <= ATTACK_DISTANCE) + { + // Set everyone's UpdateAI() waiting to 2 seconds + m_ai->SetGroupIgnoreUpdateTime(2); + // Clear their TEMP_WAIT_TANKAGGRO flag + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + // Start attacking, force target on current target + m_ai->Attack(m_ai->GetCurrentTarget()); + + // While everyone else is waiting 2 second, we need to build up aggro, so don't return + } + else + { + // TODO: add check if target is ranged + return RETURN_NO_ACTION_OK; // wait for target to get nearer + } + } + else + return RETURN_NO_ACTION_OK; // wait it out + } + else + { + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO); + } + } - if (ai->IsTank() && DEFENSIVE_STANCE > 0 && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(DEFENSIVE_STANCE)) + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC) { - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Defensive Stance (%d)", DEFENSIVE_STANCE); - return true; + if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat()) + return RETURN_NO_ACTION_OK; // wait it out + else + m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC); } - else if (ai->IsTank() && TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(TAUNT, *pTarget)) + + switch (m_ai->GetScenarioType()) { - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Taunt (%d)", TAUNT); - return false; + case PlayerbotAI::SCENARIO_PVP_DUEL: + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoFirstCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: + default: + return DoFirstCombatManeuverPVE(pTarget); + break; } - else if (BATTLE_STANCE > 0 && !m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_STANCE)) + + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVE(Unit* pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + float fTargetDist = m_bot->GetCombatDistance(pTarget, true); + + // Get bot spec. If bot has tank orders, force spec to protection + uint32 spec = ((m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) ? WARRIOR_SPEC_PROTECTION : m_bot->GetSpec()); + + if (BERSERKER_STANCE && spec == WARRIOR_SPEC_FURY && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST)) { - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Battle Stance (%d)", BATTLE_STANCE); - return true; + if (!m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BERSERKER_STANCE)) + return RETURN_CONTINUE; + if (BLOODRAGE > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0) && m_ai->GetRageAmount() <= 10) + return m_ai->CastSpell(BLOODRAGE) ? RETURN_FINISHED_FIRST_MOVES : RETURN_NO_ACTION_ERROR; + if (INTERCEPT > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) + { + if (fTargetDist < 8.0f) + return RETURN_NO_ACTION_OK; + else if (fTargetDist > 25.0f) + return RETURN_CONTINUE; // wait to come into range + else if (INTERCEPT > 0 && m_ai->CastSpell(INTERCEPT, *pTarget)) + { + float x, y, z; + pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); + m_bot->Relocate(x, y, z); + return RETURN_FINISHED_FIRST_MOVES; + } + } } - else if (BATTLE_STANCE > 0 && CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + else if (BATTLE_STANCE && (spec == WARRIOR_SPEC_ARMS || (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST))) { - if (fTargetDist < 8.0f) - return false; - else if (fTargetDist > 25.0f) - return true; - else if (CHARGE > 0 && ai->CastSpell(CHARGE, *pTarget)) + if (!m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BATTLE_STANCE)) + return RETURN_CONTINUE; + if (CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) { - float x, y, z; - pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); - m_bot->Relocate(x, y, z); + if (fTargetDist < 8.0f) + return RETURN_NO_ACTION_OK; + if (fTargetDist > 25.0f) + return RETURN_CONTINUE; // wait to come into range + else if (CHARGE > 0 && m_ai->CastSpell(CHARGE, *pTarget)) + { + float x, y, z; + pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); + m_bot->Relocate(x, y, z); + return RETURN_FINISHED_FIRST_MOVES; + } + } + } + else if (DEFENSIVE_STANCE && spec == WARRIOR_SPEC_PROTECTION) + { + if (!m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DEFENSIVE_STANCE)) + return RETURN_CONTINUE; + else if (TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(TAUNT, *pTarget)) + return RETURN_FINISHED_FIRST_MOVES; + } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Charge (%d)", CHARGE); - return false; + return RETURN_NO_ACTION_OK; +} + +// TODO: blatant copy of PVE for now, please PVP-port it +CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVP(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; + + float fTargetDist = m_bot->GetCombatDistance(pTarget, true); + + if (DEFENSIVE_STANCE && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK)) + { + if (!m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(DEFENSIVE_STANCE)) + return RETURN_CONTINUE; + else if (TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(TAUNT, *pTarget)) + return RETURN_FINISHED_FIRST_MOVES; + } + + if (BERSERKER_STANCE) + { + if (!m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BERSERKER_STANCE)) + return RETURN_CONTINUE; + if (BLOODRAGE > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0) && m_ai->GetRageAmount() <= 10) + return m_ai->CastSpell(BLOODRAGE) ? RETURN_FINISHED_FIRST_MOVES : RETURN_NO_ACTION_ERROR; + if (INTERCEPT > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) + { + if (fTargetDist < 8.0f) + return RETURN_NO_ACTION_OK; + else if (fTargetDist > 25.0f) + return RETURN_CONTINUE; // wait to come into range + else if (INTERCEPT > 0 && m_ai->CastSpell(INTERCEPT, *pTarget)) + { + float x, y, z; + pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); + m_bot->Relocate(x, y, z); + return RETURN_FINISHED_FIRST_MOVES; + } } } - return false; + if (BATTLE_STANCE) + { + if (!m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BATTLE_STANCE)) + return RETURN_CONTINUE; + if (CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + { + if (fTargetDist < 8.0f) + return RETURN_NO_ACTION_OK; + if (fTargetDist > 25.0f) + return RETURN_CONTINUE; // wait to come into range + else if (CHARGE > 0 && m_ai->CastSpell(CHARGE, *pTarget)) + { + float x, y, z; + pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); + m_bot->Relocate(x, y, z); + return RETURN_FINISHED_FIRST_MOVES; + } + } + } + + return RETURN_NO_ACTION_OK; } -void PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) +CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); - switch (ai->GetScenarioType()) + switch (m_ai->GetScenarioType()) { case PlayerbotAI::SCENARIO_PVP_DUEL: - if (HEROIC_STRIKE > 0) - ai->CastSpell(HEROIC_STRIKE); - return; + case PlayerbotAI::SCENARIO_PVP_BG: + case PlayerbotAI::SCENARIO_PVP_ARENA: + case PlayerbotAI::SCENARIO_PVP_OPENWORLD: + return DoNextCombatManeuverPVP(pTarget); + case PlayerbotAI::SCENARIO_PVE: + case PlayerbotAI::SCENARIO_PVE_ELITE: + case PlayerbotAI::SCENARIO_PVE_RAID: default: + return DoNextCombatManeuverPVE(pTarget); break; } - // ------- Non Duel combat ---------- - //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob + return RETURN_NO_ACTION_ERROR; +} + +CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVE(Unit *pTarget) +{ + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - // Damage Attacks + //float fTargetDist = m_bot->GetCombatDistance(pTarget, true); - Player *m_bot = GetPlayerBot(); + //Used to determine if this bot is highest on threat + Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot); Unit* pVictim = pTarget->getVictim(); - float fTargetDist = m_bot->GetDistance(pTarget); - - // decide what stance to use - if (ai->IsTank() && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(DEFENSIVE_STANCE)) - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("Stance > Defensive"); - else if (!ai->IsTank() && !m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_STANCE)) - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("Stance > Battle"); - - // get spell sequence - if (pTarget->IsNonMeleeSpellCasted(true)) - SpellSequence = WarriorSpellPreventing; - else if (m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) - SpellSequence = WarriorBattle; - else if (m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0)) - SpellSequence = WarriorDefensive; - else if (m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) - SpellSequence = WarriorBerserker; // do shouts, berserker rage, etc... - if (BERSERKER_RAGE > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_INDEX_0) && ai->CastSpell(BERSERKER_RAGE)) - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("Pre > Berseker Rage"); - else if (DEMORALIZING_SHOUT > 0 && ai->GetRageAmount() >= 10 && !pTarget->HasAura(DEMORALIZING_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(DEMORALIZING_SHOUT)) - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("Pre > Demoralizing Shout"); - else if (BATTLE_SHOUT > 0 && ai->GetRageAmount() >= 10 && !m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_SHOUT)) - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("Pre > Battle Shout"); - - std::ostringstream out; - switch (SpellSequence) + if (BERSERKER_RAGE > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_INDEX_0)) + m_ai->CastSpell(BERSERKER_RAGE); + else if (BLOODRAGE > 0 && m_ai->GetRageAmount() <= 10) + m_ai->CastSpell(BLOODRAGE); + + Creature * pCreature = (Creature*) pTarget; + + // Prevent low health humanoid from fleeing with Hamstring + if (pCreature && (m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) || m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) && pCreature->GetCreatureInfo()->CreatureType == CREATURE_TYPE_HUMANOID && pTarget->GetHealthPercent() < 20 && !pCreature->IsWorldBoss()) { - case WarriorSpellPreventing: - out << "Case Prevent"; - if (SHIELD_BASH > 0 && ai->GetRageAmount() >= 10 && ai->CastSpell(SHIELD_BASH, *pTarget)) - out << " > Shield Bash"; - else if (PUMMEL > 0 && ai->GetRageAmount() >= 10 && ai->CastSpell(PUMMEL, *pTarget)) - out << " > Pummel"; - else if (SPELL_REFLECTION > 0 && ai->GetRageAmount() >= 15 && !m_bot->HasAura(SPELL_REFLECTION, EFFECT_INDEX_0) && ai->CastSpell(SPELL_REFLECTION, *m_bot)) - out << " > Spell Reflection"; - else - out << " > NONE"; - break; + if (HAMSTRING > 0 && !pTarget->HasAura(HAMSTRING, EFFECT_INDEX_0) && m_ai->CastSpell(HAMSTRING, *pTarget)) + return RETURN_CONTINUE; + } - case WarriorBattle: - out << "Case Battle"; - if (EXECUTE > 0 && ai->GetRageAmount() >= 15 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.2 && ai->CastSpell(EXECUTE, *pTarget)) - out << " > Execute!"; - else if (LAST_STAND > 0 && !m_bot->HasAura(LAST_STAND, EFFECT_INDEX_0) && m_bot->GetHealth() < m_bot->GetMaxHealth() * 0.5 && ai->CastSpell(LAST_STAND, *m_bot)) - out << " > Last Stand!"; - else if (BLOODRAGE > 0 && ai->GetRageAmount() < 50 && !m_bot->HasAura(BLOODRAGE, EFFECT_INDEX_0) && ai->CastSpell(BLOODRAGE, *m_bot)) - out << " > Bloodrage"; - else if (DEATH_WISH > 0 && ai->GetRageAmount() >= 10 && !m_bot->HasAura(DEATH_WISH, EFFECT_INDEX_0) && ai->CastSpell(DEATH_WISH, *m_bot)) - out << " > Death Wish"; - else if (RETALIATION > 0 && pVictim == m_bot && ai->GetAttackerCount() >= 2 && !m_bot->HasAura(RETALIATION, EFFECT_INDEX_0) && ai->CastSpell(RETALIATION, *m_bot)) - out << " > Retaliation"; - else if (DEMORALIZING_SHOUT > 0 && ai->GetRageAmount() >= 10 && !pTarget->HasAura(DEMORALIZING_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(DEMORALIZING_SHOUT, *pTarget)) - out << " > Demoralizing Shout"; - else if (SWEEPING_STRIKES > 0 && ai->GetRageAmount() >= 30 && ai->GetAttackerCount() >= 2 && !m_bot->HasAura(SWEEPING_STRIKES, EFFECT_INDEX_0) && ai->CastSpell(SWEEPING_STRIKES, *m_bot)) - out << " > Sweeping Strikes!"; - else if (BLADESTORM > 0 && ai->GetRageAmount() >= 25 && pVictim == m_bot && !m_bot->HasAura(BLADESTORM, EFFECT_INDEX_0) && ai->GetAttackerCount() >= 3 && ai->CastSpell(BLADESTORM, *pTarget)) - out << " > Bladestorm!"; - else if (MORTAL_STRIKE > 0 && ai->GetRageAmount() >= 30 && !pTarget->HasAura(MORTAL_STRIKE, EFFECT_INDEX_0) && ai->CastSpell(MORTAL_STRIKE, *pTarget)) - out << " > Mortal Strike"; - else if (INTIMIDATING_SHOUT > 0 && ai->GetRageAmount() >= 25 && ai->GetAttackerCount() > 5 && ai->CastSpell(INTIMIDATING_SHOUT, *pTarget)) - out << " > Intimidating Shout"; - else if (THUNDER_CLAP > 0 && ai->GetRageAmount() >= 20 && pVictim == m_bot && !pTarget->HasAura(THUNDER_CLAP, EFFECT_INDEX_0) && ai->CastSpell(THUNDER_CLAP, *pTarget)) - out << " > Thunder Clap"; - else if (ENRAGED_REGENERATION > 0 && ai->GetRageAmount() >= 15 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_INDEX_0) && !m_bot->HasAura(ENRAGED_REGENERATION, EFFECT_INDEX_0) && m_bot->GetHealth() < m_bot->GetMaxHealth() * 0.5 && ai->CastSpell(ENRAGED_REGENERATION, *m_bot)) - out << " > Enraged Regeneration"; - else if (SHOCKWAVE > 0 && ai->GetRageAmount() >= 15 && pVictim == m_bot && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && ai->CastSpell(SHOCKWAVE, *pTarget)) - out << " > Shockwave"; - else if (REND > 0 && ai->GetRageAmount() >= 10 && !pTarget->HasAura(REND, EFFECT_INDEX_0) && ai->CastSpell(REND, *pTarget)) - out << " > Rend"; - else if (HAMSTRING > 0 && ai->GetRageAmount() >= 10 && !pTarget->HasAura(HAMSTRING, EFFECT_INDEX_0) && ai->CastSpell(HAMSTRING, *pTarget)) - out << " > Hamstring"; - else if (CHALLENGING_SHOUT > 0 && ai->GetRageAmount() >= 5 && pVictim != m_bot && ai->GetHealthPercent() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_INDEX_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(CHALLENGING_SHOUT, *pTarget)) - out << " > Challenging Shout"; - else if (BLOODTHIRST > 0 && ai->GetRageAmount() >= 20 && !m_bot->HasAura(BLOODTHIRST, EFFECT_INDEX_0) && m_bot->GetHealth() < m_bot->GetMaxHealth() * 0.7 && ai->CastSpell(BLOODTHIRST, *pTarget)) - out << " > Bloodthrist"; - else if (CLEAVE > 0 && ai->GetRageAmount() >= 20 && ai->CastSpell(CLEAVE, *pTarget)) - out << " > Cleave"; - else if (HEROIC_STRIKE > 0 && ai->GetRageAmount() >= 15 && ai->CastSpell(HEROIC_STRIKE, *pTarget)) - out << " > Heroic Strike"; - else if (CONCUSSION_BLOW > 0 && ai->GetRageAmount() >= 15 && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && ai->CastSpell(CONCUSSION_BLOW, *pTarget)) - out << " > Concussion Blow"; - else if (SLAM > 0 && ai->GetRageAmount() >= 15 && ai->CastSpell(SLAM, *pTarget)) - out << " > Slam"; - else if (PIERCING_HOWL > 0 && ai->GetRageAmount() >= 10 && ai->GetAttackerCount() >= 3 && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && ai->CastSpell(PIERCING_HOWL, *pTarget)) - out << " > Piercing Howl"; - else if (MOCKING_BLOW > 0 && ai->GetRageAmount() >= 10 && pVictim != m_bot && ai->GetHealthPercent() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_INDEX_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(MOCKING_BLOW, *pTarget)) - out << " > Mocking Blow"; - else if (OVERPOWER > 0 && ai->GetRageAmount() >= 5 && ai->CastSpell(OVERPOWER, *pTarget)) - out << " > Overpower"; - else if (SUNDER_ARMOR > 0 && ai->CastSpell(SUNDER_ARMOR, *pTarget)) - out << " > Sunder Armor"; - else if (SHATTERING_THROW > 0 && !pTarget->HasAura(SHATTERING_THROW, EFFECT_INDEX_0) && ai->CastSpell(SHATTERING_THROW, *pTarget)) - out << " > Shattering Throw"; - else if (HEROIC_THROW > 0 && ai->CastSpell(HEROIC_THROW, *pTarget)) - out << " > Heroic Throw"; - else if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && ai->CastSpell(WAR_STOMP, *pTarget)) - out << " > War Stomp"; - else if (m_bot->getRace() == RACE_HUMAN && m_bot->hasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && ai->CastSpell(EVERY_MAN_FOR_HIMSELF, *m_bot)) - out << " > Every Man for Himself"; - else if (m_bot->getRace() == RACE_UNDEAD && m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && ai->CastSpell(WILL_OF_THE_FORSAKEN, *m_bot)) - out << " > Will of the Forsaken"; - else if (m_bot->getRace() == RACE_GNOME && m_bot->hasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) && ai->CastSpell(ESCAPE_ARTIST, *m_bot)) - out << " > Escape Artist"; - else if (m_bot->getRace() == RACE_NIGHTELF && pVictim == m_bot && ai->GetHealthPercent() < 25 && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && ai->CastSpell(SHADOWMELD, *m_bot)) - out << " > Shadowmeld"; - else if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_INDEX_0) && ai->CastSpell(BLOOD_FURY, *m_bot)) - out << " > Blood Fury"; - else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0) && ai->CastSpell(BERSERKING, *m_bot)) - out << " > Berserking"; - else - out << " > NONE"; - break; + CheckShouts(); - case WarriorDefensive: - out << "Case Defensive"; - if (DISARM > 0 && ai->GetRageAmount() >= 15 && !pTarget->HasAura(DISARM, EFFECT_INDEX_0) && ai->CastSpell(DISARM, *pTarget)) - out << " > Disarm"; - else if (SUNDER_ARMOR > 0 && ai->GetRageAmount() >= 15 && ai->CastSpell(SUNDER_ARMOR, *pTarget)) - out << " > Sunder Armor"; - else if (REVENGE > 0 && ai->GetRageAmount() >= 5 && ai->CastSpell(REVENGE, *pTarget)) - out << " > Revenge"; - else if (SHIELD_BLOCK > 0 && !m_bot->HasAura(SHIELD_BLOCK, EFFECT_INDEX_0) && ai->CastSpell(SHIELD_BLOCK, *m_bot)) - out << " > Shield Block"; - else if (SHIELD_WALL > 0 && !m_bot->HasAura(SHIELD_WALL, EFFECT_INDEX_0) && ai->CastSpell(SHIELD_WALL, *m_bot)) - out << " > Shield Wall"; - else - out << " > NONE"; - break; + // Get bot spec. If bot has tank orders, force spec to protection + uint32 spec = ((m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK) ? WARRIOR_SPEC_PROTECTION : m_bot->GetSpec()); + + if (spec == WARRIOR_SPEC_FURY && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST)) + { + // Try to interrupt spell if target is casting one + if (pTarget->IsNonMeleeSpellCasted(true)) + { + if (PUMMEL > 0 && !m_bot->HasSpellCooldown(PUMMEL) && m_ai->CastSpell(PUMMEL, *pTarget)) + return RETURN_CONTINUE; + } - case WarriorBerserker: - out << "Case Berserker"; - if (WHIRLWIND > 0 && ai->GetRageAmount() >= 25 && ai->CastSpell(WHIRLWIND, *pTarget)) - out << " > Whirlwind"; - out << " > NONE"; - break; + if (DEATH_WISH > 0 && !m_bot->HasAura(DEATH_WISH, EFFECT_INDEX_0) && !m_bot->HasSpellCooldown(DEATH_WISH) && m_ai->CastSpell(DEATH_WISH, *m_bot)) + return RETURN_CONTINUE; + if (EXECUTE > 0 && pTarget->GetHealthPercent() < 20 && m_ai->CastSpell(EXECUTE, *pTarget)) + return RETURN_CONTINUE; + if (BLOODTHIRST > 0 && !m_bot->HasSpellCooldown(BLOODTHIRST) && m_ai->CastSpell(BLOODTHIRST, *pTarget)) + return RETURN_CONTINUE; + if (WHIRLWIND > 0 && !m_bot->HasSpellCooldown(WHIRLWIND) && m_ai->CastSpell(WHIRLWIND, *pTarget)) + return RETURN_CONTINUE; + if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget)) + return RETURN_CONTINUE; + } + else if (spec == WARRIOR_SPEC_ARMS || (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST)) + { + // Try to interrupt spell if target is casting one + if (pTarget->IsNonMeleeSpellCasted(true)) + { + if (SHIELD_BASH > 0 && m_ai->CastSpell(SHIELD_BASH, *pTarget)) + return RETURN_CONTINUE; + } + + // If bot's target is also attacking the bot, use retaliation for extra attacks + if (RETALIATION > 0 && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && !m_bot->HasSpellCooldown(RETALIATION) && !m_bot->HasAura(RETALIATION, EFFECT_INDEX_0) && m_ai->CastSpell(RETALIATION, *m_bot)) + return RETURN_CONTINUE; + + if (EXECUTE > 0 && pTarget->GetHealthPercent() < 20 && m_ai->CastSpell(EXECUTE, *pTarget)) + return RETURN_CONTINUE; + if (REND > 0 && !pTarget->HasAura(REND, EFFECT_INDEX_0) && m_ai->CastSpell(REND, *pTarget)) + return RETURN_CONTINUE; + if (MORTAL_STRIKE > 0 && !m_bot->HasSpellCooldown(MORTAL_STRIKE) && m_ai->CastSpell(MORTAL_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (OVERPOWER > 0 && !m_bot->HasSpellCooldown(OVERPOWER)) + { + uint8 base = pTarget->RollMeleeOutcomeAgainst(m_bot, BASE_ATTACK, SPELL_SCHOOL_MASK_NORMAL); + uint8 off = pTarget->RollMeleeOutcomeAgainst(m_bot, OFF_ATTACK, SPELL_SCHOOL_MASK_NORMAL); + if (base == MELEE_HIT_DODGE || off == MELEE_HIT_DODGE) + { + if ( !m_bot->HasSpellCooldown(OVERPOWER) && m_ai->CastSpell(OVERPOWER, *pTarget)) + return RETURN_CONTINUE; + } + } + if (THUNDER_CLAP > 0 && !pTarget->HasAura(THUNDER_CLAP) && m_ai->CastSpell(THUNDER_CLAP, *pTarget)) + return RETURN_CONTINUE; + if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget)) + return RETURN_CONTINUE; + if (SLAM > 0 && m_ai->CastSpell(SLAM, *pTarget)) + return RETURN_CONTINUE; + } + else if (spec == WARRIOR_SPEC_PROTECTION) + { + // First check: is bot's target targeting bot? + if (!newTarget) + { + // Cast taunt on bot current target if the mob is targeting someone else + if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK && TAUNT > 0 && !m_bot->HasSpellCooldown(TAUNT) && m_ai->CastSpell(TAUNT, *pTarget)) + return RETURN_CONTINUE; + } + + // If tank is on the verge of dying but "I DON'T WANT TO DIE !!! :'-((" + // TODO: should behaviour (or treshold) be different between elite and normal mobs? We don't want bots to burn such precious cooldown needlessly + if (m_bot->GetHealthPercent() < 10) + { + // Cast Last Stand first because it has lower cooldown + if (LAST_STAND > 0 && !m_bot->HasAura(LAST_STAND, EFFECT_INDEX_0) && m_ai->CastSpell(LAST_STAND, *m_bot)) + { + m_ai->TellMaster("I'm using LAST STAND"); + return RETURN_CONTINUE; + } + // Cast Shield Wall only if Last Stand is on cooldown and not active + if (SHIELD_WALL > 0 && (m_bot->HasSpellCooldown(LAST_STAND) || LAST_STAND == 0) && !m_bot->HasAura(LAST_STAND, EFFECT_INDEX_0) && !m_bot->HasAura(SHIELD_WALL, EFFECT_INDEX_0) && m_ai->CastSpell(SHIELD_WALL, *m_bot)) + { + m_ai->TellMaster("I'm using SHIELD WALL"); + return RETURN_CONTINUE; + } + } + + // Try to interrupt spell if target is casting one + if (pTarget->IsNonMeleeSpellCasted(true)) + { + if (SHIELD_BASH > 0 && m_ai->CastSpell(SHIELD_BASH, *pTarget)) + return RETURN_CONTINUE; + } + + if (REVENGE > 0 && !m_bot->HasSpellCooldown(REVENGE)) + { + uint8 base = pTarget->RollMeleeOutcomeAgainst(m_bot, BASE_ATTACK, SPELL_SCHOOL_MASK_NORMAL); + uint8 off = pTarget->RollMeleeOutcomeAgainst(m_bot, OFF_ATTACK, SPELL_SCHOOL_MASK_NORMAL); + if (base == MELEE_HIT_PARRY || base == MELEE_HIT_DODGE || base == MELEE_HIT_BLOCK || off == MELEE_HIT_PARRY || off == MELEE_HIT_DODGE || off == MELEE_HIT_BLOCK) + if (m_ai->CastSpell(REVENGE, *pTarget)) + return RETURN_CONTINUE; + } + if (REND > 0 && !pTarget->HasAura(REND, EFFECT_INDEX_0) && m_ai->CastSpell(REND, *pTarget)) + return RETURN_CONTINUE; + //Do not waste rage applying Sunder Armor if it is already stacked 5 times + if (SUNDER_ARMOR > 0) + { + if (!pTarget->HasAura(SUNDER_ARMOR) && m_ai->CastSpell(SUNDER_ARMOR, *pTarget)) // no stacks: cast it + return RETURN_CONTINUE; + else + { + SpellAuraHolder* holder = pTarget->GetSpellAuraHolder(SUNDER_ARMOR); + if (holder && (holder->GetStackAmount() < 5) && m_ai->CastSpell(SUNDER_ARMOR, *pTarget)) + return RETURN_CONTINUE; + } + } + if (DEMORALIZING_SHOUT > 0 && !pTarget->HasAura(DEMORALIZING_SHOUT, EFFECT_INDEX_0) && m_ai->CastSpell(DEMORALIZING_SHOUT, *pTarget)) + return RETURN_CONTINUE; + // TODO: only cast disarm if target has equipment? + if (DISARM > 0 && !pTarget->HasAura(DISARM, EFFECT_INDEX_0) && m_ai->CastSpell(DISARM, *pTarget)) + return RETURN_CONTINUE; + // check that target is dangerous (elite) before casting shield block: preserve bot cooldowns + if (SHIELD_BLOCK > 0 && m_ai->IsElite(pTarget) && !m_bot->HasAura(SHIELD_BLOCK, EFFECT_INDEX_0) && m_ai->CastSpell(SHIELD_BLOCK, *m_bot)) + return RETURN_CONTINUE; + if (CONCUSSION_BLOW > 0 && !m_bot->HasSpellCooldown(CONCUSSION_BLOW) && m_ai->CastSpell(CONCUSSION_BLOW, *pTarget)) + return RETURN_CONTINUE; + if (SHIELD_SLAM > 0 && !m_bot->HasSpellCooldown(SHIELD_SLAM) && m_ai->CastSpell(SHIELD_SLAM, *pTarget)) + return RETURN_CONTINUE; + if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget)) + return RETURN_CONTINUE; } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster(out.str().c_str()); + + /* case WarriorBattle: + if (SWEEPING_STRIKES > 0 && m_ai->GetAttackerCount() >= 2 && !m_bot->HasAura(SWEEPING_STRIKES, EFFECT_INDEX_0) && m_ai->CastSpell(SWEEPING_STRIKES, *m_bot)) + return RETURN_CONTINUE; + if (INTIMIDATING_SHOUT > 0 && m_ai->GetAttackerCount() > 5 && m_ai->CastSpell(INTIMIDATING_SHOUT, *pTarget)) + return RETURN_CONTINUE; + if (CHALLENGING_SHOUT > 0 && pVictim != m_bot && m_ai->GetHealthPercent() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_INDEX_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_INDEX_0) && m_ai->CastSpell(CHALLENGING_SHOUT, *pTarget)) + return RETURN_CONTINUE; + if (CLEAVE > 0 && m_ai->CastSpell(CLEAVE, *pTarget)) + return RETURN_CONTINUE; + if (PIERCING_HOWL > 0 && && m_ai->GetAttackerCount() >= 3 && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && m_ai->CastSpell(PIERCING_HOWL, *pTarget)) + return RETURN_CONTINUE; + if (MOCKING_BLOW > 0 && pVictim != m_bot && m_ai->GetHealthPercent() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_INDEX_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_INDEX_0) && m_ai->CastSpell(MOCKING_BLOW, *pTarget)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_INDEX_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_INDEX_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_INDEX_0) && m_ai->CastSpell(WAR_STOMP, *pTarget)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_HUMAN && m_bot->hasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && m_ai->CastSpell(EVERY_MAN_FOR_HIMSELF, *m_bot)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_UNDEAD && m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && m_ai->CastSpell(WILL_OF_THE_FORSAKEN, *m_bot)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && m_ai->CastSpell(STONEFORM, *m_bot)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_GNOME && m_bot->hasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) && m_ai->CastSpell(ESCAPE_ARTIST, *m_bot)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_INDEX_0) && m_ai->CastSpell(BLOOD_FURY, *m_bot)) + return RETURN_CONTINUE; + if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0) && m_ai->CastSpell(BERSERKING, *m_bot)) + return RETURN_CONTINUE; + break;*/ + + return RETURN_NO_ACTION_OK; } -void PlayerbotWarriorAI::DoNonCombatActions() +CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVP(Unit* pTarget) { - PlayerbotAI *ai = GetAI(); - Player * m_bot = GetPlayerBot(); - if (!m_bot) + if (m_ai->CastSpell(HEROIC_STRIKE)) + return RETURN_CONTINUE; + + return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative +} + +//Buff and rebuff shouts +void PlayerbotWarriorAI::CheckShouts() +{ + if (!m_ai) return; + if (!m_bot) return; + + if (!m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0) && m_ai->CastSpell(BATTLE_SHOUT)) return; +} - // TODO (by Runsttren): check if shout aura bot has is casted by this bot, - // otherwise cast other useful shout - // If the bot is protect talented, she/he needs stamina not attack power. - // With stance change can the shout change to. - // Inserted line to battle shout m_bot->HasAura( COMMANDING_SHOUT, EFFECT_INDEX_0) - // Natsukawa - if (((COMMANDING_SHOUT > 0 && !m_bot->HasAura(COMMANDING_SHOUT, EFFECT_INDEX_0)) || - (BATTLE_SHOUT > 0 && !m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0))) && - ai->GetRageAmount() < 10 && BLOODRAGE > 0 && !m_bot->HasAura(BLOODRAGE, EFFECT_INDEX_0)) - // we do have a useful shout, no rage coming but can cast bloodrage... do it - ai->CastSpell(BLOODRAGE, *m_bot); - else if (COMMANDING_SHOUT > 0 && !m_bot->HasAura(COMMANDING_SHOUT, EFFECT_INDEX_0)) - // use commanding shout now - ai->CastSpell(COMMANDING_SHOUT, *m_bot); - else if (BATTLE_SHOUT > 0 && ai->GetRageAmount() >= 10 && !m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0) && !m_bot->HasAura(COMMANDING_SHOUT, EFFECT_INDEX_0)) - // use battle shout - ai->CastSpell(BATTLE_SHOUT, *m_bot); - - // buff master with VIGILANCE - if (VIGILANCE > 0) - (!GetMaster()->HasAura(VIGILANCE, EFFECT_INDEX_0) && ai->CastSpell(VIGILANCE, *GetMaster())); +void PlayerbotWarriorAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + uint32 spec = m_bot->GetSpec(); - Item* pItem = ai->FindFood(); - Item* fItem = ai->FindBandage(); + //Stance Check + if (spec == WARRIOR_SPEC_ARMS && !m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + m_ai->CastSpell(BATTLE_STANCE); + else if (spec == WARRIOR_SPEC_FURY && !m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) + m_ai->CastSpell(BERSERKER_STANCE); + else if (spec == WARRIOR_SPEC_PROTECTION && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0)) + m_ai->CastSpell(DEFENSIVE_STANCE); - if (pItem != nullptr && ai->GetHealthPercent() < 30) - { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); + // hp check + if (EatDrinkBandage(false)) return; + + // Search and apply stones to weapons + // Mainhand ... + Item * stone, * weapon; + weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) + { + stone = m_ai->FindStoneFor(weapon); + if (stone) + { + m_ai->UseItem(stone, EQUIPMENT_SLOT_MAINHAND); + m_ai->SetIgnoreUpdateTime(5); + } } - else if (pItem == nullptr && fItem != nullptr && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + //... and offhand (we add a check to avoid trying to apply stone if the warrior is wielding a shield) + weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (weapon && (weapon->GetProto()->InventoryType == INVTYPE_WEAPONOFFHAND || weapon->GetProto()->InventoryType == INVTYPE_WEAPONMAINHAND) + && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) { - ai->TellMaster("I could use first aid."); - ai->UseItem(fItem); - return; + stone = m_ai->FindStoneFor(weapon); + if (stone) + { + m_ai->UseItem(stone, EQUIPMENT_SLOT_OFFHAND); + m_ai->SetIgnoreUpdateTime(5); + } } + + // Nothing else to do, Night Elves will cast Shadowmeld to reduce their aggro versus patrols or nearby mobs + if (SHADOWMELD && !m_bot->HasAura(SHADOWMELD, EFFECT_INDEX_0) && m_ai->CastSpell(SHADOWMELD, *m_bot)) + return; } // end DoNonCombatActions + +// Match up with "Pull()" below +bool PlayerbotWarriorAI::CanPull() +{ + if (!m_bot) return false; + if (!m_ai) return false; + + if (m_bot->GetUInt32Value(PLAYER_AMMO_ID)) // Having ammo equipped means a weapon is equipped as well. Probably. [TODO: does this work with throwing knives? Can a playerbot 'cheat' ammo into the slot without a proper weapon?] + { + // Can't do this, CanPull CANNOT check for anything that requires a target + //if (!m_ai->IsInRange(m_ai->GetCurrentTarget(), AUTO_SHOT)) + //{ + // m_ai->TellMaster("I'm out of range."); + // return false; + //} + return true; + } + + return false; +} + +// Match up with "CanPull()" above +bool PlayerbotWarriorAI::Pull() +{ + if (!m_bot) return false; + if (!m_ai) return false; + + // In Classic, Warriors had 3 differents spells for shooting with range weapons + // So we need to determine which one to use + // First step: look for the item equiped in range slot + Item* pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); + if (!pItem) + { + m_ai->TellMaster("I don't have ranged weapon equiped."); + return false; + } + + ItemPrototype const* pProto = pItem->GetProto(); + if (pProto && pProto->Class == ITEM_CLASS_WEAPON) + { + switch (pProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_BOW: + SHOOT = SHOOT_BOW; + break; + case ITEM_SUBCLASS_WEAPON_GUN: + SHOOT = SHOOT_GUN; + break; + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + SHOOT = SHOOT_XBOW; + break; + default: + m_ai->TellMaster("Can't pull: equiped range item is neither a gun, bow or crossbow."); + return false; + } + } + else + return false; + + if (m_bot->GetCombatDistance(m_ai->GetCurrentTarget(), true) > ATTACK_DISTANCE) + { + if (!m_ai->In_Reach(m_ai->GetCurrentTarget(), SHOOT)) + { + m_ai->TellMaster("I'm out of range."); + return false; + } + + // shoot at the target +// if (m_ai->CastSpell(SHOOT, m_ai->GetCurrentTarget())) + m_ai->FaceTarget(m_ai->GetCurrentTarget()); + m_bot->CastSpell(m_ai->GetCurrentTarget(), SHOOT, TRIGGERED_OLD_TRIGGERED); + m_ai->TellMaster("I'm PULLING %s.", m_ai->GetCurrentTarget()->GetName()); + return true; + } + else // target is in melee range + { + m_ai->Attack(m_ai->GetCurrentTarget()); + return true; + } + + return false; +} diff --git a/src/game/playerbot/PlayerbotWarriorAI.h b/src/game/playerbot/PlayerbotWarriorAI.h index 24ba6fb6d..dc5836ab7 100644 --- a/src/game/playerbot/PlayerbotWarriorAI.h +++ b/src/game/playerbot/PlayerbotWarriorAI.h @@ -55,6 +55,9 @@ enum WarriorSpells SHIELD_SLAM_1 = 23922, SHIELD_WALL_1 = 871, SHOCKWAVE_1 = 46968, + SHOOT_BOW_1 = 2480, + SHOOT_GUN_1 = 7918, + SHOOT_XBOW_1 = 7919, SLAM_1 = 1464, SPELL_REFLECTION_1 = 23920, SUNDER_ARMOR_1 = 7386, @@ -75,27 +78,100 @@ class MANGOS_DLL_SPEC PlayerbotWarriorAI : PlayerbotClassAI virtual ~PlayerbotWarriorAI(); // all combat actions go here - bool DoFirstCombatManeuver(Unit*); - void DoNextCombatManeuver(Unit*); + CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget); + bool Pull(); // all non combat actions go here, ex buffs, heals, rezzes void DoNonCombatActions(); + //Buff/rebuff shouts + void CheckShouts(); + + // Utility Functions + bool CanPull(); + private: + CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget); + CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget); + CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); + // ARMS - uint32 BATTLE_STANCE, CHARGE, HEROIC_STRIKE, REND, THUNDER_CLAP, HAMSTRING, MOCKING_BLOW, RETALIATION, SWEEPING_STRIKES, MORTAL_STRIKE, BLADESTORM, HEROIC_THROW, SHATTERING_THROW; + uint32 BATTLE_STANCE, + CHARGE, + HEROIC_STRIKE, + REND, + THUNDER_CLAP, + HAMSTRING, + MOCKING_BLOW, + RETALIATION, + SWEEPING_STRIKES, + MORTAL_STRIKE, + BLADESTORM, + HEROIC_THROW, + SHATTERING_THROW; // PROTECTION - uint32 DEFENSIVE_STANCE, BLOODRAGE, SUNDER_ARMOR, TAUNT, SHIELD_BASH, REVENGE, SHIELD_BLOCK, DISARM, SHIELD_WALL, SHIELD_SLAM, VIGILANCE, DEVASTATE, SHOCKWAVE, CONCUSSION_BLOW, SPELL_REFLECTION, LAST_STAND; + uint32 DEFENSIVE_STANCE, + BLOODRAGE, + SUNDER_ARMOR, + TAUNT, + SHIELD_BASH, + REVENGE, + SHIELD_BLOCK, + DISARM, + SHIELD_WALL, + SHIELD_SLAM, + VIGILANCE, + DEVASTATE, + SHOCKWAVE, + CONCUSSION_BLOW, + SPELL_REFLECTION, + LAST_STAND; // FURY - uint32 BERSERKER_STANCE, BATTLE_SHOUT, DEMORALIZING_SHOUT, OVERPOWER, CLEAVE, INTIMIDATING_SHOUT, EXECUTE, CHALLENGING_SHOUT, SLAM, INTERCEPT, DEATH_WISH, BERSERKER_RAGE, WHIRLWIND, PUMMEL, BLOODTHIRST, RECKLESSNESS, RAMPAGE, HEROIC_FURY, COMMANDING_SHOUT, ENRAGED_REGENERATION, PIERCING_HOWL; + uint32 BERSERKER_STANCE, + BATTLE_SHOUT, + DEMORALIZING_SHOUT, + OVERPOWER, + CLEAVE, + INTIMIDATING_SHOUT, + EXECUTE, + CHALLENGING_SHOUT, + SLAM, + INTERCEPT, + DEATH_WISH, + BERSERKER_RAGE, + WHIRLWIND, + PUMMEL, + BLOODTHIRST, + RECKLESSNESS, + RAMPAGE, + HEROIC_FURY, + COMMANDING_SHOUT, + ENRAGED_REGENERATION, + PIERCING_HOWL; // first aid uint32 RECENTLY_BANDAGED; // racial - uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN; + uint32 ARCANE_TORRENT, + GIFT_OF_THE_NAARU, + STONEFORM, + ESCAPE_ARTIST, + SHADOWMELD, + BLOOD_FURY, + WAR_STOMP, + BERSERKING, + WILL_OF_THE_FORSAKEN; + + // general + uint32 SHOOT, + SHOOT_BOW, + SHOOT_GUN, + SHOOT_XBOW; uint32 SpellSequence; }; diff --git a/src/realmd/CMakeLists.txt b/src/realmd/CMakeLists.txt index 24dd950ca..fb3b7c99b 100644 --- a/src/realmd/CMakeLists.txt +++ b/src/realmd/CMakeLists.txt @@ -38,6 +38,7 @@ include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/src/shared ${MYSQL_INCLUDE_DIR} + ${OPENSSL_INCLUDE_DIR} ) add_executable(${EXECUTABLE_NAME}