diff --git a/README.PlayerBot b/README.PlayerBot new file mode 100644 index 000000000..f986ade9b --- /dev/null +++ b/README.PlayerBot @@ -0,0 +1,229 @@ +What it is: +=========== + +Playerbot lets you add another character from your account as a bot that you can control and which will hopefully help you. Only characters from your account can be used, so you can have a maximum of 9 bots at one time. + +This was taken from the Trinity site, and modified slightly by me to get some of the kinks out. I reworked the priest class and also added a mage class and a warrior class, which are still in crude form. Any class can be used as a bot - just don't expect much in the way of spells or abilities until someone writes the code for them. + +Bots will only use abilities that they have - for example, a priest will only use the renew spell if it has been trained. Also, bot's equipment will lose durability like any other character. So every so often you'll need to log in and repair and train your bot. + +For Mangos 7800+ + +Commands: +========= + +/s .bot add BOTNAME (add character to world) +/s .bot remove BOTNAME +/s .bot co|combatorder BOTNAME COMBATORDER [TARGET] +/invite BOTNAME (bot will auto accept invite) +/t BOTNAME attack (bot will attack selected target, similar to the way a pet can attack) +/t BOTNAME follow (orders bot to follow player; will also revive bot if dead or teleport bot if far away) +/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 use +/t BOTNAME equip +/t BOTNAME reset (will reset states, orders and loot list) +/t BOTNAME report (bot reports all items needed to finish quests) +/t BOTNAME stats (bot shows available money, free inventory space and estimated item repair costs) +/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 +e = equip +u = use + +Gameobject interaction with bots: +================================= + + The bot(s) can now interact with gameobjects. This is particularly useful, in order to complete 'gather' type + quests (e.g Milly's harvest in Northshire). The bot(s) can also now, harvest 'ore deposits' and 'herbs' + + Three new commands have been introduced 'survey, 'find' & 'get', to facilitate this new feature. + + The 'survey' command provides the means for bot(s) to detect gameobjects in the world. It can be used to detect + available gameobjects local to a single bot, or more effectively (wider area) those for a party of bots. + + Suggestion: setup the 'survey' command as an assigned macro button, on the client (e.g /p survey). You can + then quickly refresh the gameobject list. + + Gameobject list (Currently bots can only interact with ore, herb and needed quest items) + --------------- + + [Copper Vein][Silverleaf][Earthroot][Milly's Harvest][Battered Chest][Food Crate] + + Then, use the 'find' or 'get' commands to interect with the gameobject. + + Using the gameobject list information, it is possible to locate and/or fetch each of the gameobjects. To select + a , hold down the shift key and click on the relevant link with your mouse. + +Repair with bots: +================= + + The bot(s) can now be repaired, as the player repairs. You can decide whether you wish the bot(s) to pay for + their own repair or if available, use the guild bank. Choose the appropriate repair 'Anvil' at your local + NPC. Only group bot(s) members can be repaired. If you wish to exclude certain bot(s) from repair, then + temporarily uninvite bot(s) from the group. + + Limitations: Bot(s) cannot repair individual items. + If the player does not require repair, you cannot repair bot(s). + + The new 'stats' command provides useful information to help in the repair decision. + + First: Money available to bot(s) + Second: Free inventory slots for bot(s) + Third: Estimated (excludes NPC reputation discount) item damage cost for bot(s). + +Combat Orders explained: +======================== + + There are primary and secondary commands which can be combined. In this way it is + possible to define a bot to assist the main tank and also protect the healer, making + combat management much easier. + The commands assist and protect require a target parameter or a friendly player + selected by bots master. + Available Combat Orders: + tank pri try to bind all targets involved in combat by gaining highest threat + assist pri do damage on selected targets attacker without getting highest threat + heal pri concentrate on healing - no offensive spells, try to keep threat low + protect sec if target of protect get's directly attacked gain higher threat on attacker + reset - clear out assist and protect targets and set combat order to nothing + Examples: + .bot co TheTank tank + .bot co MyHealer heal + .bot co TheBrutal assist TheTank + .bot co TheBrutal protect MyHealer + +Trading with bots: +================== + +To trade items/money with your bot simply initiate a trade and the bot will tell you how much money and items are available. To request an item simple whisper the bot and shift click the link of the item you would like. You can link multiple items on the same line. You can also request money in the following manner when the trade window is open: +/w BOTNAME 10g <-- request that the bot give you 10 gold +/w BOTNAME 6g500s25c <-- request 6 gold, 500 silver, and 25 cooper + +A bot is also able to show an item in its 'Will not be traded' slot. The item can be either +in its bags or be equipped and even be soulbound. By this you can cast spells/enchantments +on soulbound items ('nt' stands for 'not trading'). +/w BOTNAME nt [Powerful Soulbound Item] + +More Information: +================= + +If specifying a spell substring, the spell chosen will be in priority of exact name match, highest spell rank, and spell using no reagents. Case does not matter. Here's some examples: +/t BOTNAME c greater heal +/t BOTNAME cast pain +/w BOTNAME c poly +/w BOTNAME cast fort +/t BOTNAME cast +- OR - +/w BOTNAME c + +Also all commands can be broadcast to the party. For example: +/p follow +/p spells + +To use or equip items for your bot say: +/w BOTNAME use +/w BOTNAME equip +- OR - +/w BOTNAME u +/w BOTNAME e + +If you inspect your bot, your bot will tell you what items you have in your inventory that you can equip. To create a link in the chat window, hold the shift key and press the left mouse button when clicking the link. + + +Changes from Trinity to Mangos: +=============================== + +I added the following in SharedDefines.h. + +enum SpellCategory +{ + SPELL_CATEGORY_FOOD = 11, + SPELL_CATEGORY_DRINK = 59 +}; + +I also had to add the following to Player.h: + +enum PlayerStateType +{ + PLAYER_STATE_NONE = 0, + PLAYER_STATE_SIT = 1 +}; + + +Configuration variables: +======================== +Also see src/mangosd/mangosd.conf.dist for configuration variables! + + 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 + + +Some Problems: +============== + +The bots don't always face in the right direction. Sometimes when a bot makes the kill, the corpse is not lootable. The mage bot sometimes get stuck when he begins to cast a spell (but this is corrected the next time he enters combat). + +-- BotGuy +What it is: +=========== + +The new revised'botguy' utilizes NPCs already distributed throughout the world, to allow players to +summon and dismiss bots at will, from their own account. + +This is a revised (more stable) version that utilizes the new 'GOSSIP MENU SYSTEM' to modify the menus of existing NPCs +(e.g Trainers etc) to include the bot Recruit/Dismiss menu. (No GameMaster account necessary). + +Install (Server administrators only) +======= +Please apply 'mangos_botguy.sql' once to the world database to update the 'gossip_menu_option' table. + +-- ToDo +[DONE] ///---Quest---/// +[DONE] Bot can accept quest. +[DONE] Bot can join quest. + +[DONE] ///---Loot---/// +[DONE] Bot can loot. +[DONE] Bot can loot --- maybe only needed q item. + +//---Instance teleport Problems---/// +[DONE] Needs some fix. + +///---Combat and Movement Orders---/// +[DONE] Movement orders (stay, follow) +[DONE] Combat orders (protect, assist) +Combat order TANK +Combat order HEAL + +///---Temporary item enchantments---/// +[DONE] Rogue : Poison +Warrior : Sharpening Stone, Rune of Warding, Rune of Shielding. + +//---After fear bot's lose target---/// +Needs some fix. + +///---Move behind target---/// +Rogue : some abilitys require Rogue to be stealthed and behind target. + +///---Implement locale independet way of getting spellIDs---/// +[DONE] Hardcode lowest rank spellID, use function to get highest rank diff --git a/addition/744_mangos_botguy.sql b/addition/744_mangos_botguy.sql new file mode 100644 index 000000000..f0380b9f0 --- /dev/null +++ b/addition/744_mangos_botguy.sql @@ -0,0 +1 @@ +INSERT INTO `gossip_menu_option` VALUES('0','16','0','GOSSIP_OPTION_BOT','18','1','0','0','0','0','0',NULL,'0','0','0','0','0','0','0','0','0'); diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index b583e92d6..502aeb195 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -21,6 +21,7 @@ file(GLOB_RECURSE game_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp *.h) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/vmap + ${CMAKE_CURRENT_SOURCE_DIR}/playerbot ${CMAKE_SOURCE_DIR}/dep/include/g3dlite ${CMAKE_SOURCE_DIR}/dep/include ${CMAKE_SOURCE_DIR}/src/shared @@ -106,3 +107,5 @@ if(PCH) endif() endif() endif() + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/playerbot.conf.dist.in DESTINATION ${CONF_DIR} RENAME playerbot.conf.dist) diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp index 07da444e3..8c109beaa 100644 --- a/src/game/CharacterHandler.cpp +++ b/src/game/CharacterHandler.cpp @@ -38,6 +38,9 @@ #include "ArenaTeam.h" #include "Language.h" +// Playerbot mod: +#include "playerbot/PlayerbotMgr.h" + // config option SkipCinematics supported values enum CinematicsSkipMode { @@ -134,6 +137,31 @@ class CharacterHandler } session->HandlePlayerLogin((LoginQueryHolder*)holder); } + // Playerbot mod: is different from the normal HandlePlayerLoginCallback in that it + // sets up the bot's world session and also stores the pointer to the bot player in the master's + // world session m_playerBots map + void HandlePlayerBotLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder * holder) + { + if (!holder) + return; + + LoginQueryHolder* lqh = (LoginQueryHolder*) holder; + + WorldSession* masterSession = sWorld.FindSession(lqh->GetAccountId()); + + if (! masterSession || sObjectMgr.GetPlayer(lqh->GetGuid())) + { + delete holder; + return; + } + + // The bot's WorldSession is owned by the bot's Player object + // The bot's WorldSession is deleted by PlayerbotMgr::LogoutPlayerBot + WorldSession *botSession = new WorldSession(lqh->GetAccountId(), NULL, SEC_PLAYER, masterSession->Expansion(), 0, LOCALE_enUS); + botSession->m_Address = "bot"; + botSession->HandlePlayerLogin(lqh); // will delete lqh + masterSession->GetPlayer()->GetPlayerbotMgr()->OnBotLogin(botSession->GetPlayer()); + } } chrHandler; void WorldSession::HandleCharEnum(QueryResult * result) @@ -570,6 +598,27 @@ void WorldSession::HandlePlayerLoginOpcode( WorldPacket & recv_data ) CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerLoginCallback, holder); } +// Playerbot mod. Can't easily reuse HandlePlayerLoginOpcode for logging in bots because it assumes +// a WorldSession exists for the bot. The WorldSession for a bot is created after the character is loaded. +void PlayerbotMgr::AddPlayerBot(uint64 playerGuid) +{ + // has bot already been added? + if (sObjectMgr.GetPlayer(playerGuid)) + return; + + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(playerGuid); + if (accountId == 0) + return; + + LoginQueryHolder *holder = new LoginQueryHolder(accountId, playerGuid); + if(!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + return; + } + CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerBotLoginCallback, holder); +} + void WorldSession::HandlePlayerLogin(LoginQueryHolder *holder) { ObjectGuid playerGuid = holder->GetGuid(); @@ -1324,6 +1373,6 @@ void WorldSession::HandleEquipmentSetUseOpcode(WorldPacket &recv_data) } WorldPacket data(SMSG_USE_EQUIPMENT_SET_RESULT, 1); - data << uint8(0); // 4 - equipment swap failed - inventory is full + data << uint8(0); // 4 - equipment swap failed - inventory is full SendPacket(&data); } diff --git a/src/game/Chat.cpp b/src/game/Chat.cpp index c3a0a9449..b78ae9a7f 100644 --- a/src/game/Chat.cpp +++ b/src/game/Chat.cpp @@ -782,6 +782,8 @@ ChatCommand * ChatHandler::getCommandTable() { "repairitems", SEC_GAMEMASTER, true, &ChatHandler::HandleRepairitemsCommand, "", NULL }, { "stable", SEC_ADMINISTRATOR, false, &ChatHandler::HandleStableCommand, "", NULL }, { "waterwalk", SEC_GAMEMASTER, false, &ChatHandler::HandleWaterwalkCommand, "", NULL }, + //Playerbot mod + { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", NULL }, { "quit", SEC_CONSOLE, true, &ChatHandler::HandleQuitCommand, "", NULL }, { NULL, 0, false, NULL, "", NULL } diff --git a/src/game/Chat.h b/src/game/Chat.h index 42c9e4f58..e8303e7dd 100644 --- a/src/game/Chat.h +++ b/src/game/Chat.h @@ -585,6 +585,7 @@ class ChatHandler bool HandleRepairitemsCommand(char* args); bool HandleStableCommand(char* args); bool HandleWaterwalkCommand(char* args); + bool HandlePlayerbotCommand(char* args); bool HandleQuitCommand(char* args); //! Development Commands diff --git a/src/game/ChatHandler.cpp b/src/game/ChatHandler.cpp index a48dc983a..ccb4a064c 100644 --- a/src/game/ChatHandler.cpp +++ b/src/game/ChatHandler.cpp @@ -36,6 +36,9 @@ #include "GridNotifiersImpl.h" #include "CellImpl.h" +// Playerbot mod +#include "playerbot/PlayerbotAI.h" + bool WorldSession::processChatmessageFurtherAfterSecurityChecks(std::string& msg, uint32 lang) { if (lang != LANG_ADDON) @@ -234,7 +237,15 @@ void WorldSession::HandleMessagechatOpcode( WorldPacket & recv_data ) } } - GetPlayer()->Whisper(msg, lang, player->GetGUID()); + // Playerbot mod: handle whispered command to bot + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + else + GetPlayer()->Whisper(msg, lang, player->GetGUID()); } break; case CHAT_MSG_PARTY: @@ -269,6 +280,19 @@ void WorldSession::HandleMessagechatOpcode( WorldPacket & recv_data ) if ((type == CHAT_MSG_PARTY_LEADER) && !group->IsLeader(_player->GetObjectGuid())) return; + // Playerbot mod: broadcast message to bot members + for(GroupReference* itr = group->GetFirstMember(); itr != NULL; itr=itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } + // END Playerbot mod + WorldPacket data; ChatHandler::FillMessageData(&data, this, type, lang, NULL, 0, msg.c_str(), NULL); group->BroadcastPacket(&data, false, group->GetMemberGroup(GetPlayer()->GetObjectGuid())); diff --git a/src/game/Creature.h b/src/game/Creature.h index 785798827..c51d78566 100644 --- a/src/game/Creature.h +++ b/src/game/Creature.h @@ -468,6 +468,9 @@ class MANGOS_DLL_SPEC Creature : public Unit bool IsTotem() const { return m_subtype == CREATURE_SUBTYPE_TOTEM; } bool IsTemporarySummon() const { return m_subtype == CREATURE_SUBTYPE_TEMPORARY_SUMMON; } + // Playerbot mod - adds functionality to load/unload bots from NPC, also need to apply SQL scripts + void LoadBotMenu(Player *pPlayer); + bool IsCorpse() const { return getDeathState() == CORPSE; } bool IsDespawned() const { return getDeathState() == DEAD; } void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } diff --git a/src/game/GossipDef.h b/src/game/GossipDef.h index 42cbbefef..091388dbb 100644 --- a/src/game/GossipDef.h +++ b/src/game/GossipDef.h @@ -49,6 +49,7 @@ enum Gossip_Option GOSSIP_OPTION_ARMORER = 15, //UNIT_NPC_FLAG_ARMORER (4096) GOSSIP_OPTION_UNLEARNTALENTS = 16, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) GOSSIP_OPTION_UNLEARNPETSKILLS = 17, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) + GOSSIP_OPTION_BOT = 18, //UNIT_NPC_FLAG_GOSSIP (1) UNUSED (just for bot system) GOSSIP_OPTION_MAX }; diff --git a/src/game/Group.cpp b/src/game/Group.cpp index 7800092a9..a5829754e 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -35,6 +35,9 @@ #include "LootMgr.h" #include "LFGMgr.h" +// Playerbot +#include "playerbot/PlayerbotMgr.h" + #define LOOT_ROLL_TIMEOUT (1*MINUTE*IN_MILLISECONDS) //=================================================== @@ -359,6 +362,14 @@ uint32 Group::RemoveMember(ObjectGuid guid, uint8 method) BroadcastGroupUpdate(); // Frozen Mod + //Playerbot mod - if master leaves group, all bots leave group + { + Player* const player = sObjectMgr.GetPlayer(guid); + if (player && player->GetPlayerbotMgr()) + player->GetPlayerbotMgr()->RemoveAllBotsFromGroup(); + } + //END Playerbot mod + // remove member and change leader (if need) only if strong more 2 members _before_ member remove if (GetMembersCount() > uint32(isBGGroup() ? 1 : 2)) // in BG group case allow 1 members group { diff --git a/src/game/GroupHandler.cpp b/src/game/GroupHandler.cpp index dceb3d212..060f1e952 100644 --- a/src/game/GroupHandler.cpp +++ b/src/game/GroupHandler.cpp @@ -174,7 +174,8 @@ void WorldSession::HandleGroupInviteOpcode( WorldPacket & recv_data ) void WorldSession::HandleGroupAcceptOpcode( WorldPacket & recv_data ) { - recv_data.read_skip(); // roles mask? + if (!GetPlayer()->GetPlayerbotAI()) + recv_data.read_skip(); // roles mask? Group *group = GetPlayer()->GetGroupInvite(); if (!group) diff --git a/src/game/Map.cpp b/src/game/Map.cpp index 813e15365..4b54b85f2 100644 --- a/src/game/Map.cpp +++ b/src/game/Map.cpp @@ -610,7 +610,9 @@ void Map::Remove(Player *player, bool remove) SendRemoveTransports(player); UpdateObjectVisibility(player,cell,p); - player->ResetMap(); + if (!player->GetPlayerbotAI()) + player->ResetMap(); + if( remove ) DeleteFromWorld(player); } diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 60d1ac7c3..e3c564c64 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -63,6 +63,12 @@ #include +// Playerbot mod: +#include "playerbot/PlayerbotAI.h" +#include "playerbot/PlayerbotMgr.h" +#include "Config/Config.h" + + #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) #define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) @@ -77,6 +83,8 @@ #define SKILL_PERM_BONUS(x) int16(PAIR32_HIPART(x)) #define MAKE_SKILL_BONUS(t, p) MAKE_PAIR32(t,p) +extern Config botConfig; + enum CharacterFlags { CHARACTER_FLAG_NONE = 0x00000000, @@ -586,6 +594,10 @@ Player::Player (WorldSession *session): Unit(), m_mover(this), m_camera(this), m // Refer-A-Friend m_GrantableLevelsCount = 0; + // Playerbot mod: + m_playerbotAI = NULL; + m_playerbotMgr = NULL; + m_anticheat = new AntiCheat(this); SetPendingBind(NULL, 0); @@ -633,6 +645,13 @@ Player::~Player () delete m_anticheat; delete m_LFGState; + // Playerbot mod + if (m_playerbotAI) + delete m_playerbotAI; + + if (m_playerbotMgr) + delete m_playerbotMgr; + } void Player::CleanupsBeforeDelete() @@ -1510,6 +1529,12 @@ void Player::Update( uint32 update_diff, uint32 p_time ) if (IsHasDelayedTeleport()) TeleportTo(m_teleport_dest, m_teleport_options); + + // Playerbot mod + if (m_playerbotAI) + m_playerbotAI->UpdateAI(p_time); + else if (m_playerbotMgr) + m_playerbotMgr->UpdateAI(p_time); } void Player::SetDeathState(DeathState s) @@ -1765,6 +1790,11 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) Pet* pet = GetPet(); + // Playerbot mod: if this user has bots, tell them to stop following master + // so they don't try to follow the master after the master teleports + if (GetPlayerbotMgr()) + GetPlayerbotMgr()->Stay(); + MapEntry const* mEntry = sMapStore.LookupEntry(mapid); // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)... @@ -13405,6 +13435,22 @@ void Player::PrepareGossipMenu(WorldObject *pSource, uint32 menuId) case GOSSIP_OPTION_TABARDDESIGNER: case GOSSIP_OPTION_AUCTIONEER: break; // no checks + case GOSSIP_OPTION_BOT: + { + if(botConfig.GetBoolDefault("PlayerbotAI.DisableBots", false) && !pCreature->isInnkeeper()) + { + ChatHandler(this).PSendSysMessage("|cffff0000Playerbot system is currently disabled!"); + hasMenuItem = false; + break; + } + + std::string reqQuestIds = botConfig.GetStringDefault("PlayerbotAI.BotguyQuests",""); + uint32 cost = botConfig.GetIntDefault("PlayerbotAI.BotguyCost",0); + if((reqQuestIds == "" || requiredQuests(reqQuestIds.c_str())) && !pCreature->isInnkeeper() && this->GetMoney() >= cost) + pCreature->LoadBotMenu(this); + hasMenuItem = false; + break; + } default: sLog.outErrorDb("Creature entry %u have unknown gossip option %u for menu %u", pCreature->GetEntry(), itr->second.option_id, itr->second.menu_id); hasMenuItem = false; @@ -13552,12 +13598,12 @@ void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 me } } - GossipMenuItemData pMenuData = gossipmenu.GetItemData(gossipListId); - switch(gossipOptionId) { case GOSSIP_OPTION_GOSSIP: { + GossipMenuItemData pMenuData = gossipmenu.GetItemData(gossipListId); + if (pMenuData.m_gAction_poi) PlayerTalkClass->SendPointOfInterest(pMenuData.m_gAction_poi); @@ -13647,6 +13693,59 @@ void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 me GetSession()->SendBattlegGroundList(guid, bgTypeId); break; } + case GOSSIP_OPTION_BOT: + { + // DEBUG_LOG("GOSSIP_OPTION_BOT"); + PlayerTalkClass->CloseGossip(); + uint32 guidlo = PlayerTalkClass->GossipOptionSender(gossipListId); + uint32 cost = botConfig.GetIntDefault("PlayerbotAI.BotguyCost",0); + + if (!GetPlayerbotMgr()) + SetPlayerbotMgr(new PlayerbotMgr(this)); + + if(GetPlayerbotMgr()->GetPlayerBot(guidlo) != NULL) + { + GetPlayerbotMgr()->LogoutPlayerBot(guidlo); + } + else if(GetPlayerbotMgr()->GetPlayerBot(guidlo) == NULL) + { + QueryResult *resultchar = CharacterDatabase.PQuery("SELECT COUNT(*) FROM characters WHERE online = '1' AND account = '%u'", m_session->GetAccountId()); + if(resultchar) + { + Field *fields = resultchar->Fetch(); + int maxnum = botConfig.GetIntDefault("PlayerbotAI.MaxNumBots", 9); + int acctcharcount = fields[0].GetUInt32(); + if(!(m_session->GetSecurity() > SEC_PLAYER)) + if(acctcharcount > maxnum) + { + ChatHandler(this).PSendSysMessage("|cffff0000You cannot summon anymore bots.(Current Max: |cffffffff%u)",maxnum); + delete resultchar; + break; + } + } + delete resultchar; + + QueryResult *resultlvl = CharacterDatabase.PQuery("SELECT level,name FROM characters WHERE guid = '%u'", guidlo); + if(resultlvl) + { + Field *fields=resultlvl->Fetch(); + int maxlvl = botConfig.GetIntDefault("PlayerbotAI.RestrictBotLevel", 80); + int charlvl = fields[0].GetUInt32(); + if(!(m_session->GetSecurity() > SEC_PLAYER)) + if(charlvl > maxlvl) + { + ChatHandler(this).PSendSysMessage("|cffff0000You cannot summon |cffffffff[%s]|cffff0000, it's level is too high.(Current Max:lvl |cffffffff%u)",fields[1].GetString(),maxlvl); + delete resultlvl; + break; + } + } + delete resultlvl; + + GetPlayerbotMgr()->AddPlayerBot(guidlo); + this->ModifyMoney(-(int32)cost); + } + break; + } } } diff --git a/src/game/Player.h b/src/game/Player.h index 5194c3236..b17319c2f 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -41,6 +41,10 @@ #include "LFG.h" #include "AntiCheat.h" +// Playerbot mod +#include "playerbot/PlayerbotMgr.h" +#include "playerbot/PlayerbotAI.h" + #include #include @@ -1487,6 +1491,11 @@ 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); + void skill(std::list& m_spellsToLearn); + bool requiredQuests(const char* pQuestIdString); + /*********************************************************/ /*** LOAD SYSTEM ***/ /*********************************************************/ @@ -2414,6 +2423,16 @@ class MANGOS_DLL_SPEC Player : public Unit void SetTitle(CharTitlesEntry const* title, bool lost = false); bool canSeeSpellClickOn(Creature const* creature) const; + + // Playerbot mod: + // A Player can either have a playerbotMgr (to manage its bots), or have playerbotAI (if it is a bot), or + // neither. Code that enables bots must create the playerbotMgr and set it using SetPlayerbotMgr. + void SetPlayerbotAI(PlayerbotAI* ai) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotAI=ai; } + PlayerbotAI* GetPlayerbotAI() { return m_playerbotAI; } + void SetPlayerbotMgr(PlayerbotMgr* mgr) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotMgr=mgr; } + PlayerbotMgr* GetPlayerbotMgr() { return m_playerbotMgr; } + void SetBotDeathTimer() { m_deathTimer = 0; } + protected: uint32 m_contestedPvPTimer; @@ -2691,6 +2710,10 @@ class MANGOS_DLL_SPEC Player : public Unit GridReference m_gridRef; MapReference m_mapRef; + // Playerbot mod: + PlayerbotAI* m_playerbotAI; + PlayerbotMgr* m_playerbotMgr; + // Homebind coordinates uint32 m_homebindMapId; uint16 m_homebindAreaId; diff --git a/src/game/QueryHandler.cpp b/src/game/QueryHandler.cpp index c2812de72..d7852486a 100644 --- a/src/game/QueryHandler.cpp +++ b/src/game/QueryHandler.cpp @@ -332,7 +332,7 @@ void WorldSession::HandleNpcTextQueryOpcode( WorldPacket & recv_data ) GossipText const* pGossip = sObjectMgr.GetGossipText(textID); - WorldPacket data( SMSG_NPC_TEXT_UPDATE, 100 ); // guess size + WorldPacket data( SMSG_NPC_TEXT_UPDATE, 100 ); // guess size data << textID; if (!pGossip) diff --git a/src/game/QuestHandler.cpp b/src/game/QuestHandler.cpp index d8da737f6..efa41ff76 100644 --- a/src/game/QuestHandler.cpp +++ b/src/game/QuestHandler.cpp @@ -30,6 +30,9 @@ #include "ScriptMgr.h" #include "Group.h" +// Playerbot mod: +#include "playerbot/PlayerbotAI.h" + void WorldSession::HandleQuestgiverStatusQueryOpcode( WorldPacket & recv_data ) { ObjectGuid guid; @@ -515,8 +518,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->GetObjectGuid(), true ); } } } diff --git a/src/game/SpellMgr.cpp b/src/game/SpellMgr.cpp index 736c943a3..3a8bcbaff 100644 --- a/src/game/SpellMgr.cpp +++ b/src/game/SpellMgr.cpp @@ -2581,7 +2581,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/World.cpp b/src/game/World.cpp index 1349a73f0..adf0c42a8 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -23,6 +23,7 @@ #include "World.h" #include "Database/DatabaseEnv.h" #include "Config/Config.h" +#include "playerbot/config.h" #include "Platform/Define.h" #include "SystemConfig.h" #include "Log.h" @@ -79,6 +80,7 @@ float World::m_MaxVisibleDistanceInFlight = DEFAULT_VISIBILITY_DISTANCE; float World::m_VisibleUnitGreyDistance = 0; float World::m_VisibleObjectGreyDistance = 0; +extern Config botConfig; float World::m_relocation_lower_limit_sq = 10.f * 10.f; uint32 World::m_relocation_ai_notify_delay = 1000u; @@ -1413,6 +1415,16 @@ void World::SetInitialWorldSettings() // Delete all characters which have been deleted X days before Player::DeleteOldCharacters(); + //Get playerbot configuration file + if (!botConfig.SetSource(_PLAYERBOT_CONFIG)) + sLog.outError("Playerbot: Unable to open configuration file. Database will be unaccessible. Configuration values will use default."); + else + sLog.outString("Playerbot: Using configuration file %s",_PLAYERBOT_CONFIG); + + //Check playerbot config file version + if (botConfig.GetIntDefault("ConfVersion", 0) != PLAYERBOT_CONF_VERSION) + sLog.outError("Playerbot: Configuration file version doesn't match expected version. Some config variables may be wrong or missing."); + sLog.outString("Initialize AuctionHouseBot..."); auctionbot.Initialize(); diff --git a/src/game/WorldSession.cpp b/src/game/WorldSession.cpp index 069ffef7f..03f6c8d7c 100644 --- a/src/game/WorldSession.cpp +++ b/src/game/WorldSession.cpp @@ -20,7 +20,7 @@ \ingroup u2w */ -#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it +#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it #include "Common.h" #include "Database/DatabaseEnv.h" #include "Log.h" @@ -40,6 +40,10 @@ #include "Auth/HMACSHA1.h" #include "zlib/zlib.h" +// Playerbot mod +#include "playerbot/PlayerbotMgr.h" +#include "playerbot/PlayerbotAI.h" + // select opcodes appropriate for processing in Map::Update context for current session state static bool MapSessionFilterHelper(WorldSession* session, OpcodeHandler const& opHandle) { @@ -130,6 +134,14 @@ char const* WorldSession::GetPlayerName() const /// Send a packet to the client void WorldSession::SendPacket(WorldPacket const* packet) { + // Playerbot mod: send packet to bot AI + if (GetPlayer()) { + if (GetPlayer()->GetPlayerbotAI()) + GetPlayer()->GetPlayerbotAI()->HandleBotOutgoingPacket(*packet); + else if (GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->HandleMasterOutgoingPacket(*packet); + } + if (!m_Socket) return; @@ -227,6 +239,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) ExecuteOpcode(opHandle, packet); // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer + + // playerbot mod + if (_player && _player->GetPlayerbotMgr()) + _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); + // playerbot mod end break; case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT: if(!_player && !m_playerRecentlyLogout) @@ -299,6 +316,31 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) delete packet; } + // Playerbot mod - Process player bot packets + // The PlayerbotAI class adds to the packet queue to simulate a real player + // since Playerbots are known to the World obj only by its master's WorldSession object + // we need to process all master's bot's packets. + if (GetPlayer() && GetPlayer()->GetPlayerbotMgr()) { + for (PlayerBotMap::const_iterator itr = GetPlayer()->GetPlayerbotMgr()->GetPlayerBotsBegin(); + itr != GetPlayer()->GetPlayerbotMgr()->GetPlayerBotsEnd(); ++itr) + { + Player* const botPlayer = itr->second; + WorldSession* const pBotWorldSession = botPlayer->GetSession(); + if (botPlayer->IsBeingTeleported()) + botPlayer->GetPlayerbotAI()->HandleTeleportAck(); + else if (botPlayer->IsInWorld()) + { + WorldPacket* packet; + while (pBotWorldSession->_recvQueue.next(packet)) + { + OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()]; + (pBotWorldSession->*opHandle.handler)(*packet); + delete packet; + } + } + } + } + ///- Cleanup socket pointer if need if (m_Socket && m_Socket->IsClosed ()) { @@ -334,6 +376,10 @@ void WorldSession::LogoutPlayer(bool Save) if (_player) { + // Playerbot mod: log out all player bots owned by this toon + if (GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->LogoutAllBots(); + sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), GetRemoteAddress().c_str(), _player->GetName() ,_player->GetGUIDLow()); if (uint64 lguid = GetPlayer()->GetLootGUID()) @@ -427,10 +473,13 @@ void WorldSession::LogoutPlayer(bool Save) ///- Reset the online field in the account table // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage // No SQL injection as AccountID is uint32 - static SqlStatementID id; + if (!GetPlayer()->GetPlayerbotAI()) + { + static SqlStatementID id; - SqlStatement stmt = LoginDatabase.CreateStatement(id, "UPDATE account SET active_realm_id = ? WHERE id = ?"); - stmt.PExecute(uint32(0), GetAccountId()); + SqlStatement stmt = LoginDatabase.CreateStatement(id, "UPDATE account SET active_realm_id = ? WHERE id = ?"); + stmt.PExecute(uint32(0), GetAccountId()); + } ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members if (Guild *guild = sObjectMgr.GetGuildById(_player->GetGuildId())) @@ -486,6 +535,9 @@ void WorldSession::LogoutPlayer(bool Save) sSocialMgr.SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetObjectGuid(), true); sSocialMgr.RemovePlayerSocial (_player->GetGUIDLow ()); + // Playerbot - remember player GUID for update SQL below + uint32 guid = GetPlayer()->GetGUIDLow(); + ///- Remove the player from the world // the player may not be in the world when logging out // e.g if he got disconnected during a transfer to another map @@ -507,13 +559,11 @@ void WorldSession::LogoutPlayer(bool Save) WorldPacket data( SMSG_LOGOUT_COMPLETE, 0 ); SendPacket( &data ); - ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline - //No SQL injection as AccountId is uint32 - static SqlStatementID updChars; - stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE account = ?"); - stmt.PExecute(GetAccountId()); + // Playerbot mod: commented out above and do this one instead + SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); + stmt.PExecute(guid); DEBUG_LOG( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" ); } diff --git a/src/game/playerbot/PlayerbotAI.cpp b/src/game/playerbot/PlayerbotAI.cpp new file mode 100644 index 000000000..db0472723 --- /dev/null +++ b/src/game/playerbot/PlayerbotAI.cpp @@ -0,0 +1,4828 @@ +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "../ItemPrototype.h" +#include "../World.h" +#include "../SpellMgr.h" +#include "../GridNotifiers.h" +#include "../GridNotifiersImpl.h" +#include "../CellImpl.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" +#include "../MotionMaster.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; + m_collectionFlags = 0x07; // default to collect after combat for quest and profession + + // 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; + } + + HERB_GATHERING = initSpell(HERB_GATHERING_1); + MINING = initSpell(MINING_1); + SKINNING = initSpell(SKINNING_1); +} + +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: + { + WorldPacket p(packet); + uint8 err; + p >> err; + if (err != EQUIP_ERR_OK) + { + switch (err) + { + case EQUIP_ERR_CANT_CARRY_MORE_OF_THIS: + TellMaster("I can't carry anymore of those."); + return; + case EQUIP_ERR_MISSING_REAGENT: + TellMaster("I'm missing some reagents for that."); + return; + case EQUIP_ERR_ITEM_LOCKED: + TellMaster("That item is locked."); + return; + case EQUIP_ERR_ALREADY_LOOTED: + TellMaster("That is already looted."); + return; + case EQUIP_ERR_INVENTORY_FULL: + TellMaster("My inventory is full."); + return; + case EQUIP_ERR_NOT_IN_COMBAT: + TellMaster("I can't use that in combat."); + return; + case EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW: + TellMaster("I can't get that now."); + return; + case EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE: + TellMaster("I can only have one of those equipped."); + return; + default: + TellMaster("I can't use that."); + sLog.outDebug("[PlayerbotAI]: SMSG_INVENTORY_CHANGE_FAILURE: %u", err); + 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; + + out << "In my main backpack:"; + // 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(); + } + } + ChatHandler ch(m_bot->GetTrader()); + ch.SendSysMessage(out.str().c_str()); + + // 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) + { + std::ostringstream outbag; + outbag << "In my "; + const ItemPrototype* const pBagProto = pBag->GetProto(); + std::string bagName = pBagProto->Name1; + ItemLocalization(bagName, pBagProto->ItemId); + outbag << bagName << ":"; + + 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 + outbag << " |cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << itemName + << "]|h|r"; + if (pItem->GetCount() > 1) + outbag << "x" << pItem->GetCount(); + } + } + ch.SendSysMessage(outbag.str().c_str()); + } + } + + // 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"; + SendWhisper(whisper.str().c_str(), *(m_bot->GetTrader())); + } + return; + } + + case SMSG_SPELL_START: + { + WorldPacket p(packet); + uint64 castItemGuid = p.readPackGUID(); + uint64 casterGuid = p.readPackGUID(); + if (casterGuid != m_bot->GetGUID()) + return; + + uint8 castCount; + p >> castCount; + uint32 spellId; + p >> spellId; + uint32 castFlags; + p >> castFlags; + uint32 msTime; + p >> msTime; + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + return; + + if (pSpellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) + return; + + m_ignoreAIUpdatesUntilTime = time(0) + (msTime / 1000) + 1; + + 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; + } + + case SMSG_LOOT_RESPONSE: + { + WorldPacket p(packet); // (8+1+4+1+1+4+4+4+4+4+1) + ObjectGuid guid; + uint8 loot_type; + uint32 gold; + uint8 items; + + p >> guid; // 8 corpse guid + p >> loot_type; // 1 loot type + p >> gold; // 4 money on corpse + p >> items; // 1 number of items on corpse + + if (gold > 0) + { + WorldPacket* const packet = new WorldPacket(CMSG_LOOT_MONEY, 0); + m_bot->GetSession()->QueuePacket(packet); + } + + if (loot_type == LOOT_SKINNING || HasCollectFlag(COLLECT_FLAG_LOOT)) + for (uint8 i = 0; i < items; ++i) + { + uint32 itemid; + uint32 itemcount; + uint8 lootslot_type; + uint8 itemindex; + + p >> itemindex; // 1 counter + p >> itemid; // 4 itemid + p >> itemcount; // 4 item stack count + p.read_skip(); // 4 item model + p.read_skip(); // 4 randomSuffix + p.read_skip(); // 4 randomPropertyId + p >> lootslot_type; // 1 slot 0 = can get, 1 = look only, 2 = master get + // TellMaster("[%s]: loots : (%u) itemindex : (%u)",m_bot->GetName(), itemid, itemindex); + + // just auto loot everything for getting object + WorldPacket* const packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); + *packet << itemindex; + m_bot->GetSession()->QueuePacket(packet); + } + else if (loot_type == LOOT_CORPSE) // loot from a creature + + for (uint8 i = 0; i < items; ++i) + { + // look for through items on corpse and loot them + uint32 itemid; + uint32 itemcount; + uint8 lootslot_type; + uint8 itemindex; + bool grab = false; + + p >> itemindex; + p >> itemid; + p >> itemcount; + p.read_skip(); // display id + p.read_skip(); // randomSuffix + p.read_skip(); // randomPropertyId + p >> lootslot_type; // 0 = can get, 1 = look only, 2 = master get + + if (lootslot_type != 0) + continue; + + ItemPrototype const *pProto = ObjectMgr::GetItemPrototype(itemid); + if (!pProto) + continue; + + uint32 max = pProto->MaxCount; + // do we already have the max allowed of item if more than zero? + if (max > 0 && m_bot->HasItemCount(itemid, max, true)) + continue; + + // grab quest related items + if (pProto->StartQuest > 0 && HasCollectFlag(COLLECT_FLAG_QUEST)) + grab = true; + + if (m_needItemList[itemid] > 0) + grab = true; // item is in need list, just grab without further processing + else // if not in need list, run other checks + { + switch (pProto->Class) + { + case ITEM_CLASS_QUEST: + if (!HasCollectFlag(COLLECT_FLAG_QUEST) && !HasCollectFlag(COLLECT_FLAG_LOOT)) + break; + case ITEM_CLASS_KEY: + grab = true; + break; + case ITEM_CLASS_TRADE_GOODS: + if (!HasCollectFlag(COLLECT_FLAG_PROFESSION) && !HasCollectFlag(COLLECT_FLAG_LOOT)) + break; + + switch (pProto->SubClass) + { + case ITEM_SUBCLASS_PARTS: + case ITEM_SUBCLASS_EXPLOSIVES: + case ITEM_SUBCLASS_DEVICES: + grab = m_bot->HasSkill(SKILL_ENGINEERING); + break; + case ITEM_SUBCLASS_JEWELCRAFTING: + grab = m_bot->HasSkill(SKILL_JEWELCRAFTING); + break; + case ITEM_SUBCLASS_CLOTH: + grab = m_bot->HasSkill(SKILL_TAILORING); + break; + case ITEM_SUBCLASS_LEATHER: + grab = m_bot->HasSkill(SKILL_LEATHERWORKING); + break; + case ITEM_SUBCLASS_METAL_STONE: + grab = (m_bot->HasSkill(SKILL_BLACKSMITHING) || + m_bot->HasSkill(SKILL_ENGINEERING) || + m_bot->HasSkill(SKILL_MINING)); + break; + case ITEM_SUBCLASS_MEAT: + grab = m_bot->HasSkill(SKILL_COOKING); + break; + case ITEM_SUBCLASS_HERB: + grab = (m_bot->HasSkill(SKILL_HERBALISM) || + m_bot->HasSkill(SKILL_ALCHEMY) || + m_bot->HasSkill(SKILL_INSCRIPTION)); + break; + case ITEM_SUBCLASS_ELEMENTAL: + grab = true; // pretty much every profession uses these a bit + break; + case ITEM_SUBCLASS_ENCHANTING: + grab = m_bot->HasSkill(SKILL_ENCHANTING); + break; + default: + break; + } + break; + case ITEM_CLASS_RECIPE: + { + if (!HasCollectFlag(COLLECT_FLAG_PROFESSION) && !HasCollectFlag(COLLECT_FLAG_LOOT)) + break; + + // skip recipes that we have + if (m_bot->HasSpell(pProto->Spells[2].SpellId)) + break; + + switch (pProto->SubClass) + { + case ITEM_SUBCLASS_LEATHERWORKING_PATTERN: + grab = m_bot->HasSkill(SKILL_LEATHERWORKING); + break; + case ITEM_SUBCLASS_TAILORING_PATTERN: + grab = m_bot->HasSkill(SKILL_TAILORING); + break; + case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC: + grab = m_bot->HasSkill(SKILL_ENGINEERING); + break; + case ITEM_SUBCLASS_BLACKSMITHING: + grab = m_bot->HasSkill(SKILL_BLACKSMITHING); + break; + case ITEM_SUBCLASS_COOKING_RECIPE: + grab = m_bot->HasSkill(SKILL_COOKING); + break; + case ITEM_SUBCLASS_ALCHEMY_RECIPE: + grab = m_bot->HasSkill(SKILL_ALCHEMY); + break; + case ITEM_SUBCLASS_FIRST_AID_MANUAL: + grab = m_bot->HasSkill(SKILL_FIRST_AID); + break; + case ITEM_SUBCLASS_ENCHANTING_FORMULA: + grab = m_bot->HasSkill(SKILL_ENCHANTING); + break; + case ITEM_SUBCLASS_FISHING_MANUAL: + grab = m_bot->HasSkill(SKILL_FISHING); + break; + case ITEM_SUBCLASS_JEWELCRAFTING_RECIPE: + grab = m_bot->HasSkill(SKILL_JEWELCRAFTING); + break; + default: + break; + } + } + default: + break; + } + } + + // TODO: check for items that the bot has been told to collect + + if (grab) + { + sLog.outDebug("[PlayerbotAI]: Loot item index %u item id %u", itemindex, itemid); + WorldPacket* const packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); + *packet << itemindex; + m_bot->GetSession()->QueuePacket(packet); + } + } + + // release loot + WorldPacket* const packet = new WorldPacket(CMSG_LOOT_RELEASE, 8); + *packet << guid; + m_bot->GetSession()->QueuePacket(packet); + + SetQuestNeedItems(); + + return; + } + + case SMSG_LOOT_RELEASE_RESPONSE: + { + WorldPacket p(packet); + ObjectGuid guid; + + p >> guid; + + if (guid == m_lootCurrent) + { + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + + if (c && c->GetCreatureInfo()->SkinLootId && !c->lootForSkin) + { + uint32 reqSkill = c->GetCreatureInfo()->GetRequiredLootSkill(); + // check if it is a leather skin and if it is to be collected (could be ore or herb) + if (m_bot->HasSkill(reqSkill) && ((reqSkill != SKILL_SKINNING) || + (HasCollectFlag(COLLECT_FLAG_SKIN) && reqSkill == SKILL_SKINNING))) + { + // calculate skill requirement + uint32 skillValue = m_bot->GetPureSkillValue(reqSkill); + uint32 targetLevel = c->getLevel(); + uint32 reqSkillValue = targetLevel < 10 ? 0 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; + if (skillValue >= reqSkillValue) + { + if (m_lootCurrent != m_lootPrev) // if this wasn't previous loot try again + { + m_lootPrev = m_lootCurrent; + return; // so that the DoLoot function is called again to get skin + } + } + else + TellMaster("My skill is %u but it requires %u", skillValue, reqSkillValue); + } + } + + // if previous is current, clear + if (m_lootPrev == m_lootCurrent) + m_lootPrev = ObjectGuid(); + // clear current target + m_lootCurrent = ObjectGuid(); + // clear movement + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + } + + 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_lootCurrent = ObjectGuid(); + 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_lootTargets.push_back(m_targetCombat->GetGUID()); + + 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_bot has it's back to the attacker, turn + if (!m_bot->HasInArc(M_PI_F, m_targetCombat)) + { + // TellMaster("%s is facing the wrong way!", m_bot->GetName()); + m_bot->GetMotionMaster()->Clear(true); + m_bot->SetOrientation(m_bot->GetAngle(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(); + + // 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() +{ + bool looted = false; + + if (m_lootCurrent.IsEmpty() && m_lootTargets.empty()) + { + // sLog.outDebug( "[PlayerbotAI]: %s reset loot list / go back to idle", m_bot->GetName() ); + SetState(BOTSTATE_NORMAL); + m_bot->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_LOOTING); + SetQuestNeedItems(); + return; + } + + if (m_lootCurrent.IsEmpty()) + { + m_lootCurrent = m_lootTargets.front(); + m_lootTargets.pop_front(); + + GameObject *go = m_bot->GetMap()->GetGameObject(m_lootCurrent); + 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) + if (!go) + { + m_lootCurrent = ObjectGuid(); + return; + } + + if (c) + { + if (m_collectionFlags == COLLECT_FLAG_NOTHING) + { + m_lootCurrent = ObjectGuid(); // told to loot nothing + 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() ); + } + + if (go) + m_bot->GetMotionMaster()->MovePoint(go->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ()); + //sLog.outDebug( "[PlayerbotAI]: %s is going to loot '%s'", m_bot->GetName(), go->GetGOInfo()->name); + + // TEMP HACK: attempt to fix duplicate loot attempt (shows when getting ores occasionally) + // give time to move to point before trying again + m_ignoreAIUpdatesUntilTime = time(0) + 1; + } + else + { + uint32 skillId = 0; + uint32 reqSkillValue = 0; + uint32 SkillValue = 0; + bool keyFailed = false; + bool skillFailed = false; + bool forceFailed = false; + + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + GameObject *go = m_bot->GetMap()->GetGameObject(m_lootCurrent); + if (!c || c->getDeathState() != CORPSE || GetMaster()->GetDistance(c) > BOTLOOT_DISTANCE) + if (!go) + { + m_lootCurrent = ObjectGuid(); + return; + } + if (m_bot->IsWithinDistInMap(c, INTERACTION_DISTANCE) || m_bot->IsWithinDistInMap(go, INTERACTION_DISTANCE)) + { + if (c) // creature + { + if (c->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) + { + // loot the creature + WorldPacket* const packet = new WorldPacket(CMSG_LOOT, 8); + *packet << m_lootCurrent; + m_bot->GetSession()->QueuePacket(packet); + return; // no further processing is needed + // m_lootCurrent is reset in SMSG_LOOT_RELEASE_RESPONSE after checking for skinloot + } + else if (c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) + { + skillId = c->GetCreatureInfo()->GetRequiredLootSkill(); + // not all creature skins are leather, some are ore or herb + if (m_bot->HasSkill(skillId) && ((skillId != SKILL_SKINNING) || + (HasCollectFlag(COLLECT_FLAG_SKIN) && skillId == SKILL_SKINNING))) + { + // calculate skinning skill requirement + uint32 targetLevel = c->getLevel(); + reqSkillValue = targetLevel < 10 ? 0 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; + } + } + else // not a lootable creature, clear it + { + m_lootCurrent = ObjectGuid(); + // clear movement target, take next target on next update + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + return; + } + + // creatures cannot be unlocked or forced open + keyFailed = true; + forceFailed = true; + } + + if (go) // object + { + uint32 reqItem = 0; + + // check skill or lock on object + uint32 lockId = go->GetGOInfo()->GetLockId(); + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if (lockInfo) + for (int i = 0; i < 8; ++i) + { + switch (lockInfo->Type[i]) + { + case LOCK_KEY_ITEM: + if (lockInfo->Index[i] > 0) + reqItem = lockInfo->Index[i]; + break; + case LOCK_KEY_SKILL: + if (SkillByLockType(LockType(lockInfo->Index[i])) > 0) + { + skillId = SkillByLockType(LockType(lockInfo->Index[i])); + reqSkillValue = lockInfo->Skill[i]; + } + break; + default: + break; + } + } + + // use key on object if available + if (reqItem > 0 && m_bot->HasItemCount(reqItem, 1)) + { + UseItem(m_bot->GetItemByEntry(reqItem), TARGET_FLAG_OBJECT, m_lootCurrent); + m_lootCurrent = ObjectGuid(); + return; + } + else + keyFailed = true; + } + + // determine bot's skill value for object's required skill + if (skillId != SKILL_NONE) + SkillValue = uint32(m_bot->GetPureSkillValue(skillId)); + + // bot has the specific skill or object requires no skill at all + if ((m_bot->HasSkill(skillId) && skillId != SKILL_NONE) || (skillId == SKILL_NONE && go)) + { + if (SkillValue >= reqSkillValue) + { + // add this GO to our collection list if active and is chest/ore/herb + if (go && HasCollectFlag(COLLECT_FLAG_NEAROBJECT) && go->GetGoType() == GAMEOBJECT_TYPE_CHEST) + { + m_collectObjects.push_back(go->GetEntry()); + m_collectObjects.unique(); + } + + switch (skillId) + { + case SKILL_MINING: + if (HasTool(TC_MINING_PICK) && CastSpell(MINING)) + return; + else + skillFailed = true; + break; + case SKILL_HERBALISM: + if (CastSpell(HERB_GATHERING)) + return; + else + skillFailed = true; + break; + case SKILL_SKINNING: + if (c && HasCollectFlag(COLLECT_FLAG_SKIN) && + HasTool(TC_SKINNING_KNIFE) && CastSpell(SKINNING, *c)) + return; + else + skillFailed = true; + break; + case SKILL_LOCKPICKING: + if (CastSpell(PICK_LOCK_1)) + return; + else + skillFailed = true; + break; + case SKILL_NONE: + if (CastSpell(3365)) //Spell 3365 = Opening? + return; + else + skillFailed = true; + break; + default: + TellMaster("I'm not sure how to get that."); + skillFailed = true; + sLog.outDebug("[PlayerbotAI]:DoLoot Skill %u is not implemented", skillId); + break; + } + } + else + { + TellMaster("My skill is not high enough. It requires %u, but mine is %u.", + reqSkillValue, SkillValue); + skillFailed = true; + } + } + else + { + TellMaster("I do not have the required skill."); + skillFailed = true; + } + + if (go) // only go's can be forced + { + // if pickable, check if a forcible item is available for the bot + if (skillId == SKILL_LOCKPICKING && (m_bot->HasSkill(SKILL_BLACKSMITHING) || + m_bot->HasSkill(SKILL_ENGINEERING))) + { + // check for skeleton keys appropriate for lock value + if (m_bot->HasSkill(SKILL_BLACKSMITHING)) + { + Item *kItem = FindKeyForLockValue(reqSkillValue); + if (kItem) + { + TellMaster("I have a skeleton key that can open it!"); + UseItem(kItem, TARGET_FLAG_OBJECT, m_lootCurrent); + return; + } + else + { + TellMaster("I have no skeleton keys that can open that lock."); + forceFailed = true; + } + } + + // check for a charge that can blast it open + if (m_bot->HasSkill(SKILL_ENGINEERING)) + { + Item *bItem = FindBombForLockValue(reqSkillValue); + if (bItem) + { + TellMaster("I can blast it open!"); + UseItem(bItem, TARGET_FLAG_OBJECT, m_lootCurrent); + return; + } + else + { + TellMaster("I have nothing to blast it open with."); + forceFailed = true; + } + } + } + else + forceFailed = true; + } + + // if all attempts failed in some way then clear because it won't get SMSG_LOOT_RESPONSE + if (keyFailed && skillFailed && forceFailed) + { + sLog.outDebug("[PlayerbotAI]: DoLoot attempts failed on [%s]", + go ? go->GetGOInfo()->name : c->GetCreatureInfo()->Name); + m_lootCurrent = ObjectGuid(); + // clear movement target, take next target on next update + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + } + } + } +} + +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::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) +{ + if (IsMoving()) + return; + + 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; + + // Send updates to world if chasing target or moving to point + MovementGeneratorType movementType = m_bot->GetMotionMaster()->GetCurrentMovementGeneratorType(); + if (movementType == CHASE_MOTION_TYPE || movementType == POINT_MOTION_TYPE) + { + float x, y, z; + m_bot->GetMotionMaster()->GetDestination(x, y, z); + if (x != m_destX || y != m_destY || z != m_destZ) + { + m_bot->SendMonsterMoveWithSpeed(x, y, z); + m_destX = x; + m_destY = y; + m_destZ = z; + } + } + + time_t currentTime = time(0); + if (currentTime < m_ignoreAIUpdatesUntilTime) + return; + + // default updates occur every two seconds + m_ignoreAIUpdatesUntilTime = time(0) + 2; + + 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_lootTargets.clear(); + m_lootCurrent = ObjectGuid(); + // 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(); + if (HasCollectFlag(COLLECT_FLAG_COMBAT)) + m_lootTargets.unique(); + else + m_lootTargets.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(); + + // have we been told to collect GOs + if (HasCollectFlag(COLLECT_FLAG_NEAROBJECT)) + { + findNearbyGO(); + // start looting if have targets + if (!m_lootTargets.empty()) + SetState(BOTSTATE_LOOTING); + } + } + } +} + +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 + SpellRanges::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(); + } + + uint32 target_type = TARGET_FLAG_UNIT; + + if (pSpellInfo->Effect[0] == SPELL_EFFECT_OPEN_LOCK) + target_type = TARGET_FLAG_OBJECT; + + if (pSpellInfo->Effect[0] == SPELL_EFFECT_OPEN_LOCK || + pSpellInfo->Effect[0] == SPELL_EFFECT_SKINNING) + { + if (!m_lootCurrent.IsEmpty()) + { + WorldPacket* const packet = new WorldPacket(CMSG_CAST_SPELL, 1 + 4 + 1 + 4 + 8); + *packet << uint8(0); // spells cast count; + *packet << spellId; + *packet << uint8(0); // unk_flags + *packet << uint32(target_type); + *packet << m_lootCurrent.WriteAsPacked(); + m_bot->GetSession()->QueuePacket(packet); // queue the packet to get around race condition + + if (target_type == TARGET_FLAG_OBJECT) + { + WorldPacket* const packetgouse = new WorldPacket(CMSG_GAMEOBJ_REPORT_USE, 8); + *packetgouse << uint64(m_lootCurrent.GetRawValue()); + m_bot->GetSession()->QueuePacket(packetgouse); // queue the packet to get around race condition + } + } + else + return false; + } + else + m_bot->CastSpell(pTarget, pSpellInfo, false); // actually cast spell + + 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); + } + + 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; +} + +Item* PlayerbotAI::FindKeyForLockValue(uint32 reqSkillValue) +{ + if (reqSkillValue <= 25 && m_bot->HasItemCount(SILVER_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(SILVER_SKELETON_KEY); + if (reqSkillValue <= 125 && m_bot->HasItemCount(GOLDEN_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(GOLDEN_SKELETON_KEY); + if (reqSkillValue <= 200 && m_bot->HasItemCount(TRUESILVER_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(TRUESILVER_SKELETON_KEY); + if (reqSkillValue <= 300 && m_bot->HasItemCount(ARCANITE_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(ARCANITE_SKELETON_KEY); + if (reqSkillValue <= 375 && m_bot->HasItemCount(TITANIUM_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(TITANIUM_SKELETON_KEY); + if (reqSkillValue <= 400 && m_bot->HasItemCount(COBALT_SKELETON_KEY, 1)) + return m_bot->GetItemByEntry(COBALT_SKELETON_KEY); + + return NULL; +} + +Item* PlayerbotAI::FindBombForLockValue(uint32 reqSkillValue) +{ + if (reqSkillValue <= 150 && m_bot->HasItemCount(SMALL_SEAFORIUM_CHARGE, 1)) + return m_bot->GetItemByEntry(SMALL_SEAFORIUM_CHARGE); + if (reqSkillValue <= 250 && m_bot->HasItemCount(LARGE_SEAFORIUM_CHARGE, 1)) + return m_bot->GetItemByEntry(LARGE_SEAFORIUM_CHARGE); + if (reqSkillValue <= 300 && m_bot->HasItemCount(POWERFUL_SEAFORIUM_CHARGE, 1)) + return m_bot->GetItemByEntry(POWERFUL_SEAFORIUM_CHARGE); + if (reqSkillValue <= 350 && m_bot->HasItemCount(ELEMENTAL_SEAFORIUM_CHARGE, 1)) + return m_bot->GetItemByEntry(ELEMENTAL_SEAFORIUM_CHARGE); + + 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::HasTool(uint32 TC) +{ + std::ostringstream out; + + switch (TC) + { + case TC_MINING_PICK: + + if (m_bot->HasItemTotemCategory(TC)) + return true; + else + out << "|cffffffffI do not have a pick!"; + break; + + case TC_SKINNING_KNIFE: + + if (m_bot->HasItemTotemCategory(TC)) + return true; + else + out << "|cffffffffI do not have a skinning knife!"; + break; + default: + out << "|cffffffffI do not know what tool that needs!"; + } + 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); + } +} + +// Build an hlink for spells +void PlayerbotAI::MakeSpellLink(const SpellEntry *sInfo, std::ostringstream &out, Player *player) +{ + LocaleConstant loc = LOCALE_enUS; + if (player) + loc = player->GetSession()->GetSessionDbcLocale(); + out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[loc] << "]|h|r"; +} + +void 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; + + // 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; + + // 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 +} + +void PlayerbotAI::extractSpellIdList(const std::string& text, BotSpellList& m_spellsToLearn) 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; + while (true) + { + int i = text.find("Hspell:", pos); + if (i == -1) + break; + + // DEBUG_LOG("extractSpellIdList 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) + break; + + // DEBUG_LOG("extractSpellIdList second endpos : %u pos : %u",endPos,pos); + std::string idC = text.substr(pos, endPos - pos); // 26 - 23 + uint32 spellId = atol(idC.c_str()); + pos = endPos; // end + + if (spellId) + m_spellsToLearn.push_back(spellId); + } +} + +void PlayerbotAI::extractGOinfo(const std::string& text, std::list& m_lootTargets) const +{ + + // Link format + // |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << "|h[" << gInfo->name << "]|h|r"; + // |cFFFFFF00|Hfound:9582:1731|h[Copper Vein]|h|r + + uint8 pos = 0; + while (true) + { + // extract GO guid + int i = text.find("Hfound:", pos); // base H = 11 + if (i == -1) // break if error + break; + + pos = i + 7; //start of window in text 11 + 7 = 18 + int endPos = text.find(':', pos); // end of window in text 22 + if (endPos == -1) //break if error + break; + std::string guidC = text.substr(pos, endPos - pos); // get string within window i.e guid 22 - 18 = 4 + uint32 guid = atol(guidC.c_str()); // convert ascii to long int + + // extract GO entry + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + break; + + std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry + uint32 entry = atol(entryC.c_str()); // convert ascii to float + + ObjectGuid lootCurrent = ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid); + + if (guid) + m_lootTargets.push_back(lootCurrent.GetRawValue()); + } +} + +// 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; + + if (m_bot->GetTrader() && m_bot->GetTradeData()->HasItem(pItem->GetObjectGuid())) + 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; + + if (m_bot->GetTrader() && m_bot->GetTradeData()->HasItem(pItem->GetObjectGuid())) + continue; + + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } + } +} + +void PlayerbotAI::findNearbyGO() +{ + if (m_collectObjects.empty()) + return; + + std::list tempTargetGOList; + float radius = 20.0f; + + for (BotLootEntry::iterator itr = m_collectObjects.begin(); itr != m_collectObjects.end(); ++itr) + { + uint32 entry = *(itr); + + // search for GOs with entry, within range of m_bot + MaNGOS::GameObjectEntryInPosRangeCheck go_check(*m_bot, entry, m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), radius); + MaNGOS::GameObjectListSearcher checker(tempTargetGOList, go_check); + Cell::VisitGridObjects(m_bot, checker, radius); + + // no objects found, continue to next entry + if (tempTargetGOList.empty()) + continue; + + // add any objects found to our lootTargets + for (std::list::iterator iter = tempTargetGOList.begin(); iter != tempTargetGOList.end(); ++iter) + { + GameObject* go = (*iter); + if (go->isSpawned()) + m_lootTargets.push_back(go->GetObjectGuid().GetRawValue()); + } + } +} + +// 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 | TARGET_FLAG_OBJECT)) + *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_lootTargets.clear(); + m_lootCurrent = ObjectGuid(); + 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 project: 20:50 02/12/10 rev.4 item in world and wait until ordered to follow + else if (text.size() > 2 && text.substr(0, 2) == "f " || text.size() > 5 && text.substr(0, 5) == "find ") + { + extractGOinfo(text, m_lootTargets); + + m_lootCurrent = m_lootTargets.front(); + m_lootTargets.pop_front(); + + GameObject *go = m_bot->GetMap()->GetGameObject(m_lootCurrent); + if (!go) + { + m_lootTargets.clear(); + m_lootCurrent = ObjectGuid(); + return; + } + + SetMovementOrder(MOVEMENT_STAY); + m_bot->GetMotionMaster()->MovePoint(go->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ()); + m_lootTargets.clear(); + m_lootCurrent = ObjectGuid(); + } + + // get project: 20:50 02/12/10 rev.4 compact edition, handles multiple linked gameobject & improves visuals + else if (text.size() > 2 && text.substr(0, 2) == "g " || text.size() > 4 && text.substr(0, 4) == "get ") + { + extractGOinfo(text, m_lootTargets); + SetState(BOTSTATE_LOOTING); + } + else if (text == "g" || text == "get") // get a selected lootable corpse + { + ObjectGuid getOnGuid = fromPlayer.GetSelectionGuid(); + if (!getOnGuid.IsEmpty()) + { + Creature *c = m_bot->GetMap()->GetCreature(getOnGuid); + if (!c) + return; + + if (c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE) || c->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) + { + m_lootTargets.push_back(getOnGuid.GetRawValue()); + SetState(BOTSTATE_LOOTING); + } + else + TellMaster("Target is not lootable."); + } + else + { + TellMaster("No target is selected."); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + return; + } + + // Handle all collection related commands here + else if (text.size() >= 7 && text.substr(0, 7) == "collect") + { + std::string part = ""; + std::string subcommand = ""; + + if (text.size() > 7 && text.substr(0, 8) == "collect ") + part = text.substr(8); // Truncate 'collect ' part + + while (true) + { + if (part.find(" ") > 0) + { + subcommand = part.substr(0, part.find(" ")); + if (part.size() > subcommand.size()) + part = part.substr(subcommand.size() + 1); + } + else + subcommand = part; + + if (subcommand == "combat") + SetCollectFlag(COLLECT_FLAG_COMBAT); + else if (subcommand == "loot") + SetCollectFlag(COLLECT_FLAG_LOOT); + else if (subcommand == "quest") + SetCollectFlag(COLLECT_FLAG_QUEST); + else if (subcommand == "profession" || subcommand == "skill") + SetCollectFlag(COLLECT_FLAG_PROFESSION); + else if (subcommand == "skin" && m_bot->HasSkill(SKILL_SKINNING)) + SetCollectFlag(COLLECT_FLAG_SKIN); + else if (subcommand == "objects" || subcommand == "nearby") + { + SetCollectFlag(COLLECT_FLAG_NEAROBJECT); + if (!HasCollectFlag(COLLECT_FLAG_NEAROBJECT)) + m_collectObjects.clear(); + } + else if (subcommand == "none" || subcommand == "nothing") + { + m_collectionFlags = 0; + m_collectObjects.clear(); + } + else + { + std::string collout = ""; + if (m_bot->HasSkill(SKILL_SKINNING)) + collout += ", skin"; + // TODO: perhaps change the command syntax, this way may be lacking in ease of use + TellMaster("Collect ?: none, combat, loot, quest, profession, objects" + collout); + break; + } + if (part == subcommand) + break; + } + + std::string collset = ""; + if (HasCollectFlag(COLLECT_FLAG_LOOT)) + collset += ", all loot"; + if (HasCollectFlag(COLLECT_FLAG_PROFESSION)) + collset += ", profession"; + if (HasCollectFlag(COLLECT_FLAG_QUEST)) + collset += ", quest"; + if (HasCollectFlag(COLLECT_FLAG_SKIN)) + collset += ", skin"; + if (HasCollectFlag(COLLECT_FLAG_COMBAT)) + collset += " items after combat"; + else + collset += " items"; + + if (HasCollectFlag(COLLECT_FLAG_NEAROBJECT)) + { + collset += " and nearby objects ("; + if (!m_collectObjects.empty()) + { + std::string strobjects = ""; + for (BotLootEntry::iterator itr = m_collectObjects.begin(); itr != m_collectObjects.end(); ++itr) + { + uint32 objectentry = *(itr); + GameObjectInfo const * gInfo = ObjectMgr::GetGameObjectInfo(objectentry); + strobjects += ", "; + strobjects += gInfo->name; + } + collset += strobjects.substr(2); + } + else + collset += "use survey and get to set"; + collset += ")"; + } + + if (collset.length() > 1) + TellMaster("I'm collecting " + collset.substr(2)); + else + TellMaster("I'm collecting nothing."); + } + + 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: 20:50 02/12/10 rev.4 compact edition + 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(); + + GameObject *go = m_bot->GetMap()->GetGameObject(ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guid)); + if (!go) + continue; + + if (!go->isSpawned()) + continue; + + detectout << "|cFFFFFF00|Hfound:" << guid << ":" << entry << ":" << "|h[" << go->GetGOInfo()->name << "]|h|r"; + ++count; + } while (result->NextRow()); + + delete result; + } + SendWhisper(detectout.str().c_str(), fromPlayer); + } + + // Handle class & professions training: + // skill -- Lists bot(s) Primary profession skills. + // skill train -- List available class or profession (Primary or Secondary) skills & spells, from selected trainer. + // skill learn [HLINK][HLINK] .. -- Learn selected skill and spells, from selected trainer ([HLINK] from skill train). + // skill unlearn [HLINK][HLINK] .. -- Unlearn selected primary profession skill(s) and all associated spells ([HLINK] from skill) + else if (text.size() >= 5 && text.substr(0, 5) == "skill") + { + uint32 rank[8] = {0, 75, 150, 225, 300, 375, 450, 525}; + + std::ostringstream msg; + + std::string part = ""; + std::string subcommand = ""; + + if (text.size() > 5 && text.substr(0, 6) == "skill ") + part = text.substr(6); // Truncate 'skill ' part + + if (part.find(" ") > 0) + { + subcommand = part.substr(0, part.find(" ")); + if (part.size() > subcommand.size()) + part = part.substr(subcommand.size() + 1); + } + else + subcommand = part; + + if (subcommand == "train" || subcommand == "learn") + { + uint32 totalCost = 0; + + Unit* unit = ObjectAccessor::GetUnit(*m_bot, fromPlayer.GetSelectionGuid()); + if (!unit) + { + SendWhisper("Please select the trainer!", fromPlayer); + return; + } + + if (!unit->isTrainer()) + { + SendWhisper("This is not a trainer!", fromPlayer); + return; + } + + Creature *creature = m_bot->GetMap()->GetCreature(fromPlayer.GetSelectionGuid()); + if (!creature) + return; + + if (!creature->IsTrainerOf(m_bot, false)) + { + SendWhisper("This trainer can not teach me anything!", fromPlayer); + return; + } + + // check present spell in trainer spell list + TrainerSpellData const* cSpells = creature->GetTrainerSpells(); + TrainerSpellData const* tSpells = creature->GetTrainerTemplateSpells(); + if (!cSpells && !tSpells) + { + SendWhisper("No spells can be learnt from this trainer", fromPlayer); + return; + } + + // reputation discount + float fDiscountMod = m_bot->GetReputationPriceDiscount(creature); + + // Handle: Learning class or profession (primary or secondary) skill & spell(s) for selected trainer, skill learn [HLINK][HLINK][HLINK].. ([HLINK] from skill train) + if (subcommand == "learn") + { + msg << "I have learnt the following spells:\r"; + uint32 totalSpellLearnt = 0; + m_spellsToLearn.clear(); + extractSpellIdList(part, m_spellsToLearn); + for (std::list::iterator it = m_spellsToLearn.begin(); it != m_spellsToLearn.end(); advance(it, 1)) + { + uint32 spellId = *it; + + if (!spellId) + break; + + TrainerSpell const* trainer_spell = cSpells->Find(spellId); + if (!trainer_spell) + trainer_spell = tSpells->Find(spellId); + + if (!trainer_spell || !trainer_spell->learnedSpell) + continue; + + // apply reputation discount + uint32 cost = uint32(floor(trainer_spell->spellCost * fDiscountMod)); + // check money requirement + if (m_bot->GetMoney() < cost) + continue; + + m_bot->ModifyMoney(-int32(cost)); + // learn explicitly or cast explicitly + if (trainer_spell->IsCastable()) + m_bot->CastSpell(m_bot, trainer_spell->spell, true); + else + m_bot->learnSpell(spellId, false); + ++totalSpellLearnt; + totalCost += cost; + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + MakeSpellLink(pSpellInfo, msg, &fromPlayer); + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + msg << " "; + if (gold > 0) + msg << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + msg << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + msg << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t\r"; + } + uint32 gold = uint32(totalCost / 10000); + totalCost -= (gold * 10000); + uint32 silver = uint32(totalCost / 100); + totalCost -= (silver * 100); + msg << "Total of " << totalSpellLearnt << " spell"; + if (totalSpellLearnt != 1) msg << "s"; + msg << " learnt, "; + if (gold > 0) + msg << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + msg << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + msg << totalCost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t spent."; + } + // Handle: List class or profession skill & spells for selected trainer, skill train + else + if (subcommand == "train") + { + msg << "The spells I can learn and their cost:\r"; + + TrainerSpellData const* trainer_spells = cSpells; + if (!trainer_spells) + trainer_spells = tSpells; + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell) + break; + + if (!tSpell->learnedSpell && !m_bot->IsSpellFitByClassAndRace(tSpell->learnedSpell)) + continue; + + if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(tSpell->learnedSpell) && m_bot->HasSpell(tSpell->learnedSpell)) + continue; + + TrainerSpellState state = m_bot->GetTrainerSpellState(tSpell); + if (state != TRAINER_SPELL_GREEN) + continue; + + uint32 spellId = tSpell->spell; + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod)); + totalCost += cost; + + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + MakeSpellLink(pSpellInfo, msg, &fromPlayer); + msg << " "; + if (gold > 0) + msg << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + msg << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + msg << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t\r"; + } + uint32 moneyDiff = m_bot->GetMoney() - totalCost; + if (moneyDiff >= 0) + { + // calculate how much money bot has + uint32 gold = uint32(moneyDiff / 10000); + moneyDiff -= (gold * 10000); + uint32 silver = uint32(moneyDiff / 100); + moneyDiff -= (silver * 100); + msg << " "; + if (gold > 0) + msg << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + msg << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + msg << moneyDiff << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t left."; + } + else + { + moneyDiff *= -1; + uint32 gold = uint32(moneyDiff / 10000); + moneyDiff -= (gold * 10000); + uint32 silver = uint32(moneyDiff / 100); + moneyDiff -= (silver * 100); + msg << "I need "; + if (gold > 0) + msg << " " << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + msg << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + msg << moneyDiff << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t more to learn all the spells!"; + } + } + } + // Handle: Unlearning selected primary profession skill(s) and all associated spells, skill unlearn [HLINK][HLINK].. ([HLINK] from skill) + else + if (subcommand == "unlearn") + { + m_spellsToLearn.clear(); + extractSpellIdList(part, m_spellsToLearn); + } + // Handle: Lists bot(s) primary profession skills, skill. + else + { + m_spellsToLearn.clear(); + m_bot->skill(m_spellsToLearn); + msg << "My Primary Professions are: "; + } + + for (std::list::iterator it = m_spellsToLearn.begin(); it != m_spellsToLearn.end(); ++it) + { + if (sSpellMgr.IsPrimaryProfessionSpell(*it) && subcommand != "learn") + { + SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(*it); + + uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(*it); + if (!prev_spell) // first rank, remove skill + GetPlayer()->SetSkill(spellLearnSkill->skill, 0, 0); + else + { + // search prev. skill setting by spell ranks chain + SpellLearnSkillNode const* prevSkill = sSpellMgr.GetSpellLearnSkill(prev_spell); + while (!prevSkill && prev_spell) + { + prev_spell = sSpellMgr.GetPrevSpellInChain(prev_spell); + prevSkill = sSpellMgr.GetSpellLearnSkill(sSpellMgr.GetFirstSpellInChain(prev_spell)); + } + if (!prevSkill) // not found prev skill setting, remove skill + GetPlayer()->SetSkill(spellLearnSkill->skill, 0, 0); + } + } + else + if (IsPrimaryProfessionSkill(*it)) + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + { + SkillLineAbilityEntry const *skillLine = sSkillLineAbilityStore.LookupEntry(j); + if (!skillLine) + continue; + + // has skill + if (skillLine->skillId == *it && skillLine->learnOnGetSkill == 0) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(skillLine->spellId); + if (!spellInfo) + continue; + + if (m_bot->GetSkillValue(*it) <= rank[sSpellMgr.GetSpellRank(skillLine->spellId)] && m_bot->HasSpell(skillLine->spellId)) + { + // sLog.outDebug("(%u)(%u)(%u):",skillLine->spellId, rank[sSpellMgr.GetSpellRank(skillLine->spellId)], m_bot->GetSkillValue(*it)); + MakeSpellLink(spellInfo, msg, &fromPlayer); + break; + } + } + } + } + SendWhisper(msg.str(), fromPlayer); + m_spellsToLearn.clear(); + m_bot->GetPlayerbotAI()->GetClassAI(); + } + + // stats project: 11:30 15/12/10 rev.2 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(); + } + + } + + // estimate how much item damage the bot has + uint32 copper = EstRepairAll(); + 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 |cff00ff00"; + out << totalfree << " |h|cffffffff bag slots,|h" << " |cff00ff00"; + if (gold > 0) + out << "|r|cff00ff00" << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + out << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + out << copper << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t"; + + // calculate how much money bot has + copper = m_bot->GetMoney(); + gold = uint32(copper / 10000); + copper -= (gold * 10000); + silver = uint32(copper / 100); + copper -= (silver * 100); + + out << "|h|cffffffff item damage & has " << "|r|cff00ff00"; + if (gold > 0) + out << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver > 0) + out << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + out << copper << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t"; + 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, collect"; + 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..6954005f9 --- /dev/null +++ b/src/game/playerbot/PlayerbotAI.h @@ -0,0 +1,414 @@ +#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 75.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 +}; + +enum ProfessionSpells +{ + ALCHEMY_1 = 2259, + BLACKSMITHING_1 = 2018, + COOKING_1 = 2550, + ENCHANTING_1 = 7411, + ENGINEERING_1 = 4036, + FIRST_AID_1 = 3273, + FISHING_1 = 7620, + HERB_GATHERING_1 = 2366, + INSCRIPTION_1 = 45357, + JEWELCRAFTING_1 = 25229, + MINING_1 = 2575, + SKINNING_1 = 8613, + TAILORING_1 = 3908 +}; + +enum NotableItems +{ + // Skeleton Keys + SILVER_SKELETON_KEY = 15869, + GOLDEN_SKELETON_KEY = 15870, + TRUESILVER_SKELETON_KEY = 15871, + ARCANITE_SKELETON_KEY = 15872, + TITANIUM_SKELETON_KEY = 43853, + COBALT_SKELETON_KEY = 43854, + // Lock Charges + SMALL_SEAFORIUM_CHARGE = 4367, + LARGE_SEAFORIUM_CHARGE = 4398, + POWERFUL_SEAFORIUM_CHARGE = 18594, + ELEMENTAL_SEAFORIUM_CHARGE = 23819 +}; + +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 CollectionFlags + { + COLLECT_FLAG_NOTHING = 0x00, // skip looting of anything + COLLECT_FLAG_COMBAT = 0x01, // loot after combat + COLLECT_FLAG_QUEST = 0x02, // quest and needed items + COLLECT_FLAG_PROFESSION = 0x04, // items related to skills + COLLECT_FLAG_LOOT = 0x08, // all loot on corpses + COLLECT_FLAG_SKIN = 0x10, // skin creatures if available + COLLECT_FLAG_NEAROBJECT = 0x20 // collect specified nearby object + }; + + enum MovementOrderType + { + MOVEMENT_NONE = 0x00, + MOVEMENT_FOLLOW = 0x01, + MOVEMENT_STAY = 0x02 + }; + + typedef std::map BotNeedItem; + typedef std::list BotLootCreature; + typedef std::list BotLootEntry; + typedef std::list BotSpellList; + + // 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; + typedef std::map SpellRanges; + +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 + void extractSpellId(const std::string& text, uint32 &spellId) const; + + // extract spellids from links to list + void extractSpellIdList(const std::string& text, BotSpellList& m_spellsToLearn) 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 + void extractGOinfo(const std::string& text, std::list& m_lootTargets) 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; + // finds nearby game objects that are specified in m_collectObjects then adds them to the m_lootTargets list + void findNearbyGO(); + + void MakeSpellLink(const SpellEntry *sInfo, std::ostringstream &out, Player* player = NULL); + + // 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 HasTool(uint32 TC); + 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* FindKeyForLockValue(uint32 reqSkillValue); + Item* FindBombForLockValue(uint32 reqSkillValue); + 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(); + + bool HasCollectFlag(uint8 flag) { return m_collectionFlags & flag; } + void SetCollectFlag(uint8 flag) + { + if (HasCollectFlag(flag)) m_collectionFlags &= ~flag; + else m_collectionFlags |= flag; + } + + 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 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_lootTargets; // list of creatures + BotSpellList m_spellsToLearn; // list of spells + ObjectGuid m_lootCurrent; // current remains of interest + ObjectGuid m_lootPrev; // previous loot + BotLootEntry m_collectObjects; // object entries searched for in findNearbyGO + + uint8 m_collectionFlags; // what the bot should look for to loot + + 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? + + uint32 FISHING, + HERB_GATHERING, + MINING, + SKINNING; + + SpellRanges m_spellRangeMap; + + float m_destX, m_destY, m_destZ; // latest coordinates for chase and point movement types +}; + +#endif diff --git a/src/game/playerbot/PlayerbotClassAI.cpp b/src/game/playerbot/PlayerbotClassAI.cpp new file mode 100644 index 000000000..0309c1b64 --- /dev/null +++ b/src/game/playerbot/PlayerbotClassAI.cpp @@ -0,0 +1,18 @@ +#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..f0fc6e500 --- /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..873e1ab1b --- /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..051c1c3c4 --- /dev/null +++ b/src/game/playerbot/PlayerbotMageAI.cpp @@ -0,0 +1,460 @@ + +#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..1d8270af4 --- /dev/null +++ b/src/game/playerbot/PlayerbotMgr.cpp @@ -0,0 +1,821 @@ +#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.FollowDistanceMax", 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) +{ + bot->SetMap(m_master->GetMap()); + // 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; + + if(sConfig.GetBoolDefault("PlayerbotAI.DisableBots", false)) + 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 + { + // 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::skill(std::list& m_spellsToLearn) +{ + for (SkillStatusMap::const_iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr) + { + if (itr->second.uState == SKILL_DELETED) + continue; + + uint32 pskill = itr->first; + + m_spellsToLearn.push_back(pskill); + } +} + +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; +} + +bool ChatHandler::HandlePlayerbotCommand(char* args) +{ + 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, " "); + ObjectGuid targetGUID = m_session->GetPlayer()->GetSelectionGuid(); + if (!targetChar && targetGUID.IsEmpty()) + { + 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.Set(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 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..62f55b7ac --- /dev/null +++ b/src/game/playerbot/PlayerbotPaladinAI.cpp @@ -0,0 +1,517 @@ +/* + 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..c9269ad6b --- /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 = 21084, + 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..e8f79eee2 --- /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..c40ed13d0 --- /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 && 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..37e5d916a --- /dev/null +++ b/src/game/playerbot/PlayerbotWarlockAI.cpp @@ -0,0 +1,564 @@ + +#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..b2bf0ac35 --- /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..edada7484 --- /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 && 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..ff6d2becb --- /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 = 1 +PlayerbotAI.DebugWhisper = 0 +PlayerbotAI.FollowDistanceMin = 0.5 +PlayerbotAI.FollowDistanceMax = 1.0 +PlayerbotAI.MaxNumBots = 9 +PlayerbotAI.RestrictBotLevel = 80 +PlayerbotAI.BotguyQuests = "" +PlayerbotAI.BotguyCost = 0