diff --git a/README.AntiCheat b/README.AntiCheat deleted file mode 100644 index 740ef8b6b..000000000 --- a/README.AntiCheat +++ /dev/null @@ -1,268 +0,0 @@ -============================================================================================== -English translations, big thx to Faq - -AntiCheat v3 for mangos, (c) 2010 /dev/rsa -http://github.com/rsa - -I express gratitude to authors of previous anticheat versions, on what base is made this code -- gimly -- CWN -also thnx to those who wrote code for extra checking, what used in this code -- Insider42 - -Anticheat configs can be changed in characters/anticheat_config table. -Config can be reloaded with ".reload anticheat" command. -In mangos configs left only these: - -Anticheat.Enable = 1 (Yes/No) -Anticheat.DelayAfterAction = 30 (skip this time after any actions, applyed to player -before other action be apply to his, but still all actions will be logged.) -Anticheat.GmLevel = 0 (level, higher levels wont be checked. 0 - player lvl.) - -== Base - -1. Checks - -There are usual checks (from 0 to 99) and additional (sub-checks, numbers - 100* number of usual check + 1...99) -1st INT parameter is checked for all checks by default - time after what, countdown of puncture will drop. -If not set - time is taken from "Anticheat.DelayAfterAction". - -main checks: - // Check types - CHECK_NULL = 0, -No parameters, empty check. - - CHECK_MOVEMENT = 1, -Pseudo-check. this function holds information about other movement checks. -Parameters - period (should be 0), actions and messages(not used) - - CHECK_SPELL = 2, -Pseudo-check. For now, nothing happens. - - CHECK_QUEST = 3, -In progress. - - CHECK_TRANSPORT = 4, -Check ON_TRANSPORT condition by Insider42 - - CHECK_DAMAGE = 5, -Check server bugs with damage. - -2. Subchecks -additional checks: - // Check subtypes - // Movement checks - CHECK_MOVEMENT_SPEED = 100 * CHECK_MOVEMENT + 1, -Check for speed hack. Parameters - period (check default value in db), 1 float - speed (0,01 - max normal speed. In rare cases including lags can be 0,02). - - CHECK_MOVEMENT_FLY = 100 * CHECK_MOVEMENT + 2, -check for fly hack -First parameter - height from what check is started (for avoiding (crashes, loops?? колодцев) and bug holes in vmaps). - - CHECK_MOVEMENT_MOUNTAIN = 100 * CHECK_MOVEMENT + 3, -Checking Wall climb hack. First float parameter - Z speed max, second - tangens of moving angle. - - CHECK_MOVEMENT_WATERWALKING = 100 * CHECK_MOVEMENT + 4, -Checking for water walk hack. - - CHECK_MOVEMENT_TP2PLANE = 100 * CHECK_MOVEMENT + 5, -Parameter - delta from 0 for plane and delta for check. With default parameters works fine. - - CHECK_MOVEMENT_AIRJUMP = 100 * CHECK_MOVEMENT + 6, -Jumping in air. First parameter - from what height check is triggered. - - CHECK_MOVEMENT_TELEPORT = 100 * CHECK_MOVEMENT + 7, -If distance is greater then 1 (opcode) from number in parameter - alarm is triggered. - -In future: - CHECK_MOVEMENT_FALL = 100 * CHECK_MOVEMENT + 8, - - // Spell checks - CHECK_SPELL_VALID = 100 * CHECK_SPELL + 1, -If client is sending spell cast, but its not in dbc file - alarm is triggered. -Also could be result of damaged packets from clients. - - CHECK_SPELL_ONDEATH = 100 * CHECK_SPELL + 2, -Not done yet - - CHECK_SPELL_FAMILY = 100 * CHECK_SPELL + 3, -Check works only for skill 769 (only GM spells). - - CHECK_SPELL_INBOOK = 100 * CHECK_SPELL + 4, -Still in progress (will do simple check - if player has spell in spell-book) - - // Damage checks - CHECK_DAMAGE_SPELL = 100 * CHECK_DAMAGE + 1, -Spell damage limit. Parameter - limited meaning. - - - CHECK_DAMAGE_MELEE = 100 * CHECK_DAMAGE + 2, -Melee damage limit. Parameter - limited meaning. - // End of list - CHECK_MAX - -3. Actions (what will happen if Anticheat will detect cheat) - -Can be set to any check. - - ANTICHEAT_ACTION_NULL = 0, -Empty action. Doesn't do nothing. If action1 = 0 then wont be logged. - - ANTICHEAT_ACTION_LOG = 1, -Will only registrate detected check fails. - - ANTICHEAT_ACTION_ANNOUNCE_GM = 2, -Announce to GMs about detected cheating action (GMs with lvl higher or equal to param1). If 0 - then message will be sent to all. - - ANTICHEAT_ACTION_ANNOUNCE_ALL = 3, -Announce into public chat about detected cheating action. - - ANTICHEAT_ACTION_KICK = 4, -Just kick. - - ANTICHEAT_ACTION_BAN = 5, -Just ban. Parameter - time in seconds. - - ANTICHEAT_ACTION_SHEEP = 6, -Transformation into random animal (if in fly - then with parachute). -Parameter - time in milliseconds. - - ANTICHEAT_ACTION_STUN = 7, -Simple stun. Parameter - time in milliseconds. - - ANTICHEAT_ACTION_SICKNESS = 8, -Adding resurrection sickness spell. Parameter - time in milliseconds. - -================================================================================================ -AntiCheat v3 for mangos, (c) 2010 /dev/rsa -http://github.com/rsa - -Выражаю благодарность авторам предыдущих атичитов, на базе которых сделан этот - -- gimly -- CWN -а также людям написавшим отдельные проверки, использованные здесь -- Insider42 - -Настройка античита производится в таблице characters/anticheat_config -Конфиг перегружается онлайн командой .reload anticheat -в конфиге мангоса остались только - -Anticheat.Enable = 1 (да/нет) -Anticheat.DelayAfterAction = 30 (время, которое после акции плеер не трогается (не применяются другие -actions). лог все равно ведется.) -Anticheat.GmLevel = 0 (уровень, выше которого не проверять. 0 - только игроков) - -== база - -1. Проверки -Проверки бывают основные (номера с 0 до 99) и дополнительные (subchecks, номера - 100* номер основной -проверки + 1...99) -у всех проверок 1й INT параметр по умолчанию - время через которого счетчик проколов сбрасывается. -если не указано - берется время из Anticheat.DelayAfterAction - -основные проверки: - // Check types - CHECK_NULL = 0, -нет параметров. пустая проверка-заглушка. - - CHECK_MOVEMENT = 1, -псевдопроверка. в ней проводится накопление информации для других проверок движения. -параметры - период (должен быть 0), экшны и сообщения (не используются) - - CHECK_SPELL = 2, -псевдопроверка. в ней пока ничего не проводится. - - CHECK_QUEST = 3, -в процессе. - - CHECK_TRANSPORT = 4, -проверка ON_TRANSPORT состояния by Insider42 - - CHECK_DAMAGE = 5, -проверка на баги сервера с дамагом. - -2. Субпроверки -дополнительные проверки: - // Check subtypes - // Movement checks - CHECK_MOVEMENT_SPEED = 100 * CHECK_MOVEMENT + 1, -Проверка на СХ. параметры - период (умолчания смотрите в базе), 1 float - скорость (0,01 - максимальная -нормализованная скорость. с учетом лагов божет быть до 0,02 но редко) - - CHECK_MOVEMENT_FLY = 100 * CHECK_MOVEMENT + 2, -первый параметр - высота с которой начинаем проверять (для обхода колодцев и дыр в вмапсах). - - CHECK_MOVEMENT_MOUNTAIN = 100 * CHECK_MOVEMENT + 3, -проверка на чит Wall climb. Первый параметр float - скорость перемещения по Z, второй - тангенс угла. - - CHECK_MOVEMENT_WATERWALKING = 100 * CHECK_MOVEMENT + 4, -проверка на чит с хождением по воде. - - CHECK_MOVEMENT_TP2PLANE = 100 * CHECK_MOVEMENT + 5, -параметры - дельта от 0 для plane и дельта для проверки. с дефолтными ловит нормально. - - CHECK_MOVEMENT_AIRJUMP = 100 * CHECK_MOVEMENT + 6, -прыжок в воздухе. первый параметр - от какой высоты начинаем проверять. - - CHECK_MOVEMENT_TELEPORT = 100 * CHECK_MOVEMENT + 7, -если расстояние за 1 тик (опкод) больше чем параметр - сразу аларм. - -в планах: - CHECK_MOVEMENT_FALL = 100 * CHECK_MOVEMENT + 8, - - // Spell checks - CHECK_SPELL_VALID = 100 * CHECK_SPELL + 1, -если клиент присылает каст спелла, а его в ДБЦ нету - сразу будет этот аларм. -может быть результатом битых пакетов. - - CHECK_SPELL_ONDEATH = 100 * CHECK_SPELL + 2, -в процессе - - CHECK_SPELL_FAMILY = 100 * CHECK_SPELL + 3, -в настоящее время проверка идет только на скилл 769 (ГМ-онли спеллы) - - CHECK_SPELL_INBOOK = 100 * CHECK_SPELL + 4, -в процессе (будет просто проверка есть ли спелл в книге у плеера) - - // Damage checks - CHECK_DAMAGE_SPELL = 100 * CHECK_DAMAGE + 1, -ограничение по спеллдамаге. параметр - просто предельное значение. - - CHECK_DAMAGE_MELEE = 100 * CHECK_DAMAGE + 2, -ограничение по мили-дамаге. параметр - просто предельное значение. - // End of list - CHECK_MAX - -3. Экшны (действия которые производит античит по обнаружению чита) - -можно поставить на любую проверку. - - ANTICHEAT_ACTION_NULL = 0, -пустой экшн. ничего не делать. если action1 = 0 то даже лог не ведется. - - ANTICHEAT_ACTION_LOG = 1, -только регистрация срабатываний. - - ANTICHEAT_ACTION_ANNOUNCE_GM = 2, -анонс найденного читера только ГМам с уровнем доступа выше или равным param1. если 0 - -значит всем - - ANTICHEAT_ACTION_ANNOUNCE_ALL = 3, -анонс найденного читера в общий чат. - - ANTICHEAT_ACTION_KICK = 4, -просто кик. - - ANTICHEAT_ACTION_BAN = 5, -просто бан. параметр - время в секундах. - - ANTICHEAT_ACTION_SHEEP = 6, -превращение в случайное животное (если в полете - то с парашютом). -параметр - время в миллисекундах. - - ANTICHEAT_ACTION_STUN = 7, -просто стан. параметр - время в миллисекундах. - - ANTICHEAT_ACTION_SICKNESS = 8, -наложение resurrection sickness. параметр - время в миллисекундах. -================================================================================================ \ No newline at end of file diff --git a/README.PLAYERBOT b/README.PLAYERBOT index f4d497544..80ccc6a28 100644 --- a/README.PLAYERBOT +++ b/README.PLAYERBOT @@ -21,8 +21,7 @@ Commands: /t BOTNAME stay /t BOTNAME assist (you'll need to be attacking something and the bot only does melee atm) /t BOTNAME spells (replies with all spells known to bot) -/t BOTNAME cast -/t BOTNAME cast +/t BOTNAME cast /t BOTNAME use /t BOTNAME equip /t BOTNAME reset (will reset states, orders and loot list) @@ -32,6 +31,14 @@ Commands: /t BOTNAME survey (bot shows all available gameobjects, within a local perimeter around the bot) /t BOTNAME find (bot will travel to the gameobject location and then wait) /t BOTNAME get (bot will fetch the selected gameobject and then return to the player) +/t BOTNAME quests (List bot's current quests) +/t BOTNAME drop (Drop a quest) +/t BOTNAME orders (Shows bot's combat orders) +/t BOTNAME pet spells (Shows spells known to bot's pet. Autocast spells will be shown in green) +/t BOTNAME pet cast +/t BOTNAME pet toggle (Toggle autocast for a given spell) +/t BOTNAME pet state (Shows current react mode of bot's pet) +/t BOTNAME pet react <(a)ggressive | (d)efensive | (p)assive> (Set bot's pet reaction mode) Shortcuts: c = cast diff --git a/TODO.PLAYERBOT b/TODO.PLAYERBOT index e6bad0c1c..59d71e654 100644 --- a/TODO.PLAYERBOT +++ b/TODO.PLAYERBOT @@ -16,7 +16,7 @@ Combat order TANK Combat order HEAL ///---Temporary item enchantments---/// -Rogue : Poison +[DONE] Rogue : Poison Warrior : Sharpening Stone, Rune of Warding, Rune of Shielding. //---After fear bot's lose target---/// @@ -26,4 +26,4 @@ Needs some fix. Rogue : some abilitys require Rogue to be stealthed and behind target. ///---Implement locale independet way of getting spellIDs---/// -Hardcode lowest rank spellID, use function to get highest rank \ No newline at end of file +[DONE] Hardcode lowest rank spellID, use function to get highest rank diff --git a/configure.ac b/configure.ac index 39c3d3894..ac4f37dad 100644 --- a/configure.ac +++ b/configure.ac @@ -360,6 +360,9 @@ AC_CONFIG_FILES([ src/shared/Database/Makefile src/shared/SystemConfig.h src/game/Makefile + src/game/playerbot/Makefile + src/game/playerbot/config.h + src/game/playerbot/playerbot.conf.dist src/game/vmap/Makefile src/realmd/Makefile src/realmd/realmd.conf.dist diff --git a/src/game/Makefile.am b/src/game/Makefile.am index eefcfe5d9..33044494f 100644 --- a/src/game/Makefile.am +++ b/src/game/Makefile.am @@ -7,7 +7,7 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License @@ -17,7 +17,7 @@ ## Process this file with automake to produce Makefile.in ## Sub-directories to parse -SUBDIRS = vmap AuctionHouseBot +SUBDIRS = PlayerBot vmap AuctionHouseBot ## CPP flags for includes, defines, etc. AM_CPPFLAGS = $(MANGOS_INCLUDES) -I$(top_builddir)/src/shared -I$(srcdir) -I$(srcdir)/../../dep/include -I$(srcdir)/../framework -I$(srcdir)/../shared -I$(srcdir)/vmap -I$(srcdir)/../realmd -I$(srcdir)/../../dep/include/g3dlite -DSYSCONFDIR=\"$(sysconfdir)/\" diff --git a/src/game/Map.cpp b/src/game/Map.cpp index ed022fa5a..37b2db165 100644 --- a/src/game/Map.cpp +++ b/src/game/Map.cpp @@ -629,7 +629,7 @@ void Map::Remove(Player *player, bool remove) SendRemoveTransports(player); UpdateObjectVisibility(player,cell,p); - player->ResetMap(); + // player->ResetMap(); if( remove ) DeleteFromWorld(player); } diff --git a/src/game/Player.h b/src/game/Player.h index 6d1701df4..932f3d763 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -55,8 +55,8 @@ class Spell; class Item; // Playerbot mod -class PlayerbotAI; -class PlayerbotMgr; +#include "playerbot/PlayerbotMgr.h" +#include "playerbot/PlayerbotAI.h" typedef std::deque PlayerMails; @@ -1631,6 +1631,10 @@ 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); } + void chompAndTrim(std::string& str); + bool getNextQuestId(const std::string& pString, unsigned int& pStartPos, unsigned int& pId); + bool requiredQuests(const char* pQuestIdString); + /*********************************************************/ /*** LOAD SYSTEM ***/ /*********************************************************/ diff --git a/src/game/PlayerBot/PlayerbotAI.cpp b/src/game/PlayerBot/PlayerbotAI.cpp index 6e84358a3..3aa3efc45 100644 --- a/src/game/PlayerBot/PlayerbotAI.cpp +++ b/src/game/PlayerBot/PlayerbotAI.cpp @@ -1,22 +1,3 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "Config/Config.h" #include "Common.h" #include "Database/DatabaseEnv.h" #include "../ItemPrototype.h" @@ -62,19 +43,16 @@ class PlayerbotChatHandler : protected ChatHandler bool teleport(Player& botPlayer) { return HandleNamegoCommand((char*) botPlayer.GetName()); } void sysmessage(const char *str) { SendSysMessage(str); } bool dropQuest(char *str) { return HandleQuestRemoveCommand(str); } - bool gmstartup(char *str) { return HandleGMStartUpCommand(str); } }; PlayerbotAI::PlayerbotAI(PlayerbotMgr* const mgr, Player* const bot) : m_mgr(mgr), m_bot(bot), m_ignoreAIUpdatesUntilTime(0), - m_ignoreTeleport(0), m_spe(0), m_combatOrder(ORDERS_NONE), m_ScenarioType(SCENARIO_PVEEASY), m_TimeDoneEating(0), m_TimeDoneDrinking(0), m_CurrentlyCastingSpellId(0), m_spellIdCommand(0), m_targetGuidCommand(0), m_classAI(0) { - SetMaster(bot); - + // set bot state and needed item list m_botState = BOTSTATE_NORMAL; SetQuestNeedItems(); @@ -86,13 +64,58 @@ PlayerbotAI::PlayerbotAI(PlayerbotMgr* const mgr, Player* const bot) : m_targetAssist = 0; m_targetProtect = 0; - bot->GetPosition(m_position_fin_x, m_position_fin_y, m_position_fin_z); - uint32 m_mapId_fin = bot->GetMapId(); + // start following master (will also teleport bot to master) + SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); + + // get class specific ai + switch (m_bot->getClass()) + { + case CLASS_PRIEST: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotPriestAI(GetMaster(), m_bot, this); + break; + case CLASS_MAGE: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotMageAI(GetMaster(), m_bot, this); + break; + case CLASS_WARLOCK: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotWarlockAI(GetMaster(), m_bot, this); + break; + case CLASS_WARRIOR: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotWarriorAI(GetMaster(), m_bot, this); + break; + case CLASS_SHAMAN: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotShamanAI(GetMaster(), m_bot, this); + break; + case CLASS_PALADIN: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotPaladinAI(GetMaster(), m_bot, this); + break; + case CLASS_ROGUE: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotRogueAI(GetMaster(), m_bot, this); + break; + case CLASS_DRUID: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotDruidAI(GetMaster(), m_bot, this); + break; + case CLASS_HUNTER: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotHunterAI(GetMaster(), m_bot, this); + break; + case CLASS_DEATH_KNIGHT: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotDeathKnightAI(GetMaster(), m_bot, this); + break; + } } PlayerbotAI::~PlayerbotAI() { - delete m_classAI; + if (m_classAI) delete m_classAI; } Player* PlayerbotAI::GetMaster() const @@ -100,11 +123,6 @@ Player* PlayerbotAI::GetMaster() const return m_mgr->GetMaster(); } -void PlayerbotAI::SetMaster(Player* pl) -{ - m_mgr->SetMaster(pl); -} - // finds spell ID for matching substring args // in priority of full text match, spells not taking reagents, and highest rank uint32 PlayerbotAI::getSpellId(const char* args, bool master) const @@ -375,7 +393,7 @@ void PlayerbotAI::SendNotEquipList(Player& player) } } - TellMaster("Voici tous les objets de mon inventaire que je peux porter."); + TellMaster("Here's all the items in my inventory that I can equip."); ChatHandler ch(GetMaster()); const std::string descr[] = { "head", "neck", "shoulders", "body", "chest", @@ -424,7 +442,7 @@ void PlayerbotAI::SendQuestItemList(Player& player) << "]|h|r"; } - TellMaster("Voici la liste des objets dont j'ai besoin pour valider mes qutes :"); + TellMaster("Here's a list of all items I need for quests:"); TellMaster(out.str().c_str()); } @@ -433,40 +451,43 @@ void PlayerbotAI::SendOrders(Player& player) std::ostringstream out; if (!m_combatOrder) - out << "Je n'ai pas d'ordre de combat"; - else if (m_combatOrder&ORDERS_TANK) - out << "Je tank "; - else if (m_combatOrder&ORDERS_ASSIST) - out << "Je passe en assist " << (m_targetAssist ?m_targetAssist->GetName():"unknown"); - else if (m_combatOrder&ORDERS_HEAL) - out << "Je prends en charge les soins "; - if ((m_combatOrder&ORDERS_PRIMARY) && (m_combatOrder&ORDERS_SECONDARY)) - out << " et "; - if (m_combatOrder&ORDERS_PROTECT) - out << "Je protge " << (m_targetProtect ?m_targetProtect->GetName():"unknown"); + out << "Got no combat orders!"; + else if (m_combatOrder & ORDERS_TANK) + out << "I TANK"; + else if (m_combatOrder & ORDERS_ASSIST) + out << "I ASSIST " << (m_targetAssist ? m_targetAssist->GetName() : "unknown"); + else if (m_combatOrder & ORDERS_HEAL) + out << "I HEAL"; + if ((m_combatOrder & ORDERS_PRIMARY) && (m_combatOrder & ORDERS_SECONDARY)) + out << " and "; + if (m_combatOrder & ORDERS_PROTECT) + out << "I PROTECT " << (m_targetProtect ? m_targetProtect->GetName() : "unknown"); out << "."; - out << " " << (IsInCombat() ? "I'm in COMBAT! " : "Not in combat. "); - out << "Current state is "; - if (m_botState == BOTSTATE_NORMAL) - out << "NORMAL"; - else if (m_botState == BOTSTATE_COMBAT) - out << "COMBAT"; - else if (m_botState == BOTSTATE_DEAD) - out << "DEAD"; - else if (m_botState == BOTSTATE_DEADRELEASED) - out << "RELEASED"; - else if (m_botState == BOTSTATE_LOOTING) - out << "LOOTING"; - out << ". Movement order is "; - if (m_movementOrder == MOVEMENT_NONE) - out << "NONE"; - else if (m_movementOrder == MOVEMENT_FOLLOW) - out << "FOLLOW " << (m_followTarget ? m_followTarget->GetName() : "unknown"); - else if (m_movementOrder == MOVEMENT_STAY) - out << "STAY"; - out << ". Got " << m_attackerInfo.size() << " attacker(s) in list."; - out << " Next action in " << (m_ignoreAIUpdatesUntilTime - time(0)) << "sec."; + if (m_mgr->m_confDebugWhisper) + { + out << " " << (IsInCombat() ? "I'm in COMBAT! " : "Not in combat. "); + out << "Current state is "; + if (m_botState == BOTSTATE_NORMAL) + out << "NORMAL"; + else if (m_botState == BOTSTATE_COMBAT) + out << "COMBAT"; + else if (m_botState == BOTSTATE_DEAD) + out << "DEAD"; + else if (m_botState == BOTSTATE_DEADRELEASED) + out << "RELEASED"; + else if (m_botState == BOTSTATE_LOOTING) + out << "LOOTING"; + out << ". Movement order is "; + if (m_movementOrder == MOVEMENT_NONE) + out << "NONE"; + else if (m_movementOrder == MOVEMENT_FOLLOW) + out << "FOLLOW " << (m_followTarget ? m_followTarget->GetName() : "unknown"); + else if (m_movementOrder == MOVEMENT_STAY) + out << "STAY"; + out << ". Got " << m_attackerInfo.size() << " attacker(s) in list."; + out << " Next action in " << (m_ignoreAIUpdatesUntilTime - time(0)) << "sec."; + } TellMaster(out.str().c_str()); } @@ -512,13 +533,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // follow target in casting range float angle = rand_float(0, M_PI_F); float dist = rand_float(4, 10); - float distX, distY; + m_bot->GetMotionMaster()->Clear(true); - distX = pPlayer->GetPositionX() + (dist * cos(angle)); - distY = pPlayer->GetPositionY() + (dist * sin(angle)); - m_bot->GetMotionMaster()->MovePoint(pPlayer->GetMapId(), distX, distY, pPlayer->GetPositionZ()); - m_bot->SetInFront(pPlayer); - pPlayer->SendCreateUpdateToPlayer(m_bot); + m_bot->GetMotionMaster()->MoveFollow(pPlayer, dist, angle); m_bot->SetSelectionGuid(ObjectGuid(playerGuid)); m_ignoreAIUpdatesUntilTime = time(0) + 4; @@ -559,7 +576,65 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) uint64 guid = p.readPackGUID(); if (guid != GetMaster()->GetGUID()) return; - CheckMount(); + if (GetMaster()->IsMounted() && !m_bot->IsMounted()) + { + //Player Part + if (!GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).empty()) + { + int32 master_speed1 = 0; + int32 master_speed2 = 0; + master_speed1 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[1]; + master_speed2 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[2]; + + //Bot Part + uint32 spellMount = 0; + for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + continue; + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + if (pSpellInfo->EffectApplyAuraName[0] == SPELL_AURA_MOUNTED) + { + if (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + { + if (pSpellInfo->EffectBasePoints[1] == master_speed1) + { + spellMount = spellId; + break; + } + } + else if ((pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) + { + if ((pSpellInfo->EffectBasePoints[1] == master_speed1) + && (pSpellInfo->EffectBasePoints[2] == master_speed2)) + { + spellMount = spellId; + break; + } + } + else if ((pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) + if ((pSpellInfo->EffectBasePoints[2] == master_speed2) + && (pSpellInfo->EffectBasePoints[1] == master_speed1)) + { + spellMount = spellId; + break; + } + } + } + if (spellMount > 0) m_bot->CastSpell(m_bot, spellMount, false); + } + } + else if (!GetMaster()->IsMounted() && m_bot->IsMounted()) + { + WorldPacket emptyPacket; + m_bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); //updated code + } return; } @@ -624,7 +699,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) p.clear(); if (operation == PARTY_OP_LEAVE) if (member == GetMaster()->GetName()) - PlayerbotMgr::RemoveAllBotsFromGroup(GetMaster()); + m_bot->GetSession()->HandleGroupDisbandOpcode(p); // packet not used updated code return; } @@ -642,7 +717,16 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) return; WorldPacket p; - m_bot->GetSession()->HandleGroupAcceptOpcode(p); + if (!canObeyCommandFrom(*inviter)) + { + std::string buf = "I can't accept your invite unless you first invite my master "; + buf += GetMaster()->GetName(); + buf += "."; + SendWhisper(buf, *inviter); + m_bot->GetSession()->HandleGroupDeclineOpcode(p); // packet not used + } + else + m_bot->GetSession()->HandleGroupAcceptOpcode(p); // packet not used } return; } @@ -670,7 +754,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) if (!canObeyCommandFrom(*(m_bot->GetTrader()))) { - SendWhisper("Je n'ai pas le droit de vous donner mes objets. Mais je peux accepter les votres ainsi que votre or.", * (m_bot->GetTrader())); + SendWhisper("I'm not allowed to trade you any of my items, but you are free to give me money or items.", *(m_bot->GetTrader())); return; } @@ -729,10 +813,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) // send bot the message std::ostringstream whisper; - whisper << "Je possde actuellement |cff00ff00" << gold + whisper << "I have |cff00ff00" << gold << "|r|cfffffc00g|r|cff00ff00" << silver << "|r|cffcdcdcds|r|cff00ff00" << copper - << "|r|cffffd333c|r" << " et je peux changer les objets suivants :"; + << "|r|cffffd333c|r" << " and the following items:"; SendWhisper(whisper.str().c_str(), *(m_bot->GetTrader())); ChatHandler ch(m_bot->GetTrader()); ch.SendSysMessage(out.str().c_str()); @@ -896,12 +980,12 @@ uint8 PlayerbotAI::GetRunicPower() const //typedef std::pair spellEffectPair; //typedef std::multimap AuraMap; -bool PlayerbotAI::HasAura(uint32 spellId, const Unit* target) const +bool PlayerbotAI::HasAura(uint32 spellId, const Unit& player) const { if (spellId <= 0) return false; - for (Unit::SpellAuraHolderMap::const_iterator iter = target->GetSpellAuraHolderMap().begin(); iter != target->GetSpellAuraHolderMap().end(); ++iter) + for (Unit::SpellAuraHolderMap::const_iterator iter = player.GetSpellAuraHolderMap().begin(); iter != player.GetSpellAuraHolderMap().end(); ++iter) { if (iter->second->GetId() == spellId) return true; @@ -911,13 +995,13 @@ bool PlayerbotAI::HasAura(uint32 spellId, const Unit* target) const bool PlayerbotAI::HasAura(const char* spellName) const { - return HasAura(spellName, m_bot); + return HasAura(spellName, *m_bot); } -bool PlayerbotAI::HasAura(const char* spellName, const Unit* target) const +bool PlayerbotAI::HasAura(const char* spellName, const Unit& player) const { uint32 spellId = getSpellId(spellName); - return (spellId) ? HasAura(spellId, target) : false; + return (spellId) ? HasAura(spellId, player) : false; } // looks through all items / spells that bot could have to get a mount @@ -969,110 +1053,6 @@ Item* PlayerbotAI::FindMount(uint32 matchingRidingSkill) const return partialMatch; } -void PlayerbotAI::CheckMount() -{ - time_t currentTime = time(0); - if (currentTime < m_ignoreTeleport) - return; - - if (m_bot->IsBeingTeleported()) - return; - - if (m_bot->GetGroup()) - { - GroupReference *ref = m_bot->GetGroup()->GetFirstMember(); - while (ref) - { - if (ref->getSource()->IsBeingTeleported() ||!m_bot->IsWithinDistInMap(ref->getSource(), 100, true)) - return; - ref = ref->next(); - } - } - - if ((GetMaster()->IsMounted()) && (!m_bot->IsMounted())) - { - if (!GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).empty()) - { - int32 master_speed1 = 0; - int32 master_speed2 = 0; - master_speed1 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[1]; - master_speed2 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[2]; - - Item* pItem225 = NULL; - Item* pItem300 = NULL; - pItem225 = m_bot->GetPlayerbotAI()->FindMount(225); - pItem300 = m_bot->GetPlayerbotAI()->FindMount(300); - if (pItem300 && (master_speed1 > 150 || master_speed2 > 150)) - { - m_bot->GetPlayerbotAI()->UseItem(pItem300); - return; - } - else if (pItem225) - { - m_bot->GetPlayerbotAI()->UseItem(pItem225); - return; - } - if (pItem300) - { - m_bot->GetPlayerbotAI()->UseItem(pItem300); - return; - } - uint32 spellMount = 0; - for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) - { - uint32 spellId = itr->first; - if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) - continue; - const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); - if (!pSpellInfo) - continue; - - if (pSpellInfo->EffectApplyAuraName[0] == SPELL_AURA_MOUNTED) - { - if (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) - { - if (pSpellInfo->EffectBasePoints[1] == master_speed1) - { - spellMount = spellId; - break; - } - } - else if ((pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) - && (pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) - { - if ((pSpellInfo->EffectBasePoints[1] == master_speed1) - && (pSpellInfo->EffectBasePoints[2] == master_speed2)) - { - spellMount = spellId; - break; - } - } - else if ((pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) - && (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) - { - if ((pSpellInfo->EffectBasePoints[2] == master_speed2) - && (pSpellInfo->EffectBasePoints[1] == master_speed1)) - { - spellMount = spellId; - break; - } - } - } - } - if (spellMount > 0)m_bot->CastSpell(m_bot, spellMount, false); - } - } - else if (!GetMaster()->IsMounted() && m_bot->IsMounted()) - { - WorldPacket emptyPacket; - m_bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); - m_bot->Unmount(); - m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); - m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED); - m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); - } -} - Item* PlayerbotAI::FindFood() const { // list out items in main backpack @@ -1296,6 +1276,7 @@ Item* PlayerbotAI::FindConsumable(uint32 displayId) const void PlayerbotAI::InterruptCurrentCastingSpell() { + //TellMaster("I'm interrupting my current spell!"); WorldPacket* const packet = new WorldPacket(CMSG_CANCEL_CAST, 5); //changed from thetourist suggestion *packet << m_CurrentlyCastingSpellId; *packet << m_targetGuidCommand; //changed from thetourist suggestion @@ -1321,34 +1302,34 @@ void PlayerbotAI::Feast() && ((static_cast (m_bot->GetPower(POWER_MANA)) / m_bot->GetMaxPower(POWER_MANA)) < 0.8)) { Item* pItem = FindDrink(); - if (pItem) + if (pItem != NULL) { UseItem(pItem); m_TimeDoneDrinking = currentTime + 30; return; } - TellMaster("J'ai besoin d'eau..."); + TellMaster("I need water."); } // should we eat another if (currentTime > m_TimeDoneEating && ((static_cast (m_bot->GetHealth()) / m_bot->GetMaxHealth()) < 0.8)) { Item* pItem = FindFood(); - if (pItem) + if (pItem != NULL) { - TellMaster("eating now..."); + //TellMaster("eating now..."); UseItem(pItem); m_TimeDoneEating = currentTime + 30; return; } - TellMaster("J'ai besoin de nourriture."); + TellMaster("I need food."); } // if we are no longer eating or drinking // because we are out of items or we are above 80% in both stats if (currentTime > m_TimeDoneEating && currentTime > m_TimeDoneDrinking) { - TellMaster("Festin fini !"); + TellMaster("done feasting!"); m_bot->SetStandState(UNIT_STAND_STATE_STAND); } } @@ -1378,12 +1359,14 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) { forcedTarget = newTarget; m_targetType = TARGET_THREATEN; - TellMaster("Changing target to %s to protect %s", forcedTarget->GetName(), m_targetProtect->GetName()); + if (m_mgr->m_confDebugWhisper) + TellMaster("Changing target to %s to protect %s", forcedTarget->GetName(), m_targetProtect->GetName()); } } else if (forcedTarget) { - TellMaster("Changing target to %s by force!", forcedTarget->GetName()); + if (m_mgr->m_confDebugWhisper) + TellMaster("Changing target to %s by force!", forcedTarget->GetName()); m_targetType = (m_combatOrder == ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); } @@ -1401,7 +1384,7 @@ void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) if (!m_targetCombat && (m_combatOrder & ORDERS_ASSIST) && m_targetAssist != 0) { m_targetCombat = FindAttacker((ATTACKERINFOTYPE) (AIT_VICTIMNOTSELF | AIT_LOWESTTHREAT), m_targetAssist); - if (m_targetCombat) + 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_targetChanged = true; @@ -1478,42 +1461,24 @@ void PlayerbotAI::DoNextCombatManeuver() DoCombatMovement(); if (GetClassAI() && !m_targetChanged) - GetClassAI()->DoNextCombatManeuver(m_targetCombat); + (GetClassAI())->DoNextCombatManeuver(m_targetCombat); } void PlayerbotAI::DoCombatMovement() { - if (!m_targetCombat) - return; + if (!m_targetCombat) return; float targetDist = m_bot->GetDistance(m_targetCombat); if (m_combatStyle == COMBAT_MELEE && !m_bot->hasUnitState(UNIT_STAT_CHASE) && ((m_movementOrder == MOVEMENT_STAY && targetDist <= ATTACK_DISTANCE) || (m_movementOrder != MOVEMENT_STAY))) + // melee combat - chase target if in range or if we are not forced to stay + m_bot->GetMotionMaster()->MoveChase(m_targetCombat); + else if (m_combatStyle == COMBAT_RANGED && m_movementOrder != MOVEMENT_STAY) { - float angle = rand_float(0, M_PI_F); - float dist = rand_float(1.0f, 3.0f); - float distX, distY; - m_bot->GetMotionMaster()->Clear(true); - distX = m_targetCombat->GetPositionX() + (dist * cos(angle)); - distY = m_targetCombat->GetPositionY() + (dist * sin(angle)); - m_bot->GetMotionMaster()->MovePoint(m_targetCombat->GetMapId(), distX, distY, m_targetCombat->GetPositionZ()); - m_bot->SetInFront(m_targetCombat); - m_targetCombat->SendCreateUpdateToPlayer(m_bot); - } - else if (m_combatStyle ==COMBAT_RANGED && m_movementOrder != MOVEMENT_STAY) - { + // ranged combat - just move within spell range + // TODO: just follow in spell range! how to determine bots spell range? if (targetDist > 25.0f) - { - float angle = rand_float(0, M_PI_F); - float dist = rand_float(10.0f, 20.0f); - float distX, distY; - m_bot->GetMotionMaster()->Clear(true); - distX = m_targetCombat->GetPositionX() + (dist * cos(angle)); - distY = m_targetCombat->GetPositionY() + (dist * sin(angle)); - m_bot->GetMotionMaster()->MovePoint(m_targetCombat->GetMapId(), distX, distY, m_targetCombat->GetPositionZ()); - m_bot->SetInFront(m_targetCombat); - m_targetCombat->SendCreateUpdateToPlayer(m_bot); - } + m_bot->GetMotionMaster()->MoveChase(m_targetCombat); else MovementClear(); } @@ -1726,7 +1691,7 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) ObjectGuid giverGUID = questgiver->GetObjectGuid(); if (!m_bot->IsInMap(questgiver)) - TellMaster("Hey, tu prends des quetes sans moi !"); + TellMaster("hey you are turning in quests without me!"); else { m_bot->SetSelectionGuid(giverGUID); @@ -1758,10 +1723,10 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) if (m_bot->CanRewardQuest(pQuest, false)) { m_bot->RewardQuest(pQuest, 0, questgiver, false); - out << "Qute finie : |cff808080|Hquetes:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + out << "Quest complete: |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; } else - out << "|cffff0000Impossible de prendre la qute:|r |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + out << "|cffff0000Unable to turn quest in:|r |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; } // auto reward quest if one item as reward @@ -1778,13 +1743,13 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) std::string itemName = pRewardItem->Name1; ItemLocalization(itemName, pRewardItem->ItemId); - out << "Qute(s)finie(s): " + out << "Quest complete: " << " |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 - out << "|cffff0000Impossible de prendre la qute :|r " + out << "|cffff0000Unable to turn quest in:|r " << "|cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r" << " reward: |cffffffff|Hitem:" @@ -1794,8 +1759,8 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) // else multiple rewards - let master pick else { - out << "Quelle rcompense dois-je prendre|cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() - << "|h[" << questTitle << "]|h|r ? "; + out << "What reward should I take for |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() + << "|h[" << questTitle << "]|h|r? "; for (uint8 i = 0; i < pQuest->GetRewChoiceItemsCount(); ++i) { ItemPrototype const * const pRewardItem = sObjectMgr.GetItemPrototype(pQuest->RewChoiceItemId[i]); @@ -1808,12 +1773,12 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) } else if (status == QUEST_STATUS_INCOMPLETE) - out << "|cffff0000Qute(s)incomplte(s):|r " - << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + out << "|cffff0000Quest incomplete:|r " + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; else if (status == QUEST_STATUS_AVAILABLE) - out << "|cff00ff00Qute(s)active(s):|r " - << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + out << "|cff00ff00Quest available:|r " + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; if (!out.str().empty()) TellMaster(out.str()); @@ -1823,22 +1788,26 @@ void PlayerbotAI::TurnInQuests(WorldObject *questgiver) bool PlayerbotAI::IsInCombat() { - if (!m_bot->getAttackers().empty()) - return true; - if (!GetMaster()->getAttackers().empty()) - return true; - + Pet *pet; + bool inCombat = false; + inCombat |= m_bot->isInCombat(); + pet = m_bot->GetPet(); + if (pet) + inCombat |= pet->isInCombat(); + inCombat |= GetMaster()->isInCombat(); if (m_bot->GetGroup()) { GroupReference *ref = m_bot->GetGroup()->GetFirstMember(); while (ref) { - if (!ref->getSource()->getAttackers().empty()) - return true; + inCombat |= ref->getSource()->isInCombat(); + pet = ref->getSource()->GetPet(); + if (pet) + inCombat |= pet->isInCombat(); ref = ref->next(); } } - return false; + return inCombat; } void PlayerbotAI::UpdateAttackersForTarget(Unit *victim) @@ -2046,13 +2015,11 @@ void PlayerbotAI::SetCombatOrderByStr(std::string str, Unit *target) void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) { - if ((co == ORDERS_ASSIST || co == ORDERS_PROTECT) && !target) - { + if ((co == ORDERS_ASSIST || co == ORDERS_PROTECT) && !target) { TellMaster("Erf, you forget to target assist/protect characters!"); return; } - if (co == ORDERS_RESET) - { + if (co == ORDERS_RESET) { m_combatOrder = ORDERS_NONE; m_targetAssist = 0; m_targetProtect = 0; @@ -2084,56 +2051,27 @@ void PlayerbotAI::MovementReset() if (m_movementOrder == MOVEMENT_FOLLOW) { - if (!m_followTarget) - return; + if (!m_followTarget) return; // target player is teleporting... if (m_followTarget->GetTypeId() == TYPEID_PLAYER && ((Player*) m_followTarget)->IsBeingTeleported()) return; // check if bot needs to teleport to reach target... - if (!IsInCombat()) + if (!m_bot->isInCombat()) { if (m_followTarget->GetTypeId() == TYPEID_PLAYER && ((Player*) m_followTarget)->GetCorpse()) { - if (!FollowCheckTeleport(*((Player*) m_followTarget)->GetCorpse())) - return; + if (!FollowCheckTeleport(*((Player*) m_followTarget)->GetCorpse())) return; } - else if (!FollowCheckTeleport(*m_followTarget)) - return; + else if (!FollowCheckTeleport(*m_followTarget)) return; } if (m_bot->isAlive()) { - if (m_bot == GetMaster()) - { - if (m_bot->IsInRange3d(m_position_fin_x, m_position_fin_y, m_position_fin_z, 0.0f, 10.0f)) - { - FindPOI(m_position_fin_x, m_position_fin_y, m_position_fin_z, m_mapId_fin); - m_bot->GetMotionMaster()->Clear(true); - m_bot->GetMotionMaster()->MovePoint(m_mapId_fin, m_position_fin_x, m_position_fin_y, m_position_fin_z); - } - else - { - m_bot->GetMotionMaster()->Clear(true); - m_bot->GetMotionMaster()->MovePoint(m_mapId_fin, m_position_fin_x, m_position_fin_y, m_position_fin_z); - } - } - else - { - float angle = rand_float(0, M_PI_F); - float dist = rand_float(5.0f, 10.0f); - - if (m_bot->IsWithinDist(GetMaster(), 10.0f)) - return; - - float distX, distY; - distX = m_followTarget->GetPositionX() + (dist * cos(angle)); - distY = m_followTarget->GetPositionY() + (dist * sin(angle)); - m_bot->GetMotionMaster()->MovePoint(m_followTarget->GetMapId(), distX, distY, m_followTarget->GetPositionZ()); - m_bot->SetInFront(m_followTarget); - m_followTarget->SendCreateUpdateToPlayer(m_bot); - } + float angle = rand_float(0, M_PI_F); + float dist = rand_float(m_mgr->m_confFollowDistance[0], m_mgr->m_confFollowDistance[1]); + m_bot->GetMotionMaster()->MoveFollow(m_followTarget, dist, angle); } } } @@ -2143,28 +2081,8 @@ void PlayerbotAI::MovementUpdate() // send heartbeats to world // m_bot->SendHeartBeat(false); - float x = m_bot->GetPositionX(); - float y = m_bot->GetPositionY(); - float z = m_bot->GetPositionZ(); - float o = m_bot->GetOrientation(); - - m_bot->UpdateGroundPositionZ(x, y, z); - m_bot->SetPosition(x, y, z, o, false); -} - -void PlayerbotAI::FindPOI(float &x, float &y, float &z, uint32 &mapId) -{ - Unit* target = m_bot->SelectRandomFriendlyTarget(0, 500.0f); - if (target) - { - target->GetPosition(x, y, z); - mapId = target->GetMapId(); - } - else - { - m_bot->GetPosition(x, y, z); - mapId = m_bot->GetMapId(); - } + // call set position (updates states, exploration, etc.) + m_bot->SetPosition(m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), m_bot->GetOrientation(), false); } void PlayerbotAI::MovementClear() @@ -2213,7 +2131,7 @@ void PlayerbotAI::SetInFront(const Unit* obj) void PlayerbotAI::UpdateAI(const uint32 p_time) { - if (m_bot->GetTrader()) + if (m_bot->IsBeingTeleported() || m_bot->GetTrader()) return; time_t currentTime = time(0); @@ -2223,14 +2141,6 @@ void PlayerbotAI::UpdateAI(const uint32 p_time) // default updates occur every two seconds m_ignoreAIUpdatesUntilTime = time(0) + 2; - if (!CheckTeleport()) - return; - - if (!CheckMaster()) - return; - - CheckStuff(); - // send heartbeat MovementUpdate(); @@ -2253,8 +2163,7 @@ void PlayerbotAI::UpdateAI(const uint32 p_time) else if (m_botState == BOTSTATE_DEAD) { // become ghost - if (m_bot->GetCorpse()) - { + if (m_bot->GetCorpse()) { //sLog.outDebug( "[PlayerbotAI]: %s already has a corpse...", m_bot->GetName() ); SetState(BOTSTATE_DEADRELEASED); return; @@ -2292,7 +2201,7 @@ void PlayerbotAI::UpdateAI(const uint32 p_time) PlayerbotChatHandler ch(GetMaster()); if (!ch.revive(*m_bot)) { - ch.sysmessage(".. ne peut pas ressuciter .."); + ch.sysmessage(".. could not be revived .."); return; } // set back to normal @@ -2312,23 +2221,19 @@ void PlayerbotAI::UpdateAI(const uint32 p_time) { Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, m_targetGuidCommand); if (pTarget) - CastSpell(m_spellIdCommand, pTarget); + CastSpell(m_spellIdCommand, *pTarget); m_spellIdCommand = 0; m_targetGuidCommand = 0; } // handle combat (either self/master/group in combat, or combat state and valid target) - else if (IsInCombat()) + else if (IsInCombat() || (m_botState == BOTSTATE_COMBAT && m_targetCombat)) { - if (m_bot->getAttackers().empty() && !m_bot->IsWithinDistInMap(GetMaster(), 100, true)) - { - PlayerbotChatHandler ch(GetMaster()); - if (ch.teleport(*m_bot)) - SetIgnoreTeleport(5); - } - DoNextCombatManeuver(); + if (!pSpell || !pSpell->IsChannelActive()) + DoNextCombatManeuver(); + else + SetIgnoreUpdateTime(1); // It's better to update AI more frequently during combat } - // bot was in combat recently - loot now else if (m_botState == BOTSTATE_COMBAT) { @@ -2341,165 +2246,19 @@ void PlayerbotAI::UpdateAI(const uint32 p_time) DoLoot(); SetIgnoreUpdateTime(); } - else if (!IsInCombat() && !IsMoving()) - { +/* + // are we sitting, if so feast if possible + else if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) + Feast(); + */ + // if commanded to follow master and not already following master then follow master + else if (!m_bot->isInCombat() && !IsMoving()) MovementReset(); - SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); - CheckMount(); - } - else if (GetClassAI()) - { - GetClassAI()->DoNonCombatActions(); - SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); - CheckMount(); - } - } -} - -bool PlayerbotAI::CheckTeleport() -{ - if (m_bot->IsBeingTeleported()) - return false; - - if (m_bot->GetGroup()) - { - GroupReference *ref = m_bot->GetGroup()->GetFirstMember(); - while (ref) - { - if (ref->getSource()->IsBeingTeleported()) - return false; - ref = ref->next(); - } - } - return true; -} - -bool PlayerbotAI::CheckMaster() -{ - if (!GetMaster() || !GetMaster()->IsInWorld()) - { - if (m_bot->GetGroup()) - m_bot->RemoveFromGroup(); - m_bot->GetPlayerbotMgr()->LogoutPlayerBot(m_bot->GetGUID()); - PlayerbotMgr::AddAllBots(); - return false; - } - if ((GetMaster() != m_bot) && !m_bot->GetGroup()) - { - m_bot->GetPlayerbotMgr()->LogoutPlayerBot(m_bot->GetGUID()); - PlayerbotMgr::AddAllBots(); - return false; - } - return true; -} -void PlayerbotAI::CheckStuff() -{ - // get class specific ai - switch (m_bot->getClass()) - { - case CLASS_WARRIOR: - if (m_spe != WarriorProtection) - { - m_combatStyle = COMBAT_MELEE; - m_spe = WarriorProtection; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotWarriorAI(m_bot, this); - break; - } - case CLASS_PALADIN: - if (m_spe != PaladinHoly) - { - m_combatStyle = COMBAT_MELEE; - m_spe = PaladinHoly; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotPaladinAI(m_bot, this); - break; - } - case CLASS_HUNTER: - if (m_spe != HunterBeastMastery) - { - m_combatStyle = COMBAT_RANGED; - m_spe = HunterBeastMastery; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotHunterAI(m_bot, this); - break; - } - case CLASS_ROGUE: - if (m_spe != RogueSubtlety) - { - m_combatStyle = COMBAT_MELEE; - m_spe = RogueSubtlety; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotRogueAI(m_bot, this); - break; - } - case CLASS_PRIEST: - if (m_spe != PriestHoly) - { - m_combatStyle = COMBAT_RANGED; - m_spe = PriestHoly; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotPriestAI(m_bot, this); - break; - } - case CLASS_DEATH_KNIGHT: - if (m_spe != DeathKnightUnholy) - { - m_combatStyle = COMBAT_MELEE; - m_spe = DeathKnightUnholy; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotDeathKnightAI(m_bot, this); - break; - } - case CLASS_SHAMAN: - if (m_spe != ShamanElementalCombat) - { - m_combatStyle = COMBAT_MELEE; - m_spe = ShamanElementalCombat; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotShamanAI(m_bot, this); - break; - } - case CLASS_MAGE: - if (m_spe != MageFrost) - { - m_combatStyle = COMBAT_RANGED; - m_spe = MageFrost; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotMageAI(m_bot, this); - break; - } - case CLASS_WARLOCK: - if (m_spe != WarlockDestruction) - { - m_combatStyle = COMBAT_RANGED; - m_spe = WarlockDestruction; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotWarlockAI(m_bot, this); - break; - } - case CLASS_DRUID: - if (m_spe != DruidFeralCombat) - { - m_combatStyle = COMBAT_MELEE; - m_spe = DruidFeralCombat; - delete m_classAI; - m_classAI = (PlayerbotClassAI*) new PlayerbotDruidAI(m_bot, this); - break; - } + // do class specific non combat actions + else if (GetClassAI() && !m_bot->IsMounted()) + (GetClassAI())->DoNonCombatActions(); } - - if (GetMaster()->getLevel() == m_bot->getLevel()) - return; - - ChatHandler ch(m_bot); - m_bot->RemoveMyEquipement(); - m_bot->GiveLevel(GetMaster()->getLevel()); - ch.HandleGMStartUpCommand(""); - m_bot->SetHealth(m_bot->GetMaxHealth()); - m_bot->SetPower(m_bot->getPowerType(), m_bot->GetMaxPower(m_bot->getPowerType())); - GetClassAI()->InitSpells(m_bot->GetPlayerbotAI()); } Spell* PlayerbotAI::GetCurrentSpell() const @@ -2545,10 +2304,10 @@ bool PlayerbotAI::CastSpell(const char* args) return (spellId) ? CastSpell(spellId) : false; } -bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target) +bool PlayerbotAI::CastSpell(uint32 spellId, Unit& target) { ObjectGuid oldSel = m_bot->GetSelectionGuid(); - m_bot->SetSelectionGuid(target->GetObjectGuid()); + m_bot->SetSelectionGuid(target.GetObjectGuid()); bool rv = CastSpell(spellId); m_bot->SetSelectionGuid(oldSel); return rv; @@ -2607,9 +2366,6 @@ bool PlayerbotAI::CastSpell(uint32 spellId) SetInFront(pTarget); } - if (HasAura(spellId, pTarget)) - return false; - // stop movement to prevent cancel spell casting SpellCastTimesEntry const * castTimeEntry = sSpellCastTimesStore.LookupEntry(pSpellInfo->CastingTimeIndex); if (castTimeEntry && castTimeEntry->CastTime) @@ -2745,7 +2501,7 @@ bool PlayerbotAI::Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player * if (beforeCast) (*beforeCast)(m_bot); - return CastSpell(spellProto->Id, target); + return CastSpell(spellProto->Id, *target); } // Can be used for personal buffs like Mage Armor and Inner Fire @@ -2757,7 +2513,7 @@ bool PlayerbotAI::SelfBuff(uint32 spellId) if (m_bot->HasAura(spellId)) return false; - return CastSpell(spellId, m_bot); + return CastSpell(spellId, *m_bot); } // Checks if spell is single per target per caster and will make any effect on target @@ -2830,6 +2586,26 @@ bool PlayerbotAI::PickPocket(Unit* pTarget) Loot *loot = &c->loot; uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot); + if (m_mgr->m_confDebugWhisper) + { + std::ostringstream out; + + // calculate how much money bot loots + uint32 copper = loot->gold; + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + 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]"; + + TellMaster(out.str().c_str()); + } + if (loot->gold) { m_bot->ModifyMoney( loot->gold ); @@ -3328,7 +3104,7 @@ bool PlayerbotAI::FollowCheckTeleport(WorldObject &obj) PlayerbotChatHandler ch(GetMaster()); if (!ch.teleport(*m_bot)) { - ch.sysmessage("Tlportation impossible..."); + ch.sysmessage(".. could not be teleported .."); //sLog.outDebug( "[PlayerbotAI]: %s failed to teleport", m_bot->GetName() ); return false; } @@ -3398,7 +3174,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) // if message is not from a player in the masters account auto reply and ignore if (!canObeyCommandFrom(fromPlayer)) { - std::string msg = "Je n'ai pas l'autorisation de discuter avec vous. Voyez plutt avec "; + std::string msg = "I can't talk to you. Please speak to my master "; msg += GetMaster()->GetName(); SendWhisper(msg, fromPlayer); m_bot->HandleEmoteCommand(EMOTE_ONESHOT_NO); @@ -3549,7 +3325,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) m_bot->GetMotionMaster()->MovePoint(mapid, x, y, z); } else - SendWhisper("Je n'ai pas d'information sur cet objet", fromPlayer); + SendWhisper("I have no info on that object", fromPlayer); } // get project: 18:50 03/05/10 rev.3 allows bots to retrieve all lootable & quest items from gameobjects @@ -3706,7 +3482,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) SetQuestNeedItems(); } else - SendWhisper("Je n'ai pas d'information sur cet objet", fromPlayer); + SendWhisper("I have no info on that object", fromPlayer); } else if (text == "quests") @@ -3799,10 +3575,10 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) switch (pet->GetCharmInfo()->GetReactState()) { case REACT_AGGRESSIVE: - SendWhisper("Mon familier est en mode aggressif.", fromPlayer); + SendWhisper("My pet is aggressive.", fromPlayer); break; case REACT_DEFENSIVE: - SendWhisper("Mon familier est en mode defensif.", fromPlayer); + SendWhisper("My pet is defensive.", fromPlayer); break; case REACT_PASSIVE: SendWhisper("My pet is passive.", fromPlayer); @@ -3938,8 +3714,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) bool isProfessionOrRidingSpell = false; for (SkillLineAbilityMap::const_iterator skillIter = bounds.first; skillIter != bounds.second; ++skillIter) { - if (IsProfessionOrRidingSkill(skillIter->second->skillId) && skillIter->first == spellId) - { + if (IsProfessionOrRidingSkill(skillIter->second->skillId) && skillIter->first == spellId) { isProfessionOrRidingSpell = true; break; } @@ -3950,8 +3725,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) bool isIgnoredSpell = false; for (uint8 i = 0; i < ignoredSpellsCount; ++i) { - if (spellId == ignoredSpells[i]) - { + if (spellId == ignoredSpells[i]) { isIgnoredSpell = true; break; } @@ -3959,8 +3733,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) if (isIgnoredSpell) continue; - if (IsPositiveSpell(spellId)) - { + if (IsPositiveSpell(spellId)) { if (posSpells.find(spellName) == posSpells.end()) posSpells[spellName] = spellId; else @@ -3979,20 +3752,20 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) for (spellMap::const_iterator iter = posSpells.begin(); iter != posSpells.end(); ++iter) { - posOut << " |cffffffff|HSorts :" << iter->second << "|h[" + posOut << " |cffffffff|Hspell:" << iter->second << "|h[" << iter->first << "]|h|r"; } for (spellMap::const_iterator iter = negSpells.begin(); iter != negSpells.end(); ++iter) { - negOut << " |cffffffff|HSorts :" << iter->second << "|h[" + negOut << " |cffffffff|Hspell:" << iter->second << "|h[" << iter->first << "]|h|r"; } ChatHandler ch(&fromPlayer); - SendWhisper("Voici mes sorts non-offensifs :", fromPlayer); + SendWhisper("here's my non-attack spells:", fromPlayer); ch.SendSysMessage(posOut.str().c_str()); - SendWhisper("et mes sorts offensifs :", fromPlayer); + SendWhisper("and here's my attack spells:", fromPlayer); ch.SendSysMessage(negOut.str().c_str()); } @@ -4114,8 +3887,7 @@ void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) // if this looks like an item link, reward item it completed quest and talking to NPC std::list itemIds; extractItemIds(text, itemIds); - if (!itemIds.empty()) - { + if (!itemIds.empty()) { uint32 itemId = itemIds.front(); bool wasRewarded = false; ObjectGuid questRewarderGUID = m_bot->GetSelectionGuid(); diff --git a/src/game/PlayerBot/PlayerbotAI.h b/src/game/PlayerBot/PlayerbotAI.h index e0a570ad7..4efed3302 100644 --- a/src/game/PlayerBot/PlayerbotAI.h +++ b/src/game/PlayerBot/PlayerbotAI.h @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - #ifndef _PLAYERBOTAI_H #define _PLAYERBOTAI_H @@ -139,7 +121,6 @@ class MANGOS_DLL_SPEC PlayerbotAI public: PlayerbotAI(PlayerbotMgr * const mgr, Player * const bot); virtual ~PlayerbotAI(); - void InitSpells(PlayerbotAI* const ai); // This is called from Unit.cpp and is called every second (I think) void UpdateAI(const uint32 p_time); @@ -196,8 +177,8 @@ class MANGOS_DLL_SPEC PlayerbotAI // get current casting spell (will return NULL if no spell!) Spell* GetCurrentSpell() const; - bool HasAura(uint32 spellId, const Unit* target) const; - bool HasAura(const char* spellName, const Unit* target) const; + bool HasAura(uint32 spellId, const Unit& player) const; + bool HasAura(const char* spellName, const Unit& player) const; bool HasAura(const char* spellName) const; bool CanReceiveSpecificSpell(uint8 spec, Unit* target) const; @@ -226,7 +207,6 @@ class MANGOS_DLL_SPEC PlayerbotAI Item* FindMount(uint32 matchingRidingSkill) const; Item* FindItem(uint32 ItemId); Item* FindConsumable(uint32 displayId) const; - void CheckMount(); // ******* Actions **************************************** // Your handlers can call these actions to make the bot do things. @@ -235,7 +215,7 @@ class MANGOS_DLL_SPEC PlayerbotAI void SendWhisper(const std::string& text, Player& player) const; bool CastSpell(const char* args); bool CastSpell(uint32 spellId); - bool CastSpell(uint32 spellId, Unit* target); + bool CastSpell(uint32 spellId, Unit& target); bool CastPetSpell(uint32 spellId, Unit* target = NULL); bool Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player *) = NULL); bool SelfBuff(uint32 spellId); @@ -256,15 +236,10 @@ class MANGOS_DLL_SPEC PlayerbotAI void DoNextCombatManeuver(); void DoCombatMovement(); void SetIgnoreUpdateTime(uint8 t = 0) { m_ignoreAIUpdatesUntilTime = time(0) + t; }; - void SetIgnoreTeleport(uint8 t) {m_ignoreTeleport = time(0) + t; }; Player *GetPlayerBot() const { return m_bot; } Player *GetPlayer() const { return m_bot; } Player *GetMaster() const; - void SetMaster(Player* pl); - - uint16 GetSpe() { return m_spe; }; - void SetSpe(uint16 spe) { m_spe = spe; }; BotState GetState() { return m_botState; }; void SetState(BotState state); @@ -274,10 +249,6 @@ class MANGOS_DLL_SPEC PlayerbotAI bool FollowCheckTeleport(WorldObject &obj); void DoLoot(); - bool CheckTeleport(); - bool CheckMaster(); - void CheckStuff(); - uint32 EstRepairAll(); uint32 EstRepair(uint16 pos); @@ -297,7 +268,6 @@ class MANGOS_DLL_SPEC PlayerbotAI void MovementUpdate(); void MovementClear(); bool IsMoving(); - void FindPOI(float &x, float &y, float &z, uint32 &mapId); void SetInFront(const Unit* obj); @@ -322,12 +292,10 @@ class MANGOS_DLL_SPEC PlayerbotAI PlayerbotMgr* const m_mgr; Player* const m_bot; PlayerbotClassAI* m_classAI; - uint16 m_spe; // ignores AI updates until time specified // no need to waste CPU cycles during casting etc time_t m_ignoreAIUpdatesUntilTime; - time_t m_ignoreTeleport; CombatStyle m_combatStyle; CombatOrderType m_combatOrder; @@ -367,11 +335,6 @@ class MANGOS_DLL_SPEC PlayerbotAI Unit *m_followTarget; // whom to follow in non combat situation? std::map m_spellRangeMap; - - float m_position_fin_x; - float m_position_fin_y; - float m_position_fin_z; - uint32 m_mapId_fin; }; #endif diff --git a/src/game/PlayerBot/PlayerbotClassAI.cpp b/src/game/PlayerBot/PlayerbotClassAI.cpp index e941bbc49..98a82307a 100644 --- a/src/game/PlayerBot/PlayerbotClassAI.cpp +++ b/src/game/PlayerBot/PlayerbotClassAI.cpp @@ -1,28 +1,16 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - #include "PlayerbotClassAI.h" #include "Common.h" -PlayerbotClassAI::PlayerbotClassAI(Player* const bot, PlayerbotAI* const ai): 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) {} PlayerbotClassAI::~PlayerbotClassAI() {} -void PlayerbotClassAI::InitSpells(PlayerbotAI* const ai) {} -bool PlayerbotClassAI::DoFirstCombatManeuver(Unit *) {return false;} +bool PlayerbotClassAI::DoFirstCombatManeuver(Unit *) +{ + // return false, if done with opening moves/spells + return false; +} void PlayerbotClassAI::DoNextCombatManeuver(Unit *) {} -void PlayerbotClassAI::DoNonCombatActions() {} + +void PlayerbotClassAI::DoNonCombatActions(){} + +bool PlayerbotClassAI::BuffPlayer(Player* target) { return false; } diff --git a/src/game/PlayerBot/PlayerbotClassAI.h b/src/game/PlayerBot/PlayerbotClassAI.h index fc1ee5d47..c6dfe06a7 100644 --- a/src/game/PlayerBot/PlayerbotClassAI.h +++ b/src/game/PlayerBot/PlayerbotClassAI.h @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - #ifndef _PLAYERBOTCLASSAI_H #define _PLAYERBOTCLASSAI_H @@ -35,7 +17,7 @@ class PlayerbotAI; class MANGOS_DLL_SPEC PlayerbotClassAI { public: - PlayerbotClassAI(Player * const bot, PlayerbotAI * const ai); + PlayerbotClassAI(Player * const master, Player * const bot, PlayerbotAI * const ai); virtual ~PlayerbotClassAI(); // all combat actions go here @@ -44,7 +26,9 @@ class MANGOS_DLL_SPEC PlayerbotClassAI // all non combat actions go here, ex buffs, heals, rezzes virtual void DoNonCombatActions(); - virtual void InitSpells(PlayerbotAI* const ai); + + // buff a specific player, usually a real PC who is not in group + virtual bool BuffPlayer(Player* target); // Utilities Player* GetMaster () { return m_master; } diff --git a/src/game/PlayerBot/PlayerbotMgr.cpp b/src/game/PlayerBot/PlayerbotMgr.cpp index c8ebac535..6a8064857 100644 --- a/src/game/PlayerBot/PlayerbotMgr.cpp +++ b/src/game/PlayerBot/PlayerbotMgr.cpp @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - #include "Config/Config.h" #include "../Player.h" #include "PlayerbotAI.h" @@ -31,12 +13,20 @@ class LoginQueryHolder; class CharacterHandler; -PlayerbotMgr::PlayerbotMgr() +Config botConfig; + +PlayerbotMgr::PlayerbotMgr(Player* const master) : m_master(master) { + // load config variables + m_confMaxNumBots = botConfig.GetIntDefault("PlayerbotAI.MaxNumBots", 9); + m_confDebugWhisper = botConfig.GetBoolDefault("PlayerbotAI.DebugWhisper", false); + m_confFollowDistance[0] = botConfig.GetFloatDefault("PlayerbotAI.FollowDistanceMin", 0.5f); + m_confFollowDistance[1] = botConfig.GetFloatDefault("PlayerbotAI.FollowDistanceMin", 1.0f); } PlayerbotMgr::~PlayerbotMgr() { + LogoutAllBots(); } void PlayerbotMgr::UpdateAI(const uint32 p_time) {} @@ -48,7 +38,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) // if master is logging out, log out all bots case CMSG_LOGOUT_REQUEST: { - PlayerbotMgr::RemoveAllBotsFromGroup(m_master); + LogoutAllBots(); return; } @@ -85,6 +75,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) { // Buff anyone who bows before me. Useful for players not in bot's group // How do I get correct target??? + //Player* const pPlayer = GetPlayerBot(m_master->GetSelection()); + //if (pPlayer->GetPlayerbotAI()->GetClassAI()) + // pPlayer->GetPlayerbotAI()->GetClassAI()->BuffPlayer(pPlayer); return; } /* @@ -136,12 +129,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) case TEXTEMOTE_EAT: case TEXTEMOTE_DRINK: { - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; bot->GetPlayerbotAI()->Feast(); } return; @@ -158,12 +148,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (!thingToAttack) return; Player *bot = 0; - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) { - bot = itr->getSource(); + bot = itr->second; if (!bot->IsFriendlyTo(thingToAttack) && bot->IsWithinLOSInMap(thingToAttack)) bot->GetPlayerbotAI()->GetCombatTarget(thingToAttack); } @@ -177,16 +164,11 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (bot) bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY); else - { - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY); } - } return; } @@ -199,20 +181,14 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (bot) bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, m_master); else - { - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, m_master); } - } return; } } - return; } /* EMOTE ends here */ @@ -228,20 +204,15 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (!obj) return; - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; 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 } - - return; } break; @@ -258,13 +229,10 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) if (!pNpc) return; - if (!m_master->GetGroup()) - return; - // for all master's bots - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; bot->GetPlayerbotAI()->TurnInQuests(pNpc); } @@ -281,36 +249,31 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) p >> guid >> quest; Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest); if (qInfo) - { - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + Player* const bot = it->second; if (bot->GetQuestStatus(quest) == QUEST_STATUS_COMPLETE) - bot->GetPlayerbotAI()->TellMaster("J'ai deja fini cette quete."); + bot->GetPlayerbotAI()->TellMaster("I already completed that quest."); else if (!bot->CanTakeQuest(qInfo, false)) { if (!bot->SatisfyQuestStatus(qInfo, false)) - bot->GetPlayerbotAI()->TellMaster("Je suis deja sur cette quete."); + bot->GetPlayerbotAI()->TellMaster("I already have that quest."); else - bot->GetPlayerbotAI()->TellMaster("Je ne peux pas prendre cette quete."); + bot->GetPlayerbotAI()->TellMaster("I can't take that quest."); } else if (!bot->SatisfyQuestLog(false)) - bot->GetPlayerbotAI()->TellMaster("Mon journal de quete est plein."); + bot->GetPlayerbotAI()->TellMaster("My quest log is full."); else if (!bot->CanAddQuest(qInfo, false)) - bot->GetPlayerbotAI()->TellMaster("Je ne peux pas prendre cette quete car je dois ramasser des objets pour la terminer et mes sacs sont pleins :("); + bot->GetPlayerbotAI()->TellMaster("I can't take that quest because it requires that I take items, but my bags are full!"); else { p.rpos(0); // reset reader bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p); - bot->GetPlayerbotAI()->TellMaster("J'ai pris la quete."); + bot->GetPlayerbotAI()->TellMaster("Got the quest."); } } - } return; } case CMSG_LOOT_ROLL: @@ -325,16 +288,15 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) p >> NumberOfPlayers; //number of players invited to roll p >> rollType; //need,greed or pass on roll - if (!m_master->GetGroup()) - return; - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { + uint32 choice = urand(0, 3); //returns 0,1,2 or 3 - Player* const bot = itr->getSource(); - if(!bot) - return; + Player* const bot = it->second; + if (!bot) + return; Group* group = bot->GetGroup(); if (!group) @@ -370,12 +332,10 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) p >> itemGUID; // Not used for bot but necessary opcode data retrieval p >> guildBank; // Flagged if guild repair selected - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { - Player* const bot = itr->getSource(); + + Player* const bot = it->second; if (!bot) return; @@ -427,12 +387,9 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) case CMSG_SPIRIT_HEALER_ACTIVATE: { // sLog.outDebug("SpiritHealer is resurrecting the Player %s",m_master->GetName()); - if (!m_master->GetGroup()) - return; - - for (GroupReference *itr = m_master->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) + for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) { - Player* const bot = itr->getSource(); + Player* const bot = itr->second; Group *grp = bot->GetGroup(); if (grp) grp->RemoveMember(bot->GetGUID(), 1); @@ -466,12 +423,11 @@ void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) std::ostringstream out; out << "masterin: " << oc; - sLog.outDebug(out.str().c_str()); + sLog.outError(out.str().c_str()); } */ } } - void PlayerbotMgr::HandleMasterOutgoingPacket(const WorldPacket& packet) { /* @@ -497,22 +453,32 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(const WorldPacket& packet) std::ostringstream out; out << "masterout: " << oc; - sLog.outDebug(out.str().c_str()); + sLog.outError(out.str().c_str()); } } */ } -void PlayerbotMgr::Stay() +void PlayerbotMgr::LogoutAllBots() { - /*if (!player->GetGroup()) - return; + while (true) + { + PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); + if (itr == GetPlayerBotsEnd()) break; + Player* bot = itr->second; + LogoutPlayerBot(bot->GetGUID()); + } +} + + - for (GroupReference *itr = player->GetGroup()->GetFirstMember(); itr != NULL; itr = itr->next()) +void PlayerbotMgr::Stay() +{ + for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) { - Player* const bot = itr->getSource(); + Player* bot = itr->second; bot->GetMotionMaster()->Clear(); - }*/ + } } @@ -523,6 +489,7 @@ void PlayerbotMgr::LogoutPlayerBot(uint64 guid) if (bot) { WorldSession * botWorldSessionPtr = bot->GetSession(); + m_playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object delete botWorldSessionPtr; // finally delete the bot's WorldSession } @@ -531,72 +498,44 @@ void PlayerbotMgr::LogoutPlayerBot(uint64 guid) // Playerbot mod: Gets a player bot Player object for this WorldSession master Player* PlayerbotMgr::GetPlayerBot(uint64 playerGuid) const { - HashMapHolder < Player > ::MapType& m = sObjectAccessor.GetPlayers(); - for (HashMapHolder < Player > ::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) - { - Player* const bot = itr->second; - if (bot && bot->IsBot() && (bot->GetGUID() == playerGuid)) - return bot; - } - return NULL; + PlayerBotMap::const_iterator it = m_playerBots.find(playerGuid); + return (it == m_playerBots.end()) ? 0 : it->second; } void PlayerbotMgr::OnBotLogin(Player * const bot) { - PlayerbotAI* ai = bot->GetPlayerbotAI(); - if (!ai) - { - PlayerbotAI* ai = new PlayerbotAI(this, bot); - if (ai) - bot->SetPlayerbotAI(ai); - } - ai = bot->GetPlayerbotAI(); - - if (bot->GetGroup()) + // give the bot some AI, object is owned by the player class + PlayerbotAI* ai = new PlayerbotAI(this, bot); + bot->SetPlayerbotAI(ai); + + // tell the world session that they now manage this new bot + m_playerBots[bot->GetGUID()] = bot; + + // if bot is in a group and master is not in group then + // have bot leave their group + if (bot->GetGroup() && + (m_master->GetGroup() == NULL || + m_master->GetGroup()->IsMember(bot->GetGUID()) == false)) bot->RemoveFromGroup(); - ChatHandler ch(bot); - bot->RemoveMyEquipement(); - bot->GiveLevel(bot->GetLevelAtLoading()); - ch.HandleGMStartUpCommand(""); - bot->SetHealth(bot->GetMaxHealth()); - bot->SetPower(bot->getPowerType(), bot->GetMaxPower(bot->getPowerType())); + // sometimes master can lose leadership, pass leadership to master check + const uint64 masterGuid = m_master->GetGUID(); + if (m_master->GetGroup() && + !m_master->GetGroup()->IsLeader(masterGuid)) + m_master->GetGroup()->ChangeLeader(masterGuid); } -void PlayerbotMgr::RemoveAllBotsFromGroup(Player* player) +void PlayerbotMgr::RemoveAllBotsFromGroup() { - sLog.outDebug("PlayerbotMgr::RemoveAllBotsInGroup(...)"); - bool removed = false; - do + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); m_master->GetGroup() && it != GetPlayerBotsEnd(); ++it) { - removed = false; - Player* bot = NULL; - - if (player->GetGroup()) - { - GroupReference *ref = player->GetGroup()->GetFirstMember(); - while (ref) - { - bot = ref->getSource(); - - if (!bot || !bot->IsBot() || bot == player) - { - ref = ref->next(); - continue; - } - - sLog.outDebug("Player name : %s, removed", bot->GetName()); - bot->RemoveFromGroup(); - bot->GetPlayerbotMgr()->LogoutPlayerBot(bot->GetGUID()); - removed = true; - break; - } - } - }while(removed); - PlayerbotMgr::AddAllBots(); -} + Player* const bot = it->second; + if (bot->IsInSameGroupWith(m_master)) + m_master->GetGroup()->RemoveMember(bot->GetGUID(), 0); + } +} -/*void Creature::LoadBotMenu(Player *pPlayer) +void Creature::LoadBotMenu(Player *pPlayer) { if (pPlayer->GetPlayerbotAI()) return; @@ -697,170 +636,3 @@ bool Player::requiredQuests(const char* pQuestIdString) } return false; } - -bool ChatHandler::HandlePlayerbotCommand(char* args) -{ - if (!(m_session->GetSecurity() > SEC_PLAYER)) - if (botConfig.GetBoolDefault("PlayerbotAI.DisableBots", false)) - { - PSendSysMessage("|cffff0000Playerbot system is currently disabled!"); - 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; - } - - char *cmd = strtok ((char*) args, " "); - char *charname = strtok (NULL, " "); - if (!cmd || !charname) - { - PSendSysMessage("|cffff0000usage: add PLAYERNAME or remove PLAYERNAME"); - SetSentErrorMessage(true); - return false; - } - - std::string cmdStr = cmd; - std::string charnameStr = charname; - - if (!normalizePlayerName(charnameStr)) - return false; - - uint64 guid = sObjectMgr.GetPlayerGUIDByName(charnameStr.c_str()); - if (guid == 0 || (guid == m_session->GetPlayer()->GetGUID())) - { - SendSysMessage(LANG_PLAYER_NOT_FOUND); - SetSentErrorMessage(true); - 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; - } - - // 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 *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; - - QueryResult *resultlvl = CharacterDatabase.PQuery("SELECT level,name FROM characters WHERE guid = '%lu'", guid); - 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)) - { - PSendSysMessage("Bot already exists in world."); - SetSentErrorMessage(true); - return false; - } - CharacterDatabase.DirectPExecute("UPDATE characters SET online = 1 WHERE guid = '%lu'", guid); - mgr->AddPlayerBot(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 = '%lu'", guid); - mgr->LogoutPlayerBot(guid); - PSendSysMessage("Bot removed successfully."); - } - else if (cmdStr == "co" || cmdStr == "combatorder") - { - Unit *target = NULL; - char *orderChar = strtok(NULL, " "); - if (!orderChar) - { - PSendSysMessage("|cffff0000Syntax error:|cffffffff .bot co [targetPlayer]"); - SetSentErrorMessage(true); - return false; - } - std::string orderStr = orderChar; - if (orderStr == "protect" || orderStr == "assist") - { - char *targetChar = strtok(NULL, " "); - uint64 targetGUID = m_session->GetPlayer()->GetSelection(); - if (!targetChar && !targetGUID) - { - PSendSysMessage("|cffff0000Combat orders protect and assist expect a target either by selection or by giving target player in command string!"); - SetSentErrorMessage(true); - return false; - } - if (targetChar) - { - std::string targetStr = targetChar; - targetGUID = sObjectMgr.GetPlayerGUIDByName(targetStr.c_str()); - } - target = ObjectAccessor::GetUnit(*m_session->GetPlayer(), targetGUID); - if (!target) - { - PSendSysMessage("|cffff0000Invalid target for combat order protect or assist!"); - SetSentErrorMessage(true); - return false; - } - } - if (mgr->GetPlayerBot(guid) == NULL) - { - PSendSysMessage("|cffff0000Bot can not receive combat order because bot does not exist in world."); - SetSentErrorMessage(true); - return false; - } - mgr->GetPlayerBot(guid)->GetPlayerbotAI()->SetCombatOrderByStr(orderStr, target); - } - return true; -}*/ - diff --git a/src/game/PlayerBot/PlayerbotMgr.h b/src/game/PlayerBot/PlayerbotMgr.h index 9eb03e8de..1b67b674b 100644 --- a/src/game/PlayerBot/PlayerbotMgr.h +++ b/src/game/PlayerBot/PlayerbotMgr.h @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2005-2010 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - #ifndef _PLAYERBOTMGR_H #define _PLAYERBOTMGR_H @@ -28,10 +10,12 @@ class Object; class Item; class PlayerbotClassAI; +typedef UNORDERED_MAP PlayerBotMap; + class MANGOS_DLL_SPEC PlayerbotMgr { public: - PlayerbotMgr(); + PlayerbotMgr(Player * const master); virtual ~PlayerbotMgr(); // This is called from Unit.cpp and is called every second (I think) @@ -45,22 +29,30 @@ class MANGOS_DLL_SPEC PlayerbotMgr void HandleMasterIncomingPacket(const WorldPacket& packet); void HandleMasterOutgoingPacket(const WorldPacket& packet); - static void AddAllBots(); - static void RemoveAllBotsFromGroup(Player* player); - void AddPlayerBot(uint64 guid); void LogoutPlayerBot(uint64 guid); Player* GetPlayerBot (uint64 guid) const; Player* GetMaster() const { return m_master; }; - void SetMaster(Player* pl) { m_master = pl; }; + PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return m_playerBots.begin(); } + PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return m_playerBots.end(); } void LogoutAllBots(); - //void RemoveAllBotsFromGroup(); + void RemoveAllBotsFromGroup(); void OnBotLogin(Player * const bot); void Stay(); -protected: - Player* m_master; +public: + // config variables + uint32 m_confRestrictBotLevel; + uint32 m_confDisableBotsInRealm; + uint32 m_confMaxNumBots; + bool m_confDisableBots; + bool m_confDebugWhisper; + float m_confFollowDistance[2]; + +private: + Player* const m_master; + PlayerBotMap m_playerBots; }; #endif diff --git a/src/game/QuestHandler.cpp b/src/game/QuestHandler.cpp index 7db10fbd3..d473c07f5 100644 --- a/src/game/QuestHandler.cpp +++ b/src/game/QuestHandler.cpp @@ -516,13 +516,12 @@ void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket) continue; } - pPlayer->PlayerTalkClass->SendQuestGiverQuestDetails(pQuest, _player->GetObjectGuid(), true); pPlayer->SetDivider(_player->GetGUID()); if (pPlayer->GetPlayerbotAI()) pPlayer->GetPlayerbotAI()->AcceptQuest( pQuest, _player ); else - pPlayer->PlayerTalkClass->SendQuestGiverQuestDetails( pQuest, _player->GetGUID(), true ); + pPlayer->PlayerTalkClass->SendQuestGiverQuestDetails( pQuest, _player->GetObjectGuid(), true ); } } } diff --git a/src/game/SpellMgr.cpp b/src/game/SpellMgr.cpp index 61974b6d8..936eaab82 100644 --- a/src/game/SpellMgr.cpp +++ b/src/game/SpellMgr.cpp @@ -2431,7 +2431,7 @@ SpellEntry const* SpellMgr::SelectAuraRankForLevel(SpellEntry const* spellInfo, break; // if found appropriate level - if (level + 10 >= spellInfo->spellLevel) + if (level + 10 >= nextSpellInfo->spellLevel) return nextSpellInfo; // one rank less then diff --git a/src/game/playerbot/Makefile.am b/src/game/playerbot/Makefile.am new file mode 100644 index 000000000..3f6b4b7a6 --- /dev/null +++ b/src/game/playerbot/Makefile.am @@ -0,0 +1,81 @@ +# Copyright (C) 2005-2010 MaNGOS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +## Process this file with automake to produce Makefile.in + +## Sub-directories to parse + +## CPP flags for includes, defines, etc. +AM_CPPFLAGS = $(MANGOS_INCLUDES) -I$(top_builddir)/src/shared -I$(srcdir) -I$(srcdir)/../../../dep/include -I$(srcdir)/../../framework -I$(srcdir)/../../shared -I$(srcdir)/../../shared/vmap -I$(srcdir)/../../realmd -DSYSCONFDIR=\"$(sysconfdir)/\" + +## Build MaNGOS game library as convenience library. +# libmangosbot shared library will later be reused by world server daemon. +noinst_LIBRARIES = libmangosbot.a + +# libmangossgame library will later be reused by ... +libmangosbot_a_SOURCES = \ + config.h \ + PlayerbotMgr.cpp \ + PlayerbotMgr.h \ + PlayerbotAI.cpp \ + PlayerbotAI.h \ + PlayerbotClassAI.cpp \ + PlayerbotClassAI.h \ + PlayerbotDeathKnightAI.cpp \ + PlayerbotDeathKnightAI.h \ + PlayerbotDruidAI.cpp \ + PlayerbotDruidAI.h \ + PlayerbotHunterAI.cpp \ + PlayerbotHunterAI.h \ + PlayerbotMageAI.cpp \ + PlayerbotMageAI.h \ + PlayerbotPaladinAI.cpp \ + PlayerbotPaladinAI.h \ + PlayerbotPriestAI.cpp \ + PlayerbotPriestAI.h \ + PlayerbotRogueAI.cpp \ + PlayerbotRogueAI.h \ + PlayerbotShamanAI.cpp \ + PlayerbotShamanAI.h \ + PlayerbotWarlockAI.cpp \ + PlayerbotWarlockAI.h \ + PlayerbotWarriorAI.cpp \ + PlayerbotWarriorAI.h + +## Additional files to include when running 'make dist' +# Precompiled Headers for WIN +EXTRA_DIST = \ + config.h \ + playerbot.conf.dist + +## Additional files to install +sysconf_DATA = \ + playerbot.conf.dist + +install-data-hook: + @list='$(sysconf_DATA)' + for p in $$list; do \ + dest=`echo $$p | sed -e s/.dist//`; \ + if test -f $(DESTDIR)$(sysconfdir)/$$dest; then \ + echo "$@ will not overwrite existing $(DESTDIR)$(sysconfdir)/$$dest"; \ + else \ + echo " $(INSTALL_DATA) $$p $(DESTDIR)$(sysconfdir)/$$dest"; \ + $(INSTALL_DATA) $$p $(DESTDIR)$(sysconfdir)/$$dest; \ + fi; \ + done + +clean-local: + rm -f $(sysconf_DATA) diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp new file mode 100644 index 000000000..3aa3efc45 --- /dev/null +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -0,0 +1,3939 @@ +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "../ItemPrototype.h" +#include "../World.h" +#include "../SpellMgr.h" +#include "PlayerbotAI.h" +#include "PlayerbotMgr.h" +#include "PlayerbotDeathKnightAI.h" +#include "PlayerbotDruidAI.h" +#include "PlayerbotHunterAI.h" +#include "PlayerbotMageAI.h" +#include "PlayerbotPaladinAI.h" +#include "PlayerbotPriestAI.h" +#include "PlayerbotRogueAI.h" +#include "PlayerbotShamanAI.h" +#include "PlayerbotWarlockAI.h" +#include "PlayerbotWarriorAI.h" +#include "../Player.h" +#include "../ObjectMgr.h" +#include "../Chat.h" +#include "WorldPacket.h" +#include "../Spell.h" +#include "../Unit.h" +#include "../SpellAuras.h" +#include "../SharedDefines.h" +#include "Log.h" +#include "../GossipDef.h" + +// returns a float in range of.. +float rand_float(float low, float high) +{ + return (rand() / (static_cast (RAND_MAX) + 1.0)) * (high - low) + low; +} + +// ChatHandler already implements some useful commands the master can call on bots +// These commands are protected inside the ChatHandler class so this class provides access to the commands +// we'd like to call on our bots +class PlayerbotChatHandler : protected ChatHandler +{ +public: + explicit PlayerbotChatHandler(Player* pMasterPlayer) : ChatHandler(pMasterPlayer) {} + bool revive(Player& botPlayer) { return HandleReviveCommand((char*) botPlayer.GetName()); } + bool teleport(Player& botPlayer) { return HandleNamegoCommand((char*) botPlayer.GetName()); } + void sysmessage(const char *str) { SendSysMessage(str); } + bool dropQuest(char *str) { return HandleQuestRemoveCommand(str); } +}; + +PlayerbotAI::PlayerbotAI(PlayerbotMgr* const mgr, Player* const bot) : + m_mgr(mgr), m_bot(bot), m_ignoreAIUpdatesUntilTime(0), + m_combatOrder(ORDERS_NONE), m_ScenarioType(SCENARIO_PVEEASY), + m_TimeDoneEating(0), m_TimeDoneDrinking(0), + m_CurrentlyCastingSpellId(0), m_spellIdCommand(0), + m_targetGuidCommand(0), m_classAI(0) +{ + + // set bot state and needed item list + m_botState = BOTSTATE_NORMAL; + SetQuestNeedItems(); + + // reset some pointers + m_targetChanged = false; + m_targetType = TARGET_NORMAL; + m_targetCombat = 0; + m_targetAssist = 0; + m_targetProtect = 0; + + // start following master (will also teleport bot to master) + SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); + + // get class specific ai + switch (m_bot->getClass()) + { + case CLASS_PRIEST: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotPriestAI(GetMaster(), m_bot, this); + break; + case CLASS_MAGE: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotMageAI(GetMaster(), m_bot, this); + break; + case CLASS_WARLOCK: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotWarlockAI(GetMaster(), m_bot, this); + break; + case CLASS_WARRIOR: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotWarriorAI(GetMaster(), m_bot, this); + break; + case CLASS_SHAMAN: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotShamanAI(GetMaster(), m_bot, this); + break; + case CLASS_PALADIN: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotPaladinAI(GetMaster(), m_bot, this); + break; + case CLASS_ROGUE: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotRogueAI(GetMaster(), m_bot, this); + break; + case CLASS_DRUID: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotDruidAI(GetMaster(), m_bot, this); + break; + case CLASS_HUNTER: + m_combatStyle = COMBAT_RANGED; + m_classAI = (PlayerbotClassAI*) new PlayerbotHunterAI(GetMaster(), m_bot, this); + break; + case CLASS_DEATH_KNIGHT: + m_combatStyle = COMBAT_MELEE; + m_classAI = (PlayerbotClassAI*) new PlayerbotDeathKnightAI(GetMaster(), m_bot, this); + break; + } +} + +PlayerbotAI::~PlayerbotAI() +{ + if (m_classAI) delete m_classAI; +} + +Player* PlayerbotAI::GetMaster() const +{ + return m_mgr->GetMaster(); +} + +// finds spell ID for matching substring args +// in priority of full text match, spells not taking reagents, and highest rank +uint32 PlayerbotAI::getSpellId(const char* args, bool master) const +{ + if (!*args) + return 0; + + std::string namepart = args; + std::wstring wnamepart; + + if (!Utf8toWStr(namepart, wnamepart)) + return 0; + + // converting string that we try to find to lower case + wstrToLower(wnamepart); + + int loc = 0; + if (master) + loc = GetMaster()->GetSession()->GetSessionDbcLocale(); + else + loc = m_bot->GetSession()->GetSessionDbcLocale(); + + uint32 foundSpellId = 0; + bool foundExactMatch = false; + bool foundMatchUsesNoReagents = false; + + for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + continue; + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + const std::string name = pSpellInfo->SpellName[loc]; + if (name.empty() || !Utf8FitTo(name, wnamepart)) + continue; + + bool isExactMatch = (name.length() == wnamepart.length()) ? true : false; + bool usesNoReagents = (pSpellInfo->Reagent[0] <= 0) ? true : false; + + // if we already found a spell + bool useThisSpell = true; + if (foundSpellId > 0) + { + if (isExactMatch && !foundExactMatch) {} + else if (usesNoReagents && !foundMatchUsesNoReagents) {} + else if (spellId > foundSpellId) {} + else + useThisSpell = false; + } + if (useThisSpell) + { + foundSpellId = spellId; + foundExactMatch = isExactMatch; + foundMatchUsesNoReagents = usesNoReagents; + } + } + + return foundSpellId; +} + + +uint32 PlayerbotAI::getPetSpellId(const char* args) const +{ + if (!*args) + return 0; + + Pet* pet = m_bot->GetPet(); + if (!pet) + return 0; + + std::string namepart = args; + std::wstring wnamepart; + + if (!Utf8toWStr(namepart, wnamepart)) + return 0; + + // converting string that we try to find to lower case + wstrToLower(wnamepart); + + int loc = GetMaster()->GetSession()->GetSessionDbcLocale(); + + uint32 foundSpellId = 0; + bool foundExactMatch = false; + bool foundMatchUsesNoReagents = false; + + for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + uint32 spellId = itr->first; + + if (itr->second.state == PETSPELL_REMOVED || IsPassiveSpell(spellId)) + continue; + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + const std::string name = pSpellInfo->SpellName[loc]; + if (name.empty() || !Utf8FitTo(name, wnamepart)) + continue; + + bool isExactMatch = (name.length() == wnamepart.length()) ? true : false; + bool usesNoReagents = (pSpellInfo->Reagent[0] <= 0) ? true : false; + + // if we already found a spell + bool useThisSpell = true; + if (foundSpellId > 0) + { + if (isExactMatch && !foundExactMatch) {} + else if (usesNoReagents && !foundMatchUsesNoReagents) {} + else if (spellId > foundSpellId) {} + else + useThisSpell = false; + } + if (useThisSpell) + { + foundSpellId = spellId; + foundExactMatch = isExactMatch; + foundMatchUsesNoReagents = usesNoReagents; + } + } + + return foundSpellId; +} + + +uint32 PlayerbotAI::initSpell(uint32 spellId) +{ + // Check if bot knows this spell + if (!m_bot->HasSpell(spellId)) + return 0; + + uint32 next = 0; + SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext(); + for (SpellChainMapNext::const_iterator itr = nextMap.lower_bound(spellId); itr != nextMap.upper_bound(spellId); ++itr) + { + // Work around buggy chains + if (sSpellStore.LookupEntry(spellId)->SpellIconID != sSpellStore.LookupEntry(itr->second)->SpellIconID) + continue; + + SpellChainNode const* node = sSpellMgr.GetSpellChainNode(itr->second); + // If next spell is a requirement for this one then skip it + if (node->req == spellId) + continue; + if (node->prev == spellId) + { + next = initSpell(itr->second); + break; + } + } + if (next == 0) + { + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + sLog.outDebug("Playerbot spell init: %s is %u", pSpellInfo->SpellName[0], spellId); + + // Add spell to spellrange map + Spell *spell = new Spell(m_bot, pSpellInfo, false); + SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(pSpellInfo->rangeIndex); + float range = GetSpellMaxRange(srange, IsPositiveSpell(spellId)); + m_bot->ApplySpellMod(spellId, SPELLMOD_RANGE, range, spell); + m_spellRangeMap.insert(std::pair(spellId, range)); + delete spell; + } + 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) +{ + Pet * pet = m_bot->GetPet(); + + if (!pet) + return 0; + + for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + const uint32 spellId = itr->first; + + if (itr->second.state == PETSPELL_REMOVED || IsPassiveSpell(spellId)) + continue; + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + if (pSpellInfo->SpellIconID == spellIconId) + return spellId; + } + + // Nothing found + return 0; +} + +/* + * Send a list of equipment that is in bot's inventor that is currently unequipped. + * This is called when the master is inspecting the bot. + */ + +void PlayerbotAI::SendNotEquipList(Player& player) +{ + // find all unequipped items and put them in + // a vector of dynamically created lists where the vector index is from 0-18 + // and the list contains Item* that can be equipped to that slot + // Note: each dynamically created list in the vector must be deleted at end + // so NO EARLY RETURNS! + // see enum EquipmentSlots in Player.h to see what equipment slot each index in vector + // is assigned to. (The first is EQUIPMENT_SLOT_HEAD=0, and last is EQUIPMENT_SLOT_TABARD=18) + std::list* equip[19]; + for (uint8 i = 0; i < 19; ++i) + equip[i] = NULL; + + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!pItem) + continue; + + uint16 dest; + uint8 msg = m_bot->CanEquipItem(NULL_SLOT, dest, pItem, !pItem->IsBag()); + if (msg != EQUIP_ERR_OK) + continue; + + // the dest looks like it includes the old loc in the 8 higher bits + // so casting it to a uint8 strips them + uint8 equipSlot = uint8(dest); + if (!(equipSlot >= 0 && equipSlot < 19)) + continue; + + // create a list if one doesn't already exist + if (equip[equipSlot] == NULL) + equip[equipSlot] = new std::list; + + std::list* itemListForEqSlot = equip[equipSlot]; + itemListForEqSlot->push_back(pItem); + } + + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (!pItem) + continue; + + uint16 equipSlot; + uint8 msg = m_bot->CanEquipItem(NULL_SLOT, equipSlot, pItem, !pItem->IsBag()); + if (msg != EQUIP_ERR_OK) + continue; + if (!(equipSlot >= 0 && equipSlot < 19)) + continue; + + // create a list if one doesn't already exist + if (equip[equipSlot] == NULL) + equip[equipSlot] = new std::list; + + std::list* itemListForEqSlot = equip[equipSlot]; + itemListForEqSlot->push_back(pItem); + } + } + + TellMaster("Here's all the items in my inventory that I can equip."); + 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" }; + + // now send client all items that can be equipped by slot + for (uint8 equipSlot = 0; equipSlot < 19; ++equipSlot) + { + if (equip[equipSlot] == NULL) + continue; + std::list* itemListForEqSlot = equip[equipSlot]; + std::ostringstream out; + out << descr[equipSlot] << ": "; + for (std::list::iterator it = itemListForEqSlot->begin(); it != itemListForEqSlot->end(); ++it) + { + const ItemPrototype* const pItemProto = (*it)->GetProto(); + + std::string itemName = pItemProto->Name1; + ItemLocalization(itemName, pItemProto->ItemId); + + out << " |cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << itemName + << "]|h|r"; + } + ch.SendSysMessage(out.str().c_str()); + + delete itemListForEqSlot; // delete list of Item* + } +} + +void PlayerbotAI::SendQuestItemList(Player& player) +{ + std::ostringstream out; + + for (BotNeedItem::iterator itr = m_needItemList.begin(); itr != m_needItemList.end(); ++itr) + { + const ItemPrototype * pItemProto = sObjectMgr.GetItemPrototype(itr->first); + + std::string itemName = pItemProto->Name1; + ItemLocalization(itemName, pItemProto->ItemId); + + out << " " << itr->second << "x|cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << itemName + << "]|h|r"; + } + + TellMaster("Here's a list of all items I need for quests:"); + TellMaster(out.str().c_str()); +} + +void PlayerbotAI::SendOrders(Player& player) +{ + std::ostringstream out; + + if (!m_combatOrder) + out << "Got no combat orders!"; + else if (m_combatOrder & ORDERS_TANK) + out << "I TANK"; + else if (m_combatOrder & ORDERS_ASSIST) + out << "I ASSIST " << (m_targetAssist ? m_targetAssist->GetName() : "unknown"); + else if (m_combatOrder & ORDERS_HEAL) + out << "I HEAL"; + if ((m_combatOrder & ORDERS_PRIMARY) && (m_combatOrder & ORDERS_SECONDARY)) + out << " and "; + if (m_combatOrder & ORDERS_PROTECT) + out << "I PROTECT " << (m_targetProtect ? m_targetProtect->GetName() : "unknown"); + out << "."; + + if (m_mgr->m_confDebugWhisper) + { + out << " " << (IsInCombat() ? "I'm in COMBAT! " : "Not in combat. "); + out << "Current state is "; + if (m_botState == BOTSTATE_NORMAL) + out << "NORMAL"; + else if (m_botState == BOTSTATE_COMBAT) + out << "COMBAT"; + else if (m_botState == BOTSTATE_DEAD) + out << "DEAD"; + else if (m_botState == BOTSTATE_DEADRELEASED) + out << "RELEASED"; + else if (m_botState == BOTSTATE_LOOTING) + out << "LOOTING"; + out << ". Movement order is "; + if (m_movementOrder == MOVEMENT_NONE) + out << "NONE"; + else if (m_movementOrder == MOVEMENT_FOLLOW) + out << "FOLLOW " << (m_followTarget ? m_followTarget->GetName() : "unknown"); + else if (m_movementOrder == MOVEMENT_STAY) + out << "STAY"; + out << ". Got " << m_attackerInfo.size() << " attacker(s) in list."; + out << " Next action in " << (m_ignoreAIUpdatesUntilTime - time(0)) << "sec."; + } + + TellMaster(out.str().c_str()); +} + +// handle outgoing packets the server would send to the client +void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) +{ + switch (packet.GetOpcode()) + { + case SMSG_DUEL_WINNER: + { + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_APPLAUD); + return; + } + case SMSG_DUEL_COMPLETE: + { + m_ignoreAIUpdatesUntilTime = time(0) + 4; + m_ScenarioType = SCENARIO_PVEEASY; + m_bot->GetMotionMaster()->Clear(true); + return; + } + case SMSG_DUEL_OUTOFBOUNDS: + { + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_CHICKEN); + return; + } + case SMSG_DUEL_REQUESTED: + { + m_ignoreAIUpdatesUntilTime = 0; + WorldPacket p(packet); + uint64 flagGuid; + p >> flagGuid; + uint64 playerGuid; + p >> playerGuid; + Player* const pPlayer = ObjectAccessor::FindPlayer(playerGuid); + if (canObeyCommandFrom(*pPlayer)) + { + m_bot->GetMotionMaster()->Clear(true); + WorldPacket* const packet = new WorldPacket(CMSG_DUEL_ACCEPTED, 8); + *packet << flagGuid; + m_bot->GetSession()->QueuePacket(packet); // queue the packet to get around race condition + + // follow target in casting range + float angle = rand_float(0, M_PI_F); + float dist = rand_float(4, 10); + + m_bot->GetMotionMaster()->Clear(true); + m_bot->GetMotionMaster()->MoveFollow(pPlayer, dist, angle); + + m_bot->SetSelectionGuid(ObjectGuid(playerGuid)); + m_ignoreAIUpdatesUntilTime = time(0) + 4; + m_ScenarioType = SCENARIO_DUEL; + } + return; + } + + case SMSG_INVENTORY_CHANGE_FAILURE: + { + TellMaster("I can't use that."); + return; + } + case SMSG_SPELL_FAILURE: + { + WorldPacket p(packet); + uint8 castCount; + uint32 spellId; + + uint64 casterGuid = p.readPackGUID(); + if (casterGuid != m_bot->GetGUID()) + return; + + p >> castCount >> spellId; + if (m_CurrentlyCastingSpellId == spellId) + { + m_ignoreAIUpdatesUntilTime = time(0); + m_CurrentlyCastingSpellId = 0; + } + return; + } + + // if a change in speed was detected for the master + // make sure we have the same mount status + case SMSG_FORCE_RUN_SPEED_CHANGE: + { + WorldPacket p(packet); + uint64 guid = p.readPackGUID(); + if (guid != GetMaster()->GetGUID()) + return; + if (GetMaster()->IsMounted() && !m_bot->IsMounted()) + { + //Player Part + if (!GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).empty()) + { + int32 master_speed1 = 0; + int32 master_speed2 = 0; + master_speed1 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[1]; + master_speed2 = GetMaster()->GetAurasByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[2]; + + //Bot Part + uint32 spellMount = 0; + for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + continue; + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + if (pSpellInfo->EffectApplyAuraName[0] == SPELL_AURA_MOUNTED) + { + if (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + { + if (pSpellInfo->EffectBasePoints[1] == master_speed1) + { + spellMount = spellId; + break; + } + } + else if ((pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) + { + if ((pSpellInfo->EffectBasePoints[1] == master_speed1) + && (pSpellInfo->EffectBasePoints[2] == master_speed2)) + { + spellMount = spellId; + break; + } + } + else if ((pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED)) + if ((pSpellInfo->EffectBasePoints[2] == master_speed2) + && (pSpellInfo->EffectBasePoints[1] == master_speed1)) + { + spellMount = spellId; + break; + } + } + } + if (spellMount > 0) m_bot->CastSpell(m_bot, spellMount, false); + } + } + else if (!GetMaster()->IsMounted() && m_bot->IsMounted()) + { + WorldPacket emptyPacket; + m_bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); //updated code + } + return; + } + + // handle flying acknowledgement + case SMSG_MOVE_SET_CAN_FLY: + { + WorldPacket p(packet); + uint64 guid = p.readPackGUID(); + if (guid != m_bot->GetGUID()) + return; + m_bot->m_movementInfo.AddMovementFlag(MOVEFLAG_FLYING); + //m_bot->SetSpeed(MOVE_RUN, GetMaster()->GetSpeed(MOVE_FLIGHT) +0.1f, true); + return; + } + + // handle dismount flying acknowledgement + case SMSG_MOVE_UNSET_CAN_FLY: + { + WorldPacket p(packet); + uint64 guid = p.readPackGUID(); + if (guid != m_bot->GetGUID()) + return; + m_bot->m_movementInfo.RemoveMovementFlag(MOVEFLAG_FLYING); + //m_bot->SetSpeed(MOVE_RUN,GetMaster()->GetSpeedRate(MOVE_RUN),true); + return; + } + + // 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: + { + WorldPacket p(packet); + std::string name; + p >> name; + if (m_bot->GetGroup() && name == m_bot->GetName()) + { + if (m_bot->GetGroup()->IsMember(GetMaster()->GetGUID())) + { + p.resize(8); + p << GetMaster()->GetGUID(); + m_bot->GetSession()->HandleGroupSetLeaderOpcode(p); + } + else + { + p.clear(); // not really needed + m_bot->GetSession()->HandleGroupDisbandOpcode(p); // packet not used updated code + } + } + return; + } + + // If the master leaves the group, then the bot leaves too + case SMSG_PARTY_COMMAND_RESULT: + { + WorldPacket p(packet); + uint32 operation; + p >> operation; + std::string member; + p >> member; + uint32 result; + p >> result; + p.clear(); + if (operation == PARTY_OP_LEAVE) + if (member == GetMaster()->GetName()) + m_bot->GetSession()->HandleGroupDisbandOpcode(p); // packet not used updated code + return; + } + + // Handle Group invites (auto accept if master is in group, otherwise decline & send message + case SMSG_GROUP_INVITE: + { + if (m_bot->GetGroupInvite()) + { + const Group* const grp = m_bot->GetGroupInvite(); + if (!grp) + return; + + Player* const inviter = sObjectMgr.GetPlayer(grp->GetLeaderGuid()); + if (!inviter) + return; + + WorldPacket p; + if (!canObeyCommandFrom(*inviter)) + { + std::string buf = "I can't accept your invite unless you first invite my master "; + buf += GetMaster()->GetName(); + buf += "."; + SendWhisper(buf, *inviter); + m_bot->GetSession()->HandleGroupDeclineOpcode(p); // packet not used + } + else + m_bot->GetSession()->HandleGroupAcceptOpcode(p); // packet not used + } + return; + } + + // 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: + { + if (m_bot->GetTrader() == NULL) + break; + + WorldPacket p(packet); + uint32 status; + p >> status; + p.resize(4); + + //4 == TRADE_STATUS_TRADE_ACCEPT + if (status == 4) + m_bot->GetSession()->HandleAcceptTradeOpcode(p); // packet not used + + //1 == TRADE_STATUS_BEGIN_TRADE + else if (status == 1) + { + m_bot->GetSession()->HandleBeginTradeOpcode(p); // packet not used + + if (!canObeyCommandFrom(*(m_bot->GetTrader()))) + { + SendWhisper("I'm not allowed to trade you any of my items, but you are free to give me money or items.", *(m_bot->GetTrader())); + return; + } + + // list out items available for trade + std::ostringstream out; + + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + const Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem && pItem->CanBeTraded()) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + std::string itemName = pItemProto->Name1; + ItemLocalization(itemName, pItemProto->ItemId); + + out << " |cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + if (pItem->GetCount() > 1) + out << "x" << pItem->GetCount() << ' '; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + const Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem && pItem->CanBeTraded()) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + std::string itemName = pItemProto->Name1; + ItemLocalization(itemName, pItemProto->ItemId); + + // item link format: http://www.wowwiki.com/ItemString + // itemId, enchantId, jewelId1, jewelId2, jewelId3, jewelId4, suffixId, uniqueId + out << " |cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << itemName + << "]|h|r"; + if (pItem->GetCount() > 1) + out << "x" << pItem->GetCount() << ' '; + } + } + } + + // calculate how much money bot has + uint32 copper = m_bot->GetMoney(); + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + copper -= (silver * 100); + + // send bot the message + std::ostringstream whisper; + whisper << "I have |cff00ff00" << gold + << "|r|cfffffc00g|r|cff00ff00" << silver + << "|r|cffcdcdcds|r|cff00ff00" << copper + << "|r|cffffd333c|r" << " and the following items:"; + SendWhisper(whisper.str().c_str(), *(m_bot->GetTrader())); + ChatHandler ch(m_bot->GetTrader()); + ch.SendSysMessage(out.str().c_str()); + } + return; + } + + case SMSG_SPELL_GO: + { + WorldPacket p(packet); + uint64 castItemGuid = p.readPackGUID(); + uint64 casterGuid = p.readPackGUID(); + if (casterGuid != m_bot->GetGUID()) + return; + + uint32 spellId; + p >> spellId; + uint16 castFlags; + p >> castFlags; + uint32 msTime; + p >> msTime; + uint8 numHit; + p >> numHit; + + if (m_CurrentlyCastingSpellId == spellId) + { + Spell* const pSpell = m_bot->FindCurrentSpellBySpellId(spellId); + if (!pSpell) + return; + + if (pSpell->IsChannelActive() || pSpell->IsAutoRepeat()) + m_ignoreAIUpdatesUntilTime = time(0) + (GetSpellDuration(pSpell->m_spellInfo) / 1000) + 1; + else if (pSpell->IsAutoRepeat()) + m_ignoreAIUpdatesUntilTime = time(0) + 6; + else + { + m_ignoreAIUpdatesUntilTime = time(0) + 1; + m_CurrentlyCastingSpellId = 0; + } + } + return; + } + + // if someone tries to resurrect, then accept + case SMSG_RESURRECT_REQUEST: + { + if (!m_bot->isAlive()) + { + WorldPacket p(packet); + ObjectGuid guid; + p >> guid; + + WorldPacket* const packet = new WorldPacket(CMSG_RESURRECT_RESPONSE, 8+1); + *packet << guid; + *packet << uint8(1); // accept + m_bot->GetSession()->QueuePacket(packet); // queue the packet to get around race condition + + // set back to normal + SetState(BOTSTATE_NORMAL); + SetIgnoreUpdateTime(0); + } + return; + } + + /* 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; + + default: + { + const char* oc = LookupOpcodeName(packet.GetOpcode()); + + std::ostringstream out; + out << "botout: " << oc; + sLog.outError(out.str().c_str()); + + //TellMaster(oc); + } + */ + } +} + +uint8 PlayerbotAI::GetHealthPercent(const Unit& target) const +{ + return (static_cast (target.GetHealth()) / target.GetMaxHealth()) * 100; +} + +uint8 PlayerbotAI::GetHealthPercent() const +{ + return GetHealthPercent(*m_bot); +} + +uint8 PlayerbotAI::GetManaPercent(const Unit& target) const +{ + return (static_cast (target.GetPower(POWER_MANA)) / target.GetMaxPower(POWER_MANA)) * 100; +} + +uint8 PlayerbotAI::GetManaPercent() const +{ + return GetManaPercent(*m_bot); +} + +uint8 PlayerbotAI::GetBaseManaPercent(const Unit& target) const +{ + if (target.GetPower(POWER_MANA) >= target.GetCreateMana()) + return (100); + else + return (static_cast (target.GetPower(POWER_MANA)) / target.GetCreateMana()) * 100; +} + +uint8 PlayerbotAI::GetBaseManaPercent() const +{ + return GetBaseManaPercent(*m_bot); +} + +uint8 PlayerbotAI::GetRageAmount(const Unit& target) const +{ + return (static_cast (target.GetPower(POWER_RAGE))); +} + +uint8 PlayerbotAI::GetRageAmount() const +{ + return GetRageAmount(*m_bot); +} + +uint8 PlayerbotAI::GetEnergyAmount(const Unit& target) const +{ + return (static_cast (target.GetPower(POWER_ENERGY))); +} + +uint8 PlayerbotAI::GetEnergyAmount() const +{ + return GetEnergyAmount(*m_bot); +} + +uint8 PlayerbotAI::GetRunicPower(const Unit& target) const +{ + return (static_cast(target.GetPower(POWER_RUNIC_POWER))); +} + +uint8 PlayerbotAI::GetRunicPower() const +{ + return GetRunicPower(*m_bot); +} + +//typedef std::pair spellEffectPair; +//typedef std::multimap AuraMap; + +bool PlayerbotAI::HasAura(uint32 spellId, const Unit& player) const +{ + if (spellId <= 0) + return false; + + for (Unit::SpellAuraHolderMap::const_iterator iter = player.GetSpellAuraHolderMap().begin(); iter != player.GetSpellAuraHolderMap().end(); ++iter) + { + if (iter->second->GetId() == spellId) + return true; + } + return false; +} + +bool PlayerbotAI::HasAura(const char* spellName) const +{ + return HasAura(spellName, *m_bot); +} + +bool PlayerbotAI::HasAura(const char* spellName, const Unit& player) const +{ + uint32 spellId = getSpellId(spellName); + return (spellId) ? HasAura(spellId, player) : false; +} + +// looks through all items / spells that bot could have to get a mount +Item* PlayerbotAI::FindMount(uint32 matchingRidingSkill) const +{ + // list out items in main backpack + + Item* partialMatch = NULL; + + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK || pItemProto->RequiredSkill != SKILL_RIDING) + continue; + + if (pItemProto->RequiredSkillRank == matchingRidingSkill) + return pItem; + + else if (!partialMatch || (partialMatch && partialMatch->GetProto()->RequiredSkillRank < pItemProto->RequiredSkillRank)) + partialMatch = pItem; + } + } + + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK || pItemProto->RequiredSkill != SKILL_RIDING) + continue; + + if (pItemProto->RequiredSkillRank == matchingRidingSkill) + return pItem; + + else if (!partialMatch || (partialMatch && partialMatch->GetProto()->RequiredSkillRank < pItemProto->RequiredSkillRank)) + partialMatch = pItem; + } + } + } + return partialMatch; +} + +Item* PlayerbotAI::FindFood() const +{ + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + 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->SubClass == ITEM_SUBCLASS_FOOD) + // if is FOOD + // this enum is no longer defined in mangos. Is it no longer valid? + // according to google it was 11 + if (pItemProto->Spells[0].SpellCategory == 11) + return pItem; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + if (!pItemProto || m_bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) + continue; + + // this enum is no longer defined in mangos. Is it no longer valid? + // according to google it was 11 + if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_FOOD) + // if is FOOD + // this enum is no longer defined in mangos. Is it no longer valid? + // according to google it was 11 + // if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_FOOD) + if (pItemProto->Spells[0].SpellCategory == 11) + return pItem; + } + } + } + return NULL; +} + +Item* PlayerbotAI::FindDrink() const +{ + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + 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->SubClass == ITEM_SUBCLASS_FOOD) + // if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_DRINK) + + // this enum is no longer defined in mangos. Is it no longer valid? + // according to google it was 59 + // if (pItemProto->Spells[0].SpellCategory == 59) + if (pItemProto->Spells[0].SpellCategory == 59) + return pItem; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + 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->SubClass == ITEM_SUBCLASS_FOOD) + // if is WATER + // SPELL_CATEGORY_DRINK is no longer defined in an enum in mangos + // google says the valus is 59. Is this still valid? + // if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_DRINK) + if (pItemProto->Spells[0].SpellCategory == 59) + return pItem; + } + } + } + return NULL; +} + +Item* PlayerbotAI::FindBandage() const +{ + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + 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->SubClass == ITEM_SUBCLASS_BANDAGE) + return pItem; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + 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->SubClass == ITEM_SUBCLASS_BANDAGE) + return pItem; + } + } + } + return NULL; +} +//Find Poison ...Natsukawa +Item* PlayerbotAI::FindPoison() const +{ + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + 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->SubClass == 6) + return pItem; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + 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->SubClass == 6) + return pItem; + } + } + } + return NULL; +} + +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++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + 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; + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + 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; + } + } + } + return NULL; +} + +void PlayerbotAI::InterruptCurrentCastingSpell() +{ + //TellMaster("I'm interrupting my current spell!"); + WorldPacket* const packet = new WorldPacket(CMSG_CANCEL_CAST, 5); //changed from thetourist suggestion + *packet << m_CurrentlyCastingSpellId; + *packet << m_targetGuidCommand; //changed from thetourist suggestion + m_CurrentlyCastingSpellId = 0; + m_bot->GetSession()->QueuePacket(packet); +} + +void PlayerbotAI::Feast() +{ + // stand up if we are done feasting + if (!(m_bot->GetHealth() < m_bot->GetMaxHealth() || (m_bot->getPowerType() == POWER_MANA && m_bot->GetPower(POWER_MANA) < m_bot->GetMaxPower(POWER_MANA)))) + { + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + return; + } + + // wait 3 seconds before checking if we need to drink more or eat more + time_t currentTime = time(0); + m_ignoreAIUpdatesUntilTime = currentTime + 3; + + // should we drink another + if (m_bot->getPowerType() == POWER_MANA && currentTime > m_TimeDoneDrinking + && ((static_cast (m_bot->GetPower(POWER_MANA)) / m_bot->GetMaxPower(POWER_MANA)) < 0.8)) + { + Item* pItem = FindDrink(); + if (pItem != NULL) + { + UseItem(pItem); + m_TimeDoneDrinking = currentTime + 30; + return; + } + TellMaster("I need water."); + } + + // should we eat another + if (currentTime > m_TimeDoneEating && ((static_cast (m_bot->GetHealth()) / m_bot->GetMaxHealth()) < 0.8)) + { + Item* pItem = FindFood(); + if (pItem != NULL) + { + //TellMaster("eating now..."); + UseItem(pItem); + m_TimeDoneEating = currentTime + 30; + return; + } + TellMaster("I need food."); + } + + // if we are no longer eating or drinking + // because we are out of items or we are above 80% in both stats + if (currentTime > m_TimeDoneEating && currentTime > m_TimeDoneDrinking) + { + TellMaster("done feasting!"); + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + } +} + +// intelligently sets a reasonable combat order for this bot +// based on its class / level / etc +void PlayerbotAI::GetCombatTarget(Unit* forcedTarget) +{ + // set combat state, and clear looting, etc... + if (m_botState != BOTSTATE_COMBAT) + { + SetState(BOTSTATE_COMBAT); + SetQuestNeedItems(); + m_lootCreature.clear(); + m_lootCurrent = 0; + m_targetCombat = 0; + } + + // update attacker info now + UpdateAttackerInfo(); + + // check for attackers on protected unit, and make it a forcedTarget if any + if (!forcedTarget && (m_combatOrder & ORDERS_PROTECT) && m_targetProtect != 0) + { + Unit *newTarget = FindAttacker((ATTACKERINFOTYPE) (AIT_VICTIMNOTSELF | AIT_HIGHESTTHREAT), m_targetProtect); + if (newTarget && newTarget != m_targetCombat) + { + forcedTarget = newTarget; + m_targetType = TARGET_THREATEN; + if (m_mgr->m_confDebugWhisper) + TellMaster("Changing target to %s to protect %s", forcedTarget->GetName(), m_targetProtect->GetName()); + } + } + else if (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); + } + + // we already have a target and we are not forced to change it + if (m_targetCombat && !forcedTarget) + return; + + // are we forced on a target? + if (forcedTarget) + { + m_targetCombat = forcedTarget; + m_targetChanged = true; + } + // do we have to assist someone? + if (!m_targetCombat && (m_combatOrder & ORDERS_ASSIST) && m_targetAssist != 0) + { + 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_targetChanged = true; + } + // are there any other attackers? + if (!m_targetCombat) + { + m_targetCombat = FindAttacker(); + m_targetType = (m_combatOrder == ORDERS_TANK ? TARGET_THREATEN : TARGET_NORMAL); + m_targetChanged = true; + } + // no attacker found anyway + if (!m_targetCombat) + { + m_targetType = TARGET_NORMAL; + m_targetChanged = false; + return; + } + + // if thing to attack is in a duel, then ignore and don't call updateAI for 6 seconds + // this method never gets called when the bot is in a duel and this code + // prevents bot from helping + if (m_targetCombat->GetTypeId() == TYPEID_PLAYER && dynamic_cast (m_targetCombat)->duel) + { + m_ignoreAIUpdatesUntilTime = time(0) + 6; + return; + } + + m_bot->SetSelectionGuid((m_targetCombat->GetObjectGuid())); + m_ignoreAIUpdatesUntilTime = time(0) + 1; + + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + m_bot->Attack(m_targetCombat, true); + + // add thingToAttack to loot list + m_lootCreature.push_back(m_targetCombat->GetGUID()); + + // set movement generators for combat movement + MovementClear(); + return; +} + +void PlayerbotAI::DoNextCombatManeuver() +{ + // check for new targets + GetCombatTarget(); + // 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 + // clear our orders so we can get orders in next update + if (!m_targetCombat || m_targetCombat->isDead() || !m_targetCombat->IsInWorld() || !m_bot->IsHostileTo(m_targetCombat) || !m_bot->IsInMap(m_targetCombat)) + { + m_bot->AttackStop(); + m_bot->SetSelectionGuid(ObjectGuid()); + MovementReset(); + m_bot->InterruptNonMeleeSpells(true); + m_targetCombat = 0; + m_targetChanged = false; + m_targetType = TARGET_NORMAL; + return; + } + + // do opening moves, if we changed target + if (m_targetChanged) + { + if (GetClassAI()) + m_targetChanged = GetClassAI()->DoFirstCombatManeuver(m_targetCombat); + else + m_targetChanged = false; + } + + // do normal combat movement + DoCombatMovement(); + + if (GetClassAI() && !m_targetChanged) + (GetClassAI())->DoNextCombatManeuver(m_targetCombat); +} + +void PlayerbotAI::DoCombatMovement() +{ + if (!m_targetCombat) return; + + float targetDist = m_bot->GetDistance(m_targetCombat); + + if (m_combatStyle == COMBAT_MELEE && !m_bot->hasUnitState(UNIT_STAT_CHASE) && ((m_movementOrder == MOVEMENT_STAY && targetDist <= ATTACK_DISTANCE) || (m_movementOrder != MOVEMENT_STAY))) + // melee combat - chase target if in range or if we are not forced to stay + m_bot->GetMotionMaster()->MoveChase(m_targetCombat); + else if (m_combatStyle == COMBAT_RANGED && m_movementOrder != MOVEMENT_STAY) + { + // ranged combat - just move within spell range + // TODO: just follow in spell range! how to determine bots spell range? + if (targetDist > 25.0f) + m_bot->GetMotionMaster()->MoveChase(m_targetCombat); + else + MovementClear(); + } +} + +void PlayerbotAI::SetQuestNeedItems() +{ + // reset values first + m_needItemList.clear(); + m_lootCreature.clear(); + m_lootCurrent = 0; + + // run through accepted quests, get quest infoand data + for (QuestStatusMap::iterator iter = m_bot->getQuestStatusMap().begin(); iter != m_bot->getQuestStatusMap().end(); ++iter) + { + const Quest *qInfo = sObjectMgr.GetQuestTemplate(iter->first); + if (!qInfo) + continue; + + QuestStatusData *qData = &iter->second; + // only check quest if it is incomplete + if (qData->m_status != QUEST_STATUS_INCOMPLETE) + continue; + + // check for items we not have enough of + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + if (!qInfo->ReqItemCount[i] || (qInfo->ReqItemCount[i] - qData->m_itemcount[i]) <= 0) + continue; + m_needItemList[qInfo->ReqItemId[i]] = (qInfo->ReqItemCount[i] - qData->m_itemcount[i]); + } + } +} + +void PlayerbotAI::SetState(BotState state) +{ + //sLog.outDebug( "[PlayerbotAI]: %s switch state %d to %d", m_bot->GetName(), m_botState, state ); + m_botState = state; +} + +uint8 PlayerbotAI::GetFreeBagSpace() const +{ + uint8 space = 0; + for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item *pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (!pItem) + ++space; + } + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + Bag* pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pBag && pBag->GetProto()->BagFamily == BAG_FAMILY_MASK_NONE) + space += pBag->GetFreeSlots(); + } + return space; +} + +void PlayerbotAI::DoLoot() +{ + if (!m_lootCurrent && m_lootCreature.empty()) + { + //sLog.outDebug( "[PlayerbotAI]: %s reset loot list / go back to idle", m_bot->GetName() ); + m_botState = BOTSTATE_NORMAL; + SetQuestNeedItems(); + return; + } + + if (!m_lootCurrent) + { + m_lootCurrent = m_lootCreature.front(); + m_lootCreature.pop_front(); + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + // check if we got a creature and if it is still a corpse, otherwise bot runs to spawn point + if (!c || c->getDeathState() != CORPSE || GetMaster()->GetDistance(c) > BOTLOOT_DISTANCE) + { + m_lootCurrent = 0; + return; + } + m_bot->GetMotionMaster()->MovePoint(c->GetMapId(), c->GetPositionX(), c->GetPositionY(), c->GetPositionZ()); + //sLog.outDebug( "[PlayerbotAI]: %s is going to loot '%s' deathState=%d", m_bot->GetName(), c->GetName(), c->getDeathState() ); + } + else + { + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + if (!c || c->getDeathState() != CORPSE || GetMaster()->GetDistance(c) > BOTLOOT_DISTANCE) + { + m_lootCurrent = 0; + return; + } + if (m_bot->IsWithinDistInMap(c, INTERACTION_DISTANCE)) + { + // check for needed items + m_bot->SendLoot(m_lootCurrent, LOOT_CORPSE); + Loot *loot = &c->loot; + uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot); + //sLog.outDebug( "[PlayerbotAI]: %s looting: '%s' got %d items", m_bot->GetName(), c->GetName(), loot->GetMaxSlotInLootFor( m_bot ) ); + for (uint32 l = 0; l < lootNum; l++) + { + QuestItem *qitem = 0, *ffaitem = 0, *conditem = 0; + LootItem *item = loot->LootItemInSlot(l, m_bot, &qitem, &ffaitem, &conditem); + if (!item) + continue; + + if (!qitem && item->is_blocked) + { + m_bot->SendLootRelease(m_bot->GetLootGUID()); + continue; + } + + if (m_needItemList[item->itemid] > 0) + { + //sLog.outDebug( "[PlayerbotAI]: %s looting: needed item '%s'", m_bot->GetName(), sObjectMgr.GetItemLocale(item->itemid)->Name ); + ItemPosCountVec dest; + if (m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + { + Item * newitem = m_bot->StoreNewItem(dest, item->itemid, true, item->randomPropertyId); + + if (qitem) + { + qitem->is_looted = true; + if (item->freeforall || loot->GetPlayerQuestItems().size() == 1) + m_bot->SendNotifyLootItemRemoved(l); + else + loot->NotifyQuestItemRemoved(qitem->index); + } + else + { + if (ffaitem) + { + ffaitem->is_looted = true; + m_bot->SendNotifyLootItemRemoved(l); + } + else + { + if (conditem) + conditem->is_looted = true; + loot->NotifyItemRemoved(l); + } + } + if (!item->freeforall) + item->is_looted = true; + --loot->unlootedCount; + m_bot->SendNewItem(newitem, uint32(item->count), false, false, true); + m_bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item->itemid, item->count); + } + } + } + // release loot + // if( uint64 lguid = m_bot->GetLootGUID() && m_bot->GetSession() ) + m_bot->GetSession()->DoLootRelease(m_lootCurrent); + //else if( !m_bot->GetSession() ) + // sLog.outDebug( "[PlayerbotAI]: %s has no session. Cannot release loot!", m_bot->GetName() ); + + // clear movement target, take next target on next update + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + SetQuestNeedItems(); + //sLog.outDebug( "[PlayerbotAI]: %s looted target 0x%08X", m_bot->GetName(), m_lootCurrent ); + } + } +} + +void PlayerbotAI::AcceptQuest(Quest const *qInfo, Player *pGiver) +{ + if (!qInfo || !pGiver) + return; + + uint32 quest = qInfo->GetQuestId(); + + if (!pGiver->CanShareQuest(qInfo->GetQuestId())) + { + // giver can't share quest + m_bot->SetDivider(0); + return; + } + + if (!m_bot->CanTakeQuest(qInfo, false)) + { + // can't take quest + m_bot->SetDivider(0); + return; + } + + if (m_bot->GetDivider() != 0) + { + // send msg to quest giving player + pGiver->SendPushToPartyResponse(m_bot, QUEST_PARTY_MSG_ACCEPT_QUEST); + m_bot->SetDivider(0); + } + + if (m_bot->CanAddQuest(qInfo, false)) + { + m_bot->AddQuest(qInfo, pGiver); + + if (m_bot->CanCompleteQuest(quest)) + m_bot->CompleteQuest(quest); + + // 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(), true); + } +} + +void PlayerbotAI::TurnInQuests(WorldObject *questgiver) +{ + ObjectGuid giverGUID = questgiver->GetObjectGuid(); + + if (!m_bot->IsInMap(questgiver)) + TellMaster("hey you are turning in quests without me!"); + else + { + m_bot->SetSelectionGuid(giverGUID); + + // auto complete every completed quest this NPC has + m_bot->PrepareQuestMenu(giverGUID.GetRawValue()); + QuestMenu& questMenu = m_bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + uint32 questID = qItem.m_qId; + Quest const* pQuest = sObjectMgr.GetQuestTemplate(questID); + + std::ostringstream out; + std::string questTitle = pQuest->GetTitle(); + QuestLocalization(questTitle, questID); + + QuestStatus status = m_bot->GetQuestStatus(questID); + + // if quest is complete, turn it in + if (status == QUEST_STATUS_COMPLETE) + { + // if bot hasn't already turned quest in + if (!m_bot->GetQuestRewardStatus(questID)) + { + // auto reward quest if no choice in reward + if (pQuest->GetRewChoiceItemsCount() == 0) + { + if (m_bot->CanRewardQuest(pQuest, false)) + { + m_bot->RewardQuest(pQuest, 0, questgiver, false); + out << "Quest complete: |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + else + out << "|cffff0000Unable to turn quest in:|r |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + + // auto reward quest if one item as reward + else if (pQuest->GetRewChoiceItemsCount() == 1) + { + int rewardIdx = 0; + ItemPrototype const *pRewardItem = sObjectMgr.GetItemPrototype(pQuest->RewChoiceItemId[rewardIdx]); + std::string itemName = pRewardItem->Name1; + ItemLocalization(itemName, pRewardItem->ItemId); + if (m_bot->CanRewardQuest(pQuest, rewardIdx, false)) + { + m_bot->RewardQuest(pQuest, rewardIdx, questgiver, true); + + std::string itemName = pRewardItem->Name1; + ItemLocalization(itemName, pRewardItem->ItemId); + + out << "Quest complete: " + << " |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 + 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"; + } + + // else multiple rewards - let master pick + else + { + out << "What reward should I take for |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() + << "|h[" << questTitle << "]|h|r? "; + for (uint8 i = 0; i < pQuest->GetRewChoiceItemsCount(); ++i) + { + ItemPrototype const * const pRewardItem = sObjectMgr.GetItemPrototype(pQuest->RewChoiceItemId[i]); + std::string itemName = pRewardItem->Name1; + ItemLocalization(itemName, pRewardItem->ItemId); + out << "|cffffffff|Hitem:" << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + } + } + } + } + + else if (status == QUEST_STATUS_INCOMPLETE) + out << "|cffff0000Quest incomplete:|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"; + + if (!out.str().empty()) + TellMaster(out.str()); + } + } +} + +bool PlayerbotAI::IsInCombat() +{ + Pet *pet; + bool inCombat = false; + inCombat |= m_bot->isInCombat(); + pet = m_bot->GetPet(); + if (pet) + inCombat |= pet->isInCombat(); + inCombat |= GetMaster()->isInCombat(); + if (m_bot->GetGroup()) + { + GroupReference *ref = m_bot->GetGroup()->GetFirstMember(); + while (ref) + { + inCombat |= ref->getSource()->isInCombat(); + pet = ref->getSource()->GetPet(); + if (pet) + inCombat |= pet->isInCombat(); + ref = ref->next(); + } + } + return inCombat; +} + +void PlayerbotAI::UpdateAttackersForTarget(Unit *victim) +{ + HostileReference *ref = victim->getHostileRefManager().getFirst(); + while (ref) + { + ThreatManager *target = ref->getSource(); + uint64 guid = target->getOwner()->GetGUID(); + m_attackerInfo[guid].attacker = target->getOwner(); + m_attackerInfo[guid].victim = target->getOwner()->getVictim(); + m_attackerInfo[guid].threat = target->getThreat(victim); + m_attackerInfo[guid].count = 1; + //m_attackerInfo[guid].source = 1; // source is not used so far. + ref = ref->next(); + } +} + +void PlayerbotAI::UpdateAttackerInfo() +{ + // clear old list + m_attackerInfo.clear(); + + // check own attackers + UpdateAttackersForTarget(m_bot); + Pet *pet = m_bot->GetPet(); + if (pet) + UpdateAttackersForTarget(pet); + + // check master's attackers + UpdateAttackersForTarget(GetMaster()); + pet = GetMaster()->GetPet(); + if (pet) + UpdateAttackersForTarget(pet); + + // check all group members now + if (m_bot->GetGroup()) + { + GroupReference *gref = m_bot->GetGroup()->GetFirstMember(); + while (gref) + { + if (gref->getSource() == m_bot || gref->getSource() == GetMaster()) + { + gref = gref->next(); + continue; + } + + UpdateAttackersForTarget(gref->getSource()); + pet = gref->getSource()->GetPet(); + if (pet) + UpdateAttackersForTarget(pet); + + gref = gref->next(); + } + } + + // get highest threat not caused by bot for every entry in AttackerInfoList... + for (AttackerInfoList::iterator itr = m_attackerInfo.begin(); itr != m_attackerInfo.end(); ++itr) + { + if (!itr->second.attacker) + continue; + Unit *a = itr->second.attacker; + float t = 0.00; + std::list::const_iterator i = a->getThreatManager().getThreatList().begin(); + for (; i != a->getThreatManager().getThreatList().end(); ++i) + { + if ((*i)->getThreat() > t && (*i)->getTarget() != m_bot) + t = (*i)->getThreat(); + } + m_attackerInfo[itr->first].threat2 = t; + } + + // DEBUG: output attacker info + //sLog.outBasic( "[PlayerbotAI]: %s m_attackerInfo = {", m_bot->GetName() ); + //for( AttackerInfoList::iterator i=m_attackerInfo.begin(); i!=m_attackerInfo.end(); ++i ) + // sLog.outBasic( "[PlayerbotAI]: [%016I64X] { %08X, %08X, %.2f, %.2f, %d, %d }", + // i->first, + // (i->second.attacker?i->second.attacker->GetGUIDLow():0), + // (i->second.victim?i->second.victim->GetGUIDLow():0), + // i->second.threat, + // i->second.threat2, + // i->second.count, + // i->second.source ); + //sLog.outBasic( "[PlayerbotAI]: };" ); +} + +uint32 PlayerbotAI::EstRepairAll() +{ + uint32 TotalCost = 0; + // equipped, backpack, bags itself + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + TotalCost += EstRepair(((INVENTORY_SLOT_BAG_0 << 8) | i)); + + // bank, buyback and keys not repaired + + // items in inventory bags + for (int j = INVENTORY_SLOT_BAG_START; j < INVENTORY_SLOT_BAG_END; ++j) + for (int i = 0; i < MAX_BAG_SIZE; ++i) + TotalCost += EstRepair(((j << 8) | i)); + return TotalCost; +} + +uint32 PlayerbotAI::EstRepair(uint16 pos) +{ + Item* item = m_bot->GetItemByPos(pos); + + uint32 TotalCost = 0; + if (!item) + return TotalCost; + + uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + if (!maxDurability) + return TotalCost; + + uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY); + + uint32 LostDurability = maxDurability - curDurability; + if (LostDurability > 0) + { + ItemPrototype const *ditemProto = item->GetProto(); + + DurabilityCostsEntry const *dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel); + if (!dcost) + { + sLog.outError("RepairDurability: Wrong item lvl %u", ditemProto->ItemLevel); + return TotalCost; + } + + uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2; + DurabilityQualityEntry const *dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId); + if (!dQualitymodEntry) + { + sLog.outError("RepairDurability: Wrong dQualityModEntry %u", dQualitymodEntryId); + return TotalCost; + } + + uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)]; + uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); + + if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT + costs = 1; + + TotalCost = costs; + } + return TotalCost; +} + +Unit *PlayerbotAI::FindAttacker(ATTACKERINFOTYPE ait, Unit *victim) +{ + // list empty? why are we here? + if (m_attackerInfo.empty()) + return 0; + + // not searching something specific - return first in list + if (!ait) + return (m_attackerInfo.begin())->second.attacker; + + float t = ((ait & AIT_HIGHESTTHREAT) ? 0.00 : 9999.00); + Unit *a = 0; + AttackerInfoList::iterator itr = m_attackerInfo.begin(); + for (; itr != m_attackerInfo.end(); ++itr) + { + if ((ait & AIT_VICTIMSELF) && !(ait & AIT_VICTIMNOTSELF) && itr->second.victim != m_bot) + continue; + + if (!(ait & AIT_VICTIMSELF) && (ait & AIT_VICTIMNOTSELF) && itr->second.victim == m_bot) + continue; + + if ((ait & AIT_VICTIMNOTSELF) && victim && itr->second.victim != victim) + continue; + + if (!(ait & (AIT_LOWESTTHREAT | AIT_HIGHESTTHREAT))) + { + a = itr->second.attacker; + itr = m_attackerInfo.end(); + } + else + { + if ((ait & AIT_HIGHESTTHREAT) && /*(itr->second.victim==m_bot) &&*/ itr->second.threat >= t) + { + t = itr->second.threat; + a = itr->second.attacker; + } + else if ((ait & AIT_LOWESTTHREAT) && /*(itr->second.victim==m_bot) &&*/ itr->second.threat <= t) + { + t = itr->second.threat; + a = itr->second.attacker; + } + } + } + return a; +} + +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 + co = ORDERS_RESET; + SetCombatOrder(co, target); +} + +void PlayerbotAI::SetCombatOrder(CombatOrderType co, Unit *target) +{ + if ((co == ORDERS_ASSIST || co == ORDERS_PROTECT) && !target) { + TellMaster("Erf, you forget to target assist/protect characters!"); + return; + } + if (co == ORDERS_RESET) { + m_combatOrder = ORDERS_NONE; + m_targetAssist = 0; + m_targetProtect = 0; + TellMaster("Orders are cleaned!"); + return; + } + if (co == ORDERS_PROTECT) + m_targetProtect = target; + else if (co == ORDERS_ASSIST) + m_targetAssist = target; + if ((co & ORDERS_PRIMARY)) + m_combatOrder = (CombatOrderType) (((uint32) m_combatOrder & (uint32) ORDERS_SECONDARY) | (uint32) co); + else + m_combatOrder = (CombatOrderType) (((uint32) m_combatOrder & (uint32) ORDERS_PRIMARY) | (uint32) co); + SendOrders(*GetMaster()); +} + +void PlayerbotAI::SetMovementOrder(MovementOrderType mo, Unit *followTarget) +{ + m_movementOrder = mo; + m_followTarget = followTarget; + MovementReset(); +} + +void PlayerbotAI::MovementReset() +{ + // stop moving... + MovementClear(); + + if (m_movementOrder == MOVEMENT_FOLLOW) + { + if (!m_followTarget) return; + + // target player is teleporting... + if (m_followTarget->GetTypeId() == TYPEID_PLAYER && ((Player*) m_followTarget)->IsBeingTeleported()) + return; + + // check if bot needs to teleport to reach target... + if (!m_bot->isInCombat()) + { + if (m_followTarget->GetTypeId() == TYPEID_PLAYER && ((Player*) m_followTarget)->GetCorpse()) + { + if (!FollowCheckTeleport(*((Player*) m_followTarget)->GetCorpse())) return; + } + else if (!FollowCheckTeleport(*m_followTarget)) return; + } + + if (m_bot->isAlive()) + { + float angle = rand_float(0, M_PI_F); + float dist = rand_float(m_mgr->m_confFollowDistance[0], m_mgr->m_confFollowDistance[1]); + m_bot->GetMotionMaster()->MoveFollow(m_followTarget, dist, angle); + } + } +} + +void PlayerbotAI::MovementUpdate() +{ + // send heartbeats to world + // m_bot->SendHeartBeat(false); + + // call set position (updates states, exploration, etc.) + m_bot->SetPosition(m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), m_bot->GetOrientation(), false); +} + +void PlayerbotAI::MovementClear() +{ + // stop... + m_bot->GetMotionMaster()->Clear(true); + m_bot->clearUnitState(UNIT_STAT_CHASE); + m_bot->clearUnitState(UNIT_STAT_FOLLOW); + + // stand up... + if (!m_bot->IsStandState()) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); +} + +bool PlayerbotAI::IsMoving() +{ + return (m_bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE ? false : true); +} + +void PlayerbotAI::SetInFront(const Unit* obj) +{ + m_bot->SetInFront(obj); + + // TODO: Schmoozerd wrote a patch which adds MovementInfo::ChangeOrientation() + // and added a call to it inside WorldObject::SetOrientation. Check if it is + // merged to the core. + // http://getmangos.com/community/viewtopic.php?pid=128003 + float ori = m_bot->GetAngle(obj); + float x, y, z; + x = m_bot->m_movementInfo.GetPos()->x; + y = m_bot->m_movementInfo.GetPos()->y; + z = m_bot->m_movementInfo.GetPos()->z; + m_bot->m_movementInfo.ChangePosition(x,y,z,ori); + + m_bot->SendHeartBeat(false); +} + +// some possible things to use in AI +//GetRandomContactPoint +//GetPower, GetMaxPower +// HasSpellCooldown +// IsAffectedBySpellmod +// isMoving +// hasUnitState(FLAG) FLAG like: UNIT_STAT_ROOT, UNIT_STAT_CONFUSED, UNIT_STAT_STUNNED +// hasAuraType + +void PlayerbotAI::UpdateAI(const uint32 p_time) +{ + if (m_bot->IsBeingTeleported() || m_bot->GetTrader()) + return; + + time_t currentTime = time(0); + if (currentTime < m_ignoreAIUpdatesUntilTime) + return; + + // default updates occur every two seconds + m_ignoreAIUpdatesUntilTime = time(0) + 2; + + // send heartbeat + MovementUpdate(); + + if (!m_bot->isAlive()) + { + if (m_botState != BOTSTATE_DEAD && m_botState != BOTSTATE_DEADRELEASED) + { + //sLog.outDebug( "[PlayerbotAI]: %s died and is not in correct state...", m_bot->GetName() ); + // clear loot list on death + m_lootCreature.clear(); + m_lootCurrent = 0; + // clear combat orders + m_bot->SetSelectionGuid(ObjectGuid()); + m_bot->GetMotionMaster()->Clear(true); + // set state to dead + SetState(BOTSTATE_DEAD); + // wait 30sec + m_ignoreAIUpdatesUntilTime = time(0) + 30; + } + else if (m_botState == BOTSTATE_DEAD) + { + // become ghost + if (m_bot->GetCorpse()) { + //sLog.outDebug( "[PlayerbotAI]: %s already has a corpse...", m_bot->GetName() ); + SetState(BOTSTATE_DEADRELEASED); + return; + } + m_bot->SetBotDeathTimer(); + m_bot->BuildPlayerRepop(); + // relocate ghost + WorldLocation loc; + Corpse *corpse = m_bot->GetCorpse(); + corpse->GetPosition(loc); + m_bot->TeleportTo(loc.mapid, loc.coord_x, loc.coord_y, loc.coord_z, m_bot->GetOrientation()); + // set state to released + SetState(BOTSTATE_DEADRELEASED); + } + else if (m_botState == BOTSTATE_DEADRELEASED) + { + // get bot's corpse + Corpse *corpse = m_bot->GetCorpse(); + if (!corpse) + //sLog.outDebug( "[PlayerbotAI]: %s has no corpse!", m_bot->GetName() ); + return; + // teleport ghost from graveyard to corpse + //sLog.outDebug( "[PlayerbotAI]: Teleport %s to corpse...", m_bot->GetName() ); + FollowCheckTeleport(*corpse); + // check if we are allowed to resurrect now + if (corpse->GetGhostTime() + m_bot->GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP) > time(0)) + { + m_ignoreAIUpdatesUntilTime = corpse->GetGhostTime() + m_bot->GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP); + //sLog.outDebug( "[PlayerbotAI]: %s has to wait for %d seconds to revive...", m_bot->GetName(), m_ignoreAIUpdatesUntilTime-time(0) ); + return; + } + // resurrect now + //sLog.outDebug( "[PlayerbotAI]: Reviving %s to corpse...", m_bot->GetName() ); + m_ignoreAIUpdatesUntilTime = time(0) + 6; + PlayerbotChatHandler ch(GetMaster()); + if (!ch.revive(*m_bot)) + { + ch.sysmessage(".. could not be revived .."); + return; + } + // set back to normal + SetState(BOTSTATE_NORMAL); + } + } + else + { + // if we are casting a spell then interrupt it + // make sure any actions that cast a spell set a proper m_ignoreAIUpdatesUntilTime! + Spell* const pSpell = GetCurrentSpell(); + if (pSpell && !(pSpell->IsChannelActive() || pSpell->IsAutoRepeat())) + InterruptCurrentCastingSpell(); + + // direct cast command from master + else if (m_spellIdCommand != 0) + { + Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, m_targetGuidCommand); + if (pTarget) + CastSpell(m_spellIdCommand, *pTarget); + m_spellIdCommand = 0; + m_targetGuidCommand = 0; + } + + // handle combat (either self/master/group in combat, or combat state and valid target) + else if (IsInCombat() || (m_botState == BOTSTATE_COMBAT && m_targetCombat)) + { + if (!pSpell || !pSpell->IsChannelActive()) + DoNextCombatManeuver(); + else + SetIgnoreUpdateTime(1); // It's better to update AI more frequently during combat + } + // bot was in combat recently - loot now + else if (m_botState == BOTSTATE_COMBAT) + { + SetState(BOTSTATE_LOOTING); + m_attackerInfo.clear(); + SetIgnoreUpdateTime(); + } + else if (m_botState == BOTSTATE_LOOTING) + { + DoLoot(); + SetIgnoreUpdateTime(); + } +/* + // are we sitting, if so feast if possible + else if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) + Feast(); + */ + // if commanded to follow master and not already following master then follow master + else if (!m_bot->isInCombat() && !IsMoving()) + MovementReset(); + + // do class specific non combat actions + else if (GetClassAI() && !m_bot->IsMounted()) + (GetClassAI())->DoNonCombatActions(); + } +} + +Spell* PlayerbotAI::GetCurrentSpell() const +{ + if (m_CurrentlyCastingSpellId == 0) + return NULL; + + Spell* const pSpell = m_bot->FindCurrentSpellBySpellId(m_CurrentlyCastingSpellId); + return pSpell; +} + +void PlayerbotAI::TellMaster(const std::string& text) const +{ + SendWhisper(text, *GetMaster()); +} + +void PlayerbotAI::TellMaster(const char *fmt, ...) const +{ + char temp_buf[1024]; + va_list ap; + va_start(ap, fmt); + size_t temp_len = vsnprintf(temp_buf, 1024, fmt, ap); + va_end(ap); + std::string str = temp_buf; + TellMaster(str); +} + +void PlayerbotAI::SendWhisper(const std::string& text, Player& player) const +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + m_bot->BuildPlayerChat(&data, CHAT_MSG_WHISPER, text, LANG_UNIVERSAL); + player.GetSession()->SendPacket(&data); +} + +bool PlayerbotAI::canObeyCommandFrom(const Player& player) const +{ + return player.GetSession()->GetAccountId() == GetMaster()->GetSession()->GetAccountId(); +} + +bool PlayerbotAI::CastSpell(const char* args) +{ + uint32 spellId = getSpellId(args); + return (spellId) ? CastSpell(spellId) : false; +} + +bool PlayerbotAI::CastSpell(uint32 spellId, Unit& target) +{ + ObjectGuid oldSel = m_bot->GetSelectionGuid(); + m_bot->SetSelectionGuid(target.GetObjectGuid()); + bool rv = CastSpell(spellId); + m_bot->SetSelectionGuid(oldSel); + return rv; +} + +bool PlayerbotAI::CastSpell(uint32 spellId) +{ + // some AIs don't check if the bot doesn't have spell before using it + // so just return false when this happens + if (spellId == 0) + return false; + + // check spell cooldown + if (m_bot->HasSpellCooldown(spellId)) + return false; + + // see Creature.cpp 1738 for reference + // don't allow bot to cast damage spells on friends + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + TellMaster("missing spell entry in CastSpell for spellid %u.", spellId); + return false; + } + + // set target + ObjectGuid targetGUID = m_bot->GetSelectionGuid(); + Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, targetGUID); + + if (!pTarget) + pTarget = m_bot; + + // Check spell range + std::map::iterator it = m_spellRangeMap.find(spellId); + if (it != m_spellRangeMap.end() && (int)it->second != 0) + { + float dist = m_bot->GetCombatDistance(pTarget); + if (dist > it->second + 1.25) // See Spell::CheckRange for modifier value + return false; + } + + // Check line of sight + if (!m_bot->IsWithinLOSInMap(pTarget)) + return false; + + if (IsPositiveSpell(spellId)) + { + if (pTarget && !m_bot->IsFriendlyTo(pTarget)) + pTarget = m_bot; + } + else + { + if (pTarget && m_bot->IsFriendlyTo(pTarget)) + return false; + + SetInFront(pTarget); + } + + // stop movement to prevent cancel spell casting + SpellCastTimesEntry const * castTimeEntry = sSpellCastTimesStore.LookupEntry(pSpellInfo->CastingTimeIndex); + if (castTimeEntry && castTimeEntry->CastTime) + { + sLog.outDebug("Bot movement reset for casting %s (%u)", pSpellInfo->SpellName[0], spellId); + MovementClear(); + } + + // actually cast spell + m_bot->CastSpell(pTarget, pSpellInfo, false); + + Spell* const pSpell = m_bot->FindCurrentSpellBySpellId(spellId); + if (!pSpell) + return false; + + m_CurrentlyCastingSpellId = spellId; + m_ignoreAIUpdatesUntilTime = time(0) + (int32) ((float) pSpell->GetCastTime() / 1000.0f) + 1; + + // if this caused the caster to move (blink) update the position + // I think this is normally done on the client + // this should be done on spell success + /* + if (name == "Blink") { + float x,y,z; + m_bot->GetPosition(x,y,z); + m_bot->GetNearPoint(m_bot, x, y, z, 1, 5, 0); + m_bot->Relocate(x,y,z); + m_bot->SendHeartBeat(true); + + } + */ + + return true; +} + +bool PlayerbotAI::CastPetSpell(uint32 spellId, Unit* target) +{ + if (spellId == 0) + return false; + + Pet* pet = m_bot->GetPet(); + if (!pet) + return false; + + if (pet->HasSpellCooldown(spellId)) + return false; + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + TellMaster("Missing spell entry in CastPetSpell()"); + return false; + } + + // set target + Unit* pTarget; + if (!target) + { + ObjectGuid targetGUID = m_bot->GetSelectionGuid(); + pTarget = ObjectAccessor::GetUnit(*m_bot, targetGUID); + } + else + pTarget = target; + + if (IsPositiveSpell(spellId)) + { + if (pTarget && !m_bot->IsFriendlyTo(pTarget)) + pTarget = m_bot; + } + else + { + if (pTarget && m_bot->IsFriendlyTo(pTarget)) + return false; + + if (!pet->isInFrontInMap(pTarget, 10)) // distance probably should be calculated + { + pet->SetInFront(pTarget); + MovementUpdate(); + } + } + + pet->CastSpell(pTarget, pSpellInfo, false); + + Spell* const pSpell = pet->FindCurrentSpellBySpellId(spellId); + if (!pSpell) + return false; + + return true; +} + +// Perform sanity checks and cast spell +bool PlayerbotAI::Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player *)) +{ + if (spellId == 0) + return false; + + SpellEntry const * spellProto = sSpellStore.LookupEntry(spellId); + + if (!spellProto) + return false; + + if (!target) + return false; + + // Select appropriate spell rank for target's level + spellProto = sSpellMgr.SelectAuraRankForLevel(spellProto, target->getLevel()); + if (!spellProto) + return false; + + // Check if spell will boost one of already existent auras + bool willBenefitFromSpell = false; + for (uint8 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + if (spellProto->EffectApplyAuraName[i] == SPELL_AURA_NONE) + break; + + bool sameOrBetterAuraFound = false; + int32 bonus = m_bot->CalculateSpellDamage(target, spellProto, SpellEffectIndex(i)); + Unit::AuraList const& auras = target->GetAurasByType(AuraType(spellProto->EffectApplyAuraName[i])); + for (Unit::AuraList::const_iterator it = auras.begin(); it != auras.end(); ++it) + if ((*it)->GetModifier()->m_miscvalue == spellProto->EffectMiscValue[i] && (*it)->GetModifier()->m_amount >= bonus) + { + sameOrBetterAuraFound = true; + break; + } + willBenefitFromSpell = willBenefitFromSpell || !sameOrBetterAuraFound; + } + + if (!willBenefitFromSpell) + return false; + + // Druids may need to shapeshift before casting + if (beforeCast) + (*beforeCast)(m_bot); + + return CastSpell(spellProto->Id, *target); +} + +// Can be used for personal buffs like Mage Armor and Inner Fire +bool PlayerbotAI::SelfBuff(uint32 spellId) +{ + if (spellId == 0) + return false; + + if (m_bot->HasAura(spellId)) + return false; + + return CastSpell(spellId, *m_bot); +} + +// Checks if spell is single per target per caster and will make any effect on target +bool PlayerbotAI::CanReceiveSpecificSpell(uint8 spec, Unit* target) const +{ + if (IsSingleFromSpellSpecificPerTargetPerCaster(SpellSpecific(spec), SpellSpecific(spec))) + { + Unit::SpellAuraHolderMap holders = target->GetSpellAuraHolderMap(); + Unit::SpellAuraHolderMap::iterator it; + for (it = holders.begin(); it != holders.end(); ++it) + if ((*it).second->GetCasterGUID() == m_bot->GetGUID() && GetSpellSpecific((*it).second->GetId()) == SpellSpecific(spec)) + return false; + } + return true; +} + +Item* PlayerbotAI::FindItem(uint32 ItemId) +{ + // list out items in main backpack + //INVENTORY_SLOT_ITEM_START = 23 + //INVENTORY_SLOT_ITEM_END = 39 + + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + // sLog.outDebug("[%s's]backpack slot = %u",m_bot->GetName(),slot); // 23 to 38 = 16 + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); // 255, 23 to 38 + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + continue; + + if (pItemProto->ItemId == ItemId) // have required item + return pItem; + } + } + // list out items in other removable backpacks + //INVENTORY_SLOT_BAG_START = 19 + //INVENTORY_SLOT_BAG_END = 23 + + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) // 20 to 23 = 4 + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); // 255, 20 to 23 + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + sLog.outDebug("[%s's]bag[%u] slot = %u", m_bot->GetName(), bag, slot); // 1 to bagsize = ? + Item* const pItem = m_bot->GetItemByPos(bag, slot); // 20 to 23, 1 to bagsize + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + continue; + + if (pItemProto->ItemId == ItemId) // have required item + return pItem; + } + } + } + return NULL; +} + +bool PlayerbotAI::PickPocket(Unit* pTarget) +{ + bool looted = false; + + ObjectGuid markGuid = pTarget->GetObjectGuid(); + Creature *c = m_bot->GetMap()->GetCreature(markGuid); + m_bot->SendLoot(markGuid, LOOT_PICKPOCKETING); + Loot *loot = &c->loot; + uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot); + + if (m_mgr->m_confDebugWhisper) + { + std::ostringstream out; + + // calculate how much money bot loots + uint32 copper = loot->gold; + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + 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]"; + + TellMaster(out.str().c_str()); + } + + if (loot->gold) + { + m_bot->ModifyMoney( loot->gold ); + m_bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold); + loot->gold = 0; + loot->NotifyMoneyRemoved(); + } + + for (uint32 l = 0; l < lootNum; l++) + { + QuestItem *qitem = 0, *ffaitem = 0, *conditem = 0; + LootItem *item = loot->LootItemInSlot(l, m_bot, &qitem, &ffaitem, &conditem); + if (!item) + continue; + + ItemPosCountVec dest; + if (m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + { + Item* pItem = m_bot->StoreNewItem (dest, item->itemid, true, item->randomPropertyId); + m_bot->SendNewItem(pItem, uint32(item->count), false, false, true); + --loot->unlootedCount; + looted = true; + } + } + // release loot + if (looted) + m_bot->GetSession()->DoLootRelease(markGuid); + + return false; // ensures that the rogue only pick pockets target once +} + +bool PlayerbotAI::HasPick() +{ + if (m_bot->HasItemTotemCategory(TC_MINING_PICK)) + return true; + + std::ostringstream out; + out << "|cffffffffI do not have a pick!"; + TellMaster(out.str().c_str()); + return false; +} + +bool PlayerbotAI::HasSpellReagents(uint32 spellId) +{ + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + return false; + + if (m_bot->CanNoReagentCast(pSpellInfo)) + return true; + + for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i) + { + if(pSpellInfo->Reagent[i] <= 0) + continue; + + uint32 itemid = pSpellInfo->Reagent[i]; + uint32 count = pSpellInfo->ReagentCount[i]; + + if (!m_bot->HasItemCount(itemid, count)) + return false; + } + + return true; +} + +// extracts all item ids in format below +// I decided to roll my own extractor rather then use the one in ChatHandler +// because this one works on a const string, and it handles multiple links +// |color|linkType:key:something1:...:somethingN|h[name]|h|r +void PlayerbotAI::extractItemIds(const std::string& text, std::list& itemIds) const +{ + uint8 pos = 0; + while (true) + { + int i = text.find("Hitem:", pos); + if (i == -1) + break; + pos = i + 6; + int endPos = text.find(':', pos); + if (endPos == -1) + break; + std::string idC = text.substr(pos, endPos - pos); + uint32 id = atol(idC.c_str()); + pos = endPos; + if (id) + itemIds.push_back(id); + } +} + +bool PlayerbotAI::extractSpellId(const std::string& text, uint32 &spellId) const +{ + + // Link format + // |cffffffff|Hspell:" << spellId << ":" << "|h[" << pSpellInfo->SpellName[loc] << "]|h|r"; + // cast |cff71d5ff|Hspell:686|h[Shadow Bolt]|h|r"; + // 012345678901234567890123456 + // base = 16 >| +7 >| + + uint8 pos = 0; + + int i = text.find("Hspell:", pos); + if (i == -1) + return false; + + // DEBUG_LOG("extractSpellId first pos %u i %u",pos,i); + pos = i + 7; // start of window in text 16 + 7 = 23 + int endPos = text.find('|', pos); + if (endPos == -1) + return false; + + // DEBUG_LOG("extractSpellId second endpos : %u pos : %u",endPos,pos); + std::string idC = text.substr(pos, endPos - pos); // 26 - 23 + spellId = atol(idC.c_str()); + pos = endPos; // end + return true; +} + +bool PlayerbotAI::extractGOinfo(const std::string& text, uint32 &guid, uint32 &entry, int &mapid, float &x, float &y, float &z) const +{ + + // Link format + // |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << x << ':' << y << ':' << z << ':' << mapid << ':' << "|h[" << gInfo->name << "]|h|r"; + // |cFFFFFF00|Hfound:5093:1731:-9295:-270:81.874:0:|h[Copper Vein]|h|r + + uint8 pos = 0; + + // extract GO guid + int i = text.find("Hfound:", pos); // base H = 11 + if (i == -1) // break if error + return false; + + 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 + return false; + std::string guidC = text.substr(pos, endPos - pos); // get string within window i.e guid 22 - 18 = 4 + 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 + return false; + + std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry + entry = atol(entryC.c_str()); // convert ascii to float + + // extract GO x + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + + std::string xC = text.substr(pos, endPos - pos); // get string within window i.e x + x = atof(xC.c_str()); // convert ascii to float + + // extract GO y + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + + std::string yC = text.substr(pos, endPos - pos); // get string within window i.e y + y = atof(yC.c_str()); // convert ascii to float + + // extract GO z + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + + std::string zC = text.substr(pos, endPos - pos); // get string within window i.e z + z = atof(zC.c_str()); // convert ascii to float + + //extract GO mapid + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + + std::string mapidC = text.substr(pos, endPos - pos); // get string within window i.e mapid + mapid = atoi(mapidC.c_str()); // convert ascii to int + pos = endPos; // end + return true; +} + +// extracts currency in #g#s#c format +uint32 PlayerbotAI::extractMoney(const std::string& text) const +{ + // if user specified money in ##g##s##c format + std::string acum = ""; + uint32 copper = 0; + for (uint8 i = 0; i < text.length(); i++) + { + if (text[i] == 'g') + { + copper += (atol(acum.c_str()) * 100 * 100); + acum = ""; + } + else if (text[i] == 'c') + { + copper += atol(acum.c_str()); + acum = ""; + } + else if (text[i] == 's') + { + copper += (atol(acum.c_str()) * 100); + acum = ""; + } + else if (text[i] == ' ') + break; + else if (text[i] >= 48 && text[i] <= 57) + acum += text[i]; + else + { + copper = 0; + break; + } + } + return copper; +} + +// finds items in equipment and adds Item* to foundItemList +// also removes found item IDs from itemIdSearchList when found +void PlayerbotAI::findItemsInEquip(std::list& itemIdSearchList, std::list& foundItemList) const +{ + for (uint8 slot = EQUIPMENT_SLOT_START; itemIdSearchList.size() > 0 && slot < EQUIPMENT_SLOT_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!pItem) + continue; + + for (std::list::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if (pItem->GetProto()->ItemId != *it) + continue; + + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } +} + +// finds items in inventory and adds Item* to foundItemList +// also removes found item IDs from itemIdSearchList when found +void PlayerbotAI::findItemsInInv(std::list& itemIdSearchList, std::list& foundItemList) const +{ + + // look for items in main bag + for (uint8 slot = INVENTORY_SLOT_ITEM_START; itemIdSearchList.size() > 0 && slot < INVENTORY_SLOT_ITEM_END; ++slot) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!pItem) + continue; + + for (std::list::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if (pItem->GetProto()->ItemId != *it) + continue; + + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } + + // for all for items in other bags + for (uint8 bag = INVENTORY_SLOT_BAG_START; itemIdSearchList.size() > 0 && bag < INVENTORY_SLOT_BAG_END; ++bag) + { + Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (!pBag) + continue; + + for (uint8 slot = 0; itemIdSearchList.size() > 0 && slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (!pItem) + continue; + + for (std::list::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if (pItem->GetProto()->ItemId != *it) + continue; + + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } + } +} + +// use item on self +void PlayerbotAI::UseItem(Item *item) +{ + UseItem(item, TARGET_FLAG_SELF, ObjectGuid()); +} + +// use item on equipped item +void PlayerbotAI::UseItem(Item *item, uint8 targetInventorySlot) +{ + if (targetInventorySlot >= EQUIPMENT_SLOT_END) + return; + + Item* const targetItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, targetInventorySlot); + if (!targetItem) + return; + + UseItem(item, TARGET_FLAG_ITEM, targetItem->GetObjectGuid()); +} + +// use item on unit +void PlayerbotAI::UseItem(Item *item, Unit *target) +{ + if (!target) + return; + + UseItem(item, TARGET_FLAG_UNIT, target->GetObjectGuid()); +} + +// generic item use method +void PlayerbotAI::UseItem(Item *item, uint32 targetFlag, ObjectGuid targetGUID) +{ + if (!item) + return; + + uint8 bagIndex = item->GetBagSlot(); + uint8 slot = item->GetSlot(); + uint8 cast_count = 1; + ObjectGuid item_guid = item->GetObjectGuid(); + uint32 glyphIndex = 0; + uint8 unk_flags = 0; + + uint32 spellId = 0; + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (item->GetProto()->Spells[i].SpellId > 0) + { + spellId = item->GetProto()->Spells[i].SpellId; + break; + } + } + + WorldPacket *packet = new WorldPacket(CMSG_USE_ITEM, 28); + *packet << bagIndex << slot << cast_count << spellId << item_guid + << glyphIndex << unk_flags << targetFlag; + + if (targetFlag & (TARGET_FLAG_UNIT | TARGET_FLAG_ITEM)) + *packet << targetGUID.WriteAsPacked(); + + m_bot->GetSession()->QueuePacket(packet); + + SpellEntry const * spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo) + { + TellMaster("Can't find spell entry for spell %u on item %u", spellId, item->GetEntry()); + return; + } + + SpellCastTimesEntry const * castingTimeEntry = sSpellCastTimesStore.LookupEntry(spellInfo->CastingTimeIndex); + if (!castingTimeEntry) + { + TellMaster("Can't find casting time entry for spell %u with index %u", spellId, spellInfo->CastingTimeIndex); + return; + } + + uint8 duration, castTime; + castTime = (uint8)((float)castingTimeEntry->CastTime / 1000.0f); + + if (item->GetProto()->Class == ITEM_CLASS_CONSUMABLE && item->GetProto()->SubClass == ITEM_SUBCLASS_FOOD) + { + duration = (uint8)((float)GetSpellDuration(spellInfo) / 1000.0f); + SetIgnoreUpdateTime(castTime + duration); + } + else + SetIgnoreUpdateTime(castTime); +} + +// submits packet to use an item +void PlayerbotAI::EquipItem(Item& item) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + + WorldPacket* const packet = new WorldPacket(CMSG_AUTOEQUIP_ITEM, 2); + *packet << bagIndex << slot; + m_bot->GetSession()->QueuePacket(packet); +} + +// submits packet to trade an item (trade window must already be open) +// default slot is -1 which means trade slots 0 to 5. if slot is set +// to TRADE_SLOT_NONTRADED (which is slot 6) item will be shown in the +// 'Will not be traded' slot. +bool PlayerbotAI::TradeItem(const Item& item, int8 slot) +{ + sLog.outDebug("[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; + + int8 tradeSlot = -1; + + TradeData* pTrade = m_bot->GetTradeData(); + if ((slot >= 0 && slot < TRADE_SLOT_COUNT) && pTrade->GetItem(TradeSlots(slot)) == NULL) + tradeSlot = slot; + else + for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT && tradeSlot == -1; i++) + { + if (pTrade->GetItem(TradeSlots(i)) == NULL) + { + tradeSlot = i; + // reserve trade slot to allow multiple items to be traded + pTrade->SetItem(TradeSlots(i), const_cast(&item)); + } + } + + if (tradeSlot == -1) return false; + + WorldPacket* const packet = new WorldPacket(CMSG_SET_TRADE_ITEM, 3); + *packet << (uint8) tradeSlot << (uint8) item.GetBagSlot() + << (uint8) item.GetSlot(); + m_bot->GetSession()->QueuePacket(packet); + return true; +} + +// submits packet to trade copper (trade window must be open) +bool PlayerbotAI::TradeCopper(uint32 copper) +{ + if (copper > 0) + { + WorldPacket* const packet = new WorldPacket(CMSG_SET_TRADE_GOLD, 4); + *packet << copper; + m_bot->GetSession()->QueuePacket(packet); + return true; + } + return false; +} + +/*void PlayerbotAI::Stay() + { + m_IsFollowingMaster = false; + m_bot->GetMotionMaster()->Clear(true); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); + }*/ + +/*bool PlayerbotAI::Follow(Player& player) + { + if (GetMaster()->IsBeingTeleported()) + return false; + + m_IsFollowingMaster = true; + + if (!m_bot->IsStandState()) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + if (!m_bot->isInCombat()) + { + // follow player or his corpse if dead (stops bot from running to graveyard if player repops...) + if( player.GetCorpse() ) + { + if( !FollowCheckTeleport( *player.GetCorpse() ) ) + return false; + } + else + { + if( !FollowCheckTeleport( player ) ) + return false; + } + } + + if (m_bot->isAlive()) + { + float angle = rand_float(0, M_PI); + float dist = rand_float(0.5f, 1.0f); + m_bot->GetMotionMaster()->Clear(true); + m_bot->GetMotionMaster()->MoveFollow(&player, dist, angle); + return true; + } + return false; + }*/ + +bool PlayerbotAI::FollowCheckTeleport(WorldObject &obj) +{ + // if bot has strayed too far from the master, teleport bot + + if (!m_bot->IsWithinDistInMap(&obj, 50, true) && GetMaster()->isAlive() && !GetMaster()->IsTaxiFlying()) + { + m_ignoreAIUpdatesUntilTime = time(0) + 6; + PlayerbotChatHandler ch(GetMaster()); + if (!ch.teleport(*m_bot)) + { + ch.sysmessage(".. could not be teleported .."); + //sLog.outDebug( "[PlayerbotAI]: %s failed to teleport", m_bot->GetName() ); + return false; + } + } + return true; +} + +void PlayerbotAI::HandleTeleportAck() +{ + m_ignoreAIUpdatesUntilTime = time(0) + 6; + m_bot->GetMotionMaster()->Clear(true); + if (m_bot->IsBeingTeleportedNear()) + { + WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 8 + 4 + 4); + p.appendPackGUID(m_bot->GetGUID()); + p << (uint32) 0; // supposed to be flags? not used currently + p << (uint32) time(0); // time - not currently used + m_bot->GetSession()->HandleMoveTeleportAckOpcode(p); + } + else if (m_bot->IsBeingTeleportedFar()) + m_bot->GetSession()->HandleMoveWorldportAckOpcode(); +} + +// Localization support +void PlayerbotAI::ItemLocalization(std::string& itemName, const uint32 itemID) const +{ + uint32 loc = GetMaster()->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + ItemLocale const *pItemInfo = sObjectMgr.GetItemLocale(itemID); + if (pItemInfo) + if (pItemInfo->Name.size() > loc && !pItemInfo->Name[loc].empty()) + { + const std::string name = pItemInfo->Name[loc]; + if (Utf8FitTo(name, wnamepart)) + itemName = name.c_str(); + } +} + +void PlayerbotAI::QuestLocalization(std::string& questTitle, const uint32 questID) const +{ + uint32 loc = GetMaster()->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + QuestLocale const *pQuestInfo = sObjectMgr.GetQuestLocale(questID); + if (pQuestInfo) + if (pQuestInfo->Title.size() > loc && !pQuestInfo->Title[loc].empty()) + { + const std::string title = pQuestInfo->Title[loc]; + if (Utf8FitTo(title, wnamepart)) + questTitle = title.c_str(); + } +} + +// handle commands sent through chat channels +void PlayerbotAI::HandleCommand(const std::string& text, Player& fromPlayer) +{ + // ignore any messages from Addons + if (text.empty() || + text.find("X-Perl") != std::wstring::npos || + text.find("HealBot") != std::wstring::npos || + text.find("LOOT_OPENED") != std::wstring::npos || + text.find("CTRA") != std::wstring::npos || + text.find("GathX") == 0) // Gatherer + return; + + // if message is not from a player in the masters account auto reply and ignore + if (!canObeyCommandFrom(fromPlayer)) + { + std::string msg = "I can't talk to you. Please speak to my master "; + msg += GetMaster()->GetName(); + SendWhisper(msg, fromPlayer); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_NO); + } + + // if in the middle of a trade, and player asks for an item/money + else if (m_bot->GetTrader() && m_bot->GetTrader()->GetGUID() == fromPlayer.GetGUID()) + { + uint32 copper = extractMoney(text); + if (copper > 0) + TradeCopper(copper); + + std::list itemIds; + extractItemIds(text, itemIds); + if (itemIds.size() == 0) + SendWhisper("Show me what item you want by shift clicking the item in the chat window.", fromPlayer); + else if (!strncmp(text.c_str(), "nt ", 3)) + { + if (itemIds.size() > 1) + SendWhisper("There is only one 'Will not be traded' slot. Shift-click just one item, please!", fromPlayer); + else + { + std::list itemList; + findItemsInEquip(itemIds, itemList); + findItemsInInv(itemIds, itemList); + if (itemList.size() > 0) + TradeItem((**itemList.begin()), TRADE_SLOT_NONTRADED); + else + SendWhisper("I do not have this item equipped or in my bags!", fromPlayer); + } + } + else + { + std::list itemList; + findItemsInInv(itemIds, itemList); + for (std::list::iterator it = itemList.begin(); it != itemList.end(); ++it) + TradeItem(**it); + } + } + + // if we are turning in a quest + + else if (text == "reset") + { + SetState(BOTSTATE_NORMAL); + MovementReset(); + SetQuestNeedItems(); + UpdateAttackerInfo(); + m_lootCreature.clear(); + m_lootCurrent = 0; + m_targetCombat = 0; + // do we want to reset all states on this command? +// m_combatOrder = ORDERS_NONE; +// m_targetCombat = 0; +// m_targetAssisst = 0; +// m_targetProtect = 0; + } + else if (text == "report") + SendQuestItemList(*GetMaster()); + else if (text == "orders") + SendOrders(*GetMaster()); + else if (text == "follow" || text == "come") + SetMovementOrder(MOVEMENT_FOLLOW, GetMaster()); + else if (text == "stay" || text == "stop") + SetMovementOrder(MOVEMENT_STAY); + else if (text == "attack") + { + ObjectGuid attackOnGuid = fromPlayer.GetSelectionGuid(); + if (!attackOnGuid.IsEmpty()) + { + Unit* thingToAttack = ObjectAccessor::GetUnit(*m_bot, attackOnGuid); + if (!m_bot->IsFriendlyTo(thingToAttack) && m_bot->IsWithinLOSInMap(thingToAttack)) + GetCombatTarget(thingToAttack); + } + else + { + TellMaster("No target is selected."); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + } + + // handle cast command + else if (text.size() > 2 && text.substr(0, 2) == "c " || text.size() > 5 && text.substr(0, 5) == "cast ") + { + // sLog.outErrorDb("Selected link : %s", text.c_str()); + + std::string spellStr = text.substr(text.find(" ") + 1); + uint32 spellId = (uint32) atol(spellStr.c_str()); + + // try and get spell ID by name + if (spellId == 0) + { + spellId = getSpellId(spellStr.c_str(), true); + + // try link if text NOT (spellid OR spellname) + if (spellId == 0) + extractSpellId(text, spellId); + } + + if (m_bot->HasAura(spellId)) + { + m_bot->RemoveAurasByCasterSpell(spellId, m_bot->GetGUID()); + return; + } + + ObjectGuid castOnGuid = fromPlayer.GetSelectionGuid(); + if (spellId != 0 && !castOnGuid.IsEmpty() && m_bot->HasSpell(spellId)) + { + m_spellIdCommand = spellId; + m_targetGuidCommand = castOnGuid.GetRawValue(); + } + + } + + // use items + else if (text.size() > 2 && text.substr(0, 2) == "u " || text.size() > 4 && text.substr(0, 4) == "use ") + { + std::list itemIds; + std::list itemList; + extractItemIds(text, itemIds); + findItemsInInv(itemIds, itemList); + for (std::list::iterator it = itemList.begin(); it != itemList.end(); ++it) + UseItem(*it); + } + + // equip items + else if (text.size() > 2 && text.substr(0, 2) == "e " || text.size() > 6 && text.substr(0, 6) == "equip ") + { + std::list itemIds; + std::list itemList; + extractItemIds(text, itemIds); + findItemsInInv(itemIds, itemList); + for (std::list::iterator it = itemList.begin(); it != itemList.end(); ++it) + EquipItem(**it); + } + + // find item in world + else if (text.size() > 2 && text.substr(0, 2) == "f " || text.size() > 5 && text.substr(0, 5) == "find ") + { + uint32 guid; + float x, y, z; + uint32 entry; + int mapid; + if (extractGOinfo(text, guid, entry, mapid, x, y, z)) + { // sLog.outDebug("find: guid : %u entry : %u x : (%f) y : (%f) z : (%f) mapid : %d",guid, entry, x, y, z, mapid); + m_bot->UpdateGroundPositionZ(x, y, z); + SetMovementOrder(MOVEMENT_STAY); + m_bot->GetMotionMaster()->MovePoint(mapid, x, y, z); + } + else + SendWhisper("I have no info on that object", fromPlayer); + } + + // get project: 18:50 03/05/10 rev.3 allows bots to retrieve all lootable & quest items from gameobjects + else if (text.size() > 2 && text.substr(0, 2) == "g " || text.size() > 4 && text.substr(0, 4) == "get ") + { + uint32 guid; + float x, y, z; + uint32 entry; + int mapid; + bool looted = false; + if (extractGOinfo(text, guid, entry, mapid, x, y, z)) + { + + //sLog.outDebug("find: guid : %u entry : %u x : (%f) y : (%f) z : (%f) mapid : %d",guid, entry, x, y, z, mapid); + ObjectGuid lootCurrent = ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid); + GameObject *go = m_bot->GetMap()->GetGameObject(lootCurrent); + if (!go) + { + lootCurrent = 0; + return; + } + + if (!go->isSpawned()) + return; + + m_bot->UpdateGroundPositionZ(x, y, z); + m_bot->GetMotionMaster()->MovePoint(mapid, x, y, z); + m_bot->SetPosition(x, y, z, m_bot->GetOrientation()); + m_bot->SendLoot(lootCurrent, LOOT_CORPSE); + Loot *loot = &go->loot; + uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot); + // sLog.outDebug( "[PlayerbotAI]: GetGOType %u - %s looting: '%s' got %d items", go->GetGoType(), m_bot->GetName(), go->GetGOInfo()->name, loot->GetMaxSlotInLootFor( m_bot )); + if (lootNum == 0) // Handle opening gameobjects that contain no items + { + uint32 lockId = go->GetGOInfo()->GetLockId(); + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if (lockInfo) + for (int i = 0; i < 8; ++i) + { + uint32 skillId = SkillByLockType(LockType(lockInfo->Index[i])); + if (skillId > 0) + { + if (m_bot->HasSkill(skillId)) // Has skill + { + uint32 reqSkillValue = lockInfo->Skill[i]; + uint32 SkillValue = m_bot->GetPureSkillValue(skillId); + if (SkillValue >= reqSkillValue) + { + // sLog.outDebug("[PlayerbotAI]i: skillId : %u SkillValue : %u reqSkillValue : %u",skillId,SkillValue,reqSkillValue); + m_bot->UpdateGatherSkill(skillId, SkillValue, reqSkillValue); + looted = true; + } + } + break; + } + } + } + for (uint32 l = 0; l < lootNum; l++) + { + // sLog.outDebug("[PlayerbotAI]: lootNum : %u",lootNum); + QuestItem *qitem = 0, *ffaitem = 0, *conditem = 0; + LootItem *item = loot->LootItemInSlot(l, m_bot, &qitem, &ffaitem, &conditem); + if (!item) + continue; + + if (!qitem && item->is_blocked) + { + m_bot->SendLootRelease(lootCurrent); + continue; + } + + if (m_needItemList[item->itemid] > 0) + { + ItemPosCountVec dest; + if (m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + { + Item * newitem = m_bot->StoreNewItem(dest, item->itemid, true, item->randomPropertyId); + + if (qitem) + { + qitem->is_looted = true; + if (item->freeforall || loot->GetPlayerQuestItems().size() == 1) + m_bot->SendNotifyLootItemRemoved(l); + else + loot->NotifyQuestItemRemoved(qitem->index); + } + else + { + if (ffaitem) + { + ffaitem->is_looted = true; + m_bot->SendNotifyLootItemRemoved(l); + } + else + { + if (conditem) + conditem->is_looted = true; + loot->NotifyItemRemoved(l); + } + } + if (!item->freeforall) + item->is_looted = true; + --loot->unlootedCount; + m_bot->SendNewItem(newitem, uint32(item->count), false, false, true); + m_bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item->itemid, item->count); + looted = true; + } + continue; + } + + uint32 lockId = go->GetGOInfo()->GetLockId(); + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if (lockInfo) + { + uint32 skillId = 0; + uint32 reqSkillValue = 0; + for (int i = 0; i < 8; ++i) + { + skillId = SkillByLockType(LockType(lockInfo->Index[i])); + if (skillId > 0) + { + reqSkillValue = lockInfo->Skill[i]; + break; + } + } + + if (m_bot->HasSkill(skillId) || skillId == SKILL_NONE) // Has skill or skill not required + { + if ((skillId == SKILL_MINING) && !HasPick()) + continue; + + ItemPosCountVec dest; + if (m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + { + Item* pItem = m_bot->StoreNewItem (dest, item->itemid, true, item->randomPropertyId); + uint32 SkillValue = m_bot->GetPureSkillValue(skillId); + if (SkillValue >= reqSkillValue) + { + m_bot->SendNewItem(pItem, uint32(item->count), false, false, true); + m_bot->UpdateGatherSkill(skillId, SkillValue, reqSkillValue); + --loot->unlootedCount; + looted = true; + } + } + } + } + } + // release loot + if (looted) + m_bot->GetSession()->DoLootRelease(lootCurrent); + else + m_bot->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_LOOTING); + // sLog.outDebug( "[PlayerbotAI]: %s looted target 0x%08X", m_bot->GetName(), lootCurrent ); + SetQuestNeedItems(); + } + else + SendWhisper("I have no info on that object", fromPlayer); + } + + else if (text == "quests") + { + bool hasIncompleteQuests = false; + std::ostringstream incomout; + incomout << "my incomplete quests are:"; + bool hasCompleteQuests = false; + std::ostringstream comout; + comout << "my complete quests are:"; + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + if (uint32 questId = m_bot->GetQuestSlotQuestId(slot)) + { + Quest const* pQuest = sObjectMgr.GetQuestTemplate(questId); + + std::string questTitle = pQuest->GetTitle(); + m_bot->GetPlayerbotAI()->QuestLocalization(questTitle, questId); + + if (m_bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE) { + hasCompleteQuests = true; + comout << " |cFFFFFF00|Hquest:" << questId << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + else + { + hasIncompleteQuests = true; + incomout << " |cFFFFFF00|Hquest:" << questId << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + } + } + if (hasCompleteQuests) + SendWhisper(comout.str(), fromPlayer); + if (hasIncompleteQuests) + SendWhisper(incomout.str(), fromPlayer); + if (!hasCompleteQuests && !hasIncompleteQuests) + SendWhisper("I have no quests!", fromPlayer); + } + + // drop a quest + else if (text.size() > 5 && text.substr(0, 5) == "drop ") + { + ObjectGuid oldSelectionGUID = ObjectGuid(); + if (fromPlayer.GetSelectionGuid() != m_bot->GetObjectGuid()) + { + oldSelectionGUID = m_bot->GetObjectGuid(); + fromPlayer.SetSelectionGuid(oldSelectionGUID); + } + PlayerbotChatHandler ch(GetMaster()); + int8 linkStart = text.find("|"); + if (linkStart < 0 || !ch.dropQuest((char*) text.substr(linkStart).c_str())) + ch.sysmessage("ERROR: could not drop quest"); + if (!oldSelectionGUID.IsEmpty()) + fromPlayer.SetSelectionGuid(oldSelectionGUID); + } + + // Handle all pet related commands here + else if (text.size() > 4 && text.substr(0, 4) == "pet ") + { + Pet * pet = m_bot->GetPet(); + if (!pet) + { + SendWhisper("I have no pet.", fromPlayer); + return; + } + + std::string part = text.substr(4); // Truncate `pet` part + std::string subcommand = part.substr(0, part.find(" ")); + std::string argument; + bool argumentFound = false; + + if (part.find(" ") != std::string::npos) + { + argument = part.substr(part.find(" ") + 1); + if (argument.length() > 0) + argumentFound = true; + } + + if (subcommand == "react" && argumentFound) + { + if (argument == "a" || argument == "aggressive") + pet->GetCharmInfo()->SetReactState(REACT_AGGRESSIVE); + else if (argument == "d" || argument == "defensive") + pet->GetCharmInfo()->SetReactState(REACT_DEFENSIVE); + else if (argument == "p" || argument == "passive") + pet->GetCharmInfo()->SetReactState(REACT_PASSIVE); + } + else if (subcommand == "state" && !argumentFound) + { + 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); + } + } + else if (subcommand == "cast" && argumentFound) + { + uint32 spellId = (uint32) atol(argument.c_str()); + + if (spellId == 0) + { + spellId = getPetSpellId(argument.c_str()); + if (spellId == 0) + extractSpellId(argument, spellId); + } + + if (spellId != 0 && pet->HasSpell(spellId)) + { + if (pet->HasAura(spellId)) + { + pet->RemoveAurasByCasterSpell(spellId, pet->GetGUID()); + return; + } + + ObjectGuid castOnGuid = fromPlayer.GetSelectionGuid(); + Unit* pTarget = ObjectAccessor::GetUnit(*m_bot, castOnGuid); + CastPetSpell(spellId, pTarget); + } + } + else if (subcommand == "toggle" && argumentFound) + { + uint32 spellId = (uint32) atol(argument.c_str()); + + if (spellId == 0) + { + spellId = getPetSpellId(argument.c_str()); + if (spellId == 0) + extractSpellId(argument, spellId); + } + + if (spellId != 0 && pet->HasSpell(spellId)) + { + PetSpellMap::iterator itr = pet->m_spells.find(spellId); + if (itr != pet->m_spells.end()) + { + if (itr->second.active == ACT_ENABLED) + { + pet->ToggleAutocast(spellId, false); + if (pet->HasAura(spellId)) + pet->RemoveAurasByCasterSpell(spellId, pet->GetGUID()); + } + else + pet->ToggleAutocast(spellId, true); + } + } + } + else if (subcommand == "spells" && !argumentFound) + { + int loc = GetMaster()->GetSession()->GetSessionDbcLocale(); + + std::ostringstream posOut; + std::ostringstream negOut; + + for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + const uint32 spellId = itr->first; + + if (itr->second.state == PETSPELL_REMOVED || IsPassiveSpell(spellId)) + continue; + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + std::string color; + switch (itr->second.active) + { + 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"; + else + negOut << " |" << color << "|Hspell:" << spellId << "|h[" + << pSpellInfo->SpellName[loc] << "]|h|r"; + } + + ChatHandler ch(&fromPlayer); + SendWhisper("Here's my pet's non-attack spells:", fromPlayer); + ch.SendSysMessage(posOut.str().c_str()); + SendWhisper("and here's my pet's attack spells:", fromPlayer); + ch.SendSysMessage(negOut.str().c_str()); + } + } + + else if (text == "spells") + { + + int loc = GetMaster()->GetSession()->GetSessionDbcLocale(); + + std::ostringstream posOut; + std::ostringstream negOut; + + typedef std::map spellMap; + + spellMap posSpells, negSpells; + 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}; + uint32 ignoredSpellsCount = sizeof(ignoredSpells) / sizeof(uint32); + + for (PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + const uint32 spellId = itr->first; + + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + continue; + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + spellName = pSpellInfo->SpellName[loc]; + + SkillLineAbilityMapBounds const bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spellId); + + bool isProfessionOrRidingSpell = false; + for (SkillLineAbilityMap::const_iterator skillIter = bounds.first; skillIter != bounds.second; ++skillIter) + { + if (IsProfessionOrRidingSkill(skillIter->second->skillId) && skillIter->first == spellId) { + isProfessionOrRidingSpell = true; + break; + } + } + if (isProfessionOrRidingSpell) + continue; + + bool isIgnoredSpell = false; + for (uint8 i = 0; i < ignoredSpellsCount; ++i) + { + if (spellId == ignoredSpells[i]) { + isIgnoredSpell = true; + break; + } + } + if (isIgnoredSpell) + continue; + + if (IsPositiveSpell(spellId)) { + if (posSpells.find(spellName) == posSpells.end()) + posSpells[spellName] = spellId; + else + if (posSpells[spellName] < spellId) + posSpells[spellName] = spellId; + } + else + { + if (negSpells.find(spellName) == negSpells.end()) + negSpells[spellName] = spellId; + else + if (negSpells[spellName] < spellId) + negSpells[spellName] = spellId; + } + } + + for (spellMap::const_iterator iter = posSpells.begin(); iter != posSpells.end(); ++iter) + { + posOut << " |cffffffff|Hspell:" << iter->second << "|h[" + << iter->first << "]|h|r"; + } + + for (spellMap::const_iterator iter = negSpells.begin(); iter != negSpells.end(); ++iter) + { + negOut << " |cffffffff|Hspell:" << iter->second << "|h[" + << iter->first << "]|h|r"; + } + + ChatHandler ch(&fromPlayer); + SendWhisper("here's my non-attack spells:", fromPlayer); + ch.SendSysMessage(posOut.str().c_str()); + SendWhisper("and here's my attack spells:", fromPlayer); + ch.SendSysMessage(negOut.str().c_str()); + } + + // survey project: 18:30 29/04/10 rev.3 filter out event triggered objects & now updates list + else if (text == "survey") + { + uint32 count = 0; + std::ostringstream detectout; + QueryResult *result; + GameEventMgr::ActiveEvents const& activeEventsList = sGameEventMgr.GetActiveEventList(); + std::ostringstream eventFilter; + eventFilter << " AND (event IS NULL "; + bool initString = true; + + for (GameEventMgr::ActiveEvents::const_iterator itr = activeEventsList.begin(); itr != activeEventsList.end(); ++itr) + { + if (initString) + { + eventFilter << "OR event IN (" << *itr; + initString = false; + } + else + eventFilter << "," << *itr; + } + + if (!initString) + eventFilter << "))"; + else + eventFilter << ")"; + + result = WorldDatabase.PQuery("SELECT gameobject.guid, id, position_x, position_y, position_z, map, " + "(POW(position_x - %f, 2) + POW(position_y - %f, 2) + POW(position_z - %f, 2)) AS order_ FROM gameobject " + "LEFT OUTER JOIN game_event_gameobject on gameobject.guid=game_event_gameobject.guid WHERE map = '%i' %s ORDER BY order_ ASC LIMIT 10", + m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), m_bot->GetMapId(), eventFilter.str().c_str()); + + if (result) + { + do + { + Field *fields = result->Fetch(); + uint32 guid = fields[0].GetUInt32(); + uint32 entry = fields[1].GetUInt32(); + float x = fields[2].GetFloat(); + float y = fields[3].GetFloat(); + float z = fields[4].GetFloat(); + int mapid = fields[5].GetUInt16(); + + GameObject *go = m_bot->GetMap()->GetGameObject(ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid)); + if (!go) + continue; + + if (!go->isSpawned()) + continue; + + detectout << "|cFFFFFF00|Hfound:" << guid << ":" << entry << ":" << x << ":" << y << ":" << z << ":" << mapid << ":" << "|h[" << go->GetGOInfo()->name << "]|h|r"; + ++count; + } while (result->NextRow()); + + delete result; + } + SendWhisper(detectout.str().c_str(), fromPlayer); + } + + // stats project: 10:00 19/04/10 rev.1 display bot statistics + else if (text == "stats") + { + std::ostringstream out; + + uint32 totalused = 0; + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + const Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + totalused++; + } + uint32 totalfree = 16 - totalused; + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + ItemPrototype const* pBagProto = pBag->GetProto(); + if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER) + totalfree = totalfree + pBag->GetFreeSlots(); + } + + } + + // calculate how much money bot has + uint32 copper = m_bot->GetMoney(); + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + copper -= (silver * 100); + + out << "|cffffffff[|h|cff00ffff" << m_bot->GetName() << "|h|cffffffff]" << " has |r|cff00ff00" << gold + << "|r|cfffffc00g|r|cff00ff00" << silver + << "|r|cffcdcdcds|r|cff00ff00" << copper + << "|r|cffffd333c" << "|h|cffffffff bag slots |h|cff00ff00" << totalfree; + + // estimate how much item damage the bot has + copper = EstRepairAll(); + gold = uint32(copper / 10000); + copper -= (gold * 10000); + silver = uint32(copper / 100); + copper -= (silver * 100); + + out << "|h|cffffffff & item damage cost " << "|r|cff00ff00" << gold + << "|r|cfffffc00g|r|cff00ff00" << silver + << "|r|cffcdcdcds|r|cff00ff00" << copper + << "|r|cffffd333c"; + ChatHandler ch(&fromPlayer); + ch.SendSysMessage(out.str().c_str()); + } + else + { + // if this looks like an item link, reward item it completed quest and talking to NPC + std::list itemIds; + extractItemIds(text, itemIds); + if (!itemIds.empty()) { + uint32 itemId = itemIds.front(); + bool wasRewarded = false; + ObjectGuid questRewarderGUID = m_bot->GetSelectionGuid(); + Object* const pNpc = (WorldObject*) m_bot->GetObjectByTypeMask(questRewarderGUID, TYPEMASK_CREATURE_OR_GAMEOBJECT); + if (!pNpc) + return; + + QuestMenu& questMenu = m_bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; !wasRewarded && iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + + uint32 questID = qItem.m_qId; + Quest const* pQuest = sObjectMgr.GetQuestTemplate(questID); + QuestStatus status = m_bot->GetQuestStatus(questID); + + // if quest is complete, turn it in + if (status == QUEST_STATUS_COMPLETE && + !m_bot->GetQuestRewardStatus(questID) && + pQuest->GetRewChoiceItemsCount() > 1 && + m_bot->CanRewardQuest(pQuest, false)) + for (uint8 rewardIdx = 0; !wasRewarded && rewardIdx < pQuest->GetRewChoiceItemsCount(); ++rewardIdx) + { + ItemPrototype const * const pRewardItem = sObjectMgr.GetItemPrototype(pQuest->RewChoiceItemId[rewardIdx]); + if (itemId == pRewardItem->ItemId) + { + m_bot->RewardQuest(pQuest, rewardIdx, pNpc, false); + + std::string questTitle = pQuest->GetTitle(); + m_bot->GetPlayerbotAI()->QuestLocalization(questTitle, questID); + std::string itemName = pRewardItem->Name1; + m_bot->GetPlayerbotAI()->ItemLocalization(itemName, pRewardItem->ItemId); + + std::ostringstream out; + out << "|cffffffff|Hitem:" << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r rewarded"; + SendWhisper(out.str(), fromPlayer); + wasRewarded = true; + } + } + } + } + else + { + std::string msg = "What? follow, stay, (c)ast , spells, (e)quip , (u)se , drop , report, quests, stats"; + SendWhisper(msg, fromPlayer); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + } +} diff --git a/src/game/playerbot/PlayerbotAI.h b/src/game/playerbot/PlayerbotAI.h new file mode 100644 index 000000000..4efed3302 --- /dev/null +++ b/src/game/playerbot/PlayerbotAI.h @@ -0,0 +1,340 @@ +#ifndef _PLAYERBOTAI_H +#define _PLAYERBOTAI_H + +#include "Common.h" +#include "../QuestDef.h" +#include "../GameEventMgr.h" +#include "../ObjectGuid.h" + +class WorldPacket; +class WorldObject; +class Player; +class Unit; +class Object; +class Item; +class PlayerbotClassAI; +class PlayerbotMgr; + +#define BOTLOOT_DISTANCE 25.0f + +enum RacialTraits +{ + ARCANE_TORRENT_MANA_CLASSES = 28730, + ARCANE_TORRENT_DEATH_KNIGHT = 50613, + ARCANE_TORRENT_ROGUE = 25046, + BERSERKING_ALL = 26297, + BLOOD_FURY_MELEE_CLASSES = 20572, + 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, + GIFT_OF_THE_NAARU_PALADIN = 59542, + GIFT_OF_THE_NAARU_PRIEST = 59544, + GIFT_OF_THE_NAARU_SHAMAN = 59547, + GIFT_OF_THE_NAARU_WARRIOR = 28880, + SHADOWMELD_ALL = 58984, + STONEFORM_ALL = 20594, + WAR_STOMP_ALL = 20549, + WILL_OF_THE_FORSAKEN_ALL = 7744 +}; + +class MANGOS_DLL_SPEC PlayerbotAI +{ +public: + enum ScenarioType + { + SCENARIO_PVEEASY, + SCENARIO_PVEHARD, + SCENARIO_DUEL, + SCENARIO_PVPEASY, + SCENARIO_PVPHARD + }; + + enum CombatStyle + { + COMBAT_MELEE = 0x01, // class melee attacker + COMBAT_RANGED = 0x02 // class is ranged attacker + }; + + // masters orders that should be obeyed by the AI during the updteAI routine + // the master will auto set the target of the bot + enum CombatOrderType + { + ORDERS_NONE = 0x00, // no special orders given + ORDERS_TANK = 0x01, // bind attackers by gaining threat + ORDERS_ASSIST = 0x02, // assist someone (dps type) + ORDERS_HEAL = 0x04, // concentrate on healing (no attacks, only self defense) + ORDERS_PROTECT = 0x10, // combinable state: check if protectee is attacked + ORDERS_PRIMARY = 0x0F, + ORDERS_SECONDARY = 0xF0, + ORDERS_RESET = 0xFF + }; + + enum CombatTargetType + { + TARGET_NORMAL = 0x00, + TARGET_THREATEN = 0x01 + }; + + enum BotState + { + BOTSTATE_NORMAL, // normal AI routines are processed + BOTSTATE_COMBAT, // bot is in combat + BOTSTATE_DEAD, // we are dead and wait for becoming ghost + BOTSTATE_DEADRELEASED, // we released as ghost and wait to revive + BOTSTATE_LOOTING // looting mode, used just after combat + }; + + enum MovementOrderType + { + MOVEMENT_NONE = 0x00, + MOVEMENT_FOLLOW = 0x01, + MOVEMENT_STAY = 0x02 + }; + + typedef std::map BotNeedItem; + typedef std::list BotLootCreature; + + // attacker query used in PlayerbotAI::FindAttacker() + enum ATTACKERINFOTYPE + { + AIT_NONE = 0x00, + AIT_LOWESTTHREAT = 0x01, + AIT_HIGHESTTHREAT = 0x02, + 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 + 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; + +public: + PlayerbotAI(PlayerbotMgr * const mgr, Player * const bot); + virtual ~PlayerbotAI(); + + // This is called from Unit.cpp and is called every second (I think) + void UpdateAI(const uint32 p_time); + + // This is called from ChatHandler.cpp when there is an incoming message to the bot + // from a whisper or from the party channel + void HandleCommand(const std::string& text, Player& fromPlayer); + + // This is called by WorldSession.cpp + // It provides a view of packets normally sent to the client. + // Since there is no client at the other end, the packets are dropped of course. + // For a list of opcodes that can be caught see Opcodes.cpp (SMSG_* opcodes only) + void HandleBotOutgoingPacket(const WorldPacket& packet); + + // This is called by WorldSession.cpp + // when it detects that a bot is being teleported. It acknowledges to the server to complete the + // teleportation + void HandleTeleportAck(); + + // Returns what kind of situation we are in so the ai can react accordingly + ScenarioType GetScenarioType() { return m_ScenarioType; } + + PlayerbotClassAI* GetClassAI() { return m_classAI; } + PlayerbotMgr* const GetManager() { return m_mgr; } + + // finds spell ID for matching substring args + // in priority of full text match, spells not taking reagents, and highest rank + uint32 getSpellId(const char* args, bool master = false) const; + uint32 getPetSpellId(const char* args) const; + // Initialize spell using rank 1 spell id + uint32 initSpell(uint32 spellId); + uint32 initPetSpell(uint32 spellIconId); + + // extracts item ids from links + void extractItemIds(const std::string& text, std::list& itemIds) const; + + // extract spellid from links + bool extractSpellId(const std::string& text, uint32 &spellId) const; + + // extracts currency from a string as #g#s#c and returns the total in copper + uint32 extractMoney(const std::string& text) const; + + // extracts gameobject info from link + bool extractGOinfo(const std::string& text, uint32 &guid, uint32 &entry, int &mapid, float &x, float &y, float &z) const; + + // finds items in bots equipment and adds them to foundItemList, removes found items from itemIdSearchList + void findItemsInEquip(std::list& itemIdSearchList, std::list& foundItemList) const; + // finds items in bots inventory and adds them to foundItemList, removes found items from itemIdSearchList + void findItemsInInv(std::list& itemIdSearchList, std::list& foundItemList) const; + + // currently bots only obey commands from the master + bool canObeyCommandFrom(const Player& player) const; + + // get current casting spell (will return NULL if no spell!) + Spell* GetCurrentSpell() const; + + bool HasAura(uint32 spellId, const Unit& player) const; + bool HasAura(const char* spellName, const Unit& player) const; + bool HasAura(const char* spellName) const; + + bool CanReceiveSpecificSpell(uint8 spec, Unit* target) const; + + bool PickPocket(Unit* pTarget); + bool HasPick(); + bool HasSpellReagents(uint32 spellId); + + uint8 GetHealthPercent(const Unit& target) const; + uint8 GetHealthPercent() const; + uint8 GetBaseManaPercent(const Unit& target) const; + uint8 GetBaseManaPercent() const; + uint8 GetManaPercent(const Unit& target) const; + uint8 GetManaPercent() const; + uint8 GetRageAmount(const Unit& target) const; + uint8 GetRageAmount() const; + uint8 GetEnergyAmount(const Unit& target) const; + uint8 GetEnergyAmount() const; + uint8 GetRunicPower(const Unit& target) const; + uint8 GetRunicPower() const; + + Item* FindFood() const; + Item* FindDrink() const; + Item* FindBandage() const; + Item* FindPoison() const; + Item* FindMount(uint32 matchingRidingSkill) const; + Item* FindItem(uint32 ItemId); + Item* FindConsumable(uint32 displayId) const; + + // ******* Actions **************************************** + // Your handlers can call these actions to make the bot do things. + void TellMaster(const std::string& text) const; + void TellMaster(const char *fmt, ...) const; + void SendWhisper(const std::string& text, Player& player) const; + bool CastSpell(const char* args); + bool CastSpell(uint32 spellId); + bool CastSpell(uint32 spellId, Unit& target); + bool CastPetSpell(uint32 spellId, Unit* target = NULL); + bool Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player *) = NULL); + bool SelfBuff(uint32 spellId); + + void UseItem(Item *item, uint32 targetFlag, ObjectGuid targetGUID); + void UseItem(Item *item, uint8 targetInventorySlot); + void UseItem(Item *item, Unit *target); + void UseItem(Item *item); + + void EquipItem(Item& item); + //void Stay(); + //bool Follow(Player& player); + void SendNotEquipList(Player& player); + void Feast(); + void InterruptCurrentCastingSpell(); + void GetCombatTarget(Unit* forcedTarged = 0); + Unit *GetCurrentTarget() { return m_targetCombat; }; + void DoNextCombatManeuver(); + void DoCombatMovement(); + void SetIgnoreUpdateTime(uint8 t = 0) { m_ignoreAIUpdatesUntilTime = time(0) + t; }; + + Player *GetPlayerBot() const { return m_bot; } + Player *GetPlayer() const { return m_bot; } + Player *GetMaster() const; + + BotState GetState() { return m_botState; }; + void SetState(BotState state); + void SetQuestNeedItems(); + void SendQuestItemList(Player& player); + void SendOrders(Player& player); + bool FollowCheckTeleport(WorldObject &obj); + void DoLoot(); + + uint32 EstRepairAll(); + uint32 EstRepair(uint16 pos); + + void AcceptQuest(Quest const *qInfo, Player *pGiver); + void TurnInQuests(WorldObject *questgiver); + + bool IsInCombat(); + void UpdateAttackerInfo(); + Unit* FindAttacker(ATTACKERINFOTYPE ait = AIT_NONE, Unit *victim = 0); + uint32 GetAttackerCount() { return m_attackerInfo.size(); }; + void SetCombatOrderByStr(std::string str, Unit *target = 0); + void SetCombatOrder(CombatOrderType co, Unit *target = 0); + CombatOrderType GetCombatOrder() { return this->m_combatOrder; } + void SetMovementOrder(MovementOrderType mo, Unit *followTarget = 0); + MovementOrderType GetMovementOrder() { return this->m_movementOrder; } + void MovementReset(); + void MovementUpdate(); + 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; + + uint8 GetFreeBagSpace() const; + +private: + // ****** Closed Actions ******************************** + // These actions may only be called at special times. + // Trade methods are only applicable when the trade window is open + // and are only called from within HandleCommand. + bool TradeItem(const Item& item, int8 slot = -1); + bool TradeCopper(uint32 copper); + + // Helper routines not needed by class AIs. + void UpdateAttackersForTarget(Unit *victim); + + // it is safe to keep these back reference pointers because m_bot + // owns the "this" object and m_master owns m_bot. The owner always cleans up. + PlayerbotMgr* const m_mgr; + Player* const m_bot; + PlayerbotClassAI* m_classAI; + + // ignores AI updates until time specified + // no need to waste CPU cycles during casting etc + time_t m_ignoreAIUpdatesUntilTime; + + CombatStyle m_combatStyle; + CombatOrderType m_combatOrder; + MovementOrderType m_movementOrder; + + ScenarioType m_ScenarioType; + + // defines the state of behaviour of the bot + BotState m_botState; + + // list of items needed to fullfill quests + BotNeedItem m_needItemList; + + // list of creatures we recently attacked and want to loot + BotLootCreature m_lootCreature; // list of creatures + uint64 m_lootCurrent; // current remains of interest + + time_t m_TimeDoneEating; + time_t m_TimeDoneDrinking; + uint32 m_CurrentlyCastingSpellId; + //bool m_IsFollowingMaster; + + // if master commands bot to do something, store here until updateAI + // can do it + uint32 m_spellIdCommand; + uint64 m_targetGuidCommand; + + AttackerInfoList m_attackerInfo; + + 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_followTarget; // whom to follow in non combat situation? + + std::map m_spellRangeMap; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotClassAI.cpp b/src/game/playerbot/PlayerbotClassAI.cpp new file mode 100644 index 000000000..98a82307a --- /dev/null +++ b/src/game/playerbot/PlayerbotClassAI.cpp @@ -0,0 +1,16 @@ +#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() {} + +bool PlayerbotClassAI::DoFirstCombatManeuver(Unit *) +{ + // return false, if done with opening moves/spells + return false; +} +void PlayerbotClassAI::DoNextCombatManeuver(Unit *) {} + +void PlayerbotClassAI::DoNonCombatActions(){} + +bool PlayerbotClassAI::BuffPlayer(Player* target) { return false; } diff --git a/src/game/playerbot/PlayerbotClassAI.h b/src/game/playerbot/PlayerbotClassAI.h new file mode 100644 index 000000000..c6dfe06a7 --- /dev/null +++ b/src/game/playerbot/PlayerbotClassAI.h @@ -0,0 +1,45 @@ +#ifndef _PLAYERBOTCLASSAI_H +#define _PLAYERBOTCLASSAI_H + +#include "Common.h" +#include "../World.h" +#include "../SpellMgr.h" +#include "../Player.h" +#include "../ObjectMgr.h" +#include "WorldPacket.h" +#include "../Unit.h" +#include "../SharedDefines.h" +#include "PlayerbotAI.h" + +class Player; +class PlayerbotAI; + +class MANGOS_DLL_SPEC PlayerbotClassAI +{ +public: + PlayerbotClassAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotClassAI(); + + // all combat actions go here + virtual bool DoFirstCombatManeuver(Unit*); + virtual void DoNextCombatManeuver(Unit*); + + // 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); + + // Utilities + Player* GetMaster () { return m_master; } + Player* GetPlayerBot() { return m_bot; } + PlayerbotAI* GetAI (){ return m_ai; }; + + +private: + Player* m_master; + Player* m_bot; + PlayerbotAI* m_ai; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotDeathKnightAI.cpp b/src/game/playerbot/PlayerbotDeathKnightAI.cpp new file mode 100644 index 000000000..8cbb60f12 --- /dev/null +++ b/src/game/playerbot/PlayerbotDeathKnightAI.cpp @@ -0,0 +1,500 @@ +// a simple DK class by rrtn :) + +#include "PlayerbotDeathKnightAI.h" +#include "PlayerbotMgr.h" + +class PlayerbotAI; +PlayerbotDeathKnightAI::PlayerbotDeathKnightAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) +{ + + PLAGUE_STRIKE = ai->initSpell(PLAGUE_STRIKE_1); // Unholy + DEATH_GRIP = ai->initSpell(DEATH_GRIP_1); + DEATH_COIL = ai->initSpell(DEATH_COIL_DEATH_KNIGHT_1); + DEATH_STRIKE = ai->initSpell(DEATH_STRIKE_1); + UNHOLY_BLIGHT = 0; // Passive + SCOURGE_STRIKE = ai->initSpell(SCOURGE_STRIKE_1); + DEATH_AND_DECAY = ai->initSpell(DEATH_AND_DECAY_1); + CORPSE_EXPLOSION = ai->initSpell(CORPSE_EXPLOSION_1); + BONE_SHIELD = ai->initSpell(BONE_SHIELD_1); // buffs + ANTI_MAGIC_SHELL = ai->initSpell(ANTI_MAGIC_SHELL_1); + ANTI_MAGIC_ZONE = ai->initSpell(ANTI_MAGIC_ZONE_1); + GHOUL_FRENZY = ai->initSpell(GHOUL_FRENZY_1); + RAISE_DEAD = ai->initSpell(RAISE_DEAD_1); // pets + SUMMON_GARGOYLE = ai->initSpell(SUMMON_GARGOYLE_1); + ARMY_OF_THE_DEAD = ai->initSpell(ARMY_OF_THE_DEAD_1); + ICY_TOUCH = ai->initSpell(ICY_TOUCH_1); // Frost + OBLITERATE = ai->initSpell(OBLITERATE_1); + HOWLING_BLAST = ai->initSpell(HOWLING_BLAST_1); + FROST_STRIKE = ai->initSpell(FROST_STRIKE_1); + CHAINS_OF_ICE = ai->initSpell(CHAINS_OF_ICE_1); + RUNE_STRIKE = ai->initSpell(RUNE_STRIKE_1); + ICY_CLUTCH = 0; // No such spell + MIND_FREEZE = ai->initSpell(MIND_FREEZE_1); + HUNGERING_COLD = ai->initSpell(HUNGERING_COLD_1); + KILLING_MACHINE = 0; // Passive + DEATHCHILL = ai->initSpell(DEATHCHILL_1); + HORN_OF_WINTER = ai->initSpell(HORN_OF_WINTER_1); + ICEBOUND_FORTITUDE = ai->initSpell(ICEBOUND_FORTITUDE_1); + EMPOWER_WEAPON = ai->initSpell(EMPOWER_RUNE_WEAPON_1); + UNBREAKABLE_ARMOR = ai->initSpell(UNBREAKABLE_ARMOR_1); + BLOOD_STRIKE = ai->initSpell(BLOOD_STRIKE_1); // Blood + PESTILENCE = ai->initSpell(PESTILENCE_1); + STRANGULATE = ai->initSpell(STRANGULATE_1); + BLOOD_BOIL = ai->initSpell(BLOOD_BOIL_1); + HEART_STRIKE = ai->initSpell(HEART_STRIKE_1); + DANCING_WEAPON = ai->initSpell(DANCING_RUNE_WEAPON_1); + DARK_COMMAND = ai->initSpell(DARK_COMMAND_1); + MARK_OF_BLOOD = ai->initSpell(MARK_OF_BLOOD_1); // buffs + RUNE_TAP = ai->initSpell(RUNE_TAP_1); + VAMPIRIC_BLOOD = ai->initSpell(VAMPIRIC_BLOOD_1); + DEATH_PACT = ai->initSpell(DEATH_PACT_1); + HYSTERIA = ai->initSpell(HYSTERIA_1); + UNHOLY_PRESENCE = ai->initSpell(UNHOLY_PRESENCE_1); // presence (TODO: better spell == presence) + FROST_PRESENCE = ai->initSpell(FROST_PRESENCE_1); + BLOOD_PRESENCE = ai->initSpell(BLOOD_PRESENCE_1); + + RECENTLY_BANDAGED = 11196; // first aid check + + // racial + ARCANE_TORRENT = ai->initSpell(ARCANE_TORRENT_DEATH_KNIGHT); // blood elf + GIFT_OF_THE_NAARU = ai->initSpell(GIFT_OF_THE_NAARU_DEATH_KNIGHT); // 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 + 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 +} + +PlayerbotDeathKnightAI::~PlayerbotDeathKnightAI() {} + +void PlayerbotDeathKnightAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + ai->CastSpell(PLAGUE_STRIKE); + return; + } + + // ------- Non Duel combat ---------- + + //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob + + // DK Attacks: Unholy, Frost & Blood + + // damage spells + ai->SetInFront(pTarget); //<--- + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + Pet *pet = m_bot->GetPet(); + float dist = m_bot->GetDistance(pTarget); + std::ostringstream out; + + switch (SpellSequence) + { + case SPELL_DK_UNHOLY: + if (UNHOLY_PRESENCE > 0) + (!m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(BLOOD_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(FROST_PRESENCE, EFFECT_INDEX_0) && ai->CastSpell (UNHOLY_PRESENCE, *m_bot)); + + // check for BONE_SHIELD in combat + if (BONE_SHIELD > 0) + (!m_bot->HasAura(BONE_SHIELD, EFFECT_INDEX_0) && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_INDEX_0) && ai->CastSpell (BONE_SHIELD, *m_bot)); + + if (ARMY_OF_THE_DEAD > 0 && ai->GetAttackerCount() >= 5 && LastSpellUnholyDK < 1) + { + ai->CastSpell(ARMY_OF_THE_DEAD); + out << " summoning Army of the Dead!"; + if (ARMY_OF_THE_DEAD > 0 && m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_INDEX_0)) + ai->SetIgnoreUpdateTime(7); + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (PLAGUE_STRIKE > 0 && !pTarget->HasAura(PLAGUE_STRIKE, EFFECT_INDEX_0) && LastSpellUnholyDK < 2) + { + ai->CastSpell(PLAGUE_STRIKE, *pTarget); + out << " Plague Strike"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (DEATH_GRIP > 0 && !pTarget->HasAura(DEATH_GRIP, EFFECT_INDEX_0) && LastSpellUnholyDK < 3) + { + ai->CastSpell(DEATH_GRIP, *pTarget); + out << " Death Grip"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (DEATH_COIL > 0 && LastSpellUnholyDK < 4 && ai->GetRunicPower() >= 40) + { + ai->CastSpell(DEATH_COIL, *pTarget); + out << " Death Coil"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (DEATH_STRIKE > 0 && !pTarget->HasAura(DEATH_STRIKE, EFFECT_INDEX_0) && LastSpellUnholyDK < 5) + { + ai->CastSpell(DEATH_STRIKE, *pTarget); + out << " Death Strike"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (UNHOLY_BLIGHT > 0 && !pTarget->HasAura(UNHOLY_BLIGHT, EFFECT_INDEX_0) && LastSpellUnholyDK < 6) + { + ai->CastSpell(UNHOLY_BLIGHT); + out << " Unholy Blight"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (SCOURGE_STRIKE > 0 && LastSpellUnholyDK < 7) + { + ai->CastSpell(SCOURGE_STRIKE, *pTarget); + out << " Scourge Strike"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (DEATH_AND_DECAY > 0 && ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && !pTarget->HasAura(DEATH_AND_DECAY, EFFECT_INDEX_0) && LastSpellUnholyDK < 8) + { + ai->CastSpell(DEATH_AND_DECAY); + out << " Death and Decay"; + ai->SetIgnoreUpdateTime(1); + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (SUMMON_GARGOYLE > 0 && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_INDEX_0) && !pTarget->HasAura(SUMMON_GARGOYLE, EFFECT_INDEX_0) && LastSpellUnholyDK < 9 && ai->GetRunicPower() >= 60) + { + ai->CastSpell(SUMMON_GARGOYLE, *pTarget); + out << " summoning Gargoyle"; + ai->SetIgnoreUpdateTime(2); + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (CORPSE_EXPLOSION > 0 && dist <= ATTACK_DISTANCE && LastSpellUnholyDK < 10) + { + ai->CastSpell(CORPSE_EXPLOSION, *pTarget); + out << " Corpse Explosion"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (ANTI_MAGIC_SHELL > 0 && pTarget->IsNonMeleeSpellCasted(true) && !m_bot->HasAura(ANTI_MAGIC_SHELL, EFFECT_INDEX_0) && LastSpellUnholyDK < 11 && ai->GetRunicPower() >= 20) + { + ai->CastSpell(ANTI_MAGIC_SHELL, *m_bot); + out << " Anti-Magic Shell"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (ANTI_MAGIC_ZONE > 0 && pTarget->IsNonMeleeSpellCasted(true) && !m_bot->HasAura(ANTI_MAGIC_SHELL, EFFECT_INDEX_0) && LastSpellUnholyDK < 12) + { + ai->CastSpell(ANTI_MAGIC_ZONE, *m_bot); + out << " Anti-Magic Zone"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if ((!pet) + && (RAISE_DEAD > 0 && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_INDEX_0) && LastSpellUnholyDK < 13)) + { + ai->CastSpell(RAISE_DEAD); + out << " summoning Ghoul"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if ((pet) + && (GHOUL_FRENZY > 0 && pVictim == pet && !pet->HasAura(GHOUL_FRENZY, EFFECT_INDEX_0) && LastSpellUnholyDK < 14)) + { + ai->CastSpell(GHOUL_FRENZY, *pet); + out << " casting Ghoul Frenzy on pet"; + SpellSequence = SPELL_DK_FROST; + LastSpellUnholyDK = LastSpellUnholyDK + 1; + break; + } + else if (LastSpellUnholyDK > 15) + { + LastSpellUnholyDK = 0; + SpellSequence = SPELL_DK_FROST; + break; + } + + LastSpellUnholyDK = 0; + + case SPELL_DK_FROST: + if (FROST_PRESENCE > 0) + (!m_bot->HasAura(FROST_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(BLOOD_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_INDEX_0) && ai->CastSpell (FROST_PRESENCE, *m_bot)); + + if (DEATHCHILL > 0) + (!m_bot->HasAura(DEATHCHILL, EFFECT_INDEX_0) && !m_bot->HasAura(KILLING_MACHINE, EFFECT_INDEX_0) && ai->CastSpell (DEATHCHILL, *m_bot)); + else if (KILLING_MACHINE > 0) + (!m_bot->HasAura(KILLING_MACHINE, EFFECT_INDEX_0) && !m_bot->HasAura(DEATHCHILL, EFFECT_INDEX_0) && ai->CastSpell (KILLING_MACHINE, *m_bot)); + + if (ICY_TOUCH > 0 && !pTarget->HasAura(ICY_TOUCH, EFFECT_INDEX_0) && LastSpellFrostDK < 1) + { + ai->CastSpell(ICY_TOUCH, *pTarget); + out << " Icy Touch"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (OBLITERATE > 0 && LastSpellFrostDK < 2) + { + ai->CastSpell(OBLITERATE, *pTarget); + out << " Obliterate"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (FROST_STRIKE > 0 && LastSpellFrostDK < 3 && ai->GetRunicPower() >= 40) + { + ai->CastSpell(FROST_STRIKE, *pTarget); + out << " Frost Strike"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (HOWLING_BLAST > 0 && ai->GetAttackerCount() >= 3 && LastSpellFrostDK < 4) + { + ai->CastSpell(HOWLING_BLAST, *pTarget); + out << " Howling Blast"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (CHAINS_OF_ICE > 0 && !pTarget->HasAura(CHAINS_OF_ICE, EFFECT_INDEX_0) && LastSpellFrostDK < 5) + { + ai->CastSpell(CHAINS_OF_ICE, *pTarget); + out << " Chains of Ice"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (RUNE_STRIKE > 0 && LastSpellFrostDK < 6 && ai->GetRunicPower() >= 20) + { + ai->CastSpell(RUNE_STRIKE, *pTarget); + out << " Rune Strike"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (ICY_CLUTCH > 0 && !pTarget->HasAura(ICY_CLUTCH, EFFECT_INDEX_0) && LastSpellFrostDK < 7) + { + ai->CastSpell(ICY_CLUTCH, *pTarget); + out << " Icy Clutch"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (ICEBOUND_FORTITUDE > 0 && ai->GetHealthPercent() < 50 && pVictim == m_bot && !m_bot->HasAura(ICEBOUND_FORTITUDE, EFFECT_INDEX_0) && LastSpellFrostDK < 8 && ai->GetRunicPower() >= 20) + { + ai->CastSpell(ICEBOUND_FORTITUDE, *m_bot); + out << " Icebound Fortitude"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (MIND_FREEZE > 0 && pTarget->IsNonMeleeSpellCasted(true) && dist <= ATTACK_DISTANCE && LastSpellFrostDK < 9 && ai->GetRunicPower() >= 20) + { + ai->CastSpell(MIND_FREEZE, *pTarget); + out << " Mind Freeze"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (HUNGERING_COLD > 0 && ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && LastSpellFrostDK < 10 && ai->GetRunicPower() >= 40) + { + ai->CastSpell(HUNGERING_COLD, *pTarget); + out << " Hungering Cold"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (EMPOWER_WEAPON > 0 && ai->GetRunicPower() < 20 && LastSpellFrostDK < 11) + { + ai->CastSpell(EMPOWER_WEAPON, *m_bot); + out << " Empower Rune Weapon"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (UNBREAKABLE_ARMOR > 0 && !m_bot->HasAura(UNBREAKABLE_ARMOR, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70 && pVictim == m_bot && LastSpellFrostDK < 12) + { + ai->CastSpell(UNBREAKABLE_ARMOR, *m_bot); + out << " Unbreakable Armor"; + SpellSequence = SPELL_DK_BLOOD; + LastSpellFrostDK = LastSpellFrostDK + 1; + break; + } + else if (LastSpellFrostDK > 13) + { + LastSpellFrostDK = 0; + SpellSequence = SPELL_DK_BLOOD; + break; + } + + LastSpellFrostDK = 0; + + case SPELL_DK_BLOOD: + if (BLOOD_PRESENCE > 0) + (!m_bot->HasAura(BLOOD_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_INDEX_0) && !m_bot->HasAura(FROST_PRESENCE, EFFECT_INDEX_0) && ai->CastSpell (BLOOD_PRESENCE, *m_bot)); + + if (MARK_OF_BLOOD > 0 && !pTarget->HasAura(MARK_OF_BLOOD, EFFECT_INDEX_0) && LastSpellBloodDK < 1) + { + ai->CastSpell(MARK_OF_BLOOD, *pTarget); + out << " Mark of Blood"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (BLOOD_STRIKE > 0 && LastSpellBloodDK < 2) + { + ai->CastSpell(BLOOD_STRIKE, *pTarget); + out << " Blood Strike"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (PESTILENCE > 0 && dist <= ATTACK_DISTANCE && ai->GetAttackerCount() >= 3 && LastSpellBloodDK < 3) + { + ai->CastSpell(PESTILENCE, *pTarget); + out << " Pestilence"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (STRANGULATE > 0 && !pTarget->HasAura(STRANGULATE, EFFECT_INDEX_0) && LastSpellBloodDK < 4) + { + ai->CastSpell(STRANGULATE, *pTarget); + out << " Strangulate"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (BLOOD_BOIL > 0 && ai->GetAttackerCount() >= 5 && dist <= ATTACK_DISTANCE && LastSpellBloodDK < 5) + { + ai->CastSpell(BLOOD_BOIL, *pTarget); + out << " Blood Boil"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (HEART_STRIKE > 0 && LastSpellBloodDK < 6) + { + ai->CastSpell(HEART_STRIKE, *pTarget); + out << " Heart Strike"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (VAMPIRIC_BLOOD > 0 && ai->GetHealthPercent() < 70 && !m_bot->HasAura(VAMPIRIC_BLOOD, EFFECT_INDEX_0) && LastSpellBloodDK < 7) + { + ai->CastSpell(VAMPIRIC_BLOOD, *m_bot); + out << " Vampiric Blood"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (RUNE_TAP > 0 && ai->GetHealthPercent() < 70 && !m_bot->HasAura(VAMPIRIC_BLOOD, EFFECT_INDEX_0) && LastSpellBloodDK < 8) + { + ai->CastSpell(RUNE_TAP, *m_bot); + out << " Rune Tap"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (HYSTERIA > 0 && ai->GetHealthPercent() > 25 && !m_bot->HasAura(HYSTERIA, EFFECT_INDEX_0) && LastSpellBloodDK < 9) + { + ai->CastSpell(HYSTERIA, *m_bot); + out << " Hysteria"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (DANCING_WEAPON > 0 && !m_bot->HasAura(DANCING_WEAPON, EFFECT_INDEX_0) && ai->GetRunicPower() >= 60 && LastSpellBloodDK < 10) + { + ai->CastSpell(DANCING_WEAPON, *pTarget); + out << " summoning Dancing Rune Weapon"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (DARK_COMMAND > 0 && ai->GetHealthPercent() > 50 && pVictim != m_bot && !pTarget->HasAura(DARK_COMMAND, EFFECT_INDEX_0) && dist <= ATTACK_DISTANCE && LastSpellBloodDK < 11) + { + ai->CastSpell(DARK_COMMAND, *pTarget); + out << " Dark Command"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if ((pet) + && (DEATH_PACT > 0 && ai->GetHealthPercent() < 50 && LastSpellBloodDK < 12 && ai->GetRunicPower() >= 40)) + { + ai->CastSpell(DEATH_PACT, *pet); + out << " Death Pact (sacrifice pet)"; + SpellSequence = SPELL_DK_UNHOLY; + LastSpellBloodDK = LastSpellBloodDK + 1; + break; + } + else if (LastSpellBloodDK > 13) + { + LastSpellBloodDK = 0; + SpellSequence = SPELL_DK_UNHOLY; + break; + } + else + { + LastSpellBloodDK = 0; + SpellSequence = SPELL_DK_UNHOLY; + } + } + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster(out.str().c_str()); + +} // end DoNextCombatManeuver + +void PlayerbotDeathKnightAI::DoNonCombatActions() +{ + PlayerbotAI* ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot) + return; + + SpellSequence = SPELL_DK_UNHOLY; + + // buff master with HORN_OF_WINTER + if (HORN_OF_WINTER > 0) + (!GetMaster()->HasAura(HORN_OF_WINTER, EFFECT_INDEX_0) && ai->CastSpell (HORN_OF_WINTER, *GetMaster())); + + // 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 != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotDeathKnightAI.h b/src/game/playerbot/PlayerbotDeathKnightAI.h new file mode 100644 index 000000000..58ff975c7 --- /dev/null +++ b/src/game/playerbot/PlayerbotDeathKnightAI.h @@ -0,0 +1,99 @@ +#ifndef _PLAYERDEATHKNIGHTAI_H +#define _PLAYERDEATHKNIGHTAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_DK_UNHOLY, + SPELL_DK_FROST, + SPELL_DK_BLOOD +}; + +enum DeathKnightSpells +{ + ANTI_MAGIC_SHELL_1 = 48707, + ANTI_MAGIC_ZONE_1 = 51052, + ARMY_OF_THE_DEAD_1 = 42650, + BLOOD_BOIL_1 = 48721, + BLOOD_PRESENCE_1 = 48266, + BLOOD_STRIKE_1 = 45902, + BLOOD_TAP_1 = 45529, + BONE_SHIELD_1 = 49222, + CHAINS_OF_ICE_1 = 45524, + CORPSE_EXPLOSION_1 = 49158, + DANCING_RUNE_WEAPON_1 = 49028, + DARK_COMMAND_1 = 56222, + DEATH_AND_DECAY_1 = 43265, + DEATH_COIL_DEATH_KNIGHT_1 = 47541, + DEATH_GRIP_1 = 49576, + DEATH_PACT_1 = 48743, + DEATH_STRIKE_1 = 49998, + DEATHCHILL_1 = 49796, + EMPOWER_RUNE_WEAPON_1 = 47568, + FROST_PRESENCE_1 = 48263, + FROST_STRIKE_1 = 49143, + GHOUL_FRENZY_1 = 63560, + HEART_STRIKE_1 = 55050, + HORN_OF_WINTER_1 = 57330, + HOWLING_BLAST_1 = 49184, + HUNGERING_COLD_1 = 49203, + HYSTERIA_1 = 49016, + ICEBOUND_FORTITUDE_1 = 48792, + ICY_TOUCH_1 = 45477, + LICHBORNE_1 = 49039, + MARK_OF_BLOOD_1 = 49005, + MIND_FREEZE_1 = 47528, + OBLITERATE_1 = 49020, + PATH_OF_FROST_1 = 3714, + PESTILENCE_1 = 50842, + PLAGUE_STRIKE_1 = 45462, + RAISE_ALLY_1 = 61999, + RAISE_DEAD_1 = 46584, + RUNE_STRIKE_1 = 56815, + RUNE_TAP_1 = 48982, + SCOURGE_STRIKE_1 = 55090, + STRANGULATE_1 = 47476, + SUMMON_GARGOYLE_1 = 49206, + UNBREAKABLE_ARMOR_1 = 51271, + UNHOLY_PRESENCE_1 = 48265, + VAMPIRIC_BLOOD_1 = 55233 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotDeathKnightAI : PlayerbotClassAI +{ +public: + PlayerbotDeathKnightAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotDeathKnightAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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 + //void BuffPlayer(Player *target); + +private: + + // Unholy + uint32 BONE_SHIELD, PLAGUE_STRIKE, DEATH_GRIP, DEATH_COIL, DEATH_STRIKE, UNHOLY_BLIGHT, SCOURGE_STRIKE, DEATH_AND_DECAY, UNHOLY_PRESENCE, RAISE_DEAD, ARMY_OF_THE_DEAD, SUMMON_GARGOYLE, ANTI_MAGIC_SHELL, ANTI_MAGIC_ZONE, GHOUL_FRENZY, CORPSE_EXPLOSION; + + // Frost + uint32 ICY_TOUCH, OBLITERATE, HOWLING_BLAST, FROST_STRIKE, CHAINS_OF_ICE, RUNE_STRIKE, ICY_CLUTCH, HORN_OF_WINTER, KILLING_MACHINE, FROST_PRESENCE, DEATHCHILL, ICEBOUND_FORTITUDE, MIND_FREEZE, EMPOWER_WEAPON, HUNGERING_COLD, UNBREAKABLE_ARMOR; + + // Blood + uint32 BLOOD_STRIKE, PESTILENCE, STRANGULATE, BLOOD_BOIL, HEART_STRIKE, MARK_OF_BLOOD, BLOOD_PRESENCE, RUNE_TAP, VAMPIRIC_BLOOD, DEATH_PACT, DEATH_RUNE_MASTERY, HYSTERIA, DANCING_WEAPON, DARK_COMMAND; + + // 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 SpellSequence, LastSpellUnholyDK, LastSpellFrostDK, LastSpellBloodDK; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotDruidAI.cpp b/src/game/playerbot/PlayerbotDruidAI.cpp new file mode 100644 index 000000000..18d6d5827 --- /dev/null +++ b/src/game/playerbot/PlayerbotDruidAI.cpp @@ -0,0 +1,675 @@ +/* + Name : PlayerbotDruidAI.cpp + Complete: maybe around 33% + Authors : rrtn, Natsukawa + Version : 0.42 + */ +#include "PlayerbotDruidAI.h" + +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); + // 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); + // 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); + // Cat Finishing Move's + RIP = ai->initSpell(RIP_1); // 30 + FEROCIOUS_BITE = ai->initSpell(FEROCIOUS_BITE_1); // 35 + MAIM = 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); + + RECENTLY_BANDAGED = 11196; // first aid check + + // racial + SHADOWMELD = ai->initSpell(SHADOWMELD_ALL); + WAR_STOMP = ai->initSpell(WAR_STOMP_ALL); // tauren +} + +PlayerbotDruidAI::~PlayerbotDruidAI() {} + +bool PlayerbotDruidAI::HealTarget(Unit *target) +{ + PlayerbotAI* ai = GetAI(); + uint8 hp = target->GetHealth() * 100 / target->GetMaxHealth(); + + if (hp >= 70) + return false; + + // Reset form if needed + GoBuffForm(GetPlayerBot()); + + if (hp < 70 && REJUVENATION > 0 && !target->HasAura(REJUVENATION) && ai->CastSpell(REJUVENATION, *target)) + return true; + + if (hp < 60 && LIFEBLOOM > 0 && !target->HasAura(LIFEBLOOM) && ai->CastSpell(LIFEBLOOM,*target)) + return true; + + if (hp < 55 && REGROWTH > 0 && !target->HasAura(REGROWTH) && ai->CastSpell(REGROWTH, *target)) + return true; + + if (hp < 50 && SWIFTMEND > 0 && (target->HasAura(REJUVENATION) || target->HasAura(REGROWTH)) && ai->CastSpell(SWIFTMEND, *target)) + return true; + + if (hp < 45 && WILD_GROWTH > 0 && !target->HasAura(WILD_GROWTH) && ai->CastSpell(WILD_GROWTH, *target)) + return true; + + if (hp < 30 && NOURISH > 0 && ai->CastSpell(NOURISH, *target)) + return true; + + if (hp < 25 && HEALING_TOUCH > 0 && ai->CastSpell(HEALING_TOUCH, *target)) + return true; + + return false; +} // end HealTarget + +void PlayerbotDruidAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + ai->CastSpell(MOONFIRE); + return; + } + + uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); + + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + + if (pVictim && ai->GetHealthPercent() >= 40 && GetMaster()->GetHealth() >= GetMaster()->GetMaxHealth() * 0.4) + { + if (pVictim == m_bot) + SpellSequence = DruidTank; + } + else if (pTarget->GetHealth() > pTarget->GetMaxHealth() * 0.8 && pVictim) + { + if (pVictim != m_bot) + SpellSequence = DruidSpell; + } + else if (ai->GetHealthPercent() <= 40 || GetMaster()->GetHealth() <= GetMaster()->GetMaxHealth() * 0.4) + SpellSequence = DruidHeal; + else + SpellSequence = DruidCombat; + + switch (SpellSequence) + { + case DruidTank: // Its now a tank druid! + //ai->TellMaster("DruidTank"); + + if (!m_bot->HasInArc(M_PI_F, pTarget)) + { + m_bot->SetInFront(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 && DruidSpellCombat < 1 && !pTarget->HasAura(FAERIE_FIRE, EFFECT_INDEX_0)) + { + ai->CastSpell(FAERIE_FIRE, *pTarget); + DruidSpellCombat++; + break; + } + else if (MOONFIRE > 0 && 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 && 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 && m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0) && DruidSpellCombat < 5 && ai->GetManaPercent() >= 13) + { + ai->CastSpell(WRATH, *pTarget); + DruidSpellCombat++; + break; + } + else if (INSECT_SWARM > 0 && 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 && 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 && 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 && 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 && 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 && (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 && (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 + { + DruidSpellCombat = 0; + break; + } + break; + + 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; + } + if (m_bot->HasAura(MOONKIN_FORM, EFFECT_INDEX_0)) + { + m_bot->RemoveAurasDueToSpell(24858); + //ai->TellMaster("FormClearMoonkin"); + break; + } + if (FAERIE_FIRE > 0 && DruidSpellCombat < 1 && !pTarget->HasAura(FAERIE_FIRE, EFFECT_INDEX_0) && ai->GetManaPercent() >= 8) + { + ai->CastSpell(FAERIE_FIRE, *pTarget); + DruidSpellCombat++; + break; + } + else if (MOONFIRE > 0 && 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->GetAttackerCount() >= 5 && DruidSpellCombat < 4 && ai->GetManaPercent() >= 91) + { + //ai->TellMaster("casting hurricane!"); + ai->CastSpell(HURRICANE, *pTarget); + ai->SetIgnoreUpdateTime(10); + DruidSpellCombat++; + break; + } + else if (WRATH > 0 && DruidSpellCombat < 5 && ai->GetManaPercent() >= 13) + { + ai->CastSpell(WRATH, *pTarget); + DruidSpellCombat++; + break; + } + else if (INSECT_SWARM > 0 && DruidSpellCombat < 6 && !pTarget->HasAura(INSECT_SWARM, EFFECT_INDEX_0) && ai->GetManaPercent() >= 9) + { + ai->CastSpell(INSECT_SWARM, *pTarget); + DruidSpellCombat++; + break; + } + else if (STARFIRE > 0 && DruidSpellCombat < 7 && ai->GetManaPercent() >= 18) + { + ai->CastSpell(STARFIRE, *pTarget); + DruidSpellCombat++; + break; + } + else if (FORCE_OF_NATURE > 0 && DruidSpellCombat < 8 && ai->GetManaPercent() >= 12) + { + //ai->TellMaster("summoning treants."); + ai->CastSpell(FORCE_OF_NATURE); + DruidSpellCombat++; + break; + } + else if (STARFALL > 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 && ai->GetHealthPercent() < 75 && DruidSpellCombat < 10 && !m_bot->HasAura(BARKSKIN, EFFECT_INDEX_0)) + { + ai->CastSpell(BARKSKIN, *m_bot); + DruidSpellCombat++; + break; + } + else if (INNERVATE > 0 && 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; + + 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; + + case DruidCombat: + //ai->TellMaster("DruidCombat"); + if (!m_bot->HasInArc(M_PI_F, pTarget)) + { + m_bot->SetInFront(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 (COWER > 0 && m_bot->GetComboPoints() == 1 && ai->GetEnergyAmount() >= 20) + { + ai->CastSpell(COWER); + //ai->TellMaster("Cower"); + }*/ + if (MAIM > 0 && m_bot->GetComboPoints() >= 1 && pTarget->IsNonMeleeSpellCasted(true)) + { + ai->CastSpell(MAIM, *pTarget); + //ai->TellMaster("SpellPreventing Maim"); + break; + } + + 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; + } + + 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 (FEROCIOUS_BITE > 0 && pTarget->getClass() == CLASS_DEATH_KNIGHT && ai->GetEnergyAmount() >= 25) + ai->CastSpell(FEROCIOUS_BITE, *pTarget); + //ai->TellMaster("DK 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 + { + DruidSpellCombat = 0; + break; + } + break; + } +} // end DoNextCombatManeuver + +void PlayerbotDruidAI::DoNonCombatActions() +{ + Player * m_bot = GetPlayerBot(); + Player * master = GetMaster(); + if (!m_bot || !master) + return; + + PlayerbotAI* ai = GetAI(); + + // 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 != NULL && ai->GetManaPercent() < 30) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + 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); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + + // buff and heal master's group + if (master->GetGroup()) + { + // 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; + + 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; + + // Resurrect member if needed + if (!tPlayer->isAlive()) + { + if (ai->CastSpell(REVIVE, *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 (HealTarget(tPlayer)) + return; + } + } + } + else + { + if (master->isAlive()) + { + if (BuffPlayer(master)) + return; + if (HealTarget(master)) + return; + } + else + if (ai->CastSpell(REVIVE, *master)) + ai->TellMaster("Resurrecting you, Master."); + } + + BuffPlayer(m_bot); +} // end DoNonCombatActions + +bool PlayerbotDruidAI::BuffPlayer(Player* target) +{ + PlayerbotAI * ai = GetAI(); + + 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))) + return true; + else if (ai->Buff(THORNS, target, &(PlayerbotDruidAI::GoBuffForm))) + return true; + else + return false; +} + +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. + if (self->HasAura(CAT_FORM_1)) + self->RemoveAurasDueToSpell(CAT_FORM_1); + if (self->HasAura(BEAR_FORM_1)) + self->RemoveAurasDueToSpell(BEAR_FORM_1); + if (self->HasAura(DIRE_BEAR_FORM_1)) + self->RemoveAurasDueToSpell(DIRE_BEAR_FORM_1); + if (self->HasAura(MOONKIN_FORM_1)) + self->RemoveAurasDueToSpell(MOONKIN_FORM_1); + if (self->HasAura(TRAVEL_FORM_1)) + self->RemoveAurasDueToSpell(TRAVEL_FORM_1); +} diff --git a/src/game/playerbot/PlayerbotDruidAI.h b/src/game/playerbot/PlayerbotDruidAI.h new file mode 100644 index 000000000..9399289fe --- /dev/null +++ b/src/game/playerbot/PlayerbotDruidAI.h @@ -0,0 +1,189 @@ +#ifndef _PLAYERBOTDRUIDAI_H +#define _PLAYERBOTDRUIDAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + DruidCombat, + DruidTank, + DruidHeal, + DruidSpell +}; + +enum DruidSpells +{ + ABOLISH_POISON_1 = 2893, + AQUATIC_FORM_1 = 1066, + BARKSKIN_1 = 22812, + BASH_1 = 5211, + BEAR_FORM_1 = 5487, + BERSERK_1 = 50334, + CAT_FORM_1 = 768, + CHALLENGING_ROAR_1 = 5209, + CLAW_1 = 1082, + COWER_1 = 8998, + CURE_POISON_1 = 8946, + CYCLONE_1 = 33786, + DASH_1 = 1850, + DEMORALIZING_ROAR_1 = 99, + DIRE_BEAR_FORM_1 = 9634, + ENRAGE_1 = 5229, + ENTANGLING_ROOTS_1 = 339, + FAERIE_FIRE_1 = 770, + FAERIE_FIRE_FERAL_1 = 16857, + FERAL_CHARGE_1 = 49377, + FERAL_CHARGE_BEAR_1 = 16979, + FERAL_CHARGE_CAT_1 = 49376, + FEROCIOUS_BITE_1 = 22568, + FLIGHT_FORM_1 = 33943, + FORCE_OF_NATURE_1 = 33831, + FRENZIED_REGENERATION_1 = 22842, + GIFT_OF_THE_WILD_1 = 21849, + GROWL_1 = 6795, + HEALING_TOUCH_1 = 5185, + HIBERNATE_1 = 2637, + HURRICANE_1 = 16914, + INNERVATE_1 = 29166, + INSECT_SWARM_1 = 5570, + LACERATE_1 = 33745, + LIFEBLOOM_1 = 33763, + MAIM_1 = 22570, + MANGLE_1 = 33917, + MANGLE_BEAR_1 = 33878, + MANGLE_CAT_1 = 33876, + MARK_OF_THE_WILD_1 = 1126, + MAUL_1 = 6807, + MOONFIRE_1 = 8921, + MOONKIN_FORM_1 = 24858, + NATURES_GRASP_1 = 16689, + NATURES_SWIFTNESS_DRUID_1 = 17116, + NOURISH_1 = 50464, + POUNCE_1 = 9005, + PROWL_1 = 5215, + RAKE_1 = 1822, + RAVAGE_1 = 6785, + REBIRTH_1 = 20484, + REGROWTH_1 = 8936, + REJUVENATION_1 = 774, + REMOVE_CURSE_DRUID_1 = 2782, + REVIVE_1 = 50769, + RIP_1 = 1079, + SAVAGE_ROAR_1 = 52610, + SHRED_1 = 5221, + SOOTHE_ANIMAL_1 = 2908, + STARFALL_1 = 48505, + STARFIRE_1 = 2912, + SURVIVAL_INSTINCTS_1 = 61336, + SWIFTMEND_1 = 18562, + SWIFT_FLIGHT_FORM_1 = 40120, + SWIPE_BEAR_1 = 779, + SWIPE_CAT_1 = 62078, + THORNS_1 = 467, + TIGERS_FURY_1 = 5217, + TRANQUILITY_1 = 740, + TRAVEL_FORM_1 = 783, + TREE_OF_LIFE_1 = 33891, + TYPHOON_1 = 50516, + WILD_GROWTH_1 = 48438, + WRATH_1 = 5176 +}; + +//class Player; + +class MANGOS_DLL_SPEC PlayerbotDruidAI : PlayerbotClassAI +{ +public: + PlayerbotDruidAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotDruidAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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: + // Heals the target based off its hps + bool HealTarget (Unit *target); + // Callback method to reset shapeshift forms blocking buffs and heals + static void GoBuffForm(Player *self); + + // druid cat/bear/dire bear/moonkin/tree of life forms + uint32 CAT_FORM, + BEAR_FORM, + DIRE_BEAR_FORM, + MOONKIN_FORM, + TREE_OF_LIFE, + TRAVEL_FORM; + + // druid cat attacks + uint32 CLAW, + COWER, + TIGERS_FURY, + RAKE, + RIP, + FEROCIOUS_BITE, + MAIM, + MANGLE; + + // druid bear/dire bear attacks & buffs + uint32 BASH, + MAUL, + SWIPE, + DEMORALIZING_ROAR, + CHALLENGING_ROAR, + GROWL, + ENRAGE; + + // druid attacks & debuffs + uint32 MOONFIRE, + ROOTS, + WRATH, + STARFALL, + STARFIRE, + INSECT_SWARM, + FAERIE_FIRE, + FORCE_OF_NATURE, + HURRICANE; + + // druid buffs + uint32 MARK_OF_THE_WILD, + GIFT_OF_THE_WILD, + THORNS, + INNERVATE, + BARKSKIN; + + // druid heals + uint32 LIFEBLOOM, + REJUVENATION, + REGROWTH, + NOURISH, + HEALING_TOUCH, + WILD_GROWTH, + SWIFTMEND, + TRANQUILITY, + REVIVE; + + // 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 SpellSequence, DruidSpellCombat; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotHunterAI.cpp b/src/game/playerbot/PlayerbotHunterAI.cpp new file mode 100644 index 000000000..410802e61 --- /dev/null +++ b/src/game/playerbot/PlayerbotHunterAI.cpp @@ -0,0 +1,402 @@ +// an improved Hunter by rrtn & Runsttren :) + +#include "PlayerbotHunterAI.h" +#include "PlayerbotMgr.h" + +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_FEED = 1539; + + INTIMIDATION = ai->initSpell(INTIMIDATION_1); // (generic) + + // PET SKILLS must be initialized by pets + SONIC_BLAST = 0; // bat + DEMORALIZING_SCREECH = 0; + BAD_ATTITUDE = 0; // crocolisk + 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); + + // 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); + + // 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); + ARCANE_TRAP = 0; // non-player spell + SNAKE_TRAP = 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); + + 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 + + m_petSummonFailed = false; + m_rangedCombat = true; +} + +PlayerbotHunterAI::~PlayerbotHunterAI() {} + +bool PlayerbotHunterAI::HasPet(Player* bot) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT * FROM character_pet WHERE owner = '%u' AND (slot = '%u' OR slot = '%u')", bot->GetGUIDLow(), PET_SAVE_AS_CURRENT, PET_SAVE_NOT_IN_SLOT); + + if (result) + return true; //hunter has current pet + else + return false; //hunter either has no pet or stabled +} // end HasPet + +void PlayerbotHunterAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + ai->CastSpell(RAPTOR_STRIKE); + return; + } + + // ------- Non Duel combat ---------- + + // Hunter + ai->SetInFront(pTarget); + 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))) + { + ai->TellMaster("healing pet."); + return; + } + 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; + + // 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." ); + else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_INDEX_0)) + ai->CastSpell(BERSERKING, *m_bot); + //ai->TellMaster( "Berserking." ); + + // check if ranged combat is possible (set m_rangedCombat and switch auras + float dist = m_bot->GetDistance(pTarget); + if ((dist <= ATTACK_DISTANCE || !m_bot->GetUInt32Value(PLAYER_AMMO_ID)) && m_rangedCombat) + { + // switch to melee combat (target in melee range, out of ammo) + m_rangedCombat = false; + if (!m_bot->GetUInt32Value(PLAYER_AMMO_ID)) + ai->TellMaster("Out of ammo!"); + // become monkey (increases dodge chance)... + (ASPECT_OF_THE_MONKEY > 0 && !m_bot->HasAura(ASPECT_OF_THE_MONKEY, EFFECT_INDEX_0) && ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot)); + } + else if (dist > ATTACK_DISTANCE && !m_rangedCombat) + { + // switch to ranged combat + m_rangedCombat = true; + // increase ranged attack power... + (ASPECT_OF_THE_HAWK > 0 && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0) && ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot)); + } + else if (m_rangedCombat && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0)) + // check if we have hawk aspect in ranged combat + (ASPECT_OF_THE_HAWK > 0 && ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot)); + else if (!m_rangedCombat && !m_bot->HasAura(ASPECT_OF_THE_MONKEY, EFFECT_INDEX_0)) + // check if we have monkey aspect in melee combat + (ASPECT_OF_THE_MONKEY > 0 && ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot)); + + // activate auto shot + if (AUTO_SHOT > 0 && m_rangedCombat && !m_bot->FindCurrentSpellBySpellId(AUTO_SHOT)) + ai->CastSpell(AUTO_SHOT, *pTarget); + //ai->TellMaster( "started auto shot." ); + else if (AUTO_SHOT > 0 && m_bot->FindCurrentSpellBySpellId(AUTO_SHOT)) + m_bot->InterruptNonMeleeSpells(true, AUTO_SHOT); + //ai->TellMaster( "stopped auto shot." ); + + // damage spells + std::ostringstream out; + if (m_rangedCombat) + { + out << "Case Ranged"; + if (HUNTERS_MARK > 0 && 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->GetManaPercent() >= 13 && ai->GetAttackerCount() >= 3 && ai->CastSpell(MULTI_SHOT, *pTarget)) + out << " > Multi-Shot"; + else if (ARCANE_SHOT > 0 && ai->GetManaPercent() >= 7 && ai->CastSpell(ARCANE_SHOT, *pTarget)) + out << " > Arcane Shot"; + else if (CONCUSSIVE_SHOT > 0 && ai->GetManaPercent() >= 6 && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_INDEX_0) && ai->CastSpell(CONCUSSIVE_SHOT, *pTarget)) + out << " > Concussive Shot"; + else if (EXPLOSIVE_SHOT > 0 && ai->GetManaPercent() >= 10 && !pTarget->HasAura(EXPLOSIVE_SHOT, EFFECT_INDEX_0) && ai->CastSpell(EXPLOSIVE_SHOT, *pTarget)) + out << " > Explosive Shot"; + else if (VIPER_STING > 0 && 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->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->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->GetManaPercent() >= 12 && ai->CastSpell(CHIMERA_SHOT, *pTarget)) + out << " > Chimera Shot"; + else if (VOLLEY > 0 && ai->GetManaPercent() >= 24 && ai->GetAttackerCount() >= 3 && ai->CastSpell(VOLLEY, *pTarget)) + out << " > Volley"; + else if (BLACK_ARROW > 0 && ai->GetManaPercent() >= 6 && !pTarget->HasAura(BLACK_ARROW, EFFECT_INDEX_0) && ai->CastSpell(BLACK_ARROW, *pTarget)) + out << " > Black Arrow"; + else if (AIMED_SHOT > 0 && ai->GetManaPercent() >= 12 && ai->CastSpell(AIMED_SHOT, *pTarget)) + out << " > Aimed Shot"; + else if (STEADY_SHOT > 0 && ai->GetManaPercent() >= 5 && ai->CastSpell(STEADY_SHOT, *pTarget)) + out << " > Steady Shot"; + else if (KILL_SHOT > 0 && ai->GetManaPercent() >= 7 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.2 && ai->CastSpell(KILL_SHOT, *pTarget)) + out << " > Kill Shot!"; + else + out << " NONE!"; + } + else + { + out << "Case Melee"; + if (RAPTOR_STRIKE > 0 && ai->GetManaPercent() >= 6 && 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->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->GetManaPercent() >= 4 && 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_BLOODELF && !pTarget->HasAura(ARCANE_TORRENT, EFFECT_INDEX_0) && ai->CastSpell(ARCANE_TORRENT, *pTarget)) + out << " > Arcane Torrent"; + 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_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_DRAENEI && ai->GetHealthPercent() < 25 && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_INDEX_0) && ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot)) + out << " > Gift of the Naaru"; + else if ((pet && !pet->getDeathState() != ALIVE) + && (MISDIRECTION > 0 && 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) ) + out << " > Freezing Trap"; // this can trap your bots too + else if( BEAR_TRAP>0 && !pTarget->HasAura(BEAR_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_INDEX_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_INDEX_0) && ai->CastSpell(BEAR_TRAP,*pTarget) ) + out << " > Bear Trap"; // this was just too annoying :) + else if( DISENGAGE>0 && pVictim && ai->GetManaPercent()>=5 && ai->CastSpell(DISENGAGE,*pTarget) ) + out << " > Disengage!"; // attempt to return to ranged combat*/ + else + out << " NONE!"; + } + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster(out.str().c_str()); +} // end DoNextCombatManeuver + +void PlayerbotHunterAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + if (!ai) + return; + + Player * m_bot = GetPlayerBot(); + if (!m_bot) + return; + + // reset ranged combat state + if (!m_rangedCombat) + m_rangedCombat = true; + + // buff group + if (TRUESHOT_AURA > 0) + (!m_bot->HasAura(TRUESHOT_AURA, EFFECT_INDEX_0) && ai->CastSpell (TRUESHOT_AURA, *m_bot)); + + // buff myself + if (ASPECT_OF_THE_HAWK > 0) + (!m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_INDEX_0) && ai->CastSpell (ASPECT_OF_THE_HAWK, *m_bot)); + + // mana check + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + Item* pItem = ai->FindDrink(); + Item* fItem = ai->FindBandage(); + + if (pItem != NULL && ai->GetManaPercent() < 30) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + + // hp check + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + else if (pItem == NULL && fItem == NULL && m_bot->getRace() == RACE_DRAENEI && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I'm casting gift of the naaru."); + ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot); + return; + } + + // check for pet + 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."); + else + { + m_petSummonFailed = true; + ai->TellMaster("summon pet failed!"); + } + } + else if (pet->getDeathState() != ALIVE) + { + // revive pet + if (PET_REVIVE > 0 && ai->GetManaPercent() >= 80 && ai->CastSpell(PET_REVIVE, *m_bot)) + ai->TellMaster("reviving pet."); + } + else if (((float) pet->GetHealth() / (float) pet->GetMaxHealth()) < 0.5f) + { + // 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."); + } + else if (pet->GetHappinessState() != HAPPY) // if pet is hungry + { + Unit *caster = (Unit*) m_bot; + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + continue; + + if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet + { + //sLog.outDebug("Food for pet: %s",pItemProto->Name1); + caster->CastSpell(caster, 51284, true); // pet feed visual + uint32 count = 1; // number of items used + 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, NULL, NULL, true); // feed pet + ai->TellMaster("feeding pet."); + ai->SetIgnoreUpdateTime(10); + return; + } + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + continue; + + if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet + { + //sLog.outDebug("Food for pet: %s",pItemProto->Name1); + caster->CastSpell(caster, 51284, true); // pet feed visual + uint32 count = 1; // number of items used + 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, NULL, NULL, true); // feed pet + ai->TellMaster("feeding pet."); + 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); + } + } +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotHunterAI.h b/src/game/playerbot/PlayerbotHunterAI.h new file mode 100644 index 000000000..60c92cd0e --- /dev/null +++ b/src/game/playerbot/PlayerbotHunterAI.h @@ -0,0 +1,120 @@ +#ifndef _PLAYERHUNTERAI_H +#define _PLAYERHUNTERAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_HUNTER +}; + +enum HunterSpells +{ + ARCANE_SHOT_1 = 3044, + ASPECT_OF_THE_BEAST_1 = 13161, + ASPECT_OF_THE_CHEETAH_1 = 5118, + ASPECT_OF_THE_DRAGONHAWK_1 = 61846, + ASPECT_OF_THE_HAWK_1 = 13165, + ASPECT_OF_THE_MONKEY_1 = 13163, + ASPECT_OF_THE_PACK_1 = 13159, + ASPECT_OF_THE_VIPER_1 = 34074, + ASPECT_OF_THE_WILD_1 = 20043, + AUTO_SHOT_1 = 75, + BEAST_LORE_1 = 1462, + CALL_PET_1 = 883, + CALL_STABLED_PET_1 = 62757, + CONCUSSIVE_SHOT_1 = 5116, + DETERRENCE_1 = 19263, + DISENGAGE_1 = 781, + DISMISS_PET_1 = 2641, + DISTRACTING_SHOT_1 = 20736, + EAGLE_EYE_1 = 6197, + EXPLOSIVE_TRAP_1 = 13813, + EYES_OF_THE_BEAST_1 = 1002, + FEED_PET_1 = 6991, + FEIGN_DEATH_1 = 5384, + FLARE_1 = 1543, + FREEZING_ARROW_1 = 60192, + FREEZING_TRAP_1 = 1499, + FROST_TRAP_1 = 13809, + HUNTERS_MARK_1 = 1130, + IMMOLATION_TRAP_1 = 13795, + KILL_COMMAND_1 = 34026, + KILL_SHOT_1 = 53351, + MASTERS_CALL_1 = 53271, + MEND_PET_1 = 136, + MISDIRECTION_1 = 34477, + MONGOOSE_BITE_1 = 1495, + MULTISHOT_1 = 2643, + RAPID_FIRE_1 = 3045, + RAPTOR_STRIKE_1 = 2973, + REVIVE_PET_1 = 982, + SCARE_BEAST_1 = 1513, + SCORPID_STING_1 = 3043, + SERPENT_STING_1 = 1978, + SNAKE_TRAP_1 = 34600, + STEADY_SHOT_1 = 56641, + TAME_BEAST_1 = 1515, + TRACK_BEASTS_1 = 1494, + TRACK_DEMONS_1 = 19878, + TRACK_DRAGONKIN_1 = 19879, + TRACK_ELEMENTALS_1 = 19880, + TRACK_GIANTS_1 = 19882, + TRACK_HIDDEN_1 = 19885, + TRACK_HUMANOIDS_1 = 19883, + TRACK_UNDEAD_1 = 19884, + TRANQUILIZING_SHOT_1 = 19801, + VIPER_STING_1 = 3034, + VOLLEY_1 = 1510, + WING_CLIP_1 = 2974, + AIMED_SHOT_1 = 19434, + BESTIAL_WRATH_1 = 19574, + BLACK_ARROW_1 = 3674, + CHIMERA_SHOT_1 = 53209, + COUNTERATTACK_1 = 19306, + EXPLOSIVE_SHOT_1 = 53301, + INTIMIDATION_1 = 19577, + READINESS_1 = 23989, + SCATTER_SHOT_1 = 19503, + SILENCING_SHOT_1 = 34490, + TRUESHOT_AURA_1 = 19506, + WYVERN_STING_1 = 19386 +}; + +//class Player; + +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 + void DoNextCombatManeuver(Unit*); + + // 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 + //void BuffPlayer(Player *target); + +private: + // Hunter + bool m_petSummonFailed; + bool m_rangedCombat; + + 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; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotMageAI.cpp b/src/game/playerbot/PlayerbotMageAI.cpp new file mode 100644 index 000000000..df21cdda1 --- /dev/null +++ b/src/game/playerbot/PlayerbotMageAI.cpp @@ -0,0 +1,467 @@ + +#include "PlayerbotMageAI.h" + +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); + + 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 +} + +PlayerbotMageAI::~PlayerbotMageAI() {} + +void PlayerbotMageAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + if (FIREBALL > 0) + ai->CastSpell(FIREBALL); + return; + } + + // ------- Non Duel combat ---------- + + //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob + + // Damage Spells (primitive example) + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + float dist = m_bot->GetDistance(pTarget); + + switch (SpellSequence) + { + 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 && dist <= ATTACK_DISTANCE && !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 && dist <= ATTACK_DISTANCE && !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) + { + ai->CastSpell(COLD_SNAP, *m_bot); + SpellSequence = SPELL_FIRE; + LastSpellFrost = LastSpellFrost + 1; + break; + } + LastSpellFrost = 0; + //SpellSequence = SPELL_FIRE; + //break; + + 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 && dist <= ATTACK_DISTANCE && ai->GetManaPercent() >= 34) + { + ai->CastSpell(BLAST_WAVE, *pTarget); + SpellSequence = SPELL_ARCANE; + LastSpellFire = LastSpellFire + 1; + break; + } + else if (DRAGONS_BREATH > 0 && LastSpellFire < 9 && dist <= ATTACK_DISTANCE && 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; + + 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 && dist <= ATTACK_DISTANCE && 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; + } + else + { + LastSpellArcane = 0; + SpellSequence = SPELL_FROST; + } + } +} // end DoNextCombatManeuver + +void PlayerbotMageAI::DoNonCombatActions() +{ + Player * m_bot = GetPlayerBot(); + Player * master = GetMaster(); + + if (!m_bot || !master) + return; + + SpellSequence = SPELL_FROST; + PlayerbotAI* ai = GetAI(); + + // Buff armor + if (MOLTEN_ARMOR) + { + if (ai->SelfBuff(MOLTEN_ARMOR)) + return; + } + else if (MAGE_ARMOR) + { + if (ai->SelfBuff(MAGE_ARMOR)) + return; + } + else if (ICE_ARMOR) + { + if (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; + } + + } + // There is no group, buff master + else + { + if (master->isAlive() && BuffPlayer(master)) + return; + } + + // Buff self finally + if (BuffPlayer(m_bot)) + 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 == NULL && CONJURE_WATER && ai->GetBaseManaPercent() >= 48) + { + ai->TellMaster("I'm conjuring some water."); + ai->CastSpell(CONJURE_WATER, *m_bot); + ai->SetIgnoreUpdateTime(3); + return; + } + else if (pItem != NULL && ai->GetManaPercent() < 30) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + + pItem = ai->FindFood(); + + if (pItem == NULL && CONJURE_FOOD && ai->GetBaseManaPercent() >= 48) + { + ai->TellMaster("I'm conjuring some food."); + ai->CastSpell(CONJURE_FOOD, *m_bot); + ai->SetIgnoreUpdateTime(3); + } + + // hp check + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + +} // end DoNonCombatActions + +bool PlayerbotMageAI::BuffPlayer(Player* target) +{ + PlayerbotAI * ai = GetAI(); + Pet * pet = target->GetPet(); + + if (pet && pet->getPowerType() == POWER_MANA && ai->Buff(ARCANE_INTELLECT, pet)) + return true; + + if (ARCANE_INTELLECT) + return ai->Buff(ARCANE_INTELLECT, target); + else + return false; +} diff --git a/src/game/playerbot/PlayerbotMageAI.h b/src/game/playerbot/PlayerbotMageAI.h new file mode 100644 index 000000000..226f373bd --- /dev/null +++ b/src/game/playerbot/PlayerbotMageAI.h @@ -0,0 +1,162 @@ +#ifndef _PlayerbotMageAI_H +#define _PlayerbotMageAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_FROST, + SPELL_FIRE, + SPELL_ARCANE +}; + +enum MageSpells +{ + AMPLIFY_MAGIC_1 = 1008, + ARCANE_BARRAGE_1 = 44425, + ARCANE_BLAST_1 = 30451, + ARCANE_BRILLIANCE_1 = 23028, + ARCANE_EXPLOSION_1 = 1449, + ARCANE_INTELLECT_1 = 1459, + ARCANE_MISSILES_1 = 5143, + ARCANE_POWER_1 = 12042, + BLAST_WAVE_1 = 11113, + BLINK_1 = 1953, + BLIZZARD_1 = 10, + COLD_SNAP_1 = 11958, + COMBUSTION_1 = 11129, + CONE_OF_COLD_1 = 120, + CONJURE_FOOD_1 = 587, + CONJURE_MANA_GEM_1 = 759, + CONJURE_REFRESHMENT_1 = 42955, + CONJURE_WATER_1 = 5504, + COUNTERSPELL_1 = 2139, + DALARAN_BRILLIANCE_1 = 61316, + DALARAN_INTELLECT_1 = 61024, + DAMPEN_MAGIC_1 = 604, + DEEP_FREEZE_1 = 44572, + DRAGONS_BREATH_1 = 31661, + EVOCATION_1 = 12051, + FIRE_BLAST_1 = 2136, + FIRE_WARD_1 = 543, + FIREBALL_1 = 133, + FLAMESTRIKE_1 = 2120, + FOCUS_MAGIC_1 = 54646, + FROST_ARMOR_1 = 168, + FROST_NOVA_1 = 122, + FROST_WARD_1 = 6143, + FROSTBOLT_1 = 116, + FROSTFIRE_BOLT_1 = 44614, + ICE_ARMOR_1 = 7302, + ICE_BARRIER_1 = 11426, + ICE_BLOCK_1 = 45438, + ICE_LANCE_1 = 30455, + ICY_VEINS_1 = 12472, + INVISIBILITY_1 = 66, + LIVING_BOMB_1 = 44457, + MAGE_ARMOR_1 = 6117, + MANA_SHIELD_1 = 1463, + MIRROR_IMAGE_1 = 55342, + MOLTEN_ARMOR_1 = 30482, + PRESENCE_OF_MIND_1 = 12043, + PYROBLAST_1 = 11366, + REMOVE_CURSE_MAGE_1 = 475, + RITUAL_OF_REFRESHMENT_1 = 43987, + SCORCH_1 = 2948, + SLOW_1 = 31589, + SLOW_FALL_1 = 130, + SPELLSTEAL_1 = 30449, + SUMMON_WATER_ELEMENTAL_1 = 31687 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotMageAI : PlayerbotClassAI +{ +public: + PlayerbotMageAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotMageAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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: + // ARCANE + uint32 ARCANE_MISSILES, + ARCANE_EXPLOSION, + COUNTERSPELL, + SLOW, + ARCANE_BARRAGE, + ARCANE_BLAST, + MIRROR_IMAGE, + ARCANE_POWER; + + // FIRE + uint32 FIREBALL, + FIRE_BLAST, + FLAMESTRIKE, + SCORCH, + PYROBLAST, + BLAST_WAVE, + COMBUSTION, + DRAGONS_BREATH, + LIVING_BOMB, + FROSTFIRE_BOLT, + FIRE_WARD; + + // FROST + uint32 DEEP_FREEZE, + FROSTBOLT, + FROST_NOVA, + BLIZZARD, + ICY_VEINS, + CONE_OF_COLD, + ICE_BARRIER, + SUMMON_WATER_ELEMENTAL, + ICE_LANCE, + FROST_WARD, + ICE_BLOCK, + COLD_SNAP; + + // buffs + uint32 FROST_ARMOR, + ICE_ARMOR, + MAGE_ARMOR, + MOLTEN_ARMOR, + ARCANE_INTELLECT, + ARCANE_BRILLIANCE, + DALARAN_INTELLECT, + DALARAN_BRILLIANCE, + MANA_SHIELD, + DAMPEN_MAGIC, + AMPLIFY_MAGIC; + + // 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 SpellSequence, + LastSpellArcane, + LastSpellFire, + LastSpellFrost, + CONJURE_WATER, + CONJURE_FOOD; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotMgr.cpp b/src/game/playerbot/PlayerbotMgr.cpp new file mode 100644 index 000000000..6a8064857 --- /dev/null +++ b/src/game/playerbot/PlayerbotMgr.cpp @@ -0,0 +1,638 @@ +#include "Config/Config.h" +#include "../Player.h" +#include "PlayerbotAI.h" +#include "PlayerbotMgr.h" +#include "WorldPacket.h" +#include "../Chat.h" +#include "../ObjectMgr.h" +#include "../GossipDef.h" +#include "../Chat.h" +#include "../Language.h" +#include "../Guild.h" + +class LoginQueryHolder; +class CharacterHandler; + +Config botConfig; + +PlayerbotMgr::PlayerbotMgr(Player* const master) : m_master(master) +{ + // load config variables + m_confMaxNumBots = botConfig.GetIntDefault("PlayerbotAI.MaxNumBots", 9); + m_confDebugWhisper = botConfig.GetBoolDefault("PlayerbotAI.DebugWhisper", false); + m_confFollowDistance[0] = botConfig.GetFloatDefault("PlayerbotAI.FollowDistanceMin", 0.5f); + m_confFollowDistance[1] = botConfig.GetFloatDefault("PlayerbotAI.FollowDistanceMin", 1.0f); +} + +PlayerbotMgr::~PlayerbotMgr() +{ + LogoutAllBots(); +} + +void PlayerbotMgr::UpdateAI(const uint32 p_time) {} + +void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) +{ + switch (packet.GetOpcode()) + { + // if master is logging out, log out all bots + case CMSG_LOGOUT_REQUEST: + { + LogoutAllBots(); + return; + } + + // 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: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 guid; + p >> guid; + Player* const bot = GetPlayerBot(guid); + if (bot) bot->GetPlayerbotAI()->SendNotEquipList(*bot); + return; + } + + // handle emotes from the master + //case CMSG_EMOTE: + case CMSG_TEXT_EMOTE: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint32 emoteNum; + p >> emoteNum; + + /* std::ostringstream out; + out << "emote is: " << emoteNum; + ChatHandler ch(m_master); + ch.SendSysMessage(out.str().c_str()); */ + + switch (emoteNum) + { + case TEXTEMOTE_BOW: + { + // Buff anyone who bows before me. Useful for players not in bot's group + // How do I get correct target??? + //Player* const pPlayer = GetPlayerBot(m_master->GetSelection()); + //if (pPlayer->GetPlayerbotAI()->GetClassAI()) + // pPlayer->GetPlayerbotAI()->GetClassAI()->BuffPlayer(pPlayer); + return; + } + /* + 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 << "time(0): " << time(0) + << " 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: + { + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->Feast(); + } + return; + } + + // emote to attack selected target + case TEXTEMOTE_POINT: + { + ObjectGuid attackOnGuid = m_master->GetSelectionGuid(); + if (attackOnGuid.IsEmpty()) + return; + + Unit* thingToAttack = ObjectAccessor::GetUnit(*m_master, attackOnGuid); + if (!thingToAttack) return; + + Player *bot = 0; + for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) + { + bot = itr->second; + if (!bot->IsFriendlyTo(thingToAttack) && bot->IsWithinLOSInMap(thingToAttack)) + bot->GetPlayerbotAI()->GetCombatTarget(thingToAttack); + } + return; + } + + // emote to stay + case TEXTEMOTE_STAND: + { + Player* const bot = GetPlayerBot(m_master->GetSelectionGuid().GetRawValue()); + if (bot) + bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY); + else + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY); + } + 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: + { + Player* const bot = GetPlayerBot(m_master->GetSelectionGuid().GetRawValue()); + if (bot) + bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, m_master); + else + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, m_master); + } + return; + } + } + return; + } /* EMOTE ends here */ + + case CMSG_GAMEOBJ_USE: // not sure if we still need this one + case CMSG_GAMEOBJ_REPORT_USE: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 objGUID; + p >> objGUID; + + GameObject *obj = m_master->GetMap()->GetGameObject(objGUID); + if (!obj) + return; + + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + + 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 + } + } + break; + + // if master talks to an NPC + case CMSG_GOSSIP_HELLO: + case CMSG_QUESTGIVER_HELLO: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 npcGUID; + p >> npcGUID; + + WorldObject* pNpc = m_master->GetMap()->GetWorldObject(npcGUID); + if (!pNpc) + return; + + // for all master's bots + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->TurnInQuests(pNpc); + } + + return; + } + + // if master accepts a quest, bots should also try to accept quest + case CMSG_QUESTGIVER_ACCEPT_QUEST: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 guid; + uint32 quest; + p >> guid >> quest; + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest); + if (qInfo) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + + if (bot->GetQuestStatus(quest) == QUEST_STATUS_COMPLETE) + bot->GetPlayerbotAI()->TellMaster("I already completed that quest."); + else if (!bot->CanTakeQuest(qInfo, false)) + { + if (!bot->SatisfyQuestStatus(qInfo, false)) + bot->GetPlayerbotAI()->TellMaster("I already have that quest."); + else + bot->GetPlayerbotAI()->TellMaster("I can't take that quest."); + } + else if (!bot->SatisfyQuestLog(false)) + bot->GetPlayerbotAI()->TellMaster("My quest log is full."); + else if (!bot->CanAddQuest(qInfo, false)) + bot->GetPlayerbotAI()->TellMaster("I can't take that quest because it requires that I take items, but my bags are full!"); + + else + { + p.rpos(0); // reset reader + bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p); + bot->GetPlayerbotAI()->TellMaster("Got the quest."); + } + } + return; + } + case CMSG_LOOT_ROLL: + { + + WorldPacket p(packet); //WorldPacket packet for CMSG_LOOT_ROLL, (8+4+1) + uint64 Guid; + uint32 NumberOfPlayers; + uint8 rollType; + p.rpos(0); //reset packet pointer + p >> Guid; //guid of the item rolled + p >> NumberOfPlayers; //number of players invited to roll + p >> rollType; //need,greed or pass on roll + + + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + + uint32 choice = urand(0, 3); //returns 0,1,2 or 3 + + Player* const bot = it->second; + if (!bot) + return; + + Group* group = bot->GetGroup(); + if (!group) + return; + + group->CountRollVote(bot, Guid, NumberOfPlayers, RollVote(choice)); + + switch (choice) + { + case ROLL_NEED: + bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED, 1); + break; + case ROLL_GREED: + bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED, 1); + break; + } + } + return; + } + case CMSG_REPAIR_ITEM: + { + + WorldPacket p(packet); // WorldPacket packet for CMSG_REPAIR_ITEM, (8+8+1) + + sLog.outDebug("PlayerbotMgr: CMSG_REPAIR_ITEM"); + + ObjectGuid npcGUID; + uint64 itemGUID; + uint8 guildBank; + + p.rpos(0); //reset packet pointer + p >> npcGUID; + p >> itemGUID; // Not used for bot but necessary opcode data retrieval + p >> guildBank; // Flagged if guild repair selected + + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + + Player* const bot = it->second; + if (!bot) + return; + + Group* group = bot->GetGroup(); // check if bot is a member of group + if (!group) + return; + + Creature *unit = bot->GetNPCIfCanInteractWith(npcGUID, UNIT_NPC_FLAG_REPAIR); + if (!unit) // Check if NPC can repair bot or not + { + sLog.outDebug("PlayerbotMgr: HandleRepairItemOpcode - Unit (GUID: %s) not found or you can't interact with him.", npcGUID.GetString().c_str()); + return; + } + + // remove fake death + if (bot->hasUnitState(UNIT_STAT_DIED)) + bot->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + + // reputation discount + float discountMod = bot->GetReputationPriceDiscount(unit); + + uint32 TotalCost = 0; + if (itemGUID) // Handle redundant feature (repair individual item) for bot + { + sLog.outDebug("ITEM: Repair single item is not applicable for %s", bot->GetName()); + continue; + } + else // Handle feature (repair all items) for bot + { + sLog.outDebug("ITEM: Repair all items, npcGUID = %s", npcGUID.GetString().c_str()); + + TotalCost = bot->DurabilityRepairAll(true, discountMod, guildBank > 0 ? true : false); + } + if (guildBank) // Handle guild repair + { + uint32 GuildId = bot->GetGuildId(); + if (!GuildId) + return; + Guild *pGuild = sObjectMgr.GetGuildById(GuildId); + if (!pGuild) + return; + pGuild->LogBankEvent(GUILD_BANK_LOG_REPAIR_MONEY, 0, bot->GetGUIDLow(), TotalCost); + pGuild->SendMoneyInfo(bot->GetSession(), bot->GetGUIDLow()); + } + + } + return; + } + case CMSG_SPIRIT_HEALER_ACTIVATE: + { + // sLog.outDebug("SpiritHealer is resurrecting the Player %s",m_master->GetName()); + for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr) + { + Player* const bot = itr->second; + Group *grp = bot->GetGroup(); + if (grp) + grp->RemoveMember(bot->GetGUID(), 1); + } + return; + } + + /* + case CMSG_NAME_QUERY: + case MSG_MOVE_START_FORWARD: + case MSG_MOVE_STOP: + case MSG_MOVE_SET_FACING: + case MSG_MOVE_START_STRAFE_LEFT: + case MSG_MOVE_START_STRAFE_RIGHT: + case MSG_MOVE_STOP_STRAFE: + case MSG_MOVE_START_BACKWARD: + case MSG_MOVE_HEARTBEAT: + case CMSG_STANDSTATECHANGE: + case CMSG_QUERY_TIME: + case CMSG_CREATURE_QUERY: + case CMSG_GAMEOBJECT_QUERY: + case MSG_MOVE_JUMP: + case MSG_MOVE_FALL_LAND: + return; + + default: + { + 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()); + } + */ + } +} +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()); + + std::ostringstream out; + out << "masterout: " << oc; + sLog.outError(out.str().c_str()); + } + } + */ +} + +void PlayerbotMgr::LogoutAllBots() +{ + while (true) + { + PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); + if (itr == GetPlayerBotsEnd()) break; + Player* bot = itr->second; + LogoutPlayerBot(bot->GetGUID()); + } +} + + + +void PlayerbotMgr::Stay() +{ + for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) + { + Player* bot = itr->second; + bot->GetMotionMaster()->Clear(); + } +} + + +// Playerbot mod: logs out a Playerbot. +void PlayerbotMgr::LogoutPlayerBot(uint64 guid) +{ + Player* bot = GetPlayerBot(guid); + if (bot) + { + WorldSession * botWorldSessionPtr = bot->GetSession(); + m_playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object + delete botWorldSessionPtr; // finally delete the bot's WorldSession + } +} + +// Playerbot mod: Gets a player bot Player object for this WorldSession master +Player* PlayerbotMgr::GetPlayerBot(uint64 playerGuid) const +{ + PlayerBotMap::const_iterator it = m_playerBots.find(playerGuid); + return (it == m_playerBots.end()) ? 0 : it->second; +} + +void PlayerbotMgr::OnBotLogin(Player * const bot) +{ + // give the bot some AI, object is owned by the player class + PlayerbotAI* ai = new PlayerbotAI(this, bot); + bot->SetPlayerbotAI(ai); + + // tell the world session that they now manage this new bot + m_playerBots[bot->GetGUID()] = bot; + + // if bot is in a group and master is not in group then + // have bot leave their group + if (bot->GetGroup() && + (m_master->GetGroup() == NULL || + m_master->GetGroup()->IsMember(bot->GetGUID()) == false)) + bot->RemoveFromGroup(); + + // sometimes master can lose leadership, pass leadership to master check + const uint64 masterGuid = m_master->GetGUID(); + if (m_master->GetGroup() && + !m_master->GetGroup()->IsLeader(masterGuid)) + m_master->GetGroup()->ChangeLeader(masterGuid); +} + +void PlayerbotMgr::RemoveAllBotsFromGroup() +{ + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); m_master->GetGroup() && it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->IsInSameGroupWith(m_master)) + m_master->GetGroup()->RemoveMember(bot->GetGUID(), 0); + } +} + +void Creature::LoadBotMenu(Player *pPlayer) +{ + + if (pPlayer->GetPlayerbotAI()) return; + uint64 guid = pPlayer->GetGUID(); + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid); + QueryResult *result = CharacterDatabase.PQuery("SELECT guid, name FROM characters WHERE account='%d'", accountId); + do + { + Field *fields = result->Fetch(); + uint64 guidlo = fields[0].GetUInt64(); + std::string name = fields[1].GetString(); + std::string word = ""; + + if ((guid == 0) || (guid == guidlo)) + { + //not found or himself + } + else + { + // if(sConfig.GetBoolDefault("PlayerbotAI.DisableBots", false)) return; + // create the manager if it doesn't already exist + if (!pPlayer->GetPlayerbotMgr()) + pPlayer->SetPlayerbotMgr(new PlayerbotMgr(pPlayer)); + if (pPlayer->GetPlayerbotMgr()->GetPlayerBot(guidlo) == NULL) // add (if not already in game) + { + word += "Recruit "; + word += name; + word += " as a Bot."; + pPlayer->PlayerTalkClass->GetGossipMenu().AddMenuItem((uint8) 9, word, guidlo, GOSSIP_OPTION_BOT, word, false); + } + else if (pPlayer->GetPlayerbotMgr()->GetPlayerBot(guidlo) != NULL) // remove (if in game) + { + word += "Dismiss "; + word += name; + word += " from duty."; + pPlayer->PlayerTalkClass->GetGossipMenu().AddMenuItem((uint8) 0, word, guidlo, GOSSIP_OPTION_BOT, word, false); + } + } + } + while (result->NextRow()); + delete result; +} + +void Player::chompAndTrim(std::string& str) +{ + while (str.length() > 0) + { + char lc = str[str.length() - 1]; + if (lc == '\r' || lc == '\n' || lc == ' ' || lc == '"' || lc == '\'') + str = str.substr(0, str.length() - 1); + else + break; + while (str.length() > 0) + { + char lc = str[0]; + if (lc == ' ' || lc == '"' || lc == '\'') + str = str.substr(1, str.length() - 1); + else + break; + } + } +} + +bool Player::getNextQuestId(const std::string& pString, unsigned int& pStartPos, unsigned int& pId) +{ + bool result = false; + unsigned int i; + for (i = pStartPos; i < pString.size(); ++i) + { + if (pString[i] == ',') + break; + } + if (i > pStartPos) + { + std::string idString = pString.substr(pStartPos, i - pStartPos); + pStartPos = i + 1; + chompAndTrim(idString); + pId = atoi(idString.c_str()); + result = true; + } + return(result); +} + +bool Player::requiredQuests(const char* pQuestIdString) +{ + if (pQuestIdString != NULL) + { + unsigned int pos = 0; + unsigned int id; + std::string confString(pQuestIdString); + chompAndTrim(confString); + while (getNextQuestId(confString, pos, id)) + { + QuestStatus status = GetQuestStatus(id); + if (status == QUEST_STATUS_COMPLETE) + return true; + } + } + return false; +} diff --git a/src/game/playerbot/PlayerbotMgr.h b/src/game/playerbot/PlayerbotMgr.h new file mode 100644 index 000000000..1b67b674b --- /dev/null +++ b/src/game/playerbot/PlayerbotMgr.h @@ -0,0 +1,58 @@ +#ifndef _PLAYERBOTMGR_H +#define _PLAYERBOTMGR_H + +#include "Common.h" + +class WorldPacket; +class Player; +class Unit; +class Object; +class Item; +class PlayerbotClassAI; + +typedef UNORDERED_MAP PlayerBotMap; + +class MANGOS_DLL_SPEC PlayerbotMgr +{ +public: + PlayerbotMgr(Player * const master); + virtual ~PlayerbotMgr(); + + // This is called from Unit.cpp and is called every second (I think) + void UpdateAI(const uint32 p_time); + + // This is called whenever the master sends a packet to the server. + // These packets can be viewed, but not edited. + // It allows bot creators to craft AI in response to a master's actions. + // For a list of opcodes that can be caught see Opcodes.cpp (CMSG_* opcodes only) + // Notice: that this is static which means it is called once for all bots of the master. + void HandleMasterIncomingPacket(const WorldPacket& packet); + void HandleMasterOutgoingPacket(const WorldPacket& packet); + + void AddPlayerBot(uint64 guid); + void LogoutPlayerBot(uint64 guid); + Player* GetPlayerBot (uint64 guid) const; + Player* GetMaster() const { return m_master; }; + PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return m_playerBots.begin(); } + PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return m_playerBots.end(); } + + void LogoutAllBots(); + void RemoveAllBotsFromGroup(); + void OnBotLogin(Player * const bot); + void Stay(); + +public: + // config variables + uint32 m_confRestrictBotLevel; + uint32 m_confDisableBotsInRealm; + uint32 m_confMaxNumBots; + bool m_confDisableBots; + bool m_confDebugWhisper; + float m_confFollowDistance[2]; + +private: + Player* const m_master; + PlayerBotMap m_playerBots; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotPaladinAI.cpp b/src/game/playerbot/PlayerbotPaladinAI.cpp new file mode 100644 index 000000000..c27e30302 --- /dev/null +++ b/src/game/playerbot/PlayerbotPaladinAI.cpp @@ -0,0 +1,520 @@ +/* + Name : PlayerbotPaladinAI.cpp + Complete: maybe around 27% :D + Author : Natsukawa + Version : 0.35 + */ + +#include "PlayerbotPaladinAI.h" +#include "PlayerbotMgr.h" + +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); + + // Warrior auras + DEFENSIVE_STANCE = 71; //Def Stance + BERSERKER_STANCE = 2458; //Ber Stance + BATTLE_STANCE = 2457; //Bat Stance + + FORBEARANCE = 25771; // cannot be protected + + 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 +} + +PlayerbotPaladinAI::~PlayerbotPaladinAI() {} + +bool PlayerbotPaladinAI::HealTarget(Unit *target) +{ + 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; + + return false; +} // end HealTarget + +void PlayerbotPaladinAI::DoNextCombatManeuver(Unit *pTarget) +{ + Unit* pVictim = pTarget->getVictim(); + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + if (HAMMER_OF_JUSTICE > 0) + ai->CastSpell(HAMMER_OF_JUSTICE); + return; + } + + // damage spells + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Group *m_group = m_bot->GetGroup(); + float dist = m_bot->GetDistance(pTarget); + std::ostringstream out; + + //Shield master if low hp. + uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); + + 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()); + + // 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; + + 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; + } + } + + if (RIGHTEOUS_FURY > 0 && !m_bot->HasAura(RIGHTEOUS_FURY, EFFECT_INDEX_0)) + 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); + + if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_WARRIOR) + ai->CastSpell (DEVOTION_AURA, *m_bot); + + 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); + + if (RETRIBUTION_AURA > 0 && !m_bot->HasAura(RETRIBUTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_PRIEST) + ai->CastSpell (RETRIBUTION_AURA, *m_bot); + + if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_SHAMAN) + ai->CastSpell (DEVOTION_AURA, *m_bot); + + if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_ROGUE) + ai->CastSpell (DEVOTION_AURA, *m_bot); + + if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA, EFFECT_INDEX_0) && pTarget->getClass() == CLASS_PALADIN) + ai->CastSpell (DEVOTION_AURA, *m_bot); + + if (ai->GetHealthPercent() <= 40 || GetMaster()->GetHealth() <= GetMaster()->GetMaxHealth() * 0.4) + SpellSequence = Healing; + else + SpellSequence = Combat; + + switch (SpellSequence) + { + 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 && dist <= ATTACK_DISTANCE && 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 && dist <= ATTACK_DISTANCE && 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; + } + + case Healing: + if (ai->GetHealthPercent() <= 40) + { + HealTarget (m_bot); + out << " ...healing bot"; + break; + } + if (masterHP <= 40) + { + HealTarget (GetMaster()); + out << " ...healing master"; + break; + } + else + { + CombatCounter = 0; + //ai->TellMaster("Counter = 0"); + break; + } + } + 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); + + 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); + + if (DIVINE_SACRIFICE > 0 && ai->GetHealthPercent() > 50 && pVictim != m_bot && !m_bot->HasAura(DIVINE_SACRIFICE, EFFECT_INDEX_0)) + ai->CastSpell(DIVINE_SACRIFICE, *m_bot); +} + +void PlayerbotPaladinAI::DoNonCombatActions() +{ + PlayerbotAI* ai = GetAI(); + Player * m_bot = GetPlayerBot(); + if (!m_bot) + return; + + // Buff myself + if (ai->GetCombatOrder() == ai->ORDERS_TANK) + ai->SelfBuff(RIGHTEOUS_FURY); + BuffPlayer(m_bot); + + // Buff master + BuffPlayer(ai->GetMaster()); + + // 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 != NULL && ai->GetManaPercent() < 40) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + + // hp check original + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 40) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + + // heal and buff 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) + continue; + + if (!tPlayer->isAlive()) + { + if (ai->CastSpell(REDEMPTION, *tPlayer)) + { + std::string msg = "Resurrecting "; + msg += tPlayer->GetName(); + m_bot->Say(msg, LANG_UNIVERSAL); + return; + } + else + continue; + } + + if (HealTarget(tPlayer)) + return; + + if (tPlayer != m_bot && tPlayer != GetMaster()) + if (BuffPlayer(tPlayer)) + return; + } + } +} + +bool PlayerbotPaladinAI::BuffPlayer(Player* target) +{ + PlayerbotAI * ai = GetAI(); + uint8 SPELL_BLESSING = 2; // See SpellSpecific enum in SpellMgr.h + + Pet * pet = target->GetPet(); + bool petCanBeBlessed = false; + if (pet) + petCanBeBlessed = ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet); + + if (!ai->CanReceiveSpecificSpell(SPELL_BLESSING, target) && !petCanBeBlessed) + return false; + + 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; + case CLASS_DEATH_KNIGHT: + 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; + 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; + case CLASS_WARLOCK: + if (petCanBeBlessed) + { + if (pet->getPowerType() == POWER_MANA) + { + if (Bless(BLESSING_OF_WISDOM, pet)) + return true; + } + 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; + } + return false; +} + +bool PlayerbotPaladinAI::Bless(uint32 spellId, Unit *target) +{ + if (spellId == 0) + return false; + + PlayerbotAI * ai = GetAI(); + + 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); + } + + // Should not happen, but let it be here + return false; +} diff --git a/src/game/playerbot/PlayerbotPaladinAI.h b/src/game/playerbot/PlayerbotPaladinAI.h new file mode 100644 index 000000000..55d8e9e5f --- /dev/null +++ b/src/game/playerbot/PlayerbotPaladinAI.h @@ -0,0 +1,178 @@ +#ifndef _PlayerbotPaladinAI_H +#define _PlayerbotPaladinAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + Combat, + Healing +}; + +enum PaladinSpells +{ + AURA_MASTERY_1 = 31821, + AVENGERS_SHIELD_1 = 31935, + AVENGING_WRATH_1 = 31884, + BEACON_OF_LIGHT_1 = 53563, + BLESSING_OF_KINGS_1 = 20217, + BLESSING_OF_MIGHT_1 = 19740, + BLESSING_OF_SANCTUARY_1 = 20911, + BLESSING_OF_WISDOM_1 = 19742, + CLEANSE_1 = 4987, + CONCENTRATION_AURA_1 = 19746, + CONSECRATION_1 = 26573, + CRUSADER_AURA_1 = 32223, + CRUSADER_STRIKE_1 = 35395, + DEVOTION_AURA_1 = 465, + DIVINE_FAVOR_1 = 20216, + DIVINE_ILLUMINATION_1 = 31842, + DIVINE_INTERVENTION_1 = 19752, + DIVINE_PLEA_1 = 54428, + DIVINE_PROTECTION_1 = 498, + DIVINE_SACRIFICE_1 = 64205, + DIVINE_SHIELD_1 = 642, + DIVINE_STORM_1 = 53385, + EXORCISM_1 = 879, + FIRE_RESISTANCE_AURA_1 = 19891, + FLASH_OF_LIGHT_1 = 19750, + FROST_RESISTANCE_AURA_1 = 19888, + GREATER_BLESSING_OF_KINGS_1 = 25898, + GREATER_BLESSING_OF_MIGHT_1 = 25782, + GREATER_BLESSING_OF_SANCTUARY_1 = 25899, + GREATER_BLESSING_OF_WISDOM_1 = 25894, + HAMMER_OF_JUSTICE_1 = 853, + HAMMER_OF_THE_RIGHTEOUS_1 = 53595, + HAMMER_OF_WRATH_1 = 24275, + HAND_OF_FREEDOM_1 = 1044, + HAND_OF_PROTECTION_1 = 1022, + HAND_OF_RECKONING_1 = 62124, + HAND_OF_SACRIFICE_1 = 6940, + HAND_OF_SALVATION_1 = 1038, + HOLY_LIGHT_1 = 635, + HOLY_SHIELD_1 = 20925, + HOLY_SHOCK_1 = 20473, + HOLY_WRATH_1 = 2812, + JUDGEMENT_OF_JUSTICE_1 = 53407, + JUDGEMENT_OF_LIGHT_1 = 20271, + JUDGEMENT_OF_WISDOM_1 = 53408, + LAY_ON_HANDS_1 = 633, + PURIFY_1 = 1152, + REDEMPTION_1 = 7328, + REPENTANCE_1 = 20066, + RETRIBUTION_AURA_1 = 7294, + RIGHTEOUS_DEFENSE_1 = 31789, + RIGHTEOUS_FURY_1 = 25780, + SACRED_SHIELD_1 = 53601, + SEAL_OF_COMMAND_1 = 20375, + SEAL_OF_CORRUPTION = 53736, + SEAL_OF_JUSTICE_1 = 20164, + SEAL_OF_LIGHT_1 = 20165, + SEAL_OF_RIGHTEOUSNESS_1 = 20154, + SEAL_OF_VENGEANCE = 31801, + SEAL_OF_WISDOM_1 = 20166, + SENSE_UNDEAD_1 = 5502, + SHADOW_RESISTANCE_AURA_1 = 19876, + SHIELD_OF_RIGHTEOUSNESS_1 = 53600, + TURN_EVIL_1 = 10326 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotPaladinAI : PlayerbotClassAI +{ +public: + PlayerbotPaladinAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotPaladinAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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: + // Heals the target based off its hps + bool HealTarget (Unit *target); + // Bless target using greater blessing if possible + bool Bless(uint32 spellId, Unit *target); + + // Retribution + uint32 RETRIBUTION_AURA, + SEAL_OF_COMMAND, + JUDGEMENT_OF_LIGHT, + JUDGEMENT_OF_WISDOM, + GREATER_BLESSING_OF_WISDOM, + GREATER_BLESSING_OF_MIGHT, + BLESSING_OF_WISDOM, + BLESSING_OF_MIGHT, + HAMMER_OF_JUSTICE, + RIGHTEOUS_FURY, + CRUSADER_AURA, + CRUSADER_STRIKE, + AVENGING_WRATH, + DIVINE_STORM, + JUDGEMENT_OF_JUSTICE; + + // Holy + uint32 FLASH_OF_LIGHT, + HOLY_LIGHT, + DIVINE_SHIELD, + HAMMER_OF_WRATH, + CONSECRATION, + CONCENTRATION_AURA, + DIVINE_FAVOR, + SACRED_SHIELD, + HOLY_SHOCK, + HOLY_WRATH, + LAY_ON_HANDS, + EXORCISM, + REDEMPTION, + DIVINE_PLEA; + + // Protection + uint32 GREATER_BLESSING_OF_KINGS, + BLESSING_OF_KINGS, + HAND_OF_PROTECTION, + SHADOW_RESISTANCE_AURA, + DEVOTION_AURA, + FIRE_RESISTANCE_AURA, + FROST_RESISTANCE_AURA, + DEFENSIVE_STANCE, + BERSERKER_STANCE, + BATTLE_STANCE, + DIVINE_SACRIFICE, + DIVINE_PROTECTION, + DIVINE_INTERVENTION, + HOLY_SHIELD, + AVENGERS_SHIELD, + RIGHTEOUS_DEFENSE, + BLESSING_OF_SANCTUARY, + GREATER_BLESSING_OF_SANCTUARY, + HAND_OF_SACRIFICE, + SHIELD_OF_RIGHTEOUSNESS; + + // cannot be protected + uint32 FORBEARANCE; + + // 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 SpellSequence, CombatCounter, HealCounter; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotPriestAI.cpp b/src/game/playerbot/PlayerbotPriestAI.cpp new file mode 100644 index 000000000..e2a7aac45 --- /dev/null +++ b/src/game/playerbot/PlayerbotPriestAI.cpp @@ -0,0 +1,478 @@ + +#include "PlayerbotPriestAI.h" + +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); + + // 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); + + // 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 + + // 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 +} + +PlayerbotPriestAI::~PlayerbotPriestAI() {} + +bool PlayerbotPriestAI::HealTarget(Unit* target) +{ + PlayerbotAI* ai = GetAI(); + uint8 hp = target->GetHealth() * 100 / target->GetMaxHealth(); + + 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 && 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; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_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() < 20 && ai->CastSpell(GREATER_HEAL)) || + ai->CastSpell(SMITE); + return; + } + + // ------- Non Duel combat ---------- + + ai->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, GetMaster()); // dont want to melee mob + + Player *m_bot = GetPlayerBot(); + Group *m_group = m_bot->GetGroup(); + + // 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) + { + ai->TellMaster("I'm casting desperate prayer."); + ai->CastSpell(DESPERATE_PRAYER, *m_bot); + } + else if (ai->GetHealthPercent() < 80) + HealTarget (m_bot); + + // Heal master + uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); + if (GetMaster()->isAlive()) + { + 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()); + } + + // Heal group + if (m_group) + { + 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; + + uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); + if (memberHP < 25) + HealTarget(m_groupMember); + } + } + + // Damage Spells + ai->SetInFront(pTarget); + float dist = m_bot->GetDistance(pTarget); + + switch (SpellSequence) + { + case SPELL_HOLY: + if (SMITE > 0 && 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 && 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 && LastSpellHoly < 3 && dist <= ATTACK_DISTANCE && 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 && 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 && 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) + { + LastSpellHoly = 0; + SpellSequence = SPELL_SHADOWMAGIC; + break; + } + LastSpellHoly = LastSpellHoly + 1; + //SpellSequence = SPELL_SHADOWMAGIC; + //break; + + case SPELL_SHADOWMAGIC: + if (SHADOW_WORD_PAIN > 0 && 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 && 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 && LastSpellShadowMagic < 3 && ai->GetAttackerCount() >= 3 && ai->GetManaPercent() >= 15) + { + ai->TellMaster("I'm casting scream."); + ai->CastSpell(SCREAM); + SpellSequence = SPELL_DISCIPLINE; + (LastSpellShadowMagic = LastSpellShadowMagic + 1); + break; + } + + 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 && 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 && 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 && LastSpellShadowMagic < 8) + { + ai->CastSpell(SHADOWFIEND); + SpellSequence = SPELL_DISCIPLINE; + LastSpellShadowMagic = LastSpellShadowMagic + 1; + break; + } + else if (MIND_SEAR > 0 && 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; + + case SPELL_DISCIPLINE: + if (FEAR_WARD > 0 && LastSpellDiscipline < 1 && ai->GetManaPercent() >= 3) + { + //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 && 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; + } + else + { + LastSpellDiscipline = LastSpellDiscipline + 1; + SpellSequence = SPELL_HOLY; + } + } +} // end DoNextCombatManeuver + +void PlayerbotPriestAI::DoNonCombatActions() +{ + PlayerbotAI* ai = GetAI(); + Player * m_bot = GetPlayerBot(); + Player * master = GetMaster(); + if (!m_bot || !master) + return; + + SpellSequence = SPELL_HOLY; + + // selfbuff goes first + if (ai->SelfBuff(INNER_FIRE)) + return; + + // 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 != NULL && ai->GetManaPercent() < 30) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + + // hp check + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + + // buff and heal master's group + if (master->GetGroup()) + { + // Buff master with group buffs + if (master->isAlive()) + { + if (PRAYER_OF_FORTITUDE && ai->HasSpellReagents(PRAYER_OF_FORTITUDE) && ai->Buff(PRAYER_OF_FORTITUDE, master)) + return; + + if (PRAYER_OF_SPIRIT && ai->HasSpellReagents(PRAYER_OF_SPIRIT) && ai->Buff(PRAYER_OF_SPIRIT, master)) + return; + } + + 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; + + // 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 (HealTarget(tPlayer)) + return; + } + } + } + else + { + if (master->isAlive()) + { + if (BuffPlayer(master)) + return; + if (HealTarget(master)) + return; + } + else + if (ai->CastSpell(RESURRECTION, *master)) + ai->TellMaster("Resurrecting you, Master."); + } + + BuffPlayer(m_bot); +} // end DoNonCombatActions + +bool PlayerbotPriestAI::BuffPlayer(Player* target) +{ + PlayerbotAI * ai = GetAI(); + Pet * pet = target->GetPet(); + + if (pet && ai->Buff(POWER_WORD_FORTITUDE, pet)) + return true; + + if (ai->Buff(POWER_WORD_FORTITUDE, target)) + return true; + + if ((target->getClass() == CLASS_DRUID || target->getPowerType() == POWER_MANA) && ai->Buff(DIVINE_SPIRIT, target)) + return true; + + return false; +} diff --git a/src/game/playerbot/PlayerbotPriestAI.h b/src/game/playerbot/PlayerbotPriestAI.h new file mode 100644 index 000000000..67df6a67f --- /dev/null +++ b/src/game/playerbot/PlayerbotPriestAI.h @@ -0,0 +1,155 @@ +#ifndef _PLAYERBOTPRIESTAI_H +#define _PLAYERBOTPRIESTAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_HOLY, + SPELL_SHADOWMAGIC, + SPELL_DISCIPLINE +}; + +enum PriestSpells +{ + ABOLISH_DISEASE_1 = 552, + BINDING_HEAL_1 = 32546, + BLESSED_HEALING_1 = 70772, + CIRCLE_OF_HEALING_1 = 34861, + CURE_DISEASE_1 = 528, + DESPERATE_PRAYER_1 = 19236, + DEVOURING_PLAGUE_1 = 2944, + DISPEL_MAGIC_1 = 527, + DISPERSION_1 = 47585, + DIVINE_HYMN_1 = 64843, + DIVINE_SPIRIT_1 = 14752, + FADE_1 = 586, + FEAR_WARD_1 = 6346, + FLASH_HEAL_1 = 2061, + GREATER_HEAL_1 = 2060, + GUARDIAN_SPIRIT_1 = 47788, + HEAL_1 = 2054, + HOLY_FIRE_1 = 14914, + HOLY_NOVA_1 = 15237, + HYMN_OF_HOPE_1 = 64901, + INNER_FIRE_1 = 588, + INNER_FOCUS_1 = 14751, + LESSER_HEAL_1 = 2050, + LEVITATE_1 = 1706, + LIGHTWELL_1 = 724, + MANA_BURN_1 = 8129, + MASS_DISPEL_1 = 32375, + MIND_BLAST_1 = 8092, + MIND_CONTROL_1 = 605, + MIND_FLAY_1 = 15407, + MIND_SEAR_1 = 48045, + MIND_SOOTHE_1 = 453, + MIND_VISION_1 = 2096, + PAIN_SUPPRESSION_1 = 33206, + PENANCE_1 = 47540, + POWER_INFUSION_1 = 10060, + POWER_WORD_FORTITUDE_1 = 1243, + POWER_WORD_SHIELD_1 = 17, + PRAYER_OF_FORTITUDE_1 = 21562, + PRAYER_OF_HEALING_1 = 596, + PRAYER_OF_MENDING_1 = 33076, + PRAYER_OF_SHADOW_PROTECTION_1 = 27683, + PRAYER_OF_SPIRIT_1 = 27681, + PSYCHIC_HORROR_1 = 64044, + PSYCHIC_SCREAM_1 = 8122, + RENEW_1 = 139, + RESURRECTION_1 = 2006, + SHACKLE_UNDEAD_1 = 9484, + SHADOW_PROTECTION_1 = 976, + SHADOW_WORD_DEATH_1 = 32379, + SHADOW_WORD_PAIN_1 = 589, + SHADOWFIEND_1 = 34433, + SHADOWFORM_1 = 15473, + SILENCE_1 = 15487, + SMITE_1 = 585, + VAMPIRIC_EMBRACE_1 = 15286, + VAMPIRIC_TOUCH_1 = 34914 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotPriestAI : PlayerbotClassAI +{ +public: + PlayerbotPriestAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotPriestAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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: + // Heals the target based off its hps + bool HealTarget (Unit* target); + + // holy + uint32 BINDING_HEAL, + CIRCLE_OF_HEALING, + CLEARCASTING, + DESPERATE_PRAYER, + FLASH_HEAL, + GREATER_HEAL, + HEAL, + HOLY_FIRE, + HOLY_NOVA, + LESSER_HEAL, + MANA_BURN, + PRAYER_OF_HEALING, + PRAYER_OF_MENDING, + RENEW, + RESURRECTION, + SMITE; + + // shadowmagic + uint32 FADE, + SHADOW_WORD_PAIN, + MIND_BLAST, + SCREAM, + MIND_FLAY, + DEVOURING_PLAGUE, + SHADOW_PROTECTION, + VAMPIRIC_TOUCH, + PRAYER_OF_SHADOW_PROTECTION, + SHADOWFIEND, + MIND_SEAR; + + // discipline + uint32 POWER_WORD_SHIELD, + INNER_FIRE, + POWER_WORD_FORTITUDE, + PRAYER_OF_FORTITUDE, + FEAR_WARD, + POWER_INFUSION, + MASS_DISPEL, + PENANCE, + DIVINE_SPIRIT, + PRAYER_OF_SPIRIT, + INNER_FOCUS; + + // first aid + uint32 RECENTLY_BANDAGED; + + // racial + uint32 ARCANE_TORRENT, + GIFT_OF_THE_NAARU, + STONEFORM, + ESCAPE_ARTIST, + EVERY_MAN_FOR_HIMSELF, + SHADOWMELD, + WAR_STOMP, + BERSERKING, + WILL_OF_THE_FORSAKEN; + + uint32 SpellSequence, LastSpellHoly, LastSpellShadowMagic, LastSpellDiscipline; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotRogueAI.cpp b/src/game/playerbot/PlayerbotRogueAI.cpp new file mode 100644 index 000000000..5d7b69f31 --- /dev/null +++ b/src/game/playerbot/PlayerbotRogueAI.cpp @@ -0,0 +1,342 @@ +/* + Name : PlayerbotRogueAI.cpp + Complete: maybe around 28% + Author : Natsukawa + Version : 0.37 + */ +#include "PlayerbotRogueAI.h" +#include "PlayerbotMgr.h" + +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); + + 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 +} + +PlayerbotRogueAI::~PlayerbotRogueAI() {} + +bool PlayerbotRogueAI::DoFirstCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + Player * m_bot = GetPlayerBot(); + + if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && ai->CastSpell(STEALTH, *m_bot)) + { + + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("First > Stealth (%d)", STEALTH); + + m_bot->addUnitState(UNIT_STAT_CHASE); // ensure that the bot does not use MoveChase(), as this doesn't seem to work with STEALTH + + return true; + } + else if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0)) + { + m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); + return false; + } + return false; +} + +void PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget) + return; + + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + if (SINISTER_STRIKE > 0) + ai->CastSpell(SINISTER_STRIKE); + return; + } + + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + float fTargetDist = m_bot->GetDistance(pTarget); + + // TODO: make this work better... + /*if (pVictim) + { + if( pVictim!=m_bot && !m_bot->hasUnitState(UNIT_STAT_FOLLOW) && !pTarget->isInBackInMap(m_bot,10) ) { + GetAI()->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_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. + m_bot->AttackStop(); + m_bot->RemoveAllAttackers(); + ai->CastSpell(VANISH); + // m_bot->RemoveAllSpellCooldown(); + GetAI()->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)) + { + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("CoS!"); + return; + } + else 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) + SpellSequence = RogueThreat; + else + SpellSequence = RogueCombat; + + // we fight in melee, target is not in range, skip the next part! + if (fTargetDist > ATTACK_DISTANCE) + return; + + std::ostringstream out; + switch (SpellSequence) + { + case RogueStealth: + out << "Case Stealth"; + if (PICK_POCKET > 0 && ai->CastSpell(PICK_POCKET, *pTarget) && ai->PickPocket(pTarget)) + out << "First > Pick Pocket"; // Should never display, as PickPocket will always return false + 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; + 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!"; + 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; + 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_PLAYER && 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 (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 (EXPOSE_ARMOR > 0 && pTarget->getClass() == CLASS_DEATH_KNIGHT && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 25 && ai->CastSpell(EXPOSE_ARMOR, *pTarget)) + out << " > DK 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!"; + } + break; + } + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster(out.str().c_str()); +} + +// end DoNextCombatManeuver + +void PlayerbotRogueAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + if (!ai) + return; + + Player * m_bot = GetPlayerBot(); + 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 != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + + // Search and apply poisons to weapons + // Mainhand ... + Item * poison, * 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); + if (poison) + { + ai->UseItem(poison, EQUIPMENT_SLOT_MAINHAND); + 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); + if (poison) + { + ai->UseItem(poison, EQUIPMENT_SLOT_OFFHAND); + ai->SetIgnoreUpdateTime(5); + } + } + +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotRogueAI.h b/src/game/playerbot/PlayerbotRogueAI.h new file mode 100644 index 000000000..19bc7ea92 --- /dev/null +++ b/src/game/playerbot/PlayerbotRogueAI.h @@ -0,0 +1,102 @@ + +#ifndef _PlayerbotRogueAI_H +#define _PlayerbotRogueAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + RogueCombat, + RogueSpellPreventing, + RogueThreat, + RogueStealth +}; + +enum RoguePoisonDisplayId +{ + DEADLY_POISON_DISPLAYID = 13707, + INSTANT_POISON_DISPLAYID = 13710, + WOUND_POISON_DISPLAYID = 37278 +}; + +enum RogueSpells +{ + ADRENALINE_RUSH_1 = 13750, + AMBUSH_1 = 8676, + BACKSTAB_1 = 53, + BLADE_FLURRY_1 = 13877, + BLIND_1 = 2094, + CHEAP_SHOT_1 = 1833, + CLOAK_OF_SHADOWS_1 = 31224, + COLD_BLOOD_1 = 14177, + DEADLY_THROW_1 = 26679, + DISARM_TRAP_1 = 1842, + DISMANTLE_1 = 51722, + DISTRACT_1 = 1725, + ENVENOM_1 = 32645, + EVASION_1 = 5277, + EVISCERATE_1 = 2098, + EXPOSE_ARMOR_1 = 8647, + FAN_OF_KNIVES_1 = 51723, + FEINT_1 = 1966, + GARROTE_1 = 703, + GHOSTLY_STRIKE_1 = 14278, + GOUGE_1 = 1776, + HEMORRHAGE_1 = 16511, + HUNGER_FOR_BLOOD_1 = 51662, + KICK_1 = 1766, + KIDNEY_SHOT_1 = 408, + KILLING_SPREE_1 = 51690, + MUTILATE_1 = 1329, + PICK_LOCK_1 = 1804, + PICK_POCKET_1 = 921, + PREMEDITATION_1 = 14183, + PREPARATION_1 = 14185, + RIPOSTE_1 = 14251, + RUPTURE_1 = 1943, + SAP_1 = 6770, + SHADOW_DANCE_1 = 51713, + SHADOWSTEP_1 = 36554, + SHIV_1 = 5938, + SINISTER_STRIKE_1 = 1752, + SLICE_AND_DICE_1 = 5171, + SPRINT_1 = 2983, + STEALTH_1 = 1784, + TRICKS_OF_THE_TRADE_1 = 57934, + VANISH_1 = 1856 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotRogueAI : PlayerbotClassAI +{ +public: + PlayerbotRogueAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotRogueAI(); + + // all combat actions go here + bool DoFirstCombatManeuver(Unit*); + void DoNextCombatManeuver(Unit*); + + // all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + +private: + // COMBAT + uint32 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; + + // ASSASSINATION + uint32 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 SpellSequence, LastSpellCombat, LastSpellSubtlety, LastSpellAssassination, Aura; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotShamanAI.cpp b/src/game/playerbot/PlayerbotShamanAI.cpp new file mode 100644 index 000000000..9b12907e7 --- /dev/null +++ b/src/game/playerbot/PlayerbotShamanAI.cpp @@ -0,0 +1,522 @@ + +#include "PlayerbotShamanAI.h" + +class PlayerbotAI; +PlayerbotShamanAI::PlayerbotShamanAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai) +{ + // restoration + CHAIN_HEAL = ai->initSpell(CHAIN_HEAL_1); + HEALING_WAVE = ai->initSpell(HEALING_WAVE_1); + LESSER_HEALING_WAVE = ai->initSpell(LESSER_HEALING_WAVE_1); + RIPTIDE = ai->initSpell(RIPTIDE_1); + ANCESTRAL_SPIRIT = ai->initSpell(ANCESTRAL_SPIRIT_1); + EARTH_SHIELD = ai->initSpell(EARTH_SHIELD_1); + WATER_SHIELD = ai->initSpell(WATER_SHIELD_1); + EARTHLIVING_WEAPON = ai->initSpell(EARTHLIVING_WEAPON_1); + TREMOR_TOTEM = ai->initSpell(TREMOR_TOTEM_1); // totems + HEALING_STREAM_TOTEM = ai->initSpell(HEALING_STREAM_TOTEM_1); + MANA_SPRING_TOTEM = ai->initSpell(MANA_SPRING_TOTEM_1); + MANA_TIDE_TOTEM = ai->initSpell(MANA_TIDE_TOTEM_1); + // 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); + // 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); + 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); + 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); + + 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 +} + +PlayerbotShamanAI::~PlayerbotShamanAI() {} + +void PlayerbotShamanAI::HealTarget(Unit &target, uint8 hp) +{ + 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); + // end HealTarget +} + +void PlayerbotShamanAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + ai->CastSpell(LIGHTNING_BOLT); + return; + } + + // ------- Non Duel combat ---------- + + ai->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW, GetMaster()); // dont want to melee mob <----changed + + Player *m_bot = GetPlayerBot(); + Group *m_group = m_bot->GetGroup(); + + // 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()); + + // Heal master + uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth(); + if (GetMaster()->isAlive()) + { + if (masterHP < 30 && ai->GetManaPercent() >= 32) + ai->CastSpell(HEALING_WAVE, *(GetMaster())); + else if (masterHP < 70) + HealTarget (*GetMaster(), masterHP); + } + + // Heal group + if (m_group) + { + 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; + + uint32 memberHP = m_groupMember->GetHealth() * 100 / m_groupMember->GetMaxHealth(); + if (memberHP < 30) + HealTarget(*m_groupMember, memberHP); + } + } + + // Damage Spells + ai->SetInFront(pTarget); + + switch (SpellSequence) + { + 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; + } + } +} // end DoNextCombatManeuver + +void PlayerbotShamanAI::DoNonCombatActions() +{ + PlayerbotAI* ai = GetAI(); + Player * m_bot = GetPlayerBot(); + if (!m_bot) + return; + + SpellSequence = SPELL_ENHANCEMENT; + + // buff master with EARTH_SHIELD + if (EARTH_SHIELD > 0) + (!GetMaster()->HasAura(EARTH_SHIELD, EFFECT_INDEX_0) && ai->CastSpell(EARTH_SHIELD, *(GetMaster()))); + + // 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)); +/* + // 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) ); + 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) ); + 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) ); + 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) ); + 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) ); + */ + // 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 != NULL && ai->GetManaPercent() < 30) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + + // hp check + if (m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + pItem = ai->FindFood(); + + if (pItem != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + + // 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())); + } + } +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotShamanAI.h b/src/game/playerbot/PlayerbotShamanAI.h new file mode 100644 index 000000000..8a87dd128 --- /dev/null +++ b/src/game/playerbot/PlayerbotShamanAI.h @@ -0,0 +1,118 @@ +#ifndef _PLAYERBOTSHAMANAI_H +#define _PLAYERBOTSHAMANAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_ENHANCEMENT, + SPELL_RESTORATION, + SPELL_ELEMENTAL +}; + +enum +{ + ANCESTRAL_SPIRIT_1 = 2008, + ASTRAL_RECALL_1 = 556, + BLOODLUST_1 = 2825, + CALL_OF_THE_ANCESTORS_1 = 66843, + CALL_OF_THE_ELEMENTS_1 = 66842, + CALL_OF_THE_SPIRITS_1 = 66844, + CHAIN_HEAL_1 = 1064, + CHAIN_LIGHTNING_1 = 421, + CHAINED_HEAL_1 = 70809, + CLEANSE_SPIRIT_1 = 51886, + CLEANSING_TOTEM_1 = 8170, + CURE_TOXINS_1 = 526, + EARTH_ELEMENTAL_TOTEM_1 = 2062, + EARTH_SHIELD_1 = 974, + EARTH_SHOCK_1 = 8042, + EARTHBIND_TOTEM_1 = 2484, + EARTHLIVING_WEAPON_1 = 51730, + ELEMENTAL_MASTERY_1 = 16166, + FERAL_SPIRIT_1 = 51533, + FIRE_ELEMENTAL_TOTEM_1 = 2894, + FIRE_NOVA_1 = 1535, + FIRE_RESISTANCE_TOTEM_1 = 8184, + FLAME_SHOCK_1 = 8050, + FLAMETONGUE_TOTEM_1 = 8227, + FLAMETONGUE_WEAPON_1 = 8024, + FROST_RESISTANCE_TOTEM_1 = 8181, + FROST_SHOCK_1 = 8056, + FROSTBRAND_WEAPON_1 = 8033, + GHOST_WOLF_1 = 2645, + GROUNDING_TOTEM_1 = 8177, + HEALING_STREAM_TOTEM_1 = 5394, + HEALING_WAVE_1 = 331, + HEROISM_1 = 32182, + HEX_1 = 51514, + LAVA_BURST_1 = 51505, + LAVA_LASH_1 = 60103, + LESSER_HEALING_WAVE_1 = 8004, + LIGHTNING_BOLT_1 = 403, + LIGHTNING_SHIELD_1 = 324, + MAGMA_TOTEM_1 = 8190, + MANA_SPRING_TOTEM_1 = 5675, + MANA_TIDE_TOTEM_1 = 16190, + NATURE_RESISTANCE_TOTEM_1 = 10595, + NATURES_SWIFTNESS_SHAMAN_1 = 16188, + PURGE_1 = 370, + RIPTIDE_1 = 61295, + ROCKBITER_WEAPON_1 = 8017, + SEARING_TOTEM_1 = 3599, + SENTRY_TOTEM_1 = 6495, + SHAMANISTIC_RAGE_1 = 30823, + STONECLAW_TOTEM_1 = 5730, + STONESKIN_TOTEM_1 = 8071, + STORMSTRIKE_1 = 17364, + STRENGTH_OF_EARTH_TOTEM_1 = 8075, + THUNDERSTORM_1 = 51490, + TIDAL_FORCE_1 = 55198, + TOTEM_OF_WRATH_1 = 30706, + TOTEMIC_RECALL_1 = 36936, + TREMOR_TOTEM_1 = 8143, + WATER_BREATHING_1 = 131, + WATER_SHIELD_1 = 52127, + WATER_WALKING_1 = 546, + WIND_SHEAR_1 = 57994, + WINDFURY_TOTEM_1 = 8512, + WINDFURY_WEAPON_1 = 8232, + WRATH_OF_AIR_TOTEM_1 = 3738 +}; +//class Player; + +class MANGOS_DLL_SPEC PlayerbotShamanAI : PlayerbotClassAI +{ +public: + PlayerbotShamanAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotShamanAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + +private: + // Heals the target based off its hps + void HealTarget (Unit& target, uint8 hp); + + // 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; + + // 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; + + // 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; + + // 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 SpellSequence, LastSpellEnhancement, LastSpellRestoration, LastSpellElemental; +}; + +#endif diff --git a/src/game/playerbot/PlayerbotWarlockAI.cpp b/src/game/playerbot/PlayerbotWarlockAI.cpp new file mode 100644 index 000000000..a1cb8a9ef --- /dev/null +++ b/src/game/playerbot/PlayerbotWarlockAI.cpp @@ -0,0 +1,575 @@ + +#include "PlayerbotWarlockAI.h" + +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); + // 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); + // 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); + // 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); + 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); + // 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); + // demon skills should be initialized on demons + BLOOD_PACT = 0; // imp skill + CONSUME_SHADOWS = 0; // voidwalker skill + FEL_INTELLIGENCE = 0; // felhunter skill + + 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; +} + +PlayerbotWarlockAI::~PlayerbotWarlockAI() {} + +void PlayerbotWarlockAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + if (SHADOW_BOLT > 0) + ai->CastSpell(SHADOW_BOLT); + return; + } + + // ------- Non Duel combat ---------- + + //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob + + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + Pet *pet = m_bot->GetPet(); + + // Empower demon + if (pet && DEMONIC_EMPOWERMENT && !m_bot->HasSpellCooldown(DEMONIC_EMPOWERMENT)) + ai->CastSpell(DEMONIC_EMPOWERMENT); + + // 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); + } + + // Use healthstone + if (ai->GetHealthPercent() < 30) + { + Item* healthStone = ai->FindConsumable(HEALTHSTONE_DISPLAYID); + if (healthStone) + ai->UseItem(healthStone); + } + + // Damage Spells + switch (SpellSequence) + { + 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; + 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; + 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; + 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) + { + ai->CastSpell(CHAOS_BOLT, *pTarget); + SpellSequence = SPELL_CURSES; + ++LastSpellDestruction; + break; + } + else if (SHADOWBURN && LastSpellDestruction < 11 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && !pTarget->HasAura(SHADOWBURN)) + { + ai->CastSpell(SHADOWBURN, *pTarget); + SpellSequence = SPELL_CURSES; + ++LastSpellDestruction; + break; + } + else if (HELLFIRE && LastSpellDestruction < 12 && !m_bot->HasAura(HELLFIRE) && ai->GetAttackerCount() >= 5 && ai->GetHealthPercent() >= 50) + { + ai->CastSpell(HELLFIRE); + ai->TellMaster("casting hellfire!"); + //ai->SetIgnoreUpdateTime(15); + SpellSequence = SPELL_CURSES; + ++LastSpellDestruction; + break; + } + else + { + LastSpellDestruction = 0; + SpellSequence = SPELL_CURSES; + } + } +} // end DoNextCombatManeuver + +void PlayerbotWarlockAI::DoNonCombatActions() +{ + SpellSequence = SPELL_CURSES; + + PlayerbotAI *ai = GetAI(); + Player * m_bot = GetPlayerBot(); + if (!ai || !m_bot) + return; + + Pet *pet = m_bot->GetPet(); + + // Initialize pet spells + if (pet && pet->GetEntry() != m_lastDemon) + { + 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); + 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); + 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); + 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); + 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, NULL); + uint8 freeSpace = 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) + { + if (ai->SelfBuff(FEL_ARMOR)) + return; + } + else if (DEMON_ARMOR) + { + if (ai->SelfBuff(DEMON_ARMOR)) + return; + } + else if (DEMON_SKIN) + { + if (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)) + return; + } + + // soulstone creation and use + if (CREATE_SOULSTONE) + { + Item* soulStone = ai->FindConsumable(SOULSTONE_DISPLAYID); + if (!soulStone) + { + if (shardCount > 0 && !m_bot->HasSpellCooldown(CREATE_SOULSTONE) && ai->CastSpell(CREATE_SOULSTONE)) + return; + } + else + { + uint32 soulStoneSpell = soulStone->GetProto()->Spells[0].SpellId; + Player * master = GetMaster(); + if (!master->HasAura(soulStoneSpell) && !m_bot->HasSpellCooldown(soulStoneSpell)) + { + 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)) + return; + } + + if (LIFE_TAP && ai->GetManaPercent() <= 50 && ai->GetHealthPercent() > 50) + { + if (ai->CastSpell(LIFE_TAP, *m_bot)) + return; + } + + if (ai->GetManaPercent() < 25) + { + Item* pItem = ai->FindDrink(); + if (pItem) + { + ai->TellMaster("I could use a drink."); + ai->UseItem(pItem); + return; + } + } + + // hp check + if (ai->GetHealthPercent() < 30) + { + Item* pItem = ai->FindFood(); + if (pItem) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + } + + if (ai->GetHealthPercent() < 50 && !m_bot->HasAura(RECENTLY_BANDAGED)) + { + Item* fItem = ai->FindBandage(); + if (fItem) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + } + + //Heal Voidwalker + if (pet && pet->GetEntry() == DEMON_VOIDWALKER && CONSUME_SHADOWS && pet->GetHealthPercent() < 75 && !pet->HasAura(CONSUME_SHADOWS)) + 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; + } + } + } + + // Soul link demon + if (pet && SOUL_LINK && !m_bot->HasAura(SOUL_LINK_AURA) && 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)) + return; + + if (pet && pet->GetEntry() == DEMON_FELHUNTER && FEL_INTELLIGENCE && !m_bot->HasAura(FEL_INTELLIGENCE) && ai->CastPetSpell(FEL_INTELLIGENCE)) + return; + +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotWarlockAI.h b/src/game/playerbot/PlayerbotWarlockAI.h new file mode 100644 index 000000000..97d1b2c11 --- /dev/null +++ b/src/game/playerbot/PlayerbotWarlockAI.h @@ -0,0 +1,249 @@ +#ifndef _PlayerbotWarlockAI_H +#define _PlayerbotWarlockAI_H + +#include "PlayerbotClassAI.h" + +#define SOUL_SHARD 6265 +#define MAX_SHARD_COUNT 4 // Maximum soul shard count bot should keep + +enum +{ + SPELL_CURSES, + SPELL_AFFLICTION, + SPELL_DESTRUCTION, + SPELL_DEMONOLOGY +}; + +enum StoneDisplayId +{ + FIRESTONE_DISPLAYID = 7409, + SPELLSTONE_DISPLAYID = 13291, + SOULSTONE_DISPLAYID = 6009, + HEALTHSTONE_DISPLAYID = 8026 +}; + +enum DemonEntry +{ + DEMON_IMP = 416, + DEMON_VOIDWALKER = 1860, + DEMON_SUCCUBUS = 1863, + DEMON_FELHUNTER = 417, + DEMON_FELGUARD = 17252 +}; + +enum DemonSpellIconIds +{ + // Imp + BLOOD_PACT_ICON = 541, + FIREBOLT_ICON = 18, + FIRE_SHIELD_ICON = 16, + // Felguard + ANGUISH_ICON = 173, + CLEAVE_ICON = 277, + INTERCEPT_ICON = 516, + // Felhunter + DEVOUR_MAGIC_ICON = 47, + FEL_INTELLIGENCE_ICON = 1940, + SHADOW_BITE_ICON = 2027, + SPELL_LOCK_ICON = 77, + // Succubus + LASH_OF_PAIN_ICON = 596, + SEDUCTION_ICON = 48, + SOOTHING_KISS_ICON = 694, + // Voidwalker + CONSUME_SHADOWS_ICON = 207, + SACRIFICE_ICON = 693, + SUFFERING_ICON = 9, + TORMENT_ICON = 173 +}; + +enum WarlockSpells +{ + BANISH_1 = 710, + CHALLENGING_HOWL_1 = 59671, + CHAOS_BOLT_1 = 50796, + CONFLAGRATE_1 = 17962, + CORRUPTION_1 = 172, + CREATE_FIRESTONE_1 = 6366, + CREATE_HEALTHSTONE_1 = 6201, + CREATE_SOULSTONE_1 = 693, + CREATE_SPELLSTONE_1 = 2362, + CURSE_OF_AGONY_1 = 980, + CURSE_OF_DOOM_1 = 603, + CURSE_OF_EXHAUSTION_1 = 18223, + CURSE_OF_THE_ELEMENTS_1 = 1490, + CURSE_OF_TONGUES_1 = 1714, + CURSE_OF_WEAKNESS_1 = 702, + DARK_PACT_1 = 18220, + DEATH_COIL_WARLOCK_1 = 6789, + DEMON_ARMOR_1 = 706, + DEMON_CHARGE_1 = 54785, + DEMON_SKIN_1 = 687, + DEMONIC_CIRCLE_SUMMON_1 = 48018, + DEMONIC_CIRCLE_TELEPORT_1 = 48020, + DEMONIC_EMPOWERMENT_1 = 47193, + DEMONIC_IMMOLATE_1 = 75445, + DETECT_INVISIBILITY_1 = 132, + DRAIN_LIFE_1 = 689, + DRAIN_MANA_1 = 5138, + DRAIN_SOUL_1 = 1120, + ENSLAVE_DEMON_1 = 1098, + EYE_OF_KILROGG_1 = 126, + FEAR_1 = 5782, + FEL_ARMOR_1 = 28176, + FEL_DOMINATION_1 = 18708, + HAUNT_1 = 48181, + HEALTH_FUNNEL_1 = 755, + HELLFIRE_1 = 1949, + HOWL_OF_TERROR_1 = 5484, + IMMOLATE_1 = 348, + IMMOLATION_AURA_1 = 50589, + INCINERATE_1 = 29722, + INFERNO_1 = 1122, + LIFE_TAP_1 = 1454, + METAMORPHOSIS_1 = 59672, + RAIN_OF_FIRE_1 = 5740, + RITUAL_OF_DOOM_1 = 18540, + RITUAL_OF_SOULS_1 = 29893, + RITUAL_OF_SUMMONING_1 = 698, + SEARING_PAIN_1 = 5676, + SEED_OF_CORRUPTION_1 = 27243, + SENSE_DEMONS_1 = 5500, + SHADOW_BOLT_1 = 686, + SHADOW_CLEAVE_1 = 50581, + SHADOW_WARD_1 = 6229, + SHADOWBURN_1 = 17877, + SHADOWFLAME_1 = 47897, + SHADOWFURY_1 = 30283, + SOUL_FIRE_1 = 6353, + SOUL_LINK_1 = 19028, + SOULSHATTER_1 = 29858, + SUMMON_FELGUARD_1 = 30146, + SUMMON_FELHUNTER_1 = 691, + SUMMON_IMP_1 = 688, + SUMMON_SUCCUBUS_1 = 712, + SUMMON_VOIDWALKER_1 = 697, + UNENDING_BREATH_1 = 5697, + UNSTABLE_AFFLICTION_1 = 30108 +}; + +//class Player; +class MANGOS_DLL_SPEC PlayerbotWarlockAI : PlayerbotClassAI +{ +public: + PlayerbotWarlockAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotWarlockAI(); + + // all combat actions go here + void DoNextCombatManeuver(Unit*); + + // 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 + //void BuffPlayer(Player *target); + +private: + + // CURSES + uint32 CURSE_OF_WEAKNESS, + CURSE_OF_AGONY, + CURSE_OF_EXHAUSTION, + CURSE_OF_TONGUES, + CURSE_OF_THE_ELEMENTS, + CURSE_OF_DOOM; + + // AFFLICTION + uint32 CORRUPTION, + DRAIN_SOUL, + DRAIN_LIFE, + DRAIN_MANA, + LIFE_TAP, + UNSTABLE_AFFLICTION, + HAUNT, + SEED_OF_CORRUPTION, + DARK_PACT, + HOWL_OF_TERROR, + FEAR; + + // DESTRUCTION + uint32 SHADOW_BOLT, + IMMOLATE, + INCINERATE, + SEARING_PAIN, + CONFLAGRATE, + SOUL_FIRE, + SHADOWFURY, + CHAOS_BOLT, + SHADOWFLAME, + HELLFIRE, + RAIN_OF_FIRE, + SHADOWBURN; + + // DEMONOLOGY + uint32 DEMON_SKIN, + DEMON_ARMOR, + DEMONIC_EMPOWERMENT, + SHADOW_WARD, + FEL_ARMOR, + SOULSHATTER, + SOUL_LINK, + SOUL_LINK_AURA, + HEALTH_FUNNEL, + DETECT_INVISIBILITY, + CREATE_FIRESTONE, + CREATE_SOULSTONE, + CREATE_HEALTHSTONE; + + // DEMON SUMMON + uint32 SUMMON_IMP, + SUMMON_VOIDWALKER, + SUMMON_SUCCUBUS, + SUMMON_FELHUNTER, + SUMMON_FELGUARD; + + // DEMON SKILLS + uint32 BLOOD_PACT, + FIREBOLT, + FIRE_SHIELD, + ANGUISH, + CLEAVE, + INTERCEPT, + DEVOUR_MAGIC, + FEL_INTELLIGENCE, + SHADOW_BITE, + SPELL_LOCK, + LASH_OF_PAIN, + SEDUCTION, + SOOTHING_KISS, + CONSUME_SHADOWS, + SACRIFICE, + SUFFERING, + TORMENT; + + // 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 SpellSequence, + LastSpellCurse, + LastSpellAffliction, + 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. +}; + +#endif diff --git a/src/game/playerbot/PlayerbotWarriorAI.cpp b/src/game/playerbot/PlayerbotWarriorAI.cpp new file mode 100644 index 000000000..7f899fae0 --- /dev/null +++ b/src/game/playerbot/PlayerbotWarriorAI.cpp @@ -0,0 +1,354 @@ +/* + Name : PlayerbotWarriorAI.cpp + Complete: maybe around 37% + Author : Natsukawa + Version : 0.39 + */ +#include "PlayerbotWarriorAI.h" +#include "PlayerbotMgr.h" + +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 + 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 + + 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 +} +PlayerbotWarriorAI::~PlayerbotWarriorAI() {} + +bool PlayerbotWarriorAI::DoFirstCombatManeuver(Unit *pTarget) +{ + Player *m_bot = GetPlayerBot(); + PlayerbotAI *ai = GetAI(); + PlayerbotAI::CombatOrderType co = ai->GetCombatOrder(); + float fTargetDist = m_bot->GetDistance(pTarget); + + if ((co & PlayerbotAI::ORDERS_TANK) && DEFENSIVE_STANCE > 0 && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(DEFENSIVE_STANCE)) + { + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("First > Defensive Stance (%d)", DEFENSIVE_STANCE); + return true; + } + else if ((co & PlayerbotAI::ORDERS_TANK) && TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(TAUNT, *pTarget)) + { + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("First > Taunt (%d)", TAUNT); + return false; + } + else if (BATTLE_STANCE > 0 && !m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_STANCE)) + { + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("First > Battle Stance (%d)", BATTLE_STANCE); + return true; + } + else if (BATTLE_STANCE > 0 && CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + { + if (fTargetDist < 8.0f) + return false; + else if (fTargetDist > 25.0f) + return true; + else if (CHARGE > 0 && ai->CastSpell(CHARGE, *pTarget)) + { + float x, y, z; + pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f); + m_bot->Relocate(x, y, z); + + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("First > Charge (%d)", CHARGE); + return false; + } + } + + return false; +} + +void PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) +{ + PlayerbotAI* ai = GetAI(); + if (!ai) + return; + + switch (ai->GetScenarioType()) + { + case PlayerbotAI::SCENARIO_DUEL: + if (HEROIC_STRIKE > 0) + ai->CastSpell(HEROIC_STRIKE); + return; + } + // ------- Non Duel combat ---------- + + //ai->SetMovementOrder( PlayerbotAI::MOVEMENT_FOLLOW, GetMaster() ); // dont want to melee mob + + // Damage Attacks + + ai->SetInFront(pTarget); + Player *m_bot = GetPlayerBot(); + Unit* pVictim = pTarget->getVictim(); + float fTargetDist = m_bot->GetDistance(pTarget); + PlayerbotAI::CombatOrderType co = ai->GetCombatOrder(); + + // decide what stance to use + if ((co & PlayerbotAI::ORDERS_TANK) && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(DEFENSIVE_STANCE)) + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("Stance > Defensive"); + else if (!(co & PlayerbotAI::ORDERS_TANK) && !m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_STANCE)) + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("Stance > Battle"); + + // get spell sequence + if (pTarget->IsNonMeleeSpellCasted(true)) + SpellSequence = WarriorSpellPreventing; + else if (m_bot->HasAura(BATTLE_STANCE, EFFECT_INDEX_0)) + SpellSequence = WarriorBattle; + else if (m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_INDEX_0)) + SpellSequence = WarriorDefensive; + else if (m_bot->HasAura(BERSERKER_STANCE, EFFECT_INDEX_0)) + SpellSequence = WarriorBerserker; + + // do shouts, berserker rage, etc... + if (BERSERKER_RAGE > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_INDEX_0) && ai->CastSpell(BERSERKER_RAGE)) + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("Pre > Berseker Rage"); + else if (DEMORALIZING_SHOUT > 0 && ai->GetRageAmount() >= 10 && !pTarget->HasAura(DEMORALIZING_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(DEMORALIZING_SHOUT)) + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("Pre > Demoralizing Shout"); + else if (BATTLE_SHOUT > 0 && ai->GetRageAmount() >= 10 && !m_bot->HasAura(BATTLE_SHOUT, EFFECT_INDEX_0) && ai->CastSpell(BATTLE_SHOUT)) + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster("Pre > Battle Shout"); + + std::ostringstream out; + switch (SpellSequence) + { + 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; + + 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_PLAYER && 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_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 if (m_bot->getRace() == RACE_DRAENEI && ai->GetHealthPercent() < 25 && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_INDEX_0) && ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot)) + out << " > Gift of the Naaru"; + else + out << " > NONE"; + break; + + 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; + + case WarriorBerserker: + out << "Case Berserker"; + if (WHIRLWIND > 0 && ai->GetRageAmount() >= 25 && ai->CastSpell(WHIRLWIND, *pTarget)) + out << " > Whirlwind"; + out << " > NONE"; + break; + } + if (ai->GetManager()->m_confDebugWhisper) + ai->TellMaster(out.str().c_str()); +} + +void PlayerbotWarriorAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player * m_bot = GetPlayerBot(); + 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 && !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())); + + // 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 != NULL && ai->GetHealthPercent() < 30) + { + ai->TellMaster("I could use some food."); + ai->UseItem(pItem); + return; + } + else if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(fItem); + return; + } + else if (pItem == NULL && fItem == NULL && m_bot->getRace() == RACE_DRAENEI && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_INDEX_0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I'm casting gift of the naaru."); + ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot); + return; + } +} // end DoNonCombatActions diff --git a/src/game/playerbot/PlayerbotWarriorAI.h b/src/game/playerbot/PlayerbotWarriorAI.h new file mode 100644 index 000000000..24ba6fb6d --- /dev/null +++ b/src/game/playerbot/PlayerbotWarriorAI.h @@ -0,0 +1,103 @@ +#ifndef _PlayerbotWarriorAI_H +#define _PlayerbotWarriorAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + WarriorSpellPreventing, + WarriorBattle, + WarriorDefensive, + WarriorBerserker +}; + +enum WarriorSpells +{ + BATTLE_SHOUT_1 = 6673, + BATTLE_STANCE_1 = 2457, + BERSERKER_RAGE_1 = 18499, + BERSERKER_STANCE_1 = 2458, + BLADESTORM_1 = 46924, + BLOODRAGE_1 = 2687, + BLOODTHIRST_1 = 23881, + CHALLENGING_SHOUT_1 = 1161, + CHARGE_1 = 100, + CLEAVE_1 = 845, + COMMANDING_SHOUT_1 = 469, + CONCUSSION_BLOW_1 = 12809, + DEATH_WISH_1 = 12292, + DEFENSIVE_STANCE_1 = 71, + DEMORALIZING_SHOUT_1 = 1160, + DEVASTATE_1 = 20243, + DISARM_1 = 676, + ENRAGED_REGENERATION_1 = 55694, + EXECUTE_1 = 5308, + HAMSTRING_1 = 1715, + HEROIC_FURY_1 = 60970, + HEROIC_STRIKE_1 = 78, + HEROIC_THROW_1 = 57755, + INTERCEPT_1 = 20252, + INTERVENE_1 = 3411, + INTIMIDATING_SHOUT_1 = 5246, + LAST_STAND_1 = 12975, + MOCKING_BLOW_1 = 694, + MORTAL_STRIKE_1 = 12294, + OVERPOWER_1 = 7384, + PIERCING_HOWL_1 = 12323, + PUMMEL_1 = 6552, + RECKLESSNESS_1 = 1719, + REND_1 = 772, + RETALIATION_1 = 20230, + REVENGE_1 = 6572, + SHATTERING_THROW_1 = 64382, + SHIELD_BASH_1 = 72, + SHIELD_BLOCK_1 = 2565, + SHIELD_SLAM_1 = 23922, + SHIELD_WALL_1 = 871, + SHOCKWAVE_1 = 46968, + SLAM_1 = 1464, + SPELL_REFLECTION_1 = 23920, + SUNDER_ARMOR_1 = 7386, + SWEEPING_STRIKES_1 = 12328, + TAUNT_1 = 355, + THUNDER_CLAP_1 = 6343, + VICTORY_RUSH_1 = 34428, + VIGILANCE_1 = 50720, + WHIRLWIND_1 = 1680 +}; + +//class Player; + +class MANGOS_DLL_SPEC PlayerbotWarriorAI : PlayerbotClassAI +{ +public: + PlayerbotWarriorAI(Player * const master, Player * const bot, PlayerbotAI * const ai); + virtual ~PlayerbotWarriorAI(); + + // all combat actions go here + bool DoFirstCombatManeuver(Unit*); + void DoNextCombatManeuver(Unit*); + + // all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + +private: + // ARMS + 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; + + // 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; + + // 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 SpellSequence; +}; + +#endif diff --git a/src/game/playerbot/config.h b/src/game/playerbot/config.h new file mode 100644 index 000000000..ef68fc704 --- /dev/null +++ b/src/game/playerbot/config.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2009 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PB_CONFIG_H +#define PB_CONFIG_H + +#include "Platform/CompilerDefs.h" + +// Format is YYYYMMDDRR where RR is the change in the conf file +// for that day. +#define PLAYERBOT_CONF_VERSION 2010062001 + +#if PLATFORM == PLATFORM_WINDOWS + #define _PLAYERBOT_CONFIG "playerbot.conf" +#else + #define _PLAYERBOT_CONFIG SYSCONFDIR "playerbot.conf" +#endif + +#endif diff --git a/src/game/playerbot/config.h.in b/src/game/playerbot/config.h.in new file mode 100644 index 000000000..c26ca2d91 --- /dev/null +++ b/src/game/playerbot/config.h.in @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2009 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PB_CONFIG_H +#define PB_CONFIG_H + +#include "Platform/CompilerDefs.h" + +// Format is YYYYMMDDRR where RR is the change in the conf file +// for that day. +#define PLAYERBOT_CONF_VERSION 2010062001 + +#if PLATFORM == PLATFORM_WINDOWS + #define _PLAYERBOT_CONFIG "playerbot.conf" +#else + #define _PLAYERBOT_CONFIG SYSCONFDIR"playerbot.conf" +#endif + +#endif diff --git a/src/game/playerbot/playerbot.conf.dist.in b/src/game/playerbot/playerbot.conf.dist.in new file mode 100644 index 000000000..a6e9bc2eb --- /dev/null +++ b/src/game/playerbot/playerbot.conf.dist.in @@ -0,0 +1,54 @@ +##################################### +# Playerbot Configuration file # +##################################### + +[PlayerbotConf] +ConfVersion=2010062001 + +################################################################################################################### +# PLAYERBOTAI CONFIGURATION +# +# PlayerbotAI.DisableBots +# Disable the bot command and bot menu +# Default: 0 - off +# 1 - on +# +# PlayerbotAI.DebugWhisper +# Enable debug output by whispering master +# Default: 0 - off +# 1 - on +# +# PlayerbotAI.FollowDistanceMin +# PlayerbotAI.FollowDistanceMax +# Min. and Max. follow distance for bots +# Default: 0.5 / 1.0 +# +# PlayerbotAI.MaxNumBots +# Limits the number of bots per account (Max 9) +# Default: 9 +# +# PlayerbotAI.RestrictBotLevel +# Restrict the allowed bot level (Current Max 80) +# Default: 80 +# +# PlayerbotAI.BotguyQuests +# List of Quest ids, any of which, once completed will enable botguy menu on NPCs +# List must be enclosed in double quotes ("") and multiple Quest Ids separated by a delimiter(,) +# Example: "805,383,2160,54,8350,9313,1656,2159" +# Default: "" no quest restriction, memu always displayed by NPCs +# +# PlayerbotAI.BotguyCost +# Cost (Copper coins) levied on summoning a bot +# If player has the cost, botguy menu will be displayed by NPCs +# Default: 0 - no cost, menu always displayed by NPCs +# +################################################################################################################### + +PlayerbotAI.DisableBots = 0 +PlayerbotAI.DebugWhisper = 0 +PlayerbotAI.FollowDistanceMin = 0.5 +PlayerbotAI.FollowDistanceMax = 1.0 +PlayerbotAI.MaxNumBots = 9 +PlayerbotAI.RestrictBotLevel = 80 +PlayerbotAI.BotguyQuests = "" +PlayerbotAI.BotguyCost = 0 diff --git a/src/mangosd/Makefile.am b/src/mangosd/Makefile.am index 2c111f6d2..63ef1d934 100644 --- a/src/mangosd/Makefile.am +++ b/src/mangosd/Makefile.am @@ -45,6 +45,7 @@ mangos_worldd_LDADD = \ ../game/vmap/libmangosvmaps.a \ ../shared/Database/libmangosdatabase.a \ ../shared/Config/libmangosconfig.a \ + ../game/playerbot/libmangosbot.a \ ../shared/Auth/libmangosauth.a \ ../shared/libmangosshared.a \ ../framework/libmangosframework.a \ diff --git a/win/VC100/game.vcxproj b/win/VC100/game.vcxproj index 44bf4a2d7..c8597ac5a 100644 --- a/win/VC100/game.vcxproj +++ b/win/VC100/game.vcxproj @@ -468,19 +468,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -632,20 +632,19 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/win/VC100/game.vcxproj.filters b/win/VC100/game.vcxproj.filters index 8d716161e..b9e34444b 100644 --- a/win/VC100/game.vcxproj.filters +++ b/win/VC100/game.vcxproj.filters @@ -469,48 +469,48 @@ AhBot - - Playerbot - - - Playerbot + + Object - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Object + + World/Handlers + + World/Handlers + vmaps @@ -934,52 +934,49 @@ AhBot - - Playerbot - - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - - Playerbot + + World/Handlers - + World/Handlers - + World/Handlers @@ -1014,4 +1011,4 @@ - \ No newline at end of file +