From d13617ed1bfa680a5396956cb65c07e1191998e4 Mon Sep 17 00:00:00 2001 From: Ono Date: Tue, 28 Feb 2017 18:31:35 +0300 Subject: [PATCH 01/12] Improve quest item tracking in UI +Eliminates a mismatch between quest progress status and player fields for items +Add comments with corresponding UI events --- src/game/Player.cpp | 22 ++++++++++++++++------ src/game/Player.h | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 43b968463..894629a31 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -13519,7 +13519,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); @@ -13981,24 +13981,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()); @@ -14008,9 +14017,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 e2552ba9a..286ee20c0 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -1323,7 +1323,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; From 436174f7e24ac686483fc7fee334af18b0960aff Mon Sep 17 00:00:00 2001 From: cyberium Date: Wed, 1 Mar 2017 00:42:32 +0100 Subject: [PATCH 02/12] Fix missing value in base/mangos.sql @Boxa ;) --- sql/base/mangos.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 */; From e7265a1bdde616502d3f696378f578b2b9c79452 Mon Sep 17 00:00:00 2001 From: killerwife Date: Fri, 24 Feb 2017 23:23:25 +0100 Subject: [PATCH 03/12] Fix refund logic for rogues/druids --- src/game/Spell.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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; - } } } } From c224595b1210231f1042a7d9e27b953f32ca5cca Mon Sep 17 00:00:00 2001 From: Carl Hjerpe Date: Wed, 16 Nov 2016 13:14:34 +0100 Subject: [PATCH 04/12] Fix OSX building and implement Travis. Default Mac OpenSSL directory to home-brew installation path if nothing else is specified. (+9 squashed commits) Squashed commits: [6d2ce1c] Update CMakeLists.txt [263d9ba] Update loadlib.h Copied from CMaNGOS defs [eb0b9f9] Update loadlib.h [1f510c7] Update loadlib.h [2461169] Update .travis.yml [ecc939d] Update .travis.yml [b88d7b1] Update .travis.yml [3cd9472] Update .travis.yml [3f0b716] Update .travis.yml [ff219c0] Update .travis.yml --- .travis.yml | 35 +++++++++++++------ CMakeLists.txt | 10 ++++++ contrib/vmap_extractor/CMakeLists.txt | 8 +++++ .../vmapextract/loadlib/loadlib.h | 35 ++++++------------- 4 files changed, 52 insertions(+), 36 deletions(-) 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..72569ba4b 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 (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + 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/vmap_extractor/CMakeLists.txt b/contrib/vmap_extractor/CMakeLists.txt index 725d57c5f..1cb3e4ebf 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 (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + 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 From d067f46734f958895fdf49a775c40fc2eb589ec8 Mon Sep 17 00:00:00 2001 From: Jose123456 Date: Wed, 16 Nov 2016 22:44:30 -0800 Subject: [PATCH 05/12] Fix build and link errors on mac (#5) --- contrib/mmap/CMakeLists.txt | 7 ++++++- contrib/mmap/src/MMapCommon.h | 1 + contrib/vmap_assembler/CMakeLists.txt | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) 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) From 0e9b729d0a2406de1543776a3997a5118017c918 Mon Sep 17 00:00:00 2001 From: Carl Hjerpe Date: Thu, 17 Nov 2016 17:17:25 +0100 Subject: [PATCH 06/12] Fix OpenSSL include Fix Mac assert macro weirdness Use APPLE instead of checking if systemname matches Darwin, cleaner --- CMakeLists.txt | 2 +- contrib/vmap_extractor/CMakeLists.txt | 2 +- src/game/CMakeLists.txt | 1 + src/realmd/CMakeLists.txt | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72569ba4b..f499ea194 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,7 +129,7 @@ if(UNIX) # 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 (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + if (APPLE) if (NOT OPENSSL_ROOT_DIR) set(OPENSSL_ROOT_DIR /usr/local/opt/openssl/) endif() diff --git a/contrib/vmap_extractor/CMakeLists.txt b/contrib/vmap_extractor/CMakeLists.txt index 1cb3e4ebf..441897178 100644 --- a/contrib/vmap_extractor/CMakeLists.txt +++ b/contrib/vmap_extractor/CMakeLists.txt @@ -19,7 +19,7 @@ ADD_DEFINITIONS("-Wall") ADD_DEFINITIONS("-ggdb") ADD_DEFINITIONS("-O3") -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +if (APPLE) add_definitions("-Dfopen64=fopen") add_definitions("-Dfseeko64=fseeko") add_definitions("-Dfseek64=fseek") diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index 9a25a1fd8..e85097eaf 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/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} From 437edeec8bf6e21288ae99305de523eaa4aa317b Mon Sep 17 00:00:00 2001 From: Harley Larsen Date: Sat, 4 Mar 2017 23:42:04 -0800 Subject: [PATCH 07/12] Playerbot: first pass merging classic updates into tbc --- src/game/Chat.cpp | 2 +- src/game/Group.cpp | 2 +- src/game/Player.h | 8 +- src/game/WorldSession.h | 2 +- src/game/playerbot/PlayerbotAI.cpp | 1776 +++++++++++++-------- src/game/playerbot/PlayerbotAI.h | 146 +- src/game/playerbot/PlayerbotClassAI.cpp | 484 +++++- src/game/playerbot/PlayerbotClassAI.h | 65 +- src/game/playerbot/PlayerbotDruidAI.cpp | 1213 +++++++------- src/game/playerbot/PlayerbotDruidAI.h | 46 +- src/game/playerbot/PlayerbotHunterAI.cpp | 452 +++--- src/game/playerbot/PlayerbotHunterAI.h | 72 +- src/game/playerbot/PlayerbotMageAI.cpp | 808 +++++----- src/game/playerbot/PlayerbotMageAI.h | 58 +- src/game/playerbot/PlayerbotMgr.cpp | 573 ++++--- src/game/playerbot/PlayerbotPaladinAI.cpp | 1065 +++++++----- src/game/playerbot/PlayerbotPaladinAI.h | 47 +- src/game/playerbot/PlayerbotPriestAI.cpp | 885 +++++----- src/game/playerbot/PlayerbotPriestAI.h | 42 +- src/game/playerbot/PlayerbotRogueAI.cpp | 629 +++++--- src/game/playerbot/PlayerbotRogueAI.h | 78 +- src/game/playerbot/PlayerbotShamanAI.cpp | 875 +++++----- src/game/playerbot/PlayerbotShamanAI.h | 92 +- src/game/playerbot/PlayerbotWarlockAI.cpp | 1000 +++++++----- src/game/playerbot/PlayerbotWarlockAI.h | 46 +- src/game/playerbot/PlayerbotWarriorAI.cpp | 822 ++++++---- src/game/playerbot/PlayerbotWarriorAI.h | 88 +- 27 files changed, 6922 insertions(+), 4454 deletions(-) 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.h b/src/game/Player.h index 236be5b81..f07938e92 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -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/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..680e24fbc 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); //hlarsen test - 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, true); //hlarsen test 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) @@ -1473,11 +1491,12 @@ void PlayerbotAI::SendOrders(Player& /*player*/) else if (m_movementOrder == MOVEMENT_STAY) out << "STAY"; out << ". Got " << m_attackerInfo.size() << " attacker(s) in list."; - out << " Next action in " << m_ignoreAIUpdatesUntilTime - CurrentTime() << "sec."; + out << " Next action in " << m_ignoreAIUpdatesUntilTime - CurrentTime() << "sec."; //hlarsen classic branch has this as -time(0), look into it } 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,7 +2274,7 @@ 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 @@ -2265,7 +2283,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) for (LootItemList::const_iterator lootItr = lootList.begin(); lootItr != lootList.end(); ++lootItr) { - LootItem* lootItem = *lootItr; + LootItem* lootItem = *lootItr; // Skip non lootable items if (lootItem->GetSlotTypeForSharedLoot(m_bot, loot) != LOOT_SLOT_NORMAL) @@ -2310,7 +2328,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; } - case SMSG_LOOT_RELEASE_RESPONSE: + case SMSG_LOOT_RELEASE_RESPONSE: { WorldPacket p(packet); ObjectGuid guid; @@ -2319,7 +2337,6 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) if (guid == m_lootCurrent) { - Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); if (c && c->GetCreatureInfo()->SkinningLootId && !c->GetLootStatus() != CREATURE_LOOT_STATUS_LOOTED) @@ -2361,7 +2378,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) 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 +2398,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 +2411,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 +2468,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);*/ } - */ } } @@ -2749,8 +2765,8 @@ Item* PlayerbotAI::FindBandage() const } 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 +2779,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 +2798,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 +2806,92 @@ 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; +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; + } } } - // list out items in other removable backpacks - for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) { - const Bag* const pBag = (Bag *) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); - if (pBag) - for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + 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,6 +2904,30 @@ void PlayerbotAI::InterruptCurrentCastingSpell() m_bot->GetSession()->QueuePacket(std::move(packet)); } +// intelligently sets a reasonable combat order for this bot +// based on its class / level / etc +void PlayerbotAI::Attack(Unit* forcedTarget) +{ + // set combat state, and clear looting, etc... + if (m_botState != BOTSTATE_COMBAT) + { + 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 @@ -2909,7 +2997,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 +3012,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 +3030,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 +3058,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 +3092,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 +3117,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 +3170,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 +3230,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 +3239,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 +3618,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 +3679,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 @@ -3350,14 +3725,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 +3930,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 +3987,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 +4068,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 +4092,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 +4129,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 +4318,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 { @@ -3986,111 +4378,43 @@ 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; - if (m_FollowAutoGo == FOLLOWAUTOGO_OFF) - { - DistOverRide = 0; //set initial adjustable follow settings - IsUpOrDown = 0; - gTempDist = 0.5f; - 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); + + 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(); + 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 (combatOrders & ORDERS_PRIMARY) SetCombatOrder(combatOrders, gPrimtarget); + if (combatOrders & ORDERS_SECONDARY) SetCombatOrder(combatOrders, gSectarget); } void PlayerbotAI::SetCombatOrderByStr(std::string str, Unit *target) { 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 +4426,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 +4437,61 @@ 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."); + if (!target) + { + TellMaster("The protect command requires a target."); + return; + } + else m_targetProtect = target; + break; + } + case ORDERS_PASSIVE: // 20(100000) + { + m_combatOrder = ORDERS_PASSIVE; + m_targetAssist = 0; + m_targetProtect = 0; return; } - else - m_targetProtect = target; - break; - } - case ORDERS_PASSIVE: // 20(100000) - { - m_combatOrder = ORDERS_PASSIVE; - SendOrders(*GetMaster()); - return; - } - case ORDERS_PULL: // 80(10000000) - { - if (!target) + case ORDERS_RESET: // FFFF(11111111) { - TellMaster("The pull command requires a target."); + 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; } - else - m_targetProtect = target; + default: 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; - } } - // 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 +4512,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 +4528,7 @@ void PlayerbotAI::MovementReset() { // stop moving... MovementClear(); + if (m_movementOrder == MOVEMENT_FOLLOW) { if (!m_followTarget) @@ -4534,7 +4823,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 +4918,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 +4984,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 +4996,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 +5032,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 +5098,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 +5129,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 +5179,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 +5215,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 +5286,13 @@ 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); + return true; } @@ -5072,7 +5383,7 @@ bool PlayerbotAI::Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player * sameOrBetterAuraFound = true; break; } - willBenefitFromSpell = willBenefitFromSpell || !sameOrBetterAuraFound; + willBenefitFromSpell = willBenefitFromSpell || !sameOrBetterAuraFound; } if (!willBenefitFromSpell) @@ -5418,10 +5729,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 +5742,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 +6122,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 +6160,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 +6420,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 +6555,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; @@ -6464,12 +6848,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 +6874,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 +7458,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 +7629,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 +7880,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); @@ -7713,7 +8101,7 @@ 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; } @@ -7723,8 +8111,8 @@ void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) 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,15 +8130,12 @@ 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); + SetCombatOrder(ORDERS_ASSIST, target); return; } SetCombatOrderByStr(text, target); @@ -7891,16 +8276,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 +8296,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 +8489,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 +9829,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)); + { + m_tasks.push_back(std::pair(TAKE, *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 +9975,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 +10075,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 +10111,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..71de19c9d 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,72 @@ 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 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 +179,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 +284,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 +391,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 +413,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 +435,14 @@ 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; + bool FindAmmo() const; uint8 _findItemSlot(Item* target); bool CanStore(); @@ -426,6 +498,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 +541,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 +563,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 +597,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 +660,7 @@ class MANGOS_DLL_SPEC PlayerbotAI CombatStyle m_combatStyle; CombatOrderType m_combatOrder; - ResistType m_resistType; + MovementOrderType m_movementOrder; ScenarioType m_ScenarioType; @@ -623,15 +708,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..5b3228252 100644 --- a/src/game/playerbot/PlayerbotClassAI.cpp +++ b/src/game/playerbot/PlayerbotClassAI.cpp @@ -1,18 +1,490 @@ #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) {} +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() { - // return false, if done with opening moves/spells + 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; } -void PlayerbotClassAI::DoNextCombatManeuver(Unit *) {} -void PlayerbotClassAI::DoNonCombatActions(){} +bool PlayerbotClassAI::CanPull() +{ + DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::CanPull() rather than class specific function"); + return false; +} -bool PlayerbotClassAI::BuffPlayer(Player* target) { +bool PlayerbotClassAI::CastHoTOnTank() +{ + DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::CastHoTOnTank() rather than class specific function"); return false; } + +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; +} + +/** + * 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..4f44e58df 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,56 @@ 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); + // 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..dd61208c0 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. + 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); + + //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..debb52c40 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,57 @@ 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); // 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); + 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 +81,283 @@ 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); - switch (ai->GetScenarioType()) + 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; +} + +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 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." ); + m_ai->CastSpell(BERSERKING, *m_bot); - // check if ranged combat is possible (set m_rangedCombat and switch auras + // check if ranged combat is possible bool meleeReach = m_bot->CanReachWithMeleeAttack(pTarget); if (meleeReach || !m_has_ammo) { // 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) { // switch to ranged combat m_rangedCombat = true; - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + 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)) - ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot); + m_ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot); - // ai->TellMaster("target dist %f",m_bot->GetCombatDistance(pTarget, true)); + // 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->CastSpell(pTarget, AUTO_SHOT, TRIGGERED_OLD_TRIGGERED); m_bot->setAttackTimer(RANGED_ATTACK,500); 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); } } + // damage spells - std::ostringstream out; - if (m_rangedCombat) + if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED) { - 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!"; + 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; + else if (RAPID_FIRE > 0 && m_ai->In_Reach(pTarget,RAPID_FIRE) && !m_bot->HasAura(RAPID_FIRE, EFFECT_INDEX_0) && m_ai->CastSpell(RAPID_FIRE, *m_bot)) + return RETURN_CONTINUE; + else if (MULTI_SHOT > 0 && m_ai->In_Reach(pTarget,MULTI_SHOT) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(MULTI_SHOT, *pTarget)) + return RETURN_CONTINUE; + else if (ARCANE_SHOT > 0 && m_ai->In_Reach(pTarget,ARCANE_SHOT) && m_ai->CastSpell(ARCANE_SHOT, *pTarget)) + return RETURN_CONTINUE; + else if (CONCUSSIVE_SHOT > 0 && m_ai->In_Reach(pTarget,CONCUSSIVE_SHOT) && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CONCUSSIVE_SHOT, *pTarget)) + return RETURN_CONTINUE; + else if (VIPER_STING > 0 && m_ai->In_Reach(pTarget,VIPER_STING) && pTarget->GetPower(POWER_MANA) > 0 && m_ai->GetManaPercent() < 70 && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && m_ai->CastSpell(VIPER_STING, *pTarget)) + return RETURN_CONTINUE; + else if (SERPENT_STING > 0 && m_ai->In_Reach(pTarget,SERPENT_STING) && !pTarget->HasAura(SERPENT_STING, EFFECT_INDEX_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_INDEX_0) && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && m_ai->CastSpell(SERPENT_STING, *pTarget)) + return RETURN_CONTINUE; + else if (SCORPID_STING > 0 && m_ai->In_Reach(pTarget,SCORPID_STING) && !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) && m_ai->CastSpell(SCORPID_STING, *pTarget)) + return RETURN_CONTINUE; + else if (VOLLEY > 0 && m_ai->In_Reach(pTarget,VOLLEY) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(VOLLEY, *pTarget)) + return RETURN_CONTINUE; + else if (BLACK_ARROW > 0 && m_ai->In_Reach(pTarget,BLACK_ARROW) && !pTarget->HasAura(BLACK_ARROW, EFFECT_INDEX_0) && m_ai->CastSpell(BLACK_ARROW, *pTarget)) + return RETURN_CONTINUE; + else if (AIMED_SHOT > 0 && m_ai->In_Reach(pTarget,AIMED_SHOT) && m_ai->CastSpell(AIMED_SHOT, *pTarget)) + return RETURN_CONTINUE; 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 (RAPTOR_STRIKE > 0 && m_ai->In_Reach(pTarget,RAPTOR_STRIKE) && m_ai->CastSpell(RAPTOR_STRIKE, *pTarget)) + return RETURN_CONTINUE; + else 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; + else if (WING_CLIP > 0 && m_ai->In_Reach(pTarget,WING_CLIP) && !pTarget->HasAura(WING_CLIP, EFFECT_INDEX_0) && m_ai->CastSpell(WING_CLIP, *pTarget)) + return RETURN_CONTINUE; + else 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; + else if (MONGOOSE_BITE > 0 && m_ai->Impulse() && m_ai->CastSpell(MONGOOSE_BITE, *pTarget)) + return RETURN_CONTINUE; + else 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; + else if (DETERRENCE > 0 && pVictim == m_bot && m_bot->GetHealthPercent() < 50 && !m_bot->HasAura(DETERRENCE, EFFECT_INDEX_0) && m_ai->CastSpell(DETERRENCE, *m_bot)) + return RETURN_CONTINUE; + else 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) ) + else if(DISENGAGE > 0 && pVictim && m_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 +} + +void PlayerbotHunterAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) return; - // reset ranged combat state - if (!m_rangedCombat || ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE) + if (!m_rangedCombat || m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE) { m_rangedCombat = true; - ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); + m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED); } // buff group - if (TRUESHOT_AURA > 0) - (!m_bot->HasAura(TRUESHOT_AURA, EFFECT_INDEX_0) && ai->CastSpell (TRUESHOT_AURA, *m_bot)); + if (TRUESHOT_AURA > 0 && !m_bot->HasAura(TRUESHOT_AURA, EFFECT_INDEX_0)) + m_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)); + 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); - // mana check - 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 && ai->GetManaPercent() < 30) - { - ai->TellMaster("I could use a drink."); - ai->UseItem(pItem); + // hp/mana check + if (EatDrinkBandage()) 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) - { - 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); - 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 +375,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 +403,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..012eeabd6 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,77 @@ 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 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; + + 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; // 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..b5bef62d0 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,44 +702,43 @@ 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)) continue; - // changed the SellGarbage() function to support ch.SendSysMessaage() - bot->GetPlayerbotAI()->SellGarbage(*bot); + bot->GetPlayerbotAI()->SellGarbage(); 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 +749,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 +762,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 +777,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))) @@ -764,9 +787,8 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) } else { - // changed the SellGarbage() function to support ch.SendSysMessaage() bot->GetPlayerbotAI()->FollowAutoReset(); - bot->GetPlayerbotAI()->SellGarbage(*bot); + bot->GetPlayerbotAI()->SellGarbage(); } } return; @@ -788,51 +810,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 +868,6 @@ void PlayerbotMgr::LogoutAllBots() RemoveAllBotsFromGroup(); } - - void PlayerbotMgr::Stay() { for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) @@ -858,7 +877,6 @@ void PlayerbotMgr::Stay() } } - // Playerbot mod: logs out a Playerbot. void PlayerbotMgr::LogoutPlayerBot(ObjectGuid guid) { @@ -892,7 +910,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 +918,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 +1056,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 +1119,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..646e4601d 100644 --- a/src/game/playerbot/PlayerbotPaladinAI.h +++ b/src/game/playerbot/PlayerbotPaladinAI.h @@ -75,6 +75,11 @@ enum PaladinSpells SHADOW_RESISTANCE_AURA_1 = 19876, SHIELD_OF_RIGHTEOUSNESS_1 = 53600, 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 +90,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 +133,7 @@ class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI BLESSING_OF_MIGHT, HAMMER_OF_JUSTICE, RIGHTEOUS_FURY, + JUDGEMENT, CRUSADER_AURA, CRUSADER_STRIKE, AVENGING_WRATH, @@ -137,12 +161,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 +186,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 +201,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..4778a8e20 100644 --- a/src/game/playerbot/PlayerbotShamanAI.cpp +++ b/src/game/playerbot/PlayerbotShamanAI.cpp @@ -6,548 +6,469 @@ 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()) { - 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); - } + 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); } } - // 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; +} - switch (ai->GetScenarioType()) +CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/) +{ + return RETURN_NO_ACTION_OK; +} + +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 (masterHP < 30 && ai->GetManaPercent() >= 32) - ai->CastSpell(HEALING_WAVE, *(GetMaster())); - else if (masterHP < 70) - HealTarget (*GetMaster(), masterHP); + 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) + { + 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 - uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); - if (memberHP < 30) - HealTarget(*m_groupMember, memberHP); +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; + } + + // 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..7f9bfc34a 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 + uint8 freeSpace = m_ai->GetFreeBagSpace(); + uint8 HPThreshold = (m_ai->IsElite(pTarget) ? 10 : 25); + if (DRAIN_SOUL && 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..5cda9885c 100644 --- a/src/game/playerbot/PlayerbotWarriorAI.cpp +++ b/src/game/playerbot/PlayerbotWarriorAI.cpp @@ -10,334 +10,598 @@ 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); + + if (DEFENSIVE_STANCE && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK)) { - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Battle Stance (%d)", BATTLE_STANCE); - return true; + 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; } - else if (BATTLE_STANCE > 0 && CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + + if (BERSERKER_STANCE) { - 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(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)) { - 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; + 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; + } + } + } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster("First > Charge (%d)", CHARGE); - 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 false; + return RETURN_NO_ACTION_OK; } -void PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) +// TODO: blatant copy of PVE for now, please PVP-port it +CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVP(Unit *pTarget) { - PlayerbotAI* ai = GetAI(); - if (!ai) - return; + if (!m_ai) return RETURN_NO_ACTION_ERROR; + if (!m_bot) return RETURN_NO_ACTION_ERROR; - switch (ai->GetScenarioType()) + 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; + } + } + } + + 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; +} + +CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) +{ + // Face enemy, make sure bot is attacking + m_ai->FaceTarget(pTarget); + + 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 - - // Damage Attacks - - Player *m_bot = GetPlayerBot(); - 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; + + 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; + + //Unit* pVictim = pTarget->getVictim(); + //float fTargetDist = m_bot->GetCombatDistance(pTarget, true); + uint32 spec = m_bot->GetSpec(); + + //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); // 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; + switch (spec) + { + case WARRIOR_SPEC_ARMS: + // Execute doesn't scale too well with extra rage and uses up *all* rage preventing use of other skills + 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; + // No way to tell if overpower is active (yet), however taste for blood works + if (OVERPOWER > 0 && 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)) + { + m_ai->SetIgnoreUpdateTime(1.5); // TODO: SetIgnoreUpdateTime takes a uint8 - how will 1.5 work as a value? + return RETURN_CONTINUE; + } + + case WARRIOR_SPEC_FURY: + 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; + + case 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; + } + } - case WarriorBerserker: - out << "Case Berserker"; - if (WHIRLWIND > 0 && ai->GetRageAmount() >= 25 && ai->CastSpell(WHIRLWIND, *pTarget)) - out << " > Whirlwind"; - out << " > NONE"; + 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; + + /*case WarriorSpellPreventing: + if (SHIELD_BASH > 0 && m_ai->CastSpell(SHIELD_BASH, *pTarget)) + return RETURN_CONTINUE; + if (PUMMEL > 0 && m_ai->CastSpell(PUMMEL, *pTarget)) + return RETURN_CONTINUE; break; + + case WarriorBattle: + if (DEATH_WISH > 0 && !m_bot->HasAura(DEATH_WISH, EFFECT_INDEX_0) && m_ai->CastSpell(DEATH_WISH, *m_bot)) + return RETURN_CONTINUE; + if (RETALIATION > 0 && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && !m_bot->HasAura(RETALIATION, EFFECT_INDEX_0) && m_ai->CastSpell(RETALIATION, *m_bot)) + return RETURN_CONTINUE; + 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;*/ } - if (ai->GetManager()->m_confDebugWhisper) - ai->TellMaster(out.str().c_str()); + + return RETURN_NO_ACTION_OK; } -void PlayerbotWarriorAI::DoNonCombatActions() +CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVP(Unit* pTarget) +{ + 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() { - PlayerbotAI *ai = GetAI(); - Player * m_bot = GetPlayerBot(); - if (!m_bot) + if (!m_ai) return; + if (!m_bot) return; + + if (!m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0) && m_ai->CastSpell(BATTLE_SHOUT)) return; +} + +void PlayerbotWarriorAI::DoNonCombatActions() +{ + if (!m_ai) return; + if (!m_bot) 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())); + uint32 spec = m_bot->GetSpec(); - // hp check - if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) - m_bot->SetStandState(UNIT_STAND_STATE_STAND); + //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); - Item* pItem = ai->FindFood(); - Item* fItem = ai->FindBandage(); + // hp check + if (EatDrinkBandage(false)) + return; - if (pItem != nullptr && ai->GetHealthPercent() < 30) + // 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) { - ai->TellMaster("I could use some food."); - ai->UseItem(pItem); - return; + 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; }; From 8e457ef886d6af5679db326a148675dcc230e9e0 Mon Sep 17 00:00:00 2001 From: Harley Larsen Date: Sun, 5 Mar 2017 00:17:31 -0800 Subject: [PATCH 08/12] Playerbot: misc fixes and updates to the playerbot sql need to check and update the queries and then we'll be ready to test --- sql/playerbotai/FULL_playerbotai_characters_r5.sql | 3 +-- src/game/playerbot/PlayerbotAI.cpp | 3 +-- src/game/playerbot/PlayerbotMgr.cpp | 6 ++++-- src/game/playerbot/PlayerbotPaladinAI.h | 3 ++- src/game/playerbot/PlayerbotShamanAI.cpp | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sql/playerbotai/FULL_playerbotai_characters_r5.sql b/sql/playerbotai/FULL_playerbotai_characters_r5.sql index ca3d1a8a5..52da111a3 100644 --- a/sql/playerbotai/FULL_playerbotai_characters_r5.sql +++ b/sql/playerbotai/FULL_playerbotai_characters_r5.sql @@ -30,8 +30,7 @@ UNLOCK TABLES; DROP TABLE IF EXISTS `playerbot_saved_data`; CREATE TABLE `playerbot_saved_data` ( `guid` int(11) unsigned NOT NULL DEFAULT '0', - `bot_primary_order` tinyint(3) unsigned NOT NULL DEFAULT '0', - `bot_secondary_order` tinyint(3) unsigned NOT NULL DEFAULT '0', + `combat_order` tinyint(3) unsigned NOT NULL DEFAULT '0', `primary_target` int(11) unsigned NOT NULL DEFAULT '0', `secondary_target` int(11) unsigned NOT NULL DEFAULT '0', `pname` varchar(12) NOT NULL DEFAULT '', diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 680e24fbc..13a71e695 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -4477,7 +4477,6 @@ void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) } default: break; - } } // Do your magic @@ -9830,7 +9829,7 @@ void PlayerbotAI::_HandleCommandQuest(std::string &text, Player &fromPlayer) extractQuestIds(text, questIds); for (std::list::iterator it = questIds.begin(); it != questIds.end(); it++) { - m_tasks.push_back(std::pair(TAKE, *it)); + m_tasks.push_back(std::pair(TAKE_QUEST, *it)); DEBUG_LOG(" questid (%u)",*it); } m_findNPC.push_back(UNIT_NPC_FLAG_QUESTGIVER); diff --git a/src/game/playerbot/PlayerbotMgr.cpp b/src/game/playerbot/PlayerbotMgr.cpp index b5bef62d0..731336ac4 100644 --- a/src/game/playerbot/PlayerbotMgr.cpp +++ b/src/game/playerbot/PlayerbotMgr.cpp @@ -720,7 +720,8 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (!botConfig.GetBoolDefault("PlayerbotAI.SellGarbage", true)) continue; - bot->GetPlayerbotAI()->SellGarbage(); + // changed the SellGarbage() function to support ch.SendSysMessaage() + bot->GetPlayerbotAI()->SellGarbage(*bot); break; } case GOSSIP_OPTION_STABLEPET: @@ -787,8 +788,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) } else { + // changed the SellGarbage() function to support ch.SendSysMessaage() bot->GetPlayerbotAI()->FollowAutoReset(); - bot->GetPlayerbotAI()->SellGarbage(); + bot->GetPlayerbotAI()->SellGarbage(*bot); } } return; diff --git a/src/game/playerbot/PlayerbotPaladinAI.h b/src/game/playerbot/PlayerbotPaladinAI.h index 646e4601d..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,7 @@ 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 diff --git a/src/game/playerbot/PlayerbotShamanAI.cpp b/src/game/playerbot/PlayerbotShamanAI.cpp index 4778a8e20..e31e08f94 100644 --- a/src/game/playerbot/PlayerbotShamanAI.cpp +++ b/src/game/playerbot/PlayerbotShamanAI.cpp @@ -1,6 +1,6 @@ - #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) From f52792fa3adcca7800487b35ace7af2c4704d303 Mon Sep 17 00:00:00 2001 From: Harley Larsen Date: Sun, 5 Mar 2017 19:31:57 -0800 Subject: [PATCH 09/12] Playerbot: playerbot updates up to blueboy/portalclassic@e37bf57, sql and fixes for updated playerbot code --- .../FULL_playerbotai_characters_r5.sql | 3 +- .../playerbotai_characters_r6_update.sql | 2 + src/game/playerbot/PlayerbotAI.cpp | 123 +++++++++- src/game/playerbot/PlayerbotAI.h | 38 +++ src/game/playerbot/PlayerbotClassAI.cpp | 188 ++++++++++++++ src/game/playerbot/PlayerbotClassAI.h | 5 + src/game/playerbot/PlayerbotDruidAI.cpp | 8 +- src/game/playerbot/PlayerbotHunterAI.cpp | 194 ++++++++++----- src/game/playerbot/PlayerbotHunterAI.h | 7 +- src/game/playerbot/PlayerbotWarlockAI.cpp | 4 +- src/game/playerbot/PlayerbotWarriorAI.cpp | 229 ++++++++++-------- 11 files changed, 624 insertions(+), 177 deletions(-) create mode 100644 sql/playerbotai/playerbotai_characters_r6_update.sql diff --git a/sql/playerbotai/FULL_playerbotai_characters_r5.sql b/sql/playerbotai/FULL_playerbotai_characters_r5.sql index 52da111a3..ca3d1a8a5 100644 --- a/sql/playerbotai/FULL_playerbotai_characters_r5.sql +++ b/sql/playerbotai/FULL_playerbotai_characters_r5.sql @@ -30,7 +30,8 @@ UNLOCK TABLES; DROP TABLE IF EXISTS `playerbot_saved_data`; CREATE TABLE `playerbot_saved_data` ( `guid` int(11) unsigned NOT NULL DEFAULT '0', - `combat_order` tinyint(3) unsigned NOT NULL DEFAULT '0', + `bot_primary_order` tinyint(3) unsigned NOT NULL DEFAULT '0', + `bot_secondary_order` tinyint(3) unsigned NOT NULL DEFAULT '0', `primary_target` int(11) unsigned NOT NULL DEFAULT '0', `secondary_target` int(11) unsigned NOT NULL DEFAULT '0', `pname` varchar(12) NOT NULL DEFAULT '', 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/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 13a71e695..69f46d710 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -1491,7 +1491,7 @@ void PlayerbotAI::SendOrders(Player& /*player*/) else if (m_movementOrder == MOVEMENT_STAY) out << "STAY"; out << ". Got " << m_attackerInfo.size() << " attacker(s) in list."; - out << " Next action in " << m_ignoreAIUpdatesUntilTime - CurrentTime() << "sec."; //hlarsen classic branch has this as -time(0), look into it + out << " Next action in " << m_ignoreAIUpdatesUntilTime - CurrentTime() << "sec."; } TellMaster(out.str().c_str()); @@ -2739,7 +2739,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; } } @@ -2758,7 +2758,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; } } @@ -2855,6 +2855,49 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const return nullptr; } +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; + } + } + // Else use mana potion (and knowingly trigger the health potion cooldown) + for (uint8 i = 0; i < countof(uPriorizedManaPotionIds); ++i) + { + 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) @@ -4369,7 +4412,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) { @@ -4386,6 +4429,7 @@ void PlayerbotAI::CombatOrderRestore() 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; @@ -4394,12 +4438,23 @@ void PlayerbotAI::CombatOrderRestore() //ObjectGuid NotargetGUID = m_bot->GetObjectGuid(); //target = ObjectAccessor::GetUnit(*m_bot, NotargetGUID); + if (m_FollowAutoGo == FOLLOWAUTOGO_OFF) + { + DistOverRide = 0; //set initial adjustable follow settings + IsUpOrDown = 0; + gTempDist = 0.5f; + gTempDist2 = 1.0f; + SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); + } + 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; @@ -5290,7 +5345,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId) 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; } @@ -6770,6 +6826,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) { @@ -8077,6 +8179,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; @@ -8106,7 +8212,7 @@ void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) 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; @@ -8135,10 +8241,9 @@ void PlayerbotAI::_HandleCommandOrders(std::string &text, Player &fromPlayer) SetCombatOrder(ORDERS_PROTECT, target); else if (assist != std::string::npos) SetCombatOrder(ORDERS_ASSIST, target); - return; } - SetCombatOrderByStr(text, target); - return; + else + SetCombatOrderByStr(text, target); } else if (text != "") { diff --git a/src/game/playerbot/PlayerbotAI.h b/src/game/playerbot/PlayerbotAI.h index 71de19c9d..9a1ce8f04 100644 --- a/src/game/playerbot/PlayerbotAI.h +++ b/src/game/playerbot/PlayerbotAI.h @@ -93,6 +93,41 @@ enum WeightStoneDisplayId 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, @@ -442,6 +477,7 @@ class MANGOS_DLL_SPEC PlayerbotAI 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(); @@ -467,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); diff --git a/src/game/playerbot/PlayerbotClassAI.cpp b/src/game/playerbot/PlayerbotClassAI.cpp index 5b3228252..b507b24e2 100644 --- a/src/game/playerbot/PlayerbotClassAI.cpp +++ b/src/game/playerbot/PlayerbotClassAI.cpp @@ -1,6 +1,11 @@ #include "PlayerbotClassAI.h" #include "Common.h" +#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; @@ -297,6 +302,189 @@ Player* PlayerbotClassAI::GetHealTarget(JOB_TYPE type) 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; + } + } + + 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. diff --git a/src/game/playerbot/PlayerbotClassAI.h b/src/game/playerbot/PlayerbotClassAI.h index 4f44e58df..9260df90e 100644 --- a/src/game/playerbot/PlayerbotClassAI.h +++ b/src/game/playerbot/PlayerbotClassAI.h @@ -77,6 +77,11 @@ class MANGOS_DLL_SPEC PlayerbotClassAI 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; diff --git a/src/game/playerbot/PlayerbotDruidAI.cpp b/src/game/playerbot/PlayerbotDruidAI.cpp index dd61208c0..b0445e8f1 100644 --- a/src/game/playerbot/PlayerbotDruidAI.cpp +++ b/src/game/playerbot/PlayerbotDruidAI.cpp @@ -182,11 +182,11 @@ CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuverPVE(Unit* pTarget) 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. - 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) + // 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); diff --git a/src/game/playerbot/PlayerbotHunterAI.cpp b/src/game/playerbot/PlayerbotHunterAI.cpp index debb52c40..81f61a814 100644 --- a/src/game/playerbot/PlayerbotHunterAI.cpp +++ b/src/game/playerbot/PlayerbotHunterAI.cpp @@ -40,12 +40,15 @@ PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, Pl 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 = 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); @@ -85,7 +88,7 @@ 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)); + DEBUG_LOG("current ammo (%u)",m_bot->GetUInt32Value(PLAYER_AMMO_ID)); m_bot->setAttackTimer(RANGED_ATTACK,0); if (!m_has_ammo) { @@ -195,16 +198,26 @@ CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVE(Unit *pTarget) 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)) m_ai->CastSpell(BLOOD_FURY, *m_bot); else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0)) m_ai->CastSpell(BERSERKING, *m_bot); - - // check if ranged combat is possible + */ + // 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; @@ -215,88 +228,139 @@ CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVE(Unit *pTarget) // become monkey (increases dodge chance)... if (ASPECT_OF_THE_MONKEY > 0 && !m_bot->HasAura(ASPECT_OF_THE_MONKEY, EFFECT_INDEX_0)) m_ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot); - } - else if (!meleeReach) - { - // 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); + if (TRANQUILIZING_SHOT > 0 && IsTargetEnraged(pTarget) && !m_bot->HasSpellCooldown(TRANQUILIZING_SHOT) && m_ai->CastSpell(TRANQUILIZING_SHOT, *pTarget)) + { + m_ai->TellMaster("Casting TRANQUILIZING SHOT onto %s", pTarget->GetName()); + return RETURN_CONTINUE; + } - // m_ai->TellMaster("target dist %f",m_bot->GetCombatDistance(pTarget,true)); - if (AUTO_SHOT > 0) + //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)) { - if (m_bot->isAttackReady(RANGED_ATTACK)) - m_bot->CastSpell(pTarget, AUTO_SHOT, TRIGGERED_OLD_TRIGGERED); - - m_bot->setAttackTimer(RANGED_ATTACK,500); - - const SpellEntry* spellInfo = sSpellTemplate.LookupEntry(AUTO_SHOT); - if (!spellInfo) + // 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; - - if (m_ai->CheckBotCast(spellInfo) != SPELL_CAST_OK) - m_bot->InterruptNonMeleeSpells(true, AUTO_SHOT); } } + // 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; - else if (RAPID_FIRE > 0 && m_ai->In_Reach(pTarget,RAPID_FIRE) && !m_bot->HasAura(RAPID_FIRE, EFFECT_INDEX_0) && m_ai->CastSpell(RAPID_FIRE, *m_bot)) - return RETURN_CONTINUE; - else if (MULTI_SHOT > 0 && m_ai->In_Reach(pTarget,MULTI_SHOT) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(MULTI_SHOT, *pTarget)) - return RETURN_CONTINUE; - else if (ARCANE_SHOT > 0 && m_ai->In_Reach(pTarget,ARCANE_SHOT) && m_ai->CastSpell(ARCANE_SHOT, *pTarget)) - return RETURN_CONTINUE; - else if (CONCUSSIVE_SHOT > 0 && m_ai->In_Reach(pTarget,CONCUSSIVE_SHOT) && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_INDEX_0) && m_ai->CastSpell(CONCUSSIVE_SHOT, *pTarget)) - return RETURN_CONTINUE; - else if (VIPER_STING > 0 && m_ai->In_Reach(pTarget,VIPER_STING) && pTarget->GetPower(POWER_MANA) > 0 && m_ai->GetManaPercent() < 70 && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && m_ai->CastSpell(VIPER_STING, *pTarget)) + // 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; - else if (SERPENT_STING > 0 && m_ai->In_Reach(pTarget,SERPENT_STING) && !pTarget->HasAura(SERPENT_STING, EFFECT_INDEX_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_INDEX_0) && !pTarget->HasAura(VIPER_STING, EFFECT_INDEX_0) && m_ai->CastSpell(SERPENT_STING, *pTarget)) + 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; - else if (SCORPID_STING > 0 && m_ai->In_Reach(pTarget,SCORPID_STING) && !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) && m_ai->CastSpell(SCORPID_STING, *pTarget)) + // 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; - else if (VOLLEY > 0 && m_ai->In_Reach(pTarget,VOLLEY) && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(VOLLEY, *pTarget)) + 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; - else if (BLACK_ARROW > 0 && m_ai->In_Reach(pTarget,BLACK_ARROW) && !pTarget->HasAura(BLACK_ARROW, EFFECT_INDEX_0) && m_ai->CastSpell(BLACK_ARROW, *pTarget)) + 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; - else if (AIMED_SHOT > 0 && m_ai->In_Reach(pTarget,AIMED_SHOT) && m_ai->CastSpell(AIMED_SHOT, *pTarget)) + + //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)) + m_bot->CastSpell(pTarget, AUTO_SHOT, TRIGGERED_OLD_TRIGGERED); + + const SpellEntry* spellInfo = sSpellTemplate.LookupEntry(AUTO_SHOT); + if (!spellInfo) + return RETURN_CONTINUE; + + if (m_ai->CheckBotCast(spellInfo) != SPELL_CAST_OK) + m_bot->InterruptNonMeleeSpells(true, AUTO_SHOT); + return RETURN_CONTINUE; - else - return RETURN_NO_ACTION_OK; + } + + return RETURN_NO_ACTION_OK; } else { - if (RAPTOR_STRIKE > 0 && m_ai->In_Reach(pTarget,RAPTOR_STRIKE) && m_ai->CastSpell(RAPTOR_STRIKE, *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; - else 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)) + 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; - else if (WING_CLIP > 0 && m_ai->In_Reach(pTarget,WING_CLIP) && !pTarget->HasAura(WING_CLIP, EFFECT_INDEX_0) && m_ai->CastSpell(WING_CLIP, *pTarget)) + 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; - else 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)) + 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; - else if (MONGOOSE_BITE > 0 && m_ai->Impulse() && m_ai->CastSpell(MONGOOSE_BITE, *pTarget)) + 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; - else 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)) + 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; - else if (DETERRENCE > 0 && pVictim == m_bot && m_bot->GetHealthPercent() < 50 && !m_bot->HasAura(DETERRENCE, EFFECT_INDEX_0) && m_ai->CastSpell(DETERRENCE, *m_bot)) - return RETURN_CONTINUE; - else 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; + + //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(DISENGAGE > 0 && pVictim && m_ai->CastSpell(DISENGAGE,*pTarget) ) - out << " > Disengage!"; // attempt to return to ranged combat*/ + */ } return RETURN_NO_ACTION_OK; @@ -310,6 +374,24 @@ CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVP(Unit* pTarget) return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative } +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) + { + 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; + } + + return false; +} + void PlayerbotHunterAI::DoNonCombatActions() { if (!m_ai) return; diff --git a/src/game/playerbot/PlayerbotHunterAI.h b/src/game/playerbot/PlayerbotHunterAI.h index 012eeabd6..5be418911 100644 --- a/src/game/playerbot/PlayerbotHunterAI.h +++ b/src/game/playerbot/PlayerbotHunterAI.h @@ -108,6 +108,7 @@ class MANGOS_DLL_SPEC PlayerbotHunterAI : PlayerbotClassAI CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget); // Hunter + bool IsTargetEnraged(Unit* pTarget); bool m_petSummonFailed; bool m_rangedCombat; bool m_has_ammo; @@ -139,13 +140,15 @@ class MANGOS_DLL_SPEC PlayerbotHunterAI : PlayerbotClassAI CHIMERA_SHOT, VOLLEY, BLACK_ARROW, - KILL_SHOT; + KILL_SHOT, + TRANQUILIZING_SHOT; uint32 RAPTOR_STRIKE, WING_CLIP, MONGOOSE_BITE, DISENGAGE, - DETERRENCE; + DETERRENCE, + FEIGN_DEATH; uint32 BEAR_TRAP, FREEZING_TRAP, diff --git a/src/game/playerbot/PlayerbotWarlockAI.cpp b/src/game/playerbot/PlayerbotWarlockAI.cpp index 7f9bfc34a..6b677508d 100644 --- a/src/game/playerbot/PlayerbotWarlockAI.cpp +++ b/src/game/playerbot/PlayerbotWarlockAI.cpp @@ -237,10 +237,10 @@ CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuverPVE(Unit *pTarget) } } - // Create soul shard + // Create soul shard (only on non-worldboss) uint8 freeSpace = m_ai->GetFreeBagSpace(); uint8 HPThreshold = (m_ai->IsElite(pTarget) ? 10 : 25); - if (DRAIN_SOUL && pTarget->GetHealthPercent() < HPThreshold && m_ai->In_Reach(pTarget, DRAIN_SOUL) && + 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); diff --git a/src/game/playerbot/PlayerbotWarriorAI.cpp b/src/game/playerbot/PlayerbotWarriorAI.cpp index 5cda9885c..99bb2e624 100644 --- a/src/game/playerbot/PlayerbotWarriorAI.cpp +++ b/src/game/playerbot/PlayerbotWarriorAI.cpp @@ -149,15 +149,10 @@ CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVE(Unit* pTarget 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; - } + // 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) + if (BERSERKER_STANCE && spec == WARRIOR_SPEC_FURY && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST)) { if (!m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BERSERKER_STANCE)) return RETURN_CONTINUE; @@ -178,8 +173,7 @@ CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVE(Unit* pTarget } } } - - if (BATTLE_STANCE) + else if (BATTLE_STANCE && (spec == WARRIOR_SPEC_ARMS || (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_ASSIST))) { if (!m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && m_ai->CastSpell(BATTLE_STANCE)) return RETURN_CONTINUE; @@ -198,6 +192,13 @@ CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVE(Unit* pTarget } } } + 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; + } return RETURN_NO_ACTION_OK; } @@ -291,12 +292,11 @@ CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVE(Unit *pTarget) if (!m_ai) return RETURN_NO_ACTION_ERROR; if (!m_bot) return RETURN_NO_ACTION_ERROR; - //Unit* pVictim = pTarget->getVictim(); //float fTargetDist = m_bot->GetCombatDistance(pTarget, true); - uint32 spec = m_bot->GetSpec(); //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(); // do shouts, berserker rage, etc... if (BERSERKER_RAGE > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_INDEX_0)) @@ -315,115 +315,139 @@ CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVE(Unit *pTarget) CheckShouts(); - switch (spec) + // 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)) { - case WARRIOR_SPEC_ARMS: - // Execute doesn't scale too well with extra rage and uses up *all* rage preventing use of other skills - 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; - // No way to tell if overpower is active (yet), however taste for blood works - if (OVERPOWER > 0 && 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)) - { - m_ai->SetIgnoreUpdateTime(1.5); // TODO: SetIgnoreUpdateTime takes a uint8 - how will 1.5 work as a value? + // 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 WARRIOR_SPEC_FURY: - 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)) + 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; + } - case 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 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 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) + 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) { - // 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"); + if ( !m_bot->HasSpellCooldown(OVERPOWER) && m_ai->CastSpell(OVERPOWER, *pTarget)) 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; - } } + } + 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 (REVENGE > 0 && !m_bot->HasSpellCooldown(REVENGE)) + // 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)) { - 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)) + m_ai->TellMaster("I'm using LAST STAND"); 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)) + // 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; + } + } - /*case WarriorSpellPreventing: + // 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 (PUMMEL > 0 && m_ai->CastSpell(PUMMEL, *pTarget)) - return RETURN_CONTINUE; - break; + } - case WarriorBattle: - if (DEATH_WISH > 0 && !m_bot->HasAura(DEATH_WISH, EFFECT_INDEX_0) && m_ai->CastSpell(DEATH_WISH, *m_bot)) - return RETURN_CONTINUE; - if (RETALIATION > 0 && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && !m_bot->HasAura(RETALIATION, EFFECT_INDEX_0) && m_ai->CastSpell(RETALIATION, *m_bot)) + 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; + } + + /* 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)) @@ -451,7 +475,6 @@ CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVE(Unit *pTarget) 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; } From 47de5cf6baf95915db0a1dc953f6c2cfb2e0693a Mon Sep 17 00:00:00 2001 From: Harley Larsen Date: Mon, 6 Mar 2017 07:37:38 -0800 Subject: [PATCH 10/12] Playerot: remove debug todo and revert a change --- src/game/playerbot/PlayerbotAI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 69f46d710..0c555ad89 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -131,7 +131,7 @@ Player* PlayerbotAI::GetMaster() const bool PlayerbotAI::CanReachWithSpellAttack(Unit* target) { bool inrange = false; - float dist = m_bot->GetCombatDistance(target, false); //hlarsen test + float dist = m_bot->GetCombatDistance(target, false); for (SpellRanges::iterator itr = m_spellRangeMap.begin(); itr != m_spellRangeMap.end(); ++itr) { @@ -172,7 +172,7 @@ bool PlayerbotAI::In_Reach(Unit* Target, uint32 spellId) return false; float range = 0; - float dist = m_bot->GetCombatDistance(Target, true); //hlarsen test + float dist = m_bot->GetCombatDistance(Target, false); SpellRanges::iterator it; it = m_spellRangeMap.find(spellId); (it != m_spellRangeMap.end()) ? range = it->second : range = 0; From e0124e4c6a9b447cd815b656db1392af74050cc9 Mon Sep 17 00:00:00 2001 From: Harley Larsen Date: Mon, 6 Mar 2017 23:01:46 -0800 Subject: [PATCH 11/12] Playerbot: fix for bots not looting --- src/game/playerbot/PlayerbotAI.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 0c555ad89..2886a35ed 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -2284,10 +2284,15 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) for (LootItemList::const_iterator lootItr = lootList.begin(); lootItr != lootList.end(); ++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; + } // If bot is skinning or has collect all orders: autostore all items // else bot has order to only loot quest or useful items @@ -2324,7 +2329,6 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // release loot loot->Release(m_bot); - return; } @@ -5050,7 +5054,7 @@ 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) { if (GetCombatOrder() & ORDERS_TEMP_WAIT_TANKAGGRO) TellMaster("I was still waiting for the tank to gain aggro, but that doesn't make sense anymore..."); From 60a51fca6f316007e619edcf198ccf5220da126c Mon Sep 17 00:00:00 2001 From: hlarsen Date: Wed, 8 Mar 2017 09:24:19 -0800 Subject: [PATCH 12/12] Playerbot: another bot looting fix bots were continually trying to loot corpses which only had items tagged for other players. now we simply check that the corpse has items we can loot and if not we're done. --- src/game/playerbot/PlayerbotAI.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp index 2886a35ed..3d639c6fa 100644 --- a/src/game/playerbot/PlayerbotAI.cpp +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -2281,6 +2281,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) LootItemList lootList; loot->GetLootItemsListFor(m_bot, lootList); + bool lootableItemsPresent = false; for (LootItemList::const_iterator lootItr = lootList.begin(); lootItr != lootList.end(); ++lootItr) { LootItem* lootItem = *lootItr; @@ -2294,6 +2295,8 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) 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 if (loot_type == LOOT_SKINNING || HasCollectFlag(COLLECT_FLAG_LOOT) || (loot_type == LOOT_CORPSE && (IsInQuestItemList(lootItem->itemId) || IsItemUseful(lootItem->itemId)))) @@ -2317,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()) { @@ -2327,6 +2330,15 @@ 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; @@ -2341,15 +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(); @@ -2379,6 +2397,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) SetIgnoreUpdateTime(0); } + // sLog.outError("PlayerbotAI: SMSG_LOOT_RELEASE_RESPONSE returning"); return; } @@ -3755,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