diff --git a/DoubleXP.patch b/DoubleXP.patch new file mode 100644 index 0000000..6949412 --- /dev/null +++ b/DoubleXP.patch @@ -0,0 +1,106 @@ +From 1142cdda914e7660a5ce8f47d59d3d0f2ec15b01 Mon Sep 17 00:00:00 2001 +From: Doxramos +Date: Tue, 6 Oct 2015 15:38:13 -0700 +Subject: [PATCH 1/2] DoubleXP + +--- + src/server/scripts/Custom/DoubleXP.h | 40 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + create mode 100644 src/server/scripts/Custom/DoubleXP.h + +diff --git a/src/server/scripts/Custom/DoubleXP.h b/src/server/scripts/Custom/DoubleXP.h +new file mode 100644 +index 0000000..02c4770 +--- /dev/null ++++ b/src/server/scripts/Custom/DoubleXP.h +@@ -0,0 +1,40 @@ ++#include "ScriptMgr.h" ++#include "Player.h" ++#include "Chat.h" ++#include "World.h" ++#include "/usr/include/boost/date_time.hpp" ++ ++ ++class XpWeekend : public PlayerScript ++{ ++ public: ++ XpWeekend() : PlayerScript("XpWeekend") { } ++ void OnGiveXP(Player* player, uint32& amount, Unit* victim)override ++{ ++ boost::gregorian::date date(boost::gregorian::day_clock::local_day()); ++ auto day = date.day_of_week(); ++ if (day == boost::date_time::Friday || ++ day == boost::date_time::Saturday || ++ day == boost::date_time::Sunday) { ++ ++ amount = amount*2; } ++} ++ void OnLogin(Player* player, bool firstLogin) ++ { ++ boost::gregorian::date date(boost::gregorian::day_clock::local_day()); ++ auto day = date.day_of_week(); ++ if (day == boost::date_time::Friday || ++ day == boost::date_time::Saturday || ++ day == boost::date_time::Sunday) { ++ ++ ChatHandler(player->GetSession()).PSendSysMessage("Double XP is going on now!"); ++ } ++ ++ } ++ ++}; ++ ++void AddSC_XpWeekend() ++{ ++ new XpWeekend(); ++} +\ No newline at end of file +-- +2.6.1.windows.1 + + +From ff5a95e4ead0ce6cb58edb171bb4d8dc8acd38df Mon Sep 17 00:00:00 2001 +From: Doxramos +Date: Tue, 6 Oct 2015 15:41:34 -0700 +Subject: [PATCH 2/2] Double XP + +--- + src/server/game/Scripting/ScriptLoader.cpp | 3 ++- + src/server/scripts/Custom/CMakeLists.txt | 1 + + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index 7c4b117..85369e7 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1415,13 +1415,14 @@ void AddBattlegroundScripts() + + #ifdef SCRIPTS + /* This is where custom scripts' loading functions should be declared. */ +- ++void AddSC_XPWeekend(); + #endif + + void AddCustomScripts() + { + #ifdef SCRIPTS + /* This is where custom scripts should be added. */ ++ AddSC_XPWeekend(); + + #endif + } +diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt +index 5218f76..8581490 100644 +--- a/src/server/scripts/Custom/CMakeLists.txt ++++ b/src/server/scripts/Custom/CMakeLists.txt +@@ -13,6 +13,7 @@ + set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} + # ${sources_Custom} ++ Custom/DoubleXP.h + ) + + message(" -> Prepared: Custom") +-- +2.6.1.windows.1 + diff --git a/LANG_UNIVERSAL_en_zona.diff b/LANG_UNIVERSAL_en_zona.diff new file mode 100644 index 0000000..a5f471b --- /dev/null +++ b/LANG_UNIVERSAL_en_zona.diff @@ -0,0 +1,20 @@ +--- /home/marti/sources/test_bgs_mixtas/src/server/game/Handlers/ChatHandler.cpp 2013-03-08 20:08:04.000000000 +0100 ++++ test_challenge/src/server/game/Handlers/ChatHandler.cpp 2013-03-10 19:24:11.966856051 +0100 +@@ -1074,6 +1075,7 @@ + sLog->outError(LOG_FILTER_NETWORKIO, "Player %s (GUID: %u) sent a chatmessage with an invalid language/message type combination", + GetPlayer()->GetName().c_str(), GetPlayer()->GetGUIDLow()); + + recvData.rfinish(); + return; + } + } + // LANG_ADDON should not be changed nor be affected by flood control + else + { ++ //Custom - LANG_UNIVERSAL in Duelzone and Mountshop. ++ if(sender->GetZoneId() == 3519 || sender->GetZoneId() == 33 && !HasPermission(RBAC_PERM_TWO_SIDE_INTERACTION_CHAT )) ++ lang = LANG_UNIVERSAL; + // send in universal language if player in .gmon mode (ignore spell effects) + if (sender->isGameMaster()) + lang = LANG_UNIVERSAL; + else \ No newline at end of file diff --git a/PermMorphScale.diff b/PermMorphScale.diff new file mode 100644 index 0000000..b3f4634 --- /dev/null +++ b/PermMorphScale.diff @@ -0,0 +1,573 @@ +From 443028bd1db4b8ff86496393e0a90eadb628c50d Mon Sep 17 00:00:00 2001 +From: Jcarter4562 +Date: Thu, 19 Sep 2013 23:20:37 -0400 +Subject: [PATCH] Permanent Morph and Scale System + +--- + sql/MorphScale/characters_scale_morph.sql | 19 ++ + sql/MorphScale/npc_moprher.sql | 2 + + src/server/game/Entities/Player/Player.cpp | 18 +- + src/server/game/Handlers/CharacterHandler.cpp | 19 +- + src/server/game/Scripting/ScriptLoader.cpp | 2 + + src/server/scripts/Custom/CMakeLists.txt | 5 +- + src/server/scripts/Custom/npc_morpher.cpp | 367 ++++++++++++++++++++++++++ + src/server/worldserver/worldserver.conf.dist | 31 +++ + 8 files changed, 458 insertions(+), 5 deletions(-) + create mode 100644 sql/MorphScale/characters_scale_morph.sql + create mode 100644 sql/MorphScale/npc_moprher.sql + create mode 100644 src/server/scripts/Custom/npc_morpher.cpp + +diff --git a/sql/MorphScale/characters_scale_morph.sql b/sql/MorphScale/characters_scale_morph.sql +new file mode 100644 +index 0000000..4fb0971 +--- /dev/null ++++ b/sql/MorphScale/characters_scale_morph.sql +@@ -0,0 +1,19 @@ ++SET FOREIGN_KEY_CHECKS=0; ++-- ---------------------------- ++-- Table structure for character_morph ++-- ---------------------------- ++CREATE TABLE `character_morph` ( ++`guid` int(11) NOT NULL DEFAULT '0', ++`morph` int(11) DEFAULT NULL, ++PRIMARY KEY (`guid`) ++) ENGINE=MyISAM DEFAULT CHARSET=latin1; ++ ++-- ---------------------------- ++-- Table structure for character_scale ++-- ---------------------------- ++CREATE TABLE `character_scale` ( ++`guid` int(11) NOT NULL, ++`scale` float NOT NULL, ++`comment` text, ++PRIMARY KEY (`guid`) ++) ENGINE=InnoDB DEFAULT CHARSET=latin1; +\ No newline at end of file +diff --git a/sql/MorphScale/npc_moprher.sql b/sql/MorphScale/npc_moprher.sql +new file mode 100644 +index 0000000..7ce4ed1 +--- /dev/null ++++ b/sql/MorphScale/npc_moprher.sql +@@ -0,0 +1,2 @@ ++REPLACE INTO `creature_template` (`entry`, `difficulty_entry_1`, `difficulty_entry_2`, `difficulty_entry_3`, `KillCredit1`, `KillCredit2`, `modelid1`, `modelid2`, `modelid3`, `modelid4`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction_A`, `faction_H`, `npcflag`, `speed_walk`, `speed_run`, `scale`, `rank`, `mindmg`, `maxdmg`, `dmgschool`, `attackpower`, `dmg_multiplier`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `unit_flags2`, `dynamicflags`, `family`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `minrangedmg`, `maxrangedmg`, `rangedattackpower`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `resistance1`, `resistance2`, `resistance3`, `resistance4`, `resistance5`, `resistance6`, `spell1`, `spell2`, `spell3`, `spell4`, `spell5`, `spell6`, `spell7`, `spell8`, `PetSpellDataId`, `VehicleId`, `mingold`, `maxgold`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `Health_mod`, `Mana_mod`, `Armor_mod`, `RacialLeader`, `questItem1`, `questItem2`, `questItem3`, `questItem4`, `questItem5`, `questItem6`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`, `WDBVerified`) VALUES ++(90000, 0, 0, 0, 0, 0, 21752, 0, 0, 0, 'Morph Master', 'Permanant Morpher', '', 0, 83, 83, 2, 35, 35, 1, 1.4, 1.14286, 1.5, 4, 228, 298, 0, 500, 1, 1500, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 3, 1, 4.8, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 'npc_morpher', 12340); +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 68d30cd..d69e388 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -17032,8 +17032,22 @@ bool Player::LoadFromDB(uint32 guid, SQLQueryHolder *holder) + // set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise) + SetByteValue(PLAYER_FIELD_BYTES, 2, fields[65].GetUInt8()); + +- InitDisplayIds(); +- ++ // Permanant Morph Modification Start ++ //InitDisplayIds(); <- Old Setting ++ ++ QueryResult result2 = CharacterDatabase.PQuery("SELECT morph FROM character_morph WHERE guid = %u", guid); ++ if(result2) ++ { ++ Field *fields2 = result2->Fetch(); ++ SetNativeDisplayId(fields2[0].GetUInt32()); ++ SetDisplayId(fields2[0].GetUInt32()); ++ } ++ else ++ { ++ InitDisplayIds(); ++ } ++ // Permanant Morph Modification End ++ + // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory) + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { +diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp +index 9e1dbc1..1467994 100644 +--- a/src/server/game/Handlers/CharacterHandler.cpp ++++ b/src/server/game/Handlers/CharacterHandler.cpp +@@ -1007,8 +1007,23 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) + + if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST)) + pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST); +- +- // show time before shutdown if shutdown planned. ++ ++ // Start Permanent Scale system ++ QueryResult result3 = CharacterDatabase.PQuery("SELECT scale FROM character_scale WHERE guid = %u", pCurrChar->GetGUID()); ++ if(result3) ++ { ++ Field* fields3 = result3->Fetch(); ++ float plrScale; ++ plrScale = fields3[0].GetFloat(); ++ pCurrChar->SetFloatValue(OBJECT_FIELD_SCALE_X, plrScale); ++ } ++ else ++ { ++ CharacterDatabase.PExecute("REPLACE INTO `character_scale` VALUES (%u, 1, 'Default Scale');", pCurrChar->GetGUID()); ++ } ++ // End Permanent Scale System ++ ++ // show time before shutdown if shutdown planned. + if (sWorld->IsShuttingDown()) + sWorld->ShutdownMsg(true, pCurrChar); + +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index 31323a6..b7fe324 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1394,6 +1394,7 @@ void AddBattlegroundScripts() + + #ifdef SCRIPTS + /* This is where custom scripts' loading functions should be declared. */ ++void AddSC_npc_morpher(); + + #endif + +@@ -1401,6 +1402,7 @@ void AddCustomScripts() + { + #ifdef SCRIPTS + /* This is where custom scripts should be added. */ ++AddSC_npc_morpher(); + + #endif + } +diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt +index 99cf026..0236948 100644 +--- a/src/server/scripts/Custom/CMakeLists.txt ++++ b/src/server/scripts/Custom/CMakeLists.txt +@@ -8,8 +8,11 @@ + # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + ++file(GLOB_RECURSE sources_Custom Custom/*.cpp Custom/*.h) ++ + set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} ++ ${sources_Custom} + ) + +-message(" -> Prepared: Custom") ++message(" -> Prepared: Custom") +\ No newline at end of file +diff --git a/src/server/scripts/Custom/npc_morpher.cpp b/src/server/scripts/Custom/npc_morpher.cpp +new file mode 100644 +index 0000000..e8d2d04 +--- /dev/null ++++ b/src/server/scripts/Custom/npc_morpher.cpp +@@ -0,0 +1,367 @@ ++/* ++* GOSSIP_ICON_CHAT = 0 White chat bubble ++* GOSSIP_ICON_VENDOR = 1 Brown bag ++* GOSSIP_ICON_TAXI = 2 Flight ++* GOSSIP_ICON_TRAINER = 3 Book ++* GOSSIP_ICON_INTERACT_1 = 4 Interaction wheel ++* GOSSIP_ICON_INTERACT_2 = 5 Interaction wheel ++* GOSSIP_ICON_MONEY_BAG = 6 Brown bag with yellow dot ++* GOSSIP_ICON_TALK = 7 White chat bubble with black dots ++* GOSSIP_ICON_TABARD = 8 Tabard ++* GOSSIP_ICON_BATTLE = 9 Two swords ++* GOSSIP_ICON_DOT = 10 Yellow dot ++* GOSSIP_ICON_CHAT_11 = 11, White chat bubble ++* GOSSIP_ICON_CHAT_12 = 12, White chat bubble ++* GOSSIP_ICON_CHAT_13 = 13, White chat bubble ++* GOSSIP_ICON_UNK_14 = 14, INVALID - DO NOT USE ++* GOSSIP_ICON_UNK_15 = 15, INVALID - DO NOT USE ++* GOSSIP_ICON_CHAT_16 = 16, White chat bubble ++* GOSSIP_ICON_CHAT_17 = 17, White chat bubble ++* GOSSIP_ICON_CHAT_18 = 18, White chat bubble ++* GOSSIP_ICON_CHAT_19 = 19, White chat bubble ++* GOSSIP_ICON_CHAT_20 = 20, White chat bubble ++*/ ++ ++#include "ScriptPCH.h" ++#include "Config.h" ++ ++#define EMOTE_FUNCTION_OFF "Function Disable!" ++ ++enum MenuStructure ++{ ++ MAIN_MENU = 1, ++ MORPH_MENU = 2, ++ SCALE_MENU = 3, ++}; ++ ++enum Actions ++{ ++ // Morph Actions ++ ACTION_NAGA_LORD = 10, ++ ACTION_WORGEN = 11, ++ ACTION_PANDAREN_MONK = 12, ++ ACTION_ALGALON = 13, ++ ACTION_RUARZOG = 14, ++ ACTION_DEMORPH = 15, ++ ACTION_UNDEAD_MASKED = 16, ++ ACTION_SCARY_DWARF = 17, ++ ACTION_UNDEAD_SHADES = 18, ++ ACTION_VIKING = 19, ++ ACTION_MINOTAUR = 20, ++ ACTION_LICHKING = 21, ++ ++ // Scaled Actions ++ ACTION_SCALE_VRYKUL_DEFAULT = 23, ++ ACTION_SCALE_ONE = 24, ++ ACTION_SCALE_TWO = 25, ++ ACTION_SCALE_THREE = 26, ++ ACTION_SCALE_FOUR = 27, ++ ACTION_SCALE_FIVE = 28, ++ ACTION_SCALE_ALGALON_FIX = 29, ++}; ++ ++enum DisplayId ++{ ++ DISPLAY_ID_NAGA_LORD = 13031, // Set Scale 0.30 ++ DISPLAY_ID_WORGEN = 657, // set Scale 1.25 ++ DISPLAY_ID_PANDAREN_MONK = 30414, // set Scale 2 ++ DISPLAY_ID_ALGALON = 28641, // Set Scale 0.15 ++ DISPLAY_ID_RUARZOG = 25514, // Set Scale 0.75 ++ DISPLAY_ID_UNDEAD_MASKED = 21752, // set Scale 1 ++ DISPLAY_ID_SCARY_DWARF = 21824, // Set Scale 1.25 ++ DISPLAY_ID_UNDEAD_SHADES = 21959, // set Scale 1 ++ DISPLAY_ID_VIKING = 21997, // Set Scale 0.40 ++ DISPLAY_ID_MINOTAUR = 22007, // Set Scale 0.80 ++ DISPLAY_ID_LICHKING = 22234, // Set Scale 1.20 ++}; ++ ++class npc_morpher : public CreatureScript ++{ ++public: ++ npc_morpher() : CreatureScript("npc_morpher") { } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ ++ if (sConfigMgr->GetBoolDefault("Server.Morpher.Enable", true)) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TABARD, "Morph Menu ->", GOSSIP_SENDER_MAIN, MORPH_MENU); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TRAINER, "Scale Menu ->", GOSSIP_SENDER_MAIN, SCALE_MENU); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ return true; ++ } ++ return true; ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ ++ switch (action) ++ { ++ case MORPH_MENU: ++ if (sConfigMgr->GetBoolDefault("Permanent.Morph.Menu.Enable", true)) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Naga Lord", GOSSIP_SENDER_MAIN, ACTION_NAGA_LORD); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Worgen", GOSSIP_SENDER_MAIN, ACTION_WORGEN); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Pandaren Monk", GOSSIP_SENDER_MAIN, ACTION_PANDAREN_MONK); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Algalon", GOSSIP_SENDER_MAIN, ACTION_ALGALON); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Boss Ruarzog", GOSSIP_SENDER_MAIN, ACTION_RUARZOG); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Undead Masked", GOSSIP_SENDER_MAIN, ACTION_UNDEAD_MASKED); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Scary Dwarf", GOSSIP_SENDER_MAIN, ACTION_SCARY_DWARF); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Undead Shades", GOSSIP_SENDER_MAIN, ACTION_UNDEAD_SHADES); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Viking", GOSSIP_SENDER_MAIN, ACTION_VIKING); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Minotaur", GOSSIP_SENDER_MAIN, ACTION_MINOTAUR); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_2, "Lich King", GOSSIP_SENDER_MAIN, ACTION_LICHKING); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "[Demorph] Logout for Effect!!!", GOSSIP_SENDER_MAIN, ACTION_DEMORPH); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE,creature->GetGUID()); ++ return true; ++ } ++ else ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ creature->MonsterTextEmote(EMOTE_FUNCTION_OFF, 0, true); ++ } ++ break; ++ ++ case ACTION_NAGA_LORD: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_NAGA_LORD); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 13031);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.30, 'Naga Lord');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.75f); ++ break; ++ ++ case ACTION_WORGEN: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_WORGEN); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 657);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 2, 'Worgen');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 2); ++ break; ++ ++ case ACTION_PANDAREN_MONK: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_PANDAREN_MONK); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 30414);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 2, 'Panda');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 4); ++ break; ++ ++ case ACTION_ALGALON: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_ALGALON); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 28641);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.15, 'Alfalon');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.25f); ++ break; ++ ++ case ACTION_RUARZOG: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_RUARZOG); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 25514);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.8, 'Ruarzog');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.8f); ++ break; ++ ++ case ACTION_UNDEAD_MASKED: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_UNDEAD_MASKED); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 21752);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1, 'Undead Masked');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 1); ++ break; ++ ++ case ACTION_SCARY_DWARF: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_SCARY_DWARF); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 21824);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1.25, 'Scary Dwarf');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 1.25f); ++ break; ++ ++ case ACTION_UNDEAD_SHADES: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_UNDEAD_SHADES); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 21959);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1, 'Undead Shades');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 1); ++ break; ++ ++ case ACTION_VIKING: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_VIKING); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 21997);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.40, 'Viking');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.75f); ++ break; ++ ++ case ACTION_MINOTAUR: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_MINOTAUR); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 22007);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.8, 'Minotaur');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.8f); ++ break; ++ ++ case ACTION_LICHKING: ++ player->CLOSE_GOSSIP_MENU(); ++ player->SetDisplayId(DISPLAY_ID_LICHKING); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_morph` (`guid`, `morph`) VALUES(%u, 22234);", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1.20, 'Lich King');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 2); ++ break; ++ ++ case ACTION_DEMORPH: ++ player->CLOSE_GOSSIP_MENU(); ++ player->DeMorph(); ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 1); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_morph` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1, 'Player Default');", player->GetGUID()); ++ } ++ break; ++ ++ case SCALE_MENU: ++ if (sConfigMgr->GetBoolDefault("Permanent.Scale.Menu.Enable", true)) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Set my Scale to One", GOSSIP_SENDER_MAIN, ACTION_SCALE_ONE); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Set my Scale to Two", GOSSIP_SENDER_MAIN, ACTION_SCALE_TWO); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Set my Scale to Three", GOSSIP_SENDER_MAIN, ACTION_SCALE_THREE); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Set my Scale to Four", GOSSIP_SENDER_MAIN, ACTION_SCALE_FOUR); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Set my Scale to Five", GOSSIP_SENDER_MAIN, ACTION_SCALE_FIVE); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, "Vrykul Default Scale", GOSSIP_SENDER_MAIN, ACTION_SCALE_VRYKUL_DEFAULT); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, "ALGALON Scale Fixer", GOSSIP_SENDER_MAIN, ACTION_SCALE_ALGALON_FIX); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE,creature->GetGUID()); ++ return true; ++ } ++ else ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ creature->MonsterTextEmote(EMOTE_FUNCTION_OFF, 0, true); ++ } ++ break; ++ ++ case ACTION_SCALE_ONE: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 1, 'Scaled One');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 1); ++ break; ++ ++ case ACTION_SCALE_TWO: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 2, 'Scaled Two');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 2); ++ break; ++ ++ case ACTION_SCALE_THREE: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 3, 'Scaled Three');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 3); ++ break; ++ ++ case ACTION_SCALE_FOUR: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 4, 'Scaled Four');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 4); ++ break; ++ ++ case ACTION_SCALE_FIVE: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 5, 'Scaled Five');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 5); ++ break; ++ ++ case ACTION_SCALE_VRYKUL_DEFAULT: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.8, 'Vrykul Default');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.8f); ++ break; ++ ++ case ACTION_SCALE_ALGALON_FIX: ++ player->CLOSE_GOSSIP_MENU(); ++ { ++ CharacterDatabase.PExecute("DELETE FROM `character_scale` WHERE guid = %u;", player->GetGUID()); ++ CharacterDatabase.PExecute("INSERT INTO `character_scale` (`guid`, `scale`, `comment`) VALUES(%u, 0.25, 'Alagon Fix');", player->GetGUID()); ++ } ++ player->SetFloatValue(OBJECT_FIELD_SCALE_X, 0.25f); ++ break; ++ ++ case MAIN_MENU: ++ OnGossipHello(player, creature); ++ break; ++ } ++ return true; ++ } ++}; ++ ++void AddSC_npc_morpher() ++{ ++ new npc_morpher(); ++} +\ No newline at end of file +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 62008a3..b9fe88a 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -29,6 +29,7 @@ + # CUSTOM SERVER OPTIONS + # LOGGING SYSTEM SETTINGS + # PACKET SPOOF PROTECTION SETTINGS ++# MORPH MASTER CONTROL OPTIONS + # + ################################################################################################### + +@@ -2866,3 +2867,33 @@ PacketSpoof.BanDuration = 86400 + + # + ################################################################################################### ++ ++################################################################################################### ++# MORPH MASTER CONTROL OPTIONS ++# ++# ++# Server.Morpher.Enable ++# Description: Enable/Disable Morph/Scale Npc. ++# Default: 1 - (Enabled) ++# 0 - (Disabled) ++ ++Server.Morpher.Enable = 1 ++ ++## ++# Permanent.Morph.Menu.Enable ++# Description: Enable/Disable Morph Menu. ++# Default: 1 - (Enabled) ++# 0 - (Disabled) ++ ++Permanent.Morph.Menu.Enable = 1 ++ ++## ++# ++# Permanent.Scale.Menu.Enable ++# Description: Enable/Disable Scale Menu. ++# Default: 1 - (Enabled) ++# 0 - (Disabled) ++ ++Permanent.Scale.Menu.Enable = 1 ++# ++################################################################################################### +-- +1.8.3.msysgit.0 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e516c8a --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# TrinityCore-335a +TrinityCore Release 335a + +FixCore es una empresa de desarrollo con especialidad en el juego world of warcraft. @FixStore - @FixDevs - @FixCMS son propiedad de @FixCore. + +El 80% de los trabajos que realiza @FixCore y sus demás subempresas son de manera gratuita sin embargo el 20% de los demás trabajos suelen ser de cobro debido a que se pudo haber requerido de algún pago extra para algún template, linea etc.. No acepte cosas de cobro que estén a nombre de @FixCore sin que usted esté seguro de que sea de manera legal ** En caso de tener dudas, inquietudes o desea contactarnos puede enviarnos un E-Mail ** fixcore2@gmail.com + +--- + +FixCore is a development company specializing in the game world of warcraft. @FixStore - @FixDevs - @FixCMS Are owned by @FixCore. + +80% of the work done @FixCore and other sub-companies are free however 20% of other jobs usually collection because it could have required some extra payment for a template, line etc .. Do not accept things that are a collection @FixCore name without you sure it legally ** If you have questions, concerns or want to contact you can send an E-Mail ** fixcore2@gmail.com diff --git a/Spawn_objects_in_bg.diff b/Spawn_objects_in_bg.diff new file mode 100644 index 0000000..50243b7 --- /dev/null +++ b/Spawn_objects_in_bg.diff @@ -0,0 +1,21 @@ +en Battleground.cpp + +WorldPacket data; + sBattlegroundMgr->BuildPlayerJoinedBattlegroundPacket(&data, player); + SendPacketToTeam(team, &data, player, false); + + player->RemoveAurasByType(SPELL_AURA_MOUNTED); + ++ if (player->getClass() == CLASS_WARLOCK) ++ { ++ player->SummonGameObject(193170, player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), player->GetOrientation(), 0, 0, 0, 0, 120); // create soulwell ++ } ++ ++ if (player->getClass() == CLASS_MAGE) ++ { ++ player->SummonGameObject(193061, player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), player->GetOrientation(), 0, 0, 0, 0, 120); // create refreshment table ++ } + + // add arena specific auras + if (isArena()) + { \ No newline at end of file diff --git a/antihack.diff b/antihack.diff new file mode 100644 index 0000000..8094e27 --- /dev/null +++ b/antihack.diff @@ -0,0 +1,1345 @@ +diff --git a/sql/anticheat.sql b/sql/anticheat.sql +new file mode 100644 +index 0000000..3504594 +--- /dev/null ++++ b/sql/anticheat.sql +@@ -0,0 +1,30 @@ ++DROP TABLE IF EXISTS `players_reports_status`; ++ ++CREATE TABLE `players_reports_status` ( ++ `guid` int(10) unsigned NOT NULL DEFAULT '0', ++ `creation_time` int(10) unsigned NOT NULL DEFAULT '0', ++ `average` float NOT NULL DEFAULT '0', ++ `total_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `speed_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `fly_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `jump_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `waterwalk_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `teleportplane_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `climb_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ PRIMARY KEY (`guid`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''; ++ ++DROP TABLE IF EXISTS `daily_players_reports`; ++CREATE TABLE `daily_players_reports` ( ++ `guid` int(10) unsigned NOT NULL DEFAULT '0', ++ `creation_time` int(10) unsigned NOT NULL DEFAULT '0', ++ `average` float NOT NULL DEFAULT '0', ++ `total_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `speed_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `fly_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `jump_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `waterwalk_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `teleportplane_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ `climb_reports` bigint(20) unsigned NOT NULL DEFAULT '0', ++ PRIMARY KEY (`guid`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''; +diff --git a/src/server/game/Anticheat/AnticheatData.cpp b/src/server/game/Anticheat/AnticheatData.cpp +new file mode 100644 +index 0000000..8c69972 +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatData.cpp +@@ -0,0 +1,118 @@ ++#include "AnticheatData.h" ++ ++AnticheatData::AnticheatData() ++{ ++ lastOpcode = 0; ++ totalReports = 0; ++ for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) ++ { ++ typeReports[i] = 0; ++ tempReports[i] = 0; ++ tempReportsTimer[i] = 0; ++ } ++ average = 0; ++ creationTime = 0; ++ hasDailyReport = false; ++} ++ ++AnticheatData::~AnticheatData() ++{ ++} ++ ++void AnticheatData::SetDailyReportState(bool b) ++{ ++ hasDailyReport = b; ++} ++ ++bool AnticheatData::GetDailyReportState() ++{ ++ return hasDailyReport; ++} ++ ++void AnticheatData::SetLastOpcode(uint32 opcode) ++{ ++ lastOpcode = opcode; ++} ++ ++void AnticheatData::SetPosition(float x, float y, float z, float o) ++{ ++ lastMovementInfo.pos.m_positionX = x; ++ lastMovementInfo.pos.m_positionY = y; ++ lastMovementInfo.pos.m_positionZ = z; ++} ++ ++uint32 AnticheatData::GetLastOpcode() const ++{ ++ return lastOpcode; ++} ++ ++const MovementInfo& AnticheatData::GetLastMovementInfo() const ++{ ++ return lastMovementInfo; ++} ++ ++void AnticheatData::SetLastMovementInfo(MovementInfo& moveInfo) ++{ ++ lastMovementInfo = moveInfo; ++} ++ ++uint32 AnticheatData::GetTotalReports() const ++{ ++ return totalReports; ++} ++ ++void AnticheatData::SetTotalReports(uint32 _totalReports) ++{ ++ totalReports = _totalReports; ++} ++ ++void AnticheatData::SetTypeReports(uint32 type, uint32 amount) ++{ ++ typeReports[type] = amount; ++} ++ ++uint32 AnticheatData::GetTypeReports(uint32 type) const ++{ ++ return typeReports[type]; ++} ++ ++float AnticheatData::GetAverage() const ++{ ++ return average; ++} ++ ++void AnticheatData::SetAverage(float _average) ++{ ++ average = _average; ++} ++ ++uint32 AnticheatData::GetCreationTime() const ++{ ++ return creationTime; ++} ++ ++void AnticheatData::SetCreationTime(uint32 _creationTime) ++{ ++ creationTime = _creationTime; ++} ++ ++void AnticheatData::SetTempReports(uint32 amount, uint8 type) ++{ ++ tempReports[type] = amount; ++} ++ ++uint32 AnticheatData::GetTempReports(uint8 type) ++{ ++ return tempReports[type]; ++} ++ ++void AnticheatData::SetTempReportsTimer(uint32 time, uint8 type) ++{ ++ tempReportsTimer[type] = time; ++} ++ ++uint32 AnticheatData::GetTempReportsTimer(uint8 type) ++{ ++ return tempReportsTimer[type]; ++} +diff --git a/src/server/game/Anticheat/AnticheatData.h b/src/server/game/Anticheat/AnticheatData.h +new file mode 100644 +index 0000000..700ad2d +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatData.h +@@ -0,0 +1,63 @@ ++#ifndef SC_ACDATA_H ++#define SC_ACDATA_H ++ ++#include "AnticheatMgr.h" ++ ++#define MAX_REPORT_TYPES 6 ++ ++class AnticheatData ++{ ++public: ++ AnticheatData(); ++ ~AnticheatData(); ++ ++ void SetLastOpcode(uint32 opcode); ++ uint32 GetLastOpcode() const; ++ ++ const MovementInfo& GetLastMovementInfo() const; ++ void SetLastMovementInfo(MovementInfo& moveInfo); ++ ++ void SetPosition(float x, float y, float z, float o); ++ ++ /* ++ bool GetDisableACCheck() const; ++ void SetDisableACCheck(bool check); ++ ++ uint32 GetDisableACTimer() const; ++ void SetDisableACTimer(uint32 timer);*/ ++ ++ uint32 GetTotalReports() const; ++ void SetTotalReports(uint32 _totalReports); ++ ++ uint32 GetTypeReports(uint32 type) const; ++ void SetTypeReports(uint32 type, uint32 amount); ++ ++ float GetAverage() const; ++ void SetAverage(float _average); ++ ++ uint32 GetCreationTime() const; ++ void SetCreationTime(uint32 creationTime); ++ ++ void SetTempReports(uint32 amount, uint8 type); ++ uint32 GetTempReports(uint8 type); ++ ++ void SetTempReportsTimer(uint32 time, uint8 type); ++ uint32 GetTempReportsTimer(uint8 type); ++ ++ void SetDailyReportState(bool b); ++ bool GetDailyReportState(); ++private: ++ uint32 lastOpcode; ++ MovementInfo lastMovementInfo; ++ //bool disableACCheck; ++ //uint32 disableACCheckTimer; ++ uint32 totalReports; ++ uint32 typeReports[MAX_REPORT_TYPES]; ++ float average; ++ uint32 creationTime; ++ uint32 tempReports[MAX_REPORT_TYPES]; ++ uint32 tempReportsTimer[MAX_REPORT_TYPES]; ++ bool hasDailyReport; ++}; ++ ++#endif +diff --git a/src/server/game/Anticheat/AnticheatMgr.cpp b/src/server/game/Anticheat/AnticheatMgr.cpp +new file mode 100644 +index 0000000..f409e93 +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatMgr.cpp +@@ -0,0 +1,434 @@ ++/* ++ * 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, see . ++ */ ++ ++#include "AnticheatMgr.h" ++#include "AnticheatScripts.h" ++#include "MapManager.h" ++ ++#define CLIMB_ANGLE 1.9f ++ ++AnticheatMgr::AnticheatMgr() ++{ ++} ++ ++AnticheatMgr::~AnticheatMgr() ++{ ++ m_Players.clear(); ++} ++ ++void AnticheatMgr::JumpHackDetection(Player* player, MovementInfo /* movementInfo */,uint32 opcode) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & JUMP_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ ++ if (m_Players[key].GetLastOpcode() == MSG_MOVE_JUMP && opcode == MSG_MOVE_JUMP) ++ { ++ BuildReport(player,JUMP_HACK_REPORT); ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Jump-Hack detected player GUID (low) %u",player->GetGUIDLow()); ++ } ++} ++ ++void AnticheatMgr::WalkOnWaterHackDetection(Player* player, MovementInfo /* movementInfo */) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & WALK_WATER_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ if (!m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_WATERWALKING)) ++ return; ++ ++ // if we are a ghost we can walk on water ++ if (!player->IsAlive()) ++ return; ++ ++ if (player->HasAuraType(SPELL_AURA_FEATHER_FALL) || ++ player->HasAuraType(SPELL_AURA_SAFE_FALL) || ++ player->HasAuraType(SPELL_AURA_WATER_WALK)) ++ return; ++ ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Walk on Water - Hack detected player GUID (low) %u",player->GetGUIDLow()); ++ BuildReport(player,WALK_WATER_HACK_REPORT); ++ ++} ++ ++void AnticheatMgr::FlyHackDetection(Player* player, MovementInfo /* movementInfo */) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & FLY_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ if (!m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_FLYING)) ++ return; ++ ++ if (player->HasAuraType(SPELL_AURA_FLY) || ++ player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || ++ player->HasAuraType(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED)) ++ return; ++ ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Fly-Hack detected player GUID (low) %u",player->GetGUIDLow()); ++ BuildReport(player,FLY_HACK_REPORT); ++} ++ ++void AnticheatMgr::TeleportPlaneHackDetection(Player* player, MovementInfo movementInfo) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & TELEPORT_PLANE_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ ++ if (m_Players[key].GetLastMovementInfo().pos.GetPositionZ() != 0 || ++ movementInfo.pos.GetPositionZ() != 0) ++ return; ++ ++ if (movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING)) ++ return; ++ ++ //DEAD_FALLING was deprecated ++ //if (player->getDeathState() == DEAD_FALLING) ++ // return; ++ float x, y, z; ++ player->GetPosition(x, y, z); ++ float ground_Z = player->GetMap()->GetHeight(x, y, z); ++ float z_diff = fabs(ground_Z - z); ++ ++ // we are not really walking there ++ if (z_diff > 1.0f) ++ { ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Teleport To Plane - Hack detected player GUID (low) %u",player->GetGUIDLow()); ++ BuildReport(player,TELEPORT_PLANE_HACK_REPORT); ++ } ++} ++ ++void AnticheatMgr::StartHackDetection(Player* player, MovementInfo movementInfo, uint32 opcode) ++{ ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ return; ++ ++ if (player->IsGameMaster()) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ ++ if (player->IsInFlight() || player->GetTransport() || player->GetVehicle()) ++ { ++ m_Players[key].SetLastMovementInfo(movementInfo); ++ m_Players[key].SetLastOpcode(opcode); ++ return; ++ } ++ ++ SpeedHackDetection(player,movementInfo); ++ FlyHackDetection(player,movementInfo); ++ WalkOnWaterHackDetection(player,movementInfo); ++ JumpHackDetection(player,movementInfo,opcode); ++ TeleportPlaneHackDetection(player, movementInfo); ++ ClimbHackDetection(player,movementInfo,opcode); ++ ++ m_Players[key].SetLastMovementInfo(movementInfo); ++ m_Players[key].SetLastOpcode(opcode); ++} ++ ++// basic detection ++void AnticheatMgr::ClimbHackDetection(Player *player, MovementInfo movementInfo, uint32 opcode) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & CLIMB_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ ++ if (opcode != MSG_MOVE_HEARTBEAT || ++ m_Players[key].GetLastOpcode() != MSG_MOVE_HEARTBEAT) ++ return; ++ ++ // in this case we don't care if they are "legal" flags, they are handled in another parts of the Anticheat Manager. ++ if (player->IsInWater() || ++ player->IsFlying() || ++ player->IsFalling()) ++ return; ++ ++ Position playerPos; ++ Position pos = player->GetPosition(); ++ ++ float deltaZ = fabs(playerPos.GetPositionZ() - movementInfo.pos.GetPositionZ()); ++ float deltaXY = movementInfo.pos.GetExactDist2d(&playerPos); ++ ++ float angle = Position::NormalizeOrientation(tan(deltaZ/deltaXY)); ++ ++ if (angle > CLIMB_ANGLE) ++ { ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Climb-Hack detected player GUID (low) %u", player->GetGUIDLow()); ++ BuildReport(player,CLIMB_HACK_REPORT); ++ } ++} ++ ++void AnticheatMgr::SpeedHackDetection(Player* player,MovementInfo movementInfo) ++{ ++ if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & SPEED_HACK_DETECTION) == 0) ++ return; ++ ++ uint32 key = player->GetGUIDLow(); ++ ++ // We also must check the map because the movementFlag can be modified by the client. ++ // If we just check the flag, they could always add that flag and always skip the speed hacking detection. ++ // 369 == DEEPRUN TRAM ++ if (m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && player->GetMapId() == 369) ++ return; ++ ++ uint32 distance2D = (uint32)movementInfo.pos.GetExactDist2d(&m_Players[key].GetLastMovementInfo().pos); ++ uint8 moveType = 0; ++ ++ // we need to know HOW is the player moving ++ // TO-DO: Should we check the incoming movement flags? ++ if (player->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) ++ moveType = MOVE_SWIM; ++ else if (player->IsFlying()) ++ moveType = MOVE_FLIGHT; ++ else if (player->HasUnitMovementFlag(MOVEMENTFLAG_WALKING)) ++ moveType = MOVE_WALK; ++ else ++ moveType = MOVE_RUN; ++ ++ // how many yards the player can do in one sec. ++ uint32 speedRate = (uint32)(player->GetSpeed(UnitMoveType(moveType)) + movementInfo.jump.xyspeed); ++ ++ // how long the player took to move to here. ++ uint32 timeDiff = getMSTimeDiff(m_Players[key].GetLastMovementInfo().time,movementInfo.time); ++ ++ if (!timeDiff) ++ timeDiff = 1; ++ ++ // this is the distance doable by the player in 1 sec, using the time done to move to this point. ++ uint32 clientSpeedRate = distance2D * 1000 / timeDiff; ++ ++ // we did the (uint32) cast to accept a margin of tolerance ++ if (clientSpeedRate > speedRate) ++ { ++ BuildReport(player,SPEED_HACK_REPORT); ++ TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Speed-Hack detected player GUID (low) %u",player->GetGUIDLow()); ++ } ++} ++ ++void AnticheatMgr::StartScripts() ++{ ++ new AnticheatScripts(); ++} ++ ++void AnticheatMgr::HandlePlayerLogin(Player* player) ++{ ++ // we must delete this to prevent errors in case of crash ++ CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u",player->GetGUIDLow()); ++ // we initialize the pos of lastMovementPosition var. ++ m_Players[player->GetGUIDLow()].SetPosition(player->GetPositionX(),player->GetPositionY(),player->GetPositionZ(),player->GetOrientation()); ++ QueryResult resultDB = CharacterDatabase.PQuery("SELECT * FROM daily_players_reports WHERE guid=%u;",player->GetGUIDLow()); ++ ++ if (resultDB) ++ m_Players[player->GetGUIDLow()].SetDailyReportState(true); ++} ++ ++void AnticheatMgr::HandlePlayerLogout(Player* player) ++{ ++ // TO-DO Make a table that stores the cheaters of the day, with more detailed information. ++ ++ // We must also delete it at logout to prevent have data of offline players in the db when we query the database (IE: The GM Command) ++ CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u",player->GetGUIDLow()); ++ // Delete not needed data from the memory. ++ m_Players.erase(player->GetGUIDLow()); ++} ++ ++void AnticheatMgr::SavePlayerData(Player* player) ++{ ++ CharacterDatabase.PExecute("REPLACE INTO players_reports_status (guid,average,total_reports,speed_reports,fly_reports,jump_reports,waterwalk_reports,teleportplane_reports,climb_reports,creation_time) VALUES (%u,%f,%u,%u,%u,%u,%u,%u,%u,%u);",player->GetGUIDLow(),m_Players[player->GetGUIDLow()].GetAverage(),m_Players[player->GetGUIDLow()].GetTotalReports(), m_Players[player->GetGUIDLow()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUIDLow()].GetCreationTime()); ++} ++ ++uint32 AnticheatMgr::GetTotalReports(uint32 lowGUID) ++{ ++ return m_Players[lowGUID].GetTotalReports(); ++} ++ ++float AnticheatMgr::GetAverage(uint32 lowGUID) ++{ ++ return m_Players[lowGUID].GetAverage(); ++} ++ ++uint32 AnticheatMgr::GetTypeReports(uint32 lowGUID, uint8 type) ++{ ++ return m_Players[lowGUID].GetTypeReports(type); ++} ++ ++bool AnticheatMgr::MustCheckTempReports(uint8 type) ++{ ++ if (type == JUMP_HACK_REPORT) ++ return false; ++ ++ return true; ++} ++ ++void AnticheatMgr::BuildReport(Player* player,uint8 reportType) ++{ ++ uint32 key = player->GetGUIDLow(); ++ ++ if (MustCheckTempReports(reportType)) ++ { ++ uint32 actualTime = getMSTime(); ++ ++ if (!m_Players[key].GetTempReportsTimer(reportType)) ++ m_Players[key].SetTempReportsTimer(actualTime,reportType); ++ ++ if (getMSTimeDiff(m_Players[key].GetTempReportsTimer(reportType),actualTime) < 3000) ++ { ++ m_Players[key].SetTempReports(m_Players[key].GetTempReports(reportType)+1,reportType); ++ ++ if (m_Players[key].GetTempReports(reportType) < 3) ++ return; ++ } else ++ { ++ m_Players[key].SetTempReportsTimer(actualTime,reportType); ++ m_Players[key].SetTempReports(1,reportType); ++ return; ++ } ++ } ++ ++ // generating creationTime for average calculation ++ if (!m_Players[key].GetTotalReports()) ++ m_Players[key].SetCreationTime(getMSTime()); ++ ++ // increasing total_reports ++ m_Players[key].SetTotalReports(m_Players[key].GetTotalReports()+1); ++ // increasing specific cheat report ++ m_Players[key].SetTypeReports(reportType,m_Players[key].GetTypeReports(reportType)+1); ++ ++ // diff time for average calculation ++ uint32 diffTime = getMSTimeDiff(m_Players[key].GetCreationTime(),getMSTime()) / IN_MILLISECONDS; ++ ++ if (diffTime > 0) ++ { ++ // Average == Reports per second ++ float average = float(m_Players[key].GetTotalReports()) / float(diffTime); ++ m_Players[key].SetAverage(average); ++ } ++ ++ if (sWorld->getIntConfig(CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT) < m_Players[key].GetTotalReports()) ++ { ++ if (!m_Players[key].GetDailyReportState()) ++ { ++ CharacterDatabase.PExecute("REPLACE INTO daily_players_reports (guid,average,total_reports,speed_reports,fly_reports,jump_reports,waterwalk_reports,teleportplane_reports,climb_reports,creation_time) VALUES (%u,%f,%u,%u,%u,%u,%u,%u,%u,%u);",player->GetGUIDLow(),m_Players[player->GetGUIDLow()].GetAverage(),m_Players[player->GetGUIDLow()].GetTotalReports(), m_Players[player->GetGUIDLow()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUIDLow()].GetCreationTime()); ++ m_Players[key].SetDailyReportState(true); ++ } ++ } ++ ++ if (m_Players[key].GetTotalReports() > sWorld->getIntConfig(CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION)) ++ { ++ // display warning at the center of the screen, hacky way? ++ std::string str = ""; ++ str = "|cFFFFFC00[AC]|cFF00FFFF[|cFF60FF00" + std::string(player->GetName().c_str()) + "|cFF00FFFF] Possible cheater!"; ++ WorldPacket data(SMSG_NOTIFICATION, (str.size()+1)); ++ data << str; ++ sWorld->SendGlobalGMMessage(&data); ++ } ++} ++ ++void AnticheatMgr::AnticheatGlobalCommand(ChatHandler* handler) ++{ ++ // MySQL will sort all for us, anyway this is not the best way we must only save the anticheat data not whole player's data!. ++ sObjectAccessor->SaveAllPlayers(); ++ ++ QueryResult resultDB = CharacterDatabase.Query("SELECT guid,average,total_reports FROM players_reports_status WHERE total_reports != 0 ORDER BY average ASC LIMIT 3;"); ++ if (!resultDB) ++ { ++ handler->PSendSysMessage("No players found."); ++ return; ++ } else ++ { ++ handler->SendSysMessage("============================="); ++ handler->PSendSysMessage("Players with the lowest averages:"); ++ do ++ { ++ Field *fieldsDB = resultDB->Fetch(); ++ ++ uint32 guid = fieldsDB[0].GetUInt32(); ++ float average = fieldsDB[1].GetFloat(); ++ uint32 total_reports = fieldsDB[2].GetUInt32(); ++ ++ if (Player* player = sObjectMgr->GetPlayerByLowGUID(guid)) ++ handler->PSendSysMessage("Player: %s Average: %f Total Reports: %u",player->GetName().c_str(),average,total_reports); ++ ++ } while (resultDB->NextRow()); ++ } ++ ++ resultDB = CharacterDatabase.Query("SELECT guid,average,total_reports FROM players_reports_status WHERE total_reports != 0 ORDER BY total_reports DESC LIMIT 3;"); ++ ++ // this should never happen ++ if (!resultDB) ++ { ++ handler->PSendSysMessage("No players found."); ++ return; ++ } else ++ { ++ handler->SendSysMessage("============================="); ++ handler->PSendSysMessage("Players with the more reports:"); ++ do ++ { ++ Field *fieldsDB = resultDB->Fetch(); ++ ++ uint32 guid = fieldsDB[0].GetUInt32(); ++ float average = fieldsDB[1].GetFloat(); ++ uint32 total_reports = fieldsDB[2].GetUInt32(); ++ ++ if (Player* player = sObjectMgr->GetPlayerByLowGUID(guid)) ++ handler->PSendSysMessage("Player: %s Total Reports: %u Average: %f",player->GetName().c_str(),total_reports,average); ++ ++ } while (resultDB->NextRow()); ++ } ++} ++ ++void AnticheatMgr::AnticheatDeleteCommand(uint32 guid) ++{ ++ if (!guid) ++ { ++ for (AnticheatPlayersDataMap::iterator it = m_Players.begin(); it != m_Players.end(); ++it) ++ { ++ (*it).second.SetTotalReports(0); ++ (*it).second.SetAverage(0); ++ (*it).second.SetCreationTime(0); ++ for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) ++ { ++ (*it).second.SetTempReports(0,i); ++ (*it).second.SetTempReportsTimer(0,i); ++ (*it).second.SetTypeReports(i,0); ++ } ++ } ++ CharacterDatabase.PExecute("DELETE FROM players_reports_status;"); ++ } ++ else ++ { ++ m_Players[guid].SetTotalReports(0); ++ m_Players[guid].SetAverage(0); ++ m_Players[guid].SetCreationTime(0); ++ for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) ++ { ++ m_Players[guid].SetTempReports(0,i); ++ m_Players[guid].SetTempReportsTimer(0,i); ++ m_Players[guid].SetTypeReports(i,0); ++ } ++ CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u;",guid); ++ } ++} ++ ++void AnticheatMgr::ResetDailyReportStates() ++{ ++ for (AnticheatPlayersDataMap::iterator it = m_Players.begin(); it != m_Players.end(); ++it) ++ m_Players[(*it).first].SetDailyReportState(false); ++} +diff --git a/src/server/game/Anticheat/AnticheatMgr.h b/src/server/game/Anticheat/AnticheatMgr.h +new file mode 100644 +index 0000000..554bdfa +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatMgr.h +@@ -0,0 +1,103 @@ ++/* ++ * 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, see . ++ */ ++ ++#ifndef SC_ACMGR_H ++#define SC_ACMGR_H ++ ++//#include ++#include "Common.h" ++#include "SharedDefines.h" ++#include "ScriptPCH.h" ++#include "AnticheatData.h" ++#include "Chat.h" ++ ++class Player; ++class AnticheatData; ++ ++enum ReportTypes ++{ ++ SPEED_HACK_REPORT = 0, ++ FLY_HACK_REPORT, ++ WALK_WATER_HACK_REPORT, ++ JUMP_HACK_REPORT, ++ TELEPORT_PLANE_HACK_REPORT, ++ CLIMB_HACK_REPORT, ++ ++ // MAX_REPORT_TYPES ++}; ++ ++enum DetectionTypes ++{ ++ SPEED_HACK_DETECTION = 1, ++ FLY_HACK_DETECTION = 2, ++ WALK_WATER_HACK_DETECTION = 4, ++ JUMP_HACK_DETECTION = 8, ++ TELEPORT_PLANE_HACK_DETECTION = 16, ++ CLIMB_HACK_DETECTION = 32 ++}; ++ ++// GUIDLow is the key. ++typedef std::map AnticheatPlayersDataMap; ++ ++class AnticheatMgr ++{ ++// friend class ACE_Singleton; ++ AnticheatMgr(); ++ ~AnticheatMgr(); ++ ++ public: ++ static AnticheatMgr* instance() ++ { ++ static AnticheatMgr* instance = new AnticheatMgr(); ++ return instance; ++ } ++ ++ void StartHackDetection(Player* player, MovementInfo movementInfo, uint32 opcode); ++ void DeletePlayerReport(Player* player, bool login); ++ void DeletePlayerData(Player* player); ++ void CreatePlayerData(Player* player); ++ void SavePlayerData(Player* player); ++ ++ void StartScripts(); ++ ++ void HandlePlayerLogin(Player* player); ++ void HandlePlayerLogout(Player* player); ++ ++ uint32 GetTotalReports(uint32 lowGUID); ++ float GetAverage(uint32 lowGUID); ++ uint32 GetTypeReports(uint32 lowGUID, uint8 type); ++ ++ void AnticheatGlobalCommand(ChatHandler* handler); ++ void AnticheatDeleteCommand(uint32 guid); ++ ++ void ResetDailyReportStates(); ++ private: ++ void SpeedHackDetection(Player* player, MovementInfo movementInfo); ++ void FlyHackDetection(Player* player, MovementInfo movementInfo); ++ void WalkOnWaterHackDetection(Player* player, MovementInfo movementInfo); ++ void JumpHackDetection(Player* player, MovementInfo movementInfo,uint32 opcode); ++ void TeleportPlaneHackDetection(Player* player, MovementInfo); ++ void ClimbHackDetection(Player* player,MovementInfo movementInfo,uint32 opcode); ++ ++ void BuildReport(Player* player,uint8 reportType); ++ ++ bool MustCheckTempReports(uint8 type); ++ ++ AnticheatPlayersDataMap m_Players; ///< Player data ++}; ++ ++#define sAnticheatMgr AnticheatMgr::instance() ++ ++#endif +diff --git a/src/server/game/Anticheat/AnticheatScripts.cpp b/src/server/game/Anticheat/AnticheatScripts.cpp +new file mode 100644 +index 0000000..340178d +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatScripts.cpp +@@ -0,0 +1,14 @@ ++#include "AnticheatScripts.h" ++#include "AnticheatMgr.h" ++ ++AnticheatScripts::AnticheatScripts(): PlayerScript("AnticheatScripts") {} ++ ++void AnticheatScripts::OnLogout(Player* player) ++{ ++ sAnticheatMgr->HandlePlayerLogout(player); ++} ++ ++void AnticheatScripts::OnLogin(Player* player,bool) ++{ ++ sAnticheatMgr->HandlePlayerLogin(player); ++} +diff --git a/src/server/game/Anticheat/AnticheatScripts.h b/src/server/game/Anticheat/AnticheatScripts.h +new file mode 100644 +index 0000000..25d34d0 +--- /dev/null ++++ b/src/server/game/Anticheat/AnticheatScripts.h +@@ -0,0 +1,15 @@ ++#ifndef SC_ACSCRIPTS_H ++#define SC_ACSCRIPTS_H ++ ++#include "ScriptPCH.h" ++ ++class AnticheatScripts: public PlayerScript ++{ ++ public: ++ AnticheatScripts(); ++ ++ void OnLogout(Player* player); ++ void OnLogin(Player* player,bool); ++}; ++ ++#endif +diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt +index 84d6f87..168b41e 100644 +--- a/src/server/game/CMakeLists.txt ++++ b/src/server/game/CMakeLists.txt +@@ -9,6 +9,7 @@ + # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + file(GLOB_RECURSE sources_Accounts Accounts/*.cpp Accounts/*.h) ++file(GLOB_RECURSE sources_Anticheat Anticheat/*.cpp Anticheat/*.h) + file(GLOB_RECURSE sources_Achievements Achievements/*.cpp Achievements/*.h) + file(GLOB_RECURSE sources_Addons Addons/*.cpp Addons/*.h) + file(GLOB_RECURSE sources_AI AI/*.cpp AI/*.h) +@@ -59,6 +60,7 @@ endif () + set(game_STAT_SRCS + ${game_STAT_SRCS} + ${sources_Accounts} ++ ${sources_Anticheat} + ${sources_Achievements} + ${sources_Addons} + ${sources_AI} +@@ -128,6 +130,7 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/shared/Utilities + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/Accounts ++ ${CMAKE_CURRENT_SOURCE_DIR}/Anticheat + ${CMAKE_CURRENT_SOURCE_DIR}/Achievements + ${CMAKE_CURRENT_SOURCE_DIR}/Addons + ${CMAKE_CURRENT_SOURCE_DIR}/AI +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index a75253f..ea6852a 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -25,6 +25,7 @@ +#include "AchievementMgr.h" +#include "ArenaTeam.h" +#include "ArenaTeamMgr.h" ++#include "AnticheatMgr.h" +#include "Battlefield.h" +#include "BattlefieldMgr.h" +#include "BattlefieldWG.h" + +@@ -19457,6 +19462,12 @@ void Player::SaveToDB(bool create /*=false*/) + + CharacterDatabase.CommitTransaction(trans); + ++ // we save the data here to prevent spamming ++ sAnticheatMgr->SavePlayerData(this); ++ ++ // in this way we prevent to spam the db by each report made! ++ // sAnticheatMgr->SavePlayerData(this); ++ + // save pet (hunter pet level and experience and all type pets health/mana). + if (Pet* pet = GetPet()) + pet->SavePetToDB(PET_SAVE_AS_CURRENT); +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index de71619..5e125c9 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -16,6 +16,7 @@ + * with this program. If not, see . + */ + ++#include "AnticheatMgr.h" + #include "Unit.h" + #include "Common.h" + #include "Battlefield.h" +@@ -12168,6 +12169,9 @@ void Unit::SetVisible(bool x) + + void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) + { ++ //if (this->ToPlayer()) ++ // sAnticheatMgr->DisableAnticheatDetection(this->ToPlayer()); ++ + int32 main_speed_mod = 0; + float stack_bonus = 1.0f; + float non_stack_bonus = 1.0f; +diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp +index 6fedc48..66a1fa6 100644 +--- a/src/server/game/Handlers/MovementHandler.cpp ++++ b/src/server/game/Handlers/MovementHandler.cpp +@@ -16,6 +16,7 @@ + * with this program. If not, see . + */ + ++#include "AnticheatMgr.h" + #include "Common.h" + #include "WorldPacket.h" + #include "WorldSession.h" +@@ -343,6 +344,9 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData) + plrMover->SetInWater(!plrMover->IsInWater() || plrMover->GetBaseMap()->IsUnderWater(movementInfo.pos.GetPositionX(), movementInfo.pos.GetPositionY(), movementInfo.pos.GetPositionZ())); + } + ++ if (plrMover) ++ sAnticheatMgr->StartHackDetection(plrMover, movementInfo, opcode); ++ + uint32 mstime = getMSTime(); + /*----------------------*/ + if (m_clientTimeDelay == 0) +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index 6026945..337e68f 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -17,6 +17,7 @@ + + #include "ScriptLoader.h" + #include "World.h" ++#include "AnticheatMgr.h" + + //examples + void AddSC_example_creature(); +@@ -45,6 +46,7 @@ void AddSC_holiday_spell_scripts(); + void AddSC_SmartScripts(); + + //Commands ++void AddSC_anticheat_commandscript(); + void AddSC_account_commandscript(); + void AddSC_achievement_commandscript(); + void AddSC_ahbot_commandscript(); +@@ -706,6 +708,7 @@ void AddScripts() + AddSpellScripts(); + AddSC_SmartScripts(); + AddCommandScripts(); ++ sAnticheatMgr->StartScripts(); + #ifdef SCRIPTS + AddWorldScripts(); + AddEasternKingdomsScripts(); +@@ -750,6 +753,7 @@ void AddSpellScripts() + + void AddCommandScripts() + { ++ AddSC_anticheat_commandscript(); + AddSC_account_commandscript(); + AddSC_achievement_commandscript(); + AddSC_ahbot_commandscript(); +diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp +index 4c0d577..a6a16f3 100644 +--- a/src/server/game/Spells/SpellEffects.cpp ++++ b/src/server/game/Spells/SpellEffects.cpp +@@ -16,6 +16,7 @@ + * with this program. If not, see . + */ + ++#include "AnticheatMgr.h" + #include "Common.h" + #include "DatabaseEnv.h" + #include "WorldPacket.h" +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index 631c4e6..0f7f10b 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -76,6 +76,7 @@ + #include "AuctionHouseMgr.h" + #include "BattlefieldMgr.h" + #include "BattlegroundMgr.h" ++#include "AnticheatMgr.h" + #include "CalendarMgr.h" + #include "Channel.h" + #include "CharacterDatabaseCleaner.h" +@@ -1231,6 +1232,11 @@ void World::LoadConfigSettings(bool reload) + m_bool_configs[CONFIG_PDUMP_NO_OVERWRITE] = sConfigMgr->GetBoolDefault("PlayerDump.DisallowOverwrite", true); + m_bool_configs[CONFIG_UI_QUESTLEVELS_IN_DIALOGS] = sConfigMgr->GetBoolDefault("UI.ShowQuestLevelsInDialogs", false); + ++ m_bool_configs[CONFIG_ANTICHEAT_ENABLE] = sConfigMgr->GetBoolDefault("Anticheat.Enable", true); ++ m_int_configs[CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION] = sConfigMgr->GetIntDefault("Anticheat.ReportsForIngameWarnings", 70); ++ m_int_configs[CONFIG_ANTICHEAT_DETECTIONS_ENABLED] = sConfigMgr->GetIntDefault("Anticheat.DetectionsEnabled",31); ++ m_int_configs[CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT] = sConfigMgr->GetIntDefault("Anticheat.MaxReportsForDailyReport",70); ++ + // Wintergrasp battlefield + m_bool_configs[CONFIG_WINTERGRASP_ENABLE] = sConfigMgr->GetBoolDefault("Wintergrasp.Enable", false); + m_int_configs[CONFIG_WINTERGRASP_PLR_MAX] = sConfigMgr->GetIntDefault("Wintergrasp.PlayerMax", 100); +@@ -2875,6 +2881,8 @@ void World::ResetDailyQuests() + + // change available dailies + sPoolMgr->ChangeDailyQuests(); ++ ++ sAnticheatMgr->ResetDailyReportStates(); + } + + void World::LoadDBAllowedSecurityLevel() +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 22dece5..075775c 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -142,6 +142,7 @@ enum WorldBoolConfigs + CONFIG_ALLOW_TICKETS, + CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, + CONFIG_PRESERVE_CUSTOM_CHANNELS, ++ CONFIG_ANTICHEAT_ENABLE, + CONFIG_PDUMP_NO_PATHS, + CONFIG_PDUMP_NO_OVERWRITE, + CONFIG_QUEST_IGNORE_AUTO_ACCEPT, +@@ -307,7 +308,10 @@ enum WorldIntConfigs + CONFIG_PRESERVE_CUSTOM_CHANNEL_DURATION, + CONFIG_PERSISTENT_CHARACTER_CLEAN_FLAGS, + CONFIG_LFG_OPTIONSMASK, ++ CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION, ++ CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT, + CONFIG_MAX_INSTANCES_PER_HOUR, ++ CONFIG_ANTICHEAT_DETECTIONS_ENABLED, + CONFIG_WARDEN_CLIENT_RESPONSE_DELAY, + CONFIG_WARDEN_CLIENT_CHECK_HOLDOFF, + CONFIG_WARDEN_CLIENT_FAIL_ACTION, +diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt +index ba2709f..ca0d3c5 100644 +--- a/src/server/scripts/CMakeLists.txt ++++ b/src/server/scripts/CMakeLists.txt +@@ -71,6 +71,7 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/shared + ${CMAKE_SOURCE_DIR}/src/server/shared/Database + ${CMAKE_SOURCE_DIR}/src/server/game/Accounts ++ ${CMAKE_SOURCE_DIR}/src/server/game/Anticheat + ${CMAKE_SOURCE_DIR}/src/server/game/Achievements + ${CMAKE_SOURCE_DIR}/src/server/game/Addons + ${CMAKE_SOURCE_DIR}/src/server/game/AI +diff --git a/src/server/scripts/Commands/cs_anticheat.cpp b/src/server/scripts/Commands/cs_anticheat.cpp +new file mode 100644 +index 0000000..3cc6784 +--- /dev/null ++++ b/src/server/scripts/Commands/cs_anticheat.cpp +@@ -0,0 +1,262 @@ ++/* ++ * 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, see . ++ */ ++#include "Language.h" ++#include "ScriptMgr.h" ++#include "ObjectMgr.h" ++#include "Chat.h" ++#include "AnticheatMgr.h" ++ ++class anticheat_commandscript : public CommandScript ++{ ++public: ++ anticheat_commandscript() : CommandScript("anticheat_commandscript") { } ++ ++ ChatCommand* GetCommands() const ++ { ++ static ChatCommand anticheatCommandTable[] = ++ { ++ { "global", SEC_GAMEMASTER, true, &HandleAntiCheatGlobalCommand, "", NULL }, ++ { "player", SEC_GAMEMASTER, true, &HandleAntiCheatPlayerCommand, "", NULL }, ++ { "delete", SEC_ADMINISTRATOR, true, &HandleAntiCheatDeleteCommand, "", NULL }, ++ { "handle", SEC_ADMINISTRATOR, true, &HandleAntiCheatHandleCommand, "", NULL }, ++ { "jail", SEC_GAMEMASTER, true, &HandleAnticheatJailCommand, "", NULL }, ++ { "warn", SEC_GAMEMASTER, true, &HandleAnticheatWarnCommand, "", NULL }, ++ { NULL, 0, false, NULL, "", NULL } ++ }; ++ ++ static ChatCommand commandTable[] = ++ { ++ { "anticheat", SEC_GAMEMASTER, true, NULL, "", anticheatCommandTable}, ++ { NULL, 0, false, NULL, "", NULL } ++ }; ++ ++ return commandTable; ++ } ++ ++ static bool HandleAnticheatWarnCommand(ChatHandler* handler, const char* args) ++ { ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ return false; ++ ++ Player* pTarget = NULL; ++ ++ std::string strCommand; ++ ++ char* command = strtok((char*)args, " "); ++ ++ if (command) ++ { ++ strCommand = command; ++ normalizePlayerName(strCommand); ++ ++ pTarget = sObjectAccessor->FindPlayerByName(strCommand.c_str()); //get player by name ++ }else ++ pTarget = handler->getSelectedPlayer(); ++ ++ if (!pTarget) ++ return false; ++ ++ WorldPacket data; ++ ++ // need copy to prevent corruption by strtok call in LineFromMessage original string ++ char* buf = strdup("The anticheat system has reported several times that you may be cheating. You will be monitored to confirm if this is accurate."); ++ char* pos = buf; ++ ++ while (char* line = handler->LineFromMessage(pos)) ++ { ++ handler->BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, NULL, NULL, line); ++ pTarget->GetSession()->SendPacket(&data); ++ } ++ ++ free(buf); ++ return true; ++ } ++ ++ static bool HandleAnticheatJailCommand(ChatHandler* handler, const char* args) ++ { ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ return false; ++ ++ Player* pTarget = NULL; ++ ++ std::string strCommand; ++ ++ char* command = strtok((char*)args, " "); ++ ++ if (command) ++ { ++ strCommand = command; ++ normalizePlayerName(strCommand); ++ ++ pTarget = sObjectAccessor->FindPlayerByName(strCommand.c_str()); //get player by name ++ }else ++ pTarget = handler->getSelectedPlayer(); ++ ++ if (!pTarget) ++ { ++ handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (pTarget == handler->GetSession()->GetPlayer()) ++ return false; ++ ++ // teleport both to jail. ++ pTarget->TeleportTo(1,16226.5f,16403.6f,-64.5f,3.2f); ++ handler->GetSession()->GetPlayer()->TeleportTo(1,16226.5f,16403.6f,-64.5f,3.2f); ++ ++ WorldLocation loc; ++ ++ // the player should be already there, but no :( ++ // pTarget->GetPosition(&loc); ++ ++ loc.m_mapId = 1; ++ loc.m_positionX = 16226.5f; ++ loc.m_positionY = 16403.6f; ++ loc.m_positionZ = -64.5f; ++ loc.m_orientation = 3.2f; ++ ++ pTarget->SetHomebind(loc,876); ++ return true; ++ } ++ ++ static bool HandleAntiCheatDeleteCommand(ChatHandler* handler, const char* args) ++ { ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ return false; ++ ++ std::string strCommand; ++ ++ char* command = strtok((char*)args, " "); //get entered name ++ ++ if (!command) ++ return true; ++ ++ strCommand = command; ++ ++ if (strCommand.compare("deleteall") == 0) ++ sAnticheatMgr->AnticheatDeleteCommand(0); ++ else ++ { ++ normalizePlayerName(strCommand); ++ Player* player = sObjectAccessor->FindPlayerByName(strCommand.c_str()); //get player by name ++ if (!player) ++ handler->PSendSysMessage("Player doesn't exist"); ++ else ++ sAnticheatMgr->AnticheatDeleteCommand(player->GetGUIDLow()); ++ } ++ ++ return true; ++ } ++ ++ static bool HandleAntiCheatPlayerCommand(ChatHandler* handler, const char* args) ++ { ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ return false; ++ ++ std::string strCommand; ++ ++ char* command = strtok((char*)args, " "); ++ ++ uint32 guid = 0; ++ Player* player = NULL; ++ ++ if (command) ++ { ++ strCommand = command; ++ ++ normalizePlayerName(strCommand); ++ player = sObjectAccessor->FindPlayerByName(strCommand.c_str()); //get player by name ++ ++ if (player) ++ guid = player->GetGUIDLow(); ++ }else ++ { ++ player = handler->getSelectedPlayer(); ++ if (player) ++ guid = player->GetGUIDLow(); ++ } ++ ++ if (!guid) ++ { ++ handler->PSendSysMessage("There is no player."); ++ return true; ++ } ++ ++ float average = sAnticheatMgr->GetAverage(guid); ++ uint32 total_reports = sAnticheatMgr->GetTotalReports(guid); ++ uint32 speed_reports = sAnticheatMgr->GetTypeReports(guid,0); ++ uint32 fly_reports = sAnticheatMgr->GetTypeReports(guid,1); ++ uint32 jump_reports = sAnticheatMgr->GetTypeReports(guid,3); ++ uint32 waterwalk_reports = sAnticheatMgr->GetTypeReports(guid,2); ++ uint32 teleportplane_reports = sAnticheatMgr->GetTypeReports(guid,4); ++ uint32 climb_reports = sAnticheatMgr->GetTypeReports(guid,5); ++ ++ handler->PSendSysMessage("Information about player %s",player->GetName().c_str()); ++ handler->PSendSysMessage("Average: %f || Total Reports: %u ",average,total_reports); ++ handler->PSendSysMessage("Speed Reports: %u || Fly Reports: %u || Jump Reports: %u ",speed_reports,fly_reports,jump_reports); ++ handler->PSendSysMessage("Walk On Water Reports: %u || Teleport To Plane Reports: %u",waterwalk_reports,teleportplane_reports); ++ handler->PSendSysMessage("Climb Reports: %u", climb_reports); ++ ++ return true; ++ } ++ ++ static bool HandleAntiCheatHandleCommand(ChatHandler* handler, const char* args) ++ { ++ std::string strCommand; ++ ++ char* command = strtok((char*)args, " "); ++ ++ if (!command) ++ return true; ++ ++ if (!handler->GetSession()->GetPlayer()) ++ return true; ++ ++ strCommand = command; ++ ++ if (strCommand.compare("on") == 0) ++ { ++ sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,true); ++ handler->SendSysMessage("The Anticheat System is now: Enabled!"); ++ } ++ else if (strCommand.compare("off") == 0) ++ { ++ sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,false); ++ handler->SendSysMessage("The Anticheat System is now: Disabled!"); ++ } ++ ++ return true; ++ } ++ ++ static bool HandleAntiCheatGlobalCommand(ChatHandler* handler, const char* /* args */) ++ { ++ if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) ++ { ++ handler->PSendSysMessage("The Anticheat System is disabled."); ++ return true; ++ } ++ ++ sAnticheatMgr->AnticheatGlobalCommand(handler); ++ ++ return true; ++ } ++}; ++ ++void AddSC_anticheat_commandscript() ++{ ++ new anticheat_commandscript(); ++} +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index c4e3ad8..bc74237 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -2585,6 +2585,40 @@ LevelReq.Auction = 1 + LevelReq.Mail = 1 + + # ++# Anticheat.Enable ++# Description: Enables or disables the Anticheat System functionality ++# Default: 1 - (Enabled) ++# 0 - (Disabled) ++ ++Anticheat.Enable = 1 ++ ++# Anticheat.ReportsForIngameWarnings ++# Description: How many reports the player must have to notify to GameMasters ingame when he generates a new report. ++# Default: 70 ++ ++Anticheat.ReportsForIngameWarnings = 70 ++ ++# Anticheat.DetectionsEnabled ++# Description: It represents which detections are enabled. ++# ++# SPEED_HACK_DETECTION = 1 ++# FLY_HACK_DETECTION = 2 ++# WALK_WATER_HACK_DETECTION = 4 ++# JUMP_HACK_DETECTION = 8 ++# TELEPORT_PLANE_HACK_DETECTION = 16 ++# CLIMB_HACK_DETECTION = 32 ++# ++# Default: 31 ++ ++Anticheat.DetectionsEnabled = 31 ++ ++# Anticheat.MaxReportsForDailyReport ++# Description: How many reports must the player have to make a report that it is in DB for a day (not only during the player's session). ++# Default: 70 ++ ++Anticheat.MaxReportsForDailyReport = 70 ++ ++# + # PlayerDump.DisallowPaths + # Description: Disallow using paths in PlayerDump output files + # Default: 1 diff --git a/arena1v1.diff b/arena1v1.diff new file mode 100644 index 0000000..83c5816 --- /dev/null +++ b/arena1v1.diff @@ -0,0 +1,748 @@ +From b4745dffa1a121915421f4aa385df0379a3b39ee Mon Sep 17 00:00:00 2001 +From: Lord Psyan +Date: Sun, 7 Dec 2014 00:59:19 -0500 +Subject: [PATCH] 1v1ArenaFITTED + +--- + .../arena1v1/character_v2.0.sql | 2 + + sql/TrinityCore-Patches/arena1v1/world_v1.0.sql | 3 + + src/server/game/Battlegrounds/ArenaTeam.cpp | 4 +- + src/server/game/Battlegrounds/ArenaTeam.h | 2 +- + src/server/game/Battlegrounds/Battleground.cpp | 5 + + src/server/game/Battlegrounds/Battleground.h | 2 +- + src/server/game/Battlegrounds/BattlegroundMgr.cpp | 2 +- + .../game/Battlegrounds/BattlegroundQueue.cpp | 2 + + src/server/game/Entities/Player/Player.cpp | 1 + + src/server/game/Handlers/BattleGroundHandler.cpp | 6 +- + src/server/game/Handlers/PetitionsHandler.cpp | 4 +- + src/server/game/Scripting/ScriptLoader.cpp | 6 +- + src/server/game/World/World.cpp | 8 + + src/server/game/World/World.h | 8 +- + src/server/scripts/Custom/npc_arena1v1.cpp | 309 +++++++++++++++++++++ + src/server/scripts/Custom/npc_arena1v1.h | 69 +++++ + src/server/worldserver/worldserver.conf.dist | 65 +++++ + 17 files changed, 489 insertions(+), 9 deletions(-) + create mode 100644 sql/TrinityCore-Patches/arena1v1/character_v2.0.sql + create mode 100644 sql/TrinityCore-Patches/arena1v1/world_v1.0.sql + create mode 100644 src/server/scripts/Custom/npc_arena1v1.cpp + create mode 100644 src/server/scripts/Custom/npc_arena1v1.h + +diff --git a/sql/TrinityCore-Patches/arena1v1/character_v2.0.sql b/sql/TrinityCore-Patches/arena1v1/character_v2.0.sql +new file mode 100644 +index 0000000..9f6e4a4 +--- /dev/null ++++ b/sql/TrinityCore-Patches/arena1v1/character_v2.0.sql +@@ -0,0 +1,2 @@ ++-- Delete all 5v5 teams and members (core will crash if any 5v5 team exist) ++DELETE arena_team_member, arena_team FROM arena_team_member, arena_team WHERE arena_team_member.arenaTeamId = arena_team.arenaTeamId AND arena_team.type = 5; +diff --git a/sql/TrinityCore-Patches/arena1v1/world_v1.0.sql b/sql/TrinityCore-Patches/arena1v1/world_v1.0.sql +new file mode 100644 +index 0000000..75d4337 +--- /dev/null ++++ b/sql/TrinityCore-Patches/arena1v1/world_v1.0.sql +@@ -0,0 +1,3 @@ ++DELETE FROM `creature_template` WHERE `entry` = 535200; ++INSERT INTO `creature_template` (`entry`, `difficulty_entry_1`, `difficulty_entry_2`, `difficulty_entry_3`, `KillCredit1`, `KillCredit2`, `modelid1`, `modelid2`, `modelid3`, `modelid4`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `speed_walk`, `speed_run`, `scale`, `rank`, `dmgschool`, `BaseAttackTime`, `RangeAttackTime`, `BaseVariance`, `RangeVariance`, `unit_class`, `unit_flags`, `unit_flags2`, `dynamicflags`, `family`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `resistance1`, `resistance2`, `resistance3`, `resistance4`, `resistance5`, `resistance6`, `spell1`, `spell2`, `spell3`, `spell4`, `spell5`, `spell6`, `spell7`, `spell8`, `PetSpellDataId`, `VehicleId`, `mingold`, `maxgold`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `HealthModifier`, `ManaModifier`, `ArmorModifier`, `DamageModifier`, `ExperienceModifier`, `RacialLeader`, `questItem1`, `questItem2`, `questItem3`, `questItem4`, `questItem5`, `questItem6`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`, `VerifiedBuild`) VALUES ++('535200','0','0','0','0','0','18','0','18','0','LordPsyan','1v1 Arena master','','0','59','61','0','35','1','1.48','1.14286','0','0','0','1500','1900','1','1','1','0','0','0','0','0','0','0','0','7','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','','1','3','1','1','1','1','1','1','0','0','0','0','0','0','0','0','1','0','0','npc_1v1arena','1'); +diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp +index 19617a8..2172d82 100644 +--- a/src/server/game/Battlegrounds/ArenaTeam.cpp ++++ b/src/server/game/Battlegrounds/ArenaTeam.cpp +@@ -439,7 +439,7 @@ void ArenaTeam::Query(WorldSession* session) + WorldPacket data(SMSG_ARENA_TEAM_QUERY_RESPONSE, 4*7+GetName().size()+1); + data << uint32(GetId()); // team id + data << GetName(); // team name +- data << uint32(GetType()); // arena team type (2=2x2, 3=3x3 or 5=5x5) ++ data << uint32(GetType() == 1 ? 5 : GetType()); // arena team type (2=2x2, 3=3x3 or 1=1x1(modify 1 to 5, so player can see arenateam in 5v5 slot)) + data << uint32(BackgroundColor); // background color + data << uint32(EmblemStyle); // emblem style + data << uint32(EmblemColor); // emblem color +@@ -611,6 +611,8 @@ uint32 ArenaTeam::GetPoints(uint32 memberRating) + points *= 0.76f; + else if (Type == ARENA_TEAM_3v3) + points *= 0.88f; ++ else if (Type == ARENA_TEAM_5v5) // 1v1 Arena ++ points *= sWorld->getFloatConfig(CONFIG_ARENA_1V1_ARENAPOINTS_MULTI); + + return (uint32) points; + } +diff --git a/src/server/game/Battlegrounds/ArenaTeam.h b/src/server/game/Battlegrounds/ArenaTeam.h +index 2f6472e..5b00577 100644 +--- a/src/server/game/Battlegrounds/ArenaTeam.h ++++ b/src/server/game/Battlegrounds/ArenaTeam.h +@@ -82,7 +82,7 @@ enum ArenaTeamTypes + { + ARENA_TEAM_2v2 = 2, + ARENA_TEAM_3v3 = 3, +- ARENA_TEAM_5v5 = 5 ++ ARENA_TEAM_5v5 = 1 // 1v1 Arena - 5v5 doesn't exist anymore + }; + + struct ArenaTeamMember +diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp +index 0853d65..4bb22bd 100644 +--- a/src/server/game/Battlegrounds/Battleground.cpp ++++ b/src/server/game/Battlegrounds/Battleground.cpp +@@ -460,6 +460,11 @@ inline void Battleground::_ProcessJoin(uint32 diff) + // First start warning - 2 or 1 minute + SendMessageToAll(StartMessageIds[BG_STARTING_EVENT_FIRST], CHAT_MSG_BG_SYSTEM_NEUTRAL); + } ++ ++ // 1v1 Arena - Start arena after 15s, when all players are in arena ++ if(GetArenaType() == ARENA_TYPE_5v5 && GetStartDelayTime() > StartDelayTimes[BG_STARTING_EVENT_THIRD] && (m_PlayersCount[0] + m_PlayersCount[1]) == 2) ++ SetStartDelayTime(StartDelayTimes[BG_STARTING_EVENT_THIRD]); ++ + // After 1 minute or 30 seconds, warning is signaled + else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_SECOND] && !(m_Events & BG_STARTING_EVENT_2)) + { +diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h +index ed4885c..12f9f65 100644 +--- a/src/server/game/Battlegrounds/Battleground.h ++++ b/src/server/game/Battlegrounds/Battleground.h +@@ -173,7 +173,7 @@ enum ArenaType + { + ARENA_TYPE_2v2 = 2, + ARENA_TYPE_3v3 = 3, +- ARENA_TYPE_5v5 = 5 ++ ARENA_TYPE_5v5 = 1 // 1v1 Arena - 5v5 doesn't exist anymore + }; + + enum BattlegroundStartingEvents +diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.cpp b/src/server/game/Battlegrounds/BattlegroundMgr.cpp +index f5ee169..57e3ff0 100644 +--- a/src/server/game/Battlegrounds/BattlegroundMgr.cpp ++++ b/src/server/game/Battlegrounds/BattlegroundMgr.cpp +@@ -423,7 +423,7 @@ Battleground* BattlegroundMgr::CreateNewBattleground(BattlegroundTypeId original + maxPlayersPerTeam = 3; + break; + case ARENA_TYPE_5v5: +- maxPlayersPerTeam = 5; ++ maxPlayersPerTeam = 1; // 1v1 Arena + break; + } + +diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp +index 3993ec0..eab01b2 100644 +--- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp ++++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp +@@ -163,6 +163,7 @@ GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* grp, Battlegr + { + ArenaTeam* Team = sArenaTeamMgr->GetArenaTeamById(arenateamid); + if (Team) ++ if ((Team->GetType() == ARENA_TYPE_5v5 && sWorld->getBoolConfig(CONFIG_ARENA_1V1_ANNOUNCER)) || Team->GetType() != ARENA_TYPE_5v5) + sWorld->SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, Team->GetName().c_str(), ginfo->ArenaType, ginfo->ArenaType, ginfo->ArenaTeamRating); + } + +@@ -354,6 +355,7 @@ void BattlegroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) + // announce to world if arena team left queue for rated match, show only once + if (group->ArenaType && group->IsRated && group->Players.empty() && sWorld->getBoolConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) + if (ArenaTeam* Team = sArenaTeamMgr->GetArenaTeamById(group->ArenaTeamId)) ++ if (Team && ((Team->GetType() == ARENA_TYPE_5v5 && sWorld->getBoolConfig(CONFIG_ARENA_1V1_ANNOUNCER)) || Team->GetType() != ARENA_TYPE_5v5)) + sWorld->SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, Team->GetName().c_str(), group->ArenaType, group->ArenaType, group->ArenaTeamRating); + + // if player leaves queue and he is invited to rated arena match, then he have to lose +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index ea85f8b..b9a1cd9 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -21958,6 +21958,7 @@ uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) const + uint32 max_personal_rating = 0; + for (uint8 i = minarenaslot; i < MAX_ARENA_SLOT; ++i) + { ++ if(i == 2 && sWorld->getBoolConfig(CONFIG_ARENA_1V1_VENDOR_RATING) == false) continue; + if (ArenaTeam* at = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamId(i))) + { + uint32 p_rating = GetArenaPersonalRating(i); +diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp +index ce8ae92..14db7f1 100644 +--- a/src/server/game/Handlers/BattleGroundHandler.cpp ++++ b/src/server/game/Handlers/BattleGroundHandler.cpp +@@ -37,7 +37,7 @@ + // Prepatch by LordPsyan + // 01 + // 02 +-// 03 ++#include "../../scripts/Custom/npc_arena1v1.h" + // 04 + // 05 + // 06 +@@ -460,6 +460,10 @@ void WorldSession::HandleBattleFieldPortOpcode(WorldPacket &recvData) + if (!_player->IsInvitedForBattlegroundQueueType(bgQueueTypeId)) + return; // cheating? + ++ // 1v1 Arena. Player can't join arena when forbidden talents are used. ++ if(bgQueueTypeId == BATTLEGROUND_QUEUE_5v5 && Arena1v1CheckTalents(_player) == false) ++ return; ++ + if (!_player->InBattleground()) + _player->SetBattlegroundEntryPoint(); + +diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp +index 1e6dc1a..4c16251 100644 +--- a/src/server/game/Handlers/PetitionsHandler.cpp ++++ b/src/server/game/Handlers/PetitionsHandler.cpp +@@ -951,7 +951,7 @@ void WorldSession::SendPetitionShowList(ObjectGuid guid) + } + else + { +- data << uint8(3); // count ++ data << uint8(2); // count + // 2v2 + data << uint32(1); // index + data << uint32(ARENA_TEAM_CHARTER_2v2); // charter entry +@@ -967,12 +967,14 @@ void WorldSession::SendPetitionShowList(ObjectGuid guid) + data << uint32(3); // unknown + data << uint32(3); // required signs? + // 5v5 ++ /* Disable purchase 5v5 petition - edit to 1v1 doesn't work (client prevent it) + data << uint32(3); // index + data << uint32(ARENA_TEAM_CHARTER_5v5); // charter entry + data << uint32(CHARTER_DISPLAY_ID); // charter display id + data << uint32(ARENA_TEAM_CHARTER_5v5_COST); // charter cost + data << uint32(5); // unknown + data << uint32(5); // required signs? ++ */ + } + + SendPacket(&data); +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index bd43fbe..cf916ed 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1475,7 +1475,8 @@ void AddBattlegroundScripts() + // start58 + // start59 + // start60 +-// start61 ++// 1v1 Arena ++void AddSC_npc_1v1arena(); + // start62 + // start63 + // start64 +@@ -1601,7 +1602,8 @@ void AddCustomScripts() + // end58 + // end59 + // end60 +-// end61 ++// 1v1 Arena ++ AddSC_npc_1v1arena(); + // end62 + // end63 + // end64 +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index afd87b8..35e1971 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1044,6 +1044,14 @@ void World::LoadConfigSettings(bool reload) + m_bool_configs[CONFIG_ARENA_SEASON_IN_PROGRESS] = sConfigMgr->GetBoolDefault("Arena.ArenaSeason.InProgress", true); + m_bool_configs[CONFIG_ARENA_LOG_EXTENDED_INFO] = sConfigMgr->GetBoolDefault("ArenaLog.ExtendedInfo", false); + ++ m_bool_configs[CONFIG_ARENA_1V1_ENABLE] = sConfigMgr->GetBoolDefault("Arena.1v1.Enable", true); ++ m_bool_configs[CONFIG_ARENA_1V1_ANNOUNCER] = sConfigMgr->GetBoolDefault("Arena.1v1.Announcer", false); ++ m_int_configs[CONFIG_ARENA_1V1_MIN_LEVEL] = sConfigMgr->GetIntDefault("Arena.1v1.MinLevel", 80); ++ m_int_configs[CONFIG_ARENA_1V1_COSTS] = sConfigMgr->GetIntDefault("Arena.1v1.Costs", 400000); ++ m_bool_configs[CONFIG_ARENA_1V1_VENDOR_RATING] = sConfigMgr->GetBoolDefault("Arena.1v1.VendorRating", false); ++ m_float_configs[CONFIG_ARENA_1V1_ARENAPOINTS_MULTI] = sConfigMgr->GetFloatDefault("Arena.1v1.ArenaPointsMulti", 0.64f); ++ m_bool_configs[CONFIG_ARENA_1V1_BLOCK_FORBIDDEN_TALENTS] = sConfigMgr->GetBoolDefault("Arena.1v1.BlockForbiddenTalents", true); ++ + m_bool_configs[CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN] = sConfigMgr->GetBoolDefault("OffhandCheckAtSpellUnlearn", true); + + m_int_configs[CONFIG_CREATURE_PICKPOCKET_REFILL] = sConfigMgr->GetIntDefault("Creature.PickPocketRefillDelay", 10 * MINUTE); +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 2f13051..77ecee5 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -130,6 +130,10 @@ enum WorldBoolConfigs + CONFIG_ARENA_QUEUE_ANNOUNCER_PLAYERONLY, + CONFIG_ARENA_SEASON_IN_PROGRESS, + CONFIG_ARENA_LOG_EXTENDED_INFO, ++ CONFIG_ARENA_1V1_ENABLE, ++ CONFIG_ARENA_1V1_ANNOUNCER, ++ CONFIG_ARENA_1V1_VENDOR_RATING, ++ CONFIG_ARENA_1V1_BLOCK_FORBIDDEN_TALENTS, + CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN, + CONFIG_VMAP_INDOOR_CHECK, + CONFIG_START_ALL_SPELLS, +@@ -220,7 +224,7 @@ enum WorldFloatConfigs + // 32 + // 33 + // 34 +- // 35 ++ CONFIG_ARENA_1V1_ARENAPOINTS_MULTI, + // 36 + // 37 + // 38 +@@ -354,6 +358,8 @@ enum WorldIntConfigs + CONFIG_ARENA_START_RATING, + CONFIG_ARENA_START_PERSONAL_RATING, + CONFIG_ARENA_START_MATCHMAKER_RATING, ++ CONFIG_ARENA_1V1_MIN_LEVEL, ++ CONFIG_ARENA_1V1_COSTS, + CONFIG_MAX_WHO, + CONFIG_HONOR_AFTER_DUEL, + CONFIG_PVP_TOKEN_MAP_TYPE, +diff --git a/src/server/scripts/Custom/npc_arena1v1.cpp b/src/server/scripts/Custom/npc_arena1v1.cpp +new file mode 100644 +index 0000000..c2f6792 +--- /dev/null ++++ b/src/server/scripts/Custom/npc_arena1v1.cpp +@@ -0,0 +1,309 @@ ++/* ++ * ++ * Copyright (C) 2013 Emu-Devstore ++ * Written by Teiby ++ * ++ */ ++ ++#include "ScriptMgr.h" ++#include "ArenaTeamMgr.h" ++#include "Common.h" ++#include "DisableMgr.h" ++#include "BattlegroundMgr.h" ++#include "Battleground.h" ++#include "ArenaTeam.h" ++#include "Language.h" ++#include "npc_arena1v1.h" ++ ++ ++class npc_1v1arena : public CreatureScript ++{ ++public: ++ npc_1v1arena() : CreatureScript("npc_1v1arena") ++ { ++ } ++ ++ ++ bool JoinQueueArena(Player* player, Creature* me, bool isRated) ++ { ++ if(!player || !me) ++ return false; ++ ++ if(sWorld->getIntConfig(CONFIG_ARENA_1V1_MIN_LEVEL) > player->getLevel()) ++ return false; ++ ++ ObjectGuid guid = player->GetGUID(); ++ uint8 arenaslot = ArenaTeam::GetSlotByType(ARENA_TEAM_5v5); ++ uint8 arenatype = ARENA_TYPE_5v5; ++ uint32 arenaRating = 0; ++ uint32 matchmakerRating = 0; ++ ++ // ignore if we already in BG or BG queue ++ if (player->InBattleground()) ++ return false; ++ ++ //check existance ++ Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(BATTLEGROUND_AA); ++ if (!bg) ++ { ++ TC_LOG_ERROR("Arena", "Battleground: template bg (all arenas) not found"); ++ return false; ++ } ++ ++ if (DisableMgr::IsDisabledFor(DISABLE_TYPE_BATTLEGROUND, BATTLEGROUND_AA, NULL)) ++ { ++ ChatHandler(player->GetSession()).PSendSysMessage(LANG_ARENA_DISABLED); ++ return false; ++ } ++ ++ BattlegroundTypeId bgTypeId = bg->GetTypeID(); ++ BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(bgTypeId, arenatype); ++ PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketByLevel(bg->GetMapId(), player->getLevel()); ++ if (!bracketEntry) ++ return false; ++ ++ GroupJoinBattlegroundResult err = ERR_GROUP_JOIN_BATTLEGROUND_FAIL; ++ ++ // check if already in queue ++ if (player->GetBattlegroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES) ++ //player is already in this queue ++ return false; ++ // check if has free queue slots ++ if (!player->HasFreeBattlegroundQueueId()) ++ return false; ++ ++ uint32 ateamId = 0; ++ ++ if(isRated) ++ { ++ ateamId = player->GetArenaTeamId(arenaslot); ++ ArenaTeam* at = sArenaTeamMgr->GetArenaTeamById(ateamId); ++ if (!at) ++ { ++ player->GetSession()->SendNotInArenaTeamPacket(arenatype); ++ return false; ++ } ++ ++ // get the team rating for queueing ++ arenaRating = at->GetRating(); ++ matchmakerRating = arenaRating; ++ // the arenateam id must match for everyone in the group ++ ++ if (arenaRating <= 0) ++ arenaRating = 1; ++ } ++ ++ BattlegroundQueue &bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); ++ bg->SetRated(isRated); ++ ++ GroupQueueInfo* ginfo = bgQueue.AddGroup(player, NULL, bgTypeId, bracketEntry, arenatype, isRated, false, arenaRating, matchmakerRating, ateamId); ++ uint32 avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, bracketEntry->GetBracketId()); ++ uint32 queueSlot = player->AddBattlegroundQueueId(bgQueueTypeId); ++ ++ WorldPacket data; ++ // send status packet (in queue) ++ sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenatype, 0); ++ player->GetSession()->SendPacket(&data); ++ ++ sBattlegroundMgr->ScheduleQueueUpdate(matchmakerRating, arenatype, bgQueueTypeId, bgTypeId, bracketEntry->GetBracketId()); ++ ++ return true; ++ } ++ ++ ++ bool CreateArenateam(Player* player, Creature* me) ++ { ++ if(!player || !me) ++ return false; ++ ++ uint8 slot = ArenaTeam::GetSlotByType(ARENA_TEAM_5v5); ++ if (slot >= MAX_ARENA_SLOT) ++ return false; ++ ++ // Check if player is already in an arena team ++ if (player->GetArenaTeamId(slot)) ++ { ++ player->GetSession()->SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, player->GetName(), "", ERR_ALREADY_IN_ARENA_TEAM); ++ return false; ++ } ++ ++ ++ // Teamname = playername ++ // if teamname exist, we have to choose another name (playername + number) ++ int i = 1; ++ std::stringstream teamName; ++ teamName << player->GetName(); ++ do ++ { ++ if(sArenaTeamMgr->GetArenaTeamByName(teamName.str()) != NULL) // teamname exist, so choose another name ++ { ++ teamName.str(std::string()); ++ teamName << player->GetName() << (i++); ++ } ++ else ++ break; ++ } while (i < 100); // should never happen ++ ++ // Create arena team ++ ArenaTeam* arenaTeam = new ArenaTeam(); ++ ++ if (!arenaTeam->Create(player->GetGUID(), ARENA_TEAM_5v5, teamName.str(), 4283124816, 45, 4294242303, 5, 4294705149)) ++ { ++ delete arenaTeam; ++ return false; ++ } ++ ++ // Register arena team ++ sArenaTeamMgr->AddArenaTeam(arenaTeam); ++ arenaTeam->AddMember(player->GetGUID()); ++ ++ ChatHandler(player->GetSession()).SendSysMessage("1v1 Arenateam successful created!"); ++ ++ return true; ++ } ++ ++ ++ bool OnGossipHello(Player* player, Creature* me) ++ { ++ if(!player || !me) ++ return true; ++ ++ if(sWorld->getBoolConfig(CONFIG_ARENA_1V1_ENABLE) == false) ++ { ++ ChatHandler(player->GetSession()).SendSysMessage("1v1 disabled!"); ++ return true; ++ } ++ ++ if(player->InBattlegroundQueueForBattlegroundQueueType(BATTLEGROUND_QUEUE_5v5)) ++ player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_CHAT, "Leave queue 1v1 Arena", GOSSIP_SENDER_MAIN, 3, "Are you sure?", 0, false); ++ else ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Sign up 1v1 Arena (unrated)", GOSSIP_SENDER_MAIN, 20); ++ ++ if(player->GetArenaTeamId(ArenaTeam::GetSlotByType(ARENA_TEAM_5v5)) == 0) ++ player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_CHAT, "Create new 1v1 Arenateam", GOSSIP_SENDER_MAIN, 1, "Create 1v1 arenateam?", sWorld->getIntConfig(CONFIG_ARENA_1V1_COSTS), false); ++ else ++ { ++ if(player->InBattlegroundQueueForBattlegroundQueueType(BATTLEGROUND_QUEUE_5v5) == false) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Sign up 1v1 Arena (rated)", GOSSIP_SENDER_MAIN, 2); ++ player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_CHAT, "Disband arenateam", GOSSIP_SENDER_MAIN, 5, "Are you sure?", 0, false); ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Show statistics", GOSSIP_SENDER_MAIN, 4); ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Script Info", GOSSIP_SENDER_MAIN, 8); ++ player->SEND_GOSSIP_MENU(68, me->GetGUID()); ++ return true; ++ } ++ ++ ++ ++ bool OnGossipSelect(Player* player, Creature* me, uint32 /*uiSender*/, uint32 uiAction) ++ { ++ if(!player || !me) ++ return true; ++ ++ player->PlayerTalkClass->ClearMenus(); ++ ++ switch (uiAction) ++ { ++ case 1: // Create new Arenateam ++ { ++ if(sWorld->getIntConfig(CONFIG_ARENA_1V1_MIN_LEVEL) <= player->getLevel()) ++ { ++ if(player->GetMoney() >= sWorld->getIntConfig(CONFIG_ARENA_1V1_COSTS) && CreateArenateam(player, me)) ++ player->ModifyMoney(sWorld->getIntConfig(CONFIG_ARENA_1V1_COSTS) * -1); ++ } ++ else ++ { ++ ChatHandler(player->GetSession()).PSendSysMessage("You need level %u+ to create an 1v1 arenateam.", sWorld->getIntConfig(CONFIG_ARENA_1V1_MIN_LEVEL)); ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ } ++ break; ++ ++ case 2: // Join Queue Arena (rated) ++ { ++ if(Arena1v1CheckTalents(player) && JoinQueueArena(player, me, true) == false) ++ ChatHandler(player->GetSession()).SendSysMessage("Something went wrong while join queue."); ++ ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ break; ++ ++ case 20: // Join Queue Arena (unrated) ++ { ++ if(Arena1v1CheckTalents(player) && JoinQueueArena(player, me, false) == false) ++ ChatHandler(player->GetSession()).SendSysMessage("Something went wrong while join queue."); ++ ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ break; ++ ++ case 3: // Leave Queue ++ { ++ WorldPacket Data; ++ Data << (uint8)0x1 << (uint8)0x0 << (uint32)BATTLEGROUND_AA << (uint16)0x0 << (uint8)0x0; ++ player->GetSession()->HandleBattleFieldPortOpcode(Data); ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ break; ++ ++ case 4: // get statistics ++ { ++ ArenaTeam* at = sArenaTeamMgr->GetArenaTeamById(player->GetArenaTeamId(ArenaTeam::GetSlotByType(ARENA_TEAM_5v5))); ++ if(at) ++ { ++ std::stringstream s; ++ s << "Rating: " << at->GetStats().Rating; ++ s << "\nRank: " << at->GetStats().Rank; ++ s << "\nSeason Games: " << at->GetStats().SeasonGames; ++ s << "\nSeason Wins: " << at->GetStats().SeasonWins; ++ s << "\nWeek Games: " << at->GetStats().WeekGames; ++ s << "\nWeek Wins: " << at->GetStats().WeekWins; ++ ++ ChatHandler(player->GetSession()).PSendSysMessage(s.str().c_str()); ++ } ++ } ++ break; ++ ++ ++ case 5: // Disband arenateam ++ { ++ WorldPacket Data; ++ Data << (uint32)player->GetArenaTeamId(ArenaTeam::GetSlotByType(ARENA_TEAM_5v5)); ++ player->GetSession()->HandleArenaTeamLeaveOpcode(Data); ++ ChatHandler(player->GetSession()).SendSysMessage("Arenateam deleted!"); ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ break; ++ ++ case 8: // Script Info ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Developer: Teiby", GOSSIP_SENDER_MAIN, uiAction); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Website: www.teiby.de", GOSSIP_SENDER_MAIN, uiAction); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Version: 2.1", GOSSIP_SENDER_MAIN, uiAction); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "<-- Back", GOSSIP_SENDER_MAIN, 7); ++ player->SEND_GOSSIP_MENU(68, me->GetGUID()); ++ return true; ++ } ++ break; ++ ++ } ++ ++ OnGossipHello(player, me); ++ return true; ++ } ++}; ++ ++ ++void AddSC_npc_1v1arena() ++{ ++ new npc_1v1arena(); ++} +\ No newline at end of file +diff --git a/src/server/scripts/Custom/npc_arena1v1.h b/src/server/scripts/Custom/npc_arena1v1.h +new file mode 100644 +index 0000000..caa75a2 +--- /dev/null ++++ b/src/server/scripts/Custom/npc_arena1v1.h +@@ -0,0 +1,69 @@ ++/* ++ * ++ * Copyright (C) 2013 Emu-Devstore ++ * Written by Teiby ++ * ++ */ ++ ++#ifndef ARENA_1V1_H ++#define ARENA_1V1_H ++ ++// TalentTab.dbc -> TalentTabID ++const uint32 FORBIDDEN_TALENTS_IN_1V1_ARENA[] = ++{ ++ // Healer ++ 201, // PriestDiscipline ++ 202, // PriestHoly ++ 382, // PaladinHoly ++ 262, // ShamanRestoration ++ 282, // DruidRestoration ++ ++ // Tanks ++ //383, // PaladinProtection ++ //163, // WarriorProtection ++ ++ 0 // End ++}; ++ ++ ++// Return false, if player have invested more than 35 talentpoints in a forbidden talenttree. ++static bool Arena1v1CheckTalents(Player* player) ++{ ++ if(!player) ++ return false; ++ ++ if(sWorld->getBoolConfig(CONFIG_ARENA_1V1_BLOCK_FORBIDDEN_TALENTS) == false) ++ return true; ++ ++ uint32 count = 0; ++ for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) ++ { ++ TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); ++ ++ if (!talentInfo) ++ continue; ++ ++ for (int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank) ++ { ++ if (talentInfo->RankID[rank] == 0) ++ continue; ++ ++ if (player->HasTalent(talentInfo->RankID[rank], player->GetActiveSpec())) ++ { ++ for(int8 i = 0; FORBIDDEN_TALENTS_IN_1V1_ARENA[i] != 0; i++) ++ if(FORBIDDEN_TALENTS_IN_1V1_ARENA[i] == talentInfo->TalentTab) ++ count += rank + 1; ++ } ++ } ++ } ++ ++ if(count >= 36) ++ { ++ ChatHandler(player->GetSession()).SendSysMessage("You can't join, because you have invested too many points in a forbidden talent. Please edit your talents."); ++ return false; ++ } ++ else ++ return true; ++} ++ ++#endif +\ No newline at end of file +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index c40da51..cebdf3d 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -2331,6 +2331,71 @@ ArenaLog.ExtendedInfo = 0 + + # + ################################################################################################### ++# 1V1 ARENA CONFIG ++# ++# Arena.1v1.Enable ++# Description: Enable the 1v1 arena. ++# Default: 0 - (Disabled) ++# 1 - (Enabled) ++ ++Arena.1v1.Enable = 1 ++ ++# ++# Arena.1v1.Announcer ++# Description: Announce 1v1 arena queue status to chat. ++# Arena.QueueAnnouncer.Enable must be enabled. ++# Default: 0 - (Disabled) ++# 1 - (Enabled) ++ ++Arena.1v1.Announcer = 0 ++ ++# ++# Arena.1v1.MinLevel ++# Description: Min level to create an arena team ++# Default: 80 ++ ++Arena.1v1.MinLevel = 80 ++ ++# ++# Arena.1v1.Costs ++# Description: Costs for create an arena team ++# Default: 400000 - (40 gold) ++ ++Arena.1v1.Costs = 400000 ++ ++# ++# Arena.1v1.VendorRating ++# Description: If true, 1v1 rating will use to calculate highest personal-rating (extended costs). ++# Note: The vendor-item will show as not buyable (red), but players can buy it, if enabled and rating is high enough. ++# Default: 0 - (false) ++# 1 - (true) ++ ++Arena.1v1.VendorRating = 0 ++ ++# ++# Arena.1v1.ArenaPointsMulti ++# Description: An 5v5 arena team with 1500 rating will gain 344 points per week (blizzlike). ++# 3v3 with same rating will gain 302 points (5v5points * 0.88) ++# 2v2 will gain 261 points (5v5points * 0.76) ++# and 1v1 will gain 167 points (5v5points * 0.64) ++# With this multiplier you can modify the arenapoints for 1v1. ++# Default: 0.64 ++ ++Arena.1v1.ArenaPointsMulti = 0.64 ++ ++# ++# Arena.1v1.BlockForbiddenTalents ++# Description: If true, healers can't join 1v1 arena, if they invested more than 35 talentpoints in a healing-talenttree. ++# You can also block tanks and other talents, if you modify FORBIDDEN_TALENTS_IN_1V1_ARENA in the npc_arena1v1.h file (hardcoding). See TalentTab.dbc for available talents (you will need an DBC-Editor). ++# Default: 1 - (true) ++# 0 - (false) ++ ++Arena.1v1.BlockForbiddenTalents = 1 ++ ++# ++################################################################################################### ++ ++################################################################################################### \ No newline at end of file diff --git a/arena_ready_check.diff b/arena_ready_check.diff new file mode 100644 index 0000000..de0eda7 --- /dev/null +++ b/arena_ready_check.diff @@ -0,0 +1,116 @@ +diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp +index e8c3446..d4c4c7c 100644 +--- a/src/server/game/Battlegrounds/Battleground.cpp ++++ b/src/server/game/Battlegrounds/Battleground.cpp +@@ -464,6 +464,9 @@ inline void Battleground::_ProcessJoin(uint32 diff) + { + m_Events |= BG_STARTING_EVENT_2; + SendMessageToAll(StartMessageIds[BG_STARTING_EVENT_SECOND], CHAT_MSG_BG_SYSTEM_NEUTRAL); ++ ++ if (this->isArena()) ++ this->SendArenaReadyCheck(); + } + // After 30 or 15 seconds, warning is signaled + else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_THIRD] && !(m_Events & BG_STARTING_EVENT_3)) +@@ -489,6 +492,13 @@ inline void Battleground::_ProcessJoin(uint32 diff) + for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr) + if (Player* player = ObjectAccessor::FindPlayer(itr->first)) + { ++ if (!m_ArenaReadyCheckMap.count(itr->first)) ++ { ++ WorldPacket data(0x3C6, 0x1); ++ data << uint8(0x1); ++ player->GetSession()->SendPacket(&data); ++ } ++ + // BG Status packet + WorldPacket status; + BattlegroundQueueTypeId bgQueueTypeId = sBattlegroundMgr->BGQueueTypeId(m_TypeID, GetArenaType()); +@@ -519,6 +529,7 @@ inline void Battleground::_ProcessJoin(uint32 diff) + } + + CheckWinConditions(); ++ m_ArenaReadyCheckMap.clear(); + } + else + { +@@ -1832,3 +1843,28 @@ uint8 Battleground::GetUniqueBracketId() const + { + return GetMinLevel() / 10; + } ++ ++void Battleground::SendArenaReadyCheck() const ++{ ++ WorldPacket packet_0x1(0x051, 0x17); ++ packet_0x1.appendPackGUID(0xFFFFFFFF); ++ packet_0x1 << uint8(0x0); ++ packet_0x1 << std::string("Arena"); ++ packet_0x1 << uint8(0x0); ++ packet_0x1 << uint8(0xa); ++ packet_0x1 << uint8(0x0); ++ packet_0x1 << uint8(0x4); ++ packet_0x1 << uint8(0x0); ++ ++ WorldPacket packet_0x2(0x322, 0x8); ++ packet_0x2 << uint64(0xFFFFFFFF); ++ ++ for (const auto itr : m_Players) ++ { ++ if (const Player* const player = ObjectAccessor::FindPlayer(itr.first)) ++ { ++ player->GetSession()->SendPacket(&packet_0x1); ++ player->GetSession()->SendPacket(&packet_0x2); ++ } ++ } ++} +diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h +index 001c33c..9bd5eb8 100644 +--- a/src/server/game/Battlegrounds/Battleground.h ++++ b/src/server/game/Battlegrounds/Battleground.h +@@ -465,6 +465,9 @@ class Battleground + + virtual uint32 GetPrematureWinner(); + ++ std::set m_ArenaReadyCheckMap; ++ void SendArenaReadyCheck() const; ++ + // because BattleGrounds with different types and same level range has different m_BracketId + uint8 GetUniqueBracketId() const; + +diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp +index 8d044e7..42719e8 100644 +--- a/src/server/game/Handlers/GroupHandler.cpp ++++ b/src/server/game/Handlers/GroupHandler.cpp +@@ -32,6 +32,7 @@ + #include "World.h" + #include "WorldPacket.h" + #include "WorldSession.h" ++#include "Battleground.h" + + class Aura; + +@@ -691,6 +692,9 @@ void WorldSession::HandleRaidReadyCheckOpcode(WorldPacket& recvData) + + if (recvData.empty()) // request + { ++ if (this->GetPlayer()->InArena()) ++ return; ++ + /** error handling **/ + if (!group->IsLeader(GetPlayer()->GetGUID()) && !group->IsAssistant(GetPlayer()->GetGUID())) + return; +@@ -708,6 +712,14 @@ void WorldSession::HandleRaidReadyCheckOpcode(WorldPacket& recvData) + uint8 state; + recvData >> state; + ++ if (this->GetPlayer()->InArena() && state) ++ { ++ this->GetPlayer()->GetBattleground()->m_ArenaReadyCheckMap.insert(this->GetPlayer()->GetGUID()); ++ ++ if (this->GetPlayer()->GetBattleground()->GetPlayersSize() == this->GetPlayer()->GetBattleground()->m_ArenaReadyCheckMap.size()) ++ this->GetPlayer()->GetBattleground()->SetStartDelayTime(BG_START_DELAY_NONE); ++ } ++ + // everything's fine, do it + WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9); + data << uint64(GetPlayer()->GetGUID()); \ No newline at end of file diff --git a/arena_template.diff b/arena_template.diff new file mode 100644 index 0000000..2f56ee0 --- /dev/null +++ b/arena_template.diff @@ -0,0 +1,4290 @@ +.../character.Arena_Template_NPC.sql | 2476 ++++++++++++++++++++ + src/server/game/Scripting/ScriptLoader.cpp | 4 +- + src/server/game/World/World.cpp | 21 +- + src/server/scripts/Custom/TemplateNPC.cpp | 1466 ++++++++++++ + src/server/scripts/Custom/TemplateNPC.h | 257 ++ + 5 files changed, 4221 insertions(+), 3 deletions(-) + create mode 100644 sql/TrinityCore-Patches/Arena_Template_NPC/character.Arena_Template_NPC.sql + create mode 100644 src/server/scripts/Custom/TemplateNPC.cpp + create mode 100644 src/server/scripts/Custom/TemplateNPC.h + +diff --git a/sql/TrinityCore-Patches/Arena_Template_NPC/character.Arena_Template_NPC.sql b/sql/TrinityCore-Patches/Arena_Template_NPC/character.Arena_Template_NPC.sql +new file mode 100644 +index 0000000..72c5ee0 +--- /dev/null ++++ b/sql/TrinityCore-Patches/Arena_Template_NPC/character.Arena_Template_NPC.sql +@@ -0,0 +1,2476 @@ ++-- -------------------------------------------------------- ++-- Host: 127.0.0.1 ++-- Server version: 5.6.26-log - MySQL Community Server (GPL) ++-- Server OS: Win64 ++-- HeidiSQL Version: 9.2.0.4981 ++-- -------------------------------------------------------- ++ ++/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; ++/*!40101 SET NAMES utf8mb4 */; ++/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; ++/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; ++ ++-- Dumping structure for table characters_dev.template_npc_alliance ++CREATE TABLE IF NOT EXISTS `template_npc_alliance` ( ++ `playerClass` varchar(50) NOT NULL, ++ `playerSpec` varchar(50) NOT NULL, ++ `pos` int(10) unsigned NOT NULL DEFAULT '0', ++ `itemEntry` int(10) unsigned NOT NULL DEFAULT '0', ++ `enchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket1` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket2` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket3` int(10) unsigned NOT NULL DEFAULT '0', ++ `bonusEnchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `prismaticEnchant` int(10) DEFAULT NULL ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Templates'; ++ ++-- Dumping data for table characters_dev.template_npc_alliance: ~502 rows (approximately) ++/*!40000 ALTER TABLE `template_npc_alliance` DISABLE KEYS */; ++INSERT INTO `template_npc_alliance` (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ++ ('Rogue', 'Assassination', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Assassination', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Assassination', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Assassination', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Assassination', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Combat', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Combat', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Combat', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Combat', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 15, 42276, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 16, 42281, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 0, 41678, 3795, 3621, 3525, 0, 3314, 0), ++ ('Druid', 'Feral', 1, 46374, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 2, 41715, 3793, 3525, 0, 0, 0, 0), ++ ('Druid', 'Feral', 4, 41661, 3832, 3525, 3525, 0, 0, 0), ++ ('Druid', 'Feral', 5, 41833, 0, 3543, 3525, 0, 2877, 3729), ++ ('Druid', 'Feral', 6, 41667, 3823, 3525, 3879, 0, 3355, 0), ++ ('Druid', 'Feral', 7, 41837, 1597, 3525, 0, 0, 0, 0), ++ ('Druid', 'Feral', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 9, 41773, 1603, 3543, 0, 0, 2874, 0), ++ ('Druid', 'Feral', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 11, 47934, 0, 3528, 0, 0, 2877, 0), ++ ('Druid', 'Feral', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 15, 45951, 3789, 3525, 3525, 0, 0, 0), ++ ('Druid', 'Feral', 17, 42589, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 0, 41321, 3796, 3639, 3520, 0, 3352, 0), ++ ('Druid', 'Restoration', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 2, 41275, 3794, 3531, 0, 0, 2890, 0), ++ ('Druid', 'Restoration', 4, 41310, 3245, 3520, 3531, 0, 3600, 0), ++ ('Druid', 'Restoration', 5, 41618, 0, 3535, 3520, 0, 2872, 3729), ++ ('Druid', 'Restoration', 6, 41298, 3721, 3520, 3879, 0, 3602, 0), ++ ('Druid', 'Restoration', 7, 41622, 3232, 3531, 0, 0, 2878, 0), ++ ('Druid', 'Restoration', 8, 41626, 2332, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 9, 41287, 3246, 3548, 0, 0, 2890, 0), ++ ('Druid', 'Restoration', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 11, 47928, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 14, 42080, 3243, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 15, 42385, 3854, 3520, 3548, 0, 3602, 0), ++ ('Druid', 'Restoration', 17, 42579, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Subtlety', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Subtlety', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Subtlety', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Subtlety', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 0, 41327, 3796, 3621, 3520, 0, 3352, 0), ++ ('Druid', 'Ballance', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 4, 41316, 3245, 3520, 3531, 0, 3600, 0), ++ ('Druid', 'Ballance', 2, 41281, 3794, 3531, 0, 0, 2890, 0), ++ ('Druid', 'Ballance', 5, 41631, 0, 3879, 3520, 0, 2872, 3729), ++ ('Druid', 'Ballance', 6, 41304, 3721, 3520, 3548, 0, 3602, 0), ++ ('Druid', 'Ballance', 7, 41636, 3232, 3531, 0, 0, 2878, 0), ++ ('Druid', 'Ballance', 8, 41641, 2332, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 9, 41293, 3246, 3548, 0, 0, 2890, 0), ++ ('Druid', 'Ballance', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Druid', 'Ballance', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 14, 42076, 3243, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 15, 42364, 3854, 3520, 3535, 0, 3602, 0), ++ ('Druid', 'Ballance', 17, 42584, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Holy', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Holy', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Holy', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Holy', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Holy', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Holy', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 0, 41915, 3796, 3621, 3563, 0, 3821, 0), ++ ('Priest', 'Shadow', 1, 42045, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 2, 41934, 3794, 3563, 0, 0, 2868, 0), ++ ('Priest', 'Shadow', 4, 41921, 3832, 3563, 3563, 0, 3307, 0), ++ ('Priest', 'Shadow', 5, 49179, 0, 3590, 3590, 0, 2872, 3729), ++ ('Priest', 'Shadow', 6, 41927, 3721, 3520, 3590, 0, 2770, 0), ++ ('Priest', 'Shadow', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Priest', 'Shadow', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 9, 41940, 3246, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 13, 42137, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 15, 42347, 3834, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Arcane', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Arcane', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Arcane', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Arcane', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Arcane', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Arcane', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Arcane', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Fire', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Fire', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Fire', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Fire', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Fire', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Fire', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Fire', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Retribution', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Retribution', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Retribution', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Retribution', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 15, 42323, 3789, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Protection', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Protection', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Protection', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Protection', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 15, 42286, 3789, 3863, 0, 0, 2936, 0), ++ ('Paladin', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 0, 41019, 3796, 3621, 3866, 0, 2854, 0), ++ ('Shaman', 'Elemental', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 2, 41044, 3794, 3531, 0, 0, 2865, 0), ++ ('Shaman', 'Elemental', 4, 40993, 3832, 3866, 3531, 0, 3600, 0), ++ ('Shaman', 'Elemental', 5, 41071, 0, 3535, 3535, 0, 3596, 3729), ++ ('Shaman', 'Elemental', 6, 41033, 3721, 3866, 3535, 0, 3602, 0), ++ ('Shaman', 'Elemental', 7, 41076, 3232, 3531, 0, 0, 2878, 0), ++ ('Shaman', 'Elemental', 8, 41066, 2332, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 9, 41007, 3246, 3535, 0, 0, 2865, 0), ++ ('Shaman', 'Elemental', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 14, 42078, 3831, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 15, 42347, 3834, 3535, 0, 0, 3752, 0), ++ ('Shaman', 'Elemental', 16, 42565, 1128, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 17, 42603, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 0, 41151, 3817, 3621, 3521, 0, 2843, 0), ++ ('Shaman', 'Enhancement', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 2, 41211, 3808, 3565, 0, 0, 2874, 0), ++ ('Shaman', 'Enhancement', 4, 41081, 3832, 3521, 3521, 0, 0, 0), ++ ('Shaman', 'Enhancement', 5, 41236, 0, 3521, 3521, 0, 0, 3729), ++ ('Shaman', 'Enhancement', 6, 41199, 3823, 3521, 3521, 0, 0, 0), ++ ('Shaman', 'Enhancement', 7, 41231, 1597, 3521, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 9, 41137, 1603, 3577, 0, 0, 2874, 0), ++ ('Shaman', 'Enhancement', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 15, 42276, 3789, 3577, 0, 0, 2936, 0), ++ ('Shaman', 'Enhancement', 16, 42276, 3789, 3521, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 17, 42608, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 0, 41013, 3796, 3627, 3866, 0, 2854, 0), ++ ('Shaman', 'Restoration', 1, 42045, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 2, 41038, 3794, 3530, 0, 0, 2865, 0), ++ ('Shaman', 'Restoration', 4, 40992, 3245, 3866, 3866, 0, 0, 0), ++ ('Shaman', 'Restoration', 5, 41052, 0, 3866, 3866, 0, 0, 3729), ++ ('Shaman', 'Restoration', 6, 41027, 3721, 3866, 3863, 0, 3602, 0), ++ ('Shaman', 'Restoration', 7, 41056, 3232, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 8, 41061, 2332, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 9, 41001, 3246, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 14, 42077, 3831, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 15, 42353, 3834, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 16, 42571, 1128, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 17, 42598, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Discipline', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Discipline', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Discipline', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Discipline', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Discipline', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 13, 47041, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Beastmastery', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Beastmastery', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Beastmastery', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Beastmastery', 10, 47934, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 16, 42228, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Survival', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Survival', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Survival', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Survival', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Survival', 10, 47934, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 16, 42228, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 0, 40933, 3796, 3627, 3520, 0, 2854, 0), ++ ('Paladin', 'Holy', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 2, 40963, 3794, 3865, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 4, 40907, 3832, 3520, 3865, 0, 3600, 0), ++ ('Paladin', 'Holy', 5, 40978, 0, 3546, 3866, 0, 3596, 3729), ++ ('Paladin', 'Holy', 6, 40939, 3721, 3520, 3546, 0, 3602, 0), ++ ('Paladin', 'Holy', 7, 40979, 3232, 3865, 0, 0, 2878, 0), ++ ('Paladin', 'Holy', 9, 40927, 3246, 3546, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 8, 40984, 2332, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 14, 42076, 3831, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 15, 42353, 3834, 3546, 0, 0, 3752, 0), ++ ('Paladin', 'Holy', 16, 42571, 1128, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 17, 42615, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Frost', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Frost', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Frost', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Frost', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Frost', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Frost', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Frost', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Affliction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Affliction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Affliction', 5, 41899, 0, 3535, 3528, 0, 2872, 3729), ++ ('Warlock', 'Affliction', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Affliction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Affliction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Affliction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Demonology', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Demonology', 5, 41899, 0, 3535, 3528, 0, 2872, 3729), ++ ('Warlock', 'Demonology', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Demonology', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Demonology', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Demonology', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Destruction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Destruction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Destruction', 5, 41899, 0, 3535, 3528, 0, 2872, 3729), ++ ('Warlock', 'Destruction', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Destruction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Destruction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Destruction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Blood', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Blood', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Blood', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Blood', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 15, 42333, 3366, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Blood', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Unholy', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Unholy', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Unholy', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 15, 42333, 3367, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Unholy', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Protection', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Protection', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Protection', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Protection', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 15, 42276, 3789, 3863, 0, 0, 2936, 0), ++ ('Warrior', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Arms', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Arms', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Arms', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Arms', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Fury', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Fury', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Fury', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Fury', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 16, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Marksmanship', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Marksmanship', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Marksmanship', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Marksmanship', 10, 47934, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 13, 51377, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 16, 42209, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Frost', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Frost', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Frost', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Frost', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 15, 42209, 3370, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 16, 42209, 3594, 3535, 0, 0, 2936, 0), ++ ('DeathKnight', 'Frost', 17, 42621, 0, 0, 0, 0, 0, 0); ++/*!40000 ALTER TABLE `template_npc_alliance` ENABLE KEYS */; ++ ++ ++-- Dumping structure for table characters_dev.template_npc_glyphs ++CREATE TABLE IF NOT EXISTS `template_npc_glyphs` ( ++ `playerClass` varchar(50) NOT NULL, ++ `playerSpec` varchar(50) NOT NULL, ++ `slot` tinyint(3) unsigned NOT NULL DEFAULT '0', ++ `glyph` smallint(5) unsigned NOT NULL DEFAULT '0' ++) ENGINE=InnoDB DEFAULT CHARSET=utf8; ++ ++-- Dumping data for table characters_dev.template_npc_glyphs: ~180 rows (approximately) ++/*!40000 ALTER TABLE `template_npc_glyphs` DISABLE KEYS */; ++INSERT INTO `template_npc_glyphs` (`playerClass`, `playerSpec`, `slot`, `glyph`) VALUES ++ ('Rogue', 'Assassination', 0, 733), ++ ('Rogue', 'Assassination', 1, 464), ++ ('Rogue', 'Assassination', 2, 467), ++ ('Rogue', 'Assassination', 3, 405), ++ ('Rogue', 'Assassination', 4, 469), ++ ('Rogue', 'Assassination', 5, 408), ++ ('Druid', 'Feral', 0, 166), ++ ('Druid', 'Feral', 1, 0), ++ ('Druid', 'Feral', 2, 0), ++ ('Druid', 'Feral', 3, 165), ++ ('Druid', 'Feral', 4, 551), ++ ('Druid', 'Feral', 5, 671), ++ ('Druid', 'Ballance', 0, 676), ++ ('Druid', 'Ballance', 1, 433), ++ ('Druid', 'Ballance', 2, 434), ++ ('Druid', 'Ballance', 3, 178), ++ ('Druid', 'Ballance', 4, 551), ++ ('Druid', 'Ballance', 5, 176), ++ ('Priest', 'Holy', 0, 251), ++ ('Priest', 'Holy', 1, 458), ++ ('Priest', 'Holy', 2, 459), ++ ('Priest', 'Holy', 3, 257), ++ ('Priest', 'Holy', 4, 463), ++ ('Priest', 'Holy', 5, 255), ++ ('Mage', 'Arcane', 0, 329), ++ ('Mage', 'Arcane', 2, 447), ++ ('Mage', 'Arcane', 1, 446), ++ ('Mage', 'Arcane', 3, 315), ++ ('Mage', 'Arcane', 4, 451), ++ ('Mage', 'Arcane', 5, 312), ++ ('Mage', 'Fire', 0, 329), ++ ('Mage', 'Fire', 1, 446), ++ ('Mage', 'Fire', 2, 447), ++ ('Mage', 'Fire', 3, 315), ++ ('Mage', 'Fire', 4, 451), ++ ('Mage', 'Fire', 5, 324), ++ ('Paladin', 'Retribution', 0, 183), ++ ('Paladin', 'Retribution', 1, 453), ++ ('Paladin', 'Retribution', 2, 455), ++ ('Paladin', 'Retribution', 3, 193), ++ ('Paladin', 'Retribution', 4, 456), ++ ('Paladin', 'Retribution', 5, 707), ++ ('Paladin', 'Protection', 0, 191), ++ ('Paladin', 'Protection', 1, 456), ++ ('Paladin', 'Protection', 2, 457), ++ ('Paladin', 'Protection', 3, 705), ++ ('Paladin', 'Protection', 4, 452), ++ ('Paladin', 'Protection', 5, 183), ++ ('Shaman', 'Elemental', 0, 754), ++ ('Shaman', 'Elemental', 2, 474), ++ ('Shaman', 'Elemental', 1, 476), ++ ('Shaman', 'Elemental', 3, 214), ++ ('Shaman', 'Elemental', 4, 552), ++ ('Shaman', 'Elemental', 5, 752), ++ ('Shaman', 'Enhancement', 0, 228), ++ ('Shaman', 'Enhancement', 1, 552), ++ ('Shaman', 'Enhancement', 2, 475), ++ ('Shaman', 'Enhancement', 3, 736), ++ ('Shaman', 'Enhancement', 4, 476), ++ ('Shaman', 'Enhancement', 5, 754), ++ ('Shaman', 'Restoration', 0, 223), ++ ('Shaman', 'Restoration', 1, 476), ++ ('Shaman', 'Restoration', 2, 552), ++ ('Shaman', 'Restoration', 3, 751), ++ ('Shaman', 'Restoration', 4, 475), ++ ('Shaman', 'Restoration', 5, 754), ++ ('Priest', 'Discipline', 0, 713), ++ ('Priest', 'Discipline', 1, 458), ++ ('Priest', 'Discipline', 2, 459), ++ ('Priest', 'Discipline', 3, 257), ++ ('Priest', 'Discipline', 4, 463), ++ ('Priest', 'Discipline', 5, 710), ++ ('Druid', 'Restoration', 0, 169), ++ ('Druid', 'Restoration', 1, 433), ++ ('Druid', 'Restoration', 2, 435), ++ ('Druid', 'Restoration', 3, 168), ++ ('Druid', 'Restoration', 4, 551), ++ ('Druid', 'Restoration', 5, 676), ++ ('Hunter', 'Beastmastery', 0, 351), ++ ('Hunter', 'Beastmastery', 1, 439), ++ ('Hunter', 'Beastmastery', 2, 440), ++ ('Hunter', 'Beastmastery', 3, 358), ++ ('Hunter', 'Beastmastery', 4, 441), ++ ('Hunter', 'Beastmastery', 5, 356), ++ ('Hunter', 'Survival', 0, 351), ++ ('Hunter', 'Survival', 1, 439), ++ ('Hunter', 'Survival', 2, 440), ++ ('Hunter', 'Survival', 3, 358), ++ ('Hunter', 'Survival', 4, 441), ++ ('Hunter', 'Survival', 5, 691), ++ ('Paladin', 'Holy', 0, 706), ++ ('Paladin', 'Holy', 1, 455), ++ ('Paladin', 'Holy', 2, 456), ++ ('Paladin', 'Holy', 3, 183), ++ ('Paladin', 'Holy', 4, 452), ++ ('Paladin', 'Holy', 5, 195), ++ ('Mage', 'Frost', 0, 700), ++ ('Mage', 'Frost', 1, 446), ++ ('Mage', 'Frost', 2, 447), ++ ('Mage', 'Frost', 3, 315), ++ ('Mage', 'Frost', 4, 451), ++ ('Mage', 'Frost', 5, 329), ++ ('Warlock', 'Destruction', 0, 759), ++ ('Warlock', 'Destruction', 1, 479), ++ ('Warlock', 'Destruction', 2, 480), ++ ('Warlock', 'Destruction', 3, 273), ++ ('Warlock', 'Destruction', 4, 478), ++ ('Warlock', 'Destruction', 5, 283), ++ ('Warlock', 'Affliction', 0, 759), ++ ('Warlock', 'Affliction', 1, 482), ++ ('Warlock', 'Affliction', 2, 477), ++ ('Warlock', 'Affliction', 3, 911), ++ ('Warlock', 'Affliction', 4, 478), ++ ('Warlock', 'Affliction', 5, 283), ++ ('Warlock', 'Demonology', 0, 759), ++ ('Warlock', 'Demonology', 1, 479), ++ ('Warlock', 'Demonology', 2, 480), ++ ('Warlock', 'Demonology', 3, 281), ++ ('Warlock', 'Demonology', 4, 477), ++ ('Warlock', 'Demonology', 5, 274), ++ ('DeathKnight', 'Unholy', 0, 772), ++ ('DeathKnight', 'Unholy', 1, 522), ++ ('DeathKnight', 'Unholy', 2, 553), ++ ('DeathKnight', 'Unholy', 3, 771), ++ ('DeathKnight', 'Unholy', 4, 518), ++ ('DeathKnight', 'Unholy', 5, 527), ++ ('Warrior', 'Arms', 0, 489), ++ ('Warrior', 'Arms', 1, 484), ++ ('Warrior', 'Arms', 2, 485), ++ ('Warrior', 'Arms', 3, 499), ++ ('Warrior', 'Arms', 4, 483), ++ ('Warrior', 'Arms', 5, 500), ++ ('Warrior', 'Protection', 0, 503), ++ ('Warrior', 'Protection', 1, 484), ++ ('Warrior', 'Protection', 2, 485), ++ ('Warrior', 'Protection', 3, 763), ++ ('Warrior', 'Protection', 4, 483), ++ ('Warrior', 'Protection', 5, 502), ++ ('Warrior', 'Fury', 0, 509), ++ ('Warrior', 'Fury', 1, 484), ++ ('Warrior', 'Fury', 2, 851), ++ ('Warrior', 'Fury', 3, 490), ++ ('Warrior', 'Fury', 4, 483), ++ ('Warrior', 'Fury', 5, 494), ++ ('Rogue', 'Subtlety', 0, 404), ++ ('Rogue', 'Subtlety', 1, 464), ++ ('Rogue', 'Subtlety', 2, 467), ++ ('Rogue', 'Subtlety', 3, 716), ++ ('Rogue', 'Subtlety', 4, 469), ++ ('Rogue', 'Subtlety', 5, 408), ++ ('Rogue', 'Combat', 0, 409), ++ ('Rogue', 'Combat', 1, 464), ++ ('Rogue', 'Combat', 2, 467), ++ ('Rogue', 'Combat', 3, 404), ++ ('Rogue', 'Combat', 4, 469), ++ ('Rogue', 'Combat', 5, 715), ++ ('Hunter', 'Marksmanship', 0, 351), ++ ('Hunter', 'Marksmanship', 1, 439), ++ ('Hunter', 'Marksmanship', 2, 440), ++ ('Hunter', 'Marksmanship', 3, 358), ++ ('Hunter', 'Marksmanship', 4, 441), ++ ('Hunter', 'Marksmanship', 5, 366), ++ ('DeathKnight', 'Blood', 0, 771), ++ ('DeathKnight', 'Blood', 2, 555), ++ ('DeathKnight', 'Blood', 1, 522), ++ ('DeathKnight', 'Blood', 3, 558), ++ ('DeathKnight', 'Blood', 4, 514), ++ ('DeathKnight', 'Blood', 5, 528), ++ ('DeathKnight', 'Frost', 0, 525), ++ ('DeathKnight', 'Frost', 1, 522), ++ ('DeathKnight', 'Frost', 2, 518), ++ ('DeathKnight', 'Frost', 3, 773), ++ ('DeathKnight', 'Frost', 4, 553), ++ ('DeathKnight', 'Frost', 5, 521), ++ ('Priest', 'Shadow', 0, 263), ++ ('Priest', 'Shadow', 1, 463), ++ ('Priest', 'Shadow', 2, 458), ++ ('Priest', 'Shadow', 3, 257), ++ ('Priest', 'Shadow', 4, 459), ++ ('Priest', 'Shadow', 5, 708); ++/*!40000 ALTER TABLE `template_npc_glyphs` ENABLE KEYS */; ++ ++ ++-- Dumping structure for table characters_dev.template_npc_horde ++CREATE TABLE IF NOT EXISTS `template_npc_horde` ( ++ `playerClass` varchar(50) NOT NULL, ++ `playerSpec` varchar(50) NOT NULL, ++ `pos` int(10) unsigned NOT NULL DEFAULT '0', ++ `itemEntry` int(10) unsigned NOT NULL DEFAULT '0', ++ `enchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket1` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket2` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket3` int(10) unsigned NOT NULL DEFAULT '0', ++ `bonusEnchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `prismaticEnchant` int(10) DEFAULT NULL ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Templates'; ++ ++-- Dumping data for table characters_dev.template_npc_horde: ~501 rows (approximately) ++/*!40000 ALTER TABLE `template_npc_horde` DISABLE KEYS */; ++INSERT INTO `template_npc_horde` (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ++ ('Rogue', 'Assassination', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Assassination', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Assassination', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Assassination', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Assassination', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Subtlety', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Subtlety', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Subtlety', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Subtlety', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Combat', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Combat', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Combat', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Combat', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 15, 42276, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 16, 42281, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 0, 41678, 3795, 3621, 3525, 0, 3314, 0), ++ ('Druid', 'Feral', 1, 46374, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 2, 41715, 3793, 3525, 0, 0, 0, 0), ++ ('Druid', 'Feral', 4, 41661, 3832, 3525, 3525, 0, 0, 0), ++ ('Druid', 'Feral', 5, 41833, 0, 3543, 3525, 0, 2877, 3729), ++ ('Druid', 'Feral', 6, 41667, 3823, 3525, 3879, 0, 3355, 0), ++ ('Druid', 'Feral', 7, 41837, 1597, 3525, 0, 0, 0, 0), ++ ('Druid', 'Feral', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 9, 41773, 1603, 3543, 0, 0, 2874, 0), ++ ('Druid', 'Feral', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 11, 48007, 0, 3528, 0, 0, 2877, 0), ++ ('Druid', 'Feral', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Druid', 'Feral', 15, 45951, 3789, 3525, 3525, 0, 0, 0), ++ ('Druid', 'Feral', 17, 42589, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 0, 41327, 3796, 3621, 3520, 0, 3352, 0), ++ ('Druid', 'Ballance', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 4, 41316, 3245, 3520, 3531, 0, 3600, 0), ++ ('Druid', 'Ballance', 2, 41281, 3794, 3531, 0, 0, 2890, 0), ++ ('Druid', 'Ballance', 5, 41631, 0, 3879, 3520, 0, 2872, 3729), ++ ('Druid', 'Ballance', 6, 41304, 3721, 3520, 3548, 0, 3602, 0), ++ ('Druid', 'Ballance', 7, 41636, 3232, 3531, 0, 0, 2878, 0), ++ ('Druid', 'Ballance', 8, 41641, 2332, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 9, 41293, 3246, 3548, 0, 0, 2890, 0), ++ ('Druid', 'Ballance', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 11, 48001, 0, 3520, 0, 0, 3752, 0), ++ ('Druid', 'Ballance', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 14, 42076, 3243, 0, 0, 0, 0, 0), ++ ('Druid', 'Ballance', 15, 42364, 3854, 3520, 3535, 0, 3602, 0), ++ ('Druid', 'Ballance', 17, 42584, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Holy', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Holy', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Holy', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Holy', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Holy', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Holy', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Arcane', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Arcane', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Arcane', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Arcane', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Arcane', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Arcane', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Arcane', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 11, 48001, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 13, 47316, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Fire', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Fire', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Fire', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Fire', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Fire', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Fire', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Fire', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 11, 48001, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 13, 47316, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Retribution', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Retribution', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Retribution', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Retribution', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 15, 42323, 3789, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Protection', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Protection', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Protection', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Protection', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 15, 42286, 3789, 3863, 0, 0, 2936, 0), ++ ('Paladin', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 0, 41019, 3796, 3621, 3866, 0, 2854, 0), ++ ('Shaman', 'Elemental', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 2, 41044, 3794, 3531, 0, 0, 2865, 0), ++ ('Shaman', 'Elemental', 4, 40993, 3832, 3866, 3531, 0, 3600, 0), ++ ('Shaman', 'Elemental', 5, 41071, 0, 3535, 3535, 0, 3596, 3729), ++ ('Shaman', 'Elemental', 6, 41033, 3721, 3866, 3535, 0, 3602, 0), ++ ('Shaman', 'Elemental', 7, 41076, 3232, 3531, 0, 0, 2878, 0), ++ ('Shaman', 'Elemental', 8, 41066, 2332, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 9, 41007, 3246, 3535, 0, 0, 2865, 0), ++ ('Shaman', 'Elemental', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 14, 42078, 3831, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 15, 42347, 3834, 3535, 0, 0, 3752, 0), ++ ('Shaman', 'Elemental', 16, 42565, 1128, 0, 0, 0, 0, 0), ++ ('Shaman', 'Elemental', 17, 42603, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 0, 41151, 3817, 3621, 3521, 0, 2843, 0), ++ ('Shaman', 'Enhancement', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 2, 41211, 3808, 3565, 0, 0, 2874, 0), ++ ('Shaman', 'Enhancement', 4, 41081, 3832, 3521, 3521, 0, 0, 0), ++ ('Shaman', 'Enhancement', 5, 41236, 0, 3521, 3521, 0, 0, 3729), ++ ('Shaman', 'Enhancement', 6, 41199, 3823, 3521, 3521, 0, 0, 0), ++ ('Shaman', 'Enhancement', 7, 41231, 1597, 3521, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 9, 41137, 1603, 3577, 0, 0, 2874, 0), ++ ('Shaman', 'Enhancement', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 15, 42276, 3789, 3577, 0, 0, 2936, 0), ++ ('Shaman', 'Enhancement', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 16, 42276, 3789, 3521, 0, 0, 0, 0), ++ ('Shaman', 'Enhancement', 17, 42608, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 0, 41013, 3796, 3627, 3866, 0, 2854, 0), ++ ('Shaman', 'Restoration', 1, 42045, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 2, 41038, 3794, 3530, 0, 0, 2865, 0), ++ ('Shaman', 'Restoration', 4, 40992, 3245, 3866, 3866, 0, 0, 0), ++ ('Shaman', 'Restoration', 5, 41052, 0, 3866, 3866, 0, 0, 3729), ++ ('Shaman', 'Restoration', 6, 41027, 3721, 3866, 3863, 0, 3602, 0), ++ ('Shaman', 'Restoration', 7, 41056, 3232, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 8, 41061, 2332, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 9, 41001, 3246, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 14, 42077, 3831, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 15, 42353, 3834, 3866, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 16, 42571, 1128, 0, 0, 0, 0, 0), ++ ('Shaman', 'Restoration', 17, 42598, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 0, 41854, 3796, 3639, 3520, 0, 3352, 0), ++ ('Priest', 'Discipline', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Discipline', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Discipline', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Discipline', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Discipline', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 13, 47271, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 2, 41275, 3794, 3531, 0, 0, 2890, 0), ++ ('Druid', 'Restoration', 4, 41310, 3245, 3520, 3531, 0, 3600, 0), ++ ('Druid', 'Restoration', 5, 41618, 0, 3535, 3520, 0, 2872, 3729), ++ ('Druid', 'Restoration', 6, 41298, 3721, 3520, 3879, 0, 3602, 0), ++ ('Druid', 'Restoration', 7, 41622, 3232, 3531, 0, 0, 2878, 0), ++ ('Druid', 'Restoration', 8, 41626, 2332, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 9, 41287, 3246, 3548, 0, 0, 2890, 0), ++ ('Druid', 'Restoration', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 11, 48001, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 14, 42080, 3243, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Druid', 'Restoration', 15, 42385, 3854, 3520, 3548, 0, 3602, 0), ++ ('Druid', 'Restoration', 17, 42579, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Marksmanship', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Marksmanship', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Marksmanship', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Marksmanship', 10, 48007, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 16, 42228, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Marksmanship', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Beastmastery', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Beastmastery', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Beastmastery', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Beastmastery', 10, 48007, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 16, 42228, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Beastmastery', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 0, 41157, 3795, 3621, 3519, 0, 2843, 0), ++ ('Hunter', 'Survival', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 2, 41217, 3793, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 4, 41087, 3245, 3519, 3519, 0, 0, 0), ++ ('Hunter', 'Survival', 5, 41236, 0, 3535, 3519, 0, 2877, 3729), ++ ('Hunter', 'Survival', 6, 41205, 3823, 3519, 3537, 0, 3355, 0), ++ ('Hunter', 'Survival', 7, 41231, 3232, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 8, 41226, 3845, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 9, 41143, 1603, 3879, 0, 0, 2874, 0), ++ ('Hunter', 'Survival', 10, 48007, 0, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 15, 42209, 3833, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 16, 42228, 3731, 3519, 0, 0, 0, 0), ++ ('Hunter', 'Survival', 17, 42486, 3608, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 0, 40933, 3796, 3627, 3520, 0, 2854, 0), ++ ('Paladin', 'Holy', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 2, 40963, 3794, 3865, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 4, 40907, 3832, 3520, 3865, 0, 3600, 0), ++ ('Paladin', 'Holy', 5, 40978, 0, 3546, 3866, 0, 3596, 3729), ++ ('Paladin', 'Holy', 6, 40939, 3721, 3520, 3546, 0, 3602, 0), ++ ('Paladin', 'Holy', 7, 40979, 3232, 3865, 0, 0, 2878, 0), ++ ('Paladin', 'Holy', 8, 40984, 2332, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 9, 40927, 3246, 3546, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 14, 42076, 3831, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 15, 42353, 3834, 3546, 0, 0, 3752, 0), ++ ('Paladin', 'Holy', 16, 42571, 1128, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 17, 42615, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Destruction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Destruction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Destruction', 5, 41899, 0, 3535, 3531, 0, 2872, 3729), ++ ('Warlock', 'Destruction', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Destruction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Destruction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Destruction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 15, 42347, 0, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Demonology', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Demonology', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Demonology', 5, 41899, 0, 3535, 3528, 0, 2872, 3729), ++ ('Warlock', 'Demonology', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Demonology', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Demonology', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Demonology', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Affliction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Affliction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Affliction', 5, 41899, 0, 3535, 3528, 0, 2872, 3729), ++ ('Warlock', 'Affliction', 6, 42005, 3721, 3520, 3528, 0, 0, 0), ++ ('Warlock', 'Affliction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Affliction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Affliction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Frost', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Frost', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Frost', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Frost', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Frost', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Frost', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Frost', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 11, 48001, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 13, 47316, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Blood', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Blood', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Blood', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 15, 42333, 3366, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Blood', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Frost', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Frost', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Frost', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Frost', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 15, 42209, 3370, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 16, 42209, 3594, 3535, 0, 0, 2936, 0), ++ ('DeathKnight', 'Frost', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Unholy', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Unholy', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Unholy', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 12, 51378, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 15, 42333, 3367, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Unholy', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Protection', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Protection', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Protection', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Protection', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 15, 42276, 3789, 3863, 0, 0, 2936, 0), ++ ('Warrior', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Fury', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Fury', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Fury', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Fury', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 16, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Arms', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Arms', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Arms', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Arms', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 13, 51378, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Shadow', 1, 42045, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 2, 41934, 3794, 3563, 0, 0, 2868, 0), ++ ('Priest', 'Shadow', 4, 41921, 3832, 3563, 3563, 0, 3307, 0), ++ ('Priest', 'Shadow', 5, 49179, 0, 3590, 3590, 0, 2872, 3729), ++ ('Priest', 'Shadow', 6, 41927, 3721, 3520, 3590, 0, 2770, 0), ++ ('Priest', 'Shadow', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Priest', 'Shadow', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 9, 41940, 3246, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 12, 45518, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 13, 42137, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 15, 42347, 3834, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 17, 42520, 0, 0, 0, 0, 0, 0); ++/*!40000 ALTER TABLE `template_npc_horde` ENABLE KEYS */; ++ ++ ++-- Dumping structure for table characters_dev.template_npc_human ++CREATE TABLE IF NOT EXISTS `template_npc_human` ( ++ `playerClass` varchar(50) NOT NULL, ++ `playerSpec` varchar(50) NOT NULL, ++ `pos` int(10) unsigned NOT NULL DEFAULT '0', ++ `itemEntry` int(10) unsigned NOT NULL DEFAULT '0', ++ `enchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket1` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket2` int(10) unsigned NOT NULL DEFAULT '0', ++ `socket3` int(10) unsigned NOT NULL DEFAULT '0', ++ `bonusEnchant` int(10) unsigned NOT NULL DEFAULT '0', ++ `prismaticEnchant` int(10) DEFAULT NULL ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Templates'; ++ ++-- Dumping data for table characters_dev.template_npc_human: ~354 rows (approximately) ++/*!40000 ALTER TABLE `template_npc_human` DISABLE KEYS */; ++INSERT INTO `template_npc_human` (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ++ ('Rogue', 'Assassination', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Assassination', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Assassination', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Assassination', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Assassination', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 13, 46038, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Assassination', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Discipline', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Discipline', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Discipline', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Discipline', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Discipline', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Discipline', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 13, 47041, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Discipline', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Holy', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 2, 41869, 3794, 3531, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 4, 41859, 3245, 3520, 3531, 0, 3600, 0), ++ ('Priest', 'Holy', 5, 49179, 0, 3548, 3531, 0, 2872, 3729), ++ ('Priest', 'Holy', 6, 41864, 3721, 3520, 3879, 0, 2770, 0), ++ ('Priest', 'Holy', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Priest', 'Holy', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 9, 41874, 3246, 3548, 0, 0, 2890, 0), ++ ('Priest', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 12, 42135, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 13, 47041, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 14, 42082, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 15, 42347, 3834, 3531, 0, 0, 0, 0), ++ ('Priest', 'Holy', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Holy', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Arcane', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Arcane', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Arcane', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Arcane', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Arcane', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Arcane', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Arcane', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Arcane', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Arcane', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Fire', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Fire', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Fire', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Fire', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Fire', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Fire', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Fire', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Fire', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Fire', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Retribution', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Retribution', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Retribution', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Retribution', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 13, 46038, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Retribution', 15, 42323, 3789, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Retribution', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 0, 40828, 3795, 3628, 3518, 0, 2787, 0), ++ ('Paladin', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 2, 40869, 3793, 3552, 0, 0, 3263, 0), ++ ('Paladin', 'Protection', 4, 40788, 3832, 3518, 3518, 0, 0, 0), ++ ('Paladin', 'Protection', 5, 40883, 0, 3518, 3518, 0, 0, 3729), ++ ('Paladin', 'Protection', 6, 40849, 3823, 3518, 3536, 0, 3357, 0), ++ ('Paladin', 'Protection', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 9, 40808, 1603, 3518, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 13, 46038, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 15, 42286, 3789, 3863, 0, 0, 2936, 0), ++ ('Paladin', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Paladin', 'Protection', 17, 42853, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 0, 40933, 3796, 3627, 3520, 0, 2854, 0), ++ ('Paladin', 'Holy', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 2, 40963, 3794, 3865, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 4, 40907, 3832, 3520, 3865, 0, 3600, 0), ++ ('Paladin', 'Holy', 5, 40978, 0, 3546, 3866, 0, 3596, 3729), ++ ('Paladin', 'Holy', 7, 40979, 3232, 3865, 0, 0, 2878, 0), ++ ('Paladin', 'Holy', 6, 40939, 3721, 3520, 3546, 0, 3602, 0), ++ ('Paladin', 'Holy', 8, 40984, 2332, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 9, 40927, 3246, 3546, 0, 0, 2865, 0), ++ ('Paladin', 'Holy', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 13, 47041, 0, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 14, 42076, 3831, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 15, 42353, 3834, 3546, 0, 0, 3752, 0), ++ ('Paladin', 'Holy', 16, 42571, 1128, 0, 0, 0, 0, 0), ++ ('Paladin', 'Holy', 17, 42615, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 0, 41946, 3820, 3621, 3520, 0, 3821, 0), ++ ('Mage', 'Frost', 1, 42044, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 2, 41965, 3794, 3531, 0, 0, 2868, 0), ++ ('Mage', 'Frost', 4, 41953, 3245, 3520, 3531, 0, 3307, 0), ++ ('Mage', 'Frost', 5, 49179, 0, 3548, 3520, 0, 2872, 3729), ++ ('Mage', 'Frost', 6, 41959, 3721, 3520, 3548, 0, 2770, 0), ++ ('Mage', 'Frost', 7, 49183, 3232, 3531, 0, 0, 2878, 0), ++ ('Mage', 'Frost', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 9, 41971, 3246, 3879, 0, 0, 3198, 0), ++ ('Mage', 'Frost', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 11, 47928, 0, 3520, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 12, 42137, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 13, 47182, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 15, 42347, 3834, 3548, 0, 0, 3752, 0), ++ ('Mage', 'Frost', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Mage', 'Frost', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Frost', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Frost', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Frost', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Frost', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 12, 46038, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 15, 42209, 3370, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Frost', 16, 42209, 3594, 3535, 0, 0, 2936, 0), ++ ('DeathKnight', 'Frost', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Unholy', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Unholy', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Unholy', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 12, 46038, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Unholy', 15, 42333, 3367, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Unholy', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Destruction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Destruction', 5, 41899, 0, 3535, 3531, 0, 2872, 3729), ++ ('Warlock', 'Destruction', 6, 42005, 3721, 3520, 3531, 0, 0, 0), ++ ('Warlock', 'Destruction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Destruction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Destruction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 12, 47182, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 15, 42347, 0, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Destruction', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 0, 41993, 3796, 3621, 3525, 0, 3821, 0), ++ ('Warlock', 'Affliction', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Affliction', 3, 6097, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Affliction', 5, 41899, 0, 3535, 3531, 0, 2872, 3729), ++ ('Warlock', 'Affliction', 6, 42005, 3721, 3520, 3531, 0, 0, 0), ++ ('Warlock', 'Affliction', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Affliction', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Affliction', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 12, 47182, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 17, 42520, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Affliction', 18, 38311, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 0, 41993, 3796, 3621, 3548, 0, 3821, 0), ++ ('Warlock', 'Demonology', 1, 42043, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 4, 41998, 3245, 3520, 3528, 0, 3307, 0), ++ ('Warlock', 'Demonology', 2, 42011, 3794, 3528, 0, 0, 2868, 0), ++ ('Warlock', 'Demonology', 5, 41899, 0, 3535, 3531, 0, 2872, 3729), ++ ('Warlock', 'Demonology', 6, 42005, 3721, 3520, 3531, 0, 0, 0), ++ ('Warlock', 'Demonology', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Warlock', 'Demonology', 8, 41910, 2332, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 9, 42017, 3246, 3535, 0, 0, 2872, 0), ++ ('Warlock', 'Demonology', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 11, 42116, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 12, 51377, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 13, 42132, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 14, 42078, 3243, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 15, 42347, 3834, 3866, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Warlock', 'Demonology', 17, 42503, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Fury', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Fury', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Fury', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Fury', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 13, 50198, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Fury', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 16, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Fury', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Arms', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Arms', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Arms', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Arms', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 13, 50198, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Arms', 15, 42323, 3789, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Arms', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 0, 40826, 3795, 3625, 3525, 0, 2787, 0), ++ ('Warrior', 'Protection', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 2, 40866, 3793, 3550, 0, 0, 3263, 0), ++ ('Warrior', 'Protection', 4, 40789, 3832, 3525, 3550, 0, 3600, 0), ++ ('Warrior', 'Protection', 5, 40883, 0, 3525, 3525, 0, 0, 3729), ++ ('Warrior', 'Protection', 6, 40847, 3823, 3525, 3525, 0, 0, 0), ++ ('Warrior', 'Protection', 7, 40884, 1597, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 9, 40807, 1603, 3525, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 10, 42119, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 11, 42117, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 12, 42136, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 13, 50198, 0, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 15, 42276, 3789, 3863, 0, 0, 2936, 0), ++ ('Warrior', 'Protection', 16, 42560, 3849, 0, 0, 0, 0, 0), ++ ('Warrior', 'Protection', 17, 42486, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Subtlety', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Subtlety', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Subtlety', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Subtlety', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 12, 46038, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 15, 45958, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 16, 45962, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Subtlety', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 0, 41672, 3795, 3628, 3521, 0, 3314, 0), ++ ('Rogue', 'Combat', 1, 42042, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 2, 41683, 3793, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 4, 41650, 3245, 3521, 3566, 0, 3600, 0), ++ ('Rogue', 'Combat', 5, 41833, 0, 3521, 3521, 0, 0, 3729), ++ ('Rogue', 'Combat', 6, 41655, 3823, 3521, 3879, 0, 3355, 0), ++ ('Rogue', 'Combat', 7, 41837, 1597, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 8, 41841, 3845, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 9, 41767, 1603, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 12, 46038, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 13, 42136, 0, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 14, 42082, 1099, 0, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 15, 42276, 3789, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 16, 42281, 3731, 3521, 0, 0, 0, 0), ++ ('Rogue', 'Combat', 17, 42451, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 0, 40827, 3795, 3621, 3518, 0, 2787, 0), ++ ('DeathKnight', 'Blood', 1, 42041, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 2, 40868, 3852, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 4, 40787, 3832, 3518, 3518, 0, 0, 0), ++ ('DeathKnight', 'Blood', 5, 40883, 0, 3518, 3879, 0, 0, 3729), ++ ('DeathKnight', 'Blood', 6, 40848, 3823, 3518, 3535, 0, 3357, 0), ++ ('DeathKnight', 'Blood', 7, 40884, 1597, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 8, 40890, 3845, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 9, 40809, 1603, 3518, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 10, 42117, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 11, 42119, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 12, 46038, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 13, 42134, 0, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 14, 42081, 3243, 0, 0, 0, 0, 0), ++ ('DeathKnight', 'Blood', 15, 42333, 3366, 3518, 3535, 0, 3764, 0), ++ ('DeathKnight', 'Blood', 17, 42621, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 0, 41854, 3796, 3621, 3520, 0, 3352, 0), ++ ('Priest', 'Shadow', 1, 42045, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 2, 41934, 3794, 3563, 0, 0, 2868, 0), ++ ('Priest', 'Shadow', 4, 41921, 3832, 3563, 3563, 0, 3307, 0), ++ ('Priest', 'Shadow', 5, 49179, 0, 3590, 3590, 0, 2872, 3729), ++ ('Priest', 'Shadow', 6, 41927, 3721, 3520, 3590, 0, 2770, 0), ++ ('Priest', 'Shadow', 7, 49183, 3232, 3563, 0, 0, 2878, 0), ++ ('Priest', 'Shadow', 8, 49181, 2332, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 9, 41940, 3246, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 10, 42118, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 11, 47732, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 12, 45518, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 13, 42137, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 14, 42077, 3243, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 15, 42347, 3834, 3590, 0, 0, 3752, 0), ++ ('Priest', 'Shadow', 16, 42526, 0, 0, 0, 0, 0, 0), ++ ('Priest', 'Shadow', 17, 42520, 0, 0, 0, 0, 0, 0); ++/*!40000 ALTER TABLE `template_npc_human` ENABLE KEYS */; ++ ++ ++-- Dumping structure for table characters_dev.template_npc_talents ++CREATE TABLE IF NOT EXISTS `template_npc_talents` ( ++ `playerClass` varchar(50) NOT NULL, ++ `playerSpec` varchar(50) NOT NULL, ++ `talentId` int(10) unsigned NOT NULL DEFAULT '0' ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Templates'; ++ ++-- Dumping data for table characters_dev.template_npc_talents: ~839 rows (approximately) ++/*!40000 ALTER TABLE `template_npc_talents` DISABLE KEYS */; ++INSERT INTO `template_npc_talents` (`playerClass`, `playerSpec`, `talentId`) VALUES ++ ('Rogue', 'Assassination', 1329), ++ ('Rogue', 'Assassination', 13848), ++ ('Rogue', 'Assassination', 13866), ++ ('Rogue', 'Assassination', 13971), ++ ('Rogue', 'Assassination', 13980), ++ ('Rogue', 'Assassination', 14066), ++ ('Rogue', 'Assassination', 14072), ++ ('Rogue', 'Assassination', 14083), ++ ('Rogue', 'Assassination', 14094), ++ ('Rogue', 'Assassination', 14116), ++ ('Rogue', 'Assassination', 14135), ++ ('Rogue', 'Assassination', 14142), ++ ('Rogue', 'Assassination', 14159), ++ ('Rogue', 'Assassination', 14161), ++ ('Rogue', 'Assassination', 14173), ++ ('Rogue', 'Assassination', 14177), ++ ('Rogue', 'Assassination', 14185), ++ ('Rogue', 'Assassination', 14195), ++ ('Rogue', 'Assassination', 14983), ++ ('Rogue', 'Assassination', 16515), ++ ('Rogue', 'Assassination', 30895), ++ ('Rogue', 'Assassination', 31209), ++ ('Rogue', 'Assassination', 31245), ++ ('Rogue', 'Assassination', 51626), ++ ('Rogue', 'Assassination', 51636), ++ ('Rogue', 'Assassination', 58410), ++ ('Rogue', 'Assassination', 58425), ++ ('Rogue', 'Assassination', 58426), ++ ('Druid', 'Feral', 16835), ++ ('Druid', 'Feral', 16864), ++ ('Druid', 'Feral', 16931), ++ ('Druid', 'Feral', 16938), ++ ('Druid', 'Feral', 16941), ++ ('Druid', 'Feral', 16944), ++ ('Druid', 'Feral', 16949), ++ ('Druid', 'Feral', 16968), ++ ('Druid', 'Feral', 16975), ++ ('Druid', 'Feral', 16999), ++ ('Druid', 'Feral', 17007), ++ ('Druid', 'Feral', 17061), ++ ('Druid', 'Feral', 17071), ++ ('Druid', 'Feral', 24866), ++ ('Druid', 'Feral', 24894), ++ ('Druid', 'Feral', 33851), ++ ('Druid', 'Feral', 33856), ++ ('Druid', 'Feral', 33873), ++ ('Druid', 'Feral', 33867), ++ ('Druid', 'Feral', 33917), ++ ('Druid', 'Feral', 37116), ++ ('Druid', 'Feral', 48412), ++ ('Druid', 'Feral', 48485), ++ ('Druid', 'Feral', 48495), ++ ('Druid', 'Feral', 49377), ++ ('Druid', 'Feral', 50334), ++ ('Druid', 'Feral', 51269), ++ ('Druid', 'Feral', 61336), ++ ('Druid', 'Feral', 63503), ++ ('Druid', 'Ballance', 5570), ++ ('Druid', 'Ballance', 16818), ++ ('Druid', 'Ballance', 16820), ++ ('Druid', 'Ballance', 16835), ++ ('Druid', 'Ballance', 16840), ++ ('Druid', 'Ballance', 16847), ++ ('Druid', 'Ballance', 16864), ++ ('Druid', 'Ballance', 16899), ++ ('Druid', 'Ballance', 16913), ++ ('Druid', 'Ballance', 17051), ++ ('Druid', 'Ballance', 17066), ++ ('Druid', 'Ballance', 17119), ++ ('Druid', 'Ballance', 24858), ++ ('Druid', 'Ballance', 33591), ++ ('Druid', 'Ballance', 33596), ++ ('Druid', 'Ballance', 33606), ++ ('Druid', 'Ballance', 33956), ++ ('Druid', 'Ballance', 33831), ++ ('Druid', 'Ballance', 35364), ++ ('Druid', 'Ballance', 48393), ++ ('Druid', 'Ballance', 48396), ++ ('Druid', 'Ballance', 48412), ++ ('Druid', 'Ballance', 48505), ++ ('Druid', 'Ballance', 48511), ++ ('Druid', 'Ballance', 48514), ++ ('Druid', 'Ballance', 48525), ++ ('Druid', 'Ballance', 50516), ++ ('Druid', 'Ballance', 57865), ++ ('Druid', 'Ballance', 61346), ++ ('Priest', 'Holy', 14767), ++ ('Priest', 'Holy', 14771), ++ ('Priest', 'Holy', 14777), ++ ('Priest', 'Holy', 14791), ++ ('Priest', 'Holy', 15011), ++ ('Priest', 'Holy', 15014), ++ ('Priest', 'Holy', 15018), ++ ('Priest', 'Holy', 15031), ++ ('Priest', 'Holy', 15356), ++ ('Priest', 'Holy', 15362), ++ ('Priest', 'Holy', 17191), ++ ('Priest', 'Holy', 19236), ++ ('Priest', 'Holy', 20711), ++ ('Priest', 'Holy', 27790), ++ ('Priest', 'Holy', 27904), ++ ('Priest', 'Holy', 33142), ++ ('Priest', 'Holy', 33154), ++ ('Priest', 'Holy', 33162), ++ ('Priest', 'Holy', 34860), ++ ('Priest', 'Holy', 34861), ++ ('Priest', 'Holy', 47567), ++ ('Priest', 'Holy', 47788), ++ ('Priest', 'Holy', 63543), ++ ('Priest', 'Holy', 63737), ++ ('Mage', 'Arcane', 12042), ++ ('Mage', 'Arcane', 12043), ++ ('Mage', 'Arcane', 12469), ++ ('Mage', 'Arcane', 12472), ++ ('Mage', 'Arcane', 12496), ++ ('Mage', 'Arcane', 12503), ++ ('Mage', 'Arcane', 12577), ++ ('Mage', 'Arcane', 12592), ++ ('Mage', 'Arcane', 12598), ++ ('Mage', 'Arcane', 12672), ++ ('Mage', 'Arcane', 12840), ++ ('Mage', 'Arcane', 15060), ++ ('Mage', 'Arcane', 18459), ++ ('Mage', 'Arcane', 18464), ++ ('Mage', 'Arcane', 29440), ++ ('Mage', 'Arcane', 31572), ++ ('Mage', 'Arcane', 31583), ++ ('Mage', 'Arcane', 31588), ++ ('Mage', 'Arcane', 31589), ++ ('Mage', 'Arcane', 35581), ++ ('Mage', 'Arcane', 44379), ++ ('Mage', 'Arcane', 44399), ++ ('Mage', 'Arcane', 44403), ++ ('Mage', 'Arcane', 44425), ++ ('Mage', 'Arcane', 54490), ++ ('Mage', 'Arcane', 54646), ++ ('Mage', 'Arcane', 55094), ++ ('Mage', 'Arcane', 55340), ++ ('Mage', 'Fire', 11080), ++ ('Mage', 'Fire', 11113), ++ ('Mage', 'Fire', 11129), ++ ('Mage', 'Fire', 11247), ++ ('Mage', 'Fire', 11255), ++ ('Mage', 'Fire', 11366), ++ ('Mage', 'Fire', 11368), ++ ('Mage', 'Fire', 12350), ++ ('Mage', 'Fire', 12353), ++ ('Mage', 'Fire', 12358), ++ ('Mage', 'Fire', 12399), ++ ('Mage', 'Fire', 12469), ++ ('Mage', 'Fire', 12592), ++ ('Mage', 'Fire', 12840), ++ ('Mage', 'Fire', 12848), ++ ('Mage', 'Fire', 12873), ++ ('Mage', 'Fire', 13043), ++ ('Mage', 'Fire', 29444), ++ ('Mage', 'Fire', 31642), ++ ('Mage', 'Fire', 31658), ++ ('Mage', 'Fire', 31661), ++ ('Mage', 'Fire', 31680), ++ ('Mage', 'Fire', 34296), ++ ('Mage', 'Fire', 44448), ++ ('Mage', 'Fire', 44457), ++ ('Mage', 'Fire', 44472), ++ ('Mage', 'Fire', 54646), ++ ('Mage', 'Fire', 54659), ++ ('Mage', 'Fire', 54734), ++ ('Mage', 'Fire', 64357), ++ ('Paladin', 'Retribution', 20045), ++ ('Paladin', 'Retribution', 20057), ++ ('Paladin', 'Retribution', 20066), ++ ('Paladin', 'Retribution', 20105), ++ ('Paladin', 'Retribution', 20113), ++ ('Paladin', 'Retribution', 20121), ++ ('Paladin', 'Retribution', 20175), ++ ('Paladin', 'Retribution', 20266), ++ ('Paladin', 'Retribution', 20332), ++ ('Paladin', 'Retribution', 20337), ++ ('Paladin', 'Retribution', 20375), ++ ('Paladin', 'Retribution', 25836), ++ ('Paladin', 'Retribution', 25957), ++ ('Paladin', 'Retribution', 26023), ++ ('Paladin', 'Retribution', 31868), ++ ('Paladin', 'Retribution', 31869), ++ ('Paladin', 'Retribution', 31872), ++ ('Paladin', 'Retribution', 31878), ++ ('Paladin', 'Retribution', 31881), ++ ('Paladin', 'Retribution', 35395), ++ ('Paladin', 'Retribution', 35397), ++ ('Paladin', 'Retribution', 53376), ++ ('Paladin', 'Retribution', 53382), ++ ('Paladin', 'Retribution', 53385), ++ ('Paladin', 'Retribution', 53488), ++ ('Paladin', 'Retribution', 53503), ++ ('Paladin', 'Retribution', 53519), ++ ('Paladin', 'Protection', 20105), ++ ('Paladin', 'Protection', 20121), ++ ('Paladin', 'Protection', 20135), ++ ('Paladin', 'Protection', 20140), ++ ('Paladin', 'Protection', 20175), ++ ('Paladin', 'Protection', 20179), ++ ('Paladin', 'Protection', 20198), ++ ('Paladin', 'Protection', 20266), ++ ('Paladin', 'Protection', 20337), ++ ('Paladin', 'Protection', 20470), ++ ('Paladin', 'Protection', 20488), ++ ('Paladin', 'Protection', 20911), ++ ('Paladin', 'Protection', 20925), ++ ('Paladin', 'Protection', 25957), ++ ('Paladin', 'Protection', 31849), ++ ('Paladin', 'Protection', 31852), ++ ('Paladin', 'Protection', 31860), ++ ('Paladin', 'Protection', 31867), ++ ('Paladin', 'Protection', 31935), ++ ('Paladin', 'Protection', 33776), ++ ('Paladin', 'Protection', 53519), ++ ('Paladin', 'Protection', 53530), ++ ('Paladin', 'Protection', 53592), ++ ('Paladin', 'Protection', 53585), ++ ('Paladin', 'Protection', 53595), ++ ('Paladin', 'Protection', 53696), ++ ('Paladin', 'Protection', 53711), ++ ('Paladin', 'Protection', 63646), ++ ('Paladin', 'Protection', 64205), ++ ('Shaman', 'Elemental', 16041), ++ ('Shaman', 'Elemental', 16108), ++ ('Shaman', 'Elemental', 16130), ++ ('Shaman', 'Elemental', 16161), ++ ('Shaman', 'Elemental', 16164), ++ ('Shaman', 'Elemental', 16166), ++ ('Shaman', 'Elemental', 16287), ++ ('Shaman', 'Elemental', 16305), ++ ('Shaman', 'Elemental', 16582), ++ ('Shaman', 'Elemental', 17487), ++ ('Shaman', 'Elemental', 28998), ++ ('Shaman', 'Elemental', 29000), ++ ('Shaman', 'Elemental', 29065), ++ ('Shaman', 'Elemental', 29080), ++ ('Shaman', 'Elemental', 30666), ++ ('Shaman', 'Elemental', 30679), ++ ('Shaman', 'Elemental', 30706), ++ ('Shaman', 'Elemental', 43338), ++ ('Shaman', 'Elemental', 51470), ++ ('Shaman', 'Elemental', 51479), ++ ('Shaman', 'Elemental', 51482), ++ ('Shaman', 'Elemental', 51486), ++ ('Shaman', 'Elemental', 51490), ++ ('Shaman', 'Elemental', 60188), ++ ('Shaman', 'Elemental', 62101), ++ ('Shaman', 'Elemental', 63372), ++ ('Shaman', 'Enhancement', 16112), ++ ('Shaman', 'Enhancement', 16130), ++ ('Shaman', 'Enhancement', 16164), ++ ('Shaman', 'Enhancement', 16268), ++ ('Shaman', 'Enhancement', 16284), ++ ('Shaman', 'Enhancement', 16272), ++ ('Shaman', 'Enhancement', 16287), ++ ('Shaman', 'Enhancement', 16305), ++ ('Shaman', 'Enhancement', 16306), ++ ('Shaman', 'Enhancement', 17364), ++ ('Shaman', 'Enhancement', 17489), ++ ('Shaman', 'Enhancement', 28998), ++ ('Shaman', 'Enhancement', 29080), ++ ('Shaman', 'Enhancement', 29086), ++ ('Shaman', 'Enhancement', 29179), ++ ('Shaman', 'Enhancement', 30798), ++ ('Shaman', 'Enhancement', 30809), ++ ('Shaman', 'Enhancement', 30814), ++ ('Shaman', 'Enhancement', 30823), ++ ('Shaman', 'Enhancement', 43338), ++ ('Shaman', 'Enhancement', 51522), ++ ('Shaman', 'Enhancement', 51524), ++ ('Shaman', 'Enhancement', 51532), ++ ('Shaman', 'Enhancement', 51533), ++ ('Shaman', 'Enhancement', 51881), ++ ('Shaman', 'Enhancement', 51885), ++ ('Shaman', 'Enhancement', 60103), ++ ('Shaman', 'Enhancement', 63374), ++ ('Shaman', 'Restoration', 974), ++ ('Shaman', 'Restoration', 16130), ++ ('Shaman', 'Restoration', 16188), ++ ('Shaman', 'Restoration', 16190), ++ ('Shaman', 'Restoration', 16198), ++ ('Shaman', 'Restoration', 16206), ++ ('Shaman', 'Restoration', 16213), ++ ('Shaman', 'Restoration', 16214), ++ ('Shaman', 'Restoration', 16229), ++ ('Shaman', 'Restoration', 16232), ++ ('Shaman', 'Restoration', 16240), ++ ('Shaman', 'Restoration', 16287), ++ ('Shaman', 'Restoration', 16293), ++ ('Shaman', 'Restoration', 17487), ++ ('Shaman', 'Restoration', 29191), ++ ('Shaman', 'Restoration', 29202), ++ ('Shaman', 'Restoration', 30866), ++ ('Shaman', 'Restoration', 30869), ++ ('Shaman', 'Restoration', 30886), ++ ('Shaman', 'Restoration', 51555), ++ ('Shaman', 'Restoration', 51558), ++ ('Shaman', 'Restoration', 51561), ++ ('Shaman', 'Restoration', 51566), ++ ('Shaman', 'Restoration', 51881), ++ ('Shaman', 'Restoration', 51886), ++ ('Shaman', 'Restoration', 55198), ++ ('Shaman', 'Restoration', 61295), ++ ('Priest', 'Discipline', 10060), ++ ('Priest', 'Discipline', 14751), ++ ('Priest', 'Discipline', 14767), ++ ('Priest', 'Discipline', 14769), ++ ('Priest', 'Discipline', 14771), ++ ('Priest', 'Discipline', 14777), ++ ('Priest', 'Discipline', 14781), ++ ('Priest', 'Discipline', 14791), ++ ('Priest', 'Discipline', 15009), ++ ('Priest', 'Discipline', 15012), ++ ('Priest', 'Discipline', 15363), ++ ('Priest', 'Discipline', 18555), ++ ('Priest', 'Discipline', 19236), ++ ('Priest', 'Discipline', 27815), ++ ('Priest', 'Discipline', 27904), ++ ('Priest', 'Discipline', 33190), ++ ('Priest', 'Discipline', 33202), ++ ('Priest', 'Discipline', 33206), ++ ('Priest', 'Discipline', 34910), ++ ('Priest', 'Discipline', 45244), ++ ('Priest', 'Discipline', 47508), ++ ('Priest', 'Discipline', 47515), ++ ('Priest', 'Discipline', 47516), ++ ('Priest', 'Discipline', 47537), ++ ('Priest', 'Discipline', 47540), ++ ('Priest', 'Discipline', 52800), ++ ('Priest', 'Discipline', 57472), ++ ('Priest', 'Discipline', 63574), ++ ('Druid', 'Restoration', 16820), ++ ('Druid', 'Restoration', 16835), ++ ('Druid', 'Restoration', 16836), ++ ('Druid', 'Restoration', 16847), ++ ('Druid', 'Restoration', 16850), ++ ('Druid', 'Restoration', 16864), ++ ('Druid', 'Restoration', 17051), ++ ('Druid', 'Restoration', 17066), ++ ('Druid', 'Restoration', 17076), ++ ('Druid', 'Restoration', 17108), ++ ('Druid', 'Restoration', 17113), ++ ('Druid', 'Restoration', 17116), ++ ('Druid', 'Restoration', 17120), ++ ('Druid', 'Restoration', 24946), ++ ('Druid', 'Restoration', 33880), ++ ('Druid', 'Restoration', 33883), ++ ('Druid', 'Restoration', 33890), ++ ('Druid', 'Restoration', 34153), ++ ('Druid', 'Restoration', 35364), ++ ('Druid', 'Restoration', 48412), ++ ('Druid', 'Restoration', 48438), ++ ('Druid', 'Restoration', 48537), ++ ('Druid', 'Restoration', 51183), ++ ('Druid', 'Restoration', 57814), ++ ('Druid', 'Restoration', 57865), ++ ('Druid', 'Restoration', 61345), ++ ('Druid', 'Restoration', 63411), ++ ('Druid', 'Restoration', 65139), ++ ('Hunter', 'Beastmastery', 19421), ++ ('Hunter', 'Beastmastery', 19431), ++ ('Hunter', 'Beastmastery', 19434), ++ ('Hunter', 'Beastmastery', 19456), ++ ('Hunter', 'Beastmastery', 19490), ++ ('Hunter', 'Beastmastery', 19575), ++ ('Hunter', 'Beastmastery', 19574), ++ ('Hunter', 'Beastmastery', 19577), ++ ('Hunter', 'Beastmastery', 19587), ++ ('Hunter', 'Beastmastery', 19592), ++ ('Hunter', 'Beastmastery', 19602), ++ ('Hunter', 'Beastmastery', 19609), ++ ('Hunter', 'Beastmastery', 19620), ++ ('Hunter', 'Beastmastery', 19623), ++ ('Hunter', 'Beastmastery', 20895), ++ ('Hunter', 'Beastmastery', 34454), ++ ('Hunter', 'Beastmastery', 34460), ++ ('Hunter', 'Beastmastery', 34470), ++ ('Hunter', 'Beastmastery', 34484), ++ ('Hunter', 'Beastmastery', 34692), ++ ('Hunter', 'Beastmastery', 35030), ++ ('Hunter', 'Beastmastery', 53260), ++ ('Hunter', 'Beastmastery', 53264), ++ ('Hunter', 'Beastmastery', 53265), ++ ('Hunter', 'Beastmastery', 53270), ++ ('Hunter', 'Beastmastery', 56318), ++ ('Hunter', 'Survival', 3674), ++ ('Hunter', 'Survival', 19259), ++ ('Hunter', 'Survival', 19287), ++ ('Hunter', 'Survival', 19373), ++ ('Hunter', 'Survival', 19386), ++ ('Hunter', 'Survival', 19388), ++ ('Hunter', 'Survival', 19431), ++ ('Hunter', 'Survival', 19434), ++ ('Hunter', 'Survival', 19490), ++ ('Hunter', 'Survival', 19500), ++ ('Hunter', 'Survival', 19503), ++ ('Hunter', 'Survival', 19553), ++ ('Hunter', 'Survival', 24297), ++ ('Hunter', 'Survival', 34484), ++ ('Hunter', 'Survival', 34496), ++ ('Hunter', 'Survival', 34499), ++ ('Hunter', 'Survival', 34503), ++ ('Hunter', 'Survival', 34839), ++ ('Hunter', 'Survival', 52785), ++ ('Hunter', 'Survival', 53292), ++ ('Hunter', 'Survival', 53301), ++ ('Hunter', 'Survival', 56337), ++ ('Hunter', 'Survival', 56341), ++ ('Hunter', 'Survival', 56344), ++ ('Hunter', 'Survival', 63458), ++ ('Paladin', 'Holy', 20143), ++ ('Paladin', 'Holy', 20175), ++ ('Paladin', 'Holy', 20208), ++ ('Paladin', 'Holy', 20215), ++ ('Paladin', 'Holy', 20216), ++ ('Paladin', 'Holy', 20239), ++ ('Paladin', 'Holy', 20256), ++ ('Paladin', 'Holy', 20261), ++ ('Paladin', 'Holy', 20361), ++ ('Paladin', 'Holy', 20470), ++ ('Paladin', 'Holy', 20473), ++ ('Paladin', 'Holy', 20488), ++ ('Paladin', 'Holy', 25829), ++ ('Paladin', 'Holy', 25836), ++ ('Paladin', 'Holy', 31821), ++ ('Paladin', 'Holy', 31825), ++ ('Paladin', 'Holy', 31840), ++ ('Paladin', 'Holy', 31842), ++ ('Paladin', 'Holy', 53519), ++ ('Paladin', 'Holy', 53530), ++ ('Paladin', 'Holy', 53553), ++ ('Paladin', 'Holy', 53557), ++ ('Paladin', 'Holy', 53563), ++ ('Paladin', 'Holy', 53576), ++ ('Paladin', 'Holy', 54154), ++ ('Paladin', 'Holy', 63650), ++ ('Paladin', 'Holy', 64205), ++ ('Mage', 'Frost', 11426), ++ ('Mage', 'Frost', 11958), ++ ('Mage', 'Frost', 12467), ++ ('Mage', 'Frost', 12472), ++ ('Mage', 'Frost', 12497), ++ ('Mage', 'Frost', 12571), ++ ('Mage', 'Frost', 12592), ++ ('Mage', 'Frost', 12598), ++ ('Mage', 'Frost', 12606), ++ ('Mage', 'Frost', 12840), ++ ('Mage', 'Frost', 12952), ++ ('Mage', 'Frost', 12983), ++ ('Mage', 'Frost', 15047), ++ ('Mage', 'Frost', 16758), ++ ('Mage', 'Frost', 16766), ++ ('Mage', 'Frost', 28332), ++ ('Mage', 'Frost', 28593), ++ ('Mage', 'Frost', 29444), ++ ('Mage', 'Frost', 31683), ++ ('Mage', 'Frost', 31687), ++ ('Mage', 'Frost', 44545), ++ ('Mage', 'Frost', 44549), ++ ('Mage', 'Frost', 44557), ++ ('Mage', 'Frost', 44571), ++ ('Mage', 'Frost', 44572), ++ ('Mage', 'Frost', 54646), ++ ('Mage', 'Frost', 54659), ++ ('Mage', 'Frost', 54787), ++ ('Mage', 'Frost', 55092), ++ ('Mage', 'Frost', 55094), ++ ('Mage', 'Frost', 55340), ++ ('Warlock', 'Destruction', 17792), ++ ('Warlock', 'Destruction', 17834), ++ ('Warlock', 'Destruction', 17877), ++ ('Warlock', 'Destruction', 17918), ++ ('Warlock', 'Destruction', 17958), ++ ('Warlock', 'Destruction', 17962), ++ ('Warlock', 'Destruction', 18120), ++ ('Warlock', 'Destruction', 18130), ++ ('Warlock', 'Destruction', 18136), ++ ('Warlock', 'Destruction', 18699), ++ ('Warlock', 'Destruction', 18704), ++ ('Warlock', 'Destruction', 18708), ++ ('Warlock', 'Destruction', 18710), ++ ('Warlock', 'Destruction', 18744), ++ ('Warlock', 'Destruction', 18756), ++ ('Warlock', 'Destruction', 19028), ++ ('Warlock', 'Destruction', 30145), ++ ('Warlock', 'Destruction', 30283), ++ ('Warlock', 'Destruction', 30292), ++ ('Warlock', 'Destruction', 30302), ++ ('Warlock', 'Destruction', 34939), ++ ('Warlock', 'Destruction', 47231), ++ ('Warlock', 'Destruction', 47260), ++ ('Warlock', 'Destruction', 47270), ++ ('Warlock', 'Destruction', 50796), ++ ('Warlock', 'Destruction', 59741), ++ ('Warlock', 'Destruction', 63351), ++ ('Warlock', 'Affliction', 17785), ++ ('Warlock', 'Affliction', 17805), ++ ('Warlock', 'Affliction', 17814), ++ ('Warlock', 'Affliction', 18095), ++ ('Warlock', 'Affliction', 18176), ++ ('Warlock', 'Affliction', 18180), ++ ('Warlock', 'Affliction', 18219), ++ ('Warlock', 'Affliction', 18223), ++ ('Warlock', 'Affliction', 18275), ++ ('Warlock', 'Affliction', 18288), ++ ('Warlock', 'Affliction', 18699), ++ ('Warlock', 'Affliction', 18704), ++ ('Warlock', 'Affliction', 18708), ++ ('Warlock', 'Affliction', 18744), ++ ('Warlock', 'Affliction', 19028), ++ ('Warlock', 'Affliction', 30057), ++ ('Warlock', 'Affliction', 30064), ++ ('Warlock', 'Affliction', 30108), ++ ('Warlock', 'Affliction', 30143), ++ ('Warlock', 'Affliction', 32383), ++ ('Warlock', 'Affliction', 32394), ++ ('Warlock', 'Affliction', 32484), ++ ('Warlock', 'Affliction', 47200), ++ ('Warlock', 'Affliction', 47205), ++ ('Warlock', 'Affliction', 47231), ++ ('Warlock', 'Affliction', 48181), ++ ('Warlock', 'Affliction', 53759), ++ ('Warlock', 'Affliction', 58435), ++ ('Warlock', 'Affliction', 63108), ++ ('Warlock', 'Demonology', 17791), ++ ('Warlock', 'Demonology', 17803), ++ ('Warlock', 'Demonology', 17877), ++ ('Warlock', 'Demonology', 17959), ++ ('Warlock', 'Demonology', 18693), ++ ('Warlock', 'Demonology', 18699), ++ ('Warlock', 'Demonology', 18708), ++ ('Warlock', 'Demonology', 18707), ++ ('Warlock', 'Demonology', 18710), ++ ('Warlock', 'Demonology', 18744), ++ ('Warlock', 'Demonology', 18756), ++ ('Warlock', 'Demonology', 18768), ++ ('Warlock', 'Demonology', 18773), ++ ('Warlock', 'Demonology', 19028), ++ ('Warlock', 'Demonology', 23785), ++ ('Warlock', 'Demonology', 30145), ++ ('Warlock', 'Demonology', 30146), ++ ('Warlock', 'Demonology', 30321), ++ ('Warlock', 'Demonology', 30248), ++ ('Warlock', 'Demonology', 35693), ++ ('Warlock', 'Demonology', 47231), ++ ('Warlock', 'Demonology', 47240), ++ ('Warlock', 'Demonology', 47247), ++ ('Warlock', 'Demonology', 59672), ++ ('Warlock', 'Demonology', 63123), ++ ('Warlock', 'Demonology', 63158), ++ ('Warlock', 'Demonology', 63351), ++ ('DeathKnight', 'Unholy', 49036), ++ ('DeathKnight', 'Unholy', 49039), ++ ('DeathKnight', 'Unholy', 49158), ++ ('DeathKnight', 'Unholy', 49194), ++ ('DeathKnight', 'Unholy', 49206), ++ ('DeathKnight', 'Unholy', 49222), ++ ('DeathKnight', 'Unholy', 49565), ++ ('DeathKnight', 'Unholy', 49568), ++ ('DeathKnight', 'Unholy', 49572), ++ ('DeathKnight', 'Unholy', 49589), ++ ('DeathKnight', 'Unholy', 49599), ++ ('DeathKnight', 'Unholy', 49611), ++ ('DeathKnight', 'Unholy', 49632), ++ ('DeathKnight', 'Unholy', 49638), ++ ('DeathKnight', 'Unholy', 49657), ++ ('DeathKnight', 'Unholy', 49664), ++ ('DeathKnight', 'Unholy', 50121), ++ ('DeathKnight', 'Unholy', 50147), ++ ('DeathKnight', 'Unholy', 50392), ++ ('DeathKnight', 'Unholy', 50886), ++ ('DeathKnight', 'Unholy', 51052), ++ ('DeathKnight', 'Unholy', 51161), ++ ('DeathKnight', 'Unholy', 51456), ++ ('DeathKnight', 'Unholy', 51746), ++ ('DeathKnight', 'Unholy', 52143), ++ ('DeathKnight', 'Unholy', 55090), ++ ('DeathKnight', 'Unholy', 55237), ++ ('DeathKnight', 'Unholy', 55623), ++ ('DeathKnight', 'Unholy', 55667), ++ ('DeathKnight', 'Unholy', 56835), ++ ('Warrior', 'Arms', 12294), ++ ('Warrior', 'Arms', 12323), ++ ('Warrior', 'Arms', 12328), ++ ('Warrior', 'Arms', 12658), ++ ('Warrior', 'Arms', 12677), ++ ('Warrior', 'Arms', 12704), ++ ('Warrior', 'Arms', 12712), ++ ('Warrior', 'Arms', 12835), ++ ('Warrior', 'Arms', 12856), ++ ('Warrior', 'Arms', 12867), ++ ('Warrior', 'Arms', 12960), ++ ('Warrior', 'Arms', 12963), ++ ('Warrior', 'Arms', 16464), ++ ('Warrior', 'Arms', 16492), ++ ('Warrior', 'Arms', 16494), ++ ('Warrior', 'Arms', 20505), ++ ('Warrior', 'Arms', 29623), ++ ('Warrior', 'Arms', 29724), ++ ('Warrior', 'Arms', 29838), ++ ('Warrior', 'Arms', 29859), ++ ('Warrior', 'Arms', 35449), ++ ('Warrior', 'Arms', 46855), ++ ('Warrior', 'Arms', 46860), ++ ('Warrior', 'Arms', 46866), ++ ('Warrior', 'Arms', 46924), ++ ('Warrior', 'Arms', 56614), ++ ('Warrior', 'Arms', 56638), ++ ('Warrior', 'Arms', 61222), ++ ('Warrior', 'Arms', 64976), ++ ('Warrior', 'Protection', 12658), ++ ('Warrior', 'Protection', 12697), ++ ('Warrior', 'Protection', 12727), ++ ('Warrior', 'Protection', 12764), ++ ('Warrior', 'Protection', 12799), ++ ('Warrior', 'Protection', 12803), ++ ('Warrior', 'Protection', 12804), ++ ('Warrior', 'Protection', 12809), ++ ('Warrior', 'Protection', 12818), ++ ('Warrior', 'Protection', 12867), ++ ('Warrior', 'Protection', 12958), ++ ('Warrior', 'Protection', 12960), ++ ('Warrior', 'Protection', 12975), ++ ('Warrior', 'Protection', 16464), ++ ('Warrior', 'Protection', 16494), ++ ('Warrior', 'Protection', 16542), ++ ('Warrior', 'Protection', 20243), ++ ('Warrior', 'Protection', 29144), ++ ('Warrior', 'Protection', 29594), ++ ('Warrior', 'Protection', 29599), ++ ('Warrior', 'Protection', 29792), ++ ('Warrior', 'Protection', 46968), ++ ('Warrior', 'Protection', 46953), ++ ('Warrior', 'Protection', 47296), ++ ('Warrior', 'Protection', 50687), ++ ('Warrior', 'Protection', 57499), ++ ('Warrior', 'Protection', 58874), ++ ('Warrior', 'Protection', 61222), ++ ('Warrior', 'Protection', 59089), ++ ('Warrior', 'Fury', 12292), ++ ('Warrior', 'Fury', 12318), ++ ('Warrior', 'Fury', 12323), ++ ('Warrior', 'Fury', 12664), ++ ('Warrior', 'Fury', 12676), ++ ('Warrior', 'Fury', 12835), ++ ('Warrior', 'Fury', 12856), ++ ('Warrior', 'Fury', 12960), ++ ('Warrior', 'Fury', 12974), ++ ('Warrior', 'Fury', 13048), ++ ('Warrior', 'Fury', 16463), ++ ('Warrior', 'Fury', 16492), ++ ('Warrior', 'Fury', 20501), ++ ('Warrior', 'Fury', 20503), ++ ('Warrior', 'Fury', 23588), ++ ('Warrior', 'Fury', 23881), ++ ('Warrior', 'Fury', 29763), ++ ('Warrior', 'Fury', 29776), ++ ('Warrior', 'Fury', 29801), ++ ('Warrior', 'Fury', 29889), ++ ('Warrior', 'Fury', 46911), ++ ('Warrior', 'Fury', 46915), ++ ('Warrior', 'Fury', 46917), ++ ('Warrior', 'Fury', 56924), ++ ('Warrior', 'Fury', 56932), ++ ('Warrior', 'Fury', 60970), ++ ('Warrior', 'Fury', 61222), ++ ('Rogue', 'Subtlety', 13866), ++ ('Rogue', 'Subtlety', 13971), ++ ('Rogue', 'Subtlety', 13976), ++ ('Rogue', 'Subtlety', 14063), ++ ('Rogue', 'Subtlety', 14066), ++ ('Rogue', 'Subtlety', 14072), ++ ('Rogue', 'Subtlety', 14080), ++ ('Rogue', 'Subtlety', 14083), ++ ('Rogue', 'Subtlety', 14094), ++ ('Rogue', 'Subtlety', 14132), ++ ('Rogue', 'Subtlety', 14142), ++ ('Rogue', 'Subtlety', 14160), ++ ('Rogue', 'Subtlety', 14164), ++ ('Rogue', 'Subtlety', 14173), ++ ('Rogue', 'Subtlety', 14183), ++ ('Rogue', 'Subtlety', 14185), ++ ('Rogue', 'Subtlety', 14983), ++ ('Rogue', 'Subtlety', 16511), ++ ('Rogue', 'Subtlety', 30895), ++ ('Rogue', 'Subtlety', 30906), ++ ('Rogue', 'Subtlety', 31220), ++ ('Rogue', 'Subtlety', 31223), ++ ('Rogue', 'Subtlety', 36554), ++ ('Rogue', 'Subtlety', 51696), ++ ('Rogue', 'Subtlety', 51701), ++ ('Rogue', 'Subtlety', 51712), ++ ('Rogue', 'Subtlety', 51713), ++ ('Rogue', 'Subtlety', 58425), ++ ('Rogue', 'Combat', 5952), ++ ('Rogue', 'Combat', 13750), ++ ('Rogue', 'Combat', 13789), ++ ('Rogue', 'Combat', 13803), ++ ('Rogue', 'Combat', 13843), ++ ('Rogue', 'Combat', 13852), ++ ('Rogue', 'Combat', 13854), ++ ('Rogue', 'Combat', 13863), ++ ('Rogue', 'Combat', 13867), ++ ('Rogue', 'Combat', 13875), ++ ('Rogue', 'Combat', 13872), ++ ('Rogue', 'Combat', 13877), ++ ('Rogue', 'Combat', 13971), ++ ('Rogue', 'Combat', 14062), ++ ('Rogue', 'Combat', 14066), ++ ('Rogue', 'Combat', 14094), ++ ('Rogue', 'Combat', 14166), ++ ('Rogue', 'Combat', 14251), ++ ('Rogue', 'Combat', 14278), ++ ('Rogue', 'Combat', 31126), ++ ('Rogue', 'Combat', 31131), ++ ('Rogue', 'Combat', 32601), ++ ('Rogue', 'Combat', 51674), ++ ('Rogue', 'Combat', 51689), ++ ('Rogue', 'Combat', 51690), ++ ('Rogue', 'Combat', 58413), ++ ('Rogue', 'Combat', 58425), ++ ('Rogue', 'Combat', 61329), ++ ('Rogue', 'Combat', 61331), ++ ('Hunter', 'Marksmanship', 19256), ++ ('Hunter', 'Marksmanship', 19286), ++ ('Hunter', 'Marksmanship', 19388), ++ ('Hunter', 'Marksmanship', 19407), ++ ('Hunter', 'Marksmanship', 19418), ++ ('Hunter', 'Marksmanship', 19431), ++ ('Hunter', 'Marksmanship', 19434), ++ ('Hunter', 'Marksmanship', 19466), ++ ('Hunter', 'Marksmanship', 19490), ++ ('Hunter', 'Marksmanship', 19500), ++ ('Hunter', 'Marksmanship', 19503), ++ ('Hunter', 'Marksmanship', 19506), ++ ('Hunter', 'Marksmanship', 19508), ++ ('Hunter', 'Marksmanship', 23989), ++ ('Hunter', 'Marksmanship', 24691), ++ ('Hunter', 'Marksmanship', 34476), ++ ('Hunter', 'Marksmanship', 34484), ++ ('Hunter', 'Marksmanship', 34489), ++ ('Hunter', 'Marksmanship', 34490), ++ ('Hunter', 'Marksmanship', 34496), ++ ('Hunter', 'Marksmanship', 34950), ++ ('Hunter', 'Marksmanship', 35102), ++ ('Hunter', 'Marksmanship', 35111), ++ ('Hunter', 'Marksmanship', 52785), ++ ('Hunter', 'Marksmanship', 53209), ++ ('Hunter', 'Marksmanship', 53217), ++ ('Hunter', 'Marksmanship', 53221), ++ ('Hunter', 'Marksmanship', 53232), ++ ('Hunter', 'Marksmanship', 53238), ++ ('Hunter', 'Marksmanship', 53246), ++ ('DeathKnight', 'Blood', 48982), ++ ('DeathKnight', 'Blood', 49005), ++ ('DeathKnight', 'Blood', 49028), ++ ('DeathKnight', 'Blood', 49393), ++ ('DeathKnight', 'Blood', 49395), ++ ('DeathKnight', 'Blood', 49480), ++ ('DeathKnight', 'Blood', 49483), ++ ('DeathKnight', 'Blood', 49489), ++ ('DeathKnight', 'Blood', 49497), ++ ('DeathKnight', 'Blood', 49504), ++ ('DeathKnight', 'Blood', 49530), ++ ('DeathKnight', 'Blood', 49534), ++ ('DeathKnight', 'Blood', 49787), ++ ('DeathKnight', 'Blood', 50029), ++ ('DeathKnight', 'Blood', 50034), ++ ('DeathKnight', 'Blood', 50147), ++ ('DeathKnight', 'Blood', 50150), ++ ('DeathKnight', 'Blood', 50371), ++ ('DeathKnight', 'Blood', 51456), ++ ('DeathKnight', 'Blood', 53138), ++ ('DeathKnight', 'Blood', 55050), ++ ('DeathKnight', 'Blood', 55062), ++ ('DeathKnight', 'Blood', 55108), ++ ('DeathKnight', 'Blood', 55225), ++ ('DeathKnight', 'Blood', 55233), ++ ('DeathKnight', 'Blood', 61158), ++ ('DeathKnight', 'Blood', 62908), ++ ('DeathKnight', 'Frost', 48982), ++ ('DeathKnight', 'Frost', 49039), ++ ('DeathKnight', 'Frost', 49143), ++ ('DeathKnight', 'Frost', 49184), ++ ('DeathKnight', 'Frost', 49203), ++ ('DeathKnight', 'Frost', 49393), ++ ('DeathKnight', 'Frost', 49479), ++ ('DeathKnight', 'Frost', 49483), ++ ('DeathKnight', 'Frost', 49489), ++ ('DeathKnight', 'Frost', 49491), ++ ('DeathKnight', 'Frost', 49538), ++ ('DeathKnight', 'Frost', 49657), ++ ('DeathKnight', 'Frost', 49662), ++ ('DeathKnight', 'Frost', 49791), ++ ('DeathKnight', 'Frost', 49796), ++ ('DeathKnight', 'Frost', 50043), ++ ('DeathKnight', 'Frost', 50130), ++ ('DeathKnight', 'Frost', 50138), ++ ('DeathKnight', 'Frost', 50147), ++ ('DeathKnight', 'Frost', 50191), ++ ('DeathKnight', 'Frost', 50884), ++ ('DeathKnight', 'Frost', 51130), ++ ('DeathKnight', 'Frost', 51456), ++ ('DeathKnight', 'Frost', 51473), ++ ('DeathKnight', 'Frost', 54637), ++ ('DeathKnight', 'Frost', 59057), ++ ('DeathKnight', 'Frost', 66192), ++ ('Priest', 'Shadow', 14747), ++ ('Priest', 'Shadow', 14791), ++ ('Priest', 'Shadow', 15008), ++ ('Priest', 'Shadow', 15286), ++ ('Priest', 'Shadow', 15308), ++ ('Priest', 'Shadow', 15316), ++ ('Priest', 'Shadow', 15317), ++ ('Priest', 'Shadow', 15331), ++ ('Priest', 'Shadow', 15336), ++ ('Priest', 'Shadow', 15338), ++ ('Priest', 'Shadow', 15407), ++ ('Priest', 'Shadow', 15448), ++ ('Priest', 'Shadow', 15473), ++ ('Priest', 'Shadow', 15487), ++ ('Priest', 'Shadow', 17191), ++ ('Priest', 'Shadow', 17322), ++ ('Priest', 'Shadow', 27816), ++ ('Priest', 'Shadow', 27840), ++ ('Priest', 'Shadow', 27904), ++ ('Priest', 'Shadow', 33193), ++ ('Priest', 'Shadow', 33213), ++ ('Priest', 'Shadow', 33225), ++ ('Priest', 'Shadow', 33371), ++ ('Priest', 'Shadow', 34914), ++ ('Priest', 'Shadow', 47570), ++ ('Priest', 'Shadow', 47581), ++ ('Priest', 'Shadow', 47585), ++ ('Priest', 'Shadow', 51167), ++ ('Priest', 'Shadow', 63627), ++ ('Priest', 'Shadow', 64044); ++/*!40000 ALTER TABLE `template_npc_talents` ENABLE KEYS */; ++/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; ++/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; ++/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index e983c9e..6865dfa 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1512,7 +1512,7 @@ void AddBattlegroundScripts() + // start101 + // start102 + // start103 +-// start104 ++void AddSC_TemplateNPC(); + // start105 + // start106 + // start107 +@@ -1718,7 +1718,7 @@ void AddCustomScripts() + // end101 + // end102 + // end103 +-// end104 ++ AddSC_TemplateNPC(); + // end105 + // end106 + // end107 +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..714c7f1 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -71,7 +71,7 @@ + // 04 + // 05 + // 06 +-// 07 ++#include "../../scripts/Custom/TemplateNPC.h" + // 08 + // 09 + // 10 +@@ -1950,6 +1950,25 @@ void World::SetInitialWorldSettings() + + LoadCharacterInfoStore(); + ++ TC_LOG_INFO("server.loading", "Loading Template Talents..."); ++ sTemplateNpcMgr->LoadTalentsContainer(); ++ ++ // Load templates for Template NPC #2 ++ TC_LOG_INFO("server.loading", "Loading Template Glyphs..."); ++ sTemplateNpcMgr->LoadGlyphsContainer(); ++ ++ // Load templates for Template NPC #3 ++ TC_LOG_INFO("server.loading", "Loading Template Gear for Humans..."); ++ sTemplateNpcMgr->LoadHumanGearContainer(); ++ ++ // Load templates for Template NPC #4 ++ TC_LOG_INFO("server.loading", "Loading Template Gear for Alliances..."); ++ sTemplateNpcMgr->LoadAllianceGearContainer(); ++ ++ // Load templates for Template NPC #5 ++ TC_LOG_INFO("server.loading", "Loading Template Gear for Hordes..."); ++ sTemplateNpcMgr->LoadHordeGearContainer(); ++ + uint32 startupDuration = GetMSTimeDiffToNow(startupBegin); + + TC_LOG_INFO("server.worldserver", "World initialized in %u minutes %u seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000)); +diff --git a/src/server/scripts/Custom/TemplateNPC.cpp b/src/server/scripts/Custom/TemplateNPC.cpp +new file mode 100644 +index 0000000..16dc313 +--- /dev/null ++++ b/src/server/scripts/Custom/TemplateNPC.cpp +@@ -0,0 +1,1466 @@ ++#include "TemplateNPC.h" ++ ++void sTemplateNPC::LearnPlateMailSpells(Player* player) ++{ ++ switch (player->getClass()) ++ { ++ case CLASS_WARRIOR: ++ case CLASS_PALADIN: ++ case CLASS_DEATH_KNIGHT: ++ player->LearnSpell(PLATE_MAIL, true); ++ break; ++ case CLASS_SHAMAN: ++ case CLASS_HUNTER: ++ player->LearnSpell(MAIL, true); ++ break; ++ default: ++ break; ++ } ++} ++ ++void sTemplateNPC::ApplyBonus(Player* player, Item* item, EnchantmentSlot slot, uint32 bonusEntry) ++{ ++ if (!item) ++ return; ++ ++ if (!bonusEntry || bonusEntry == 0) ++ return; ++ ++ player->ApplyEnchantment(item, slot, false); ++ item->SetEnchantment(slot, bonusEntry, 0, 0); ++ player->ApplyEnchantment(item, slot, true); ++} ++ ++void sTemplateNPC::ApplyGlyph(Player* player, uint8 slot, uint32 glyphID) ++{ ++ if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyphID)) ++ { ++ if (uint32 oldGlyph = player->GetGlyph(slot)) ++ { ++ player->RemoveAurasDueToSpell(sGlyphPropertiesStore.LookupEntry(oldGlyph)->SpellId); ++ player->SetGlyph(slot, 0); ++ } ++ player->CastSpell(player, gp->SpellId, true); ++ player->SetGlyph(slot, glyphID); ++ } ++} ++ ++void sTemplateNPC::LearnTemplateTalents(Player* player) ++{ ++ for (TalentContainer::const_iterator itr = m_TalentContainer.begin(); itr != m_TalentContainer.end(); ++itr) ++ { ++ if ((*itr)->playerClass == GetClassString(player).c_str() && (*itr)->playerSpec == sTalentsSpec) ++ { ++ player->LearnSpell((*itr)->talentId, false); ++ player->AddTalent((*itr)->talentId, player->GetActiveSpec(), true); ++ } ++ } ++ player->SetFreeTalentPoints(0); ++ player->SendTalentsInfoData(false); ++} ++ ++void sTemplateNPC::LearnTemplateGlyphs(Player* player) ++{ ++ for (GlyphContainer::const_iterator itr = m_GlyphContainer.begin(); itr != m_GlyphContainer.end(); ++itr) ++ { ++ if ((*itr)->playerClass == GetClassString(player).c_str() && (*itr)->playerSpec == sTalentsSpec) ++ ApplyGlyph(player, (*itr)->slot, (*itr)->glyph); ++ } ++ player->SendTalentsInfoData(false); ++} ++ ++void sTemplateNPC::EquipTemplateGear(Player* player) ++{ ++ if (player->getRace() == RACE_HUMAN) ++ { ++ for (HumanGearContainer::const_iterator itr = m_HumanGearContainer.begin(); itr != m_HumanGearContainer.end(); ++itr) ++ { ++ if ((*itr)->playerClass == GetClassString(player).c_str() && (*itr)->playerSpec == sTalentsSpec) ++ { ++ player->EquipNewItem((*itr)->pos, (*itr)->itemEntry, true); // Equip the item and apply enchants and gems ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PERM_ENCHANTMENT_SLOT, (*itr)->enchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT, (*itr)->socket1); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_2, (*itr)->socket2); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_3, (*itr)->socket3); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), BONUS_ENCHANTMENT_SLOT, (*itr)->bonusEnchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PRISMATIC_ENCHANTMENT_SLOT, (*itr)->prismaticEnchant); ++ } ++ } ++ } ++ else if (player->GetTeam() == ALLIANCE && player->getRace() != RACE_HUMAN) ++ { ++ for (AllianceGearContainer::const_iterator itr = m_AllianceGearContainer.begin(); itr != m_AllianceGearContainer.end(); ++itr) ++ { ++ if ((*itr)->playerClass == GetClassString(player).c_str() && (*itr)->playerSpec == sTalentsSpec) ++ { ++ player->EquipNewItem((*itr)->pos, (*itr)->itemEntry, true); // Equip the item and apply enchants and gems ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PERM_ENCHANTMENT_SLOT, (*itr)->enchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT, (*itr)->socket1); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_2, (*itr)->socket2); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_3, (*itr)->socket3); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), BONUS_ENCHANTMENT_SLOT, (*itr)->bonusEnchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PRISMATIC_ENCHANTMENT_SLOT, (*itr)->prismaticEnchant); ++ } ++ } ++ } ++ else if (player->GetTeam() == HORDE) ++ { ++ for (HordeGearContainer::const_iterator itr = m_HordeGearContainer.begin(); itr != m_HordeGearContainer.end(); ++itr) ++ { ++ if ((*itr)->playerClass == GetClassString(player).c_str() && (*itr)->playerSpec == sTalentsSpec) ++ { ++ player->EquipNewItem((*itr)->pos, (*itr)->itemEntry, true); // Equip the item and apply enchants and gems ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PERM_ENCHANTMENT_SLOT, (*itr)->enchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT, (*itr)->socket1); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_2, (*itr)->socket2); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), SOCK_ENCHANTMENT_SLOT_3, (*itr)->socket3); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), BONUS_ENCHANTMENT_SLOT, (*itr)->bonusEnchant); ++ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, (*itr)->pos), PRISMATIC_ENCHANTMENT_SLOT, (*itr)->prismaticEnchant); ++ } ++ } ++ } ++} ++ ++void sTemplateNPC::LoadTalentsContainer() ++{ ++ for (TalentContainer::const_iterator itr = m_TalentContainer.begin(); itr != m_TalentContainer.end(); ++itr) ++ delete *itr; ++ ++ m_TalentContainer.clear(); ++ ++ uint32 oldMSTime = getMSTime(); ++ uint32 count = 0; ++ ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec, talentId FROM template_npc_talents;"); ++ ++ if (!result) ++ { ++ TC_LOG_INFO("server.worldserver", ">> Loaded 0 talent templates. DB table `template_npc_talents` is empty!"); ++ return; ++ } ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ TalentTemplate* pTalent = new TalentTemplate; ++ ++ pTalent->playerClass = fields[0].GetString(); ++ pTalent->playerSpec = fields[1].GetString(); ++ pTalent->talentId = fields[2].GetUInt32(); ++ ++ m_TalentContainer.push_back(pTalent); ++ ++count; ++ } ++ while (result->NextRow()); ++ TC_LOG_INFO("server.worldserver", ">> Loaded %u talent templates in %u ms.", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ ++void sTemplateNPC::LoadGlyphsContainer() ++{ ++ for (GlyphContainer::const_iterator itr = m_GlyphContainer.begin(); itr != m_GlyphContainer.end(); ++itr) ++ delete *itr; ++ ++ m_GlyphContainer.clear(); ++ ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec, slot, glyph FROM template_npc_glyphs;"); ++ ++ uint32 oldMSTime = getMSTime(); ++ uint32 count = 0; ++ ++ if (!result) ++ { ++ TC_LOG_INFO("server.worldserver", ">> Loaded 0 glyph templates. DB table `template_npc_glyphs` is empty!"); ++ return; ++ } ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ GlyphTemplate* pGlyph = new GlyphTemplate; ++ ++ pGlyph->playerClass = fields[0].GetString(); ++ pGlyph->playerSpec = fields[1].GetString(); ++ pGlyph->slot = fields[2].GetUInt8(); ++ pGlyph->glyph = fields[3].GetUInt32(); ++ ++ m_GlyphContainer.push_back(pGlyph); ++ ++count; ++ } ++ while (result->NextRow()); ++ TC_LOG_INFO("server.worldserver", ">> Loaded %u glyph templates in %u ms.", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ ++void sTemplateNPC::LoadHumanGearContainer() ++{ ++ for (HumanGearContainer::const_iterator itr = m_HumanGearContainer.begin(); itr != m_HumanGearContainer.end(); ++itr) ++ delete *itr; ++ ++ m_HumanGearContainer.clear(); ++ ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec, pos, itemEntry, enchant, socket1, socket2, socket3, bonusEnchant, prismaticEnchant FROM template_npc_human;"); ++ ++ uint32 oldMSTime = getMSTime(); ++ uint32 count = 0; ++ ++ if (!result) ++ { ++ TC_LOG_INFO("server.worldserver", ">> Loaded 0 'gear templates. DB table `template_npc_human` is empty!"); ++ return; ++ } ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ HumanGearTemplate* pItem = new HumanGearTemplate; ++ ++ pItem->playerClass = fields[0].GetString(); ++ pItem->playerSpec = fields[1].GetString(); ++ pItem->pos = fields[2].GetUInt8(); ++ pItem->itemEntry = fields[3].GetUInt32(); ++ pItem->enchant = fields[4].GetUInt32(); ++ pItem->socket1 = fields[5].GetUInt32(); ++ pItem->socket2 = fields[6].GetUInt32(); ++ pItem->socket3 = fields[7].GetUInt32(); ++ pItem->bonusEnchant = fields[8].GetUInt32(); ++ pItem->prismaticEnchant = fields[9].GetUInt32(); ++ ++ m_HumanGearContainer.push_back(pItem); ++ ++count; ++ } ++ while (result->NextRow()); ++ TC_LOG_INFO("server.worldserver", ">> Loaded %u gear templates for Humans in %u ms.", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ ++void sTemplateNPC::LoadAllianceGearContainer() ++{ ++ for (AllianceGearContainer::const_iterator itr = m_AllianceGearContainer.begin(); itr != m_AllianceGearContainer.end(); ++itr) ++ delete *itr; ++ ++ m_AllianceGearContainer.clear(); ++ ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec, pos, itemEntry, enchant, socket1, socket2, socket3, bonusEnchant, prismaticEnchant FROM template_npc_alliance;"); ++ ++ uint32 oldMSTime = getMSTime(); ++ uint32 count = 0; ++ ++ if (!result) ++ { ++ TC_LOG_INFO("server.worldserver", ">> Loaded 0 'gear templates. DB table `template_npc_alliance` is empty!"); ++ return; ++ } ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ AllianceGearTemplate* pItem = new AllianceGearTemplate; ++ ++ pItem->playerClass = fields[0].GetString(); ++ pItem->playerSpec = fields[1].GetString(); ++ pItem->pos = fields[2].GetUInt8(); ++ pItem->itemEntry = fields[3].GetUInt32(); ++ pItem->enchant = fields[4].GetUInt32(); ++ pItem->socket1 = fields[5].GetUInt32(); ++ pItem->socket2 = fields[6].GetUInt32(); ++ pItem->socket3 = fields[7].GetUInt32(); ++ pItem->bonusEnchant = fields[8].GetUInt32(); ++ pItem->prismaticEnchant = fields[9].GetUInt32(); ++ ++ m_AllianceGearContainer.push_back(pItem); ++ ++count; ++ } ++ while (result->NextRow()); ++ TC_LOG_INFO("server.worldserver", ">> Loaded %u gear templates for Alliances in %u ms.", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ ++void sTemplateNPC::LoadHordeGearContainer() ++{ ++ for (HordeGearContainer::const_iterator itr = m_HordeGearContainer.begin(); itr != m_HordeGearContainer.end(); ++itr) ++ delete *itr; ++ ++ m_HordeGearContainer.clear(); ++ ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec, pos, itemEntry, enchant, socket1, socket2, socket3, bonusEnchant, prismaticEnchant FROM template_npc_horde;"); ++ ++ uint32 oldMSTime = getMSTime(); ++ uint32 count = 0; ++ ++ if (!result) ++ { ++ TC_LOG_INFO("server.worldserver", ">> Loaded 0 'gear templates. DB table `template_npc_horde` is empty!"); ++ return; ++ } ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ HordeGearTemplate* pItem = new HordeGearTemplate; ++ ++ pItem->playerClass = fields[0].GetString(); ++ pItem->playerSpec = fields[1].GetString(); ++ pItem->pos = fields[2].GetUInt8(); ++ pItem->itemEntry = fields[3].GetUInt32(); ++ pItem->enchant = fields[4].GetUInt32(); ++ pItem->socket1 = fields[5].GetUInt32(); ++ pItem->socket2 = fields[6].GetUInt32(); ++ pItem->socket3 = fields[7].GetUInt32(); ++ pItem->bonusEnchant = fields[8].GetUInt32(); ++ pItem->prismaticEnchant = fields[9].GetUInt32(); ++ ++ m_HordeGearContainer.push_back(pItem); ++ ++count; ++ } ++ while (result->NextRow()); ++ TC_LOG_INFO("server.worldserver", ">> Loaded %u gear templates for Hordes in %u ms.", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ ++std::string sTemplateNPC::GetClassString(Player* player) ++{ ++ switch (player->getClass()) ++ { ++ case CLASS_PRIEST: return "Priest"; break; ++ case CLASS_PALADIN: return "Paladin"; break; ++ case CLASS_WARRIOR: return "Warrior"; break; ++ case CLASS_MAGE: return "Mage"; break; ++ case CLASS_WARLOCK: return "Warlock"; break; ++ case CLASS_SHAMAN: return "Shaman"; break; ++ case CLASS_DRUID: return "Druid"; break; ++ case CLASS_HUNTER: return "Hunter"; break; ++ case CLASS_ROGUE: return "Rogue"; break; ++ case CLASS_DEATH_KNIGHT: return "DeathKnight"; break; ++ default: ++ break; ++ } ++ return "Unknown"; // Fix warning, this should never happen ++} ++ ++bool sTemplateNPC::OverwriteTemplate(Player* player, std::string& playerSpecStr) ++{ ++ // Delete old talent and glyph templates before extracting new ones ++ CharacterDatabase.PExecute("DELETE FROM template_npc_talents WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ CharacterDatabase.PExecute("DELETE FROM template_npc_glyphs WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ ++ // Delete old gear templates before extracting new ones ++ if (player->getRace() == RACE_HUMAN) ++ { ++ CharacterDatabase.PExecute("DELETE FROM template_npc_human WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ player->GetSession()->SendAreaTriggerMessage("Template successfuly created!"); ++ return false; ++ } ++ else if (player->GetTeam() == ALLIANCE && player->getRace() != RACE_HUMAN) ++ { ++ CharacterDatabase.PExecute("DELETE FROM template_npc_alliance WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ player->GetSession()->SendAreaTriggerMessage("Template successfuly created!"); ++ return false; ++ } ++ else if (player->GetTeam() == HORDE) ++ { // ????????????? sTemplateNpcMgr here?? ++ CharacterDatabase.PExecute("DELETE FROM template_npc_horde WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ player->GetSession()->SendAreaTriggerMessage("Template successfuly created!"); ++ return false; ++ } ++ return true; ++} ++ ++void sTemplateNPC::ExtractGearTemplateToDB(Player* player, std::string& playerSpecStr) ++{ ++ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) ++ { ++ Item* equippedItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); ++ ++ if (equippedItem) ++ { ++ if (player->getRace() == RACE_HUMAN) ++ { ++ CharacterDatabase.PExecute("INSERT INTO template_npc_human (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ('%s', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u');" ++ , GetClassString(player).c_str(), playerSpecStr.c_str(), equippedItem->GetSlot(), equippedItem->GetEntry(), equippedItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT), ++ equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_2), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_3), ++ equippedItem->GetEnchantmentId(BONUS_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT)); ++ } ++ else if (player->GetTeam() == ALLIANCE && player->getRace() != RACE_HUMAN) ++ { ++ CharacterDatabase.PExecute("INSERT INTO template_npc_alliance (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ('%s', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u');" ++ , GetClassString(player).c_str(), playerSpecStr.c_str(), equippedItem->GetSlot(), equippedItem->GetEntry(), equippedItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT), ++ equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_2), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_3), ++ equippedItem->GetEnchantmentId(BONUS_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT)); ++ } ++ else if (player->GetTeam() == HORDE) ++ { ++ CharacterDatabase.PExecute("INSERT INTO template_npc_horde (`playerClass`, `playerSpec`, `pos`, `itemEntry`, `enchant`, `socket1`, `socket2`, `socket3`, `bonusEnchant`, `prismaticEnchant`) VALUES ('%s', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u');" ++ , GetClassString(player).c_str(), playerSpecStr.c_str(), equippedItem->GetSlot(), equippedItem->GetEntry(), equippedItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT), ++ equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_2), equippedItem->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_3), ++ equippedItem->GetEnchantmentId(BONUS_ENCHANTMENT_SLOT), equippedItem->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT)); ++ } ++ } ++ } ++} ++ ++void sTemplateNPC::ExtractTalentTemplateToDB(Player* player, std::string& playerSpecStr) ++{ ++ QueryResult result = CharacterDatabase.PQuery("SELECT spell FROM character_talent WHERE guid = '%u' " ++ "AND spec = '%u';", player->GetGUID(), player->GetActiveSpec()); ++ ++ if (!result) ++ { ++ return; ++ } ++ else if (player->GetFreeTalentPoints() > 0) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You have unspend talent points. Please spend all your talent points and re-extract the template."); ++ return; ++ } ++ else ++ { ++ do ++ { ++ Field* fields = result->Fetch(); ++ uint32 spell = fields[0].GetUInt32(); ++ ++ CharacterDatabase.PExecute("INSERT INTO template_npc_talents (playerClass, playerSpec, talentId) " ++ "VALUES ('%s', '%s', '%u');", GetClassString(player).c_str(), playerSpecStr.c_str(), spell); ++ } ++ while (result->NextRow()); ++ } ++} ++ ++void sTemplateNPC::ExtractGlyphsTemplateToDB(Player* player, std::string& playerSpecStr) ++{ ++ QueryResult result = CharacterDatabase.PQuery("SELECT glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 " ++ "FROM character_glyphs WHERE guid = '%u' AND spec = '%u';", player->GetGUID(), player->GetActiveSpec()); ++ ++ for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot) ++ { ++ if (!result) ++ { ++ player->GetSession()->SendAreaTriggerMessage("Get glyphs and re-extract the template!"); ++ continue; ++ } ++ ++ Field* fields = result->Fetch(); ++ uint32 glyph1 = fields[0].GetUInt32(); ++ uint32 glyph2 = fields[1].GetUInt32(); ++ uint32 glyph3 = fields[2].GetUInt32(); ++ uint32 glyph4 = fields[3].GetUInt32(); ++ uint32 glyph5 = fields[4].GetUInt32(); ++ uint32 glyph6 = fields[5].GetUInt32(); ++ ++ uint32 storedGlyph; ++ ++ switch (slot) ++ { ++ case 0: ++ storedGlyph = glyph1; ++ break; ++ case 1: ++ storedGlyph = glyph2; ++ break; ++ case 2: ++ storedGlyph = glyph3; ++ break; ++ case 3: ++ storedGlyph = glyph4; ++ break; ++ case 4: ++ storedGlyph = glyph5; ++ break; ++ case 5: ++ storedGlyph = glyph6; ++ break; ++ default: ++ break; ++ } ++ ++ CharacterDatabase.PExecute("INSERT INTO template_npc_glyphs (playerClass, playerSpec, slot, glyph) " ++ "VALUES ('%s', '%s', '%u', '%u');", GetClassString(player).c_str(), playerSpecStr.c_str(), slot, storedGlyph); ++ } ++} ++ ++bool sTemplateNPC::CanEquipTemplate(Player* player, std::string& playerSpecStr) ++{ ++ if (player->getRace() == RACE_HUMAN) ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec FROM template_npc_human " ++ "WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ ++ if (!result) ++ return false; ++ } ++ else if (player->GetTeam() == ALLIANCE && player->getRace() != RACE_HUMAN) ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec FROM template_npc_alliance " ++ "WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ ++ if (!result) ++ return false; ++ } ++ else if (player->GetTeam() == HORDE) ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT playerClass, playerSpec FROM template_npc_horde " ++ "WHERE playerClass = '%s' AND playerSpec = '%s';", GetClassString(player).c_str(), playerSpecStr.c_str()); ++ ++ if (!result) ++ return false; ++ } ++ return true; ++} ++ ++class TemplateNPC_Commands : public CommandScript ++{ ++public: ++ TemplateNPC_Commands() : CommandScript("TemplateNPC_Commands") { } ++ ++ std::vector GetCommands() const override ++ { ++ static std::vector createDeathKnightItemSetTable = ++ { ++ { "blood", SEC_ADMINISTRATOR, false, &HandleCreateDeathKnightBloodItemSetCommand, ""}, ++ { "frost", SEC_ADMINISTRATOR, false, &HandleCreateDeathKnightFrostItemSetCommand, ""}, ++ { "unholy", SEC_ADMINISTRATOR, false, &HandleCreateDeathKnightUnholyItemSetCommand, ""}, ++ }; ++ static std::vector createRogueItemSetTable = ++ { ++ { "assassination", SEC_ADMINISTRATOR, false, &HandleCreateRogueAssassinationItemSetCommand, ""}, ++ { "combat", SEC_ADMINISTRATOR, false, &HandleCreateRogueCombatItemSetCommand, ""}, ++ { "subtlety", SEC_ADMINISTRATOR, false, &HandleCreateRogueSubtletyItemSetCommand, ""}, ++ }; ++ static std::vector createHunterItemSetTable = ++ { ++ { "marksmanship", SEC_ADMINISTRATOR, false, &HandleCreateHunterMarksmanshipItemSetCommand, ""}, ++ { "beastmastery", SEC_ADMINISTRATOR, false, &HandleCreateHunterBeastmasteryItemSetCommand, ""}, ++ { "survival", SEC_ADMINISTRATOR, false, &HandleCreateHunterSurvivalItemSetCommand, ""}, ++ }; ++ static std::vector createDruidItemSetTable = ++ { ++ { "ballance", SEC_ADMINISTRATOR, false, &HandleCreateDruidBallanceItemSetCommand, ""}, ++ { "feral", SEC_ADMINISTRATOR, false, &HandleCreateDruidFeralItemSetCommand, ""}, ++ { "restoration", SEC_ADMINISTRATOR, false, &HandleCreateDruidRestorationItemSetCommand, ""}, ++ }; ++ static std::vector createShamanItemSetTable = ++ { ++ { "elemental", SEC_ADMINISTRATOR, false, &HandleCreateShamanElementalItemSetCommand, ""}, ++ { "enhancement", SEC_ADMINISTRATOR, false, &HandleCreateShamanEnhancementItemSetCommand, ""}, ++ { "restoration", SEC_ADMINISTRATOR, false, &HandleCreateShamanRestorationItemSetCommand, ""}, ++ }; ++ static std::vector createWarlockItemSetTable = ++ { ++ { "affliction", SEC_ADMINISTRATOR, false, &HandleCreateWarlockAfflictionItemSetCommand, ""}, ++ { "demonology", SEC_ADMINISTRATOR, false, &HandleCreateWarlockDemonologyItemSetCommand, ""}, ++ { "destruction", SEC_ADMINISTRATOR, false, &HandleCreateWarlockDestructionItemSetCommand, ""}, ++ }; ++ static std::vector createMageItemSetTable = ++ { ++ { "frost", SEC_ADMINISTRATOR, false, &HandleCreateMageFrostItemSetCommand, ""}, ++ { "fire", SEC_ADMINISTRATOR, false, &HandleCreateMageFireItemSetCommand, ""}, ++ { "arcane", SEC_ADMINISTRATOR, false, &HandleCreateMageArcaneItemSetCommand, ""}, ++ }; ++ static std::vector createWarriorItemSetTable = ++ { ++ { "arms", SEC_ADMINISTRATOR, false, &HandleCreateWarriorArmsItemSetCommand, ""}, ++ { "fury", SEC_ADMINISTRATOR, false, &HandleCreateWarriorFuryItemSetCommand, ""}, ++ { "protection", SEC_ADMINISTRATOR, false, &HandleCreateWarriorProtectionItemSetCommand, ""}, ++ }; ++ static std::vector createPaladinItemSetTable = ++ { ++ { "holy", SEC_ADMINISTRATOR, false, &HandleCreatePaladinHolyItemSetCommand, ""}, ++ { "protection", SEC_ADMINISTRATOR, false, &HandleCreatePaladinProtectionItemSetCommand, ""}, ++ { "retribution", SEC_ADMINISTRATOR, false, &HandleCreatePaladinRetributionItemSetCommand, ""}, ++ }; ++ ++ static std::vector createPriestItemSetTable = ++ { ++ { "discipline", SEC_ADMINISTRATOR, false, &HandleCreatePriestDisciplineItemSetCommand, ""}, ++ { "shadow", SEC_ADMINISTRATOR, false, &HandleCreatePriestShadowItemSetCommand, ""}, ++ { "holy", SEC_ADMINISTRATOR, false, &HandleCreatePriestHolyItemSetCommand, ""}, ++ }; ++ static std::vector createItemSetCommandTable = ++ { ++ { "priest", SEC_ADMINISTRATOR, true, NULL, "", createPriestItemSetTable }, ++ { "paladin", SEC_ADMINISTRATOR, true, NULL, "", createPaladinItemSetTable }, ++ { "warrior", SEC_ADMINISTRATOR, true, NULL, "", createWarriorItemSetTable }, ++ { "mage", SEC_ADMINISTRATOR, true, NULL, "", createMageItemSetTable }, ++ { "warlock", SEC_ADMINISTRATOR, true, NULL, "", createWarlockItemSetTable }, ++ { "shaman", SEC_ADMINISTRATOR, true, NULL, "", createShamanItemSetTable }, ++ { "druid", SEC_ADMINISTRATOR, true, NULL, "", createDruidItemSetTable }, ++ { "hunter", SEC_ADMINISTRATOR, true, NULL, "", createHunterItemSetTable }, ++ { "rogue", SEC_ADMINISTRATOR, true, NULL, "", createRogueItemSetTable }, ++ { "deathknight", SEC_ADMINISTRATOR, true, NULL, "", createDeathKnightItemSetTable }, ++ }; ++ static std::vector commandTable = ++ { ++ { "create", SEC_ADMINISTRATOR, true, NULL, "", createItemSetCommandTable }, ++ }; ++ return commandTable; ++ } ++ ++ // DISCIPLINE PRIEST ++ static bool HandleCreatePriestDisciplineItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ ++ if (player->getClass() != CLASS_PRIEST) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a priest!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Discipline"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // SHADOW PRIEST ++ static bool HandleCreatePriestShadowItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_PRIEST) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a priest!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Shadow"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // HOLY PRIEST ++ static bool HandleCreatePriestHolyItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_PRIEST) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a priest!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Holy"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // HOLY PALADIN ++ static bool HandleCreatePaladinHolyItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_PALADIN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a paladin!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Holy"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // PROTECTION PALADIN ++ static bool HandleCreatePaladinProtectionItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_PALADIN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a paladin!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Protection"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // RETRIBUTION PALADIN ++ static bool HandleCreatePaladinRetributionItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_PALADIN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a paladin!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Retribution"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // FURY WARRIOR ++ static bool HandleCreateWarriorFuryItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARRIOR) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warrior!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Fury"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // ARMS WARRIOR ++ static bool HandleCreateWarriorArmsItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARRIOR) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warrior!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Arms"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // PROTECTION WARRIOR ++ static bool HandleCreateWarriorProtectionItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARRIOR) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warrior!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Protection"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // ARCANE MAGE ++ static bool HandleCreateMageArcaneItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_MAGE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a mage!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Arcane"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // FIRE MAGE ++ static bool HandleCreateMageFireItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_MAGE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a mage!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Fire"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // FROST MAGE ++ static bool HandleCreateMageFrostItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_MAGE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a mage!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Frost"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // AFFLICTION WARLOCK ++ static bool HandleCreateWarlockAfflictionItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARLOCK) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warlock!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Affliction"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // DEMONOLOGY WARLOCK ++ static bool HandleCreateWarlockDemonologyItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARLOCK) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warlock!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Demonology"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // DESTRUCTION WARLOCK ++ static bool HandleCreateWarlockDestructionItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_WARLOCK) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a warlock!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Destruction"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // ELEMENTAL SHAMAN ++ static bool HandleCreateShamanElementalItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_SHAMAN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a shaman!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Elemental"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // ENHANCEMENT SHAMAN ++ static bool HandleCreateShamanEnhancementItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_SHAMAN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a shaman!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Enhancement"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // RESTORATION SHAMAN ++ static bool HandleCreateShamanRestorationItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_SHAMAN) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a shaman!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Restoration"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // BALLANCE DRUID ++ static bool HandleCreateDruidBallanceItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DRUID) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a druid!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Ballance"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // FERAL DRUID ++ static bool HandleCreateDruidFeralItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DRUID) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a druid!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Feral"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // RESTORATION DRUID ++ static bool HandleCreateDruidRestorationItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DRUID) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a druid!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Restoration"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // MARKSMANSHIP HUNTER ++ static bool HandleCreateHunterMarksmanshipItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_HUNTER) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a hunter!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Marksmanship"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // BEASTMASTERY HUNTER ++ static bool HandleCreateHunterBeastmasteryItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_HUNTER) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a hunter!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Beastmastery"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // SURVIVAL HUNTER ++ static bool HandleCreateHunterSurvivalItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_HUNTER) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a hunter!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Survival"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // ASSASSINATION ROGUE ++ static bool HandleCreateRogueAssassinationItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_ROGUE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a rogue!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Assassination"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // COMBAT ROGUE ++ static bool HandleCreateRogueCombatItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_ROGUE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a rogue!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Combat"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // SUBTLETY ROGUE ++ static bool HandleCreateRogueSubtletyItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_ROGUE) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a rogue!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Subtlety"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // BLOOD DEATHKNIGHT ++ static bool HandleCreateDeathKnightBloodItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DEATH_KNIGHT) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a death knight!"); ++ return false; ++ } ++ player->SaveToDB(); ++ sTemplateNpcMgr->sTalentsSpec = "Blood"; ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // FROST DEATHKNIGHT ++ static bool HandleCreateDeathKnightFrostItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DEATH_KNIGHT) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a death knight!"); ++ return false; ++ } ++ sTemplateNpcMgr->sTalentsSpec = "Frost"; ++ player->SaveToDB(); ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++ ++ // UNHOLY DEATHKNIGHT ++ static bool HandleCreateDeathKnightUnholyItemSetCommand(ChatHandler* handler, const char* args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (player->getClass() != CLASS_DEATH_KNIGHT) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You're not a death knight!"); ++ return false; ++ } ++ sTemplateNpcMgr->sTalentsSpec = "Unholy"; ++ player->SaveToDB(); ++ sTemplateNpcMgr->OverwriteTemplate(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGearTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractTalentTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ sTemplateNpcMgr->ExtractGlyphsTemplateToDB(player, sTemplateNpcMgr->sTalentsSpec); ++ return true; ++ } ++}; ++ ++class TemplateNPC : public CreatureScript ++{ ++public: ++ TemplateNPC() : CreatureScript("TemplateNPC") { } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ switch (player->getClass()) ++ { ++ case CLASS_PRIEST: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_wordfortitude:30|t|r Use Discipline Spec", GOSSIP_SENDER_MAIN, 0); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_holybolt:30|t|r Use Holy Spec", GOSSIP_SENDER_MAIN, 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_shadow_shadowwordpain:30|t|r Use Shadow Spec", GOSSIP_SENDER_MAIN, 2); ++ } ++ break; ++ case CLASS_PALADIN: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_holybolt:30|t|r Use Holy Spec", GOSSIP_SENDER_MAIN, 3); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_devotionaura:30|t|r Use Protection Spec", GOSSIP_SENDER_MAIN, 4); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_auraoflight:30|t|r Use Retribution Spec", GOSSIP_SENDER_MAIN, 5); ++ } ++ break; ++ case CLASS_WARRIOR: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_warrior_innerrage:30|t|r Use Fury Spec", GOSSIP_SENDER_MAIN, 6); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_rogue_eviscerate:30|t|r Use Arms Spec", GOSSIP_SENDER_MAIN, 7); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_warrior_defensivestance:30|t|r Use Protection Spec", GOSSIP_SENDER_MAIN, 8); ++ } ++ break; ++ case CLASS_MAGE: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_holy_magicalsentry:30|t|r Use Arcane Spec", GOSSIP_SENDER_MAIN, 9); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_fire_flamebolt:30|t|r Use Fire Spec", GOSSIP_SENDER_MAIN, 10); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_frost_frostbolt02:30|t|r Use Frost Spec", GOSSIP_SENDER_MAIN, 11); ++ } ++ break; ++ case CLASS_WARLOCK: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_shadow_deathcoil:30|t|r Use Affliction Spec", GOSSIP_SENDER_MAIN, 12); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_shadow_metamorphosis:30|t|r Use Demonology Spec", GOSSIP_SENDER_MAIN, 13); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_shadow_rainoffire:30|t|r Use Destruction Spec", GOSSIP_SENDER_MAIN, 14); ++ } ++ break; ++ case CLASS_SHAMAN: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_nature_lightning:30|t|r Use Elemental Spec", GOSSIP_SENDER_MAIN, 15); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_nature_lightningshield:30|t|r Use Enhancement Spec", GOSSIP_SENDER_MAIN, 16); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_nature_magicimmunity:30|t|r Use Restoration Spec", GOSSIP_SENDER_MAIN, 17); ++ } ++ break; ++ case CLASS_DRUID: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_nature_starfall:30|t|r Use Ballance Spec", GOSSIP_SENDER_MAIN, 18); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_racial_bearform:30|t|r Use Feral Spec", GOSSIP_SENDER_MAIN, 19); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_nature_healingtouch:30|t|r Use Restoration Spec", GOSSIP_SENDER_MAIN, 20); ++ } ++ break; ++ case CLASS_HUNTER: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_marksmanship:30|t|r Use Markmanship Spec", GOSSIP_SENDER_MAIN, 21); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_hunter_beasttaming:30|t|r Use Beastmastery Spec", GOSSIP_SENDER_MAIN, 22); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_Hunter_swiftstrike:30|t|r Use Survival Spec", GOSSIP_SENDER_MAIN, 23); ++ } ++ break; ++ case CLASS_ROGUE: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_rogue_eviscerate:30|t|r Use Assasination Spec", GOSSIP_SENDER_MAIN, 24); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_backstab:30|t|r Use Combat Spec", GOSSIP_SENDER_MAIN, 25); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\ability_stealth:30|t|r Use Subtlety Spec", GOSSIP_SENDER_MAIN, 26); ++ } ++ break; ++ case CLASS_DEATH_KNIGHT: ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_deathknight_bloodpresence:30|t|r Use Blood Spec", GOSSIP_SENDER_MAIN, 27); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_deathknight_frostpresence:30|t|r Use Frost Spec", GOSSIP_SENDER_MAIN, 28); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, "|cff00ff00|TInterface\\icons\\spell_deathknight_unholypresence:30|t|r Use Unholy Spec", GOSSIP_SENDER_MAIN, 29); ++ } ++ break; ++ } ++ player->SEND_GOSSIP_MENU(60025, creature->GetGUID()); ++ return true; ++ } ++ ++ void EquipFullTemplateGear(Player* player, std::string& playerSpecStr) // Merge ++ { ++ if (sTemplateNpcMgr->CanEquipTemplate(player, playerSpecStr) == false) ++ { ++ player->GetSession()->SendAreaTriggerMessage("There's no templates for %s specialization yet.", playerSpecStr.c_str()); ++ return; ++ } ++ ++ // Don't let players to use Template feature while wearing some gear ++ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) ++ { ++ if (Item* haveItemEquipped = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ { ++ if (haveItemEquipped) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You need to remove all your equipped items in order to use this feature!"); ++ player->CLOSE_GOSSIP_MENU(); ++ return; ++ } ++ } ++ } ++ ++ // Don't let players to use Template feature after spending some talent points ++ if (player->GetFreeTalentPoints() < 71) ++ { ++ player->GetSession()->SendAreaTriggerMessage("You have already spent some talent points. You need to reset your talents first!"); ++ player->CLOSE_GOSSIP_MENU(); ++ return; ++ } ++ ++ sTemplateNpcMgr->LearnTemplateTalents(player); ++ sTemplateNpcMgr->LearnTemplateGlyphs(player); ++ sTemplateNpcMgr->EquipTemplateGear(player); ++ sTemplateNpcMgr->LearnPlateMailSpells(player); ++ ++ LearnWeaponSkills(player); ++ ++ player->GetSession()->SendAreaTriggerMessage("Successfuly equipped %s %s template!", playerSpecStr.c_str(), sTemplateNpcMgr->GetClassString(player).c_str()); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 /*uiSender*/, uint32 uiAction) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ ++ if (!player || !creature) ++ return true; ++ ++ switch (uiAction) ++ { ++ case 0: // Use Discipline Priest Spec ++ sTemplateNpcMgr->sTalentsSpec = "Discipline"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 1: // Use Holy Priest Spec ++ sTemplateNpcMgr->sTalentsSpec = "Holy"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 2: // Use Shadow Priest Spec ++ sTemplateNpcMgr->sTalentsSpec = "Shadow"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 3: // Use Holy Paladin Spec ++ sTemplateNpcMgr->sTalentsSpec = "Holy"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 4: // Use Protection Paladin Spec ++ sTemplateNpcMgr->sTalentsSpec = "Protection"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 5: // Use Retribution Paladin Spec ++ sTemplateNpcMgr->sTalentsSpec = "Retribution"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 6: // Use Fury Warrior Spec ++ sTemplateNpcMgr->sTalentsSpec = "Fury"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 7: // Use Arms Warrior Spec ++ sTemplateNpcMgr->sTalentsSpec = "Arms"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 8: // Use Protection Warrior Spec ++ sTemplateNpcMgr->sTalentsSpec = "Protection"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 9: // Use Arcane Mage Spec ++ sTemplateNpcMgr->sTalentsSpec = "Arcane"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 10: // Use Fire Mage Spec ++ sTemplateNpcMgr->sTalentsSpec = "Fire"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 11: // Use Frost Mage Spec ++ sTemplateNpcMgr->sTalentsSpec = "Frost"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 12: // Use Affliction Warlock Spec ++ sTemplateNpcMgr->sTalentsSpec = "Affliction"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 13: // Use Demonology Warlock Spec ++ sTemplateNpcMgr->sTalentsSpec = "Demonology"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 14: // Use Destruction Warlock Spec ++ sTemplateNpcMgr->sTalentsSpec = "Destruction"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 15: // Use Elemental Shaman Spec ++ sTemplateNpcMgr->sTalentsSpec = "Elemental"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 16: // Use Enhancement Shaman Spec ++ sTemplateNpcMgr->sTalentsSpec = "Enhancement"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 17: // Use Restoration Shaman Spec ++ sTemplateNpcMgr->sTalentsSpec = "Restoration"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 18: // Use Ballance Druid Spec ++ sTemplateNpcMgr->sTalentsSpec = "Ballance"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 19: // Use Feral Druid Spec ++ sTemplateNpcMgr->sTalentsSpec = "Feral"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 20: // Use Restoration Druid Spec ++ sTemplateNpcMgr->sTalentsSpec = "Restoration"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 21: // Use Marksmanship Hunter Spec ++ sTemplateNpcMgr->sTalentsSpec = "Marksmanship"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 22: // Use Beastmastery Hunter Spec ++ sTemplateNpcMgr->sTalentsSpec = "Beastmastery"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 23: // Use Survival Hunter Spec ++ sTemplateNpcMgr->sTalentsSpec = "Survival"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 24: // Use Assassination Rogue Spec ++ sTemplateNpcMgr->sTalentsSpec = "Assassination"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 25: // Use Combat Rogue Spec ++ sTemplateNpcMgr->sTalentsSpec = "Combat"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 26: // Use Subtlety Rogue Spec ++ sTemplateNpcMgr->sTalentsSpec = "Subtlety"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 27: // Use Blood DK Spec ++ sTemplateNpcMgr->sTalentsSpec = "Blood"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 28: // Use Frost DK Spec ++ sTemplateNpcMgr->sTalentsSpec = "Frost"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ ++ case 29: // Use Unholy DK Spec ++ sTemplateNpcMgr->sTalentsSpec = "Unholy"; ++ EquipFullTemplateGear(player, sTemplateNpcMgr->sTalentsSpec); ++ player->CLOSE_GOSSIP_MENU(); ++ break; ++ default: // Just in case ++ player->GetSession()->SendAreaTriggerMessage("Something went wrong in the code. Please contact the administrator."); ++ break; ++ } ++ player->UpdateSkillsForLevel(); ++ return true; ++ } ++}; ++ ++void AddSC_TemplateNPC() ++{ ++ new TemplateNPC_Commands(); ++ new TemplateNPC(); ++} +\ No newline at end of file +diff --git a/src/server/scripts/Custom/TemplateNPC.h b/src/server/scripts/Custom/TemplateNPC.h +new file mode 100644 +index 0000000..86ef307 +--- /dev/null ++++ b/src/server/scripts/Custom/TemplateNPC.h +@@ -0,0 +1,257 @@ ++#ifndef TALENT_FUNCTIONS_H ++#define TALENT_FUNCTIONS_H ++ ++#include "Define.h" ++ ++enum templateSpells ++{ ++ PLATE_MAIL = 750, ++ MAIL = 8737 ++}; ++ ++enum WeaponProficiencies ++{ ++ BLOCK = 107, ++ BOWS = 264, ++ CROSSBOWS = 5011, ++ DAGGERS = 1180, ++ FIST_WEAPONS = 15590, ++ GUNS = 266, ++ ONE_H_AXES = 196, ++ ONE_H_MACES = 198, ++ ONE_H_SWORDS = 201, ++ POLEARMS = 200, ++ SHOOT = 5019, ++ STAVES = 227, ++ TWO_H_AXES = 197, ++ TWO_H_MACES = 199, ++ TWO_H_SWORDS = 202, ++ WANDS = 5009, ++ THROW_WAR = 2567 ++}; ++ ++static void LearnWeaponSkills(Player* player) ++{ ++ WeaponProficiencies wepSkills[] = { ++ BLOCK, BOWS, CROSSBOWS, DAGGERS, FIST_WEAPONS, GUNS, ONE_H_AXES, ONE_H_MACES, ++ ONE_H_SWORDS, POLEARMS, SHOOT, STAVES, TWO_H_AXES, TWO_H_MACES, TWO_H_SWORDS, WANDS, THROW_WAR ++ }; ++ ++ uint32 size = 17; ++ ++ for (uint32 i = 0; i < size; ++i) ++ if (player->HasSpell(wepSkills[i])) ++ continue; ++ ++ switch (player->getClass()) ++ { ++ case CLASS_WARRIOR: ++ player->LearnSpell(THROW_WAR, false); ++ player->LearnSpell(TWO_H_SWORDS, false); ++ player->LearnSpell(TWO_H_MACES, false); ++ player->LearnSpell(TWO_H_AXES, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(POLEARMS, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ player->LearnSpell(GUNS, false); ++ player->LearnSpell(FIST_WEAPONS, false); ++ player->LearnSpell(DAGGERS, false); ++ player->LearnSpell(CROSSBOWS, false); ++ player->LearnSpell(BOWS, false); ++ player->LearnSpell(BLOCK, false); ++ break; ++ case CLASS_PRIEST: ++ player->LearnSpell(WANDS, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(SHOOT, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(DAGGERS, false); ++ break; ++ case CLASS_PALADIN: ++ player->LearnSpell(TWO_H_SWORDS, false); ++ player->LearnSpell(TWO_H_MACES, false); ++ player->LearnSpell(TWO_H_AXES, false); ++ player->LearnSpell(POLEARMS, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ player->LearnSpell(BLOCK, false); ++ break; ++ case CLASS_ROGUE: ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ player->LearnSpell(GUNS, false); ++ player->LearnSpell(FIST_WEAPONS, false); ++ player->LearnSpell(DAGGERS, false); ++ player->LearnSpell(CROSSBOWS, false); ++ player->LearnSpell(BOWS, false); ++ break; ++ case CLASS_DEATH_KNIGHT: ++ player->LearnSpell(TWO_H_SWORDS, false); ++ player->LearnSpell(TWO_H_MACES, false); ++ player->LearnSpell(TWO_H_AXES, false); ++ player->LearnSpell(POLEARMS, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ break; ++ case CLASS_MAGE: ++ player->LearnSpell(WANDS, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(SHOOT, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(DAGGERS, false); ++ break; ++ case CLASS_SHAMAN: ++ player->LearnSpell(TWO_H_MACES, false); ++ player->LearnSpell(TWO_H_AXES, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ player->LearnSpell(FIST_WEAPONS, false); ++ player->LearnSpell(DAGGERS, false); ++ player->LearnSpell(BLOCK, false); ++ break; ++ case CLASS_HUNTER: ++ player->LearnSpell(THROW_WAR, false); ++ player->LearnSpell(TWO_H_SWORDS, false); ++ player->LearnSpell(TWO_H_AXES, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(POLEARMS, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(ONE_H_AXES, false); ++ player->LearnSpell(GUNS, false); ++ player->LearnSpell(FIST_WEAPONS, false); ++ player->LearnSpell(DAGGERS, false); ++ player->LearnSpell(CROSSBOWS, false); ++ player->LearnSpell(BOWS, false); ++ break; ++ case CLASS_DRUID: ++ player->LearnSpell(TWO_H_MACES, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(POLEARMS, false); ++ player->LearnSpell(ONE_H_MACES, false); ++ player->LearnSpell(FIST_WEAPONS, false); ++ player->LearnSpell(DAGGERS, false); ++ break; ++ case CLASS_WARLOCK: ++ player->LearnSpell(WANDS, false); ++ player->LearnSpell(STAVES, false); ++ player->LearnSpell(SHOOT, false); ++ player->LearnSpell(ONE_H_SWORDS, false); ++ player->LearnSpell(DAGGERS, false); ++ break; ++ default: ++ break; ++ } ++} ++ ++struct TalentTemplate ++{ ++ std::string playerClass; ++ std::string playerSpec; ++ uint32 talentId; ++}; ++ ++struct GlyphTemplate ++{ ++ std::string playerClass; ++ std::string playerSpec; ++ uint8 slot; ++ uint32 glyph; ++}; ++ ++struct HumanGearTemplate ++{ ++ std::string playerClass; ++ std::string playerSpec; ++ uint8 pos; ++ uint32 itemEntry; ++ uint32 enchant; ++ uint32 socket1; ++ uint32 socket2; ++ uint32 socket3; ++ uint32 bonusEnchant; ++ uint32 prismaticEnchant; ++}; ++ ++struct AllianceGearTemplate ++{ ++ std::string playerClass; ++ std::string playerSpec; ++ uint8 pos; ++ uint32 itemEntry; ++ uint32 enchant; ++ uint32 socket1; ++ uint32 socket2; ++ uint32 socket3; ++ uint32 bonusEnchant; ++ uint32 prismaticEnchant; ++}; ++ ++struct HordeGearTemplate ++{ ++ std::string playerClass; ++ std::string playerSpec; ++ uint8 pos; ++ uint32 itemEntry; ++ uint32 enchant; ++ uint32 socket1; ++ uint32 socket2; ++ uint32 socket3; ++ uint32 bonusEnchant; ++ uint32 prismaticEnchant; ++}; ++ ++typedef std::vector HumanGearContainer; ++typedef std::vector AllianceGearContainer; ++typedef std::vector HordeGearContainer; ++ ++typedef std::vector TalentContainer; ++typedef std::vector GlyphContainer; ++ ++class sTemplateNPC ++{ ++public: ++ static sTemplateNPC* instance() ++ { ++ static sTemplateNPC* instance = new sTemplateNPC(); ++ return instance; ++ } ++ void LoadTalentsContainer(); ++ void LoadGlyphsContainer(); ++ ++ void LoadHumanGearContainer(); ++ void LoadAllianceGearContainer(); ++ void LoadHordeGearContainer(); ++ ++ void ApplyGlyph(Player* player, uint8 slot, uint32 glyphID); ++ void ApplyBonus(Player* player, Item* item, EnchantmentSlot slot, uint32 bonusEntry); ++ ++ bool OverwriteTemplate(Player* /*player*/, std::string& /*playerSpecStr*/); ++ void ExtractGearTemplateToDB(Player* /*player*/, std::string& /*playerSpecStr*/); ++ void ExtractTalentTemplateToDB(Player* /*player*/, std::string& /*playerSpecStr*/); ++ void ExtractGlyphsTemplateToDB(Player* /*player*/, std::string& /*playerSpecStr*/); ++ bool CanEquipTemplate(Player* /*player*/, std::string& /*playerSpecStr*/); ++ ++ std::string GetClassString(Player* /*player*/); ++ std::string sTalentsSpec; ++ ++ void LearnTemplateTalents(Player* /*player*/); ++ void LearnTemplateGlyphs(Player* /*player*/); ++ void EquipTemplateGear(Player* /*player*/); ++ ++ void LearnPlateMailSpells(Player* /*player*/); ++ ++ GlyphContainer m_GlyphContainer; ++ TalentContainer m_TalentContainer; ++ ++ HumanGearContainer m_HumanGearContainer; ++ AllianceGearContainer m_AllianceGearContainer; ++ HordeGearContainer m_HordeGearContainer; ++}; ++#define sTemplateNpcMgr sTemplateNPC::instance() ++#endif +\ No newline at end of file \ No newline at end of file diff --git a/arenaespectador.diff b/arenaespectador.diff new file mode 100644 index 0000000..cfb2417 --- /dev/null +++ b/arenaespectador.diff @@ -0,0 +1,1615 @@ +From ed9b7b2fb9333d19789afe4204ed176eecbb31f6 Mon Sep 17 00:00:00 2001 +From: Flameshot +Date: Fri, 19 Jun 2015 14:36:18 +0300 +Subject: Implemented Arena Spectator + +*Update code for last TrinityCore Commit +*Added 5 vs 5 Spectate +*Credits Flameshot/Lillecarl and for who had the idea of Arena Spectator +--- + src/server/game/Accounts/RBAC.h | 7 + + src/server/game/Battlegrounds/Battleground.cpp | 24 +- + src/server/game/Battlegrounds/Battleground.h | 11 + + src/server/game/Battlegrounds/BattlegroundMgr.h | 11 +- + src/server/game/Battlegrounds/SpectatorAddon.cpp | 221 +++++++ + src/server/game/Battlegrounds/SpectatorAddon.h | 96 +++ + src/server/game/Entities/GameObject/GameObject.cpp | 10 + + src/server/game/Entities/Player/Player.cpp | 131 ++++- + src/server/game/Entities/Player/Player.h | 16 +- + src/server/game/Entities/Unit/Unit.cpp | 13 + + src/server/game/Handlers/ChatHandler.cpp | 6 + + src/server/game/Maps/Map.cpp | 7 + + src/server/game/Miscellaneous/Language.h | 3 + + src/server/game/Scripting/ScriptLoader.cpp | 2 + + src/server/game/Spells/Spell.cpp | 4 + + src/server/scripts/Commands/cs_gm.cpp | 3 + + src/server/scripts/Custom/ArenaSpectator/README.md | 34 ++ + .../Custom/ArenaSpectator/arena_spectator.cpp | 642 +++++++++++++++++++++ + .../ArenaSpectator/sql/Arena Spectator NPC.sql | 10 + + .../ArenaSpectator/sql/rbac_linked_permissions.sql | 6 + + .../Custom/ArenaSpectator/sql/rbac_permissions.sql | 6 + + src/server/scripts/Custom/CMakeLists.txt | 3 +- + 22 files changed, 1249 insertions(+), 17 deletions(-) + create mode 100644 src/server/game/Battlegrounds/SpectatorAddon.cpp + create mode 100644 src/server/game/Battlegrounds/SpectatorAddon.h + create mode 100644 src/server/scripts/Custom/ArenaSpectator/README.md + create mode 100644 src/server/scripts/Custom/ArenaSpectator/arena_spectator.cpp + create mode 100644 src/server/scripts/Custom/ArenaSpectator/sql/Arena Spectator NPC.sql + create mode 100644 src/server/scripts/Custom/ArenaSpectator/sql/rbac_linked_permissions.sql + create mode 100644 src/server/scripts/Custom/ArenaSpectator/sql/rbac_permissions.sql + +diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h +index 339788d..3123723 100644 +--- a/src/server/game/Accounts/RBAC.h ++++ b/src/server/game/Accounts/RBAC.h +@@ -694,6 +694,13 @@ enum RBACPermissions + RBAC_PERM_COMMAND_INSTANCE_GET_BOSS_STATE = 796, + RBAC_PERM_COMMAND_PVPSTATS = 797, + RBAC_PERM_COMMAND_MODIFY_XP = 798, ++ ++ //Arena Spectator ++ RBAC_PERM_COMMAND_SPECTATE = 1003, ++ RBAC_PERM_COMMAND_SPECTATE_PLAYER = 1004, ++ RBAC_PERM_COMMAND_SPECTATE_VIEW = 1005, ++ RBAC_PERM_COMMAND_SPECTATE_RESET = 1006, ++ RBAC_PERM_COMMAND_SPECTATE_LEAVE = 1007, + + // custom permissions 1000+ + RBAC_PERM_MAX +diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp +index 3e9b68d..3dce38c 100644 +--- a/src/server/game/Battlegrounds/Battleground.cpp ++++ b/src/server/game/Battlegrounds/Battleground.cpp +@@ -1128,13 +1128,23 @@ void Battleground::EventPlayerLoggedOut(Player* player) + m_Players[guid].OfflineRemoveTime = sWorld->GetGameTime() + MAX_OFFLINE_TIME; + if (GetStatus() == STATUS_IN_PROGRESS) + { +- // drop flag and handle other cleanups +- RemovePlayer(player, guid, GetPlayerTeam(guid)); +- +- // 1 player is logging out, if it is the last, then end arena! +- if (isArena()) +- if (GetAlivePlayersCountByTeam(player->GetBGTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetBGTeam()))) +- EndBattleground(GetOtherTeam(player->GetBGTeam())); ++ if (!player->IsSpectator()) ++ { ++ // drop flag and handle other cleanups ++ RemovePlayer(player, guid, GetPlayerTeam(guid)); ++ // 1 player is logging out, if it is the last, then end arena! ++ if (isArena()) ++ if (GetAlivePlayersCountByTeam(player->GetTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetTeam()))) ++ EndBattleground(GetOtherTeam(player->GetTeam())); ++ } ++ } ++ ++ if (!player->IsSpectator()) ++ player->LeaveBattleground(); ++ else ++ { ++ player->TeleportToBGEntryPoint(); ++ RemoveSpectator(player->GetGUID()); + } + } + +diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h +index 562a20b..3cb081a 100644 +--- a/src/server/game/Battlegrounds/Battleground.h ++++ b/src/server/game/Battlegrounds/Battleground.h +@@ -26,6 +26,7 @@ + #include "WorldPacket.h" + #include "Object.h" + #include "GameObject.h" ++#include "SpectatorAddon.h" + + class Creature; + class GameObject; +@@ -299,6 +300,13 @@ class Battleground + uint32 GetInvitedCount(uint32 team) const { return (team == ALLIANCE) ? m_InvitedAlliance : m_InvitedHorde; } + bool HasFreeSlots() const; + uint32 GetFreeSlotsForTeam(uint32 Team) const; ++ ++ /* Arena Spectator */ ++ typedef std::set SpectatorList; ++ void AddSpectator(uint32 playerId) { m_Spectators.insert(playerId); } ++ void RemoveSpectator(uint32 playerId) { m_Spectators.erase(playerId); } ++ bool HaveSpectators() { return (m_Spectators.size() > 0); } ++ /* Arena Spectator */ + + bool isArena() const { return m_IsArena; } + bool isBattleground() const { return !m_IsArena; } +@@ -577,6 +585,9 @@ class Battleground + // Raid Group + Group* m_BgRaids[BG_TEAMS_COUNT]; // 0 - alliance, 1 - horde + ++ // Arena Spectator ++ SpectatorList m_Spectators; ++ + // Players count by team + uint32 m_PlayersCount[BG_TEAMS_COUNT]; + +diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.h b/src/server/game/Battlegrounds/BattlegroundMgr.h +index f732f43..12143cb 100644 +--- a/src/server/game/Battlegrounds/BattlegroundMgr.h ++++ b/src/server/game/Battlegrounds/BattlegroundMgr.h +@@ -109,6 +109,8 @@ class BattlegroundMgr + + bool isArenaTesting() const { return m_ArenaTesting; } + bool isTesting() const { return m_Testing; } ++ ++ bool IsArenaType(BattlegroundTypeId bgTypeId); + + static BattlegroundQueueTypeId BGQueueTypeId(BattlegroundTypeId bgTypeId, uint8 arenaType); + static BattlegroundTypeId BGTemplateId(BattlegroundQueueTypeId bgQueueTypeId); +@@ -117,6 +119,14 @@ class BattlegroundMgr + static HolidayIds BGTypeToWeekendHolidayId(BattlegroundTypeId bgTypeId); + static BattlegroundTypeId WeekendHolidayIdToBGType(HolidayIds holiday); + static bool IsBGWeekend(BattlegroundTypeId bgTypeId); ++ BattlegroundData* GetAllBattlegroundsWithTypeId(BattlegroundTypeId bgTypeId) ++ { ++ BattlegroundDataContainer::iterator it = bgDataStore.find(bgTypeId); ++ if (it == bgDataStore.end()) ++ return NULL; ++ ++ return &it->second; ++ } + + uint32 GetMaxRatingDifference() const; + uint32 GetRatingDiscardTimer() const; +@@ -134,7 +144,6 @@ class BattlegroundMgr + private: + bool CreateBattleground(BattlegroundTemplate const* bgTemplate); + uint32 CreateClientVisibleInstanceId(BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id); +- static bool IsArenaType(BattlegroundTypeId bgTypeId); + BattlegroundTypeId GetRandomBG(BattlegroundTypeId id); + + typedef std::map BattlegroundDataContainer; +diff --git a/src/server/game/Battlegrounds/SpectatorAddon.cpp b/src/server/game/Battlegrounds/SpectatorAddon.cpp +new file mode 100644 +index 0000000..39ce7dd +--- /dev/null ++++ b/src/server/game/Battlegrounds/SpectatorAddon.cpp +@@ -0,0 +1,221 @@ ++/* ++* Copyright (C) 2008-2012 TrinityCore ++* ++* 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, see . ++*/ ++ ++#include "ScriptPCH.h" ++#include "Player.h" ++#include "Item.h" ++#include "SpellInfo.h" ++#include "SpectatorAddon.h" ++ ++SpectatorAddonMsg::SpectatorAddonMsg() ++{ ++ for (uint8 i = 0; i < SPECTATOR_PREFIX_COUNT; ++i) ++ prefixFlags[i] = false; ++ ++ player = ""; ++ target = ""; ++ isAlive = false; ++ pClass = CLASS_WARRIOR; ++ maxHP = 0; ++ maxPower = 0; ++ currHP = 0; ++ currPower = 0; ++ powerType = POWER_MANA; ++ spellId = 0; ++ castTime = 0; ++ team = ALLIANCE; ++} ++ ++bool SpectatorAddonMsg::CanSandAura(uint32 auraID) ++{ ++ const SpellInfo *spell = sSpellMgr->GetSpellInfo(auraID); ++ if (!spell) ++ return false; ++ ++ if (spell->SpellIconID == 1) ++ return false; ++ ++ return true; ++} ++ ++void SpectatorAddonMsg::CreateAura(uint32 _caster, uint32 _spellId, bool _isDebuff, uint8 _type, int32 _duration, int32 _expire, uint16 _stack, bool _isRemove) ++{ ++ if (!CanSandAura(_spellId)) ++ return; ++ ++ aCaster = _caster; ++ aSpellId = _spellId; ++ aIsDebuff = _isDebuff; ++ aType = _type; ++ aDuration = _duration; ++ aExpire = _expire; ++ aStack = _stack; ++ aRemove = _isRemove; ++ EnableFlag(SPECTATOR_PREFIX_AURA); ++} ++ ++std::string SpectatorAddonMsg::GetMsgData() ++{ ++ std::string addonData = ""; ++ ++ if (!isFilledIn(SPECTATOR_PREFIX_PLAYER)) ++ { ++ TC_LOG_INFO("battleground", "SPECTATOR ADDON: player is not filled in."); ++ return addonData; ++ } ++ ++ std::string msg = ""; ++ for (uint8 i = 0; i < SPECTATOR_PREFIX_COUNT; ++i) ++ if (isFilledIn(i)) ++ { ++ switch (i) ++ { ++ case SPECTATOR_PREFIX_PLAYER: ++ msg += player + ";"; ++ break; ++ case SPECTATOR_PREFIX_TARGET: ++ msg += "TRG=" + target + ";"; ++ break; ++ case SPECTATOR_PREFIX_TEAM: ++ { ++ char buffer[20]; ++ sprintf(buffer, "TEM=%i;", (uint16)team); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_STATUS: ++ { ++ char buffer[20]; ++ sprintf(buffer, "STA=%d;", isAlive); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_CLASS: ++ { ++ char buffer[20]; ++ sprintf(buffer, "CLA=%i;", (int)pClass); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_MAXHP: ++ { ++ char buffer[30]; ++ sprintf(buffer, "MHP=%i;", maxHP); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_CURHP: ++ { ++ char buffer[30]; ++ sprintf(buffer, "CHP=%i;", currHP); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_MAXPOWER: ++ { ++ char buffer[30]; ++ sprintf(buffer, "MPW=%i;", maxPower); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_CURPOWER: ++ { ++ char buffer[30]; ++ sprintf(buffer, "CPW=%i;", currPower); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_POWERTYPE: ++ { ++ char buffer[20]; ++ sprintf(buffer, "PWT=%i;", (uint8)powerType); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_SPELL: ++ { ++ char buffer[80]; ++ sprintf(buffer, "SPE=%i,%i;", spellId, castTime); ++ msg += buffer; ++ break; ++ } ++ case SPECTATOR_PREFIX_AURA: ++ { ++ char buffer[300]; ++ sprintf(buffer, "AUR=%i,%i,%i,%i,%i,%i,%i,0x%X;", aRemove, aStack, ++ aExpire, aDuration, ++ aSpellId, aType, ++ aIsDebuff, aCaster); ++ msg += buffer; ++ break; ++ } ++ } ++ } ++ ++ if (msg != "") ++ addonData = "ARENASPEC " + msg; ++ ++ return addonData; ++} ++ ++bool SpectatorAddonMsg::SendPacket(ObjectGuid receiver) ++{ ++ std::string addonData = GetMsgData(); ++ if (addonData == "") ++ return false; ++ ++ Player* rPlayer = ObjectAccessor::FindPlayer(receiver); ++ if (!rPlayer) ++ return false; ++ ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ data << uint8(CHAT_MSG_WHISPER); ++ data << uint32(LANG_ADDON); ++ data << uint64(0); ++ data << uint32(LANG_ADDON); //language 2.1.0 ? ++ data << uint64(0); ++ data << uint32(addonData.length() + 1); ++ data << addonData; ++ data << uint8(CHAT_TAG_NONE); ++ rPlayer->GetSession()->SendPacket(&data); ++ ++ return true; ++} ++ ++bool SpectatorAddonMsg::SendPacket(SpectatorAddonMsg msg, ObjectGuid receiver) ++{ ++ std::string addonData = msg.GetMsgData(); ++ if (addonData == "") ++ return false; ++ ++ Player* rPlayer = ObjectAccessor::FindPlayer(receiver); ++ if (!rPlayer) ++ return false; ++ ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ data << uint8(CHAT_MSG_WHISPER); ++ data << uint32(LANG_ADDON); ++ data << uint64(0); ++ data << uint32(LANG_ADDON); //language 2.1.0 ? ++ data << uint64(0); ++ data << uint32(addonData.length() + 1); ++ data << addonData; ++ data << uint8(CHAT_TAG_NONE); ++ rPlayer->GetSession()->SendPacket(&data); ++ ++ return true; ++} +\ No newline at end of file +diff --git a/src/server/game/Battlegrounds/SpectatorAddon.h b/src/server/game/Battlegrounds/SpectatorAddon.h +new file mode 100644 +index 0000000..21f44c4 +--- /dev/null ++++ b/src/server/game/Battlegrounds/SpectatorAddon.h +@@ -0,0 +1,96 @@ ++/* ++ * Copyright (C) 2008-2012 TrinityCore ++ * 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, see . ++ */ ++ ++#define SPECTATOR_ADDON_SPELL_INTERUPTED 99999 // specific addons ++#define SPECTATOR_ADDON_SPELL_CANCELED 99998 // numbers =\ ++ ++ ++enum SpectatorPrefix { ++ SPECTATOR_PREFIX_PLAYER, ++ SPECTATOR_PREFIX_STATUS, ++ SPECTATOR_PREFIX_MAXHP, ++ SPECTATOR_PREFIX_CURHP, ++ SPECTATOR_PREFIX_MAXPOWER, ++ SPECTATOR_PREFIX_CURPOWER, ++ SPECTATOR_PREFIX_POWERTYPE, ++ SPECTATOR_PREFIX_TARGET, ++ SPECTATOR_PREFIX_CLASS, ++ SPECTATOR_PREFIX_TEAM, ++ SPECTATOR_PREFIX_SPELL, ++ SPECTATOR_PREFIX_AURA, ++ SPECTATOR_PREFIX_COUNT // must be at the end of list ++}; ++ ++class SpectatorAddonMsg { ++ public: ++ SpectatorAddonMsg(); ++ ++ void SetPlayer(std::string _player) { player = _player; EnableFlag(SPECTATOR_PREFIX_PLAYER); } ++ void SetStatus(bool _isAlive) { isAlive = _isAlive; EnableFlag(SPECTATOR_PREFIX_STATUS); } ++ void SetClass(uint8 _class) { pClass = _class; EnableFlag(SPECTATOR_PREFIX_CLASS); } ++ void SetTarget(std::string _target) { target = _target; EnableFlag(SPECTATOR_PREFIX_TARGET); } ++ void SetTeam(uint32 _team) { team = _team; EnableFlag(SPECTATOR_PREFIX_TEAM); } ++ ++ void SetMaxHP(uint16 hp) { maxHP = hp; EnableFlag(SPECTATOR_PREFIX_MAXHP); } ++ void SetCurrentHP(uint16 hp) { currHP = hp; EnableFlag(SPECTATOR_PREFIX_CURHP); } ++ void SetMaxPower(uint16 power) { maxPower = power; EnableFlag(SPECTATOR_PREFIX_MAXPOWER); } ++ void SetCurrentPower(uint16 power) { currPower = power; EnableFlag(SPECTATOR_PREFIX_CURPOWER); } ++ void SetPowerType(Powers power) { powerType = power; EnableFlag(SPECTATOR_PREFIX_POWERTYPE); } ++ ++ void CastSpell(uint32 _spellId, uint32 _castTime) { spellId = _spellId; castTime = _castTime; EnableFlag(SPECTATOR_PREFIX_SPELL); } ++ void CreateAura(uint32 _caster, uint32 _spellId, bool _isDebuff, uint8 _type, int32 _duration, int32 _expire, uint16 _stack, bool _isRemove); ++ ++ static bool SendPacket(SpectatorAddonMsg msg, ObjectGuid receiver); ++ bool SendPacket(ObjectGuid receiver); ++ ++ std::string GetMsgData(); ++ ++ bool isFilledIn(uint8 prefix) { return prefixFlags[prefix]; } ++ ++ static bool CanSandAura(uint32 auraID); ++ private: ++ ++ void EnableFlag(uint8 prefix) { prefixFlags[prefix] = true; } ++ std::string player; ++ bool isAlive; ++ std::string target; ++ uint8 pClass; ++ ++ uint16 maxHP; ++ uint16 maxPower; ++ uint16 currHP; ++ uint16 currPower; ++ Powers powerType; ++ ++ uint32 spellId; ++ uint32 castTime; ++ ++ uint32 team; ++ ++ // aura data ++ uint32 aCaster; ++ uint32 aSpellId; ++ bool aIsDebuff; ++ uint8 aType; ++ int32 aDuration; ++ int32 aExpire; ++ uint16 aStack; ++ bool aRemove; ++ ++ bool prefixFlags[SPECTATOR_PREFIX_COUNT]; ++}; +diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp +index a4b140b..c3833ed 100644 +--- a/src/server/game/Entities/GameObject/GameObject.cpp ++++ b/src/server/game/Entities/GameObject/GameObject.cpp +@@ -564,6 +564,11 @@ void GameObject::Update(uint32 diff) + } + else if (Unit* target = ObjectAccessor::GetUnit(*this, m_lootStateUnitGUID)) + { ++ // If player is spectator do not activate. ++ if (Player *tmpPlayer = target->ToPlayer()) ++ if (tmpPlayer->IsSpectator()) ++ return; ++ + // Some traps do not have a spell but should be triggered + if (goInfo->trap.spellId) + CastSpell(target, goInfo->trap.spellId); +@@ -1765,6 +1770,11 @@ void GameObject::Use(Unit* user) + + void GameObject::CastSpell(Unit* target, uint32 spellId, bool triggered /*= true*/) + { ++ if (target) ++ if (Player *tmpPlayer = target->ToPlayer()) ++ if (tmpPlayer->IsSpectator()) ++ return; ++ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return; +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 275dbf9..157d11c 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -901,8 +901,13 @@ Player::Player(WorldSession* session): Unit(true) + m_MonthlyQuestChanged = false; + + m_SeasonalQuestChanged = false; +- +- SetPendingBind(0, 0); ++ ++ // Arena Spectator ++ spectatorFlag = false; ++ spectateCanceled = false; ++ spectateFrom = NULL; ++ ++ SetPendingBind(0, 0); + + _activeCheats = CHEAT_NONE; + m_achievementMgr = new AchievementMgr(this); +@@ -2395,7 +2400,16 @@ bool Player::TeleportToBGEntryPoint() + ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE); + ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE); + ScheduleDelayedOperation(DELAYED_BG_GROUP_RESTORE); +- return TeleportTo(m_bgData.joinPos); ++ Battleground *oldBg = GetBattleground(); ++ bool result = TeleportTo(m_bgData.joinPos); ++ ++ if (IsSpectator() && result) ++ { ++ SetSpectate(false); ++ if (oldBg) ++ oldBg->RemoveSpectator(GetGUID()); ++ } ++ return result; + } + + void Player::ProcessDelayedOperations() +@@ -24100,6 +24114,15 @@ void Player::SetViewpoint(WorldObject* target, bool apply) + { + if (apply) + { ++ if (target->ToPlayer() == this) ++ return; ++ ++ //remove Viewpoint if already have ++ if (IsSpectator() && spectateFrom) ++ { ++ SetViewpoint(spectateFrom, false); ++ spectateFrom = NULL; ++ } + TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player %s create seer %u (TypeId: %u).", GetName().c_str(), target->GetEntry(), target->GetTypeId()); + + if (!AddGuidValue(PLAYER_FARSIGHT, target->GetGUID())) +@@ -24111,11 +24134,19 @@ void Player::SetViewpoint(WorldObject* target, bool apply) + // farsight dynobj or puppet may be very far away + UpdateVisibilityOf(target); + +- if (target->isType(TYPEMASK_UNIT) && !GetVehicle()) +- ((Unit*)target)->AddPlayerToVision(this); +- } ++ if (target->isType(TYPEMASK_UNIT) && !GetVehicle()) ++ { ++ if (IsSpectator()) ++ spectateFrom = (Unit*)target; ++ ++ ((Unit*)target)->AddPlayerToVision(this); ++ } ++ } + else + { ++ if (IsSpectator() && !spectateFrom) ++ return; ++ + TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player %s remove seer", GetName().c_str()); + + if (!RemoveGuidValue(PLAYER_FARSIGHT, target->GetGUID())) +@@ -24129,6 +24160,9 @@ void Player::SetViewpoint(WorldObject* target, bool apply) + + //must immediately set seer back otherwise may crash + m_seer = this; ++ ++ if (IsSpectator()) ++ spectateFrom = NULL; + + //WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0); + //GetSession()->SendPacket(&data); +@@ -26557,3 +26591,88 @@ bool Player::ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 ha + + return true; + } ++ ++void Player::SetSelection(ObjectGuid guid) ++{ ++ uint32 m_curSelection = guid; ++ SetUInt64Value(UNIT_FIELD_TARGET, guid); ++} ++ ++void Player::SetSpectate(bool on) ++{ ++ if (on) ++ { ++ SetSpeed(MOVE_RUN, 5.0); ++ spectatorFlag = true; ++ ++ m_ExtraFlags |= PLAYER_EXTRA_GM_ON; ++ setFaction(35); ++ ++ if (Pet* pet = GetPet()) ++ { ++ RemovePet(pet, PET_SAVE_AS_CURRENT); ++ } ++ UnsummonPetTemporaryIfAny(); ++ ++ RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); ++ ResetContestedPvP(); ++ ++ getHostileRefManager().setOnlineOfflineState(false); ++ CombatStopWithPets(); ++ ++ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_ADMINISTRATOR); ++ } ++ else ++ { ++ uint32 newPhase = 0; ++ AuraEffectList const& phases = GetAuraEffectsByType(SPELL_AURA_PHASE); ++ if (!phases.empty()) ++ for (AuraEffectList::const_iterator itr = phases.begin(); itr != phases.end(); ++itr) ++ newPhase |= (*itr)->GetMiscValue(); ++ ++ if (!newPhase) ++ newPhase = PHASEMASK_NORMAL; ++ ++ SetPhaseMask(newPhase, false); ++ ++ m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON; ++ setFactionForRace(getRace()); ++ RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM); ++ RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS); ++ ++ if (spectateFrom) ++ SetViewpoint(spectateFrom, false); ++ ++ // restore FFA PvP Server state ++ if (sWorld->IsFFAPvPRealm()) ++ SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); ++ ++ // restore FFA PvP area state, remove not allowed for GM mounts ++ UpdateArea(m_areaUpdateId); ++ ++ getHostileRefManager().setOnlineOfflineState(true); ++ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); ++ spectateCanceled = false; ++ spectatorFlag = false; ++ RestoreDisplayId(); ++ UpdateSpeed(MOVE_RUN, true); ++ } ++ UpdateObjectVisibility(); ++} ++ ++bool Player::HaveSpectators() ++{ ++ if (IsSpectator()) ++ return false; ++ ++ if (Battleground *bg = GetBattleground()) ++ if (bg->isArena()) ++ { ++ if (bg->GetStatus() != STATUS_IN_PROGRESS) ++ return false; ++ ++ return bg->HaveSpectators(); ++ } ++ ++ return false; ++} +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index 98b9d8a..50de4e7 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -1155,7 +1155,7 @@ class Player : public Unit, public GridObject + // mount_id can be used in scripting calls + bool isAcceptWhispers() const { return (m_ExtraFlags & PLAYER_EXTRA_ACCEPT_WHISPERS) != 0; } + void SetAcceptWhispers(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_ACCEPT_WHISPERS; else m_ExtraFlags &= ~PLAYER_EXTRA_ACCEPT_WHISPERS; } +- bool IsGameMaster() const { return (m_ExtraFlags & PLAYER_EXTRA_GM_ON) != 0; } ++ bool IsGameMaster() const { return (m_ExtraFlags & PLAYER_EXTRA_GM_ON); } + void SetGameMaster(bool on); + bool isGMChat() const { return (m_ExtraFlags & PLAYER_EXTRA_GM_CHAT) != 0; } + void SetGMChat(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_CHAT; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_CHAT; } +@@ -1166,6 +1166,14 @@ class Player : public Unit, public GridObject + bool Has310Flyer(bool checkAllSpells, uint32 excludeSpellId = 0); + void SetHas310Flyer(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_HAS_310_FLYER; else m_ExtraFlags &= ~PLAYER_EXTRA_HAS_310_FLYER; } + void SetPvPDeath(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_PVP_DEATH; else m_ExtraFlags &= ~PLAYER_EXTRA_PVP_DEATH; } ++ ++ // Arena Spectator ++ bool HaveSpectators(); ++ bool isSpectateCanceled() { return spectateCanceled; } ++ void CancelSpectate() { spectateCanceled = true; } ++ Unit* getSpectateFrom() { return spectateFrom; } ++ bool IsSpectator() const { return spectatorFlag; } ++ void SetSpectate(bool on); + + void GiveXP(uint32 xp, Unit* victim, float group_rate=1.0f); + void GiveLevel(uint8 level); +@@ -1540,7 +1548,7 @@ class Player : public Unit, public GridObject + Player* GetSelectedPlayer() const; + + void SetTarget(ObjectGuid /*guid*/) override { } /// Used for serverside target changes, does not apply to players +- void SetSelection(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_TARGET, guid); } ++ void SetSelection(ObjectGuid guid); + + uint8 GetComboPoints() const { return m_comboPoints; } + ObjectGuid GetComboTarget() const { return m_comboTarget; } +@@ -2640,6 +2648,10 @@ class Player : public Unit, public GridObject + InstanceTimeMap _instanceResetTimes; + uint32 _pendingBindId; + uint32 _pendingBindTimer; ++ ++ bool spectatorFlag; ++ bool spectateCanceled; ++ Unit *spectateFrom; + + uint32 _activeCheats; + }; +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 9b0da57..fed1ef2 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -268,6 +268,19 @@ Unit::~Unit() + m_currentSpells[i]->SetReferencedFromCurrent(false); + m_currentSpells[i] = NULL; + } ++ ++ // remove view point for spectator ++ if (!m_sharedVision.empty()) ++ { ++ for (SharedVisionList::iterator itr = m_sharedVision.begin(); itr != m_sharedVision.end(); ++itr) ++ if ((*itr)->IsSpectator() && (*itr)->getSpectateFrom()) ++ { ++ (*itr)->SetViewpoint((*itr)->getSpectateFrom(), false); ++ if (m_sharedVision.empty()) ++ break; ++ --itr; ++ } ++ } + + _DeleteRemovedAuras(); + +diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp +index 2acbaba..60aef83 100644 +--- a/src/server/game/Handlers/ChatHandler.cpp ++++ b/src/server/game/Handlers/ChatHandler.cpp +@@ -551,6 +551,12 @@ void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData) + recvData >> text_emote; + recvData >> emoteNum; + recvData >> guid; ++ ++ if (GetPlayer()->IsSpectator()) ++ { ++ SendNotification(LANG_SPEC_CAN_NOT_CHAT); ++ return; ++ } + + sScriptMgr->OnPlayerTextEmote(GetPlayer(), text_emote, emoteNum, guid); + +diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp +index c1a4624..71f5ea0 100644 +--- a/src/server/game/Maps/Map.cpp ++++ b/src/server/game/Maps/Map.cpp +@@ -3267,6 +3267,13 @@ bool BattlegroundMap::AddPlayerToMap(Player* player) + + void BattlegroundMap::RemovePlayerFromMap(Player* player, bool remove) + { ++ if (player && player->IsSpectator() && !player->isSpectateCanceled()) ++ { ++ if (GetBG()) ++ GetBG()->RemoveSpectator(player->GetGUID()); ++ player->SetSpectate(false); ++ } ++ + TC_LOG_DEBUG("maps", "MAP: Removing player '%s' from bg '%u' of map '%s' before relocating to another map", player->GetName().c_str(), GetInstanceId(), GetMapName()); + Map::RemovePlayerFromMap(player, remove); + } +diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h +index 9bae8fb..dcf9dea 100644 +--- a/src/server/game/Miscellaneous/Language.h ++++ b/src/server/game/Miscellaneous/Language.h +@@ -853,6 +853,9 @@ enum TrinityStrings + LANG_ACCOUNT_SEC_TYPE = 880, + LANG_RBAC_EMAIL_REQUIRED = 881, + // Room for in-game strings 882-999 not used ++ ++ // Arena Spectator ++ LANG_SPEC_CAN_NOT_CHAT = 900, + + // Level 4 (CLI only commands) + LANG_COMMAND_EXIT = 1000, +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index f0ee013..724df1c 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1417,6 +1417,7 @@ void AddBattlegroundScripts() + + #ifdef SCRIPTS + /* This is where custom scripts' loading functions should be declared. */ ++void AddSC_arena_spectator_script(); + + #endif + +@@ -1424,6 +1425,7 @@ void AddCustomScripts() + { + #ifdef SCRIPTS + /* This is where custom scripts should be added. */ ++ AddSC_arena_spectator_script(); + + #endif + } +diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp +index ff7c6a8..8eccc60 100644 +--- a/src/server/game/Spells/Spell.cpp ++++ b/src/server/game/Spells/Spell.cpp +@@ -4661,6 +4661,10 @@ SpellCastResult Spell::CheckCast(bool strict) + m_caster->GetMap()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ())) + return SPELL_FAILED_ONLY_INDOORS; + } ++ ++ if (Player *tmpPlayer = m_caster->ToPlayer()) ++ if (tmpPlayer->IsSpectator()) ++ return SPELL_FAILED_SPELL_UNAVAILABLE; + + // only check at first call, Stealth auras are already removed at second call + // for now, ignore triggered spells +diff --git a/src/server/scripts/Commands/cs_gm.cpp b/src/server/scripts/Commands/cs_gm.cpp +index 94e974b..24e48f7 100644 +--- a/src/server/scripts/Commands/cs_gm.cpp ++++ b/src/server/scripts/Commands/cs_gm.cpp +@@ -133,6 +133,9 @@ public: + itrSec <= AccountTypes(sWorld->getIntConfig(CONFIG_GM_LEVEL_IN_GM_LIST)))) && + (!handler->GetSession() || itr->second->IsVisibleGloballyFor(handler->GetSession()->GetPlayer()))) + { ++ if (itr->second->IsSpectator()) ++ continue; // don't show spectators, they're not really gms ++ + if (first) + { + first = false; +diff --git a/src/server/scripts/Custom/ArenaSpectator/README.md b/src/server/scripts/Custom/ArenaSpectator/README.md +new file mode 100644 +index 0000000..fdc42f0 +--- /dev/null ++++ b/src/server/scripts/Custom/ArenaSpectator/README.md +@@ -0,0 +1,34 @@ ++#Arena Spectator ++ ++####About ++Arena Spectator gives you the ++oportunity to spectate players when ++they are in arena.You can spectate a ++2vs2 / 3vs3 / 5vs5 / Specific Player arena. ++ ++ ++####Installation ++ ++Available as: ++- Direct merge: https://github.com/Flameshot/TrinityCore/tree/Arena-Spectator ++- Diff: ++- Patch: ++ ++Using direct merge: ++- open git bash to source location ++- do `git remote add flameshot https://github.com/Flameshot/TrinityCore.git` ++- do `git pull flameshot Arena-Spectator` ++- use cmake and compile ++ ++Using diff: ++- download the diff by __right clicking__ the link and select __Save link as__ ++- place the downloaded `arena-spectator.diff` to the source root folder ++- open git bash to source location ++- do `git apply arena-spectator.diff` ++- use cmake and compile ++ ++After compiling: ++- Navigate to `\src\server\scripts\Custom\ArenaSpectator\sql\` ++- Run `Arena Spectator NPC.sql` to your world database ++- Run `rbac_linked_permissions.sql` to your auth database ++- Run `rbac_permissions.sql` to your auth database +diff --git a/src/server/scripts/Custom/ArenaSpectator/arena_spectator.cpp b/src/server/scripts/Custom/ArenaSpectator/arena_spectator.cpp +new file mode 100644 +index 0000000..38aa52b +--- /dev/null ++++ b/src/server/scripts/Custom/ArenaSpectator/arena_spectator.cpp +@@ -0,0 +1,642 @@ ++/* ++* Copyright (C) 2008-2012 TrinityCore ++* Copyright (C) 2006-2009 ScriptDev2 ++* ++* 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, see . ++*/ ++ ++/* ScriptData ++Name: Arena Spectator ++Comment: Script allow spectate arena games ++Category: Custom Script ++Credits: Flameshot/Lillecarl ++EndScriptData */ ++ ++#include "ScriptPCH.h" ++#include "Chat.h" ++#include "ArenaTeamMgr.h" ++#include "BattlegroundMgr.h" ++#include "WorldSession.h" ++#include "Player.h" ++#include "ArenaTeam.h" ++#include "Battleground.h" ++#include "BattlegroundMgr.h" ++#include "CreatureTextMgr.h" ++#include "Config.h" ++ ++int8 UsingGossip; ++ ++class arena_spectator_commands : public CommandScript ++{ ++public: ++ arena_spectator_commands() : CommandScript("arena_spectator_commands") { } ++ ++ static bool HandleSpectateCommand(ChatHandler* handler, char const* args) ++ { ++ Player* target; ++ ObjectGuid target_guid; ++ std::string target_name; ++ if (!handler->extractPlayerTarget((char*)args, &target, &target_guid, &target_name)) ++ return false; ++ ++ Player* player = handler->GetSession()->GetPlayer(); ++ if (target == player || target_guid == player->GetGUID()) ++ { ++ handler->PSendSysMessage("You can't spectate yourself."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->IsInCombat()) ++ { ++ handler->PSendSysMessage("You are in combat."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (!target) ++ { ++ handler->PSendSysMessage("Target is not online or does not exist."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->GetPet()) ++ { ++ handler->PSendSysMessage("You must hide your pet."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->GetMap()->IsBattlegroundOrArena() && !player->IsSpectator()) ++ { ++ handler->PSendSysMessage("You are already in a battleground or arena."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Map* cMap = target->GetMap(); ++ if (!cMap->IsBattleArena()) ++ { ++ handler->PSendSysMessage("Player is not in an Arena match."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->GetMap()->IsBattleground()) ++ { ++ handler->PSendSysMessage("You can't do that while in a battleground."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (target->HasAura(32728) || target->HasAura(32727)) ++ { ++ handler->PSendSysMessage("You can't do that. The Arena match didn't start yet."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (target->IsSpectator()) ++ { ++ handler->PSendSysMessage("You can't do that. Your target is a spectator."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->IsMounted()) ++ { ++ handler->PSendSysMessage("Cannot Spectate while mounted."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ // all's well, set bg id ++ // when porting out from the bg, it will be reset to 0 ++ player->SetBattlegroundId(target->GetBattlegroundId(), target->GetBattlegroundTypeId()); ++ // remember current position as entry point for return at bg end teleportation ++ if (!player->GetMap()->IsBattlegroundOrArena()) ++ player->SetBattlegroundEntryPoint(); ++ ++ // stop flight if need ++ if (player->IsInFlight()) ++ { ++ player->GetMotionMaster()->MovementExpired(); ++ player->CleanupAfterTaxiFlight(); ++ } ++ // save only in non-flight case ++ else ++ player->SaveRecallPosition(); ++ ++ // search for two teams ++ Battleground *bGround = target->GetBattleground(); ++ if (bGround->isRated()) ++ { ++ uint32 slot = bGround->GetArenaType() - 2; ++ if (bGround->GetArenaType() > 3) ++ slot = 2; ++ uint32 firstTeamID = target->GetArenaTeamId(slot); ++ uint32 secondTeamID = 0; ++ Player *firstTeamMember = target; ++ Player *secondTeamMember = NULL; ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = bGround->GetPlayers().begin(); itr != bGround->GetPlayers().end(); ++itr) ++ if (Player* tmpPlayer = ObjectAccessor::FindPlayer(itr->first)) ++ { ++ if (tmpPlayer->IsSpectator()) ++ continue; ++ ++ uint32 tmpID = tmpPlayer->GetArenaTeamId(slot); ++ if (tmpID != firstTeamID && tmpID > 0) ++ { ++ secondTeamID = tmpID; ++ secondTeamMember = tmpPlayer; ++ break; ++ } ++ } ++ ++ if (firstTeamID > 0 && secondTeamID > 0 && secondTeamMember) ++ { ++ ArenaTeam *firstTeam = sArenaTeamMgr->GetArenaTeamById(firstTeamID); ++ ArenaTeam *secondTeam = sArenaTeamMgr->GetArenaTeamById(secondTeamID); ++ if (firstTeam && secondTeam) ++ { ++ handler->PSendSysMessage("You entered a Rated Arena."); ++ handler->PSendSysMessage("Teams:"); ++ handler->PSendSysMessage("|cFFffffff%s|r vs |cFFffffff%s|r", firstTeam->GetName().c_str(), secondTeam->GetName().c_str()); ++ handler->PSendSysMessage("|cFFffffff%u(%u)|r -- |cFFffffff%u(%u)|r", firstTeam->GetRating(), firstTeam->GetAverageMMR(firstTeamMember->GetGroup()), ++ secondTeam->GetRating(), secondTeam->GetAverageMMR(secondTeamMember->GetGroup())); ++ } ++ } ++ } ++ ++ // to point to see at target with same orientation ++ float x, y, z; ++ target->GetContactPoint(player, x, y, z); ++ ++ player->TeleportTo(target->GetMapId(), x, y, z, player->GetAngle(target), TELE_TO_GM_MODE); ++ player->SetPhaseMask(target->GetPhaseMask(), true); ++ player->SetSpectate(true); ++ target->GetBattleground()->AddSpectator(player->GetGUID()); ++ ++ return true; ++ } ++ ++ static bool HandleSpectateCancelCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ ++ if (!player->IsSpectator()) ++ { ++ handler->PSendSysMessage("You are not a spectator."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ player->GetBattleground()->RemoveSpectator(player->GetGUID()); ++ player->CancelSpectate(); ++ player->TeleportToBGEntryPoint(); ++ ++ return true; ++ } ++ ++ static bool HandleSpectateFromCommand(ChatHandler* handler, const char *args) ++ { ++ Player* target; ++ ObjectGuid target_guid; ++ std::string target_name; ++ if (!handler->extractPlayerTarget((char*)args, &target, &target_guid, &target_name)) ++ return false; ++ ++ Player* player = handler->GetSession()->GetPlayer(); ++ ++ if (target->HasAuraType(SPELL_AURA_MOD_STEALTH)) ++ { ++ handler->PSendSysMessage("You can't target stealthed players."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (!target) ++ { ++ handler->PSendSysMessage("Player is not online or does not exist."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (!player->IsSpectator()) ++ { ++ handler->PSendSysMessage("You are not a spectator, spectate someone first."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (target->IsSpectator() && target != player) ++ { ++ handler->PSendSysMessage("You can't do that. Your target is a spectator."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (player->GetMap() != target->GetMap()) ++ { ++ handler->PSendSysMessage("You can't do that. Your target might be in a different arena match."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ // check for arena preperation ++ // if exists than battle didn`t begin ++ if (target->HasAura(32728) || target->HasAura(32727)) ++ { ++ handler->PSendSysMessage("You can't do that. The Arena match didn't start yet."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ (target == player && player->getSpectateFrom()) ? player->SetViewpoint(player->getSpectateFrom(), false) : ++ player->SetViewpoint(target, true); ++ return true; ++ } ++ ++ static bool HandleSpectateResetCommand(ChatHandler* handler, const char *args) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ ++ if (!player) ++ { ++ handler->PSendSysMessage("Cant find player."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (!player->IsSpectator()) ++ { ++ handler->PSendSysMessage("You are not a spectator!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Battleground *bGround = player->GetBattleground(); ++ if (!bGround) ++ return false; ++ ++ if (bGround->GetStatus() != STATUS_IN_PROGRESS) ++ return true; ++ ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = bGround->GetPlayers().begin(); itr != bGround->GetPlayers().end(); ++itr) ++ if (Player* tmpPlayer = ObjectAccessor::FindPlayer(itr->first)) ++ { ++ if (tmpPlayer->IsSpectator()) ++ continue; ++ ++ uint32 tmpID = bGround->GetPlayerTeam(tmpPlayer->GetGUID()); ++ ++ // generate addon massage ++ std::string pName = tmpPlayer->GetName(); ++ std::string tName = ""; ++ ++ if (Player *target = tmpPlayer->GetSelectedPlayer()) ++ tName = target->GetName(); ++ ++ SpectatorAddonMsg msg; ++ msg.SetPlayer(pName); ++ if (tName != "") ++ msg.SetTarget(tName); ++ msg.SetStatus(tmpPlayer->IsAlive()); ++ msg.SetClass(tmpPlayer->getClass()); ++ msg.SetCurrentHP(tmpPlayer->GetHealth()); ++ msg.SetMaxHP(tmpPlayer->GetMaxHealth()); ++ Powers powerType = tmpPlayer->getPowerType(); ++ msg.SetMaxPower(tmpPlayer->GetMaxPower(powerType)); ++ msg.SetCurrentPower(tmpPlayer->GetPower(powerType)); ++ msg.SetPowerType(powerType); ++ msg.SetTeam(tmpID); ++ msg.SendPacket(player->GetGUID()); ++ } ++ ++ return true; ++ } ++ ++ ChatCommand* GetCommands() const ++ { ++ static ChatCommand spectateCommandTable[] = ++ { ++ { "player", rbac::RBAC_PERM_COMMAND_SPECTATE_PLAYER, true, &HandleSpectateCommand, "", NULL }, ++ { "view", rbac::RBAC_PERM_COMMAND_SPECTATE_VIEW, true, &HandleSpectateFromCommand, "", NULL }, ++ { "reset", rbac::RBAC_PERM_COMMAND_SPECTATE_RESET, true, &HandleSpectateResetCommand, "", NULL }, ++ { "leave", rbac::RBAC_PERM_COMMAND_SPECTATE_LEAVE, true, &HandleSpectateCancelCommand, "", NULL }, ++ { NULL, 0, false, NULL, "", NULL } ++ }; ++ ++ static ChatCommand commandTable[] = ++ { ++ { "spectate", rbac::RBAC_PERM_COMMAND_SPECTATE, false, NULL, "", spectateCommandTable }, ++ { NULL, 0, false, NULL, "", NULL } ++ }; ++ return commandTable; ++ } ++}; ++ ++ ++enum NpcSpectatorAtions { ++ // will be used for scrolling ++ NPC_SPECTATOR_ACTION_2V2_GAMES = 2000, ++ NPC_SPECTATOR_ACTION_3V3_GAMES = 3000, ++ NPC_SPECTATOR_ACTION_5V5_GAMES = 1000, ++ NPC_SPECTATOR_ACTION_SPECIFIC = 500, ++ ++ // NPC_SPECTATOR_ACTION_SELECTED_PLAYER + player.Guid() ++ NPC_SPECTATOR_ACTION_SELECTED_PLAYER = 4000 ++}; ++ ++const uint8 GamesOnPage = 15; ++ ++class npc_arena_spectator : public CreatureScript ++{ ++public: ++ npc_arena_spectator() : CreatureScript("npc_arena_spectator") { } ++ ++ bool OnGossipHello(Player* pPlayer, Creature* pCreature) ++ { ++ pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Achievement_Arena_2v2_7:35:35:-30:0|tGames: 2v2", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES); ++ pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Achievement_Arena_3v3_7:35:35:-30:0|tGames: 3v3", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES); ++ pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Achievement_Arena_5v5_7:35:35:-30:0|tGames: 5v5", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_5V5_GAMES); ++ pPlayer->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Spell_Holy_DevineAegis:35:35:-30:0|tSpectate Specific Player.", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SPECIFIC, "", 0, true); ++ pPlayer->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, pCreature->GetGUID()); ++ return true; ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ ++ if (action == NPC_SPECTATOR_ACTION_SPECIFIC) ++ { ++ ++ } ++ ++ if (action >= NPC_SPECTATOR_ACTION_5V5_GAMES && action < NPC_SPECTATOR_ACTION_2V2_GAMES) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, " Refresh", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_5V5_GAMES); ++ ShowPage(player, action - NPC_SPECTATOR_ACTION_5V5_GAMES, true); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ } ++ else if (action >= NPC_SPECTATOR_ACTION_2V2_GAMES && action < NPC_SPECTATOR_ACTION_3V3_GAMES) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Refresh", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES); ++ ShowPage(player, action - NPC_SPECTATOR_ACTION_2V2_GAMES, false); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ } ++ else if (action >= NPC_SPECTATOR_ACTION_3V3_GAMES && action < NPC_SPECTATOR_ACTION_SELECTED_PLAYER) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Refresh", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES); ++ ShowPage(player, action - NPC_SPECTATOR_ACTION_3V3_GAMES, true); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ } ++ else ++ { ++ ++ ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, action - NPC_SPECTATOR_ACTION_SELECTED_PLAYER); ++ if (Player* target = ObjectAccessor::FindPlayer(guid)) ++ { ++ ChatHandler handler(player->GetSession()); ++ char const* pTarget = target->GetName().c_str(); ++ arena_spectator_commands::HandleSpectateCommand(&handler, pTarget); ++ } ++ } ++ return true; ++ } ++ ++ std::string GetClassNameById(uint8 id) ++ { ++ std::string sClass = ""; ++ switch (id) ++ { ++ case CLASS_WARRIOR: sClass = "Warrior "; break; ++ case CLASS_PALADIN: sClass = "Paladin "; break; ++ case CLASS_HUNTER: sClass = "Hunter "; break; ++ case CLASS_ROGUE: sClass = "Rogue "; break; ++ case CLASS_PRIEST: sClass = "Priest "; break; ++ case CLASS_DEATH_KNIGHT: sClass = "DKnight "; break; ++ case CLASS_SHAMAN: sClass = "Shaman "; break; ++ case CLASS_MAGE: sClass = "Mage "; break; ++ case CLASS_WARLOCK: sClass = "Warlock "; break; ++ case CLASS_DRUID: sClass = "Druid "; break; ++ } ++ return sClass; ++ } ++ ++ std::string GetGamesStringData(Battleground* team, uint16 mmr, uint16 mmrTwo) ++ { ++ std::string teamsMember[BG_TEAMS_COUNT]; ++ uint32 firstTeamId = 0; ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = team->GetPlayers().begin(); itr != team->GetPlayers().end(); ++itr) ++ if (Player* player = ObjectAccessor::FindPlayer(itr->first)) ++ { ++ if (player->IsSpectator()) ++ continue; ++ ++ if (player->IsGameMaster()) ++ continue; ++ ++ uint32 team = itr->second.Team; ++ if (!firstTeamId) ++ firstTeamId = team; ++ ++ teamsMember[firstTeamId == team] += GetClassNameById(player->getClass()); ++ } ++ ++ std::string data = teamsMember[0] + "("; ++ std::stringstream ss; ++ std::stringstream sstwo; ++ ss << mmr; ++ sstwo << mmrTwo; ++ data += ss.str(); ++ data += ") - "; ++ data += teamsMember[1] + "(" + sstwo.str(); ++ data += ")"; ++ return data; ++ } ++ ++ ObjectGuid GetFirstPlayerGuid(Battleground* team) ++ { ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = team->GetPlayers().begin(); itr != team->GetPlayers().end(); ++itr) ++ if (Player* player = ObjectAccessor::FindPlayer(itr->first)) ++ return itr->first; ++ return ObjectGuid::Empty; ++ } ++ ++ void ShowPage(Player* player, uint16 page, uint32 IsTop) ++ { ++ uint32 firstTeamId = 0; ++ uint16 TypeOne = 0; ++ uint16 TypeTwo = 0; ++ uint16 TypeThree = 0; ++ uint16 mmr = 0; ++ uint16 mmrTwo = 0; ++ bool haveNextPage = false; ++ for (uint8 i = 0; i <= MAX_BATTLEGROUND_TYPE_ID; ++i) ++ { ++ if (!sBattlegroundMgr->IsArenaType(BattlegroundTypeId(i))) ++ continue; ++ ++ //BattlegroundContainer arenas = sBattlegroundMgr->GetBattlegroundsByType((BattlegroundTypeId)i); ++ BattlegroundData* arenas = sBattlegroundMgr->GetAllBattlegroundsWithTypeId(BattlegroundTypeId(i)); ++ ++ if (!arenas || arenas->m_Battlegrounds.empty()) ++ continue; ++ ++ for (BattlegroundContainer::const_iterator itr = arenas->m_Battlegrounds.begin(); itr != arenas->m_Battlegrounds.end(); ++itr) ++ { ++ Battleground* arena = itr->second; ++ Player* target = ObjectAccessor::FindPlayer(GetFirstPlayerGuid(arena)); ++ if (target && (target->HasAura(32728) || target->HasAura(32727))) ++ continue; ++ ++ if (!arena->GetPlayersSize()) ++ continue; ++ ++ if (arena->GetArenaType() == ARENA_TYPE_2v2) ++ { ++ mmr = arena->GetArenaMatchmakerRating(0); ++ firstTeamId = target->GetArenaTeamId(0); ++ Battleground::BattlegroundPlayerMap::const_iterator citr = arena->GetPlayers().begin(); ++ for (; citr != arena->GetPlayers().end(); ++citr) ++ if (Player* plrs = sObjectAccessor->FindPlayer(citr->first)) ++ if (plrs->GetArenaTeamId(0) != firstTeamId) ++ mmrTwo = arena->GetArenaMatchmakerRating(citr->second.Team); ++ } ++ else if (arena->GetArenaType() == ARENA_TYPE_3v3) ++ { ++ mmr = arena->GetArenaMatchmakerRating(1); ++ firstTeamId = target->GetArenaTeamId(1); ++ Battleground::BattlegroundPlayerMap::const_iterator citr = arena->GetPlayers().begin(); ++ for (; citr != arena->GetPlayers().end(); ++citr) ++ if (Player* plrs = sObjectAccessor->FindPlayer(citr->first)) ++ if (plrs->GetArenaTeamId(1) != firstTeamId) ++ mmrTwo = arena->GetArenaMatchmakerRating(citr->second.Team); ++ } ++ else if (arena->GetArenaType() == ARENA_TYPE_5v5) ++ { ++ mmr = arena->GetArenaMatchmakerRating(2); ++ firstTeamId = target->GetArenaTeamId(2); ++ Battleground::BattlegroundPlayerMap::const_iterator citr = arena->GetPlayers().begin(); ++ for (; citr != arena->GetPlayers().end(); ++citr) ++ if (Player* plrs = sObjectAccessor->FindPlayer(citr->first)) ++ if (plrs->GetArenaTeamId(2) != firstTeamId) ++ mmrTwo = arena->GetArenaMatchmakerRating(citr->second.Team); ++ } ++ ++ if (IsTop == 3 && arena->GetArenaType() == ARENA_TYPE_3v3) ++ { ++ TypeThree++; ++ if (TypeThree > (page + 1) * GamesOnPage) ++ { ++ haveNextPage = true; ++ break; ++ } ++ ++ if (TypeThree >= page * GamesOnPage) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, GetGamesStringData(arena, mmr, mmrTwo), GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SELECTED_PLAYER + GetFirstPlayerGuid(arena)); ++ } ++ else if (IsTop == 2 && arena->GetArenaType() == ARENA_TYPE_2v2) ++ { ++ TypeTwo++; ++ if (TypeTwo > (page + 1) * GamesOnPage) ++ { ++ haveNextPage = true; ++ break; ++ } ++ ++ if (TypeTwo >= page * GamesOnPage) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, GetGamesStringData(arena, mmr, mmrTwo), GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SELECTED_PLAYER + GetFirstPlayerGuid(arena)); ++ } ++ else if (IsTop == 1 && arena->GetArenaType() == ARENA_TYPE_5v5) ++ { ++ TypeOne++; ++ if (TypeOne > (page + 1) * GamesOnPage) ++ { ++ haveNextPage = true; ++ break; ++ } ++ if (TypeOne >= page * GamesOnPage) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, GetGamesStringData(arena, mmr, mmrTwo), GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SELECTED_PLAYER + GetFirstPlayerGuid(arena)); ++ } ++ } ++ } ++ ++ if (page > 0) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Prev..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES + page - 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Prev..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES + page - 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Prev..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_5V5_GAMES + page - 1); ++ } ++ ++ if (haveNextPage) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Next..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES + page + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Next..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES + page + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Next..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_5V5_GAMES + page + 1); ++ } ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, const char* code) ++ { ++ if (!player) ++ return true; ++ ++ player->PlayerTalkClass->ClearMenus(); ++ player->CLOSE_GOSSIP_MENU(); ++ if (sender == GOSSIP_SENDER_MAIN) ++ { ++ switch (action) ++ { ++ case NPC_SPECTATOR_ACTION_SPECIFIC: // choosing a player ++ ++ const char* plrName = code; ++ ++ char playerName[50]; ++ strcpy(playerName, plrName); ++ ++ for (int i = 0; i < 13; i++) ++ { ++ if (playerName[i] == NULL) ++ break; ++ if (i == 0 && playerName[i] > 96) ++ playerName[0] -= 32; ++ else if (playerName[i] < 97) ++ playerName[i] += 32; ++ } ++ ++ if (Player* target = sObjectAccessor->FindPlayerByName(playerName)) ++ { ++ ChatHandler handler(player->GetSession()); ++ char const* pTarget = target->GetName().c_str(); ++ arena_spectator_commands::HandleSpectateCommand(&handler, pTarget); ++ } ++ ChatHandler(player->GetSession()).PSendSysMessage("Player is not online or does not exist."); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++}; ++ ++ ++void AddSC_arena_spectator_script() ++{ ++ new arena_spectator_commands(); ++ new npc_arena_spectator(); ++} +diff --git a/src/server/scripts/Custom/ArenaSpectator/sql/Arena Spectator NPC.sql b/src/server/scripts/Custom/ArenaSpectator/sql/Arena Spectator NPC.sql +new file mode 100644 +index 0000000..2f4aed5 +--- /dev/null ++++ b/src/server/scripts/Custom/ArenaSpectator/sql/Arena Spectator NPC.sql +@@ -0,0 +1,10 @@ ++SET ++@Entry = 190000, ++@Name = "Arena Spectator", ++@Subname = "Spectate Master", ++@IconName = "Speak"; ++ ++DELETE FROM `creature_template` WHERE `entry`=@Entry; ++INSERT INTO `creature_template` (`entry`, `modelid1`, `modelid2`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `scale`, `rank`, `dmgschool`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`) VALUES ++(@Entry, 29348, 0, @Name, @Subname, @IconName, 0, 80, 80, 2, 35, 3, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 3, 1, 0, 0, 1, 0, 0, 'npc_arena_spectator'); ++ +diff --git a/src/server/scripts/Custom/ArenaSpectator/sql/rbac_linked_permissions.sql b/src/server/scripts/Custom/ArenaSpectator/sql/rbac_linked_permissions.sql +new file mode 100644 +index 0000000..04f0aaf +--- /dev/null ++++ b/src/server/scripts/Custom/ArenaSpectator/sql/rbac_linked_permissions.sql +@@ -0,0 +1,6 @@ ++insert into `rbac_linked_permissions` (`id`, `linkedId`) values ++('195','1003'), ++('195','1004'), ++('195','1005'), ++('195','1006'), ++('195','1007'); +\ No newline at end of file +diff --git a/src/server/scripts/Custom/ArenaSpectator/sql/rbac_permissions.sql b/src/server/scripts/Custom/ArenaSpectator/sql/rbac_permissions.sql +new file mode 100644 +index 0000000..baba4bd +--- /dev/null ++++ b/src/server/scripts/Custom/ArenaSpectator/sql/rbac_permissions.sql +@@ -0,0 +1,6 @@ ++insert into `rbac_permissions` (`id`, `name`) values ++('1003', 'spectate'), ++('1004', 'spectate player'), ++('1005', 'spectate view'), ++('1006', 'spectate reset'), ++('1007', 'spectate leave'); +\ No newline at end of file +diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt +index 5218f76..7db4bc6 100644 +--- a/src/server/scripts/Custom/CMakeLists.txt ++++ b/src/server/scripts/Custom/CMakeLists.txt +@@ -12,7 +12,8 @@ + + set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} +-# ${sources_Custom} ++ Custom/ArenaSpectator/arena_spectator.cpp ++ + ) + + message(" -> Prepared: Custom") +-- +1.9.4.msysgit.0 \ No newline at end of file diff --git a/armeria.diff b/armeria.diff new file mode 100644 index 0000000..0e7113e --- /dev/null +++ b/armeria.diff @@ -0,0 +1,582 @@ +.../WoWArmory/characters.armory.sql | 50 ++++++++++++++ + src/server/game/Achievements/AchievementMgr.cpp | 6 ++ + src/server/game/Battlegrounds/Arena.cpp | 45 ++++++++++++- + src/server/game/Battlegrounds/Battleground.cpp | 12 ++++ + src/server/game/Battlegrounds/Battleground.h | 4 +- + src/server/game/Battlegrounds/BattlegroundScore.h | 30 +++++++-- + src/server/game/Entities/Item/Item.cpp | 11 ++++ + src/server/game/Entities/Player/Player.cpp | 76 ++++++++++++++++++++++ + src/server/game/Entities/Player/Player.h | 21 ++++++ + src/server/game/Entities/Unit/Unit.cpp | 20 ++++++ + src/server/game/World/World.cpp | 4 ++ + src/server/game/World/World.h | 1 + + src/server/worldserver/worldserver.conf.dist | 7 ++ + 13 files changed, 280 insertions(+), 7 deletions(-) + create mode 100644 sql/TrinityCore-Patches/WoWArmory/characters.armory.sql + +diff --git a/sql/TrinityCore-Patches/WoWArmory/characters.armory.sql b/sql/TrinityCore-Patches/WoWArmory/characters.armory.sql +new file mode 100644 +index 0000000..c816fe8 +--- /dev/null ++++ b/sql/TrinityCore-Patches/WoWArmory/characters.armory.sql +@@ -0,0 +1,50 @@ ++SET FOREIGN_KEY_CHECKS=0; ++ ++-- ---------------------------- ++-- Table structure for armory_character_stats ++-- ---------------------------- ++DROP TABLE IF EXISTS `armory_character_stats`; ++CREATE TABLE `armory_character_stats` ( ++ `guid` int(11) NOT NULL, ++ `data` longtext NOT NULL, ++ `save_date` int(11) DEFAULT NULL, ++ PRIMARY KEY (`guid`) ++) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='World of Warcraft Armory table'; ++ ++-- ---------------------------- ++-- Table structure for armory_game_chart ++-- ---------------------------- ++DROP TABLE IF EXISTS `armory_game_chart`; ++CREATE TABLE `armory_game_chart` ( ++ `gameid` int(11) NOT NULL, ++ `teamid` int(11) NOT NULL, ++ `guid` int(11) NOT NULL, ++ `changeType` int(11) NOT NULL, ++ `ratingChange` int(11) NOT NULL, ++ `teamRating` int(11) NOT NULL, ++ `damageDone` int(11) NOT NULL, ++ `deaths` int(11) NOT NULL, ++ `healingDone` int(11) NOT NULL, ++ `damageTaken` int(11) NOT NULL, ++ `healingTaken` int(11) NOT NULL, ++ `killingBlows` int(11) NOT NULL, ++ `mapId` int(11) NOT NULL, ++ `start` int(11) NOT NULL, ++ `end` int(11) NOT NULL, ++ PRIMARY KEY (`gameid`,`teamid`,`guid`) ++) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='WoWArmory Game Chart'; ++ ++-- ---------------------------- ++-- Table structure for character_feed_log ++-- ---------------------------- ++DROP TABLE IF EXISTS `character_feed_log`; ++CREATE TABLE `character_feed_log` ( ++ `guid` int(11) NOT NULL, ++ `type` smallint(1) NOT NULL, ++ `data` int(11) NOT NULL, ++ `date` int(11) DEFAULT NULL, ++ `counter` int(11) NOT NULL, ++ `difficulty` smallint(6) DEFAULT '-1', ++ `item_guid` int(11) DEFAULT '-1', ++ `item_quality` smallint(6) NOT NULL DEFAULT '-1' ++) ENGINE=MyISAM DEFAULT CHARSET=utf8; +diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp +index 90e6182..78d0d9a 100644 +--- a/src/server/game/Achievements/AchievementMgr.cpp ++++ b/src/server/game/Achievements/AchievementMgr.cpp +@@ -1512,6 +1512,12 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) + achievement->ID, m_player->GetName().c_str(), m_player->GetGUID().GetCounter()); + + SendAchievementEarned(achievement); ++ ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ GetPlayer()->CreateWowarmoryFeed(1, achievement->ID, 0, 0); ++ /** World of Warcraft Armory **/ ++ + CompletedAchievementData& ca = m_completedAchievements[achievement->ID]; + ca.date = time(NULL); + ca.changed = true; +diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp +index 1ccf310..8706c44 100644 +--- a/src/server/game/Battlegrounds/Arena.cpp ++++ b/src/server/game/Battlegrounds/Arena.cpp +@@ -171,7 +171,50 @@ void Arena::EndBattleground(uint32 winner) + + _arenaTeamScores[winnerTeam].Assign(winnerChange, winnerMatchmakerRating, winnerArenaTeam->GetName()); + _arenaTeamScores[loserTeam].Assign(loserChange, loserMatchmakerRating, loserArenaTeam->GetName()); +- ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ { ++ uint32 maxChartID; ++ QueryResult result = CharacterDatabase.PQuery("SELECT MAX(gameid) FROM armory_game_chart"); ++ if(!result) ++ maxChartID = 0; ++ else ++ { ++ maxChartID = (*result)[0].GetUInt32(); ++ //result.release(); ++ } ++ uint32 gameID = maxChartID+1; ++ for(BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr) ++ { ++ Player* player = ObjectAccessor::FindConnectedPlayer(ObjectGuid(HighGuid::Player, itr->first)); ++ if (!player) ++ continue; ++ uint32 plTeamID = player->GetArenaTeamId(winnerArenaTeam->GetSlot()); ++ int changeType; ++ uint32 resultRating; ++ uint32 resultTeamID; ++ int32 ratingChange; ++ if (plTeamID == winnerArenaTeam->GetId()) ++ { ++ changeType = 1; //win ++ resultRating = winnerTeamRating; ++ resultTeamID = plTeamID; ++ ratingChange = winnerChange; ++ } ++ else ++ { ++ changeType = 2; //lose ++ resultRating = loserTeamRating; ++ resultTeamID = loserArenaTeam->GetId(); ++ ratingChange = loserChange; ++ } ++ std::ostringstream sql_query; ++ // gameid, teamid, guid, changeType, ratingChange, teamRating, damageDone, deaths, healingDone, damageTaken,, healingTaken, killingBlows, mapId, start, end ++ sql_query << "INSERT INTO armory_game_chart VALUES ('" << gameID << "', '" << resultTeamID << "', '" << player->GetGUID() << "', '" << changeType << "', '" << ratingChange << "', '" << resultRating << "', '" << itr->second->DamageDone << "', '" << itr->second->Deaths << "', '" << itr->second->HealingDone << "', '" << itr->second->DamageTaken << "', '" << itr->second->HealingTaken << "', '" << itr->second->KillingBlows << "', '" << m_MapId << "', '" << m_StartTime << "', '" << m_EndTime << "')"; ++ CharacterDatabase.Execute(sql_query.str().c_str()); ++ } ++ } ++ /** World of Warcraft Armory **/ + TC_LOG_DEBUG("bg.arena", "Arena match Type: %u for Team1Id: %u - Team2Id: %u ended. WinnerTeamId: %u. Winner rating: +%d, Loser rating: %d", + GetArenaType(), GetArenaTeamIdByIndex(TEAM_ALLIANCE), GetArenaTeamIdByIndex(TEAM_HORDE), winnerArenaTeam->GetId(), winnerChange, loserChange); + +diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp +index 100e2e6..5fe85a4 100644 +--- a/src/server/game/Battlegrounds/Battleground.cpp ++++ b/src/server/game/Battlegrounds/Battleground.cpp +@@ -147,6 +147,7 @@ Battleground::Battleground() + m_StartMaxDist = 0.0f; + ScriptId = 0; + ++ + m_ArenaTeamIds[TEAM_ALLIANCE] = 0; + m_ArenaTeamIds[TEAM_HORDE] = 0; + +@@ -504,6 +505,7 @@ inline void Battleground::_ProcessJoin(uint32 diff) + sBattlegroundMgr->BuildBattlegroundStatusPacket(&status, this, queueSlot, STATUS_IN_PROGRESS, 0, GetStartTime(), GetArenaType(), player->GetBGTeam()); + player->SendDirectMessage(&status); + ++ + player->RemoveAurasDueToSpell(SPELL_ARENA_PREPARATION); + player->ResetAllPowers(); + if (!player->IsGameMaster()) +@@ -526,6 +528,7 @@ inline void Battleground::_ProcessJoin(uint32 diff) + } + } + ++ + CheckWinConditions(); + } + else +@@ -802,6 +805,8 @@ void Battleground::EndBattleground(uint32 winner) + stmt->setUInt32(6, score->second->GetBonusHonor()); + stmt->setUInt32(7, score->second->GetDamageDone()); + stmt->setUInt32(8, score->second->GetHealingDone()); ++ stmt->setUInt32(8, score->second->GetDamageTaken()); ++ stmt->setUInt32(8, score->second->GetHealingTaken()); + stmt->setUInt32(9, score->second->GetAttr1()); + stmt->setUInt32(10, score->second->GetAttr2()); + stmt->setUInt32(11, score->second->GetAttr3()); +@@ -811,6 +816,11 @@ void Battleground::EndBattleground(uint32 winner) + CharacterDatabase.Execute(stmt); + } + ++ ++ ++ ++ ++ + // Reward winner team + if (team == winner) + { +@@ -823,6 +833,7 @@ void Battleground::EndBattleground(uint32 winner) + player->SetRandomWinner(true); + } + ++ + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_BG, 1); + } + else +@@ -1639,6 +1650,7 @@ void Battleground::SendWarningToAll(uint32 entry, ...) + vsnprintf(str, 1024, format, ap); + va_end(ap); + ++ + ChatHandler::BuildChatPacket(localizedPackets[player->GetSession()->GetSessionDbLocaleIndex()], CHAT_MSG_RAID_BOSS_EMOTE, LANG_UNIVERSAL, NULL, NULL, str); + } + +diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h +index 03f138a..829aeff 100644 +--- a/src/server/game/Battlegrounds/Battleground.h ++++ b/src/server/game/Battlegrounds/Battleground.h +@@ -520,7 +520,7 @@ class Battleground + + ArenaTeamScore _arenaTeamScores[BG_TEAMS_COUNT]; + +- private: ++ public: + // Battleground + BattlegroundTypeId m_TypeID; + BattlegroundTypeId m_RandomTypeID; +@@ -602,7 +602,7 @@ class Battleground + uint32 m_MaxPlayers; + uint32 m_MinPlayersPerTeam; + uint32 m_MinPlayers; +- ++ public: + // Start location + uint32 m_MapId; + BattlegroundMap* m_Map; +diff --git a/src/server/game/Battlegrounds/BattlegroundScore.h b/src/server/game/Battlegrounds/BattlegroundScore.h +index 428dece..c6650d6 100644 +--- a/src/server/game/Battlegrounds/BattlegroundScore.h ++++ b/src/server/game/Battlegrounds/BattlegroundScore.h +@@ -47,7 +47,11 @@ enum ScoreType + + // SOTA + SCORE_DESTROYED_DEMOLISHER = 16, +- SCORE_DESTROYED_WALL = 17 ++ SCORE_DESTROYED_WALL = 17, ++ /** World of Warcraft Armory **/ ++ SCORE_DAMAGE_TAKEN = 18, ++ SCORE_HEALING_TAKEN = 19 ++ /** World of Warcraft Armory **/ + }; + + struct BattlegroundScore +@@ -57,7 +61,7 @@ struct BattlegroundScore + + protected: + BattlegroundScore(ObjectGuid playerGuid) : PlayerGuid(playerGuid), KillingBlows(0), Deaths(0), +- HonorableKills(0), BonusHonor(0), DamageDone(0), HealingDone(0) { } ++ HonorableKills(0), BonusHonor(0), DamageDone(0), HealingDone(0), DamageTaken(0), HealingTaken(0) { } + + virtual ~BattlegroundScore() { } + +@@ -83,6 +87,14 @@ struct BattlegroundScore + case SCORE_HEALING_DONE: // Healing Done + HealingDone += value; + break; ++ /** World of Warcraft Armory **/ ++ case SCORE_DAMAGE_TAKEN: // Damage Taken ++ DamageTaken += value; ++ break; ++ case SCORE_HEALING_TAKEN: // Healing Taken ++ HealingTaken += value; ++ break; ++ /** World of Warcraft Armory **/ + default: + ASSERT(false && "Not implemented Battleground score type!"); + break; +@@ -99,7 +111,10 @@ struct BattlegroundScore + data << uint32(BonusHonor); + data << uint32(DamageDone); + data << uint32(HealingDone); +- ++ /** World of Warcraft Armory **/ ++ data << uint32(DamageTaken); ++ data << uint32(HealingTaken); ++ /** World of Warcraft Armory **/ + BuildObjectivesBlock(data); + } + +@@ -114,12 +129,17 @@ struct BattlegroundScore + uint32 GetBonusHonor() const { return BonusHonor; } + uint32 GetDamageDone() const { return DamageDone; } + uint32 GetHealingDone() const { return HealingDone; } +- ++ /** World of Warcraft Armory **/ ++ uint32 GetDamageTaken() const { return DamageTaken; } ++ uint32 GetHealingTaken() const { return HealingTaken; } ++ /** World of Warcraft Armory **/ + virtual uint32 GetAttr1() const { return 0; } + virtual uint32 GetAttr2() const { return 0; } + virtual uint32 GetAttr3() const { return 0; } + virtual uint32 GetAttr4() const { return 0; } + virtual uint32 GetAttr5() const { return 0; } ++ virtual uint32 GetAttr6() const { return 0; } ++ virtual uint32 GetAttr7() const { return 0; } + + ObjectGuid PlayerGuid; + +@@ -130,6 +150,8 @@ struct BattlegroundScore + uint32 BonusHonor; + uint32 DamageDone; + uint32 HealingDone; ++ uint32 DamageTaken; ++ uint32 HealingTaken; + }; + + #endif // TRINITY_BATTLEGROUND_SCORE_H +diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp +index 70fa471..1a153a2 100644 +--- a/src/server/game/Entities/Item/Item.cpp ++++ b/src/server/game/Entities/Item/Item.cpp +@@ -282,6 +282,17 @@ bool Item::Create(ObjectGuid::LowType guidlow, uint32 itemid, Player const* owne + + SetUInt32Value(ITEM_FIELD_DURATION, itemProto->Duration); + SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, 0); ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ { ++ if (itemProto->Quality > 2 && itemProto->Flags != 2048 && (itemProto->Class == ITEM_CLASS_WEAPON || itemProto->Class == ITEM_CLASS_ARMOR)) ++ { ++ if (!GetOwner()) ++ return true; ++ GetOwner()->CreateWowarmoryFeed(2, itemid, guidlow, itemProto->Quality); ++ } ++ } ++ /** World of Warcraft Armory **/ + return true; + } + +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..65e1d0b 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -4561,6 +4561,12 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe + + Corpse::DeleteFromDB(playerguid, trans); + ++ /* World of Warcraft Armory */ ++ trans->PAppend("DELETE FROM armory_character_stats WHERE guid = '%u'",guid); ++ trans->PAppend("DELETE FROM character_feed_log WHERE guid = '%u'",guid); ++ /* World of Warcraft Armory */ ++ ++ + CharacterDatabase.CommitTransaction(trans); + break; + } +@@ -16922,6 +16928,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) + CharacterDatabase.Execute(stmt); + return false; + } ++ // Cleanup old Wowarmory feeds ++ InitWowarmoryFeeds(); + + // overwrite possible wrong/corrupted guid + SetGuidValue(OBJECT_FIELD_GUID, guid); +@@ -19238,6 +19246,33 @@ void Player::SaveToDB(bool create /*=false*/) + + CharacterDatabase.CommitTransaction(trans); + ++ /* World of Warcraft Armory */ ++ // Place this code AFTER CharacterDatabase.CommitTransaction(); to avoid some character saving errors. ++ // Wowarmory feeds ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ { ++ std::ostringstream sWowarmory; ++ for (WowarmoryFeeds::iterator iter = m_wowarmory_feeds.begin(); iter < m_wowarmory_feeds.end(); ++iter) { ++ sWowarmory << "INSERT IGNORE INTO character_feed_log (guid,type,data,date,counter,difficulty,item_guid,item_quality) VALUES "; ++ // guid type data date counter difficulty item_guid item_quality ++ sWowarmory << "(" << (*iter).guid << ", " << (*iter).type << ", " << (*iter).data << ", " << uint64((*iter).date) << ", " << (*iter).counter << ", " << uint32((*iter).difficulty) << ", " << (*iter).item_guid << ", " << (*iter).item_quality << ");"; ++ CharacterDatabase.PExecute(sWowarmory.str().c_str()); ++ sWowarmory.str(""); ++ } ++ // Clear old saved feeds from storage - they are not required for server core. ++ InitWowarmoryFeeds(); ++ // Character stats ++ std::ostringstream ps; ++ time_t t = time(NULL); ++ CharacterDatabase.PExecute("DELETE FROM armory_character_stats WHERE guid = %u", GetGUID().GetCounter()); ++ ps << "INSERT INTO armory_character_stats (guid, data, save_date) VALUES (" << GetGUID().GetCounter() << ", '"; ++ for (uint16 i = 0; i < m_valuesCount; ++i) ++ ps << GetUInt32Value(i) << " "; ++ ps << "', " << uint64(t) << ");"; ++ CharacterDatabase.PExecute(ps.str().c_str()); ++ } ++ /* World of Warcraft Armory */ ++ + // save pet (hunter pet level and experience and all type pets health/mana). + if (Pet* pet = GetPet()) + pet->SavePetToDB(PET_SAVE_AS_CURRENT); +@@ -26126,6 +26161,47 @@ void Player::_SaveInstanceTimeRestrictions(SQLTransaction& trans) + } + } + ++/** World of Warcraft Armory **/ ++void Player::InitWowarmoryFeeds() { ++ // Clear feeds ++ m_wowarmory_feeds.clear(); ++} ++ ++void Player::CreateWowarmoryFeed(uint32 type, uint32 data, uint32 item_guid, uint32 item_quality) { ++ /* ++ 1 - TYPE_ACHIEVEMENT_FEED ++ 2 - TYPE_ITEM_FEED ++ 3 - TYPE_BOSS_FEED ++ */ ++ if (GetGUID().GetCounter() == 0) ++ { ++ TC_LOG_DEBUG("server.loading", "[Wowarmory]: player is not initialized, unable to create log entry!"); ++ return; ++ } ++ if (type <= 0 || type > 3) ++ { ++ TC_LOG_DEBUG("server.loading", "[Wowarmory]: unknown feed type: %d, ignore.", type); ++ return; ++ } ++ if (data == 0) ++ { ++ TC_LOG_DEBUG("server.loading", "[Wowarmory]: empty data (GUID: %u), ignore.", GetGUID().GetCounter()); ++ return; ++ } ++ WowarmoryFeedEntry feed; ++ feed.guid = GetGUID().GetCounter(); ++ feed.type = type; ++ feed.data = data; ++ feed.difficulty = type == 3 ? GetMap()->GetDifficulty() : 0; ++ feed.item_guid = item_guid; ++ feed.item_quality = item_quality; ++ feed.counter = 0; ++ feed.date = time(NULL); ++ TC_LOG_DEBUG("server.loading", "[Wowarmory]: create wowarmory feed (GUID: %u, type: %d, data: %u).", feed.guid, feed.type, feed.data); ++ m_wowarmory_feeds.push_back(feed); ++} ++/** World of Warcraft Armory **/ ++ + bool Player::IsInWhisperWhiteList(ObjectGuid guid) + { + for (GuidList::const_iterator itr = WhisperList.begin(); itr != WhisperList.end(); ++itr) +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index b7d7d81..91fe06e 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -1000,6 +1000,21 @@ struct TradeStatusInfo + uint8 Slot; + }; + ++/* World of Warcraft Armory */ ++struct WowarmoryFeedEntry { ++ uint32 guid; // Player GUID ++ time_t date; // Log date ++ uint32 type; // TYPE_ACHIEVEMENT_FEED, TYPE_ITEM_FEED, TYPE_BOSS_FEED ++ uint32 data; // TYPE_ITEM_FEED: item_entry, TYPE_BOSS_FEED: creature_entry ++ uint32 item_guid; // Can be 0 ++ uint32 item_quality; // Can be 0 ++ uint8 difficulty; // Can be 0 ++ int counter; // Can be 0 ++}; ++ ++typedef std::vector WowarmoryFeeds; ++/* World of Warcraft Armory */ ++ + class Player : public Unit, public GridObject + { + friend class WorldSession; +@@ -2102,6 +2117,10 @@ class Player : public Unit, public GridObject + void SendCinematicStart(uint32 CinematicSequenceId); + void SendMovieStart(uint32 MovieId); + ++ /* World of Warcraft Armory */ ++ void CreateWowarmoryFeed(uint32 type, uint32 data, uint32 item_guid, uint32 item_quality); ++ void InitWowarmoryFeeds(); ++ /* World of Warcraft Armory */ + /*********************************************************/ + /*** INSTANCE SYSTEM ***/ + /*********************************************************/ +@@ -2579,6 +2598,8 @@ class Player : public Unit, public GridObject + uint32 m_timeSyncTimer; + uint32 m_timeSyncClient; + uint32 m_timeSyncServer; ++ // World of Warcraft Armory Feeds ++ WowarmoryFeeds m_wowarmory_feeds; + + InstanceTimeMap _instanceResetTimes; + uint32 _pendingBindId; +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 3aed5fd..c70128a 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -730,8 +730,17 @@ uint32 Unit::DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDam + + // in bg, count dmg if victim is also a player + if (victim->GetTypeId() == TYPEID_PLAYER) ++ { + if (Battleground* bg = killer->GetBattleground()) ++ { + bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ if (Battleground *bgV = ((Player*)victim)->GetBattleground()) ++ bgV->UpdatePlayerScore(((Player*)victim), SCORE_DAMAGE_TAKEN, damage); ++ /** World of Warcraft Armory **/ ++ } ++ } + + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, damage, 0, victim); + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); +@@ -9602,6 +9611,11 @@ int32 Unit::DealHeal(Unit* victim, uint32 addhealth) + { + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED, gain); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED, addhealth); ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ if (Battleground *bgV = victim->ToPlayer()->GetBattleground()) ++ bgV->UpdatePlayerScore((Player*)victim, SCORE_HEALING_TAKEN, gain); ++ /** World of Warcraft Armory **/ + } + + return gain; +@@ -15517,7 +15531,13 @@ void Unit::Kill(Unit* victim, bool durabilityLoss) + if (instanceMap->IsRaidOrHeroicDungeon()) + { + if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND) ++ { + ((InstanceMap*)instanceMap)->PermBindAllPlayers(creditedPlayer); ++ /** World of Warcraft Armory **/ ++ if (sWorld->getBoolConfig(CONFIG_ARMORY_ENABLE)) ++ creditedPlayer->CreateWowarmoryFeed(3, creature->GetCreatureTemplate()->Entry, 0, 0); ++ /** World of Warcraft Armory **/ ++ } + } + else + { +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..5fe8f26 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1297,6 +1297,10 @@ void World::LoadConfigSettings(bool reload) + m_timers[WUPDATE_AUTOBROADCAST].Reset(); + } + ++ /** World of Warcraft Armory **/ ++ m_bool_configs[CONFIG_ARMORY_ENABLE] = sConfigMgr->GetBoolDefault("Armory.Enable", true); ++ /** World of Warcraft Armory **/ ++ + // MySQL ping time interval + m_int_configs[CONFIG_DB_PING_INTERVAL] = sConfigMgr->GetIntDefault("MaxPingTime", 30); + +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 7a3e56c..4b854ee 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -148,6 +148,7 @@ enum WorldBoolConfigs + CONFIG_DELETE_CHARACTER_TICKET_TRACE, + CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, + CONFIG_PRESERVE_CUSTOM_CHANNELS, ++ CONFIG_ARMORY_ENABLE, + CONFIG_PDUMP_NO_PATHS, + CONFIG_PDUMP_NO_OVERWRITE, + CONFIG_QUEST_IGNORE_AUTO_ACCEPT, +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..9621d98 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -2933,6 +2933,13 @@ AuctionHouseBot.Alliance.Items.Amount.Ratio = 100 + + AuctionHouseBot.Horde.Items.Amount.Ratio = 100 + ++# Armory.Enable ++# Description: Activates the WowArmory. ++# Default: 0 - (Disable) ++# 1 - (Enable) ++ ++Armory.Enable = 1 ++ + # + # AuctionHouseBot.Neutral.Items.Amount.Ratio + # Description: Enable/Disable (disabled if 0) the part of AHBot that puts items up for auction on Neutral AH \ No newline at end of file diff --git a/autobalance.diff b/autobalance.diff new file mode 100644 index 0000000..4a2969e --- /dev/null +++ b/autobalance.diff @@ -0,0 +1,1042 @@ +src/server/game/Entities/Creature/Creature.cpp | 2 + + src/server/game/Entities/Unit/Unit.cpp | 2 + + src/server/game/Scripting/ScriptLoader.cpp | 6 +- + src/server/game/Scripting/ScriptMgr.cpp | 60 +++ + src/server/game/Scripting/ScriptMgr.h | 60 +++ + src/server/game/World/World.cpp | 19 + + src/server/game/World/World.h | 22 ++ + src/server/scripts/Custom/VAS_AutoBalance.cpp | 510 +++++++++++++++++++++++++ + src/server/worldserver/worldserver.conf.dist | 72 ++++ + 9 files changed, 751 insertions(+), 2 deletions(-) + create mode 100644 src/server/scripts/Custom/VAS_AutoBalance.cpp + +diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp +index 73b9e05..a941a52 100644 +--- a/src/server/game/Entities/Creature/Creature.cpp ++++ b/src/server/game/Entities/Creature/Creature.cpp +@@ -1186,6 +1186,8 @@ void Creature::SelectLevel() + + SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, stats->AttackPower); + SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, stats->RangedAttackPower); ++ ++ sScriptMgr->Creature_SelectLevel(cInfo, this); + } + + float Creature::_GetHealthMod(int32 Rank) +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 3aed5fd..a5888eb 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -9838,6 +9838,8 @@ int32 Unit::HealBySpell(Unit* victim, SpellInfo const* spellInfo, uint32 addHeal + // calculate heal absorb and reduce healing + CalcHealAbsorb(victim, spellInfo, addHealth, absorb); + ++ sScriptMgr->ModifyHealRecieved(this, victim, addHealth); ++ + int32 gain = DealHeal(victim, addHealth); + SendHealSpellLog(victim, spellInfo->Id, addHealth, uint32(addHealth - gain), absorb, critical); + return gain; +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index e983c9e..9c31c67 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1493,7 +1493,8 @@ void AddBattlegroundScripts() + // start82 + // start83 + // start84 +-// start85 ++ //Vas AutoBalance ++ void AddSC_VAS_AutoBalance(); + // start86 + // start87 + // start88 +@@ -1699,7 +1700,8 @@ void AddCustomScripts() + // end82 + // end83 + // end84 +-// end85 ++ //VAS AutoBalance ++ AddSC_VAS_AutoBalance(); + // end86 + // end87 + // end88 +diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp +index 3fa16cf..71b7831 100644 +--- a/src/server/game/Scripting/ScriptMgr.cpp ++++ b/src/server/game/Scripting/ScriptMgr.cpp +@@ -34,6 +34,7 @@ + #include "WorldPacket.h" + #include "WorldSession.h" + #include "Chat.h" ++#include "Creature.h" + + // namespace + // { +@@ -283,10 +284,12 @@ void ScriptMgr::Unload() + SCR_CLEAR(ServerScript); + SCR_CLEAR(WorldScript); + SCR_CLEAR(FormulaScript); ++ SCR_CLEAR(AllMapScript); + SCR_CLEAR(WorldMapScript); + SCR_CLEAR(InstanceMapScript); + SCR_CLEAR(BattlegroundMapScript); + SCR_CLEAR(ItemScript); ++ SCR_CLEAR(AllCreatureScript); + SCR_CLEAR(CreatureScript); + SCR_CLEAR(GameObjectScript); + SCR_CLEAR(AreaTriggerScript); +@@ -685,6 +688,8 @@ void ScriptMgr::OnPlayerEnterMap(Map* map, Player* player) + ASSERT(map); + ASSERT(player); + ++ FOREACH_SCRIPT(AllMapScript)->OnPlayerEnterAll(map, player); ++ + FOREACH_SCRIPT(PlayerScript)->OnMapChanged(player); + + SCR_MAP_BGN(WorldMapScript, map, itr, end, entry, IsWorldMap); +@@ -705,6 +710,8 @@ void ScriptMgr::OnPlayerLeaveMap(Map* map, Player* player) + ASSERT(map); + ASSERT(player); + ++ FOREACH_SCRIPT(AllMapScript)->OnPlayerLeaveAll(map, player); ++ + SCR_MAP_BGN(WorldMapScript, map, itr, end, entry, IsWorldMap); + itr->second->OnPlayerLeave(map, player); + SCR_MAP_END; +@@ -894,10 +901,17 @@ void ScriptMgr::OnCreatureUpdate(Creature* creature, uint32 diff) + { + ASSERT(creature); + ++ FOREACH_SCRIPT(AllCreatureScript)->OnAllCreatureUpdate(creature, diff); ++ + GET_SCRIPT(CreatureScript, creature->GetScriptId(), tmpscript); + tmpscript->OnUpdate(creature, diff); + } + ++void ScriptMgr::Creature_SelectLevel(const CreatureTemplate *cinfo, Creature* creature) ++{ ++ FOREACH_SCRIPT(AllCreatureScript)->Creature_SelectLevel(cinfo, creature); ++} ++ + bool ScriptMgr::OnGossipHello(Player* player, GameObject* go) + { + ASSERT(player); +@@ -908,6 +922,22 @@ bool ScriptMgr::OnGossipHello(Player* player, GameObject* go) + return tmpscript->OnGossipHello(player, go); + } + ++void ScriptMgr::SetInitialWorldSettings() ++{ ++ FOREACH_SCRIPT(WorldScript)->SetInitialWorldSettings(); ++} ++ ++float ScriptMgr::VAS_Script_Hooks() ++{ ++ float VAS_Script_Hook_Version = 1.03f; ++ ++// TC_LOG_DEBUG(LOG_FILTER_WORLDSERVER, "------------------------------------------------------------"); ++// TC_LOG_DEBUG(LOG_FILTER_WORLDSERVER, " Powered by {VAS} Script Hooks v%4.2f : Updated by Natfoth",VAS_Script_Hook_Version); ++// TC_LOG_DEBUG(LOG_FILTER_WORLDSERVER, "--------------------------------------------------------------"); ++ ++ return VAS_Script_Hook_Version; ++} ++ + bool ScriptMgr::OnGossipSelect(Player* player, GameObject* go, uint32 sender, uint32 action) + { + ASSERT(player); +@@ -1232,6 +1262,14 @@ bool ScriptMgr::OnCriteriaCheck(uint32 scriptId, Player* source, Unit* target) + return tmpscript->OnCheck(source, target); + } + ++//Called From Unit::DealDamage ++uint32 ScriptMgr::DealDamage(Unit* AttackerUnit, Unit *pVictim, uint32 damage, DamageEffectType damagetype) ++{ ++ FOR_SCRIPTS_RET(UnitScript, itr, end, damage) ++ damage = itr->second->DealDamage(AttackerUnit, pVictim, damage, damagetype); ++ return damage; ++} ++ + // Player + void ScriptMgr::OnPVPKill(Player* killer, Player* killed) + { +@@ -1534,12 +1572,32 @@ SpellScriptLoader::SpellScriptLoader(const char* name) + ScriptRegistry::AddScript(this); + } + ++void ScriptMgr::ModifyHealRecieved(Unit* target, Unit* attacker, uint32& damage) ++{ ++ FOREACH_SCRIPT(UnitScript)->ModifyHealRecieved(target, attacker, damage); ++} ++ ++AllMapScript::AllMapScript(const char* name) ++ : ScriptObject(name) ++{ ++ ScriptRegistry::AddScript(this); ++} ++ ++ + ServerScript::ServerScript(const char* name) + : ScriptObject(name) + { + ScriptRegistry::AddScript(this); + } + ++AllCreatureScript::AllCreatureScript(const char* name) ++: ScriptObject(name) ++{ ++ ScriptRegistry::AddScript(this); ++} ++ ++ ++ + WorldScript::WorldScript(const char* name) + : ScriptObject(name) + { +@@ -1703,10 +1761,12 @@ template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; ++template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; ++template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; + template class ScriptRegistry; +diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h +index 5dfc0be..a064c44 100644 +--- a/src/server/game/Scripting/ScriptMgr.h ++++ b/src/server/game/Scripting/ScriptMgr.h +@@ -26,6 +26,7 @@ + #include "SharedDefines.h" + #include "World.h" + #include "Weather.h" ++#include "Unit.h" + + class AccountMgr; + class AuctionHouseObject; +@@ -63,6 +64,7 @@ class WorldSession; + + struct AchievementCriteriaData; + struct AuctionEntry; ++struct CreatureTemplate; + struct ConditionSourceInfo; + struct Condition; + struct ItemTemplate; +@@ -266,6 +268,9 @@ class WorldScript : public ScriptObject + + // Called when the world is actually shut down. + virtual void OnShutdown() { } ++ ++ // Called at End of SetInitialWorldSettings. ++ virtual void SetInitialWorldSettings() { } + }; + + class FormulaScript : public ScriptObject +@@ -298,6 +303,21 @@ class FormulaScript : public ScriptObject + virtual void OnGroupRateCalculation(float& /*rate*/, uint32 /*count*/, bool /*isRaid*/) { } + }; + ++class AllMapScript : public ScriptObject ++{ ++ protected: ++ ++ AllMapScript(const char* name); ++ ++ public: ++ ++ // Called when a player enters any Map ++ virtual void OnPlayerEnterAll(Map* /*map*/, Player* /*player*/) { } ++ ++ // Called when a player leave any Map ++ virtual void OnPlayerLeaveAll(Map* /*map*/, Player* /*player*/) { } ++}; ++ + template class MapScript : public UpdatableScript + { + MapEntry const* _mapEntry; +@@ -406,6 +426,13 @@ class UnitScript : public ScriptObject + + // Called when Spell Damage is being Dealt + virtual void ModifySpellDamageTaken(Unit* /*target*/, Unit* /*attacker*/, int32& /*damage*/) { } ++ ++ // Called when Heal is Recieved ++ virtual void ModifyHealRecieved(Unit* /*target*/, Unit* /*attacker*/, uint32& /*damage*/) { } ++ ++ //VAS AutoBalance ++ virtual uint32 DealDamage(Unit* AttackerUnit, Unit *pVictim, uint32 damage, DamageEffectType damagetype) { return damage;} ++ + }; + + class CreatureScript : public UnitScript, public UpdatableScript +@@ -444,6 +471,21 @@ class CreatureScript : public UnitScript, public UpdatableScript + virtual CreatureAI* GetAI(Creature* /*creature*/) const { return NULL; } + }; + ++class AllCreatureScript : public ScriptObject ++{ ++ protected: ++ ++ AllCreatureScript(const char* name); ++ ++ public: ++ ++ // Called from End of Creature Update. ++ virtual void OnAllCreatureUpdate(Creature* /*creature*/, uint32 /*diff*/) { } ++ ++ // Called from End of Creature SelectLevel. ++ virtual void Creature_SelectLevel(const CreatureTemplate* /*cinfo*/, Creature* /*creature*/) { } ++}; ++ + class GameObjectScript : public ScriptObject, public UpdatableScript + { + protected: +@@ -880,6 +922,10 @@ class ScriptMgr + void Unload(); + void UnloadUnusedScripts(); + ++ public: /* {VAS} Script Hooks */ ++ ++ float VAS_Script_Hooks(); ++ + public: /* SpellScriptLoader */ + + void CreateSpellScripts(uint32 spellId, std::list& scriptVector); +@@ -906,6 +952,7 @@ class ScriptMgr + void OnWorldUpdate(uint32 diff); + void OnStartup(); + void OnShutdown(); ++ void SetInitialWorldSettings(); + + public: /* FormulaScript */ + +@@ -917,6 +964,11 @@ class ScriptMgr + void OnGainCalculation(uint32& gain, Player* player, Unit* unit); + void OnGroupRateCalculation(float& rate, uint32 count, bool isRaid); + ++ public: /* AllScript */ ++ ++ void OnPlayerEnterMapAll(Map* map, Player* player); ++ void OnPlayerLeaveMapAll(Map* map, Player* player); ++ + public: /* MapScript */ + + void OnCreateMap(Map* map); +@@ -939,6 +991,12 @@ class ScriptMgr + bool OnItemExpire(Player* player, ItemTemplate const* proto); + bool OnItemRemove(Player* player, Item* item); + ++ public: /* AllCreatureScript */ ++ ++ void OnAllCreatureUpdate(Creature* creature, uint32 diff); ++ void Creature_SelectLevel(const CreatureTemplate *cinfo, Creature* creature); ++ ++ + public: /* CreatureScript */ + + bool OnDummyEffect(Unit* caster, uint32 spellId, SpellEffIndex effIndex, Creature* target); +@@ -1097,6 +1155,8 @@ class ScriptMgr + void ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage); + void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage); + void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage); ++ void ModifyHealRecieved(Unit* target, Unit* attacker, uint32& addHealth); ++ uint32 DealDamage(Unit* AttackerUnit, Unit *pVictim,uint32 damage,DamageEffectType damagetype); + + public: /* Scheduled scripts */ + +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..876fc03 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1305,6 +1305,22 @@ void World::LoadConfigSettings(bool reload) + m_bool_configs[CONFIG_PDUMP_NO_OVERWRITE] = sConfigMgr->GetBoolDefault("PlayerDump.DisallowOverwrite", true); + m_bool_configs[CONFIG_UI_QUESTLEVELS_IN_DIALOGS] = sConfigMgr->GetBoolDefault("UI.ShowQuestLevelsInDialogs", false); + ++ //VAS ++ m_int_configs[VAS_VasDebug] = sConfigMgr->GetIntDefault ("VAS.AutoBalance.Debug", 1); ++ m_int_configs[VAS_AutoInstance] = sConfigMgr->GetIntDefault ("VAS.AutoBalance.AutoInstance", 1); ++ m_int_configs[VAS_PlayerChangeNotify] = sConfigMgr->GetIntDefault ("VAS.AutoBalance.PlayerChangeNotify", 1); ++ ++ m_float_configs[VAS_Config_xPlayer] = sConfigMgr->GetFloatDefault("VAS.AutoBalance.XPlayer", 1.0f); ++ m_float_configs[VAS_Min_D_Mod] = sConfigMgr->GetFloatDefault("Min.D.Mod", 0.10f); ++ m_float_configs[VAS_Min_HP_Mod] = sConfigMgr->GetFloatDefault("Min.HP.Mod", 0.20f); ++ ++ std::string VAS_AutoBalance_40_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.40.Name", ""); ++ std::string VAS_AutoBalance_25_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.25.Name", ""); ++ std::string VAS_AutoBalance_20_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.20.Name", ""); ++ std::string VAS_AutoBalance_10_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.10.Name", ""); ++ std::string VAS_AutoBalance_5_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.5.Name", ""); ++ std::string VAS_AutoBalance_2_Name = sConfigMgr->GetStringDefault("VAS.AutoBalance.2.Name", ""); ++ + // Wintergrasp battlefield + m_bool_configs[CONFIG_WINTERGRASP_ENABLE] = sConfigMgr->GetBoolDefault("Wintergrasp.Enable", false); + m_int_configs[CONFIG_WINTERGRASP_PLR_MAX] = sConfigMgr->GetIntDefault("Wintergrasp.PlayerMax", 100); +@@ -1896,6 +1912,9 @@ void World::SetInitialWorldSettings() + // Delete all characters which have been deleted X days before + Player::DeleteOldCharacters(); + ++ TC_LOG_INFO("server.loading", "Loading VAS Autobalance..."); ++ sScriptMgr->SetInitialWorldSettings(); ++ + TC_LOG_INFO("server.loading", "Initialize AuctionHouseBot..."); + sAuctionBot->Initialize(); + +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 1eb6feb..7ff1001 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -226,6 +226,9 @@ enum WorldFloatConfigs + CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS, + CONFIG_THREAT_RADIUS, + CONFIG_CHANCE_OF_GM_SURVEY, ++ VAS_Config_xPlayer, ++ VAS_Min_D_Mod, ++ VAS_Min_HP_Mod, + CONFIG_STATS_LIMITS_DODGE, + CONFIG_STATS_LIMITS_PARRY, + CONFIG_STATS_LIMITS_BLOCK, +@@ -419,6 +422,9 @@ enum WorldIntConfigs + CONFIG_WARDEN_CLIENT_BAN_DURATION, + CONFIG_WARDEN_NUM_MEM_CHECKS, + CONFIG_WARDEN_NUM_OTHER_CHECKS, ++ VAS_VasDebug, ++ VAS_AutoInstance, ++ VAS_PlayerChangeNotify, + CONFIG_WINTERGRASP_PLR_MAX, + CONFIG_WINTERGRASP_PLR_MIN, + CONFIG_WINTERGRASP_PLR_MIN_LVL, +@@ -736,6 +742,14 @@ class World + /// Get the path where data (dbc, maps) are stored on disk + std::string const& GetDataPath() const { return m_dataPath; } + ++ /// Return the Mob IDs to be Autobalanced ++ std::string GetVAS40() const { return VAS_AutoBalance_40_Name; } ++ std::string GetVAS25() const { return VAS_AutoBalance_25_Name; } ++ std::string GetVAS20() const { return VAS_AutoBalance_20_Name; } ++ std::string GetVAS10() const { return VAS_AutoBalance_10_Name; } ++ std::string GetVAS5() const { return VAS_AutoBalance_5_Name; } ++ std::string GetVAS2() const { return VAS_AutoBalance_2_Name; } ++ + /// When server started? + time_t const& GetStartTime() const { return m_startTime; } + /// What time is it? +@@ -911,6 +925,14 @@ class World + uint32 m_ShutdownTimer; + uint32 m_ShutdownMask; + ++ std::string VAS_AutoBalance_40_Name; ++ std::string VAS_AutoBalance_25_Name; ++ std::string VAS_AutoBalance_20_Name; ++ std::string VAS_AutoBalance_10_Name; ++ std::string VAS_AutoBalance_5_Name; ++ std::string VAS_AutoBalance_2_Name; ++ std::string VAS_color; ++ + uint32 m_CleaningFlags; + + bool m_isClosed; +diff --git a/src/server/scripts/Custom/VAS_AutoBalance.cpp b/src/server/scripts/Custom/VAS_AutoBalance.cpp +new file mode 100644 +index 0000000..fa406ae +--- /dev/null ++++ b/src/server/scripts/Custom/VAS_AutoBalance.cpp +@@ -0,0 +1,510 @@ ++/* ++ * Copyright (C) 2012 CVMagic ++ * Copyright (C) 2008-2010 TrinityCore ++ * Copyright (C) 2006-2009 ScriptDev2 ++ * Copyright (C) 1985-2010 {VAS} KalCorp ++ * ++ * 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, see . ++ */ ++ ++/* ++ * Script Name: AutoBalance ++ * Original Authors: KalCorp and Vaughner ++ * Maintainer(s): CVMagic ++ * Original Script Name: VAS.AutoBalance ++ * Description: This script is intended to scale based on number of players, instance mobs & world bosses' health, mana, and damage. ++ */ ++ ++ ++//#include "ScriptPCH.h" ++#include "Configuration/Config.h" ++#include "Unit.h" ++#include "Chat.h" ++#include "Creature.h" ++#include "Player.h" ++#include "ObjectMgr.h" ++#include "MapManager.h" ++#include "World.h" ++#include "Map.h" ++#include "ScriptMgr.h" ++#include ++ ++#define BOOL_TO_STRING(b) ((b)? "true":"false") ++ ++struct AutoBalanceCreatureInfo ++{ ++ uint32 instancePlayerCount; ++ float DamageMultiplier; ++}; ++ ++static std::map CreatureInfo; // A hook should be added to remove the mapped entry when the creature is dead or this should be added into the creature object ++static std::map forcedCreatureIds; // The map values correspond with the VAS.AutoBalance.XX.Name entries in the configuration file. ++ ++int GetValidDebugLevel() ++{ ++ int debugLevel = sWorld->getIntConfig(VAS_VasDebug); ++ ++ if ((debugLevel < 0) || (debugLevel > 3)) ++ { ++ return 1; ++ } ++ return debugLevel; ++} ++ ++void LoadForcedCreatureIdsFromString(std::string creatureIds, int forcedPlayerCount) // Used for reading the string from the configuration file to for those creatures who need to be scaled for XX number of players. ++{ ++ std::string delimitedValue; ++ std::stringstream creatureIdsStream; ++ ++ creatureIdsStream.str(creatureIds); ++ while (std::getline(creatureIdsStream, delimitedValue, ',')) // Process each Creature ID in the string, delimited by the comma - "," ++ { ++ int creatureId = atoi(delimitedValue.c_str()); ++ if (creatureId >= 0) ++ { ++ forcedCreatureIds[creatureId] = forcedPlayerCount; ++ } ++ } ++} ++ ++int GetForcedCreatureId(int creatureId) ++{ ++ if(forcedCreatureIds.find(creatureId) == forcedCreatureIds.end()) // Don't want the forcedCreatureIds map to blowup to a massive empty array ++ { ++ return 0; ++ } ++ return forcedCreatureIds[creatureId]; ++} ++ ++class VAS_AutoBalance_WorldScript : public WorldScript ++{ ++ public: ++ VAS_AutoBalance_WorldScript() ++ : WorldScript("VAS_AutoBalance_WorldScript") ++ { ++ } ++ ++ void OnConfigLoad(bool /*reload*/) ++ { ++ } ++ ++ void OnStartup() ++ { ++ } ++ ++ void SetInitialWorldSettings() ++ { ++ // Load from the VAS.AutoBalance.XX.Name entries in the Configuration File ++ forcedCreatureIds.clear(); ++ LoadForcedCreatureIdsFromString(sWorld->GetVAS40(), 40); ++ LoadForcedCreatureIdsFromString(sWorld->GetVAS25(), 25); ++ LoadForcedCreatureIdsFromString(sWorld->GetVAS10(), 10); ++ LoadForcedCreatureIdsFromString(sWorld->GetVAS5(), 5); ++ LoadForcedCreatureIdsFromString(sWorld->GetVAS2(), 2); ++ ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, "----------------------------------------------------"); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " Powered by {VAS} AutoBalance"); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, "----------------------------------------------------"); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " xPlayer = %4.1f ", sWorld->getFloatConfig(VAS_Config_xPlayer)); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " AutoInstance = %u ", sWorld->getIntConfig(VAS_AutoInstance)); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " PlayerChangeNotify = %u ", sWorld->getIntConfig(VAS_PlayerChangeNotify)); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " Min.D.Mod = %4.2f ", sWorld->getFloatConfig(VAS_Min_D_Mod)); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " Min.HP.Mod = %4.2f ", sWorld->getFloatConfig(VAS_Min_HP_Mod)); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, " VasDebug = %u ", GetValidDebugLevel()); ++ //sLog->outInfo(LOG_FILTER_WORLDSERVER, "----------------------------------------------------\n"); ++ } ++ ++}; ++ ++class VAS_AutoBalance_PlayerScript : public PlayerScript ++{ ++ public: ++ VAS_AutoBalance_PlayerScript() ++ : PlayerScript("VAS_AutoBalance_PlayerScript") ++ { ++ } ++ ++ void OnLogin(Player *Player, bool /*firstLogin*/) ++ { ++ //TC_LOG_INFO("vas.player.loading", "### VAS_AutoBalance_PlayerScript - OnLogin Player=%s", Player->GetName()); ++ } ++}; ++ ++class VAS_AutoBalance_UnitScript : public UnitScript ++{ ++ public: ++ VAS_AutoBalance_UnitScript() ++ : UnitScript("VAS_AutoBalance_UnitScript") ++ { ++ } ++ ++ uint32 DealDamage(Unit* AttackerUnit, Unit *playerVictim, uint32 damage, DamageEffectType damagetype) ++ { ++ if (AttackerUnit->GetMap()->IsDungeon() && playerVictim->GetMap()->IsDungeon()) ++ if (AttackerUnit->GetTypeId() != TYPEID_PLAYER) ++ { ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - VAS_Unit_DealDamage Attacker=%s Victim=%s Start Damage=%u",AttackerUnit->GetName(),playerVictim->GetName(),damage); ++ damage = VAS_Modifer_DealDamage(AttackerUnit,damage); ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - VAS_Unit_DealDamage Attacker=%s Victim=%s End Damage=%u",AttackerUnit->GetName(),playerVictim->GetName(),damage); ++ } ++ return damage; ++ } ++ ++ uint32 HandlePeriodicDamageAurasTick(Unit *target, Unit *caster, int32 damage) ++ { ++ if (caster->GetMap()->IsDungeon() && target->GetMap()->IsDungeon()) ++ if (caster->GetTypeId() != TYPEID_PLAYER) ++ { ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - VAS_Unit_HandlePeriodicDamage Attacker=%s Victim=%s Start Damage=%u",caster->GetName(),target->GetName(),damage); ++ ++ if (!((caster->IsHunterPet() || caster->IsPet() || caster->IsSummon()) && caster->IsControlledByPlayer())) ++ damage = (float)damage * (float)CreatureInfo[caster->GetGUID()].DamageMultiplier; ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - VAS_Unit_HandlePeriodicDamage Attacker=%s Victim=%s End Damage=%u",caster->GetName(),target->GetName(),damage); ++ } ++ return damage; ++ } ++ ++ void CalculateSpellDamageTaken(SpellNonMeleeDamage *damageInfo, int32 damage, SpellInfo const *spellInfo, WeaponAttackType attackType, bool crit) ++ { ++ if ((damageInfo->attacker->GetMap()->IsDungeon() && damageInfo->target->GetMap()->IsDungeon()) || ( damageInfo->attacker->GetMap()->IsBattleground() && damageInfo->target->GetMap()->IsBattleground())) ++ { ++ if (damageInfo->attacker->GetTypeId() != TYPEID_PLAYER) ++ { ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - CalculateSpellDamageTaken Attacker=%s Victim=%s Start Damage=%u",damageInfo->attacker->GetName(),damageInfo->target->GetName(),damageInfo->damage); ++ ++ if ((damageInfo->attacker->IsHunterPet() || damageInfo->attacker->IsPet() || damageInfo->attacker->IsSummon()) && damageInfo->attacker->IsControlledByPlayer()) ++ return; ++ ++ damageInfo->damage = (float)damageInfo->damage * (float)CreatureInfo[damageInfo->attacker->GetGUID()].DamageMultiplier; ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - CalculateSpellDamageTaken Attacker=%s Victim=%s End Damage=%u",damageInfo->attacker->GetName(),damageInfo->target->GetName(),damageInfo->damage); ++ } ++ } ++ return; ++ } ++ ++ void CalculateMeleeDamage(Unit *playerVictim, uint32 damage, CalcDamageInfo *damageInfo, WeaponAttackType attackType) ++ { ++ // Make sure the Attacker and the Victim are in the same location, in addition that the attacker is not player. ++ if (((damageInfo->attacker->GetMap()->IsDungeon() && damageInfo->target->GetMap()->IsDungeon()) || (damageInfo->attacker->GetMap()->IsBattleground() && damageInfo->target->GetMap()->IsBattleground())) && (damageInfo->attacker->GetTypeId() != TYPEID_PLAYER)) ++ if (!((damageInfo->attacker->IsHunterPet() || damageInfo->attacker->IsPet() || damageInfo->attacker->IsSummon()) && damageInfo->attacker->IsControlledByPlayer())) // Make sure that the attacker Is not a Pet of some sort ++ { ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - CalculateMeleeDamage Attacker=%s Victim=%s Start Damage=%u",damageInfo->attacker->GetName(),damageInfo->target->GetName(),damageInfo->damage); ++ ++ damageInfo->damage = (float)damageInfo->damage * (float)CreatureInfo[damageInfo->attacker->GetGUID()].DamageMultiplier; ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_UnitScript - CalculateMeleeDamage Attacker=%s Victim=%s End Damage=%u",damageInfo->attacker->GetName(),damageInfo->target->GetName(),damageInfo->damage); ++ } ++ return; ++ } ++ ++ uint32 VAS_Modifer_DealDamage(Unit* AttackerUnit,uint32 damage) ++ { ++ if ((AttackerUnit->IsHunterPet() || AttackerUnit->IsPet() || AttackerUnit->IsSummon()) && AttackerUnit->IsControlledByPlayer()) ++ return damage; ++ ++ float damageMultiplier = CreatureInfo[AttackerUnit->GetGUID()].DamageMultiplier; ++ ++ return damage * damageMultiplier; ++ ++ } ++ ++}; ++ ++ ++class VAS_AutoBalance_AllMapScript : public AllMapScript ++{ ++ public: ++ VAS_AutoBalance_AllMapScript() ++ : AllMapScript("VAS_AutoBalance_AllMapScript") ++ { ++ } ++ ++ void OnPlayerEnterAll(Map* map, Player* player) ++ { ++ if (GetValidDebugLevel() >= 2) ++ { ++ //sLog->outInfo(LOG_FILTER_TSCR, "----------------------------------------------------"); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS_AutoBalance_AllMapScript - OnPlayerEnterAll"); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## For InsatanceID %u",map->GetInstanceId()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## IsDungeon= %u",map->GetEntry()->IsDungeon()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## For Map %u",player->GetMapId()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## PlayersInMap %u",map->GetPlayersCountExceptGMs()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## pDifficulty %u",uint32(player->GetDifficulty(player->GetMap()->IsHeroic()))); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## pGetDungeonDifficulty %u",uint32(player->GetDungeonDifficulty())); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## pGetRaidDifficulty %u",uint32(player->GetRaidDifficulty())); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## maxPlayers %u",((InstanceMap*)sMapMgr->FindMap(player->GetMapId(), player->GetInstanceId()))->GetMaxPlayers()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## IsHeroic=%s IsRaid=%s IsRegularDifficulty=%s IsRaidOrHeroicDungeon=%s IsNonRaidDungeon=%s",BOOL_TO_STRING(player->GetMap()->IsHeroic()),BOOL_TO_STRING(player->GetMap()->IsRaid()),BOOL_TO_STRING(player->GetMap()->IsRegularDifficulty()),BOOL_TO_STRING(player->GetMap()->IsRaidOrHeroicDungeon()),BOOL_TO_STRING(player->GetMap()->IsNonRaidDungeon())); ++ //sLog->outInfo(LOG_FILTER_TSCR, "----------------------------------------------------\n"); ++ } ++ ++ if (sWorld->getIntConfig(VAS_PlayerChangeNotify) >= 1) ++ { ++ if ((map->GetEntry()->IsDungeon()) && !player->IsGameMaster() ) ++ { ++ Map::PlayerList const &playerList = map->GetPlayers(); ++ if (!playerList.isEmpty()) ++ { ++ for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) ++ { ++ if (Player* playerHandle = playerIteration->GetSource()) ++ { ++ ChatHandler chatHandle = ChatHandler(playerHandle->GetSession()); ++ chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s entered the Instance %s. Auto setting player count to %u |r",player->GetName().c_str(),map->GetMapName(),map->GetPlayersCountExceptGMs()); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ void OnPlayerLeaveAll(Map* map, Player* player) ++ { ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "#### VAS_AutoBalance_AllMapScript - OnPlayerLeaveAll map=%s player=%s", map->GetMapName(),player->GetName()); ++ ++ int instancePlayerCount = map->GetPlayersCountExceptGMs() - 1; ++ ++ if (instancePlayerCount >=1) ++ { ++ if (GetValidDebugLevel() >= 2) ++ { ++ //sLog->outInfo(LOG_FILTER_TSCR, "----------------------------------------------------"); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS_AutoBalance_AllMapScript - OnPlayerLeaveAll"); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## For InsatanceID %u",map->GetInstanceId()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## IsDungeon= %u",map->GetEntry()->IsDungeon()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## For Map %u",player->GetMapId()); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## PlayersInMap %u",instancePlayerCount); ++ //sLog->outInfo(LOG_FILTER_TSCR, "----------------------------------------------------\n"); ++ } ++ ++ if (sWorld->getIntConfig(VAS_PlayerChangeNotify) >= 1) ++ { ++ if ((map->GetEntry()->IsDungeon()) && !player->IsGameMaster()) ++ { ++ Map::PlayerList const &playerList = map->GetPlayers(); ++ if (!playerList.isEmpty()) ++ { ++ for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) ++ { ++ if (Player* playerHandle = playerIteration->GetSource()) ++ { ++ ChatHandler chatHandle = ChatHandler(playerHandle->GetSession()); ++ chatHandle.PSendSysMessage("|cffFF0000 [VAS-AutoBalance]|r|cffFF8000 %s left the Instance %s. Auto setting player count to %u |r",player->GetName().c_str(),map->GetMapName(),instancePlayerCount); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++}; ++ ++class VAS_AutoBalance_WorldMapScript : public WorldMapScript ++{ ++ public: ++ VAS_AutoBalance_WorldMapScript() ++ : WorldMapScript("VAS_AutoBalance_WorldMapScript",0) ++ { ++ } ++ ++ void OnPlayerEnter(Map* map, Player* player) ++ { ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_WorldMapScript - OnPlayerEnter Map=%s player=%s",map->GetMapName(),player->GetName()); ++ } ++ ++ void OnPlayerLeave(Map* map, Player* player) ++ { ++ ++ //if (GetValidDebugLevel() >= 3) ++ //sLog->outInfo(LOG_FILTER_TSCR, "### VAS_AutoBalance_WorldMapScript - OnPlayerLeave Map=%s player=%s",map->GetMapName(),player->GetName()); ++ } ++}; ++ ++ ++class VAS_AutoBalance_AllCreatureScript : public AllCreatureScript ++{ ++ public: ++ VAS_AutoBalance_AllCreatureScript() ++ : AllCreatureScript("VAS_AutoBalance_AllCreatureScript") ++ { ++ } ++ ++ ++ void Creature_SelectLevel(const CreatureTemplate *creatureTemplate, Creature* creature) ++ { ++ ++ if (creature->GetMap()->IsDungeon()) ++ { ++ ModifyCreatureAttributes(creature); ++ CreatureInfo[creature->GetGUID()].instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs(); ++ } ++ } ++ ++ void OnAllCreatureUpdate(Creature* creature, uint32 diff) ++ { ++ if(!(CreatureInfo[creature->GetGUID()].instancePlayerCount == creature->GetMap()->GetPlayersCountExceptGMs())) ++ { ++ if (creature->GetMap()->IsDungeon() || creature->GetMap()->IsBattleground()) ++ ModifyCreatureAttributes(creature); ++ CreatureInfo[creature->GetGUID()].instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs(); ++ } ++ } ++ ++ void ModifyCreatureAttributes(Creature* creature) ++ { ++ if(((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) || sWorld->getIntConfig(VAS_AutoInstance) < 1 || creature->GetMap()->GetPlayersCountExceptGMs() <= 0) ++ { ++ return; ++ } ++ ++ CreatureTemplate const *creatureTemplate = creature->GetCreatureTemplate(); ++ CreatureBaseStats const* creatureStats = sObjectMgr->GetCreatureBaseStats(creature->getLevel(), creatureTemplate->unit_class); ++ ++ float damageMultiplier = 1.0f; ++ float healthMultiplier = 1.0f; ++ ++ uint32 baseHealth = creatureStats->GenerateHealth(creatureTemplate); ++ uint32 baseMana = creatureStats->GenerateMana(creatureTemplate); ++ uint32 instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs(); ++ uint32 maxNumberOfPlayers = ((InstanceMap*)sMapMgr->FindMap(creature->GetMapId(), creature->GetInstanceId()))->GetMaxPlayers(); ++ uint32 scaledHealth = 0; ++ uint32 scaledMana = 0; ++ ++ // VAS SOLO - By MobID ++ if(GetForcedCreatureId(creatureTemplate->Entry) > 0) ++ { ++ maxNumberOfPlayers = GetForcedCreatureId(creatureTemplate->Entry); // Force maxNumberOfPlayers to be changed to match the Configuration entry. ++ } ++ ++ // (tanh((X-2.2)/1.5) +1 )/2 // 5 Man formula X = Number of Players ++ // (tanh((X-5)/2) +1 )/2 // 10 Man Formula X = Number of Players ++ // (tanh((X-16.5)/6.5) +1 )/2 // 25 Man Formula X = Number of players ++ // ++ // Note: The 2.2, 5, and 16.5 are the number of players required to get 50% health. ++ // It's not required this be a whole number, you'd adjust this to raise or lower ++ // the hp modifier for per additional player in a non-whole group. These ++ // values will eventually be part of the configuration file once I finalize the mod. ++ // ++ // The 1.5, 2, and 6.5 modify the rate of percentage increase between ++ // number of players. Generally the closer to the value of 1 you have this ++ // the less gradual the rate will be. For example in a 5 man it would take 3 ++ // total players to face a mob at full health. ++ // ++ // The +1 and /2 values raise the TanH function to a positive range and make ++ // sure the modifier never goes above the value or 1.0 or below 0. ++ // ++ // Lastly this formula has one side effect on full groups Bosses and mobs will ++ // never have full health, this can be tested against by making sure the number ++ // of players match the maxNumberOfPlayers variable. ++ ++ switch (maxNumberOfPlayers) ++ { ++ case 40: ++ healthMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers; // 40 Man Instances oddly enough scale better with the old formula ++ break; ++ case 25: ++ healthMultiplier = (tanh((instancePlayerCount - 16.5f) / 1.5f) + 1.0f) / 2.0f; ++ break; ++ case 10: ++ healthMultiplier = (tanh((instancePlayerCount - 4.5f) / 1.5f) + 1.0f) / 2.0f; ++ break; ++ case 2: ++ healthMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers; // Two Man Creatures are too easy if handled by the 5 man formula, this would only ++ break; // apply in the situation where it's specified in the configuration file. ++ default: ++ healthMultiplier = (tanh((instancePlayerCount - 2.2f) / 1.5f) + 1.0f) / 2.0f; // default to a 5 man group ++ } ++ ++ // VAS SOLO - Map 0,1 and 530 ( World Mobs ) // This may be where VAS_AutoBalance_CheckINIMaps might have come into play. None the less this is ++ if((creature->GetMapId() == 0 || creature->GetMapId() == 1 || creature->GetMapId() == 530) && (creature->isElite() || creature->isWorldBoss())) // specific to World Bosses and elites in those Maps, this is going to use the entry XPlayer in place of instancePlayerCount. ++ { ++ if(baseHealth > 800000){ ++ healthMultiplier = (tanh((sWorld->getFloatConfig(VAS_Config_xPlayer) - 5.0f) / 1.5f) + 1.0f) / 2.0f; ++ }else{ ++ healthMultiplier = (tanh((sWorld->getFloatConfig(VAS_Config_xPlayer) - 2.2f) / 1.5f) + 1.0f) / 2.0f; // Assuming a 5 man configuration, as World Bosses have been relatively retired since BC so unless the boss has some substantial baseHealth ++ } ++ ++ } ++ ++ // Ensure that the healthMultiplier is not lower than the configuration specified value. -- This may be Deprecated later. ++ if(healthMultiplier <= sWorld->getFloatConfig(VAS_Min_HP_Mod) ) ++ { ++ healthMultiplier = sWorld->getFloatConfig(VAS_Min_HP_Mod); ++ } ++ ++ //Getting the list of Classes in this group - this will be used later on to determine what additional scaling will be required based on the ratio of tank/dps/healer ++ //GetPlayerClassList(creature, playerClassList); // Update playerClassList with the list of all the participating Classes ++ ++ ++ scaledHealth = uint32((baseHealth * healthMultiplier) + 1.0f); ++ // Now adjusting Mana, Mana is something that can be scaled linearly ++ if (maxNumberOfPlayers==0){ ++ scaledMana = uint32((baseMana * healthMultiplier) + 1.0f); ++ // Now Adjusting Damage, this too is linear for now .... this will have to change I suspect. ++ damageMultiplier = healthMultiplier; ++ }else{ ++ scaledMana = ((baseMana/maxNumberOfPlayers) * instancePlayerCount); ++ // Now Adjusting Damage, this too is linear for now .... this will have to change I suspect. ++ damageMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers; ++ } ++ ++ // Can not be less then Min_D_Mod ++ if(damageMultiplier <= sWorld->getFloatConfig(VAS_Min_D_Mod)) ++ { ++ damageMultiplier = sWorld->getFloatConfig(VAS_Min_D_Mod); ++ } ++ ++ if((GetValidDebugLevel() >= 3)) ++ { ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS-AutoBalance MobID=%u MapID=%u creatureName=%s GUID=%llu instancePlayerCount=%u", creatureTemplate->Entry, creature->GetMapId(), creatureTemplate->Name.c_str(), creature->GetGUID(), instancePlayerCount); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS-AutoBalance MapDifficulty=%u Health=%u / %u healthMultiplier=%4.5f", creature->GetMap()->GetDifficulty(), scaledHealth, baseHealth, healthMultiplier); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS-AutoBalance maxNumberOfPlayers=%u IsRaid=%s", maxNumberOfPlayers, BOOL_TO_STRING(creature->GetMap()->IsRaid())); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS-AutoBalance Mana %u / %u", baseMana, scaledMana); ++ //sLog->outInfo(LOG_FILTER_TSCR, "## VAS-AutoBalance damageMultiplier=%4.2f", damageMultiplier); ++ } ++ ++ creature->SetCreateHealth(scaledHealth); ++ creature->SetMaxHealth(scaledHealth); ++ creature->ResetPlayerDamageReq(); ++ creature->SetCreateMana(scaledMana); ++ creature->SetMaxPower(POWER_MANA, scaledMana); ++ creature->SetPower(POWER_MANA, scaledMana); ++ creature->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)scaledHealth); ++ creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)scaledMana); ++ CreatureInfo[creature->GetGUID()].DamageMultiplier = damageMultiplier; ++ } ++}; ++ ++void AddSC_VAS_AutoBalance() ++{ ++ new VAS_AutoBalance_WorldScript; ++ new VAS_AutoBalance_PlayerScript; ++ new VAS_AutoBalance_UnitScript; ++ new VAS_AutoBalance_AllCreatureScript; ++ new VAS_AutoBalance_AllMapScript; ++ new VAS_AutoBalance_WorldMapScript; ++} +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..95f3755 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -55,6 +55,7 @@ PrepatchGI.Added = 0 + # AUCTION HOUSE BOT BUYER CONFIG + # LOGGING SYSTEM SETTINGS + # PACKET SPOOF PROTECTION SETTINGS ++# VAS AUTOBALANCE OPTIONS + # + ################################################################################################### + +@@ -3527,6 +3528,77 @@ PacketSpoof.BanDuration = 86400 + + # + ################################################################################################### ++# ++# VAS AUTOBALANCE OPTIONS ++# ++# VAS.AutoBalance.XPlayer ++# Set Server to level of solo mode. ++# Set to 0 to Disable VAS-SOLO Mod. ++# Example: VAS.AutoBalance.XPlayer = 1 will set everything for a 1 player game. ++# Default: 1 ++ ++VAS.AutoBalance.XPlayer = 1 ++ ++# ++# VAS.AutoBalance.AutoInstance ++# Set instances to Auto chance XPlayer depending on players in it. ++# Default: 1 (1 = ON, 0 = OFF) ++ ++VAS.AutoBalance.AutoInstance = 1 ++ ++# ++# VAS.AutoBalance.Debug ++# 0 = None ++# 1 = Errors Only ++# 2 = Errors and Basic Information ++# 3 = All VAS Info ++# Default: 1 ++ ++VAS.AutoBalance.Debug = 2 ++ ++# ++# VAS.AutoBalance.PlayerChangeNotify ++# Set Auto Notifications to all players in Instance that player count has changed. ++# Default: 1 (1 = ON, 0 = OFF) ++ ++VAS.AutoBalance.PlayerChangeNotify = 1 ++ ++# ++# VAS.AutoBalance.Color ++# In Game Color for mod information in chat window. ++# Default: cffFF8000 (Orange) ++ ++VAS.AutoBalance.Color = cffFF8000 ++ ++# ++# Min.HP.Mod ++# Minimum Modifier setting for Health Modification ++# Default: 0.20 ++ ++Min.HP.Mod = 0.20 ++ ++# ++# Min.D.Mod ++# Minimum Modifier setting for Damage Modification ++# Default: 0.10 ++ ++Min.D.Mod = 0.10 ++ ++# ++# VAS.AutoBalance.XX.Name ++# Sets MobIDs for the group they belong to. ++# All 5 Man Mobs should go in VAS.AutoBalance.5.Name ++# All 10 Man Mobs should go in VAS.AutoBalance.10.Name etc. ++ ++VAS.AutoBalance.40.Name = "11583,16441,30057,13020,15589,14435,18192,14889,14888,14887,14890,15302,15818,15742,15741,15740,18338" ++VAS.AutoBalance.25.Name = "22997,21966,21965,21964,21806,21215,21845,19728,12397,17711,18256,18192," ++VAS.AutoBalance.10.Name = "15689,15550,16152,17521,17225,16028,29324,31099" ++VAS.AutoBalance.5.Name = "15203,15204,15205,15305,6109,26801,30508,26799,30495,26803,30497,27859,27249" ++VAS.AutoBalance.2.Name = "25549,24558,25574,24559,25556,25557,25578,24561,25555,24555,25541,24553,25550,24554,24552,25564,15931,29373" ++ ++ ++# ++################################################################################################### \ No newline at end of file diff --git a/borrar_fatiga.diff b/borrar_fatiga.diff new file mode 100644 index 0000000..643061c --- /dev/null +++ b/borrar_fatiga.diff @@ -0,0 +1,57 @@ + src/server/game/Entities/Player/Player.cpp | 8 +++++++- + src/server/worldserver/worldserver.conf.dist | 7 +++++++ + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..7c2855a 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -1005,8 +1005,11 @@ int32 Player::getMaxTimer(MirrorTimerType timer) + { + switch (timer) + { ++ if(sConfigMgr->GetBoolDefault("fatigue.enabled", true)) // If "fatigue.enabled" is enabled ++ { + case FATIGUE_TIMER: + return MINUTE * IN_MILLISECONDS; ++ } + case BREATH_TIMER: + { + if (!IsAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) || GetSession()->GetSecurity() >= AccountTypes(sWorld->getIntConfig(CONFIG_DISABLE_BREATHING))) +@@ -1089,6 +1092,9 @@ void Player::HandleDrowning(uint32 time_diff) + } + + // In dark water ++if(sConfigMgr->GetBoolDefault("fatigue.enabled", true)) // If "fatigue.enabled" is enabled ++{ ++ + if (m_MirrorTimerFlags & UNDERWARER_INDARKWATER) + { + // Fatigue timer not activated - activate it +@@ -1125,7 +1131,7 @@ void Player::HandleDrowning(uint32 time_diff) + else if (m_MirrorTimerFlagsLast & UNDERWARER_INDARKWATER) + SendMirrorTimer(FATIGUE_TIMER, DarkWaterTime, m_MirrorTimer[FATIGUE_TIMER], 10); + } +- ++} + if (m_MirrorTimerFlags & (UNDERWATER_INLAVA /*| UNDERWATER_INSLIME*/) && !(_lastLiquid && _lastLiquid->SpellId)) + { + // Breath timer not activated - activate it +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..463a7da 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -812,6 +812,13 @@ RecruitAFriend.MaxDifference = 4 + DisableWaterBreath = 4 + + # ++# Enable or Disable "Fatigue" timer ++# default = 1 (enabled) ++# = 0 (disabled) ++ ++fatigue.enabled = 1 ++ ++# + # AllFlightPaths + # Description: Character knows all flight paths (of both factions) after creation. + # Default: 0 - (Disabled) \ No newline at end of file diff --git a/crossfaction_bg.diff b/crossfaction_bg.diff new file mode 100644 index 0000000..3f0d8cd --- /dev/null +++ b/crossfaction_bg.diff @@ -0,0 +1,1452 @@ + src/server/game/Battlegrounds/Arena.cpp | 4 +- + src/server/game/Battlegrounds/Battleground.cpp | 55 ++-- + src/server/game/Battlegrounds/Battleground.h | 2 +- + src/server/game/Battlegrounds/BattlegroundMgr.cpp | 2 +- + .../game/Battlegrounds/BattlegroundQueue.cpp | 83 +++-- + src/server/game/Battlegrounds/BattlegroundQueue.h | 11 +- + .../game/Battlegrounds/Zones/BattlegroundAB.cpp | 2 +- + .../game/Battlegrounds/Zones/BattlegroundAV.cpp | 19 +- + .../game/Battlegrounds/Zones/BattlegroundWS.cpp | 3 +- + src/server/game/CMakeLists.txt | 2 + + src/server/game/Cfbg/Cfbg.cpp | 347 +++++++++++++++++++++ + src/server/game/Cfbg/Cfbg.h | 44 +++ + src/server/game/Entities/Player/Player.cpp | 97 ++++-- + src/server/game/Entities/Player/Player.h | 36 ++- + src/server/game/Entities/Unit/Unit.cpp | 15 + + src/server/game/Entities/Unit/Unit.h | 4 +- + src/server/game/Handlers/BattleGroundHandler.cpp | 2 +- + src/server/game/Handlers/CharacterHandler.cpp | 3 + + src/server/game/Handlers/ChatHandler.cpp | 4 + + src/server/game/Handlers/MiscHandler.cpp | 15 + + src/server/game/Handlers/QueryHandler.cpp | 2 +- + src/server/game/World/World.cpp | 2 + + src/server/game/World/World.h | 1 + + src/server/worldserver/worldserver.conf.dist | 12 + + 24 files changed, 666 insertions(+), 101 deletions(-) + create mode 100644 src/server/game/Cfbg/Cfbg.cpp + create mode 100644 src/server/game/Cfbg/Cfbg.h + +diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp +index dc7f5b2..4defea7 100644 +--- a/src/server/game/Battlegrounds/Arena.cpp ++++ b/src/server/game/Battlegrounds/Arena.cpp +@@ -40,9 +40,9 @@ Arena::Arena() + void Arena::AddPlayer(Player* player) + { + Battleground::AddPlayer(player); +- PlayerScores[player->GetGUIDLow()] = new ArenaScore(player->GetGUID(), player->GetBGTeam()); ++ PlayerScores[player->GetGUIDLow()] = new ArenaScore(player->GetGUID(), player->GetTeam()); + +- if (player->GetBGTeam() == ALLIANCE) // gold ++ if (player->GetTeam() == ALLIANCE) // gold + { + if (player->GetTeam() == HORDE) + player->CastSpell(player, SPELL_HORDE_GOLD_FLAG, true); +diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp +index e3ff372..6c67d2f 100644 +--- a/src/server/game/Battlegrounds/Battleground.cpp ++++ b/src/server/game/Battlegrounds/Battleground.cpp +@@ -286,7 +286,7 @@ inline void Battleground::_CheckSafePositions(uint32 diff) + if (Player* player = ObjectAccessor::FindPlayer(itr->first)) + { + Position pos = player->GetPosition(); +- Position const* startPos = GetTeamStartPosition(Battleground::GetTeamIndexByTeamId(player->GetBGTeam())); ++ Position const* startPos = GetTeamStartPosition(Battleground::GetTeamIndexByTeamId(player->GetTeam())); + if (pos.GetExactDistSq(startPos) > maxDist) + { + TC_LOG_DEBUG("bg.battleground", "BATTLEGROUND: Sending %s back to start location (map: %u) (possible exploit)", player->GetName().c_str(), GetMapId()); +@@ -502,7 +502,7 @@ inline void Battleground::_ProcessJoin(uint32 diff) + WorldPacket status; + BattlegroundQueueTypeId bgQueueTypeId = sBattlegroundMgr->BGQueueTypeId(m_TypeID, GetArenaType()); + uint32 queueSlot = player->GetBattlegroundQueueIndex(bgQueueTypeId); +- sBattlegroundMgr->BuildBattlegroundStatusPacket(&status, this, queueSlot, STATUS_IN_PROGRESS, 0, GetStartTime(), GetArenaType(), player->GetBGTeam()); ++ sBattlegroundMgr->BuildBattlegroundStatusPacket(&status, this, queueSlot, STATUS_IN_PROGRESS, 0, GetStartTime(), GetArenaType(), player->GetTeam()); + player->SendDirectMessage(&status); + + player->RemoveAurasDueToSpell(SPELL_ARENA_PREPARATION); +@@ -672,23 +672,34 @@ void Battleground::RewardHonorToTeam(uint32 Honor, uint32 TeamID) + UpdatePlayerScore(player, SCORE_BONUS_HONOR, Honor); + } + +-void Battleground::RewardReputationToTeam(uint32 faction_id, uint32 Reputation, uint32 TeamID) ++void Battleground::RewardReputationToTeam(uint32 a_faction_id, uint32 h_faction_id, uint32 Reputation, uint32 TeamID) + { +- FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction_id); +- if (!factionEntry) +- return; ++ FactionEntry const* a_factionEntry = sFactionStore.LookupEntry(a_faction_id); ++ FactionEntry const* h_factionEntry = sFactionStore.LookupEntry(h_faction_id); + +- for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) +- { +- Player* player = _GetPlayerForTeam(TeamID, itr, "RewardReputationToTeam"); +- if (!player) +- continue; ++ if (!a_factionEntry || !h_factionEntry) ++ return; + +- uint32 repGain = Reputation; +- AddPct(repGain, player->GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN)); +- AddPct(repGain, player->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_FACTION_REPUTATION_GAIN, faction_id)); +- player->GetReputationMgr().ModifyReputation(factionEntry, repGain); +- } ++ for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) ++ { ++ if (itr->second.OfflineRemoveTime) ++ continue; ++ ++ Player* player = ObjectAccessor::FindPlayer(itr->first); ++ ++ if (!player) ++ { ++ TC_LOG_ERROR("bg.battleground", "BattleGround:RewardReputationToTeam: %u not found!", itr->first); ++ continue; ++ } ++ uint32 team = player->GetTeam(); ++ if (team == TeamID) ++ ++ if (Player* player = _GetPlayerForTeam(TeamID, itr, "RewardReputationToTeam")) ++ { ++ player->GetReputationMgr().ModifyReputation(player->GetCFSTeam() == ALLIANCE ? a_factionEntry : h_factionEntry, Reputation); ++ } ++ } + } + + void Battleground::UpdateWorldState(uint32 Field, uint32 Value) +@@ -840,7 +851,7 @@ void Battleground::EndBattleground(uint32 winner) + player->SendDirectMessage(&pvpLogData); + + WorldPacket data; +- sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), player->GetBGTeam()); ++ sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), player->GetTeam()); + player->SendDirectMessage(&data); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND, 1); + } +@@ -956,6 +967,7 @@ void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool Sen + + if (player) + { ++ player->FitPlayerInTeam(false, this); + // Do next only if found in battleground + player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE); // We're not in BG. + // reset destination bg team +@@ -1024,7 +1036,7 @@ void Battleground::AddPlayer(Player* player) + + // score struct must be created in inherited class + +- uint32 team = player->GetBGTeam(); ++ uint32 team = player->GetTeam(); + + BattlegroundPlayer bp; + bp.OfflineRemoveTime = 0; +@@ -1075,6 +1087,7 @@ void Battleground::AddPlayer(Player* player) + // setup BG group membership + PlayerAddedToBGCheckIfBGIsRunning(player); + AddOrSetPlayerToCorrectBgGroup(player, team); ++ player->FitPlayerInTeam(true, this); + } + + // this method adds player to his team's bg group, or sets his correct group if player is already in bg group +@@ -1144,8 +1157,8 @@ void Battleground::EventPlayerLoggedOut(Player* player) + + // 1 player is logging out, if it is the last, then end arena! + if (isArena()) +- if (GetAlivePlayersCountByTeam(player->GetBGTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetBGTeam()))) +- EndBattleground(GetOtherTeam(player->GetBGTeam())); ++ if (GetAlivePlayersCountByTeam(player->GetTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetTeam()))) ++ EndBattleground(GetOtherTeam(player->GetTeam())); + } + } + +@@ -1758,7 +1771,7 @@ void Battleground::PlayerAddedToBGCheckIfBGIsRunning(Player* player) + BuildPvPLogDataPacket(data); + player->SendDirectMessage(&data); + +- sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetBGTeam()); ++ sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetTeam()); + player->SendDirectMessage(&data); + } + +diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h +index 19414e6..715ae82 100644 +--- a/src/server/game/Battlegrounds/Battleground.h ++++ b/src/server/game/Battlegrounds/Battleground.h +@@ -355,7 +355,7 @@ class Battleground + void CastSpellOnTeam(uint32 SpellID, uint32 TeamID); + void RemoveAuraOnTeam(uint32 SpellID, uint32 TeamID); + void RewardHonorToTeam(uint32 Honor, uint32 TeamID); +- void RewardReputationToTeam(uint32 faction_id, uint32 Reputation, uint32 TeamID); ++ void RewardReputationToTeam(uint32 a_faction_id, uint32 h_faction_id, uint32 Reputation, uint32 TeamID); + void UpdateWorldState(uint32 Field, uint32 Value); + void UpdateWorldStateForPlayer(uint32 Field, uint32 Value, Player* player); + virtual void EndBattleground(uint32 winner); +diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.cpp b/src/server/game/Battlegrounds/BattlegroundMgr.cpp +index 3a95f58..faa72e3 100644 +--- a/src/server/game/Battlegrounds/BattlegroundMgr.cpp ++++ b/src/server/game/Battlegrounds/BattlegroundMgr.cpp +@@ -693,7 +693,7 @@ void BattlegroundMgr::SendToBattleground(Player* player, uint32 instanceId, Batt + if (Battleground* bg = GetBattleground(instanceId, bgTypeId)) + { + uint32 mapid = bg->GetMapId(); +- uint32 team = player->GetBGTeam(); ++ uint32 team = player->GetTeam(); + if (team == 0) + team = player->GetTeam(); + +diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp +index 1006b5e..06bbf63 100644 +--- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp ++++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp +@@ -154,6 +154,10 @@ GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* grp, Battlegr + index += BG_TEAMS_COUNT; + if (ginfo->Team == HORDE) + index++; ++ ++ if (sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS) && ArenaType == 0) ++ index = BG_QUEUE_CROSSFACTION; ++ + TC_LOG_DEBUG("bg.battleground", "Adding Group to BattlegroundQueue bgTypeId : %u, bracket_id : %u, index : %u", BgTypeId, bracketId, index); + + uint32 lastOnlineTime = getMSTime(); +@@ -198,30 +202,57 @@ GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* grp, Battlegr + { + if (Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(ginfo->BgTypeId)) + { +- uint32 MinPlayers = bg->GetMinPlayersPerTeam(); +- uint32 qHorde = 0; +- uint32 qAlliance = 0; +- uint32 q_min_level = bracketEntry->minLevel; +- uint32 q_max_level = bracketEntry->maxLevel; +- GroupsQueueType::const_iterator itr; +- for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) +- if (!(*itr)->IsInvitedToBGInstanceGUID) +- qAlliance += (*itr)->Players.size(); +- for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) +- if (!(*itr)->IsInvitedToBGInstanceGUID) +- qHorde += (*itr)->Players.size(); +- +- // Show queue status to player only (when joining queue) +- if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) ++ if (sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS)) + { +- ChatHandler(leader->GetSession()).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bg->GetName().c_str(), q_min_level, q_max_level, +- qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); ++ char const* bgName = bg->GetName().c_str(); ++ uint32 MinPlayers = bg->GetMinPlayersPerTeam() * 2; ++ uint32 qPlayers = 0; ++ uint32 q_min_level = bracketEntry->minLevel; ++ uint32 q_max_level = bracketEntry->maxLevel; ++ for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracketId][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_CROSSFACTION].end(); ++itr) ++ if (!(*itr)->IsInvitedToBGInstanceGUID) ++ qPlayers += (*itr)->Players.size(); ++ ++ if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) ++ { ++ ChatHandler(leader->GetSession()).PSendSysMessage("Queue status for %s (Lvl: %u to %u) Queued players: %u (Need at least %u more)", bgName, q_min_level, q_max_level, qPlayers, MinPlayers - qPlayers); ++ } ++ else ++ { ++ std::ostringstream ss; ++ ss << "|cffff0000[BG Queue Announcer]:|r " << bgName << " -- [" << q_min_level << "-" << q_max_level << "] " << qPlayers << "/" << MinPlayers; ++ sWorld->SendGlobalText(ss.str().c_str(), NULL); ++ } + } +- // System message + else + { +- sWorld->SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bg->GetName().c_str(), q_min_level, q_max_level, +- qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); ++ // std::string bgName = bg->GetName().c_str(); ++ char const* bgName = bg->GetName().c_str(); ++ uint32 MinPlayers = bg->GetMinPlayersPerTeam(); ++ uint32 qHorde = 0; ++ uint32 qAlliance = 0; ++ uint32 q_min_level = bracketEntry->minLevel; ++ uint32 q_max_level = bracketEntry->maxLevel; ++ GroupsQueueType::const_iterator itr; ++ for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) ++ if (!(*itr)->IsInvitedToBGInstanceGUID) ++ qAlliance += (*itr)->Players.size(); ++ for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) ++ if (!(*itr)->IsInvitedToBGInstanceGUID) ++ qHorde += (*itr)->Players.size(); ++ ++ // Show queue status to player only (when joining queue) ++ if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) ++ { ++ ChatHandler(leader->GetSession()).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, q_max_level, //****** ++ qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); ++ } ++ // System message ++ else ++ { ++ sWorld->SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, q_max_level, //******* ++ qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); ++ } + } + } + } +@@ -308,7 +339,7 @@ void BattlegroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) + { + //we must check premade and normal team's queue - because when players from premade are joining bg, + //they leave groupinfo so we can't use its players size to find out index +- for (uint32 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_TEAMS_COUNT) ++ for (uint8 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) + { + GroupsQueueType::iterator k = m_QueuedGroups[bracket_id_tmp][j].begin(); + for (; k != m_QueuedGroups[bracket_id_tmp][j].end(); ++k) +@@ -497,6 +528,10 @@ void BattlegroundQueue::FillPlayersToBG(Battleground* bg, BattlegroundBracketId + int32 hordeFree = bg->GetFreeSlotsForTeam(HORDE); + int32 aliFree = bg->GetFreeSlotsForTeam(ALLIANCE); + ++ if (!bg->isArena()) ++ if (FillXPlayersToBG(bracket_id, bg, false)) ++ return; ++ + //iterator for iterating through bg queue + GroupsQueueType::const_iterator Ali_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].begin(); + //count of groups in queue - used to stop cycles +@@ -745,7 +780,8 @@ void BattlegroundQueue::BattlegroundQueueUpdate(uint32 /*diff*/, BattlegroundTyp + if (m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty() && + m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty() && + m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].empty() && +- m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].empty()) ++ m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].empty() && ++ m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].empty()) + return; + + // battleground with free slot for player should be always in the beggining of the queue +@@ -836,7 +872,8 @@ void BattlegroundQueue::BattlegroundQueueUpdate(uint32 /*diff*/, BattlegroundTyp + { + // if there are enough players in pools, start new battleground or non rated arena + if (CheckNormalMatch(bg_template, bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam) +- || (bg_template->isArena() && CheckSkirmishForSameFaction(bracket_id, MinPlayersPerTeam))) ++ || (bg_template->isArena() && CheckSkirmishForSameFaction(bracket_id, MinPlayersPerTeam)) ++ || CheckCrossFactionMatch(bracket_id, bg_template)) + { + // we successfully created a pool + Battleground* bg2 = sBattlegroundMgr->CreateNewBattleground(bgTypeId, bracketEntry, arenaType, false); +diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.h b/src/server/game/Battlegrounds/BattlegroundQueue.h +index b3b7fb3..466678f 100644 +--- a/src/server/game/Battlegrounds/BattlegroundQueue.h ++++ b/src/server/game/Battlegrounds/BattlegroundQueue.h +@@ -42,6 +42,7 @@ struct GroupQueueInfo // stores informatio + { + std::map Players; // player queue info map + uint32 Team; // Player team (ALLIANCE/HORDE) ++ uint32 CFSTeam; // Player team (ALLIANCE/HORDE) + BattlegroundTypeId BgTypeId; // battleground type id + bool IsRated; // rated + uint8 ArenaType; // 2v2, 3v3, 5v5 or 0 when BG +@@ -60,9 +61,10 @@ enum BattlegroundQueueGroupTypes + BG_QUEUE_PREMADE_ALLIANCE = 0, + BG_QUEUE_PREMADE_HORDE = 1, + BG_QUEUE_NORMAL_ALLIANCE = 2, +- BG_QUEUE_NORMAL_HORDE = 3 ++ BG_QUEUE_NORMAL_HORDE = 3, ++ BG_QUEUE_CROSSFACTION = 4 + }; +-#define BG_QUEUE_GROUP_TYPES_COUNT 4 ++#define BG_QUEUE_GROUP_TYPES_COUNT 5 + + class Battleground; + class BattlegroundQueue +@@ -74,6 +76,11 @@ class BattlegroundQueue + void BattlegroundQueueUpdate(uint32 diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, uint8 arenaType = 0, bool isRated = false, uint32 minRating = 0); + void UpdateEvents(uint32 diff); + ++ bool FillXPlayersToBG(BattlegroundBracketId bracket_id, Battleground* bg, bool start = false); ++ typedef std::multimap QueuedGroupMap; ++ int32 PreAddPlayers(QueuedGroupMap m_PreGroupMap, int32 MaxAdd, uint32 MaxInTeam); ++ bool CheckCrossFactionMatch(BattlegroundBracketId bracket_id, Battleground* bg); ++ + void FillPlayersToBG(Battleground* bg, BattlegroundBracketId bracket_id); + bool CheckPremadeMatch(BattlegroundBracketId bracket_id, uint32 MinPlayersPerTeam, uint32 MaxPlayersPerTeam); + bool CheckNormalMatch(Battleground* bg_template, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers); +diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp +index 01acd78..d5560dc 100644 +--- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp ++++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp +@@ -139,7 +139,7 @@ void BattlegroundAB::PostUpdateImpl(uint32 diff) + + if (m_ReputationScoreTics[team] >= m_ReputationTics) + { +- (team == TEAM_ALLIANCE) ? RewardReputationToTeam(509, 10, ALLIANCE) : RewardReputationToTeam(510, 10, HORDE); ++ RewardReputationToTeam(509, 510, 10, team == ALLIANCE ? ALLIANCE : HORDE); + m_ReputationScoreTics[team] -= m_ReputationTics; + } + +diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp +index 016a113..060097e 100644 +--- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp ++++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp +@@ -88,7 +88,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) + if (entry == BG_AV_CreatureInfo[AV_NPC_A_BOSS]) + { + CastSpellOnTeam(23658, HORDE); //this is a spell which finishes a quest where a player has to kill the boss +- RewardReputationToTeam(729, BG_AV_REP_BOSS, HORDE); ++ RewardReputationToTeam(729, 730, BG_AV_REP_BOSS, killer->GetTeam() == ALLIANCE ? ALLIANCE : HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), HORDE); + EndBattleground(HORDE); + DelCreature(AV_CPLACE_TRIGGER17); +@@ -96,7 +96,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) + else if (entry == BG_AV_CreatureInfo[AV_NPC_H_BOSS]) + { + CastSpellOnTeam(23658, ALLIANCE); //this is a spell which finishes a quest where a player has to kill the boss +- RewardReputationToTeam(730, BG_AV_REP_BOSS, ALLIANCE); ++ RewardReputationToTeam(729, 730, BG_AV_REP_BOSS, killer->GetTeam() == ALLIANCE ? ALLIANCE : HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), ALLIANCE); + EndBattleground(ALLIANCE); + DelCreature(AV_CPLACE_TRIGGER19); +@@ -109,7 +109,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) + return; + } + m_CaptainAlive[0]=false; +- RewardReputationToTeam(729, BG_AV_REP_CAPTAIN, HORDE); ++ RewardReputationToTeam(729, 730, BG_AV_REP_CAPTAIN, killer->GetTeam() == ALLIANCE ? ALLIANCE : HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), HORDE); + UpdateScore(ALLIANCE, (-1)*BG_AV_RES_CAPTAIN); + //spawn destroyed aura +@@ -128,7 +128,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) + return; + } + m_CaptainAlive[1]=false; +- RewardReputationToTeam(730, BG_AV_REP_CAPTAIN, ALLIANCE); ++ RewardReputationToTeam(729, 730, BG_AV_REP_CAPTAIN, killer->GetTeam() == ALLIANCE ? ALLIANCE : HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), ALLIANCE); + UpdateScore(HORDE, (-1)*BG_AV_RES_CAPTAIN); + //spawn destroyed aura +@@ -150,6 +150,7 @@ void BattlegroundAV::HandleQuestComplete(uint32 questid, Player* player) + if (GetStatus() != STATUS_IN_PROGRESS) + return;//maybe we should log this, cause this must be a cheater or a big bug + uint8 team = GetTeamIndexByTeamId(player->GetTeam()); ++ uint8 CFSteam = GetTeamIndexByTeamId(GetOtherTeam(player->GetTeam())); + /// @todo add reputation, events (including quest not available anymore, next quest available, go/npc de/spawning)and maybe honor + TC_LOG_DEBUG("bg.battleground", "BG_AV Quest %i completed", questid); + switch (questid) +@@ -174,21 +175,21 @@ void BattlegroundAV::HandleQuestComplete(uint32 questid, Player* player) + case AV_QUEST_A_COMMANDER1: + case AV_QUEST_H_COMMANDER1: + m_Team_QuestStatus[team][1]++; +- RewardReputationToTeam(team, 1, player->GetTeam()); ++ RewardReputationToTeam(team, CFSteam, 1, player->GetTeam()); + if (m_Team_QuestStatus[team][1] == 30) + TC_LOG_DEBUG("bg.battleground", "BG_AV Quest %i completed (need to implement some events here", questid); + break; + case AV_QUEST_A_COMMANDER2: + case AV_QUEST_H_COMMANDER2: + m_Team_QuestStatus[team][2]++; +- RewardReputationToTeam(team, 1, player->GetTeam()); ++ RewardReputationToTeam(team, CFSteam, 1, player->GetTeam()); + if (m_Team_QuestStatus[team][2] == 60) + TC_LOG_DEBUG("bg.battleground", "BG_AV Quest %i completed (need to implement some events here", questid); + break; + case AV_QUEST_A_COMMANDER3: + case AV_QUEST_H_COMMANDER3: + m_Team_QuestStatus[team][3]++; +- RewardReputationToTeam(team, 1, player->GetTeam()); ++ RewardReputationToTeam(team, CFSteam, 1, player->GetTeam()); + if (m_Team_QuestStatus[team][3] == 120) + TC_LOG_DEBUG("bg.battleground", "BG_AV Quest %i completed (need to implement some events here", questid); + break; +@@ -470,7 +471,7 @@ void BattlegroundAV::EndBattleground(uint32 winner) + rep[i] += BG_AV_REP_SURVIVING_CAPTAIN; + } + if (rep[i] != 0) +- RewardReputationToTeam(i == 0 ? 730 : 729, rep[i], i == 0 ? ALLIANCE : HORDE); ++ RewardReputationToTeam(729, 730, 10, i == ALLIANCE ? ALLIANCE : HORDE); + if (kills[i] != 0) + RewardHonorToTeam(GetBonusHonorFromKill(kills[i]), i == 0 ? ALLIANCE : HORDE); + } +@@ -575,7 +576,7 @@ void BattlegroundAV::EventPlayerDestroyedPoint(BG_AV_Nodes node) + SpawnBGObject(BG_AV_OBJECT_BURN_DUNBALDAR_SOUTH + i + (tmp * 10), RESPAWN_IMMEDIATELY); + + UpdateScore((owner == ALLIANCE) ? HORDE : ALLIANCE, -1 * BG_AV_RES_TOWER); +- RewardReputationToTeam(owner == ALLIANCE ? 730 : 729, BG_AV_REP_TOWER, owner); ++ RewardReputationToTeam(729, 730, BG_AV_REP_TOWER, owner); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_TOWER), owner); + + SpawnBGObject(BG_AV_OBJECT_TAURA_A_DUNBALDAR_SOUTH+GetTeamIndexByTeamId(owner)+(2*tmp), RESPAWN_ONE_DAY); +diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp +index ef02b24..7a0f325 100644 +--- a/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp ++++ b/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp +@@ -307,7 +307,6 @@ void BattlegroundWS::EventPlayerCapturedFlag(Player* player) + if (GetTeamScore(TEAM_ALLIANCE) < BG_WS_MAX_TEAM_SCORE) + AddPoint(ALLIANCE, 1); + PlaySoundToAll(BG_WS_SOUND_FLAG_CAPTURED_ALLIANCE); +- RewardReputationToTeam(890, m_ReputationCapture, ALLIANCE); + } + else + { +@@ -326,8 +325,8 @@ void BattlegroundWS::EventPlayerCapturedFlag(Player* player) + if (GetTeamScore(TEAM_HORDE) < BG_WS_MAX_TEAM_SCORE) + AddPoint(HORDE, 1); + PlaySoundToAll(BG_WS_SOUND_FLAG_CAPTURED_HORDE); +- RewardReputationToTeam(889, m_ReputationCapture, HORDE); + } ++ RewardReputationToTeam(890, 889, m_ReputationCapture, player->GetTeam()); + //for flag capture is reward 2 honorable kills + RewardHonorToTeam(GetBonusHonorFromKill(2), player->GetTeam()); + +diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt +index b604209..ff39e7f 100644 +--- a/src/server/game/CMakeLists.txt ++++ b/src/server/game/CMakeLists.txt +@@ -50,6 +50,7 @@ file(GLOB_RECURSE sources_Tickets Tickets/*.cpp Tickets/*.h) + file(GLOB_RECURSE sources_Warden Warden/*.cpp Warden/*.h) + file(GLOB_RECURSE sources_Weather Weather/*.cpp Weather/*.h) + file(GLOB_RECURSE sources_World World/*.cpp World/*.h) ++file(GLOB_RECURSE sources_Cfbg Cfbg/*.cpp Cfbg/*.h) + + # Create game-libary + +@@ -102,6 +103,7 @@ set(game_STAT_SRCS + ${sources_Warden} + ${sources_Weather} + ${sources_World} ++ ${sources_Cfbg} + ) + + include_directories( +diff --git a/src/server/game/Cfbg/Cfbg.cpp b/src/server/game/Cfbg/Cfbg.cpp +new file mode 100644 +index 0000000..b02db1c +--- /dev/null ++++ b/src/server/game/Cfbg/Cfbg.cpp +@@ -0,0 +1,347 @@ ++/* ++ * Copyright (C) 2013-2015 DeathCore ++ * ++ * 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, see . ++*/ ++ ++ ++#include "Cfbg.h" ++#include "Battleground.h" ++#include "BattlegroundMgr.h" ++#include "Player.h" ++#include "Chat.h" ++#include "BattlegroundQueue.h" ++ ++/*#################################################################################### ++###############################CROSSFACTION BATTLEGROUNDS############################# ++####################################################################################*/ ++ ++uint8 Unit::getRace(bool forceoriginal) const ++{ ++ if (GetTypeId() == TYPEID_PLAYER) ++ { ++ Player* pPlayer = ((Player*)this); ++ ++ if (forceoriginal) ++ return pPlayer->getCFSRace(); ++ ++ if (pPlayer->InArena()) ++ return GetByteValue(UNIT_FIELD_BYTES_0, 0); ++ ++ if (!pPlayer->IsPlayingNative()) ++ return pPlayer->getFRace(); ++ } ++ ++ return GetByteValue(UNIT_FIELD_BYTES_0, 0); ++} ++ ++bool Player::SendRealNameQuery() ++{ ++ if (IsPlayingNative()) ++ return false; ++ ++ WorldPacket data(SMSG_NAME_QUERY_RESPONSE, (8 + 1 + 1 + 1 + 1 + 1 + 10)); ++ data.appendPackGUID(GetGUID()); // player guid ++ data << uint8(0); // added in 3.1; if > 1, then end of packet ++ data << GetName(); // played name ++ data << uint8(0); // realm name for cross realm BG usage ++ data << uint8(getCFSRace()); ++ data << uint8(getGender()); ++ data << uint8(getClass()); ++ data << uint8(0); // is not declined ++ GetSession()->SendPacket(&data); ++ ++ return true; ++} ++ ++void Player::SetFakeRaceAndMorph() ++{ ++ if (getClass() == CLASS_DRUID) ++ { ++ if (GetCFSTeam() == ALLIANCE) ++ { ++ m_FakeMorph = getGender() == GENDER_MALE ? FAKE_M_TAUREN : FAKE_F_TAUREN; ++ m_FakeRace = RACE_TAUREN; ++ } ++ else if (getGender() == GENDER_MALE) // HORDE PLAYER, ONLY HAVE MALE NELF ID ++ { ++ m_FakeMorph = FAKE_M_NELF; ++ m_FakeRace = RACE_NIGHTELF; ++ } ++ else ++ m_FakeRace = GetCFSTeam() == ALLIANCE ? RACE_BLOODELF : RACE_HUMAN; ++ } ++ else if (getClass() == CLASS_SHAMAN && GetCFSTeam() == HORDE && getGender() == GENDER_FEMALE) ++ { ++ m_FakeMorph = FAKE_F_DRAENEI; // Female Draenei ++ m_FakeRace = RACE_DRAENEI; ++ } ++ else ++ { ++ m_FakeRace = GetCFSTeam() == ALLIANCE ? RACE_BLOODELF : RACE_HUMAN; ++ ++ if (GetCFSTeam() == HORDE) ++ { ++ if (getGender() == GENDER_MALE) ++ m_FakeMorph = 19723; ++ else ++ m_FakeMorph = 19724; ++ } ++ else ++ { ++ if (getGender() == GENDER_MALE) ++ m_FakeMorph = 20578; ++ else ++ m_FakeMorph = 20579; ++ } ++ } ++} ++ ++bool Player::SendBattleGroundChat(uint32 msgtype, std::string message) ++{ ++ // Select distance to broadcast to. ++ float distance = msgtype == CHAT_MSG_SAY ? sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY) : sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL); ++ ++ if (Battleground* pBattleGround = GetBattleground()) ++ { ++ if (pBattleGround->isArena()) // Only fake chat in BG's. CFBG should not interfere with arenas. ++ return false; ++ ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = pBattleGround->GetPlayers().begin(); itr != pBattleGround->GetPlayers().end(); ++itr) ++ { ++ if (Player* pPlayer = ObjectAccessor::FindPlayer(itr->first)) ++ { ++ if (GetDistance2d(pPlayer->GetPositionX(), pPlayer->GetPositionY()) <= distance) ++ { ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ ++ if (GetTeam() == pPlayer->GetTeam()) ++ { ++ WorldPacket data; ++ ChatHandler::BuildChatPacket(data, ChatMsg(msgtype), LANG_UNIVERSAL, pPlayer, NULL, message); ++ pPlayer->GetSession()->SendPacket(&data); ++ } ++ else if (msgtype != CHAT_MSG_EMOTE) ++ { ++ WorldPacket data; ++ ChatHandler::BuildChatPacket(data, ChatMsg(msgtype), pPlayer->GetTeam() == ALLIANCE ? LANG_ORCISH : LANG_COMMON, pPlayer, NULL, message); ++ pPlayer->GetSession()->SendPacket(&data); ++ } ++ pPlayer->GetSession()->SendPacket(&data); ++ } ++ } ++ } ++ return true; ++ } ++ else ++ return false; ++} ++ ++void Player::MorphFit(bool value) ++{ ++ if (!IsPlayingNative() && value) ++ { ++ SetDisplayId(GetFakeMorph()); ++ SetNativeDisplayId(GetFakeMorph()); ++ } ++ else ++ InitDisplayIds(); ++} ++ ++void Player::FitPlayerInTeam(bool action, Battleground* pBattleGround) ++{ ++ if (!pBattleGround) ++ pBattleGround = GetBattleground(); ++ ++ if ((!pBattleGround || pBattleGround->isArena()) && action) ++ return; ++ ++ if (!IsPlayingNative() && action) ++ setFactionForRace(getRace()); ++ else ++ setFactionForRace(getCFSRace()); ++ ++ if (action) ++ SetForgetBGPlayers(true); ++ else ++ SetForgetInListPlayers(true); ++ ++ MorphFit(action); ++ ++ if (pBattleGround && action) ++ SendChatMessage("%sYou are playing for the %s%s in this %s", MSG_COLOR_WHITE, GetTeam() == ALLIANCE ? MSG_COLOR_DARKBLUE"alliance" : MSG_COLOR_RED"horde", MSG_COLOR_WHITE, pBattleGround->GetName().c_str()); ++} ++ ++void Player::DoForgetPlayersInList() ++{ ++ // m_FakePlayers is filled from a vector within the battleground ++ // they were in previously so all players that have been in that BG will be invalidated. ++ for (FakePlayers::const_iterator itr = m_FakePlayers.begin(); itr != m_FakePlayers.end(); ++itr) ++ { ++ WorldPacket data(SMSG_INVALIDATE_PLAYER, 8); ++ data << *itr; ++ GetSession()->SendPacket(&data); ++ if (Player* pPlayer = ObjectAccessor::FindPlayer(ObjectGuid(*itr))) ++ // if (Player* pPlayer = ObjectAccessor::FindPlayer(*itr)) ++ GetSession()->SendNameQueryOpcode(pPlayer->GetGUID()); ++ } ++ m_FakePlayers.clear(); ++} ++ ++void Player::DoForgetPlayersInBG(Battleground* pBattleGround) ++{ ++ for (Battleground::BattlegroundPlayerMap::const_iterator itr = pBattleGround->GetPlayers().begin(); itr != pBattleGround->GetPlayers().end(); ++itr) ++ { ++ // Here we invalidate players in the bg to the added player ++ WorldPacket data1(SMSG_INVALIDATE_PLAYER, 8); ++ data1 << itr->first; ++ GetSession()->SendPacket(&data1); ++ ++ if (Player* pPlayer = ObjectAccessor::FindPlayer(itr->first)) ++ { ++ GetSession()->SendNameQueryOpcode(pPlayer->GetGUID()); // Send namequery answer instantly if player is available ++ // Here we invalidate the player added to players in the bg ++ WorldPacket data2(SMSG_INVALIDATE_PLAYER, 8); ++ data2 << GetGUID(); ++ pPlayer->GetSession()->SendPacket(&data2); ++ pPlayer->GetSession()->SendNameQueryOpcode(GetGUID()); ++ } ++ } ++} ++ ++bool BattlegroundQueue::CheckCrossFactionMatch(BattlegroundBracketId bracket_id, Battleground* bg) ++{ ++ if (!sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS) || bg->isArena()) ++ return false; // Only do this if crossbg's are enabled. ++ ++ // Here we will add all players to selectionpool, later we check if there are enough and launch a bg. ++ FillXPlayersToBG(bracket_id, bg, true); ++ ++ if (sBattlegroundMgr->isTesting() && (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() || m_SelectionPools[TEAM_HORDE].GetPlayerCount())) ++ return true; ++ ++ uint8 MPT = bg->GetMinPlayersPerTeam(); ++ if (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() < MPT || m_SelectionPools[TEAM_HORDE].GetPlayerCount() < MPT) ++ return false; ++ ++ return true; ++} ++ ++// This function will invite players in the least populated faction, which makes battleground queues much faster. ++// This function will return true if cross faction battlegrounds are enabled, otherwise return false, ++// which is useful in FillPlayersToBG. Because then we can interrupt the regular invitation if cross faction bg's are enabled. ++bool BattlegroundQueue::FillXPlayersToBG(BattlegroundBracketId bracket_id, Battleground* bg, bool start) ++{ ++ uint8 queuedPeople = 0; ++ for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr) ++ if (!(*itr)->IsInvitedToBGInstanceGUID) ++ queuedPeople += (*itr)->Players.size(); ++ ++ if (sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS) && (sBattlegroundMgr->isTesting() || queuedPeople >= bg->GetMinPlayersPerTeam() * 2 || !start)) ++ { ++ int32 aliFree = start ? bg->GetMaxPlayersPerTeam() : bg->GetFreeSlotsForTeam(ALLIANCE); ++ int32 hordeFree = start ? bg->GetMaxPlayersPerTeam() : bg->GetFreeSlotsForTeam(HORDE); ++ // Empty selection pools. They will be refilled from queued groups. ++ m_SelectionPools[TEAM_ALLIANCE].Init(); ++ m_SelectionPools[TEAM_HORDE].Init(); ++ int32 valiFree = aliFree; ++ int32 vhordeFree = hordeFree; ++ int32 diff = 0; ++ ++ ++ // Add teams to their own factions as far as possible. ++ if (start) ++ { ++ QueuedGroupMap m_PreGroupMap_a, m_PreGroupMap_h; ++ int32 m_SmallestOfTeams = 0; ++ int32 queuedAlliance = 0; ++ int32 queuedHorde = 0; ++ ++ for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr) ++ { ++ if ((*itr)->IsInvitedToBGInstanceGUID) ++ continue; ++ ++ bool alliance = (*itr)->CFSTeam == ALLIANCE; ++ ++ if (alliance) ++ { ++ m_PreGroupMap_a.insert(std::make_pair((*itr)->Players.size(), *itr)); ++ queuedAlliance += (*itr)->Players.size(); ++ } ++ else ++ { ++ m_PreGroupMap_h.insert(std::make_pair((*itr)->Players.size(), *itr)); ++ queuedHorde += (*itr)->Players.size(); ++ } ++ } ++ ++ m_SmallestOfTeams = std::min(std::min(aliFree, queuedAlliance), std::min(hordeFree, queuedHorde)); ++ ++ valiFree -= PreAddPlayers(m_PreGroupMap_a, m_SmallestOfTeams, aliFree); ++ vhordeFree -= PreAddPlayers(m_PreGroupMap_h, m_SmallestOfTeams, hordeFree); ++ } ++ ++ QueuedGroupMap m_QueuedGroupMap; ++ ++ for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr) ++ m_QueuedGroupMap.insert(std::make_pair((*itr)->Players.size(), *itr)); ++ ++ for (QueuedGroupMap::reverse_iterator itr = m_QueuedGroupMap.rbegin(); itr != m_QueuedGroupMap.rend(); ++itr) ++ { ++ GroupsQueueType allypool = m_SelectionPools[TEAM_ALLIANCE].SelectedGroups; ++ GroupsQueueType hordepool = m_SelectionPools[TEAM_HORDE].SelectedGroups; ++ ++ GroupQueueInfo* ginfo = itr->second; ++ ++ // If player already was invited via pre adding (add to own team first) or he was already invited to a bg, skip. ++ if (ginfo->IsInvitedToBGInstanceGUID || ++ std::find(allypool.begin(), allypool.end(), ginfo) != allypool.end() || ++ std::find(hordepool.begin(), hordepool.end(), ginfo) != hordepool.end() || ++ (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() >= bg->GetMinPlayersPerTeam() && ++ m_SelectionPools[TEAM_HORDE].GetPlayerCount() >= bg->GetMinPlayersPerTeam())) ++ continue; ++ ++ diff = abs(valiFree - vhordeFree); ++ bool moreAli = valiFree < vhordeFree; ++ ++ if (diff > 0) ++ ginfo->Team = moreAli ? HORDE : ALLIANCE; ++ ++ bool alliance = ginfo->Team == ALLIANCE; ++ ++ if (m_SelectionPools[alliance ? TEAM_ALLIANCE : TEAM_HORDE].AddGroup(ginfo, alliance ? aliFree : hordeFree)) ++ alliance ? valiFree -= ginfo->Players.size() : vhordeFree -= ginfo->Players.size(); ++ } ++ ++ return true; ++ } ++ return false; ++} ++ ++int32 BattlegroundQueue::PreAddPlayers(QueuedGroupMap m_PreGroupMap, int32 MaxAdd, uint32 MaxInTeam) ++{ ++ int32 LeftToAdd = MaxAdd; ++ uint32 Added = 0; ++ ++ for (QueuedGroupMap::reverse_iterator itr = m_PreGroupMap.rbegin(); itr != m_PreGroupMap.rend(); ++itr) ++ { ++ int32 PlayerSize = itr->first; ++ bool alliance = itr->second->CFSTeam == ALLIANCE; ++ ++ if (PlayerSize <= LeftToAdd && m_SelectionPools[alliance ? TEAM_ALLIANCE : TEAM_HORDE].AddGroup(itr->second, MaxInTeam)) ++ LeftToAdd -= PlayerSize, Added -= PlayerSize; ++ } ++ ++ return LeftToAdd; ++} ++ ++void Player::SendChatMessage(const char *format, ...) ++{ ++ if (!IsInWorld()) ++ return; ++ ++ if (format) ++ { ++ va_list ap; ++ char str[2048]; ++ va_start(ap, format); ++ vsnprintf(str, 2048, format, ap); ++ va_end(ap); ++ ++ ChatHandler(GetSession()).SendSysMessage(str); ++ } ++} +\ No newline at end of file +diff --git a/src/server/game/Cfbg/Cfbg.h b/src/server/game/Cfbg/Cfbg.h +new file mode 100644 +index 0000000..3f8285d +--- /dev/null ++++ b/src/server/game/Cfbg/Cfbg.h +@@ -0,0 +1,44 @@ ++/* ++ * Copyright (C) 2013-2015 DeathCore ++ * ++ * 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, see . ++*/ ++ ++ ++#ifndef _CUSTOM_H ++#define _CUSTOM_H ++ ++#define MSG_COLOR_LIGHTRED "|cffff6060" ++#define MSG_COLOR_LIGHTBLUE "|cff00ccff" ++#define MSG_COLOR_ANN_GREEN "|c1f40af20" ++#define MSG_COLOR_RED "|cffff0000" ++#define MSG_COLOR_GOLD "|cffffcc00" ++#define MSG_COLOR_SUBWHITE "|cffbbbbbb" ++#define MSG_COLOR_MAGENTA "|cffff00ff" ++#define MSG_COLOR_YELLOW "|cffffff00" ++#define MSG_COLOR_CYAN "|cff00ffff" ++#define MSG_COLOR_DARKBLUE "|cff0000ff" ++ ++#define MSG_COLOR_GREY "|cff9d9d9d" ++#define MSG_COLOR_WHITE "|cffffffff" ++#define MSG_COLOR_GREEN "|cff1eff00" ++#define MSG_COLOR_BLUE "|cff0080ff" ++#define MSG_COLOR_PURPLE "|cffb048f8" ++#define MSG_COLOR_ORANGE "|cffff8000" ++ ++#define MSG_COLOR_DRUID "|cffff7d0a" ++#define MSG_COLOR_HUNTER "|cffabd473" ++#define MSG_COLOR_MAGE "|cff69ccf0" ++#define MSG_COLOR_PALADIN "|cfff58cba" ++#define MSG_COLOR_PRIEST "|cffffffff" ++#define MSG_COLOR_ROGUE "|cfffff569" ++#define MSG_COLOR_SHAMAN "|cff0070de" ++#define MSG_COLOR_WARLOCK "|cff9482c9" ++#define MSG_COLOR_WARRIOR "|cffc79c6e" ++#define MSG_COLOR_DEATH_KNIGHT "|cffc41f3b" ++#define MSG_COLOR_MONK "|cff00ff96" ++ ++#define LIMIT_UINT32 2147483647 ++ ++enum FakeMorphs ++{ ++ FAKE_F_TAUREN = 20584, ++ FAKE_M_TAUREN = 20585, ++ FAKE_M_NELF = 20318, ++ FAKE_F_DRAENEI = 20323, ++}; ++ ++#endif +\ No newline at end of file +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 8d7c800..52b6afb 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -659,6 +659,12 @@ void KillRewarder::Reward() + + Player::Player(WorldSession* session): Unit(true) + { ++ m_FakeRace = 0; ++ m_RealRace = 0; ++ m_FakeMorph = 0; ++ m_ForgetBGPlayers = false; ++ m_ForgetInListPlayers = false; ++ + m_speakTime = 0; + m_speakCount = 0; + +@@ -1017,6 +1023,12 @@ bool Player::Create(uint32 guidlow, CharacterCreateInfo* createInfo) + uint32 RaceClassGender = (createInfo->Race) | (createInfo->Class << 8) | (createInfo->Gender << 16); + + SetUInt32Value(UNIT_FIELD_BYTES_0, (RaceClassGender | (powertype << 24))); ++ ++ SetCFSRace(); ++ m_team = TeamForRace(getCFSRace()); ++ SetFakeRaceAndMorph(); // m_team must be set before this can be used. ++ setFactionForRace(getCFSRace()); ++ + InitDisplayIds(); + if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP) + { +@@ -3055,7 +3067,7 @@ void Player::GiveLevel(uint8 level) + guild->UpdateMemberData(this, GUILD_MEMBER_DATA_LEVEL, level); + + PlayerLevelInfo info; +- sObjectMgr->GetPlayerLevelInfo(getRace(), getClass(), level, &info); ++ sObjectMgr->GetPlayerLevelInfo(getCFSRace(), getClass(), level, &info); + + PlayerClassLevelInfo classInfo; + sObjectMgr->GetPlayerClassLevelInfo(getClass(), level, &classInfo); +@@ -3193,7 +3205,7 @@ void Player::InitStatsForLevel(bool reapplyMods) + sObjectMgr->GetPlayerClassLevelInfo(getClass(), getLevel(), &classInfo); + + PlayerLevelInfo info; +- sObjectMgr->GetPlayerLevelInfo(getRace(), getClass(), getLevel(), &info); ++ sObjectMgr->GetPlayerLevelInfo(getCFSRace(), getClass(), getLevel(), &info); + + SetUInt32Value(PLAYER_FIELD_MAX_LEVEL, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)); + SetUInt32Value(PLAYER_NEXT_LEVEL_XP, sObjectMgr->GetXPForLevel(getLevel())); +@@ -5256,7 +5268,7 @@ void Player::CreateCorpse() + return; + } + +- _uf = GetUInt32Value(UNIT_FIELD_BYTES_0); ++ _uf = getCFSRace(); + _pb = GetUInt32Value(PLAYER_BYTES); + _pb2 = GetUInt32Value(PLAYER_BYTES_2); + +@@ -6940,10 +6952,10 @@ uint32 Player::TeamForRace(uint8 race) + + void Player::setFactionForRace(uint8 race) + { +- m_team = TeamForRace(race); ++ SetBGTeam(TeamForRace(race)); + + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race); +- setFaction(rEntry ? rEntry->FactionID : 0); ++ setFaction(rEntry ? rEntry->FactionID : getFaction()); + } + + ReputationRank Player::GetReputationRank(uint32 faction) const +@@ -7045,6 +7057,27 @@ void Player::RewardReputation(Unit* victim, float rate) + if (!Rep) + return; + ++ uint32 repfaction1 = Rep->RepFaction1; ++ uint32 repfaction2 = Rep->RepFaction2; ++ ++ if (!IsPlayingNative()) ++ { ++ if (GetCFSTeam() == ALLIANCE) ++ { ++ if (repfaction1 == 729) ++ repfaction1 = 730; ++ if (repfaction2 == 729) ++ repfaction2 = 730; ++ } ++ else ++ { ++ if (repfaction1 == 730) ++ repfaction1 = 729; ++ if (repfaction2 == 730) ++ repfaction2 = 729; ++ } ++ } ++ + uint32 ChampioningFaction = 0; + + if (GetChampioningFaction()) +@@ -7059,23 +7092,23 @@ void Player::RewardReputation(Unit* victim, float rate) + + uint32 team = GetTeam(); + +- if (Rep->RepFaction1 && (!Rep->TeamDependent || team == ALLIANCE)) ++ if (repfaction1 && (!Rep->TeamDependent || team == ALLIANCE)) + { +- int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : Rep->RepFaction1); ++ int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : repfaction1); + donerep1 = int32(donerep1 * rate); + +- FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1); ++ FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : repfaction1); + uint32 current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1); + if (factionEntry1 && current_reputation_rank1 <= Rep->ReputationMaxCap1) + GetReputationMgr().ModifyReputation(factionEntry1, donerep1); + } + +- if (Rep->RepFaction2 && (!Rep->TeamDependent || team == HORDE)) ++ if (repfaction2 && (!Rep->TeamDependent || team == HORDE)) + { +- int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : Rep->RepFaction2); ++ int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : repfaction2); + donerep2 = int32(donerep2 * rate); + +- FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2); ++ FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : repfaction2); + uint32 current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2); + if (factionEntry2 && current_reputation_rank2 <= Rep->ReputationMaxCap2) + GetReputationMgr().ModifyReputation(factionEntry2, donerep2); +@@ -7170,7 +7203,7 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + if (!victim || victim == this || victim->GetTypeId() != TYPEID_PLAYER) + return false; + +- if (GetBGTeam() == victim->ToPlayer()->GetBGTeam()) ++ if (GetTeam() == victim->ToPlayer()->GetTeam()) + return false; + + return true; +@@ -12055,13 +12088,13 @@ InventoryResult Player::CanUseItem(ItemTemplate const* proto) const + if (!proto) + return EQUIP_ERR_ITEM_NOT_FOUND; + +- if ((proto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY) && GetTeam() != HORDE) ++ if ((proto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY) && GetCFSTeam() != HORDE) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + +- if ((proto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY) && GetTeam() != ALLIANCE) ++ if ((proto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY) && GetCFSTeam() != ALLIANCE) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + +- if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getRaceMask()) == 0) ++ if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getCFSRaceMask()) == 0) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + + if (proto->RequiredSkill != 0) +@@ -17321,6 +17354,11 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) + bytes0 |= gender << 16; // gender + SetUInt32Value(UNIT_FIELD_BYTES_0, bytes0); + ++ SetCFSRace(); //**** umisteni **** ++ m_team = TeamForRace(getCFSRace()); ++ SetFakeRaceAndMorph(); // m_team must be set before this can be used. ++ setFactionForRace(getCFSRace());//Need to call it to initialize m_team (m_team can be calculated from race) ++ + // check if race/class combination is valid + PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass()); + if (!info) +@@ -17389,10 +17427,6 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) + TC_LOG_DEBUG("entities.player.loading", "Load Basic value of player %s is: ", m_name.c_str()); + outDebugValues(); + +- //Need to call it to initialize m_team (m_team can be calculated from race) +- //Other way is to saves m_team into characters table. +- setFactionForRace(getRace()); +- + // load home bind and check in same time class/race pair, it used later for restore broken positions + if (!_LoadHomeBind(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND))) + return false; +@@ -19220,7 +19254,7 @@ void Player::AddInstanceEnterTime(uint32 instanceId, time_t enterTime) + + bool Player::_LoadHomeBind(PreparedQueryResult result) + { +- PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass()); ++ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getCFSRace(), getClass()); + if (!info) + { + TC_LOG_ERROR("entities.player", "Player (Name %s) has incorrect race/class pair. Can't be loaded.", GetName().c_str()); +@@ -19313,7 +19347,7 @@ void Player::SaveToDB(bool create /*=false*/) + stmt->setUInt32(index++, GetGUIDLow()); + stmt->setUInt32(index++, GetSession()->GetAccountId()); + stmt->setString(index++, GetName()); +- stmt->setUInt8(index++, getRace()); ++ stmt->setUInt8(index++, getCFSRace()); + stmt->setUInt8(index++, getClass()); + stmt->setUInt8(index++, getGender()); + stmt->setUInt8(index++, getLevel()); +@@ -19418,7 +19452,7 @@ void Player::SaveToDB(bool create /*=false*/) + // Update query + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER); + stmt->setString(index++, GetName()); +- stmt->setUInt8(index++, getRace()); ++ stmt->setUInt8(index++, getCFSRace()); + stmt->setUInt8(index++, getClass()); + stmt->setUInt8(index++, getGender()); + stmt->setUInt8(index++, getLevel()); +@@ -21682,22 +21716,26 @@ void Player::InitDataForForm(bool reapplyMods) + + void Player::InitDisplayIds() + { +- PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass()); ++ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getCFSRace(), getClass()); + if (!info) + { + TC_LOG_ERROR("entities.player", "Player %u has incorrect race/class pair. Can't init display ids.", GetGUIDLow()); + return; + } + ++ bool isMorphed = GetNativeDisplayId() != GetDisplayId(); ++ + uint8 gender = getGender(); + switch (gender) + { + case GENDER_FEMALE: +- SetDisplayId(info->displayId_f); ++ if (!isMorphed) ++ SetDisplayId(info->displayId_f); + SetNativeDisplayId(info->displayId_f); + break; + case GENDER_MALE: +- SetDisplayId(info->displayId_m); ++ if (!isMorphed) ++ SetDisplayId(info->displayId_m); + SetNativeDisplayId(info->displayId_m); + break; + default: +@@ -22496,11 +22534,6 @@ void Player::SetBGTeam(uint32 team) + SetByteValue(PLAYER_BYTES_3, 3, uint8(team == ALLIANCE ? 1 : 0)); + } + +-uint32 Player::GetBGTeam() const +-{ +- return m_bgData.bgTeam ? m_bgData.bgTeam : GetTeam(); +-} +- + void Player::LeaveBattleground(bool teleportToEntryPoint) + { + if (Battleground* bg = GetBattleground()) +@@ -22576,7 +22609,7 @@ void Player::ReportedAfkBy(Player* reporter) + + WorldLocation Player::GetStartPosition() const + { +- PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass()); ++ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getCFSRace(), getClass()); + uint32 mapId = info->mapId; + if (getClass() == CLASS_DEATH_KNIGHT && HasSpell(50977)) + mapId = 0; +@@ -23261,7 +23294,7 @@ void Player::LearnCustomSpells() + for (PlayerCreateInfoSpells::const_iterator itr = info->customSpells.begin(); itr != info->customSpells.end(); ++itr) + { + uint32 tspell = *itr; +- TC_LOG_DEBUG("entities.player.loading", "PLAYER (Class: %u Race: %u): Adding initial spell, id = %u", uint32(getClass()), uint32(getRace()), tspell); ++ TC_LOG_DEBUG("entities.player.loading", "PLAYER (Class: %u Race: %u): Adding initial spell, id = %u", uint32(getClass()), uint32(getCFSRace()), tspell); + if (!IsInWorld()) // will send in INITIAL_SPELLS in list anyway at map add + AddSpell(tspell, true, true, true, false); + else // but send in normal spell in game learn case +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index 313937a..0a4a939 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -1121,6 +1121,36 @@ class Player : public Unit, public GridObject + explicit Player(WorldSession* session); + ~Player(); + ++ private: ++ bool m_ForgetBGPlayers; ++ bool m_ForgetInListPlayers; ++ uint8 m_FakeRace; ++ uint8 m_RealRace; ++ uint32 m_FakeMorph; ++ public: ++ typedef std::vector FakePlayers; ++ void SendChatMessage(const char *format, ...); ++ void FitPlayerInTeam(bool action, Battleground* pBattleGround = NULL); // void FitPlayerInTeam(bool action, Battleground* bg = NULL); ++ void DoForgetPlayersInList(); ++ void DoForgetPlayersInBG(Battleground* pBattleGround); // void DoForgetPlayersInBG(Battleground* bg); ++ uint8 getCFSRace() const { return m_RealRace; } ++ void SetCFSRace() { m_RealRace = GetByteValue(UNIT_FIELD_BYTES_0, 0); }; // SHOULD ONLY BE CALLED ON LOGIN ++ void SetFakeRace(); // SHOULD ONLY BE CALLED ON LOGIN ++ void SetFakeRaceAndMorph(); // SHOULD ONLY BE CALLED ON LOGIN ++ uint32 GetFakeMorph() { return m_FakeMorph; }; ++ uint8 getFRace() const { return m_FakeRace; } ++ void SetForgetBGPlayers(bool value) { m_ForgetBGPlayers = value; } ++ bool ShouldForgetBGPlayers() { return m_ForgetBGPlayers; } ++ void SetForgetInListPlayers(bool value) { m_ForgetInListPlayers = value; } ++ bool ShouldForgetInListPlayers() { return m_ForgetInListPlayers; } ++ bool SendBattleGroundChat(uint32 msgtype, std::string message); ++ void MorphFit(bool value); ++ bool IsPlayingNative() const { return GetTeam() == m_team; } ++ uint32 GetCFSTeam() const { return m_team; } ++ uint32 GetTeam() const { return m_bgData.bgTeam && GetBattleground() ? m_bgData.bgTeam : m_team; } ++ bool SendRealNameQuery(); ++ FakePlayers m_FakePlayers; ++ + void CleanupsBeforeDelete(bool finalCleanup = true) override; + + void AddToWorld() override; +@@ -1174,7 +1204,7 @@ class Player : public Unit, public GridObject + PlayerSocial *GetSocial() { return m_social; } + + PlayerTaxi m_taxi; +- void InitTaxiNodesForLevel() { m_taxi.InitTaxiNodesForLevel(getRace(), getClass(), getLevel()); } ++ void InitTaxiNodesForLevel() { m_taxi.InitTaxiNodesForLevel(getCFSRace(), getClass(), getLevel()); } + bool ActivateTaxiPathTo(std::vector const& nodes, Creature* npc = NULL, uint32 spellid = 0); + bool ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid = 0); + void CleanupAfterTaxiFlight(); +@@ -1952,8 +1982,7 @@ class Player : public Unit, public GridObject + void CheckAreaExploreAndOutdoor(void); + + static uint32 TeamForRace(uint8 race); +- uint32 GetTeam() const { return m_team; } +- TeamId GetTeamId() const { return m_team == ALLIANCE ? TEAM_ALLIANCE : TEAM_HORDE; } ++ TeamId GetTeamId() const { return GetTeam() == ALLIANCE ? TEAM_ALLIANCE : TEAM_HORDE; } + void setFactionForRace(uint8 race); + + void InitDisplayIds(); +@@ -2098,7 +2127,6 @@ class Player : public Unit, public GridObject + void SetBattlegroundEntryPoint(); + + void SetBGTeam(uint32 team); +- uint32 GetBGTeam() const; + + void LeaveBattleground(bool teleportToEntryPoint = true); + bool CanJoinToBattleground(Battleground const* bg) const; +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 37f6950..0f7a4d3 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -16773,6 +16773,21 @@ uint32 Unit::GetModelForTotem(PlayerTotemType totemType) + } + break; + } ++ default: // One standard for other races. ++ { ++ switch (totemType) ++ { ++ case SUMMON_TYPE_TOTEM_FIRE: // fire ++ return 4589; ++ case SUMMON_TYPE_TOTEM_EARTH: // earth ++ return 4588; ++ case SUMMON_TYPE_TOTEM_WATER: // water ++ return 4587; ++ case SUMMON_TYPE_TOTEM_AIR: // air ++ return 4590; ++ } ++ break; ++ } + } + return 0; + } +diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h +index 20344fb..566d755 100644 +--- a/src/server/game/Entities/Unit/Unit.h ++++ b/src/server/game/Entities/Unit/Unit.h +@@ -1356,8 +1356,10 @@ class Unit : public WorldObject + uint8 getLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); } + uint8 getLevelForTarget(WorldObject const* /*target*/) const override { return getLevel(); } + void SetLevel(uint8 lvl); +- uint8 getRace() const { return GetByteValue(UNIT_FIELD_BYTES_0, 0); } ++ uint8 getRace(bool forceoriginal = false) const; ++ uint8 getCFSRace() { return getRace(true); } + uint32 getRaceMask() const { return 1 << (getRace()-1); } ++ uint32 getCFSRaceMask() const { return 1 << (getRace(true) - 1); } + uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, 1); } + uint32 getClassMask() const { return 1 << (getClass()-1); } + uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, 2); } +diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp +index 7f28fc8..be9285d 100644 +--- a/src/server/game/Handlers/BattleGroundHandler.cpp ++++ b/src/server/game/Handlers/BattleGroundHandler.cpp +@@ -543,7 +543,7 @@ void WorldSession::HandleBattlefieldStatusOpcode(WorldPacket & /*recvData*/) + { + // this line is checked, i only don't know if GetStartTime is changing itself after bg end! + // send status in Battleground +- sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, i, STATUS_IN_PROGRESS, bg->GetEndTime(), bg->GetStartTime(), arenaType, _player->GetBGTeam()); ++ sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, i, STATUS_IN_PROGRESS, bg->GetEndTime(), bg->GetStartTime(), arenaType, _player->GetTeam()); + SendPacket(&data); + continue; + } +diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp +index 7df4065..970af65 100644 +--- a/src/server/game/Handlers/CharacterHandler.cpp ++++ b/src/server/game/Handlers/CharacterHandler.cpp +@@ -994,6 +994,9 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) + sScriptMgr->OnPlayerLogin(pCurrChar, firstLogin); + + delete holder; ++ ++ if (pCurrChar->GetTeam() != pCurrChar->GetCFSTeam()) ++ pCurrChar->FitPlayerInTeam(pCurrChar->GetBattleground() && !pCurrChar->GetBattleground()->isArena() ? true : false, pCurrChar->GetBattleground()); + } + + void WorldSession::HandleSetFactionAtWar(WorldPacket& recvData) +diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp +index a583133..a6e04d9 100644 +--- a/src/server/game/Handlers/ChatHandler.cpp ++++ b/src/server/game/Handlers/ChatHandler.cpp +@@ -252,6 +252,10 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) + return; + } + ++ if (!GetPlayer()->IsGameMaster()) ++ if (GetPlayer()->SendBattleGroundChat(type, msg)) ++ return; ++ + if (type == CHAT_MSG_SAY) + sender->Say(msg, Language(lang)); + else if (type == CHAT_MSG_EMOTE) +diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp +index 49f251e..959df74 100644 +--- a/src/server/game/Handlers/MiscHandler.cpp ++++ b/src/server/game/Handlers/MiscHandler.cpp +@@ -1421,6 +1421,21 @@ void WorldSession::HandleSetTitleOpcode(WorldPacket& recvData) + + void WorldSession::HandleTimeSyncResp(WorldPacket& recvData) + { ++ Battleground* bg = _player->GetBattleground(); ++ if (bg) ++ { ++ if (_player->ShouldForgetBGPlayers() && bg) ++ { ++ _player->DoForgetPlayersInBG(bg); ++ _player->SetForgetBGPlayers(false); ++ } ++ } ++ else if (_player->ShouldForgetInListPlayers()) ++ { ++ _player->DoForgetPlayersInList(); ++ _player->SetForgetInListPlayers(false); ++ } ++ + TC_LOG_DEBUG("network", "CMSG_TIME_SYNC_RESP"); + + uint32 counter, clientTicks; +diff --git a/src/server/game/Handlers/QueryHandler.cpp b/src/server/game/Handlers/QueryHandler.cpp +index 9e2b497..dbb01f1 100644 +--- a/src/server/game/Handlers/QueryHandler.cpp ++++ b/src/server/game/Handlers/QueryHandler.cpp +@@ -46,7 +46,7 @@ void WorldSession::SendNameQueryOpcode(ObjectGuid guid) + data << uint8(0); // name known + data << nameData->m_name; // played name + data << uint8(0); // realm name - only set for cross realm interaction (such as Battlegrounds) +- data << uint8(nameData->m_race); ++ data << uint8(player ? player->getRace() : nameData->m_race); + data << uint8(nameData->m_gender); + data << uint8(nameData->m_class); + +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e28b74d..bc9534f 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1058,6 +1058,8 @@ void World::LoadConfigSettings(bool reload) + + m_bool_configs[CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN] = sConfigMgr->GetBoolDefault("OffhandCheckAtSpellUnlearn", true); + ++ m_bool_configs[CROSSFACTION_SYSTEM_BATTLEGROUNDS] = sConfigMgr->GetBoolDefault("CrossFactionSystem.Battlegrounds", true); ++ + m_int_configs[CONFIG_CREATURE_PICKPOCKET_REFILL] = sConfigMgr->GetIntDefault("Creature.PickPocketRefillDelay", 10 * MINUTE); + + if (int32 clientCacheId = sConfigMgr->GetIntDefault("ClientCacheVersion", 0)) +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 26d6029..7ece36e 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -86,6 +86,7 @@ enum WorldTimers + enum WorldBoolConfigs + { + CONFIG_DURABILITY_LOSS_IN_PVP = 0, ++ CROSSFACTION_SYSTEM_BATTLEGROUNDS, + CONFIG_ADDON_CHANNEL, + CONFIG_ALLOW_PLAYER_COMMANDS, + CONFIG_CLEAN_CHARACTER_DB, +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 08983a5..b037b5f 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -3412,3 +3412,15 @@ PacketSpoof.BanDuration = 86400 + + # + ################################################################################################### ++ ++################################################################################################### ++# ++# CROSSFACTION SYSTEM ++# ++# CrossFactionSystem.Battlegrounds = 1 - Activado ++# CrossFactionSystem.Battlegrounds = 0 - Desactivado ++ ++CrossFactionSystem.Battlegrounds = 1 ++ ++# ++################################################################################################### \ No newline at end of file diff --git a/encantamiento_visual.diff b/encantamiento_visual.diff new file mode 100644 index 0000000..b2e4b2f --- /dev/null +++ b/encantamiento_visual.diff @@ -0,0 +1,55 @@ +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 161fca4..8c68d8a 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -12486,12 +12486,13 @@ void Player::QuickEquipItem(uint16 pos, Item* pItem) + } + } + ++extern uint32 GetItemEnchantVisual(Player* player, Item* item); + void Player::SetVisibleItemSlot(uint8 slot, Item* pItem) + { + if (pItem) + { + SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry()); +- SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, pItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); ++ SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, GetItemEnchantVisual(this, pItem)); + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 1, pItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + } + else +@@ -24930,6 +24931,7 @@ void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore cons + } + } + ++extern void SetRandomEnchantVisual(Player* player, Item* item); + void Player::StoreLootItem(uint8 lootSlot, Loot* loot) + { + QuestItem* qitem = NULL; +@@ -25005,6 +25007,7 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot) + if (loot->containerID > 0) + loot->DeleteLootItemFromContainerItemDB(item->itemid); + ++ SetRandomEnchantVisual(this, newitem); + } + else + SendEquipError(msg, NULL, NULL, item->itemid); +diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp +index b4f1923..7531bd6 100644 +--- a/src/server/game/Handlers/LootHandler.cpp ++++ b/src/server/game/Handlers/LootHandler.cpp +@@ -386,6 +386,7 @@ void WorldSession::DoLootRelease(ObjectGuid lguid) + loot->RemoveLooter(player->GetGUID()); + } + ++extern void SetRandomEnchantVisual(Player* player, Item* item); + void WorldSession::HandleLootMasterGiveOpcode(WorldPacket& recvData) + { + uint8 slotid; +@@ -479,6 +480,7 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPacket& recvData) + target->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE, loot->loot_type, item.count); + target->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM, item.itemid, item.count); + ++ SetRandomEnchantVisual(target, newitem); + // mark as looted + item.count = 0; + item.is_looted = true; diff --git a/fast_fishing.diff b/fast_fishing.diff new file mode 100644 index 0000000..7b143cd --- /dev/null +++ b/fast_fishing.diff @@ -0,0 +1,290 @@ +src/server/game/Entities/Creature/Creature.cpp | 2 +- + src/server/game/Entities/Player/Player.cpp | 14 +++-- + src/server/game/Entities/Unit/Unit.cpp | 9 +++- + src/server/game/Spells/Spell.cpp | 4 +- + src/server/game/Spells/SpellEffects.cpp | 3 ++ + src/server/game/Spells/SpellInfo.cpp | 6 ++- + src/server/game/World/World.cpp | 11 ++-- + src/server/game/World/World.h | 12 +++-- + src/server/worldserver/worldserver.conf.dist | 74 ++++++++++++++++++++++++++ + 9 files changed, 117 insertions(+), 18 deletions(-) + +diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp +index 73b9e05..552a643 100644 +--- a/src/server/game/Entities/Creature/Creature.cpp ++++ b/src/server/game/Entities/Creature/Creature.cpp +@@ -1594,7 +1594,7 @@ void Creature::setDeathState(DeathState s) + if (s == JUST_DIED) + { + m_corpseRemoveTime = time(NULL) + m_corpseDelay; +- m_respawnTime = time(NULL) + m_respawnDelay + m_corpseDelay; ++ m_respawnTime = time(NULL) + (m_respawnDelay / sWorld->getFloatConfig(CONFIG_RESPAWNSPEED)) + m_corpseDelay; + + // always save boss respawn time at death to prevent crash cheating + if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss()) +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..e1d6fc1 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -1351,7 +1351,10 @@ void Player::Update(uint32 p_time) + + // do attack + AttackerStateUpdate(victim, BASE_ATTACK); +- resetAttackTimer(BASE_ATTACK); ++ if (sWorld->getBoolConfig(CONFIG_HURT_IN_REAL_TIME)) ++ AttackStop(); ++ else ++ resetAttackTimer(BASE_ATTACK); + } + } + +@@ -22417,10 +22420,13 @@ void Player::SendInitialPacketsBeforeAddToMap() + + SendEquipmentSetList(); + ++ float speedrate = sWorld->getFloatConfig(CONFIG_SPEED_GAME); ++ uint32 speedtime = ((sWorld->GetGameTime() - sWorld->GetUptime()) + (sWorld->GetUptime() * speedrate)); ++ + data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4 + 4); +- data.AppendPackedTime(sWorld->GetGameTime()); +- data << float(0.01666667f); // game speed +- data << uint32(0); // added in 3.1.2 ++ data.AppendPackedTime(speedtime); ++ data << float(0.01666667f) * speedrate; // game speed ++ data << uint32(0); // added in 3.1.2 + GetSession()->SendPacket(&data); + + GetReputationMgr().SendForceReactions(); // SMSG_SET_FORCED_REACTIONS +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 3aed5fd..fddeff8 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -455,7 +455,14 @@ void Unit::DisableSpline() + + void Unit::resetAttackTimer(WeaponAttackType type) + { +- m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type]); ++ if (GetTypeId() == TYPEID_PLAYER || (ToCreature()->GetOwner() && ToCreature()->GetOwner()->GetTypeId() == TYPEID_PLAYER)) ++ { ++ m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type] / sWorld->getFloatConfig(CONFIG_ATTACKSPEED_PLAYER)); ++ } ++ else ++ { ++ m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type] / sWorld->getFloatConfig(CONFIG_ATTACKSPEED_ALL)); ++ } + } + + float Unit::GetMeleeReach() const +diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp +index 63fe148..10bc9b1 100644 +--- a/src/server/game/Spells/Spell.cpp ++++ b/src/server/game/Spells/Spell.cpp +@@ -4717,8 +4717,8 @@ SpellCastResult Spell::CheckCast(bool strict) + } + + // Check global cooldown +- if (strict && !(_triggeredCastFlags & TRIGGERED_IGNORE_GCD) && HasGlobalCooldown()) +- return SPELL_FAILED_NOT_READY; ++ if (strict && !(_triggeredCastFlags & TRIGGERED_IGNORE_GCD) && HasGlobalCooldown()) ++ return SPELL_FAILED_NOT_READY; + + // only triggered spells can be processed an ended battleground + if (!IsTriggered() && m_caster->GetTypeId() == TYPEID_PLAYER) +diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp +index f961654..9f4099d 100644 +--- a/src/server/game/Spells/SpellEffects.cpp ++++ b/src/server/game/Spells/SpellEffects.cpp +@@ -5163,6 +5163,9 @@ void Spell::EffectTransmitted(SpellEffIndex effIndex) + case 3: lastSec = 17; break; + } + ++ if (sWorld->getBoolConfig(CONFIG_FAST_FISHING)) ++ lastSec = 17; ++ + duration = duration - lastSec*IN_MILLISECONDS + FISHING_BOBBER_READY_TIME*IN_MILLISECONDS; + break; + } +diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp +index 699e485..5f8f336 100644 +--- a/src/server/game/Spells/SpellInfo.cpp ++++ b/src/server/game/Spells/SpellInfo.cpp +@@ -25,6 +25,7 @@ + #include "Battleground.h" + #include "Vehicle.h" + #include "Pet.h" ++#include "World.h" + + uint32 GetTargetFlagMask(SpellTargetObjectTypes objType) + { +@@ -2114,7 +2115,10 @@ uint32 SpellInfo::CalcCastTime(Spell* spell /*= NULL*/) const + if (HasAttribute(SPELL_ATTR0_REQ_AMMO) && (!IsAutoRepeatRangedSpell())) + castTime += 500; + +- return (castTime > 0) ? uint32(castTime) : 0; ++ if (!sWorld->getBoolConfig(CONFIG_NO_CAST_TIME)) ++ return (castTime > 0) ? uint32(castTime) : 0; ++ else ++ return 0; + } + + uint32 SpellInfo::GetMaxTicks() const +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..98852d1 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1143,11 +1143,13 @@ void World::LoadConfigSettings(bool reload) + // Prepatch by LordPsyan + // 01 + // 02 +- // 03 ++ m_float_configs[CONFIG_SPEED_GAME] = sConfigMgr->GetFloatDefault("Custom.SpeedGame", 1.0f); ++ m_bool_configs[CONFIG_NO_CAST_TIME] = sConfigMgr->GetBoolDefault("Custom.NoCastTime", false); ++ m_bool_configs[CONFIG_HURT_IN_REAL_TIME] = sConfigMgr->GetBoolDefault("Custom.HurtInRealTime", false); + // 04 + // 05 + // 06 +- // 07 ++ m_bool_configs[CONFIG_FAST_FISHING] = sConfigMgr->GetBoolDefault("Custom.FastFishing", false); + // 08 + // 09 + // 10 +@@ -1155,12 +1157,13 @@ void World::LoadConfigSettings(bool reload) + // 12 + // 13 + // 14 +- // 15 ++ m_float_configs[CONFIG_ATTACKSPEED_PLAYER] = sConfigMgr->GetFloatDefault("Custom.AttackSpeedForPlayer", 1.0f); ++ m_float_configs[CONFIG_ATTACKSPEED_ALL] = sConfigMgr->GetFloatDefault("Custom.AttackSpeedForMobs", 1.0f); + // 16 + // 17 + // 18 + // 19 +- // 20 ++ m_float_configs[CONFIG_RESPAWNSPEED] = sConfigMgr->GetFloatDefault("Custom.RespawnSpeed", 1.0f); + // Visit http://www.realmsofwarcraft.com/bb for forums and information + // + // End of prepatch +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 1eb6feb..4c51fe0 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -164,7 +164,8 @@ enum WorldBoolConfigs + // Prepatch by LordPsyan + // 01 + // 02 +- // 03 ++ CONFIG_NO_CAST_TIME, ++ CONFIG_HURT_IN_REAL_TIME, + // 04 + // 05 + // 06 +@@ -172,7 +173,7 @@ enum WorldBoolConfigs + // 08 + // 09 + // 10 +- // 11 ++ CONFIG_FAST_FISHING, + // 12 + // 13 + // 14 +@@ -237,15 +238,16 @@ enum WorldFloatConfigs + // Prepatch by LordPsyan + // 41 + // 42 +- // 43 ++ CONFIG_SPEED_GAME, + // 44 + // 45 + // 46 +- // 47 ++ CONFIG_ATTACKSPEED_PLAYER, ++ CONFIG_ATTACKSPEED_ALL, + // 48 + // 49 + // 50 +- // 51 ++ CONFIG_RESPAWNSPEED, + // 52 + // 53 + // 54 +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..ee9b797 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -3528,6 +3528,80 @@ PacketSpoof.BanDuration = 86400 + # + ################################################################################################### + ++################################################################################################### ++# ++# Custom.SpeedGame ++# Set it to a number upper than 1 to speed up realm timer ++# Default : 1 (Real Time) ++# Example : 60 to force 1scde (real time) = 1 minute (realm timer) ++# ++ ++Custom.SpeedGame = 1 ++ ++# ++# Custom.NoCastTime ++# Set it to 1 to disable cast time ++# Default : 0 (Cast Time Enable) ++# ++ ++Custom.NoCastTime = 0 ++ ++# ++# Custom.HurtInRealTime ++# Set it to 1 to disable autohurt with weapon ++# Default : 0 (Autohurt Enable) ++# ++ ++Custom.HurtInRealTime = 0 ++ ++# ++################################################################################################### ++ ++################################################################################################### ++# ++# Custom.FastFishing ++# Set it to 1 to make fishing a lot faster! ++# Default : 0 (Fishing is normal) ++# ++ ++Custom.FastFishing = 0 ++ ++# ++################################################################################################### ++ ++################################################################################################### ++# ++# Custom.RespawnSpeed ++# Set it to a number upper than 1 to speed up the respawn of mobs (and lower than 1 to slow down). ++# Default : 1.0 (Normal speed) ++# ++ ++Custom.RespawnSpeed = 1.0 ++ ++# ++################################################################################################### ++ ++# ++# Custom.AttackSpeedForPlayer ++# Set it to a number upper than 1 to speed up the attack of the player ++# Default : 1.0 (Normal speed) ++# Example : 2.0 (Player attack speed is 200% faster) ++# ++ ++Custom.AttackSpeedForPlayer = 1.0 ++ ++# ++# Custom.AttackSpeedForMobs ++# Set it to a number upper than 1 to speed up the attack of the mobs ++# Default : 1.0 (Normal speed) ++# Example : 0.5 (Mobs attack speed is 50% slower) ++# ++ ++Custom.AttackSpeedForMobs = 1.0 ++ ++# ++################################################################################################### \ No newline at end of file diff --git a/fixtransfo.diff b/fixtransfo.diff new file mode 100644 index 0000000..831db1c --- /dev/null +++ b/fixtransfo.diff @@ -0,0 +1,19 @@ +src/server/game/Entities/Unit/Unit.cpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index dba6abb..db931b0 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -13111,6 +13111,11 @@ bool Unit::IsInFeralForm() const + + bool Unit::IsInDisallowedMountForm() const + { ++ if (SpellInfo const* transformSpellInfo = sSpellMgr->GetSpellInfo(getTransForm())) ++ if (transformSpellInfo->Attributes & SPELL_ATTR0_CASTABLE_WHILE_MOUNTED) ++ return false; ++ + if (ShapeshiftForm form = GetShapeshiftForm()) + { + SpellShapeshiftEntry const* shapeshift = sSpellShapeshiftStore.LookupEntry(form); +-- \ No newline at end of file diff --git a/guard_elite_honor.diff b/guard_elite_honor.diff new file mode 100644 index 0000000..875d2cb --- /dev/null +++ b/guard_elite_honor.diff @@ -0,0 +1,121 @@ + src/server/game/Entities/Player/Player.cpp | 47 ++++++++++++++++++++++++++++ + src/server/game/World/World.cpp | 3 +- + src/server/game/World/World.h | 3 +- + src/server/worldserver/worldserver.conf.dist | 22 +++++++++++++ + 4 files changed, 73 insertions(+), 2 deletions(-) + +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..0b77495 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -6853,6 +6853,53 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId()); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); + } ++ else if (sWorld->getBoolConfig(CONFIG_GAIN_HONOR_GUARD) && victim->ToCreature()->IsGuard()) ++ { ++ uint8 k_level = getLevel(); ++ uint8 k_grey = Trinity::XP::GetGrayLevel(k_level); ++ uint8 v_level = victim->getLevel(); ++ ++ if (v_level <= k_grey) ++ return false; ++ ++ uint32 victim_title = 0; ++ victim_guid = ObjectGuid::Empty; ++ ++ honor_f = ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); ++ ++ // count the number of playerkills in one day ++ ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true); ++ // and those in a lifetime ++ ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS, 1, true); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, victim->getClass()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, victim->getRace()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); ++ } ++ else if (sWorld->getBoolConfig(CONFIG_GAIN_HONOR_ELITE) && victim->ToCreature()->isElite()) ++ { ++ uint8 k_level = getLevel(); ++ uint8 k_grey = Trinity::XP::GetGrayLevel(k_level); ++ uint8 v_level = victim->getLevel(); ++ ++ if (v_level <= k_grey) ++ return false; ++ ++ uint32 victim_title = 0; ++ victim_guid = ObjectGuid::Empty; ++ honor_f = ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); ++ // count the number of playerkills in one day ++ ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true); ++ ++ // and those in a lifetime ++ ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS, 1, true); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, victim->getClass()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, victim->getRace()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId()); ++ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); ++ } + else + { + if (!victim->ToCreature()->IsRacialLeader()) +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..169f230 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1151,7 +1151,8 @@ void World::LoadConfigSettings(bool reload) + // 08 + // 09 + // 10 +- // 11 ++ m_bool_configs[CONFIG_GAIN_HONOR_GUARD] = sConfigMgr->GetBoolDefault("Custom.GainHonorOnGuardKill", false); ++ m_bool_configs[CONFIG_GAIN_HONOR_ELITE] = sConfigMgr->GetBoolDefault("Custom.GainHonorOnEliteKill", false); + // 12 + // 13 + // 14 +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 1eb6feb..8892493 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -180,7 +180,8 @@ enum WorldBoolConfigs + // 16 + // 17 + // 18 +- // 19 ++ CONFIG_GAIN_HONOR_GUARD, ++ CONFIG_GAIN_HONOR_ELITE, + // 20 + // 21 + // 22 +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..83cfb1a 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -3528,6 +3528,28 @@ PacketSpoof.BanDuration = 86400 + # + ################################################################################################### + ++################################################################################################### ++# ++# Custom.GainHonorOnGuardKill ++# Set it to 1 to gain honor when you kill a guard. ++# Note that not all city guards will give you honor. It seems only capital and big city guards are flagged ++# as so in the database. ++# Default : 0 (No honor when you kill a guard) ++# ++ ++Custom.GainHonorOnGuardKill = 0 ++ ++# ++# Custom.GainHonorOnEliteKill ++# Set it to 1 to gain honor when you kill an elite mob (this does not apply on rare mob, unless he is also elite). ++# Default : 0 (No honor when you kill an elite mob) ++# ++ ++Custom.GainHonorOnEliteKill = 0 ++ ++# ++################################################################################################### \ No newline at end of file diff --git a/lvl_255_list.diff b/lvl_255_list.diff new file mode 100644 index 0000000..b7f3b92 --- /dev/null +++ b/lvl_255_list.diff @@ -0,0 +1,16 @@ +diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp +index 758d5af..c72ee87 100644 +--- a/src/server/game/Handlers/MiscHandler.cpp ++++ b/src/server/game/Handlers/MiscHandler.cpp +@@ -265,9 +265,10 @@ void WorldSession::HandleWhoOpcode(WorldPacket& recvData) + continue; + + // check if target is globally visible for player ++ /* Remove check so Level 255 shows up in who list + if (!target->IsVisibleGloballyFor(_player)) + continue; +- ++*/ + // check if target's level is in level range + uint8 lvl = target->getLevel(); + if (lvl < level_min || lvl > level_max) \ No newline at end of file diff --git a/mercenario.diff b/mercenario.diff new file mode 100644 index 0000000..73cdb13 --- /dev/null +++ b/mercenario.diff @@ -0,0 +1,480 @@ +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp +index 6494f92..2de6b5a 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp ++++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp +@@ -610,4 +610,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() + PrepareStatement(CHAR_UPD_QUEST_TRACK_GM_COMPLETE, "UPDATE quest_tracker SET completed_by_gm = 1 WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, "UPDATE quest_tracker SET quest_complete_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_QUEST_TRACK_ABANDON_TIME, "UPDATE quest_tracker SET quest_abandon_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); ++ ++ // Mercenary ++ PrepareStatement(CHAR_INS_MERCENARY, "INSERT INTO mercenaries VALUES(?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH); ++ PrepareStatement(CHAR_DEL_MERCENARY, "DELETE FROM mercenaries WHERE Id=?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_UPD_MERCENARY_SUMMON, "UPDATE mercenaries SET summoned=? WHERE Id=?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_INS_MERCENARY_GEAR, "INSERT INTO mercenary_gear (guid, itemId, slot) VALUES (?, ?, ?)", CONNECTION_BOTH); ++ PrepareStatement(CHAR_UPD_MERCENARY_GEAR, "UPDATE mercenary_gear SET itemId=? WHERE guid=? AND slot=?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_UPD_MERCENARY_NAME, "UPDATE character_pet SET name=? WHERE Id=? and owner=?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_DEL_MERCENARY_GEAR, "DELETE FROM mercenary_gear WHERE guid=?", CONNECTION_ASYNC); + } +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h +index a7ab5d0..afc838b 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.h ++++ b/src/server/database/Database/Implementation/CharacterDatabase.h +@@ -543,6 +543,14 @@ enum CharacterDatabaseStatements + CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, + CHAR_UPD_QUEST_TRACK_ABANDON_TIME, + ++ CHAR_INS_MERCENARY, ++ CHAR_DEL_MERCENARY, ++ CHAR_UPD_MERCENARY_SUMMON, ++ CHAR_INS_MERCENARY_GEAR, ++ CHAR_UPD_MERCENARY_GEAR, ++ CHAR_DEL_MERCENARY_GEAR, ++ CHAR_UPD_MERCENARY_NAME, ++ + MAX_CHARACTERDATABASE_STATEMENTS + }; + +diff --git a/src/server/game/AI/CreatureAISelector.cpp b/src/server/game/AI/CreatureAISelector.cpp +index d3e1173..c079b19 100644 +--- a/src/server/game/AI/CreatureAISelector.cpp ++++ b/src/server/game/AI/CreatureAISelector.cpp +@@ -24,6 +24,7 @@ + #include "TemporarySummon.h" + #include "CreatureAIFactory.h" + #include "ScriptMgr.h" ++#include "MercenaryMgr.h" + + namespace FactorySelector + { +@@ -32,7 +33,7 @@ namespace FactorySelector + const CreatureAICreator* ai_factory = NULL; + CreatureAIRegistry& ai_registry(*CreatureAIRegistry::instance()); + +- if (creature->IsPet()) ++ if (creature->IsPet() && creature->GetScriptName() != sMercenaryMgr->GetAIName()) + ai_factory = ai_registry.GetRegistryItem("PetAI"); + + //scriptname in db +diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp +index e268b37..570d4db 100644 +--- a/src/server/game/DataStores/DBCStores.cpp ++++ b/src/server/game/DataStores/DBCStores.cpp +@@ -119,7 +119,7 @@ DBCStorage sHolidaysStore(Holidaysfmt); + DBCStorage sItemStore(Itemfmt); + DBCStorage sItemBagFamilyStore(ItemBagFamilyfmt); + //DBCStorage sItemCondExtCostsStore(ItemCondExtCostsEntryfmt); +-//DBCStorage sItemDisplayInfoStore(ItemDisplayTemplateEntryfmt); -- not used currently ++DBCStorage sItemDisplayInfoStore(ItemDisplayTemplateEntryfmt); + DBCStorage sItemExtendedCostStore(ItemExtendedCostEntryfmt); + DBCStorage sItemLimitCategoryStore(ItemLimitCategoryEntryfmt); + DBCStorage sItemRandomPropertiesStore(ItemRandomPropertiesfmt); +@@ -175,6 +175,7 @@ DBCStorage sSpellRadiusStore(SpellRadiusfmt); + DBCStorage sSpellRangeStore(SpellRangefmt); + DBCStorage sSpellRuneCostStore(SpellRuneCostfmt); + DBCStorage sSpellShapeshiftStore(SpellShapeshiftfmt); ++DBCStorage sSpellIconStore(SpellIconEntryfmt); + DBCStorage sStableSlotPricesStore(StableSlotPricesfmt); + DBCStorage sSummonPropertiesStore(SummonPropertiesfmt); + DBCStorage sTalentStore(TalentEntryfmt); +@@ -378,7 +379,7 @@ void LoadDBCStores(const std::string& dataPath) + + LoadDBC(availableDbcLocales, bad_dbc_files, sItemStore, dbcPath, "Item.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sItemBagFamilyStore, dbcPath, "ItemBagFamily.dbc"); +- //LoadDBC(dbcCount, availableDbcLocales, bad_dbc_files, sItemDisplayInfoStore, dbcPath, "ItemDisplayInfo.dbc"); -- not used currently ++ LoadDBC(availableDbcLocales, bad_dbc_files, sItemDisplayInfoStore, dbcPath, "ItemDisplayInfo.dbc"); + //LoadDBC(dbcCount, availableDbcLocales, bad_dbc_files, sItemCondExtCostsStore, dbcPath, "ItemCondExtCosts.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sItemExtendedCostStore, dbcPath, "ItemExtendedCost.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sItemLimitCategoryStore, dbcPath, "ItemLimitCategory.dbc"); +@@ -478,6 +479,7 @@ void LoadDBCStores(const std::string& dataPath) + LoadDBC(availableDbcLocales, bad_dbc_files, sSpellRangeStore, dbcPath, "SpellRange.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sSpellRuneCostStore, dbcPath, "SpellRuneCost.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sSpellShapeshiftStore, dbcPath, "SpellShapeshiftForm.dbc"); ++ LoadDBC(availableDbcLocales, bad_dbc_files, sSpellIconStore, dbcPath, "SpellIcon.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sStableSlotPricesStore, dbcPath, "StableSlotPrices.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sSummonPropertiesStore, dbcPath, "SummonProperties.dbc"); + +diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h +index d955e95..647ff52 100644 +--- a/src/server/game/DataStores/DBCStores.h ++++ b/src/server/game/DataStores/DBCStores.h +@@ -132,7 +132,7 @@ extern DBCStorage sGtRegenMPPerSptStore; + extern DBCStorage sHolidaysStore; + extern DBCStorage sItemStore; + extern DBCStorage sItemBagFamilyStore; +-//extern DBCStorage sItemDisplayInfoStore; -- not used currently ++extern DBCStorage sItemDisplayInfoStore; + extern DBCStorage sItemExtendedCostStore; + extern DBCStorage sItemLimitCategoryStore; + extern DBCStorage sItemRandomPropertiesStore; +@@ -172,6 +172,7 @@ extern DBCStorage sSpellRangeStore; + extern DBCStorage sSpellRuneCostStore; + extern DBCStorage sSpellShapeshiftStore; + extern DBCStorage sSpellStore; ++extern DBCStorage sSpellIconStore; + extern DBCStorage sStableSlotPricesStore; + extern DBCStorage sSummonPropertiesStore; + extern DBCStorage sTalentStore; +diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h +index 092ef71..b08d458 100644 +--- a/src/server/game/DataStores/DBCStructure.h ++++ b/src/server/game/DataStores/DBCStructure.h +@@ -1165,6 +1165,7 @@ struct ItemBagFamilyEntry + struct ItemDisplayInfoEntry + { + uint32 ID; // 0 m_ID ++ char* inventoryIcon; // [1] + // 1 m_modelName[2] + // 2 m_modelTexture[2] + // 3 m_inventoryIcon +@@ -1856,6 +1857,12 @@ struct SpellItemEnchantmentConditionEntry + //uint8 Logic[5] // 25-30 m_logic[5] + }; + ++struct SpellIconEntry ++{ ++ uint32 ID; // 0 ++ char* spellIcon; // 1 ++}; ++ + struct StableSlotPricesEntry + { + uint32 Slot; +diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h +index 5c24e6f..6bdc0d6 100644 +--- a/src/server/game/DataStores/DBCfmt.h ++++ b/src/server/game/DataStores/DBCfmt.h +@@ -73,7 +73,7 @@ char const GtRegenMPPerSptfmt[] = "f"; + char const Holidaysfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiixxsiix"; + char const Itemfmt[] = "niiiiiii"; + char const ItemBagFamilyfmt[] = "nxxxxxxxxxxxxxxxxx"; +-//char const ItemDisplayTemplateEntryfmt[] = "nxxxxxxxxxxixxxxxxxxxxx"; ++char const ItemDisplayTemplateEntryfmt[] = "nxxxxsxxxxxxxxxxxxxxxxxxx"; + //char const ItemCondExtCostsEntryfmt[] = "xiii"; + char const ItemExtendedCostEntryfmt[] = "niiiiiiiiiiiiiix"; + char const ItemLimitCategoryEntryfmt[] = "nxxxxxxxxxxxxxxxxxii"; +@@ -118,6 +118,7 @@ char const SpellRadiusfmt[] = "nfff"; + char const SpellRangefmt[] = "nffffixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + char const SpellRuneCostfmt[] = "niiii"; + char const SpellShapeshiftfmt[] = "nxxxxxxxxxxxxxxxxxxiixiiixxiiiiiiii"; ++char const SpellIconEntryfmt[] = "ns"; + char const StableSlotPricesfmt[] = "ni"; + char const SummonPropertiesfmt[] = "niiiii"; + char const TalentEntryfmt[] = "niiiiiiiixxxxixxixxxxxx"; +diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp +index 8ac54d5..b647514 100644 +--- a/src/server/game/Entities/Creature/Creature.cpp ++++ b/src/server/game/Entities/Creature/Creature.cpp +@@ -50,6 +50,7 @@ + #include "LuaEngine.h" + #endif + #include "Transport.h" ++#include "MercenaryMgr.h" + + TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const + { +@@ -330,6 +331,11 @@ bool Creature::InitEntry(uint32 entry, CreatureData const* data /*= nullptr*/) + return false; + } + ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_MIRROR_IMAGE); ++ else ++ RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_MIRROR_IMAGE); ++ + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); +@@ -392,7 +398,10 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/) + SetUInt32Value(UNIT_NPC_FLAGS, npcflag); + + SetUInt32Value(UNIT_FIELD_FLAGS, unit_flags); +- SetUInt32Value(UNIT_FIELD_FLAGS_2, cInfo->unit_flags2); ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ SetUInt32Value(UNIT_FIELD_FLAGS_2, cInfo->unit_flags2 | UNIT_FLAG2_MIRROR_IMAGE); ++ else ++ SetUInt32Value(UNIT_FIELD_FLAGS_2, cInfo->unit_flags2); + + SetUInt32Value(UNIT_DYNAMIC_FLAGS, dynamicflags); + +diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp +index 57fc56a..4462bd7 100644 +--- a/src/server/game/Entities/Pet/Pet.cpp ++++ b/src/server/game/Entities/Pet/Pet.cpp +@@ -31,6 +31,7 @@ + #include "Util.h" + #include "Group.h" + #include "WorldSession.h" ++#include "MercenaryMgr.h" + + #define PET_XP_FACTOR 0.05f + +@@ -313,6 +314,8 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c + owner->SetMinion(this, true); + map->AddToMap(this->ToCreature()); + ++ sMercenaryMgr->OnSummon(owner); ++ + InitTalentForLevel(); // set original talents points before spell loading + + uint32 timediff = uint32(time(NULL) - fields[14].GetUInt32()); +@@ -510,6 +513,8 @@ void Pet::DeleteFromDB(uint32 guidlow) + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); ++ ++ sMercenaryMgr->OnDelete(guidlow); + } + + void Pet::setDeathState(DeathState s) // overwrite virtual Creature::setDeathState and Unit::setDeathState +diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp +index fb27fea..9aac24d 100644 +--- a/src/server/game/Entities/Unit/StatSystem.cpp ++++ b/src/server/game/Entities/Unit/StatSystem.cpp +@@ -24,6 +24,7 @@ + #include "SpellAuras.h" + #include "SpellAuraEffects.h" + #include "World.h" ++#include "MercenaryMgr.h" + + inline bool _ModifyUInt32(bool apply, uint32& baseValue, int32& amount) + { +@@ -958,6 +959,9 @@ bool Creature::UpdateStats(Stats /*stat*/) + + bool Creature::UpdateAllStats() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return false; ++ + UpdateMaxHealth(); + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); +@@ -983,18 +987,27 @@ void Creature::UpdateResistances(uint32 school) + + void Creature::UpdateArmor() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float value = GetTotalAuraModValue(UNIT_MOD_ARMOR); + SetArmor(int32(value)); + } + + void Creature::UpdateMaxHealth() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float value = GetTotalAuraModValue(UNIT_MOD_HEALTH); + SetMaxHealth(uint32(value)); + } + + void Creature::UpdateMaxPower(Powers power) + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + UnitMods unitMod = UnitMods(UNIT_MOD_POWER_START + power); + + float value = GetTotalAuraModValue(unitMod); +@@ -1003,6 +1016,9 @@ void Creature::UpdateMaxPower(Powers power) + + void Creature::UpdateAttackPowerAndDamage(bool ranged) + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + UnitMods unitMod = ranged ? UNIT_MOD_ATTACK_POWER_RANGED : UNIT_MOD_ATTACK_POWER; + + uint16 index = UNIT_FIELD_ATTACK_POWER; +@@ -1036,6 +1052,9 @@ void Creature::UpdateAttackPowerAndDamage(bool ranged) + + void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage) + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float variance = 1.0f; + UnitMods unitMod; + switch (attType) +@@ -1105,6 +1124,9 @@ bool Guardian::UpdateStats(Stats stat) + if (stat >= MAX_STATS) + return false; + ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return false; ++ + // value = ((base_value * base_pct) + total_value) * total_pct + float value = GetTotalStatValue(stat); + ApplyStatBuffMod(stat, m_statFromOwner[stat], false); +@@ -1197,6 +1219,9 @@ bool Guardian::UpdateStats(Stats stat) + + bool Guardian::UpdateAllStats() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return false; ++ + for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) + UpdateStats(Stats(i)); + +@@ -1226,6 +1251,9 @@ void Guardian::UpdateResistances(uint32 school) + + void Guardian::UpdateArmor() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float value = 0.0f; + float bonus_armor = 0.0f; + UnitMods unitMod = UNIT_MOD_ARMOR; +@@ -1245,6 +1273,9 @@ void Guardian::UpdateArmor() + + void Guardian::UpdateMaxHealth() + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + UnitMods unitMod = UNIT_MOD_HEALTH; + float stamina = GetStat(STAT_STAMINA) - GetCreateStat(STAT_STAMINA); + +@@ -1270,6 +1301,9 @@ void Guardian::UpdateMaxHealth() + + void Guardian::UpdateMaxPower(Powers power) + { ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + UnitMods unitMod = UnitMods(UNIT_MOD_POWER_START + power); + + float addValue = (power == POWER_MANA) ? GetStat(STAT_INTELLECT) - GetCreateStat(STAT_INTELLECT) : 0.0f; +@@ -1298,6 +1332,9 @@ void Guardian::UpdateAttackPowerAndDamage(bool ranged) + if (ranged) + return; + ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float val = 0.0f; + float bonusAP = 0.0f; + UnitMods unitMod = UNIT_MOD_ATTACK_POWER; +@@ -1386,6 +1423,9 @@ void Guardian::UpdateDamagePhysical(WeaponAttackType attType) + if (attType > BASE_ATTACK) + return; + ++ if (GetScriptName() == sMercenaryMgr->GetAIName()) ++ return; ++ + float bonusDamage = 0.0f; + if (m_owner->GetTypeId() == TYPEID_PLAYER) + { +diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp +index 52dc2ad..60ca1c5 100644 +--- a/src/server/game/Handlers/MiscHandler.cpp ++++ b/src/server/game/Handlers/MiscHandler.cpp +@@ -112,7 +112,7 @@ void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recvData) + #endif + Creature* unit = NULL; + GameObject* go = NULL; +- if (guid.IsCreatureOrVehicle()) ++ if (guid.IsCreatureOrVehicle() || guid.IsCreatureOrPet()) + { + unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE); + if (!unit) +diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp +index 0f0640e..ac9859f 100644 +--- a/src/server/game/Handlers/SpellHandler.cpp ++++ b/src/server/game/Handlers/SpellHandler.cpp +@@ -31,6 +31,7 @@ + #include "SpellAuraEffects.h" + #include "Player.h" + #include "Config.h" ++#include "MercenaryMgr.h" + + void WorldSession::HandleClientCastFlags(WorldPacket& recvPacket, uint8 castFlags, SpellCastTargets& targets) + { +@@ -583,6 +584,40 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) + if (!unit) + return; + ++ if (Pet* pet = unit->ToPet()) ++ { ++ Mercenary* mercenary = sMercenaryMgr->GetMercenary(pet->GetCharmInfo()->GetPetNumber()); ++ if (mercenary) ++ { ++ WorldPacket data(SMSG_MIRRORIMAGE_DATA, 68); ++ data << uint64(pet->GetGUID()); ++ data << uint32(mercenary->GetDisplay()); ++ data << uint8(mercenary->GetRace()); ++ data << uint8(mercenary->GetGender()); ++ data << uint8(1); ++ data << uint8(0); // Skin ++ data << uint8(0); // Face ++ data << uint8(0); // Hair ++ data << uint8(0); // Hair color ++ data << uint8(0); // Facial hair ++ data << uint32(0); ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_HEAD))); ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_SHOULDERS))); ++ data << uint32(0); // Shirt? ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_CHEST))); ++ data << uint32(0); // Waist ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_LEGS))); ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_FEET))); ++ data << uint32(0); // Wrists ++ data << uint32(sMercenaryMgr->GetItemDisplayId(mercenary->GetItemBySlot(SLOT_HANDS))); ++ data << uint32(0); // Cloak ++ data << uint32(0); // Tabard ++ ++ SendPacket(&data); ++ return; ++ } ++ } ++ + if (!unit->HasAuraType(SPELL_AURA_CLONE_CASTER)) + return; + +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index 7c4b117..6251aee 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1415,13 +1415,13 @@ void AddBattlegroundScripts() + + #ifdef SCRIPTS + /* This is where custom scripts' loading functions should be declared. */ +- ++ void MercenarySetup(); + #endif + + void AddCustomScripts() + { + #ifdef SCRIPTS + /* This is where custom scripts should be added. */ +- ++ MercenarySetup(); + #endif + } +diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp +index 02fec47..c576207 100644 +--- a/src/server/game/Server/WorldSession.cpp ++++ b/src/server/game/Server/WorldSession.cpp +@@ -1390,6 +1390,7 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co + case CMSG_BEGIN_TRADE: // 0 2.5 + case CMSG_INITIATE_TRADE: // 0 3 + case CMSG_MESSAGECHAT: // 0 3.5 ++ case CMSG_GET_MIRRORIMAGE_DATA: // not profiled + case CMSG_INSPECT: // 0 3.5 + case CMSG_AREA_SPIRIT_HEALER_QUERY: // not profiled + case CMSG_STANDSTATECHANGE: // not profiled +diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt +index 5218f76..f1fcb85 100644 +--- a/src/server/scripts/Custom/CMakeLists.txt ++++ b/src/server/scripts/Custom/CMakeLists.txt +@@ -12,7 +12,7 @@ + + set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} +-# ${sources_Custom} ++ ${sources_Custom} + ) + + message(" -> Prepared: Custom") \ No newline at end of file diff --git a/min-player-time.diff b/min-player-time.diff new file mode 100644 index 0000000..e0c6cc9 --- /dev/null +++ b/min-player-time.diff @@ -0,0 +1,63 @@ +diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp +index 4ebccc5..d8ef043 100644 +--- a/src/server/game/Handlers/ChatHandler.cpp ++++ b/src/server/game/Handlers/ChatHandler.cpp +@@ -47,6 +47,18 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) + + recvData >> type; + recvData >> lang; ++/* Chat Min PlayedTime Coded By IranCore.Ir */ ++ Player* chatkonande = GetPlayer(); ++ { ++ if ((chatkonande->GetTotalPlayedTime() <= sWorld->getIntConfig(CONFIG_INT_CHAT_PLAYED_TIME)) && chatkonande->GetSession()->GetSecurity() == SEC_PLAYER) ++ { ++ std::string adStr = secsToTimeString(sWorld->getIntConfig(CONFIG_INT_CHAT_PLAYED_TIME) - chatkonande->GetTotalPlayedTime()); ++ SendNotification("Shoma Bayad %s seconds Dar Server Bazi Konid Ta Dar Server Betonid Chat Konid.", adStr.c_str()); ++ recvData.rfinish(); ++ return; ++ } ++ } ++/*End Chat MIn Played Time */ + + if (type >= MAX_CHAT_MSG_TYPE) + { +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index cf6ad3f..0f19768 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1239,6 +1239,8 @@ void World::LoadConfigSettings(bool reload) + m_int_configs[CONFIG_PACKET_SPOOF_BANMODE] = BAN_ACCOUNT; + + m_int_configs[CONFIG_PACKET_SPOOF_BANDURATION] = sConfigMgr->GetIntDefault("PacketSpoof.BanDuration", 86400); ++ // Chat Min Played Time ++ m_int_configs[CONFIG_INT_CHAT_PLAYED_TIME] = sConfigMgr->GetIntDefault("bastanchatbaplayertime", 60); + + // call ScriptMgr if we're reloading the configuration + if (reload) +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 9074914..a8eec5e 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -330,6 +330,7 @@ enum WorldIntConfigs + CONFIG_PACKET_SPOOF_BANMODE, + CONFIG_PACKET_SPOOF_BANDURATION, + CONFIG_ACC_PASSCHANGESEC, ++ CONFIG_INT_CHAT_PLAYED_TIME, + INT_CONFIG_VALUE_COUNT + }; + +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index dbf2a80..2208540 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -2839,3 +2839,10 @@ PacketSpoof.BanDuration = 86400 + + # + ################################################################################################### ++############################################Custom################################################# ++# Chat Min Player Time ++# Tozihat: In Script Be Shoma Emkane Ino Mide Ke Baraye BaziKonan Time Moshkhas Konid Ta Betonan ++# Chat Konan Time Chat be Sanie Ast Pas Lotfan Be Sanie Time Ro Vared Konid. ++# tanzimate Pishfarz: 3600 Sanie = 60 min ++ ++bastanchatbaplayertime = 3600 \ No newline at end of file diff --git a/multivendor.diff b/multivendor.diff new file mode 100644 index 0000000..0608379 --- /dev/null +++ b/multivendor.diff @@ -0,0 +1,220 @@ +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 1a9c0fb..072477e 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -13914,7 +13914,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool + break; + case GOSSIP_OPTION_VENDOR: + { +- VendorItemData const* vendorItems = creature->GetVendorItems(); ++ VendorItemData const* vendorItems = itr->second.ActionMenuId ? sObjectMgr->GetNpcVendorItemList(itr->second.ActionMenuId) : creature->GetVendorItems(); + if (!vendorItems || vendorItems->Empty()) + { + TC_LOG_ERROR("sql.sql", "Creature %s (Entry: %u GUID: %u DB GUID: %u) has UNIT_NPC_FLAG_VENDOR set but has an empty trading item list.", creature->GetName().c_str(), creature->GetEntry(), creature->GetGUID().GetCounter(), creature->GetSpawnId()); +@@ -14125,7 +14125,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men + break; + case GOSSIP_OPTION_VENDOR: + case GOSSIP_OPTION_ARMORER: +- GetSession()->SendListInventory(guid); ++ GetSession()->SendListInventory(guid, menuItemData->GossipActionMenuId); + break; + case GOSSIP_OPTION_STABLEPET: + GetSession()->SendStablePet(guid); +@@ -21192,7 +21192,11 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin + return false; + } + +- VendorItemData const* vItems = creature->GetVendorItems(); ++ uint32 currentVendor = GetSession()->GetCurrentVendor(); ++ if (currentVendor && vendorguid != PlayerTalkClass->GetGossipMenu().GetSenderGUID()) ++ return false; // Cheating ++ ++ VendorItemData const* vItems = currentVendor ? sObjectMgr->GetNpcVendorItemList(currentVendor) : creature->GetVendorItems(); + if (!vItems || vItems->Empty()) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0); +diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp +index 046ce42..33706ea 100644 +--- a/src/server/game/Globals/ObjectMgr.cpp ++++ b/src/server/game/Globals/ObjectMgr.cpp +@@ -8467,8 +8467,9 @@ bool ObjectMgr::RemoveVendorItem(uint32 entry, uint32 item, bool persist /*= tru + return true; + } + +-bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* player, std::set* skip_vendors, uint32 ORnpcflag) const ++bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* player, std::set* /*skip_vendors*/, uint32 /*ORnpcflag*/) const + { ++ /* + CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(vendor_entry); + if (!cInfo) + { +@@ -8493,6 +8494,7 @@ bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 max + } + return false; + } ++ */ + + if (!sObjectMgr->GetItemTemplate(item_id)) + { +diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp +index b1f46c3..6a78db1 100644 +--- a/src/server/game/Handlers/ItemHandler.cpp ++++ b/src/server/game/Handlers/ItemHandler.cpp +@@ -726,7 +726,7 @@ void WorldSession::HandleListInventoryOpcode(WorldPacket& recvData) + SendListInventory(guid); + } + +-void WorldSession::SendListInventory(ObjectGuid vendorGuid) ++void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry) + { + TC_LOG_DEBUG("network", "WORLD: Sent SMSG_LIST_INVENTORY"); + +@@ -746,7 +746,7 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) + if (vendor->HasUnitState(UNIT_STATE_MOVING)) + vendor->StopMoving(); + +- VendorItemData const* items = vendor->GetVendorItems(); ++ VendorItemData const* items = vendorEntry ? sObjectMgr->GetNpcVendorItemList(vendorEntry) : vendor->GetVendorItems(); + if (!items) + { + WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + 1); +@@ -757,6 +757,8 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) + return; + } + ++ SetCurrentVendor(vendorEntry); ++ + uint8 itemCount = items->GetItemCount(); + uint8 count = 0; + +diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp +index b1a043c..a392f26 100644 +--- a/src/server/game/Server/WorldSession.cpp ++++ b/src/server/game/Server/WorldSession.cpp +@@ -123,6 +123,7 @@ WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptrGetEntry(); ++ char* addMulti = strtok(NULL, " "); ++ uint32 vendor_entry = addMulti ? handler->GetSession()->GetCurrentVendor() : vendor ? vendor->GetEntry() : 0; + + if (!sObjectMgr->IsVendorItemValid(vendor_entry, itemId, maxcount, incrtime, extendedcost, handler->GetSession()->GetPlayer())) + { +@@ -531,7 +532,8 @@ class npc_commandscript : public CommandScript + } + uint32 itemId = atoul(pitem); + +- if (!sObjectMgr->RemoveVendorItem(vendor->GetEntry(), itemId)) ++ char* addMulti = strtok(NULL, " "); ++ if (!sObjectMgr->RemoveVendorItem(addMulti ? handler->GetSession()->GetCurrentVendor() : vendor->GetEntry(), itemId)) + { + handler->PSendSysMessage(LANG_ITEM_NOT_IN_LIST, itemId); + handler->SetSentErrorMessage(true); +diff --git a/src/server/scripts/Custom/Multivendor/MultivendorExample.sql b/src/server/scripts/Custom/Multivendor/MultivendorExample.sql +new file mode 100644 +index 0000000..ef94f21 +--- /dev/null ++++ b/src/server/scripts/Custom/Multivendor/MultivendorExample.sql +@@ -0,0 +1,10 @@ ++SET @ENTRY = (SELECT max(entry)+1 FROM creature_template); ++SET @MENU_ID = (SELECT max(menu_id)+1 FROM gossip_menu_option); ++ ++INSERT INTO `creature_template` (`entry`, `modelid1`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `speed_walk`, `speed_run`, `scale`, `rank`, `dmgschool`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `unit_flags2`, `dynamicflags`, `family`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `PetSpellDataId`, `VehicleId`, `mingold`, `maxgold`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`) VALUES ++(@ENTRY, 1298, "Herbert", "MultiVendor", NULL, @MENU_ID, 10, 10, 0, 35, 129, 1, 1.14286, 1, 0, 0, 1500, 0, 1, 512, 2048, 8, 0, 0, 0, 0, 0, 7, 138412032, 0, 0, 0, 0, 0, 0, 0, '', 0, 3, 1, 0, 0, 1, 0, 2, ''); ++ ++INSERT INTO `gossip_menu_option` (`menu_id`, `id`, `option_icon`, `option_text`, `option_id`, `npc_option_npcflag`, `action_menu_id`, `action_poi_id`, `box_coded`, `box_money`, `box_text`) VALUES ++(@MENU_ID, 0, 4, 'VendorTest 465', 3, 128, 465, 0, 0, 0, ''), ++(@MENU_ID, 1, 9, 'VendorTest 54', 3, 128, 54, 0, 0, 0, ''), ++(@MENU_ID, 2, 6, 'VendorTest 35574', 3, 128, 35574, 0, 0, 100, 'These goods are special, so pay up!'); +diff --git a/src/server/scripts/Custom/Multivendor/README.md b/src/server/scripts/Custom/Multivendor/README.md +new file mode 100644 +index 0000000..cb752bb +--- /dev/null ++++ b/src/server/scripts/Custom/Multivendor/README.md +@@ -0,0 +1,42 @@ ++#Multivendor [![Build Status](https://travis-ci.org/Rochet2/TrinityCore.svg?branch=multivendor)](https://travis-ci.org/Rochet2/TrinityCore) ++ ++####About ++Allows you to show gossip options that show different vendors from npc_vendor.
++Source: https://rochet2.github.io/?page=Multivendor ++ ++####Installation ++ ++Available as: ++- Direct merge: https://github.com/Rochet2/TrinityCore/tree/multivendor ++- Diff: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...multivendor.diff ++- Patch: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...multivendor.patch ++ ++Using direct merge: ++- open git bash to source location ++- do `git remote add rochet2 https://github.com/Rochet2/TrinityCore.git` ++- do `git pull rochet2 multivendor` ++- use cmake and compile ++ ++Using diff: ++- DO NOT COPY THE DIFF DIRECTLY! It causes apply to fail. ++- download the diff by __right clicking__ the link and select __Save link as__ ++- place the downloaded `multivendor.diff` to the source root folder ++- open git bash to source location ++- do `git apply multivendor.diff` ++- use cmake and compile ++ ++####Usage ++Set your NPC to have gossip and vendor NPCflags (129)
++Add a gossip menu for him and add a new option to it.
++The option needs to have option_id set to 3 so it acts as a vendor button,
++npc_option_npcflag can be 1 or 128 (shows only if the NPC has vendor flag then)
++and the action_menu_id is the vendor entry from npc_vendor that you want to show to the player.
++ ++YOU CAN also send menus from C++. All you need to do is to provide the vendor entry to the
++`void WorldSession::SendListInventory(ObjectGuid guid, uint32 vendorEntry)` function. ++ ++The npc add item command was modified so you can add items to multivendors as well.
++The last vendor window must have been the multivendor you want to add your item to.
++After opening the window you need to type `.npc add item #itemId <#maxcount><#incrtime><#extendedcost> 1`
++The 1 in the end specifies that you are adding the item to the multivendor currently open.
++Same thing works with `.npc delete item #itemId 1` \ No newline at end of file diff --git a/npc_bot.diff b/npc_bot.diff new file mode 100644 index 0000000..e03a030 --- /dev/null +++ b/npc_bot.diff @@ -0,0 +1,28445 @@ +From 7fe24e0091b760c246de8cc180ea9cc616e59c0c Mon Sep 17 00:00:00 2001 +From: LordPsyan +Date: Fri, 5 Feb 2016 18:07:13 -0600 +Subject: [PATCH] 2016_02_05-New-NPCBots + +--- + sql/TrinityCore-Patches/Bots/auth_bots.sql | 27 + + sql/TrinityCore-Patches/Bots/character_bots.sql | 26 + + sql/TrinityCore-Patches/Bots/world_bots.sql | 170 + + .../Database/Implementation/CharacterDatabase.cpp | 20 +- + .../Database/Implementation/CharacterDatabase.h | 15 +- + .../Database/Implementation/WorldDatabase.cpp | 5 + + .../Database/Implementation/WorldDatabase.h | 5 + + src/server/game/AI/NpcBots/QBots/botQ_Airen.cpp | 121 + + src/server/game/AI/NpcBots/bot_Events.h | 133 + + src/server/game/AI/NpcBots/bot_GridNotifiers.h | 724 ++ + src/server/game/AI/NpcBots/bot_ai.cpp | 8126 ++++++++++++++++++++ + src/server/game/AI/NpcBots/bot_ai.h | 908 +++ + src/server/game/AI/NpcBots/bot_bm_ai.cpp | 968 +++ + src/server/game/AI/NpcBots/bot_death_knight_ai.cpp | 1622 ++++ + src/server/game/AI/NpcBots/bot_druid_ai.cpp | 1389 ++++ + src/server/game/AI/NpcBots/bot_hunter_ai.cpp | 1086 +++ + src/server/game/AI/NpcBots/bot_mage_ai.cpp | 910 +++ + src/server/game/AI/NpcBots/bot_paladin_ai.cpp | 1173 +++ + src/server/game/AI/NpcBots/bot_priest_ai.cpp | 1066 +++ + src/server/game/AI/NpcBots/bot_rogue_ai.cpp | 828 ++ + src/server/game/AI/NpcBots/bot_shaman_ai.cpp | 1340 ++++ + src/server/game/AI/NpcBots/bot_warlock_ai.cpp | 519 ++ + src/server/game/AI/NpcBots/bot_warrior_ai.cpp | 1915 +++++ + src/server/game/AI/NpcBots/botcommands.cpp | 931 +++ + src/server/game/AI/NpcBots/botmgr.cpp | 841 ++ + src/server/game/AI/NpcBots/botmgr.h | 110 + + src/server/game/Accounts/RBAC.h | 12 +- + src/server/game/CMakeLists.txt | 1 + + src/server/game/DungeonFinding/LFGMgr.cpp | 159 + + src/server/game/Entities/Creature/Creature.cpp | 465 ++ + src/server/game/Entities/Creature/Creature.h | 86 +- + .../game/Entities/Creature/TemporarySummon.cpp | 10 + + src/server/game/Entities/Object/Object.cpp | 16 + + src/server/game/Entities/Player/KillRewarder.cpp | 14 + + src/server/game/Entities/Player/Player.cpp | 111 +- + src/server/game/Entities/Player/Player.h | 27 +- + src/server/game/Entities/Totem/Totem.cpp | 7 + + src/server/game/Entities/Unit/StatSystem.cpp | 4 + + src/server/game/Entities/Unit/Unit.cpp | 565 ++ + src/server/game/Entities/Unit/Unit.h | 12 + + src/server/game/Globals/ObjectMgr.cpp | 76 + + src/server/game/Globals/ObjectMgr.h | 20 + + src/server/game/Groups/Group.cpp | 23 +- + src/server/game/Groups/Group.h | 3 + + src/server/game/Handlers/SpellHandler.cpp | 30 + + src/server/game/Maps/Map.cpp | 30 +- + src/server/game/Maps/MapManager.cpp | 80 + + src/server/game/Movement/MotionMaster.cpp | 14 + + src/server/game/OutdoorPvP/OutdoorPvP.cpp | 17 + + src/server/game/Scripting/ScriptLoader.cpp | 34 +- + src/server/game/Server/WorldSession.cpp | 4 + + src/server/game/Spells/Spell.cpp | 20 + + src/server/game/Spells/SpellInfo.cpp | 5 + + src/server/game/World/World.cpp | 3 + + src/server/scripts/CMakeLists.txt | 1 + + src/server/scripts/Commands/cs_npc.cpp | 103 + + src/server/scripts/Spells/spell_paladin.cpp | 40 + + src/server/scripts/Spells/spell_priest.cpp | 4 + + src/server/worldserver/worldserver.conf.dist | 122 + + 59 files changed, 27085 insertions(+), 11 deletions(-) + create mode 100644 sql/TrinityCore-Patches/Bots/auth_bots.sql + create mode 100644 sql/TrinityCore-Patches/Bots/character_bots.sql + create mode 100644 sql/TrinityCore-Patches/Bots/world_bots.sql + create mode 100644 src/server/game/AI/NpcBots/QBots/botQ_Airen.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_Events.h + create mode 100644 src/server/game/AI/NpcBots/bot_GridNotifiers.h + create mode 100644 src/server/game/AI/NpcBots/bot_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_ai.h + create mode 100644 src/server/game/AI/NpcBots/bot_bm_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_death_knight_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_druid_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_hunter_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_mage_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_paladin_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_priest_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_rogue_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_shaman_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_warlock_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/bot_warrior_ai.cpp + create mode 100644 src/server/game/AI/NpcBots/botcommands.cpp + create mode 100644 src/server/game/AI/NpcBots/botmgr.cpp + create mode 100644 src/server/game/AI/NpcBots/botmgr.h + +diff --git a/sql/TrinityCore-Patches/Bots/auth_bots.sql b/sql/TrinityCore-Patches/Bots/auth_bots.sql +new file mode 100644 +index 0000000..fa76a45 +--- /dev/null ++++ b/sql/TrinityCore-Patches/Bots/auth_bots.sql +@@ -0,0 +1,27 @@ ++DELETE FROM `rbac_permissions` WHERE `id` IN (1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810); ++INSERT INTO `rbac_permissions` (`id`, `name`) VALUES ++(1800, 'Command: npcbot'), ++(1801, 'Command: npcbot set faction'), ++(1802, 'Command: npcbot set owner'), ++(1803, 'Command: npcbot set'), ++(1804, 'Command: npcbot add'), ++(1805, 'Command: npcbot remove'), ++(1806, 'Command: npcbot spawn'), ++(1807, 'Command: npcbot delete'), ++(1808, 'Command: npcbot lookup'), ++(1809, 'Command: npcbot revive'), ++(1810, 'Command: npcbot cast'); ++ ++DELETE FROM `rbac_linked_permissions` WHERE `linkedid` IN (1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810); ++INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES ++(197, 1800), ++(197, 1801), ++(197, 1802), ++(197, 1803), ++(197, 1804), ++(197, 1805), ++(197, 1806), ++(197, 1807), ++(197, 1808), ++(197, 1809), ++(197, 1810); +diff --git a/sql/TrinityCore-Patches/Bots/character_bots.sql b/sql/TrinityCore-Patches/Bots/character_bots.sql +new file mode 100644 +index 0000000..026e420 +--- /dev/null ++++ b/sql/TrinityCore-Patches/Bots/character_bots.sql +@@ -0,0 +1,26 @@ ++DROP TABLE IF EXISTS `characters_npcbot`; ++CREATE TABLE `characters_npcbot` ( ++ `entry` int(10) unsigned NOT NULL COMMENT 'creature_template.entry', ++ `owner` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'characters.guid (lowguid)', ++ `roles` tinyint(3) unsigned NOT NULL COMMENT 'bitmask: tank(1),dps(2),heal(4),ranged(8)', ++ `faction` int(10) unsigned NOT NULL DEFAULT '14', ++ `equipMhEx` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipOhEx` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipRhEx` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipHead` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipShoulders` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipChest` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipWaist` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipLegs` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipFeet` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipWrist` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipHands` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipBack` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipBody` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipFinger1` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipFinger2` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipTrinket1` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipTrinket2` int(10) unsigned NOT NULL DEFAULT '0', ++ `equipNeck` int(10) unsigned NOT NULL DEFAULT '0', ++ PRIMARY KEY (`entry`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8; +diff --git a/sql/TrinityCore-Patches/Bots/world_bots.sql b/sql/TrinityCore-Patches/Bots/world_bots.sql +new file mode 100644 +index 0000000..6a85210 +--- /dev/null ++++ b/sql/TrinityCore-Patches/Bots/world_bots.sql +@@ -0,0 +1,170 @@ ++-- GENERAL -- ++ ++SET @BOT_START = 70001; ++SET @BOT_END = 71000; ++ ++delete from `creature_template` where entry between @BOT_START and @BOT_END; ++INSERT INTO `creature_template` (`entry`, `difficulty_entry_1`, `difficulty_entry_2`, `difficulty_entry_3`, `KillCredit1`, `KillCredit2`, `modelid1`, `modelid2`, `modelid3`, `modelid4`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `speed_walk`, `speed_run`, `scale`, `rank`, `dmgschool`, `BaseAttackTime`, `RangeAttackTime`, `BaseVariance`, `RangeVariance`, `unit_class`, `unit_flags`, `unit_flags2`, `dynamicflags`, `family`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `resistance1`, `resistance2`, `resistance3`, `resistance4`, `resistance5`, `resistance6`, `spell1`, `spell2`, `spell3`, `spell4`, `spell5`, `spell6`, `spell7`, `spell8`, `PetSpellDataId`, `VehicleId`, `mingold`, `maxgold`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `HealthModifier`, `ManaModifier`, `ArmorModifier`, `DamageModifier`, `ExperienceModifier`, `RacialLeader`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`, `VerifiedBuild`) VALUES (70001,0,0,0,0,0,5001,0,5001,0,'Khelden','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70002,0,0,0,0,0,1294,0,1294,0,'Zaldimar','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70003,0,0,0,0,0,1484,0,1484,0,'Maginor','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70004,0,0,0,0,0,3344,0,3344,0,'Anetta','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70005,0,0,0,0,0,1495,0,1495,0,'Laurena','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70006,0,0,0,0,0,1295,0,1295,0,'Josetta','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70007,0,0,0,0,0,3345,0,3345,0,'Drusilla','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70008,0,0,0,0,0,1930,0,1930,0,'Alamar','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70009,0,0,0,0,0,1469,0,1469,0,'Demisette','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70010,0,0,0,0,0,12749,0,12749,0,'Nalesette','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70011,0,0,0,0,0,3401,0,3401,0,'Branstock','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70012,0,0,0,0,0,3395,0,3395,0,'Thorgas','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70013,0,0,0,0,0,3343,0,3343,0,'Llane','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70014,0,0,0,0,0,3399,0,3399,0,'Thran','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70015,0,0,0,0,0,1300,0,1300,0,'Lyria','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70016,0,0,0,0,0,3351,0,3351,0,'Jorik','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70017,0,0,0,0,0,3407,0,3407,0,'Solm','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70018,0,0,0,0,0,1297,0,1297,0,'Keryn','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70019,0,0,0,0,0,1507,0,1507,0,'Osborne','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70020,0,0,0,0,0,3346,0,3346,0,'Sammuel','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70021,0,0,0,0,0,3393,0,3393,0,'Bob','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70022,0,0,0,0,0,1299,0,1299,0,'Wilhelm','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70023,0,0,0,0,0,1499,0,1499,0,'Brisombre','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70024,0,0,0,0,0,10216,0,10216,0,'Marry','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70025,0,0,0,0,0,4552,0,4552,0,'Haromm','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70026,0,0,0,0,0,4567,0,4567,0,'Kartosh','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70027,0,0,0,0,0,3429,0,3429,0,'MaxanAnvol','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70028,0,0,0,0,0,10215,0,10215,0,'Magis','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70029,0,0,0,0,0,3431,0,3431,0,'GranVivehache','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70030,0,0,0,0,0,1622,0,1622,0,'Azar','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70031,0,0,0,0,0,3436,0,3436,0,'Hogral','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70032,0,0,0,0,0,3053,0,3053,0,'Kelstrum','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70033,0,0,0,0,0,1578,0,1578,0,'Dannal','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70034,0,0,0,0,0,1579,0,1579,0,'SombreDuesten','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70035,0,0,0,0,0,1592,0,1592,0,'Isabella','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70036,0,0,0,0,0,1581,0,1581,0,'Maximillion','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70037,0,0,0,0,0,1604,0,1604,0,'Rupert','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70038,0,0,0,0,0,1600,0,1600,0,'Cain','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70039,0,0,0,0,0,1602,0,1602,0,'SombreBeryl','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70041,0,0,0,0,0,10548,0,10548,0,'Milituus','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70042,0,0,0,0,0,2810,0,2810,0,'Lexington','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70043,0,0,0,0,0,2123,0,2123,0,'Siln','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70044,0,0,0,0,0,19598,0,19598,0,'Umbrua','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70045,0,0,0,0,0,2102,0,2102,0,'Tigor','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70046,0,0,0,0,0,2082,0,2082,0,'Beram','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70047,0,0,0,0,0,2106,0,2106,0,'Turak','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70048,0,0,0,0,0,2121,0,2121,0,'Sheal','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70049,0,0,0,0,0,2115,0,2115,0,'Kym','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70050,0,0,0,0,0,2112,0,2112,0,'Kary','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70051,0,0,0,0,0,2087,0,2087,0,'Holt','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70052,0,0,0,0,0,2105,0,2105,0,'Urek','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70053,0,0,0,0,0,2103,0,2103,0,'Torm','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70054,0,0,0,0,0,2096,0,2096,0,'Sark','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70055,0,0,0,0,0,17211,0,17211,0,'Kerra','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70056,0,0,0,0,0,2139,0,2139,0,'Miles Welsh','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70057,0,0,0,0,0,2138,0,2138,0,'Malakai','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70058,0,0,0,0,0,2137,0,2137,0,'Cobb','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70059,0,0,0,0,0,2134,0,2134,0,'Shymm','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,1,0,0,0,0,0,0,0,0,0,0,143,145,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70060,0,0,0,0,0,6058,0,6058,0,'Ursyn','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70061,0,0,0,0,0,2135,0,2135,0,'Thurston','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70062,0,0,0,0,0,3793,0,3793,0,'Harutt','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70063,0,0,0,0,0,3819,0,3819,0,'Gart','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70064,0,0,0,0,0,3810,0,3810,0,'Lanka','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70065,0,0,0,0,0,10180,0,10180,0,'Meela','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70066,0,0,0,0,0,3794,0,3794,0,'Krang','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70067,0,0,0,0,0,10734,0,10734,0,'Gennia','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70068,0,0,0,0,0,3811,0,3811,0,'Yaw','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70069,0,0,0,0,0,3816,0,3816,0,'Narm','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70070,0,0,0,0,0,1880,0,1880,0,'Frang','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70071,0,0,0,0,0,1882,0,1882,0,'Jenshan','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70072,0,0,0,0,0,1884,0,1884,0,'Nartok','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70073,0,0,0,0,0,1878,0,1878,0,'Shikrik','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70074,0,0,0,0,0,3743,0,3743,0,'Tarshaw','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70075,0,0,0,0,0,3744,0,3744,0,'Thotar','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70076,0,0,0,0,0,3745,0,3745,0,'Dhugru','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70077,0,0,0,0,0,3746,0,3746,0,'Swart','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70078,0,0,0,0,0,1324,0,1324,0,'Groldar','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70079,0,0,0,0,0,1325,0,1325,0,'Mirket','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70080,0,0,0,0,0,1326,0,1326,0,'Zevrost','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70081,0,0,0,0,0,1360,0,1360,0,'Kardris','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70082,0,0,0,0,0,1373,0,1373,0,'Ormak','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70083,0,0,0,0,0,1374,0,1374,0,'Grezz','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70084,0,0,0,0,0,1375,0,1375,0,'Sorek','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70085,0,0,0,0,0,4231,0,4231,0,'Siantsu','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70086,0,0,0,0,0,4239,0,4239,0,'Xorjuul','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70087,0,0,0,0,0,4241,0,4241,0,'Siandur','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70088,0,0,0,0,0,4242,0,4242,0,'Zelmak','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70089,0,0,0,0,0,7915,0,7915,0,'ClaudeErksine','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70090,0,0,0,0,0,1721,0,1721,0,'Alyissia','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70091,0,0,0,0,0,1725,0,1725,0,'FrahunMurmombre','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70092,0,0,0,0,0,1733,0,1733,0,'Shanda','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70093,0,0,0,0,0,1732,0,1732,0,'Mardant','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70094,0,0,0,0,0,1707,0,1707,0,'Kyra','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70095,0,0,0,0,0,1704,0,1704,0,'Jannok','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70096,0,0,0,0,0,1708,0,1708,0,'Laurna','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70097,0,0,0,0,0,1706,0,1706,0,'Kal','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70098,0,0,0,0,0,4296,0,4296,0,'Harruk','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70099,0,0,0,0,0,4299,0,4299,0,'Reban','Hunter bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70100,0,0,0,0,0,4304,0,4304,0,'Bolyun','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70101,0,0,0,0,0,1897,0,1897,0,'Taijin','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70102,0,0,0,0,0,4068,0,4068,0,'Kenjai','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70103,0,0,0,0,0,2066,0,2066,0,'Danlaar','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70104,0,0,0,0,0,2196,0,2196,0,'Ariasta','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70105,0,0,0,0,0,2198,0,2198,0,'Sildanair','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70106,0,0,0,0,0,2200,0,2200,0,'Astarii','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70107,0,0,0,0,0,2201,0,2201,0,'Jandria','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70108,0,0,0,0,0,2202,0,2202,0,'Lariia','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70109,0,0,0,0,0,2231,0,2231,0,'Syurna','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70110,0,0,0,0,0,7669,0,7669,0,'Elissa','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70111,0,0,0,0,0,2252,0,2252,0,'Erion','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70112,0,0,0,0,0,2243,0,2243,0,'Anishar','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70113,0,0,0,0,0,2250,0,2250,0,'Denatharion','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70114,0,0,0,0,0,2255,0,2255,0,'Fylerian','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70115,0,0,0,0,0,2416,0,2416,0,'Caelyb','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70116,0,0,0,0,0,2675,0,2675,0,'Kaal','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70117,0,0,0,0,0,16800,0,16800,0,'Lana','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70118,0,0,0,0,0,2646,0,2646,0,'Richard','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70119,0,0,0,0,0,10214,0,10214,0,'Kaelystia','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,6,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70120,0,0,0,0,0,2644,0,2644,0,'Pierce','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70121,0,0,0,0,0,2657,0,2657,0,'Anastasia','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70122,0,0,0,0,0,2620,0,2620,0,'Chris','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70123,0,0,0,0,0,2658,0,2658,0,'Angela','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70124,0,0,0,0,0,2614,0,2614,0,'Baltus','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70125,0,0,0,0,0,3054,0,3054,0,'Kelv','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70126,0,0,0,0,0,3055,0,3055,0,'Bilban','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70127,0,0,0,0,0,3056,0,3056,0,'Daera','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70128,0,0,0,0,0,3072,0,3072,0,'Olmin','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70129,0,0,0,0,0,3073,0,3073,0,'Regnus','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70130,0,0,0,0,0,3086,0,3086,0,'Theodrus','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70131,0,0,0,0,0,3066,0,3066,0,'Braenna','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70132,0,0,0,0,0,3085,0,3085,0,'Toldren','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70134,0,0,0,0,0,3108,0,3108,0,'Bink','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70135,0,0,0,0,0,10214,0,10214,0,'Juli','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70136,0,0,0,0,0,3109,0,3109,0,'Nittegousse','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70137,0,0,0,0,0,3089,0,3089,0,'Valgar','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70138,0,0,0,0,0,3088,0,3088,0,'Beldruk','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70139,0,0,0,0,0,3087,0,3087,0,'Brandur','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70140,0,0,0,0,0,3101,0,3101,0,'Hulfdan','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70141,0,0,0,0,0,3100,0,3100,0,'Ormyr','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70142,0,0,0,0,0,3113,0,3113,0,'Phenwick','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70143,0,0,0,0,0,3115,0,3115,0,'Coeurdechardon','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70144,0,0,0,0,0,3116,0,3116,0,'Eglantin','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70145,0,0,0,0,0,3122,0,3122,0,'Alexander','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70146,0,0,0,0,0,3280,0,3280,0,'Wu','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70147,0,0,0,0,0,3287,0,3287,0,'Ilsa','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70148,0,0,0,0,0,3283,0,3283,0,'Joshua','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70149,0,0,0,0,0,3284,0,3284,0,'Arthur','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70150,0,0,0,0,0,3289,0,3289,0,'Katherine','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70151,0,0,0,0,0,3291,0,3291,0,'Deline','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70152,0,0,0,0,0,3286,0,3286,0,'Sandahl','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70153,0,0,0,0,0,3292,0,3292,0,'Jennea','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70154,0,0,0,0,0,19803,0,19803,0,'Elsharin','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70155,0,0,0,0,0,3299,0,3299,0,'Kaerbrus','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70156,0,0,0,0,0,3300,0,3300,0,'Sheldras','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70157,0,0,0,0,0,3301,0,3301,0,'Theridran','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70158,0,0,0,0,0,3312,0,3312,0,'Einris','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70159,0,0,0,0,0,3309,0,3309,0,'Ulfir','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70160,0,0,0,0,0,3310,0,3310,0,'Thorfin','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70161,0,0,0,0,0,10171,0,10171,0,'UnThuwa','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70162,0,0,0,0,0,4524,0,4524,0,'Pephredo','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70163,0,0,0,0,0,4522,0,4522,0,'Enyo','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70164,0,0,0,0,0,4526,0,4526,0,'Mai','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70165,0,0,0,0,0,4523,0,4523,0,'Deino','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70166,0,0,0,0,0,4665,0,4665,0,'Birgitte','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70167,0,0,0,0,0,12849,0,12849,0,'Thuul','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70168,0,0,0,0,0,4690,0,4690,0,'Zayus','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70169,0,0,0,0,0,10473,0,10473,0,'Xyera','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70170,0,0,0,0,0,4711,0,4711,0,'Urkyo','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70171,0,0,0,0,0,6060,0,6060,0,'Uthelnay','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70172,0,0,0,0,0,6072,0,6072,0,'Dink','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70173,0,0,0,0,0,6071,0,6071,0,'Darnath','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70174,0,0,0,0,0,7356,0,7356,0,'Karman','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70175,0,0,0,0,0,11037,0,11037,0,'Evencane','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70176,0,0,0,0,0,7357,0,7357,0,'Jannos','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70177,0,0,0,0,0,7538,0,7538,0,'Alenndaar','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70178,0,0,0,0,0,10738,0,10738,0,'Golhine','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70179,0,0,0,0,0,9337,0,9337,0,'Hesuwa','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70180,0,0,0,0,0,9336,0,9336,0,'Xao\'tsu','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70181,0,0,0,0,0,9338,0,9338,0,'Belia','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,3,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70182,0,0,0,0,0,10245,0,10245,0,'Dargh','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70183,0,0,0,0,0,11044,0,11044,0,'Meideros','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70184,0,0,0,0,0,11048,0,11048,0,'Presse','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70185,0,0,0,0,0,11053,0,11053,0,'Rohan','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70186,0,0,0,0,0,12053,0,12053,0,'Loganaar','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70187,0,0,0,0,0,13171,0,13171,0,'Romano','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70188,0,0,0,0,0,13341,0,13341,0,'Sagorne','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70189,0,0,0,0,0,15522,0,15522,0,'Julia','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70190,0,0,0,0,0,16811,0,16811,0,'Ithelis','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70191,0,0,0,0,0,15524,0,15524,0,'Invocateur','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70192,0,0,0,0,0,15518,0,15518,0,'Matrone','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70193,0,0,0,0,0,2659,0,2659,0,'Eclaireur','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70194,0,0,0,0,0,15520,0,15520,0,'Sallina','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70195,0,0,0,0,0,16685,0,16685,0,'Noellene','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70196,0,0,0,0,0,16707,0,16707,0,'Ponaris','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70197,0,0,0,0,0,16222,0,16222,0,'Keilnei','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70198,0,0,0,0,0,16223,0,16223,0,'Valaatu','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70199,0,0,0,0,0,16224,0,16224,0,'Aurelon','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70200,0,0,0,0,0,16225,0,16225,0,'Zalduun','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70201,0,0,0,0,0,16226,0,16226,0,'Kore','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70202,0,0,0,0,0,16787,0,16787,0,'Alamma','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70203,0,0,0,0,0,16800,0,16800,0,'Talionia','Warlock Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,8,0,16384,0,0,0,0,9,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warlock_bot',-1),(70204,0,0,0,0,0,16831,0,16831,0,'Zanien','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3500,2000,1,1,2,0,16384,0,0,0,0,9,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70205,0,0,0,0,0,16781,0,16781,0,'Zaedana','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70206,0,0,0,0,0,16824,0,16824,0,'Quithas','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70207,0,0,0,0,0,16739,0,16739,0,'Harene','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70208,0,0,0,0,0,16778,0,16778,0,'Tana','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70209,0,0,0,0,0,16816,0,16816,0,'Oninath','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70210,0,0,0,0,0,16829,0,16829,0,'Bachi','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70211,0,0,0,0,0,16767,0,16767,0,'Zelanis','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70212,0,0,0,0,0,16798,0,16798,0,'Elara','Rogue Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,1600,2000,1,1,4,0,16384,0,0,0,0,4,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'rogue_bot',-1),(70213,0,0,0,0,0,16858,0,16858,0,'Shalannius','Druid Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2200,2000,1,1,2,0,16384,0,0,0,0,11,6,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'druid_bot',-1),(70214,0,0,0,0,0,17434,0,17434,0,'Deremiis','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70215,0,0,0,0,0,17247,0,17247,0,'Caedmos','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70216,0,0,0,0,0,17225,0,17225,0,'Baatun','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70217,0,0,0,0,0,17212,0,17212,0,'Ahonan','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70218,0,0,0,0,0,17598,0,17598,0,'Firmanvaar','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70219,0,0,0,0,0,16860,0,16860,0,'Actron','Hunter Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70220,0,0,0,0,0,17213,0,17213,0,'Behomat','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70221,0,0,0,0,0,17600,0,17600,0,'Nobundo','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70222,0,0,0,0,0,17599,0,17599,0,'Tuluun','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70223,0,0,0,0,0,16914,0,16914,0,'Sulaa','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70224,0,0,0,0,0,17215,0,17215,0,'Ruada','Warrior Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3400,2000,1,1,1,0,16384,0,0,0,0,1,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'warrior_bot',-1),(70225,0,0,0,0,0,17233,0,17233,0,'Semid','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70226,0,0,0,0,0,17232,0,17232,0,'Guvan','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70227,0,0,0,0,0,17234,0,17234,0,'Tullas','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70228,0,0,0,0,0,17488,0,17488,0,'Killac','Hunter bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2800,2000,1,1,2,0,16384,0,0,0,0,3,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'hunter_bot',-1),(70229,0,0,0,0,0,17226,0,17226,0,'Jol','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70230,0,0,0,0,0,17248,0,17248,0,'Fallat','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70231,0,0,0,0,0,17243,0,17243,0,'Harnan','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70232,0,0,0,0,0,17241,0,17241,0,'Bati','Mage Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3800,2000,1,1,8,0,16384,0,0,0,0,8,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'mage_bot',-1),(70233,0,0,0,0,0,17792,0,17792,0,'Hobahken','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70234,0,0,0,0,0,6820,0,6820,0,'Gurrag','Shaman Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2600,2000,1,1,2,0,16384,0,0,0,0,7,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'shaman_bot',-1),(70235,0,0,0,0,0,19596,0,19596,0,'Auberose','Paladin Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,2300,2000,1,1,2,0,16384,0,0,0,0,2,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'paladin_bot',-1),(70236,0,0,0,0,0,10335,10335,10335,10335,'Afina','Priest Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3600,2000,1,1,8,0,16384,0,0,0,0,5,1,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'priest_bot',-1),(70237,0,0,0,0,0,26939,26939,26939,26939,'Imhadria','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70238,0,0,0,0,0,28039,28039,28039,28039,'Mynx','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,0.8,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70239,0,0,0,0,0,26688,26688,26688,26688,'Lankral','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,1,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70240,0,0,0,0,0,26195,26195,26195,26195,'Silver','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70241,0,0,0,0,0,27402,27402,27402,27402,'Vereth','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,0.8,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70242,0,0,0,0,0,27189,27189,27189,27189,'Arly','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70243,0,0,0,0,0,26217,26217,26217,26217,'Setaal','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,11,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70244,0,0,0,0,0,28842,28842,28842,28842,'Illyrie','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70245,0,0,0,0,0,28840,28840,28840,28840,'Zor\'be','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70246,0,0,0,0,0,25512,25512,25512,25512,'Datura','Death Knight Bot','',0,1,80,2,14,1,1.2,1.3,1,0,0,3300,2000,1,1,1,0,16384,0,0,0,0,6,10,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157552,'death_knight_bot',-1),(70247,0,0,0,0,0,1132,0,1132,0,'Voidwalker','Warlock\'s Pet Bot',NULL,0,1,80,2,14,0,1.2,1.3,1,0,0,2000,2000,1,1,2,0,0,0,16,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,1048688,'voidwalker_bot',-1),(70248,0,0,0,0,0,1105,0,0,0,'Hunter\'s Pet',NULL,NULL,0,1,80,0,14,0,1.1,1.14286,1,0,0,2000,0,1,1,1,0,0,0,7,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,5708,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,149,1,0,0,'',-1),(70301,0,0,0,0,0,17659,17659,17659,17659,'Gorkramato','Ex. Blademaster','',0,81,81,2,14,1,1.2,1.3,1.05,4,0,2200,2000,1,1,1,0,16384,0,0,0,0,12,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,1,1,1,0,0,0,1,68157560,'blademaster_bot',-1),(71000,0,0,0,0,0,16853,16853,16853,16853,'Airen','Priestess of Suffering',NULL,0,95,95,1,14,1,1.2,1.3,1.173,4,0,1500,0,1,1,1,898,49152,2,0,0,0,0,0,3,67110912,0,0,0,450,450,450,450,450,450,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1000,1,5,5,1,1,0,0,617299839,1048946,'npc_Airen_qI',-213); ++ ++ ++-- EQUIPS -- ++ ++delete from `creature_equip_template` where CreatureID between @BOT_START and @BOT_END; ++ ++insert into `creature_equip_template` (`CreatureID`, `ID`, `itemID1`, `itemID2`, `itemID3`, `VerifiedBuild`) values ++('70001','1','18842','0','0','0'), ('70002','1','18842','0','0','0'), ('70003','1','18842','0','0','0'), ('70004','1','31289','0','0','0'), ('70005','1','31289','0','0','0'), ('70006','1','31289','0','0','0'), ++('70007','1','31186','0','0','0'), ('70008','1','31186','0','0','0'), ('70009','1','31186','0','0','0'), ('70010','1','2291','0','2825','0'), ('70011','1','31289','0','0','0'), ('70012','1','2291','0','2825','0'), ++('70013','1','18002','0','0','0'), ('70014','1','27903','0','0','0'), ('70015','1','7723','0','0','0'), ('70016','1','13984','6448','0','0'), ('70017','1','13984','6448','0','0'), ('70018','1','6633','820','0','0'), ++('70019','1','13984','6448','0','0'), ('70020','1','12584','18825','0','0'), ('70021','1','18876','0','0','0'), ('70022','1','12584','18825','0','0'), ('70023','1','18876','0','0','0'), ('70024','1','18842','0','0','0'), ++('70025','1','18203','18202','0','0'), ('70026','1','31186','0','0','0'), ('70027','1','31289','0','0','0'), ('70028','1','18842','0','0','0'), ('70029','1','28367','0','0','0'), ('70030','1','12584','18825','0','0'), ++('70031','1','13984','6448','0','0'), ('70032','1','27903','0','0','0'), ('70033','1','18002','0','0','0'), ('70034','1','31289','0','0','0'), ('70035','1','18842','0','0','0'), ('70036','1','31186','0','0','0'), ++('70037','1','31186','0','0','0'), ('70038','1','18842','0','0','0'), ('70039','1','31289','0','0','0'), ('70041','1','18842','0','0','0'), ('70042','1','18842','0','0','0'), ('70043','1','18203','18202','0','0'), ++('70044','1','18203','18202','0','0'), ('70045','1','18203','18202','0','0'), ('70046','1','18203','18202','0','0'), ('70047','1','25622','0','0','0'), ('70048','1','25622','0','0','0'), ('70049','1','25622','0','0','0'), ++('70050','1','2291','0','2825','0'), ('70051','1','2291','0','2825','0'), ('70052','1','2291','0','2825','0'), ('70053','1','18002','0','0','0'), ('70054','1','27903','0','0','0'), ('70055','1','28367','0','0','0'), ++('70056','1','31289','0','0','0'), ('70057','1','31289','0','0','0'), ('70058','1','31289','0','0','0'), ('70059','1','18842','0','0','0'), ('70060','1','18842','0','0','0'), ('70061','1','18842','0','0','0'), ++('70062','1','28367','0','0','0'), ('70063','1','25622','0','0','0'), ('70064','1','2291','0','2825','0'), ('70065','1','18203','18202','0','0'), ('70066','1','18002','0','0','0'), ('70067','1','25622','0','0','0'), ++('70068','1','2291','0','2825','0'), ('70069','1','18203','18202','0','0'), ('70070','1','27903','0','0','0'), ('70071','1','2291','0','2825','0'), ('70072','1','31186','0','0','0'), ('70073','1','18203','18202','0','0'), ++('70074','1','18002','0','0','0'), ('70075','1','2291','0','2825','0'), ('70076','1','31186','0','0','0'), ('70077','1','18203','18202','0','0'), ('70078','1','31186','0','0','0'), ('70079','1','31186','0','0','0'), ++('70080','1','31186','0','0','0'), ('70081','1','18203','18202','0','0'), ('70082','1','2291','0','2825','0'), ('70083','1','7723','0','0','0'), ('70084','1','18002','0','0','0'), ('70085','1','18203','18202','0','0'), ++('70086','1','2291','0','2825','0'), ('70087','1','2291','0','2825','0'), ('70088','1','27903','0','0','0'), ('70089','1','2291','0','2825','0'), ('70090','1','28367','0','0','0'), ('70091','1','6633','820','0','0'), ++('70092','1','31289','0','0','0'), ('70093','1','25622','0','0','0'), ('70094','1','18002','0','0','0'), ('70095','1','13984','6448','0','0'), ('70096','1','31289','0','0','0'), ('70097','1','25622','0','0','0'), ++('70098','1','2291','0','2825','0'), ('70099','1','2291','0','2825','0'), ++('70100','1','2291','0','2825','0'), ('70101','1','31289','0','0','0'), ('70102','1','31289','0','0','0'), ('70103','1','2291','0','2825','0'), ('70104','1','7723','0','0','0'), ('70105','1','18002','0','0','0'), ++('70106','1','31289','0','0','0'), ('70107','1','31289','0','0','0'), ('70108','1','31289','0','0','0'), ('70109','1','13984','6448','0','0'), ('70110','1','18842','0','0','0'), ('70111','1','6633','820','0','0'), ++('70112','1','13984','6448','0','0'), ('70113','1','25622','0','0','0'), ('70114','1','25622','0','0','0'), ('70115','1','2291','0','2825','0'), ('70116','1','31186','0','0','0'), ('70117','1','31186','0','0','0'), ++('70118','1','31186','0','0','0'), ('70119','1','18842','0','0','0'), ('70120','1','18842','0','0','0'), ('70121','1','18842','0','0','0'), ('70122','1','27903','0','0','0'), ('70123','1','18002','0','0','0'), ++('70124','1','7723','0','0','0'), ('70125','1','18002','0','0','0'), ('70126','1','28367','0','0','0'), ('70127','1','2291','0','2825','0'), ('70128','1','2291','0','2825','0'), ('70129','1','2291','0','2825','0'), ++('70130','1','31289','0','0','0'), ('70131','1','31289','0','0','0'), ('70132','1','31289','0','0','0'), ('70134','1','18842','0','0','0'), ('70135','1','18842','0','0','0'), ('70136','1','18842','0','0','0'), ++('70137','1','18876','0','0','0'), ('70138','1','12584','18825','0','0'), ('70139','1','18876','0','0','0'), ('70140','1','6633','820','0','0'), ('70141','1','13984','6448','0','0'), ('70142','1','6633','820','0','0'), ++('70143','1','31186','0','0','0'), ('70144','1','31186','0','0','0'), ('70145','1','31186','0','0','0'), ('70146','1','27903','0','0','0'), ('70147','1','18002','0','0','0'), ('70148','1','31289','0','0','0'), ++('70149','1','12584','18825','0','0'), ('70150','1','18876','0','0','0'), ++('70151','1','31186','0','0','0'), ('70152','1','31186','0','0','0'), ('70153','1','18842','0','0','0'), ('70154','1','18842','0','0','0'), ('70155','1','2291','0','2825','0'), ('70156','1','25622','0','0','0'), ++('70157','1','25622','0','0','0'), ('70158','1','2291','0','2825','0'), ('70159','1','2291','0','2825','0'), ('70160','1','2291','0','2825','0'), ('70161','1','18842','0','0','0'), ('70162','1','18842','0','0','0'), ++('70163','1','18842','0','0','0'), ('70164','1','18842','0','0','0'), ('70165','1','18842','0','0','0'), ('70166','1','18842','0','0','0'), ('70167','1','18842','0','0','0'), ('70168','1','31289','0','0','0'), ++('70169','1','31289','0','0','0'), ('70170','1','31289','0','0','0'), ('70171','1','18842','0','0','0'), ('70172','1','18842','0','0','0'), ('70173','1','28367','0','0','0'), ('70174','1','12584','18825','0','0'), ++('70175','1','7723','0','0','0'), ('70176','1','25622','0','0','0'), ('70177','1','2291','0','2825','0'), ('70178','1','25622','0','0','0'), ('70179','1','2291','0','2825','0'), ('70180','1','2291','0','2825','0'), ++('70181','1','2291','0','2825','0'), ('70182','1','2291','0','2825','0'), ('70183','1','31289','0','0','0'), ('70184','1','31289','0','0','0'), ('70185','1','31289','0','0','0'), ('70186','1','25622','0','0','0'), ++('70187','1','13984','6448','0','0'), ('70188','1','18203','18202','0','0'), ('70189','1','18842','0','0','0'), ('70190','1','12584','18826','0','0'), ('70191','1','31186','0','0','0'), ('70192','1','31289','0','0','0'), ++('70193','1','13984','6448','0','0'), ('70194','1','2291','0','2825','0'), ('70195','1','12584','18826','0','0'), ('70196','1','31289','0','0','0'), ('70197','1','2291','0','2825','0'), ('70198','1','18842','0','0','0'), ++('70199','1','18876','0','0','0'), ('70200','1','31289','0','0','0'), ++('70201','1','27903','0','0','0'), ('70202','1','31186','0','0','0'), ('70203','1','31186','0','0','0'), ('70204','1','31186','0','0','0'), ('70205','1','18842','0','0','0'), ('70206','1','18842','0','0','0'), ++('70207','1','25622','0','0','0'), ('70208','1','2291','0','2825','0'), ('70209','1','2291','0','2825','0'), ('70210','1','12584','18826','0','0'), ('70211','1','6633','820','0','0'), ('70212','1','13984','6448','0','0'), ++('70213','1','25622','0','0','0'), ('70214','1','2291','0','2825','0'), ('70215','1','31289','0','0','0'), ('70216','1','18876','0','0','0'), ('70217','1','28367','0','0','0'), ('70218','1','18203','18202','0','0'), ++('70219','1','2291','0','2825','0'), ('70220','1','18002','0','0','0'), ('70221','1','18203','18202','0','0'), ('70222','1','18203','18202','0','0'), ('70223','1','18203','18202','0','0'), ('70224','1','27903','0','0','0'), ++('70225','1','18842','0','0','0'), ('70226','1','31289','0','0','0'), ('70227','1','12584','18825','0','0'), ('70228','1','2291','0','2825','0'), ('70229','1','18876','0','0','0'), ('70230','1','31289','0','0','0'), ++('70231','1','18842','0','0','0'), ('70232','1','18842','0','0','0'), ('70233','1','18203','18202','0','0'), ('70234','1','18203','18202','0','0'), ('70235','1','29175','18826','0','0'), ('70236','1','31289','0','0','0'), ++('70237','1','13505','0','0','0'), ('70238','1','12775','0','0','0'), ('70239','1','24044','0','0','0'), ('70240','1','43601','0','0','0'), ('70241','1','23499','0','0','0'), ('70242','1','38632','0','0','0'), ++('70243','1','34891','0','0','0'), ('70244','1','38632','0','0','0'), ('70245','1','50798','0','0','0'), ('70246','1','12592','0','0','0'), ('70301','1','24044','0','0','0'), ++ ++('71000','1','0','30902','0','0'); ++ ++-- -- -- Update 18.09.13 - Equips for shamans ++-- Orcs and Draenei. Mainhand: Cudgel of Furious Justice, Offhand: Azure-Shield of Coldarra ++UPDATE `creature_equip_template` SET `itemID1` = '50050', `itemID2` = '29266', `itemID3` = '0' WHERE `CreatureID` IN (SELECT entry FROM `creature_template` WHERE (`entry` BETWEEN @BOT_START AND @BOT_END) AND `trainer_class` = '7' AND (`trainer_race` = '2' OR `trainer_race` = '11')); ++-- Taurens and some Draenei. De-Raged Waraxe (Two-Hand) ++UPDATE `creature_equip_template` SET `itemID1` = '41816', `itemID2` = '0', `itemID3` = '0' WHERE `CreatureID` IN (SELECT entry FROM `creature_template` WHERE (`entry` BETWEEN @BOT_START AND @BOT_END) AND `trainer_class` = '7' AND (`trainer_race` = '6' OR `entry` IN (70218,70222,70223,70233))); ++ ++ ++-- GOSSIPS -- ++delete from `npc_text` where ID between @BOT_START and @BOT_END; ++insert into `npc_text` (`ID`, `text0_0`, `text0_1`, `lang0`, `Probability0`, `em0_0`, `em0_1`, `em0_2`, `em0_3`, `em0_4`, `em0_5`, `text1_0`, `text1_1`, `lang1`, `Probability1`, `em1_0`, `em1_1`, `em1_2`, `em1_3`, `em1_4`, `em1_5`, `text2_0`, `text2_1`, `lang2`, `Probability2`, `em2_0`, `em2_1`, `em2_2`, `em2_3`, `em2_4`, `em2_5`, `text3_0`, `text3_1`, `lang3`, `Probability3`, `em3_0`, `em3_1`, `em3_2`, `em3_3`, `em3_4`, `em3_5`, ++`text4_0`, `text4_1`, `lang4`, `Probability4`, `em4_0`, `em4_1`, `em4_2`, `em4_3`, `em4_4`, `em4_5`, `text5_0`, `text5_1`, `lang5`, `Probability5`, `em5_0`, `em5_1`, `em5_2`, `em5_3`, `em5_4`, `em5_5`, `text6_0`, `text6_1`, `lang6`, `Probability6`, `em6_0`, `em6_1`, `em6_2`, `em6_3`, `em6_4`, `em6_5`, `text7_0`, `text7_1`, `lang7`, `Probability7`, `em7_0`, `em7_1`, `em7_2`, `em7_3`, `em7_4`, `em7_5`, `VerifiedBuild`) ++values ++('70001','I live only to serve the master.','','0','1','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','-213'), ++('70002','You need something?','','0','1','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','','','0','0','0','0','0','0','0','0','-213'), ++('70003','Mortals... usually I kill wretches like you at sight',NULL,'0','1','396','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0',NULL,NULL,'0','0','0','0','0','0','0','0','-213'); ++ ++ ++-- OUTFITS -- ++-- Npc Dress mod by Rochet2 ++CREATE TABLE IF NOT EXISTS `creature_template_outfits` ( ++ `entry` INT(10) UNSIGNED NOT NULL, ++ `race` tinyint(3) UNSIGNED NOT NULL DEFAULT '1', ++ `gender` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 for male, 1 for female', ++ `skin` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', ++ `face` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', ++ `hair` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', ++ `haircolor` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', ++ `facialhair` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', ++ `head` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `shoulders` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `body` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `chest` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `waist` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `legs` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `feet` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `wrists` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `hands` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `back` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ `tabard` INT(10) UNSIGNED NOT NULL DEFAULT '0', ++ PRIMARY KEY (`entry`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8; ++-- End Npc Dress mod ++ ++replace into `creature_template_outfits` (`entry`, `race`, `gender`, `skin`, `face`, `hair`, `haircolor`, `facialhair`, `head`, `shoulders`, `body`, `chest`, `waist`, `legs`, `feet`, `wrists`, `hands`, `back`, `tabard`) ++values ++('70301','2','0','0','14','9','7','5','0','0','0','0','59194','64674','0','36248','0','0','0'), -- Blademaster ++('71000','11','1','0','5','0','6','0','0','53903','21842','35049','35058','35051','35067','35044','0','0','0'); -- Airen ++ ++ ++-- Customize section ++-- You can create your own values to be in line with your own server if these are not acceptable. ++ ++SET @CLASS_WARRIOR = 1; ++SET @CLASS_PALADIN = 2; ++SET @CLASS_HUNTER = 3; ++SET @CLASS_ROGUE = 4; ++SET @CLASS_PRIEST = 5; ++SET @CLASS_DK = 6; ++SET @CLASS_SHAMAN = 7; ++SET @CLASS_MAGE = 8; ++SET @CLASS_WARLOCK = 9; ++SET @CLASS_DRUID = 11; ++SET @CLASS_BM = 12; ++ ++-- Add flags_extra ++-- -- -- Update 6.04.14 - extra flags for recognizing bots core-side - CREATURE_FLAG_EXTRA_NPCBOT ++ ++SET @EX_NO_PARRY_HASTEN = 8; -- 0x00000008 - CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN ++SET @EX_NO_BLOCK = 16; -- 0x00000010 - CREATURE_FLAG_EXTRA_NO_BLOCK ++SET @EX_NO_CRUSH = 32; -- 0x00000020 - CREATURE_FLAG_EXTRA_NO_CRUSH ++SET @EX_NO_XP = 64; -- 0x00000040 - CREATURE_FLAG_EXTRA_NO_XP_AT_KILL ++SET @EX_DIMINISH = 1048576; -- 0x00100000 - CREATURE_FLAG_EXTRA_ALL_DIMINISH ++SET @EX_NPCBOT = 67108864; -- 0x04000000 - CREATURE_FLAG_EXTRA_NPCBOT - custom flag ++SET @FLAGS_EX = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT; ++SET @FLAGS_EXN = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT | @EX_NO_PARRY_HASTEN; ++SET @FLAGS_EX_PET = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH; ++ ++-- Add extra 'unit_flags2' flags ++SET @U2_ENEMY_INTERRACT = 16384; -- 0x00004000 - UNIT_FLAG2_ALLOW_ENEMY_INTERACT ++SET @FLAGS_U2 = @U2_ENEMY_INTERRACT; ++ ++-- minions ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=3300, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_DK; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=2200, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_DRUID; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=2800, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_HUNTER; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=3800, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_MAGE; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=2300, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_PALADIN; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=3600, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_PRIEST; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=1600, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_ROGUE; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=2600, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_SHAMAN; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=3500, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_WARLOCK; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=3400, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_WARRIOR; ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=81, maxlevel:=81, baseattacktime:=2200, rangeattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EXN, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and trainer_class=@CLASS_BM; ++ ++-- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK -- DK ++-- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid -- Druid ++-- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter -- Hunter ++-- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage -- Mage ++-- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin -- Paladin ++-- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest -- Priest ++-- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue -- Rogue ++-- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman -- Shaman ++-- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock -- Warlock ++-- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior -- Warrior ++-- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster -- BMaster ++ ++-- pets ++UPDATE `creature_template` SET exp:=2, faction:=14, DamageModifier:=1.0, minlevel:=80, maxlevel:=80, baseattacktime:=2000, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX_PET, AIName:='', VerifiedBuild:=-1 where entry between @BOT_START and @BOT_END-1 and name='Voidwalker'; ++ ++-- end +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp +index 65dcad6..9129021 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp ++++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp +@@ -621,7 +621,25 @@ void CharacterDatabaseConnection::DoPrepareStatements() + // 04 + // 05 + // 06 +- // 07 ++ // New NPCBots ++ PrepareStatement(CHAR_SEL_NPCBOTS, "SELECT entry FROM characters_npcbot", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_SEL_NPCBOT_OWNER, "SELECT owner FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_UPD_NPCBOT_OWNER, "UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_UPD_NPCBOT_OWNER_ALL, "UPDATE characters_npcbot SET owner = ? WHERE owner = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_SEL_NPCBOT_ROLES, "SELECT roles FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_UPD_NPCBOT_ROLES, "UPDATE characters_npcbot SET roles = ? WHERE entry = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_SEL_NPCBOT_EQUIP, "SELECT equipMhEx, equipOhEx, equipRhEx, " ++ "equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet, equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE, "SELECT ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.guid, ii.itemEntry, ii.owner_guid " ++ "FROM item_instance ii JOIN characters_npcbot cn ON ii.guid IN " ++ "(cn.equipMhEx, cn.equipOhEx, cn.equipRhEx, cn.equipHead, cn.equipShoulders, cn.equipChest, cn.equipWaist, cn.equipLegs, cn.equipFeet, cn.equipWrist, cn.equipHands, cn.equipBack, cn.equipBody, cn.equipFinger1, cn.equipFinger2, cn.equipTrinket1, cn.equipTrinket2, cn.equipNeck) " ++ "WHERE cn.entry = ?", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_UPD_NPCBOT_EQUIP, "UPDATE characters_npcbot SET equipMhEx = ?, equipOhEx = ?, equipRhEx = ?, " ++ "equipHead = ?, equipShoulders = ?, equipChest = ?, equipWaist = ?, equipLegs = ?, equipFeet = ?, equipWrist = ?, equipHands = ?, equipBack = ?, equipBody = ?, equipFinger1 = ?, equipFinger2 = ?, equipTrinket1 = ?, equipTrinket2 = ?, equipNeck = ? WHERE entry = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_DEL_NPCBOT, "DELETE FROM characters_npcbot WHERE entry = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_INS_NPCBOT, "INSERT INTO characters_npcbot (entry, roles) VALUES (?, ?)", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_UPD_NPCBOT_FACTION, "UPDATE characters_npcbot SET faction = ? WHERE entry = ?", CONNECTION_SYNCH); ++ PrepareStatement(CHAR_SEL_NPCBOT_FACTION, "SELECT faction FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH); + // 08 + // 09 + // 10 +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h +index d801556..780cf79 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.h ++++ b/src/server/database/Database/Implementation/CharacterDatabase.h +@@ -539,7 +539,20 @@ enum CharacterDatabaseStatements + // 04 + // 05 + // 06 +- // 07 ++ // New NPCBots ++ CHAR_SEL_NPCBOTS, ++ CHAR_SEL_NPCBOT_OWNER, ++ CHAR_UPD_NPCBOT_OWNER, ++ CHAR_UPD_NPCBOT_OWNER_ALL, ++ CHAR_SEL_NPCBOT_ROLES, ++ CHAR_UPD_NPCBOT_ROLES, ++ CHAR_SEL_NPCBOT_EQUIP, ++ CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE, ++ CHAR_UPD_NPCBOT_EQUIP, ++ CHAR_DEL_NPCBOT, ++ CHAR_INS_NPCBOT, ++ CHAR_UPD_NPCBOT_FACTION, ++ CHAR_SEL_NPCBOT_FACTION, + // 08 + // 09 + // 10 +diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp +index 7a183d5..2e63681 100644 +--- a/src/server/database/Database/Implementation/WorldDatabase.cpp ++++ b/src/server/database/Database/Implementation/WorldDatabase.cpp +@@ -91,4 +91,9 @@ void WorldDatabaseConnection::DoPrepareStatements() + PrepareStatement(WORLD_DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_ASYNC); + PrepareStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC); ++ ++ // Bot ++ PrepareStatement(WORLD_SEL_NPCBOT_INFO, "SELECT guid, map, position_x, position_y, position_z, orientation FROM creature WHERE id = ?", CONNECTION_SYNCH); ++ PrepareStatement(WORLD_SEL_NPCBOT_PET_LEVELSTATS, "SELECT hp, mana, armor, str, agi, sta, inte, spi FROM pet_levelstats WHERE creature_entry = ? AND level = ?", CONNECTION_SYNCH); ++ PrepareStatement(WORLD_UPD_NPCBOT_POSITION, "UPDATE creature SET map = ?, position_x = ?, position_y = ?, position_z = ?, orientation = ? WHERE guid = ?", CONNECTION_ASYNC); + } +diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h +index 6ac4ce5..76050d3 100644 +--- a/src/server/database/Database/Implementation/WorldDatabase.h ++++ b/src/server/database/Database/Implementation/WorldDatabase.h +@@ -100,6 +100,11 @@ enum WorldDatabaseStatements + WORLD_UPD_CREATURE_ZONE_AREA_DATA, + WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA, + ++ // Bot ++ WORLD_SEL_NPCBOT_INFO, ++ WORLD_SEL_NPCBOT_PET_LEVELSTATS, ++ WORLD_UPD_NPCBOT_POSITION, ++ + MAX_WORLDDATABASE_STATEMENTS + }; + +diff --git a/src/server/game/AI/NpcBots/QBots/botQ_Airen.cpp b/src/server/game/AI/NpcBots/QBots/botQ_Airen.cpp +new file mode 100644 +index 0000000..041834a +--- /dev/null ++++ b/src/server/game/AI/NpcBots/QBots/botQ_Airen.cpp +@@ -0,0 +1,121 @@ ++#include "bot_ai.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptedGossip.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++#include "WorldSession.h" ++/* ++Bot Quest npc Airen by Graff onlysuffering@gmail.com ++Complete - 0% ++TODO: ++*/ ++#define ACT GOSSIP_ACTION_INFO_DEF ++ ++class Airen_chapter1 : public CreatureScript ++{ ++public: ++ Airen_chapter1() : CreatureScript("npc_Airen_qI") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new Airen_AI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, "nothing here", 6000, ACT + 1, "nothing here either", 0 * COPPER, true); ++ player->PlayerTalkClass->SendGossipMenu(GOSSIP_MURDER, creature->GetGUID()); ++ ++ std::ostringstream msg; ++ msg << "..." << player->GetName() << ", huh?"; ++ bot_ai::BotSpeak(msg.str(), CHAT_MSG_WHISPER, LANG_UNIVERSAL, creature->GetGUID(), player->GetGUID()); ++ ++ return true; ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ ++ switch (sender) ++ { ++ case 6000: ++ { ++ if (action == ACT + 1) ++ { ++ if (!player->HasEnoughMoney(1 * COPPER)) ++ { ++ player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, creature, 0, 0); ++ break; ++ } ++ player->ModifyMoney(-(1 * COPPER)); ++ } ++ ++ break; ++ } ++ default: ++ break; ++ } ++ ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ std::string answer = "asd"; ++ ++ switch (sender) ++ { ++ case 6000: ++ { ++ if (action == ACT + 1 && code == answer) ++ bot_ai::BotSpeak("hehe", CHAT_MSG_YELL, LANG_UNIVERSAL, creature->GetGUID(), player->GetGUID()); ++ break; ++ } ++ } ++ ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ struct Airen_AI : public ScriptedAI ++ { ++ Airen_AI(Creature* creature) : ScriptedAI(creature) { } ++ ++ void EnterCombat(Unit*) { } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { } ++ void MoveInLineOfSight(Unit*) { } ++ void JustDied(Unit*) { me->DisappearAndDie(); } ++ ++ void UpdateAI(uint32 /*diff*/) ++ { ++ } ++ ++ void Reset() ++ { ++ me->SetCreateHealth(213000213); ++ me->SetMaxHealth(me->GetCreateHealth()); ++ me->SetFullHealth(); ++ ++ me->setPowerType(POWER_RAGE); ++ me->SetMaxPower(POWER_RAGE, 10000); ++ me->SetPower(POWER_RAGE, me->GetMaxPower(POWER_RAGE)); ++ } ++ ++ void DamageTaken(Unit* /*u*/, uint32& damage) ++ { ++ damage = me->GetHealth() > 1 ? 1 : 0; ++ } ++ }; ++}; ++ ++void AddSC_BotQuests_chapter1() ++{ ++ new Airen_chapter1(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_Events.h b/src/server/game/AI/NpcBots/bot_Events.h +new file mode 100644 +index 0000000..d72af36 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_Events.h +@@ -0,0 +1,133 @@ ++#ifndef _BOT_EVENTS_H ++#define _BOT_EVENTS_H ++ ++//#include "Player.h" ++//#include "SpellAuras.h" ++//#include "bot_ai.h" ++#include "Creature.h" ++//#include "MapManager.h" ++/* ++Name: bot_Events ++%Complete: 1 ++Comment: Custom event types for NPCBot system by Graff (onlysuffering@gmail.com) ++Category: creature_cripts/custom/bots/events ++ ++Notes: ++All events must be executed through botAI ++*/ ++//DEPRECATED Visibility update: needed after near teleport ++//class VisibilityUpdateEvent : public BasicEvent ++//{ ++// friend class bot_minion_ai; ++// protected: ++// VisibilityUpdateEvent(uint64 botGuid, bool force = true) : _botGuid(botGuid), _force(force) { } ++// ~VisibilityUpdateEvent() {} ++// ++// bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++// { ++// if (Creature* bot = ObjectAccessor::GetObjectInWorld(_botGuid, (Creature*)NULL)) ++// { ++// bot->GetBotMinionAI()->UpdateBotVisibility(_force); ++// return true; ++// } ++// return false; ++// } ++// ++// private: ++// uint64 _botGuid; ++// bool _force; ++//}; ++//Teleport home: near or far, only used for free bots ++class TeleportHomeEvent : public BasicEvent ++{ ++ friend class bot_minion_ai; ++ friend class bot_ai; ++ protected: ++ TeleportHomeEvent(bot_minion_ai* ai/*, uint64 botGuid*/) : ++ _ai(ai)/*, _botGuid(botGuid)*/ ++ { } ++ ~TeleportHomeEvent() {} ++ ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ _ai->TeleportHome(); ++ return true; ++ } ++ ++ private: ++ bot_minion_ai* _ai; ++ //uint64 _botGuid; ++}; ++//DEPRECATEDEvade mode enable/disable: adds UNIT_STATE_EVADE ++//class EvadeEvent : public BasicEvent ++//{ ++// friend class bot_minion_ai; ++// protected: ++// EvadeEvent(uint64 botGuid, bool apply) : _botGuid(botGuid), _apply(apply) { } ++// ~EvadeEvent() {} ++// ++// bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++// { ++// if (Creature* bot = ObjectAccessor::GetObjectInWorld(_botGuid, (Creature*)NULL)) ++// { ++// bot->GetBotMinionAI()->SetEvade(_apply); ++// return true; ++// } ++// return false; ++// } ++// ++// private: ++// uint64 _botGuid; ++// bool _apply; ++//}; ++//Delayed teleport finish: adds bot back to world on new location ++class TeleportFinishEvent : public BasicEvent ++{ ++ friend class bot_minion_ai; ++ friend class BotMgr; ++ protected: ++ TeleportFinishEvent(bot_minion_ai* ai/*, uint32 mapId, uint32 instanceId, float x, float y, float z, float o*/) : ++ _ai(ai)//, _mapId(mapId), _instanceId(instanceId), _x(x), _y(y), _z(z), _o(o) ++ { } ++ ~TeleportFinishEvent() {} ++ ++ //Execute is always called while creature is out of world so ai is never deleted ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ _ai->FinishTeleport(/*_mapId, _instanceId, _x, _y, _z, _o*/); ++ return true; ++ } ++ ++ private: ++ bot_minion_ai* _ai; ++ //uint32 _mapId; ++ //uint32 _instanceId; ++ //float _x; ++ //float _y; ++ //float _z; ++ //float _o; ++}; ++//DEPRECATED ++//class NearTeleportEvent : public BasicEvent ++//{ ++// friend class bot_ai; ++// protected: ++// NearTeleportEvent(uint64 botGuid, Position* pos) : _botGuid(botGuid), _pos(pos) { } ++// ~NearTeleportEvent() {} ++// ++// bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++// { ++// if (Creature* bot = ObjectAccessor::GetObjectInWorld(_botGuid, (Creature*)NULL)) ++// { ++// bot->Relocate(_pos); ++// return true; ++// } ++// return false; ++// } ++// ++// private: ++// uint64 _botGuid; ++// Position* _pos; ++//}; ++ ++#endif +diff --git a/src/server/game/AI/NpcBots/bot_GridNotifiers.h b/src/server/game/AI/NpcBots/bot_GridNotifiers.h +new file mode 100644 +index 0000000..9f3c35d +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_GridNotifiers.h +@@ -0,0 +1,724 @@ ++#ifndef _BOT_GRIDNOTIFIERS_H ++#define _BOT_GRIDNOTIFIERS_H ++ ++#include "Group.h" ++#include "Player.h" ++#include "SpellAuras.h" ++#include "bot_ai.h" ++/* ++Name: bot_GridNotifiers ++%Complete: 99+ ++Comment: Custom grid notifiers for Bot system by Graff (onlysuffering@gmail.com) ++Category: creature_cripts/custom/bots/grids ++*/ ++ ++extern bool _botPvP; ++ ++class NearestHostileUnitCheck ++{ ++ public: ++ explicit NearestHostileUnitCheck(Unit const* unit, float dist, bool magic, bot_ai const* m_ai, bool targetCCed = false) : ++ me(unit), m_range(dist), byspell(magic), ai(m_ai), AttackCCed(targetCCed) ++ { free = ai->IAmFree(); } ++ bool operator()(Unit const* u) ++ { ++ if (u == me) ++ return false; ++ if (!_botPvP && !free && u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (me->HasUnitState(UNIT_STATE_ROOT) && (ai->HasRole(BOT_ROLE_RANGED) == me->IsWithinDistInMap(u, 8.f))) ++ return false; ++ if (/*!free && */!u->IsInCombat()) ++ return false; ++ if (!ai->CanBotAttack(u, byspell)) ++ return false; ++ if (ai->InDuel(u)) ++ return false; ++ if (!AttackCCed && (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE))) ++ return false;//do not allow CCed units if checked ++ //if (u->HasUnitState(UNIT_STATE_CASTING) && (u->GetTypeId() == TYPEID_PLAYER || u->IsPet())) ++ // for (uint8 i = 0; i != CURRENT_MAX_SPELL; ++i) ++ // if (Spell* spell = u->GetCurrentSpell(i)) ++ // if (ai->IsInBotParty(spell->m_targets.GetUnitTarget())) ++ // return true; ++ if (!ai->IsInBotParty(u->GetVictim())) ++ return false; ++ ++ if (free) ++ { ++ if (u->IsControlledByPlayer() && !u->IsInCombat()) ++ return false; ++ if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack()) ++ return false; ++ } ++ ++ m_range = me->GetDistance(u); // use found unit range as new range limit for next check ++ return true; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ bool byspell; ++ bot_ai const* ai; ++ bool AttackCCed; ++ bool free; ++ NearestHostileUnitCheck(NearestHostileUnitCheck const&); ++}; ++ ++class HostileDispelTargetCheck ++{ ++ public: ++ explicit HostileDispelTargetCheck(Unit const* unit, float dist = 30, bool stealable = false, bot_ai const* m_ai = NULL) : ++ me(unit), m_range(dist), checksteal(stealable), ai(m_ai) { } ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !ai->IAmFree() && u->IsControlledByPlayer()) ++ return false; ++ if (u->IsWithinDistInMap(me, m_range) && ++ u->IsAlive() && ++ u->InSamePhase(me) && ++ u->IsInCombat() && ++ u->isTargetableForAttack() && ++ u->IsVisible() && ++ u->GetReactionTo(me) <= REP_NEUTRAL && ++ ai->IsInBotParty(u->GetVictim())) ++ { ++ if (checksteal && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(30449))) return false;//immune to steal ++ if (!checksteal) ++ { ++ if (me->getLevel() >= 70 && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(32375))) return false;//immune to mass dispel ++ if (me->getLevel() < 70 && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(527))) return false;//immune to direct dispel ++ } ++ Unit::AuraMap const &Auras = u->GetOwnedAuras(); ++ SpellInfo const* Info; ++ uint32 id; ++ for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) ++ { ++ Aura* aura = itr->second; ++ Info = aura->GetSpellInfo(); ++ id = Info->Id; ++ if (id == 20050 || id == 20052 || id == 20053 || //Vengeance ++ id == 50447 || id == 50448 || id == 50449) //Bloody Vengeance ++ continue; ++ if (Info->Dispel != DISPEL_MAGIC) continue; ++ if (Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; ++ //if (Info->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; ++ if (checksteal && (Info->AttributesEx4 & SPELL_ATTR4_NOT_STEALABLE)) continue; ++ AuraApplication const* aurApp = aura->GetApplicationOfTarget(u->GetGUID()); ++ ++ if (aurApp && aurApp->IsPositive()) ++ return true; ++ } ++ } ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ bool checksteal; ++ bot_ai const* ai; ++ HostileDispelTargetCheck(HostileDispelTargetCheck const&); ++}; ++ ++class AffectedTargetCheck ++{ ++ public: ++ explicit AffectedTargetCheck(ObjectGuid casterguid, float dist, uint32 spellId, Player const* groupCheck = 0, uint8 hostileCheckType = 0) : ++ caster(casterguid), m_range(dist), spell(spellId), checker(groupCheck), needhostile(hostileCheckType) ++ { gr = NULL; if (checker->GetTypeId() != TYPEID_PLAYER) return; gr = checker->GetGroup(); } ++ bool operator()(Unit const* u) const ++ { ++ if (caster && u->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) ++ return false; ++ if (needhostile == 0 && !u->IsHostileTo(checker)) return false; ++ if (needhostile == 1 && !(gr && gr->IsMember(u->GetGUID()) && u->GetTypeId() == TYPEID_PLAYER)) return false; ++ if (needhostile == 2 && !(gr && gr->IsMember(u->GetGUID()))) return false; ++ if (needhostile == 3 && !u->IsFriendlyTo(checker)) return false; ++ ++ if (u->IsAlive() && checker->IsWithinDistInMap(u, m_range)) ++ { ++ Unit::AuraMap const &Auras = u->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) ++ { ++ Aura* aura = itr->second; ++ if (aura->GetId() == spell) ++ if (caster == 0 || aura->GetCasterGUID() == caster) ++ return true; ++ } ++ } ++ return false; ++ } ++ private: ++ ObjectGuid const caster; ++ float m_range; ++ uint32 const spell; ++ Player const* checker; ++ uint8 needhostile; ++ Group const* gr; ++ AffectedTargetCheck(AffectedTargetCheck const&); ++}; ++ ++class PolyUnitCheck ++{ ++ public: ++ explicit PolyUnitCheck(Unit const* unit, float dist, Unit const* currTarget) : me(unit), m_range(dist), mytar(currTarget) {} ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (u == mytar) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (!u->IsInCombat() || !u->IsAlive() || !u->GetVictim()) ++ return false; ++ if (u->GetCreatureType() != CREATURE_TYPE_HUMANOID && ++ u->GetCreatureType() != CREATURE_TYPE_BEAST) ++ return false; ++ if (me->GetDistance(u) < 6 || mytar->GetDistance(u) < 5 || ++ (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE && u->GetHealthPct() < 70)) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!u->isTargetableForAttack()) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE ? !u->getAttackers().empty() : u->getAttackers().size() > 1) ++ return false; ++ if (!u->IsHostileTo(me)) ++ return false; ++ if (u->IsPolymorphed() || ++ u->isFrozen() || ++ u->isInRoots() || ++ u->HasAura(51514)/*hex*/ || ++ u->HasAura(20066)/*repentance*/ || ++ //u->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_DAMAGE, sSpellMgr->GetSpellInfo(339)) || //entangling roots ++ //u->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_DAMAGE, sSpellMgr->GetSpellInfo(16914)) || //hurricane ++ //u->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_DAMAGE, sSpellMgr->GetSpellInfo(10)) || //blizzard ++ //u->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_DAMAGE, sSpellMgr->GetSpellInfo(2121)) || //flamestrike ++ //u->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_DAMAGE, sSpellMgr->GetSpellInfo(20116)) || //consecration ++ u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ++ return false; ++ ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE && !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(118)))//Polymorph ++ return true; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_SHAMAN && !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(51514)))//Hex ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ Unit const* mytar; ++ PolyUnitCheck(PolyUnitCheck const&); ++}; ++ ++class FearUnitCheck ++{ ++ public: ++ explicit FearUnitCheck(Unit const* unit, float dist = 30) : me(unit), m_range(dist) {} ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!u->IsInCombat()) ++ return false; ++ if (u->GetCreatureType() == CREATURE_TYPE_UNDEAD) ++ return false; ++ if (u->GetCreatureType() != CREATURE_TYPE_BEAST && ++ me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER) ++ return false; ++ if (!u->IsAlive()) ++ return false; ++ if (!u->isTargetableForAttack()) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (u->getAttackers().size() > 1 && u->GetVictim() != me) ++ return false; ++ if (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) ++ return false; ++ if (u->isFeared()) ++ return false; ++ if (u->GetReactionTo(me) > REP_NEUTRAL) ++ return false; ++ ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_WARLOCK && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(5782)))//fear rank1 ++ return true; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(1513)))//scare beast rank1 ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ FearUnitCheck(FearUnitCheck const&); ++}; ++ ++class StunUnitCheck ++{ ++ public: ++ explicit StunUnitCheck(Unit const* unit, float dist = 20) : me(unit), m_range(dist) {} ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (!u->IsInCombat()) ++ return false; ++ if (me->GetVictim() == u) ++ return false; ++ if (me->GetTypeId() == TYPEID_UNIT) ++ if (Player* mymaster = me->ToCreature()->GetBotOwner()) ++ if (mymaster->GetVictim() == u) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (u->GetReactionTo(me) > REP_NEUTRAL) ++ return false; ++ if (!u->IsAlive()) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (!u->isTargetableForAttack()) ++ return false; ++ if (!u->getAttackers().empty()) ++ return false; ++ if (u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ++ return false; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && ++ !(u->GetCreatureType() == CREATURE_TYPE_HUMANOID || ++ u->GetCreatureType() == CREATURE_TYPE_DEMON || ++ u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN || ++ u->GetCreatureType() == CREATURE_TYPE_GIANT || ++ u->GetCreatureType() == CREATURE_TYPE_UNDEAD)) ++ return false; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && u->isFeared()) ++ return false; ++ if (me->GetDistance(u) < 10)//do not allow close cast to prevent break due to AOE damage ++ return false; ++ if (u->IsPolymorphed() || ++ u->HasAura(51514)/*hex*/ || ++ u->HasAura(20066)/*repentance*/ || ++ u->HasAuraWithMechanic((1<ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(20066)))//repentance ++ return true; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(60210)))//freezing arrow effect ++ return true; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(19386)))//wyvern sting rank 1 ++ return true; ++ if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && ++ !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(1991)))//scatter shot ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ StunUnitCheck(StunUnitCheck const&); ++}; ++ ++class UndeadCCUnitCheck ++{ ++ public: ++ explicit UndeadCCUnitCheck(Unit const* unit, float dist = 30, uint32 spell = 0) : me(unit), m_range(dist), m_spellId(spell) { if (!spell) return; } ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!u->IsInCombat()) ++ return false; ++ if (u->GetReactionTo(me) > REP_NEUTRAL) ++ return false; ++ if (!u->IsAlive()) ++ return false; ++ if (!u->isTargetableForAttack()) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (me->GetVictim() == u && u->GetVictim() == me) ++ return false; ++ if (!u->getAttackers().empty()) ++ return false; ++ if (u->GetCreatureType() != CREATURE_TYPE_UNDEAD && ++ (m_spellId == 9484 || m_spellId == 9485 || m_spellId == 10955))//shackle undead ++ return false; ++ //most horrible hacks ++ if (u->GetCreatureType() != CREATURE_TYPE_UNDEAD && ++ u->GetCreatureType() != CREATURE_TYPE_DEMON && ++ (m_spellId == 2812 || m_spellId == 10318 || //holy ++ m_spellId == 27139 || m_spellId == 48816 || //wra ++ m_spellId == 48817 || //th or ++ m_spellId == 10326)) //turn evil ++ return false; ++ if (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) ++ return false; ++ if (u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && ++ (m_spellId == 9484 || m_spellId == 9485 || m_spellId == 10955))//shackle undead ++ return false; ++ if (!u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(m_spellId))) ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range; ++ uint32 m_spellId; ++ UndeadCCUnitCheck(UndeadCCUnitCheck const&); ++}; ++ ++class RootUnitCheck ++{ ++ public: ++ explicit RootUnitCheck(Unit const* unit, Unit const* mytarget, float dist = 30, uint32 spell = 0) : me(unit), curtar(mytarget), m_range(dist), m_spellId(spell) ++ { if (!spell) return; } ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (u == curtar) ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range)) ++ return false; ++ if (!u->IsAlive()) ++ return false; ++ if (!u->IsInCombat()) ++ return false; ++ if (me->GetDistance(u) < 8) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (!u->isTargetableForAttack()) ++ return false; ++ if (u->GetReactionTo(me) > REP_NEUTRAL) ++ return false; ++ if (u->isFrozen() || u->isInRoots()) ++ return false; ++ if (!u->getAttackers().empty()) ++ return false; ++ if (u->IsPolymorphed() || ++ u->HasAura(51514)/*hex*/ || ++ u->HasAura(20066)/*repentance*/ || ++ u->HasAuraWithMechanic(1<IsImmunedToSpell(sSpellMgr->GetSpellInfo(m_spellId))) ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ Unit const* curtar; ++ float m_range; ++ uint32 m_spellId; ++ RootUnitCheck(RootUnitCheck const&); ++}; ++ ++class CastingUnitCheck ++{ ++ public: ++ explicit CastingUnitCheck(Unit const* unit, float mindist = 0.f, float maxdist = 30, bool friendly = false, uint32 spell = 0) : ++ me(unit), min_range(mindist), max_range(maxdist), m_friend(friendly), m_spell(spell) {} ++ bool operator()(Unit const* u) const ++ { ++ if (!m_friend && !_botPvP && !(me->ToCreature() && me->ToCreature()->IsFreeBot()) && u->IsControlledByPlayer()) ++ return false; ++ if (min_range > 0.1f && me->GetDistance(u) < min_range) ++ return false; ++ if (!me->IsWithinDistInMap(u, max_range)) ++ return false; ++ if (!u->IsAlive()) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!u->IsVisible()) ++ return false; ++ if (!m_friend && !u->isTargetableForAttack()) ++ return false; ++ //if (!m_friend && u->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))//prevent double silence ++ // return false; ++ if (!u->IsNonMeleeSpellCast(false)) ++ return false; ++ if (m_friend == (u->GetReactionTo(me) < REP_FRIENDLY)) ++ return false; ++ if (m_spell == 10326 && //turn evil ++ u->GetCreatureType() != CREATURE_TYPE_UNDEAD && ++ u->GetCreatureType() != CREATURE_TYPE_DEMON) ++ return false; ++ if (m_spell == 20066 && //repentance ++ !(u->GetCreatureType() == CREATURE_TYPE_HUMANOID || ++ u->GetCreatureType() == CREATURE_TYPE_DEMON || ++ u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN || ++ u->GetCreatureType() == CREATURE_TYPE_GIANT || ++ u->GetCreatureType() == CREATURE_TYPE_UNDEAD)) ++ return false; ++ if (!m_spell || !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(m_spell))) ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float min_range, max_range; ++ bool m_friend; ++ uint32 m_spell; ++ CastingUnitCheck(CastingUnitCheck const&); ++}; ++ ++class SecondEnemyCheck ++{ ++ public: ++ explicit SecondEnemyCheck(Unit const* unit, float dist, float splashdist, Unit const* currtarget, bot_ai const* m_ai) : ++ me(unit), m_range(dist), m_splashrange(splashdist), mytar(currtarget), ai(m_ai) {} ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !ai->IAmFree() && u->IsControlledByPlayer()) ++ return false; ++ if (u == mytar) ++ return false;//We need to find SECONDARY target ++ if (!u->IsInCombat()) ++ return false; ++ if (u->isMoving() != mytar->isMoving())//only when both targets idle or both moving ++ return false; ++ if (!me->IsWithinDistInMap(u, m_range + 1.f))//distance check ++ return false; ++ if (mytar->GetDistance(u) > m_splashrange)//not close enough to each other ++ return false; ++ ++ if (ai->CanBotAttack(u)) ++ return true; ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float m_range, m_splashrange; ++ Unit const* mytar; ++ bot_ai const* ai; ++ SecondEnemyCheck(SecondEnemyCheck const&); ++}; ++ ++class TranquilTargetCheck ++{ ++ public: ++ explicit TranquilTargetCheck(Unit const* unit, float mindist, float maxdist, bot_ai const* m_ai) : ++ me(unit), min_range(mindist), max_range(maxdist), ai(m_ai) { } ++ bool operator()(Unit const* u) const ++ { ++ if (!_botPvP && !ai->IAmFree() && u->IsControlledByPlayer()) ++ return false; ++ if (u != me->GetVictim() &&//check hunter_bot::hunter_botAI::CheckTranquil(uint32) ++ u->IsWithinDistInMap(me, max_range) && ++ u->GetDistance(me) > min_range && ++ u->IsAlive() && ++ u->InSamePhase(me) && ++ u->IsInCombat() && ++ u->isTargetableForAttack() && ++ u->IsVisible() && ++ u->GetReactionTo(me) <= REP_NEUTRAL && ++ ai->IsInBotParty(u->GetVictim())) ++ { ++ if (u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(19801))) return false;//immune to tranquilizing shot ++ Unit::AuraMap const &Auras = u->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) ++ { ++ SpellInfo const* Info = itr->second->GetSpellInfo(); ++ if (Info->Dispel != DISPEL_MAGIC && Info->Dispel != DISPEL_ENRAGE) continue; ++ if (Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; ++ //if (Info->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; ++ AuraApplication const* aurApp = itr->second->GetApplicationOfTarget(u->GetGUID()); ++ if (aurApp && aurApp->IsPositive()) ++ { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ private: ++ Unit const* me; ++ float min_range, max_range; ++ bot_ai const* ai; ++ TranquilTargetCheck(TranquilTargetCheck const&); ++}; ++ ++class NearbyHostileUnitCheck ++{ ++ public: ++ explicit NearbyHostileUnitCheck(Unit const* unit, float maxdist, float mindist, bot_ai const* m_ai, bool forCC) : ++ me(unit), max_range(maxdist), min_range(mindist), ai(m_ai), m_forCC(forCC) ++ { ++ free = ai->IAmFree(); ++ } ++ bool operator()(Unit const* u) const ++ { ++ if (u == me) ++ return false; ++ if (me->HasUnitState(UNIT_STATE_ROOT) && (ai->HasRole(BOT_ROLE_RANGED) == me->IsWithinDistInMap(u, 8.f))) ++ return false; ++ if (/*!free && */!u->IsInCombat()) ++ return false; ++ if (!free && !ai->CanBotAttack(u)) ++ return false; ++ if (!_botPvP && !ai->IAmFree() && u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsWithinDistInMap(u, max_range)) ++ return false; ++ if (min_range > 0.1f && me->GetDistance(u) < min_range) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (ai->InDuel(u)) ++ return false; ++ if (u->HasUnitState(UNIT_STATE_CONFUSED|UNIT_STATE_STUNNED|UNIT_STATE_FLEEING|UNIT_STATE_DISTRACTED|UNIT_STATE_CONFUSED_MOVE)) ++ return false; ++ if (m_forCC && u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ++ return false; ++ ++ if (!free && !ai->IsInBotParty(u->GetVictim())) ++ return false; ++ ++ if (free) ++ { ++ if (u->IsControlledByPlayer()) ++ return false; ++ if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack()) ++ return false; ++ } ++ ++ return true; ++ } ++ private: ++ Unit const* me; ++ float max_range, min_range; ++ bot_ai const* ai; ++ bool m_forCC; ++ bool free; ++ NearbyHostileUnitCheck(NearbyHostileUnitCheck const&); ++}; ++ ++class NearbyFriendlyUnitCheck ++{ ++ public: ++ explicit NearbyFriendlyUnitCheck(Unit const* unit, float maxdist, bot_ai const* m_ai) : me(unit), max_range(maxdist), ai(m_ai) { } ++ bool operator()(Unit const* u) const ++ { ++ if (u == me) ++ return false; ++ //if (!u->IsInCombat()) ++ // return false; ++ if (u->IsTotem() || u->IsSummon()) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!me->IsWithinDistInMap(u, max_range)) ++ return false; ++ if (!me->CanSeeOrDetect(u)) ++ return false; ++ if (ai->InDuel(u)) ++ return false; ++ if (!ai->IsInBotParty(u)) ++ return false; ++ ++ return true; ++ } ++ private: ++ Unit const* me; ++ float max_range; ++ bot_ai const* ai; ++ NearbyFriendlyUnitCheck(NearbyFriendlyUnitCheck const&); ++}; ++ ++class NearbyRezTargetCheck ++{ ++ public: ++ explicit NearbyRezTargetCheck(Unit const* unit, float maxdist, bot_ai const* m_ai) : me(unit), max_range(maxdist), ai(m_ai) { } ++ bool operator()(WorldObject const* u) const ++ { ++ if (u == me) ++ return false; ++ if (u->GetTypeId() != TYPEID_PLAYER && u->GetTypeId() != TYPEID_CORPSE) ++ return false; ++ if (!u->InSamePhase(me)) ++ return false; ++ if (!me->IsWithinDistInMap(u, max_range)) ++ return false; ++ if (Player const* p = u->ToPlayer()) ++ { ++ if (p->IsAlive()) ++ return false; ++ if (p->isResurrectRequested()) ++ return false; ++ if (!ai->IsInBotParty(p)) ++ return false; ++ } ++ if (!me->CanSeeOrDetect(u)) ++ return false; ++ if (urand(0,100) > 20) ++ return false; ++ if (u->GetTypeId() == TYPEID_CORPSE && !ObjectAccessor::FindPlayer(u->ToCorpse()->GetOwnerGUID())) ++ return false; ++ ++ return true; ++ } ++ private: ++ Unit const* me; ++ float max_range; ++ bot_ai const* ai; ++ NearbyRezTargetCheck(NearbyRezTargetCheck const&); ++}; ++ ++template ++struct UnitListSearcher ++{ ++ uint32 i_phaseMask; ++ GuidList &i_objects; ++ Check& i_check; ++ ++ UnitListSearcher(WorldObject const* searcher, GuidList &objects, Check &check) ++ : i_phaseMask(searcher->GetPhaseMask()), i_objects(objects), i_check(check) { } ++ ++ void Visit(PlayerMapType &m) ++ { ++ for (PlayerMapType::iterator itr = m.begin(); itr != m.end(); ++itr) ++ if (itr->GetSource()->InSamePhase(i_phaseMask)) ++ if (i_check(itr->GetSource())) ++ i_objects.push_back(itr->GetSource()->GetGUID()); ++ } ++ void Visit(CreatureMapType &m) ++ { ++ for (CreatureMapType::iterator itr = m.begin(); itr != m.end(); ++itr) ++ if (itr->GetSource()->InSamePhase(i_phaseMask)) ++ if (i_check(itr->GetSource())) ++ i_objects.push_back(itr->GetSource()->GetGUID()); ++ } ++ ++ template void Visit(GridRefManager &) { } ++}; ++ ++#endif +diff --git a/src/server/game/AI/NpcBots/bot_ai.cpp b/src/server/game/AI/NpcBots/bot_ai.cpp +new file mode 100644 +index 0000000..ac4c381 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_ai.cpp +@@ -0,0 +1,8126 @@ ++#include "bot_ai.h" ++#include "bot_Events.h" ++#include "bot_GridNotifiers.h" ++#include "botmgr.h" ++#include "CellImpl.h" ++#include "Chat.h" ++#include "GameEventMgr.h" ++#include "GridNotifiers.h" ++#include "GridNotifiersImpl.h" ++#include "MapManager.h" ++#include "ScriptedGossip.h" ++#include "SpellAuraEffects.h" ++/* ++NpcBot System by Graff (onlysuffering@gmail.com) ++Original patch from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch ++TODO: ++Implement Racial Abilities ++Quests ++I NEED MORE ++*/ ++const uint8 GroupIconsFlags[TARGETICONCOUNT] = ++{ ++ /*STAR = */0x001, ++ /*CIRCLE = */0x002, ++ /*DIAMOND = */0x004, ++ /*TRIANGLE = */0x008, ++ /*MOON = */0x010, ++ /*SQUARE = */0x020, ++ /*CROSS = */0x040, ++ /*SKULL = */0x080 ++}; ++ ++static std::set BotCustomSpells; ++static bool SPELLS_DEFINED = false; ++ ++extern bool _enableNpcBots; ++extern bool _botPvP; ++extern uint8 _maxClassNpcBots; ++extern uint8 _healTargetIconFlags; ++extern float _mult_dmg_melee; ++extern float _mult_dmg_spell; ++extern float _mult_healing; ++ ++bot_minion_ai::bot_minion_ai(Creature* creature) : bot_ai(creature) ++{ ++ Potion_cd = 0; ++ pvpTrinket_cd = 30000; ++ rezz_cd = 0; ++ myangle = 0.f; ++ mana_cd = 0; ++ health_cd = 0; ++ feast_health = false; ++ feast_mana = false; ++ _classinfo = new PlayerClassLevelInfo(); ++ ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ for (uint8 j = 0; j != MAX_BOT_ITEM_MOD; ++j) ++ _stats[i][j] = 0; ++ ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ _equips[i] = NULL; ++ ++ _reviveTimer = 0; ++ _saveTimer = 0; ++ _powersTimer = 0; ++ _chaseTimer = 0; ++ ++ _jumpCount = 0; ++ _evadeCount = 0; ++ ++ _lastTargetGuid.Clear(); ++} ++bot_minion_ai::~bot_minion_ai() ++{ ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ if (_equips[i]) ++ delete _equips[i]; ++ delete _classinfo; ++} ++ ++bot_pet_ai::bot_pet_ai(Creature* creature) : bot_ai(creature) ++{ ++ m_creatureOwner = me->GetCreatureOwner(); ++ basearmor = 0; ++} ++bot_pet_ai::~bot_pet_ai() { } ++ ++bot_ai::bot_ai(Creature* creature) : ScriptedAI(creature) ++{ ++ bot_ai::InitBotCustomSpells(); ++ ++ ResetBotAI(BOTAI_RESET_INIT); ++ m_botCommandState = COMMAND_FOLLOW; ++ checkMasterTimer = urand(5000, 15000); ++ needparty = false; ++ spawned = false; ++ firstspawn = true; ++ _evadeMode = false; ++ _atHome = true; ++ _temp = me->GetSpawnId() ? false : true; ++ _roleMask = 0; ++ haste = 0; ++ blockvalue = 1; ++ hit = 0.f; ++ parry = 0.f; ++ dodge = 0.f; ++ block = 0.f; ++ crit = 0.f; ++ dmg_taken = 1.f; ++ expertise = 0; ++ spellpower = 0; ++ spellpen = 0; ++ regen_mp = 0; ++ regenTimer_hp = 0; ++ regenTimer_mp = 0; ++ m_botSpellInfo = NULL; ++ clear_cd = 2; ++ temptimer = 0; ++ wait = 15; ++ GC_Timer = 0; ++ lastdiff = 0; ++ _bootTimer = -1; ++ _updateTimerMedium = 0; ++ checkAurasTimer = 20; ++ roleTimer = 0; ++ cost = 0; ++ doHealth = false; ++ doMana = false; ++ //shouldUpdateStats = true; ++ pos.m_positionX = 0.f; ++ pos.m_positionY = 0.f; ++ pos.m_positionZ = 0.f; ++ aftercastTargetGuid.Clear(); ++ currentSpell = 0; ++ ++ //visUpEvent = NULL; ++ teleHomeEvent = NULL; ++ //evadeEvent = NULL; ++ teleFinishEvent = NULL; ++} ++bot_ai::~bot_ai() { } ++ ++uint16 bot_ai::Rand() const ++{ ++ return IAmFree() ? urand(0, 100) : urand(0, 100 + (master->GetNpcBotsCount() - 1) * 10); ++} ++ ++void bot_ai::BotSay(char const* text, Player const* target) const ++{ ++ if (!target && master->GetTypeId() == TYPEID_PLAYER) ++ target = master; ++ if (!target) ++ return; ++ ++ me->Say(text, LANG_UNIVERSAL, target); ++} ++void bot_ai::BotWhisper(char const* text, Player* target) const ++{ ++ if (!target && master->GetTypeId() == TYPEID_PLAYER) ++ target = master; ++ if (!target) ++ return; ++ ++ me->Whisper(text, LANG_UNIVERSAL, target); ++} ++void bot_ai::BotYell(char const* text, Player const* target) const ++{ ++ if (!target && master->GetTypeId() == TYPEID_PLAYER) ++ target = master; ++ if (!target) ++ return; ++ ++ me->Yell(text, LANG_UNIVERSAL, target); ++} ++ ++bool bot_ai::SetBotOwner(Player* newowner) ++{ ++ ASSERT(newowner && "Trying to set NULL owner!!!"); ++ ASSERT(newowner->GetGUID().IsPlayer() && "Trying to set a non-player as owner!!!"); ++ //ASSERT(master->GetGUID() == me->GetGUID()); ++ //ASSERT(!IsMinionAI() || IAmFree()); ++ ++ //have master already ++ if (master->GetGUID() != me->GetGUID()) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::SetBotOwner(): bot %s (id: %u) has master %s while trying to set to %s...", ++ me->GetName().c_str(), me->GetEntry(), master->GetName().c_str(), newowner->GetName().c_str()); ++ return false; ++ } ++ if (IsMinionAI() && !IAmFree()) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::SetBotOwner(): minion bot %s (id: %u) IS NOT FREE (has master %s) while trying to set to %s", ++ me->GetName().c_str(), me->GetEntry(), master->GetName().c_str(), newowner->GetName().c_str()); ++ return false; ++ } ++ ++ if (IsMinionAI()) ++ { ++ BotMgr* mgr = newowner->GetBotMgr(); ++ if (!mgr) ++ mgr = new BotMgr(newowner); ++ ++ bool takeMoney = (_ownerGuid != newowner->GetGUID().GetCounter()); ++ if (mgr->AddBot(me, takeMoney) & BOT_ADD_FATAL) ++ { ++ //TC_LOG_ERROR("entities.player", "bot_ai::SetBotOwner(): player %s (%u) can't add bot %s (FATAL), removing...", ++ // master->GetName().c_str(), master->GetGUID().GetCounter(), me->GetName().c_str()); ++ //failed to add bot ++ //if (_ownerGuid) ++ //{ ++ // PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); ++ // //"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC ++ // stmt->setUInt32(0, uint32(0)); ++ // stmt->setUInt32(1, me->GetEntry()); ++ // CharacterDatabase.Execute(stmt); ++ //} ++ ++ if (_ownerGuid) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::FindMaster(): %s's master %s (guid: %u) is found but bot failed to set owner (fatal)! Unbinding bot temporarily (until server restart)...", ++ me->GetName().c_str(), newowner->GetName().c_str(), newowner->GetGUID().GetCounter()); ++ _ownerGuid = 0; ++ } ++ ++ checkMasterTimer = 30000; ++ ResetBotAI(BOTAI_RESET_LOST); ++ return false; ++ } ++ ++ (const_cast(me->GetCreatureTemplate()))->unit_flags2 &= ~(UNIT_FLAG2_ALLOW_ENEMY_INTERACT); ++ me->SetUInt32Value(UNIT_FIELD_FLAGS_2, me->GetCreatureTemplate()->unit_flags2); ++ } ++ ++ //recursive ++ if (master->GetGUID() == newowner->GetGUID()) ++ return true; ++ ++ master = newowner; ++ _ownerGuid = newowner->GetGUID().GetCounter(); ++ spawned = false; ++ ++ ASSERT(me->IsInWorld()); ++ AbortTeleport(); ++ return true; ++} ++ ++void bot_ai::ResetBotAI(uint8 resetType) ++{ ++ //ASSERT(me->IsInWorld()); ++ ++ master = reinterpret_cast(me); ++ if (resetType & BOTAI_RESET_ABANDON_MASTER) ++ _ownerGuid = 0; ++ ++ (const_cast(me->GetCreatureTemplate()))->unit_flags2 |= (UNIT_FLAG2_ALLOW_ENEMY_INTERACT); ++ me->SetUInt32Value(UNIT_FIELD_FLAGS_2, me->GetCreatureTemplate()->unit_flags2); ++ ++ me->IsAIEnabled = true; ++ me->SetCanUpdate(true); ++ ++ if (spawned) ++ ReturnHome(); ++ ++ if (!me->IsInWorld()) ++ { ++ ASSERT(IsMinionAI()); ++ AbortTeleport(); ++ ++ //if no master - will teleport to spawn position ++ //otherwise - will teleport to master ++ teleHomeEvent = new TeleportHomeEvent(ToMinionAI()); ++ events.AddEvent(teleHomeEvent, events.CalculateTime(0)); //make sure event will be deleted ++ teleHomeEvent->to_Abort = true; //make sure event will not be executed twice ++ teleHomeEvent->Execute(0,0); ++ } ++ else ++ { ++ _atHome = false; ++ spawned = false; ++ } ++} ++ ++SpellCastResult bot_ai::CheckBotCast(Unit* victim, uint32 spellId, uint8 botclass) const ++{ ++ if (spellId == 0) ++ return SPELL_FAILED_DONT_REPORT; ++ ++ if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->IsGameMaster()) ++ return SPELL_FAILED_BAD_TARGETS; ++ ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); ++ if (!spellInfo) ++ return SPELL_FAILED_DONT_REPORT; ++ ++ if (me->IsMounted() && !(spellInfo->Attributes & SPELL_ATTR0_CASTABLE_WHILE_MOUNTED)) ++ return SPELL_FAILED_NOT_MOUNTED; ++ ++ //if (Powers(spellInfo->PowerType) == me->getPowerType() && ++ // (int32)me->GetPower(me->getPowerType()) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) ++ // return SPELL_FAILED_NO_POWER; ++ ++ if ((int32)me->GetPower(Powers(spellInfo->PowerType)) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) ++ return SPELL_FAILED_NO_POWER; ++ ++ if (victim->isType(TYPEMASK_UNIT) && InDuel(victim)) ++ return SPELL_FAILED_BAD_TARGETS; ++ ++ if (victim->isType(TYPEMASK_UNIT) && !spellInfo->IsPassive()) ++ { ++ bool needRankSelection = false; ++ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) ++ { ++ if (spellInfo->IsPositiveEffect(i) && ++ (spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA || ++ spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AREA_AURA_PARTY || ++ spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID)) ++ { ++ needRankSelection = true; ++ break; ++ } ++ } ++ if (needRankSelection && victim->getLevel() < spellInfo->GetFirstRankSpell()->BaseLevel) ++ return SPELL_FAILED_LOWLEVEL; ++ } ++ ++ //disarmed ++ if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON) ++ { ++ if (spellInfo->EquippedItemInventoryTypeMask != 0) ++ { ++ if ((spellInfo->EquippedItemInventoryTypeMask & (1 << INVTYPE_WEAPONMAINHAND)) && ++ !me->CanUseAttackType(BASE_ATTACK)) ++ return SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND; ++ if ((spellInfo->EquippedItemInventoryTypeMask & (1 << INVTYPE_WEAPONOFFHAND)) && ++ !me->CanUseAttackType(OFF_ATTACK)) ++ return SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND; ++ if ((spellInfo->EquippedItemInventoryTypeMask & ((1 << INVTYPE_RANGED) | (1 << INVTYPE_RANGEDRIGHT))) && ++ !me->CanUseAttackType(RANGED_ATTACK)) ++ return SPELL_FAILED_EQUIPPED_ITEM_CLASS; ++ } ++ else if (!me->CanUseAttackType(BASE_ATTACK)) ++ return SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND; ++ } ++ ++ if (victim->isType(TYPEMASK_UNIT) && !CheckImmunities(spellId, victim)) ++ return SPELL_FAILED_BAD_TARGETS; ++ ++ switch (botclass) ++ { ++ case BOT_CLASS_PALADIN: ++ case BOT_CLASS_MAGE: ++ case BOT_CLASS_PRIEST: ++ case BOT_CLASS_DRUID: ++ case BOT_CLASS_WARLOCK: ++ case BOT_CLASS_SHAMAN: ++ if (Feasting() && !master->IsInCombat() && !master->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ++ return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; ++ break; ++ case BOT_CLASS_WARRIOR: ++ //BladeStorm ++ if (me->HasAura(46924/*67541*/)) ++ return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; ++ break; ++ case BOT_CLASS_BM: ++ //BladeStorm PLACEHOLDER ++ if (me->HasAura(46924/*67541*/)) ++ return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; ++ break; ++ case BOT_CLASS_ROGUE: ++ case BOT_CLASS_HUNTER: ++ case BOT_CLASS_DEATH_KNIGHT: ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "CheckBotCast(): Unknown bot class %u", botclass); ++ break; ++ } ++ ++ return SPELL_CAST_OK; ++} ++ ++bool bot_ai::doCast(Unit* victim, uint32 spellId, bool triggered, ObjectGuid originalCaster) ++{ ++ if (spellId == 0) return false; ++ if (IsCasting()) return false; ++ if (!victim || !victim->IsInWorld() || me->GetMap() != victim->FindMap()) return false; ++ ++ m_botSpellInfo = sSpellMgr->GetSpellInfo(spellId); ++ if (!m_botSpellInfo) ++ return false; ++ ++ //select aura level ++ if (victim->isType(TYPEMASK_UNIT)) ++ if (SpellInfo const* actualSpellInfo = m_botSpellInfo->GetAuraRankForLevel(victim->getLevel())) ++ m_botSpellInfo = actualSpellInfo; ++ ++ if (m_botSpellInfo->CalcCastTime() && JumpingFlyingOrFalling()) ++ return false; ++ ++ if (spellId == MANAPOTION) ++ { ++ value = urand(me->GetMaxPower(POWER_MANA)/4, me->GetMaxPower(POWER_MANA)/2); ++ me->CastCustomSpell(victim, spellId, &value, 0, 0, true); ++ return true; ++ } ++ else if (spellId == HEALINGPOTION) ++ { ++ value = urand(me->GetMaxHealth()/3, me->GetMaxHealth()/2); ++ me->CastCustomSpell(victim, spellId, &value, 0, 0, true); ++ return true; ++ } ++ ++ //check cooldown ++ if (!IsSpellReady(m_botSpellInfo->GetFirstRankSpell()->Id, lastdiff, false)) ++ return false; ++ ++ //remove shapeshifts manually to restore powers/stats ++ if (me->GetShapeshiftForm() != FORM_NONE) ++ { ++ if (m_botSpellInfo->CheckShapeshift(me->GetShapeshiftForm()) != SPELL_CAST_OK) ++ removeFeralForm(true); ++ } ++ ++ if (!(m_botSpellInfo->Attributes & SPELL_ATTR0_CASTABLE_WHILE_SITTING)) ++ me->SetStandState(UNIT_STAND_STATE_STAND); ++ ++ if (!IAmFree() && victim->isType(TYPEMASK_UNIT) && !victim->IsWithinLOSInMap(me) && IsInBotParty(victim)) ++ { ++ //std::ostringstream msg; ++ //msg << "casting " << spellInfo->SpellName[0] << " on " << victim->GetName(); ++ //BotWhisper(msg.str().c_str(), master); ++ me->Relocate(victim); ++ } ++ ++ if (me->isMoving() && m_botSpellInfo->CalcCastTime() > 0) ++ me->BotStopMovement(); ++ ++ TriggerCastFlags flags = triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE; ++ SpellCastTargets targets; ++ targets.SetUnitTarget(victim); ++ Spell* spell = new Spell(me, m_botSpellInfo, flags, originalCaster); ++ spell->prepare(&targets); //sets current spell if succeed ++ ++ bool casted = triggered; //triggered casts are casted immediately ++ for (uint8 i = 0; i != CURRENT_MAX_SPELL; ++i) ++ { ++ if (me->GetCurrentSpell(i) == spell) ++ { ++ casted = true; ++ break; ++ } ++ } ++ ++ if (!casted) ++ { ++ //failed to cast ++ //delete spell; //crash due to invalid event added to master's eventmap ++ return false; ++ } ++ ++ currentSpell = spellId; ++ ++ float gcd; ++ ++ if (_botclass == BOT_CLASS_ROGUE || GetBotStance() == DRUID_CAT_FORM || GetBotStance() == DEATH_KNIGHT_UNHOLY_PRESENCE) ++ gcd = 1000.f; ++ else ++ gcd = 1500.f; ++ ++ ApplyBotSpellGlobalCooldownMods(m_botSpellInfo, gcd); ++ ++ //Apply haste to cooldown ++ if (haste) ++ ApplyPercentModFloatVar(gcd, float(haste), false); ++ //global cd cannot be less than 500 ms ++ GC_Timer = std::max(gcd, 500); ++ //global cd cannot be greater than 1500 ms ++ GC_Timer = std::min(gcd, 1500); ++ ++ return true; ++} ++//Follow point calculation ++void bot_minion_ai::_calculatePos(Position& pos) ++{ ++ ASSERT(!IAmFree()); ++ ++ uint8 followdist = master->GetBotFollowDist(); ++ float mydist, angle; ++ ++ if (IsTank()) ++ { ++ mydist = frand(1.5f, 4.5f); //stand a bit farther ++ angle = (M_PI/2.f) / 16.f * frand(-3.f, 3.f); //in front +-pi/5 ++ } ++ else if (IsMelee()) ++ { ++ mydist = frand(0.5f, 2.f); ++ angle = (M_PI/2.f) / 8.f * RAND(frand(5.f, 10.f), frand(-10.f, -5.f)); //to the sides +-(pi/3 to pi/1.6) ++ } ++ else ++ { ++ mydist = frand(0.15f, 0.8f); ++ angle = (M_PI/2.f) / 6.f * frand(10.5f, 13.5f); //behind pi+-pi/4.5 ++ } ++ //myangle = angle used last time ++ //if difference between last angle and cur angle is too big, use new angle ++ //else use last angle (prevent constant struggling) ++ if (abs(abs(myangle) - abs(angle)) > M_PI/3.f) ++ myangle = angle; ++ else ++ angle = myangle; ++ mydist += std::max(int8(followdist) - 30, 0) / 5.f; //0.f-9.f ++ //mydist += followdist > 10 ? float(followdist - 10)/4.f : 0.f; //distance from 10+ is reduced ++ //mydist = std::min(mydist, 35.f); //do not spread bots too much ++ mydist = std::max(mydist - 5.f, 0.0f); //get bots closer ++ angle += master->GetOrientation(); ++ float x(0),y(0),z(0); ++ float size = me->GetObjectSize()/3.f; ++ bool over = false; ++ for (uint8 i = 0; i != 5 + over; ++i) ++ { ++ if (over) ++ { ++ mydist *= 0.2f; ++ break; ++ } ++ master->GetNearPoint(me, x, y, z, size, mydist, angle); ++ if (!master->IsWithinLOS(x,y,z)) //try to get much closer to master ++ { ++ mydist *= 0.4f - float(i*0.07f); ++ size *= 0.1f; ++ if (size < 0.1) ++ size = 0.f; ++ if (size == 0.f && me->GetPositionZ() < master->GetPositionZ()) ++ z += 0.25f; //prevent going underground ++ } ++ else ++ over = true; ++ } ++ pos.m_positionX = x; ++ pos.m_positionY = y; ++ pos.m_positionZ = z; ++ ++ // TTT ++ // m T m ++ // mmmmmm MMM mmmmmm ++ // m ddddddd m ++ // ddddddddddddd ++ // ddddddddd ++ // ++ //MMM - master ++ //T - bot tank (ROLE_TANK) ++ //m - melee (ROLE_MELEE) ++ //d - default ++} ++// Movement set ++void bot_minion_ai::SetBotCommandState(CommandStates st, bool force, Position* newpos) ++{ ++ if (!me->IsAlive()) ++ return; ++ ++ if (JumpingFlyingOrFalling()) ++ return; ++ ++ if (st == COMMAND_FOLLOW && !IsChanneling() && ((!me->isMoving() && !IsCasting() && master->IsAlive()) || force)) ++ { ++ if (!me->IsInMap(master)) return; ++ if (CCed(me, true)/* || master->HasUnitState(UNIT_STATE_FLEEING)*/) return; ++ if (me->isMoving() && Rand() > 20) return; ++ if (!newpos) ++ _calculatePos(pos); ++ else ++ { ++ pos.m_positionX = newpos->m_positionX; ++ pos.m_positionY = newpos->m_positionY; ++ pos.m_positionZ = newpos->m_positionZ; ++ } ++ if (me->getStandState() == UNIT_STAND_STATE_SIT && !Feasting()) ++ me->SetStandState(UNIT_STAND_STATE_STAND); ++ me->GetMotionMaster()->MovePoint(master->GetMapId(), pos); ++ //me->GetMotionMaster()->MoveFollow(master, mydist, angle); ++ } ++ else if (st == COMMAND_STAY) ++ { ++ me->BotStopMovement(); ++ } ++ else if (st == COMMAND_ATTACK) ++ { } ++ m_botCommandState = st; ++ if (Creature* m_botsPet = me->GetBotsPet()) ++ m_botsPet->SetBotCommandState(st, force); ++} ++ ++void bot_pet_ai::SetBotCommandState(CommandStates st, bool force, Position* /*newpos*/) ++{ ++ if (me->isDead() || IAmDead()) ++ return; ++ ++ if (JumpingFlyingOrFalling()) ++ return; ++ ++ if (st == COMMAND_FOLLOW && ((!me->isMoving() && !IsCasting() && master->IsAlive()) || force)) ++ { ++ if (!me->IsInMap(master)) return; ++ if (CCed(me, true)) return; ++ if (me->isMoving() && Rand() > 20) return; ++ Unit* followtarget = m_creatureOwner; ++ if (CCed(m_creatureOwner)) ++ followtarget = master; ++ if (followtarget == m_creatureOwner) ++ { ++ if (!me->HasUnitState(UNIT_STATE_FOLLOW) || me->GetDistance(master)*0.75f < me->GetDistance(m_creatureOwner)) ++ me->GetMotionMaster()->MoveFollow(m_creatureOwner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); ++ } ++ else ++ if (!me->HasUnitState(UNIT_STATE_FOLLOW) || me->GetDistance(m_creatureOwner)*0.75f < me->GetDistance(master)) ++ me->GetMotionMaster()->MoveFollow(master, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); ++ } ++ else if (st == COMMAND_STAY)//NUY ++ { ++ me->BotStopMovement(); ++ } ++ else if (st == COMMAND_ATTACK) ++ { } ++ m_botCommandState = st; ++} ++// Buffs And Heal (really) ++void bot_minion_ai::BuffAndHealGroup(Player* gPlayer, uint32 diff) ++{ ++ if (GC_Timer > diff) return; ++ if (me->IsMounted()) return; ++ if (IsCasting() || Feasting()) return; ++ ++ if (IAmFree()) ++ { ++ if (HealTarget(me, GetHealthPCT(me), diff)) ++ return; ++ if (BuffTarget(me, diff)) ++ return; ++ if (Creature* pet = me->GetBotsPet()) ++ { ++ if (HealTarget(pet, GetHealthPCT(pet), diff)) ++ return; ++ if (BuffTarget(pet, diff)) ++ return; ++ } ++ ++ if (me->HasAura(BERSERK)) ++ return; ++ ++ GuidList targets; ++ GetNearbyFriendlyTargetsList(targets, 30); ++ for (GuidList::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) ++ { ++ if (Unit* u = ObjectAccessor::GetUnit(*me, *itr)) ++ { ++ if (HealTarget(u, GetHealthPCT(u), diff)) ++ return; ++ if (BuffTarget(u, diff)) ++ return; ++ } ++ } ++ ++ return; ++ } ++ ++ Group* pGroup = gPlayer->GetGroup(); ++ if (!pGroup) ++ { ++ if (!master->IsInWorld() || master->IsBeingTeleported()) ++ return; ++ if (HasRole(BOT_ROLE_HEAL) && HealTarget(master, GetHealthPCT(master), diff)) ++ return; ++ if (BuffTarget(master, diff)) ++ return; ++ for (Unit::ControlList::const_iterator itr = master->m_Controlled.begin(); itr != master->m_Controlled.end(); ++itr) ++ { ++ Unit* u = *itr; ++ if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; ++ if (HasRole(BOT_ROLE_HEAL) && HealTarget(u, GetHealthPCT(u), diff)) ++ return; ++ if (Creature* cre = u->ToCreature()) ++ if (cre->GetIAmABot() || cre->IsPet()) ++ if (BuffTarget(u, diff)) ++ return; ++ } ++ return; ++ } ++ bool Bots = false; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL) continue; ++ if (me->GetMap() != tPlayer->FindMap()) continue; ++ if (!tPlayer->m_Controlled.empty()) ++ Bots = true; ++ if (!tPlayer->IsAlive()) continue; ++ if (HasRole(BOT_ROLE_HEAL) && HealTarget(tPlayer, GetHealthPCT(tPlayer), diff)) ++ return; ++ if (BuffTarget(tPlayer, diff)) ++ return; ++ } ++ if (Bots) ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL || tPlayer->m_Controlled.empty()) continue; ++ if (me->GetMap() != tPlayer->FindMap()) continue; ++ for (Unit::ControlList::const_iterator itr = tPlayer->m_Controlled.begin(); itr != tPlayer->m_Controlled.end(); ++itr) ++ { ++ Unit* u = *itr; ++ if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; ++ if (HasRole(BOT_ROLE_HEAL) && HealTarget(u, GetHealthPCT(u), diff)) ++ return; ++ if (Creature* cre = u->ToCreature()) ++ if (cre->GetIAmABot() || cre->IsPet()) ++ if (BuffTarget(u, diff)) ++ return; ++ } ++ } ++ } ++ //check if we have pointed heal target ++ if (HasRole(BOT_ROLE_HEAL)) ++ { ++ for (uint8 i = 0; i != TARGETICONCOUNT; ++i) ++ { ++ if (_healTargetIconFlags & GroupIconsFlags[i]) ++ { ++ if (ObjectGuid guid = pGroup->GetTargetIcons()[i])//check this one ++ { ++ if (Unit* unit = ObjectAccessor::FindConnectedPlayer(guid)) ++ { ++ if (unit->IsAlive() && me->GetMap() == unit->FindMap() && ++ master->GetVictim() != unit && unit->GetVictim() != master && ++ unit->GetReactionTo(master) >= REP_NEUTRAL) ++ { ++ if (HealTarget(unit, GetHealthPCT(unit), diff)) ++ return; ++ //if (CureTarget(unit, getCureSpell(), diff)) ++ // return; ++ } ++ } ++ } ++ } ++ } ++ } ++} ++// Attempt to resurrect dead players using class spells ++// Target is either player or its corpse ++void bot_minion_ai::RezGroup(uint32 REZZ, Player* gPlayer) ++{ ++ if (!REZZ || !gPlayer || me->IsMounted()) return; ++ if (rezz_cd > 0 || Rand() > 10) return; ++ ++ if (IAmFree()) ++ { ++ if (me->HasAura(BERSERK)) ++ return; ++ ++ WorldObject* playerOrCorpse = GetNearbyRezTarget(30); ++ if (!playerOrCorpse) ++ return; ++ ++ if (!playerOrCorpse->IsWithinLOSInMap(me)) ++ me->Relocate(*playerOrCorpse); ++ ++ Unit* target = playerOrCorpse->GetTypeId() == TYPEID_PLAYER ? playerOrCorpse->ToUnit() : (Unit*)playerOrCorpse->ToCorpse(); ++ if (doCast(target, REZZ)) //rezzing it ++ { ++ if (Player* player = playerOrCorpse->GetTypeId() == TYPEID_PLAYER ? playerOrCorpse->ToPlayer() : ObjectAccessor::FindPlayer(playerOrCorpse->ToCorpse()->GetOwnerGUID())) ++ BotWhisper("Rezzing You", player); ++ rezz_cd = 20; ++ } ++ ++ return; ++ } ++ ++ //TC_LOG_ERROR("entities.player", "RezGroup by %s", me->GetName().c_str()); ++ Group* pGroup = gPlayer->GetGroup(); ++ if (!pGroup) ++ { ++ Unit* target = master; ++ if (master->IsAlive()) return; ++ if (master->isResurrectRequested()) return; //resurrected ++ if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ++ target = (Unit*)master->GetCorpse(); ++ if (!target || !target->IsInWorld()) return; ++ if (me->GetMap() != target->FindMap()) return; ++ if (me->GetDistance(target) > 30) ++ { ++ me->GetMotionMaster()->MovePoint(master->GetMapId(), *target); ++ rezz_cd = 3;//6-9 sec reset ++ return; ++ } ++ else if (!target->IsWithinLOSInMap(me)) ++ me->Relocate(*target); ++ ++ if (doCast(target, REZZ))//rezzing it ++ { ++ BotWhisper("Rezzing You", master); ++ rezz_cd = 60; ++ } ++ return; ++ } ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ Unit* target = tPlayer; ++ if (!tPlayer || tPlayer->IsAlive()) continue; ++ if (tPlayer->isResurrectRequested()) continue; //resurrected ++ if (Rand() > 5) continue; ++ if (tPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ++ target = (Unit*)tPlayer->GetCorpse(); ++ if (!target || !target->IsInWorld()) continue; ++ if (master->GetMap() != target->FindMap()) continue; ++ if (me->GetDistance(target) > 30) ++ { ++ me->GetMotionMaster()->MovePoint(master->GetMapId(), *target); ++ rezz_cd = 3;//6-9 sec reset ++ return; ++ } ++ else if (!target->IsWithinLOSInMap(me)) ++ me->Relocate(*target); ++ ++ if (doCast(target, REZZ))//rezzing it ++ { ++ BotWhisper("Rezzing You", tPlayer); ++ if (tPlayer != master) ++ { ++ std::string rezstr = "Rezzing "; ++ rezstr += tPlayer->GetName(); ++ BotWhisper(rezstr.c_str(), master); ++ } ++ rezz_cd = 60; ++ return; ++ } ++ } ++} ++// CURES ++//cycle through the group sending members for cure ++void bot_minion_ai::CureGroup(Player* pTarget, uint32 cureSpell, uint32 diff) ++{ ++ if (!cureSpell || GC_Timer > diff) return; ++ if (me->getLevel() < 10 || pTarget->getLevel() < 10) return; ++ if (me->IsMounted()) return; ++ if (IsCasting() || Feasting()) return; ++ ++ if (IAmFree()) ++ { ++ if (CureTarget(me, cureSpell, diff)) ++ return; ++ if (Creature* pet = me->GetBotsPet()) ++ if (CureTarget(pet, cureSpell, diff)) ++ return; ++ ++ if (me->HasAura(BERSERK)) ++ return; ++ /* stop spam buff/dispell from uncontrolled bots ++ GuidList targets; ++ GetNearbyFriendlyTargetsList(targets, 38); ++ for (GuidList::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) ++ if (Unit* u = ObjectAccessor::GetUnit(*me, *itr)) ++ if (CureTarget(u, cureSpell, diff)) ++ return; ++ */ ++ return; ++ } ++ ++ if (!master->GetMap()->IsRaid() && Rand() > 75) return; ++ //TC_LOG_ERROR("entities.player", "%s: CureGroup() on %s", me->GetName().c_str(), pTarget->GetName().c_str()); ++ Group* pGroup = pTarget->GetGroup(); ++ if (!pGroup) ++ { ++ if (CureTarget(master, cureSpell, diff)) ++ return; ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ Creature* cre = itr->second; ++ if (!cre || !cre->IsInWorld() || me->GetDistance(cre) > 30) continue; ++ if (CureTarget(cre, cureSpell, diff)) ++ return; ++ } ++ } ++ else ++ { ++ bool Bots = false; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || (tPlayer->isDead() && !tPlayer->HaveBot())) continue; ++ if (!Bots && tPlayer->HaveBot()) ++ Bots = true; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (me->GetMap() != tPlayer->FindMap()) continue; ++ if (me->GetDistance(tPlayer) > 30) continue; ++ if (CureTarget(tPlayer, cureSpell, diff)) ++ return; ++ } ++ if (!Bots) return; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL || !tPlayer->HaveBot()) continue; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (me->GetMap() != tPlayer->FindMap()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre || !cre->IsInWorld() || me->GetDistance(cre) > 30) continue; ++ if (CureTarget(cre, cureSpell, diff)) ++ return; ++ } ++ } ++ } ++} ++ ++bool bot_minion_ai::CureTarget(Unit* target, uint32 cureSpell, uint32 diff) ++{ ++ return _canCureTarget(target, cureSpell, diff) ? doCast(target, cureSpell) : false; ++} ++// determines if unit has something to cure ++bool bot_minion_ai::_canCureTarget(Unit* target, uint32 cureSpell, uint32 diff) const ++{ ++ if (!cureSpell || GC_Timer > diff) return false; ++ if (!target || !target->IsAlive()) return false; ++ if (me->getLevel() < 10 || target->getLevel() < 10) return false; ++ if (me->IsMounted()) return false; ++ if (IsCasting() || Feasting()) return false; ++ if (me->GetDistance(target) > 30) return false; ++ if (!IsInBotParty(target)) return false; ++ ++ SpellInfo const* info = sSpellMgr->GetSpellInfo(cureSpell); ++ if (!info) ++ return false; ++ ++ uint32 dispelMask = 0; ++ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) ++ if (info->Effects[i].Effect == SPELL_EFFECT_DISPEL) ++ dispelMask |= SpellInfo::GetDispelMask(DispelType(info->Effects[i].MiscValue)); ++ ++ if (dispelMask == 0) ++ return false; ++ ++ DispelChargesList dispel_list; ++ _getBotDispellableAuraList(target, me, dispelMask, dispel_list); ++ ++ return !(dispel_list.empty()); ++} ++ ++void bot_minion_ai::_getBotDispellableAuraList(Unit* target, Unit* caster, uint32 dispelMask, DispelChargesList& dispelList) const ++{ ++ if (dispelMask & (1 << DISPEL_DISEASE) && target->HasAura(50536)) ++ dispelMask &= ~(1 << DISPEL_DISEASE); ++ ++ Unit::AuraMap const& auras = target->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) ++ { ++ Aura* aura = itr->second; ++ ++ if (aura->IsPassive()) ++ continue; ++ ++ AuraApplication* aurApp = aura->GetApplicationOfTarget(target->GetGUID()); ++ if (!aurApp) ++ continue; ++ ++ if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) ++ { ++ //do not dispel positive auras from enemies and negative ones from friends ++ if (aurApp->IsPositive() == target->IsFriendlyTo(caster)) ++ continue; ++ ++ //skip Vampiric Touch to prevent being CCed just heal it out ++ if (aura->GetSpellInfo()->IsRankOf(sSpellMgr->GetSpellInfo(34914))) ++ continue; ++ ++ uint8 charges = (aura->GetSpellInfo()->AttributesEx7 & SPELL_ATTR7_DISPEL_CHARGES) ? aura->GetCharges() : aura->GetStackAmount(); ++ if (charges > 0) ++ dispelList.push_back(std::make_pair(aura, charges)); ++ } ++ } ++} ++//protected ++bool bot_ai::HasAuraName(Unit* unit, uint32 spellId, ObjectGuid casterGuid, bool exclude) const ++{ ++ ASSERT(spellId); ++ ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); ++ if (!spellInfo) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::HasAuraName(uint32): no spellInfo found for spell %u!", spellId); ++ ASSERT(false); ++ } ++ ++ uint8 loc = IAmFree() ? sWorld->GetDefaultDbcLocale() : master->GetSession()->GetSessionDbcLocale(); ++ std::string const name = spellInfo->SpellName[loc]; ++ ++ return _hasAuraName(unit, name, casterGuid, exclude); ++} ++//private ++bool bot_ai::_hasAuraName(Unit* unit, std::string const spell, ObjectGuid casterGuid, bool exclude) const ++{ ++ ASSERT(unit); ++ ASSERT(spell.length() != 0); ++ ++ uint8 loc = IAmFree() ? sWorld->GetDefaultDbcLocale() : master->GetSession()->GetSessionDbcLocale(); ++ ++ Unit::AuraMap const& vAuras = unit->GetOwnedAuras(); ++ SpellInfo const* spellInfo; ++ std::string name; ++ ++ for (Unit::AuraMap::const_iterator itr = vAuras.begin(); itr != vAuras.end(); ++itr) ++ { ++ spellInfo = itr->second->GetSpellInfo(); ++ name = spellInfo->SpellName[loc]; ++ if (spell == name) ++ if (!casterGuid || (exclude == (casterGuid != itr->second->GetCasterGUID()))) ++ return true; ++ } ++ ++ return false; ++} ++//LIST AURAS ++// Debug: Returns bot's info to called player ++void bot_ai::_listAuras(Player* player, Unit* unit) const ++{ ++ //if (player->GetSession()->GetSecurity() == SEC_PLAYER) return; ++ if (!player->IsGameMaster() && (IAmFree() || !IsInBotParty(player))) return; ++ if (!IsInBotParty(unit)) return; ++ ChatHandler ch(player->GetSession()); ++ std::ostringstream botstring; ++ if (unit->GetTypeId() == TYPEID_PLAYER) ++ botstring << "player"; ++ else if (unit->GetTypeId() == TYPEID_UNIT) ++ { ++ if (unit->ToCreature()->GetIAmABot()) ++ { ++ botstring << "minion bot, master: "; ++ Player* owner = unit->ToCreature()->GetBotAI()->GetBotOwner(); ++ botstring << (owner != unit ? owner->GetName() : "none"); ++ } ++ else if (unit->ToCreature()->GetIAmABotsPet()) ++ { ++ Player* owner = unit->ToCreature()->GetBotAI()->GetBotOwner(); ++ Creature* creowner = unit->ToCreature()->GetBotPetAI()->GetCreatureOwner(); ++ std::string const& ownername = owner != unit ? owner->GetName() : "none"; ++ std::string const& creownername = creowner ? creowner->GetName() : "none"; ++ botstring << "pet bot, master: "; ++ botstring << ownername; ++ botstring << ", creature owner: "; ++ botstring << creownername; ++ if (creowner) ++ botstring << " (" << creowner->GetGUID().GetCounter() << ')'; ++ } ++ } ++ uint32 const bot_pet_player_class = unit->GetTypeId() == TYPEID_PLAYER ? unit->getClass() : unit->ToCreature()->GetBotAI()->GetBotClass(); ++ ch.PSendSysMessage("ListAuras for %s (class: %u), %s", unit->GetName().c_str(), bot_pet_player_class, botstring.str().c_str()); ++ uint8 locale = player->GetSession()->GetSessionDbcLocale(); ++ Unit::AuraMap const &vAuras = unit->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator itr = vAuras.begin(); itr != vAuras.end(); ++itr) ++ { ++ SpellInfo const* spellInfo = itr->second->GetSpellInfo(); ++ if (!spellInfo) ++ continue; ++ uint32 id = spellInfo->Id; ++ SpellInfo const* learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell); ++ const std::string name = spellInfo->SpellName[locale]; ++ std::ostringstream spellmsg; ++ spellmsg << id << " - |cffffffff|Hspell:" << id << "|h[" << name; ++ spellmsg << ' ' << localeNames[locale] << "]|h|r"; ++ uint32 talentcost = GetTalentSpellCost(id); ++ uint32 rank = 0; ++ if (talentcost > 0 && spellInfo->GetNextRankSpell()) ++ rank = talentcost; ++ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell()) ++ rank = spellInfo->GetRank(); ++ else if (spellInfo->GetNextRankSpell()) ++ rank = spellInfo->GetRank(); ++ if (rank > 0) ++ spellmsg << " Rank " << rank; ++ if (talentcost > 0) ++ spellmsg << " [talent]"; ++ if (spellInfo->IsPassive()) ++ spellmsg << " [passive]"; ++ if ((spellInfo->Attributes & SPELL_ATTR0_HIDDEN_CLIENTSIDE) || ++ (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR)) ++ spellmsg << " [hidden]"; ++ if (unit->GetTypeId() == TYPEID_PLAYER && unit->ToPlayer()->HasSpell(id)) ++ spellmsg << " [known]"; ++ else if (unit == me && GetSpell(spellInfo->GetFirstRankSpell()->Id)) ++ spellmsg << " [known]"; ++ ++ ch.PSendSysMessage(spellmsg.str().c_str()); ++ } ++ for (uint8 i = STAT_STRENGTH; i != MAX_STATS; ++i) ++ { ++ std::string mystat; ++ switch (i) ++ { ++ case STAT_STRENGTH: mystat = "str"; break; ++ case STAT_AGILITY: mystat = "agi"; break; ++ case STAT_STAMINA: mystat = "sta"; break; ++ case STAT_INTELLECT: mystat = "int"; break; ++ case STAT_SPIRIT: mystat = "spi"; break; ++ default: mystat = "unk stat"; break; ++ } ++ float totalstat = unit->GetTotalStatValue(Stats(i)); ++ if (unit == me && IsMinionAI()) ++ { ++ int8 t = -1; ++ switch (i) ++ { ++ case STAT_STRENGTH: t = BOT_ITEM_MOD_STRENGTH; break; ++ case STAT_AGILITY: t = BOT_ITEM_MOD_AGILITY; break; ++ case STAT_STAMINA: t = BOT_ITEM_MOD_STAMINA; break; ++ case STAT_INTELLECT: t = BOT_ITEM_MOD_INTELLECT; break; ++ case STAT_SPIRIT: t = BOT_ITEM_MOD_SPIRIT; break; ++ default: break; ++ } ++ ++ if (t >= BOT_ITEM_MOD_MANA) ++ totalstat += GetMinionAI()->GetTotalBotStat(t); ++ } ++ ch.PSendSysMessage("total %s: %.1f", mystat.c_str(), totalstat); ++ } ++ ch.PSendSysMessage("Melee AP: %.1f", unit->GetTotalAttackPowerValue(BASE_ATTACK)); ++ ch.PSendSysMessage("Ranged AP: %.1f", unit->GetTotalAttackPowerValue(RANGED_ATTACK)); ++ ch.PSendSysMessage("armor: %u", unit->GetArmor()); ++ ch.PSendSysMessage("crit: %.2f pct", unit->GetUnitCriticalChance(BASE_ATTACK, me)); ++ ch.PSendSysMessage("dodge: %.2f pct", unit->GetUnitDodgeChance()); ++ ch.PSendSysMessage("parry: %.2f pct", unit->GetUnitParryChance()); ++ ch.PSendSysMessage("block: %.2f pct", unit->GetUnitBlockChance()); ++ ch.PSendSysMessage("block value: %u", unit->GetShieldBlockValue()); ++ ch.PSendSysMessage("Damage taken melee: %.3f", unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_NORMAL)); ++ ch.PSendSysMessage("Damage taken spell: %.3f", unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_MAGIC)); ++ if (unit != me || ap_mod > 0.0f) ++ { ++ WeaponAttackType type = BASE_ATTACK; ++ float attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; ++ ch.PSendSysMessage("Damage range mainhand: min: %.0f, max: %.0f", unit->GetFloatValue(UNIT_FIELD_MINDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXDAMAGE)); ++ ch.PSendSysMessage("Damage mult mainhand: %.3f", unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT)); ++ ch.PSendSysMessage("Attack time mainhand: %.2f (%.1f DPS)", attSpeed, ++ ((unit->GetFloatValue(UNIT_FIELD_MINDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXDAMAGE)) / 2) * unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT) * unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT) / attSpeed); ++ if (unit->haveOffhandWeapon()) ++ { ++ type = OFF_ATTACK; ++ attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; ++ ch.PSendSysMessage("Damage range offhand: min: %.0f, max: %.0f", unit->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE)); ++ ch.PSendSysMessage("Damage mult offhand: %.3f", unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT)); ++ ch.PSendSysMessage("Attack time offhand: %.2f (%.1f DPS)", attSpeed, ++ ((unit->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE)) / 2) * unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT) * unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT) / attSpeed); ++ } ++ if (unit != me || ++ (me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2) && ++ GetBotClass() != BOT_CLASS_PALADIN && ++ GetBotClass() != BOT_CLASS_DEATH_KNIGHT && ++ GetBotClass() != BOT_CLASS_DRUID && ++ GetBotClass() != BOT_CLASS_SHAMAN)) ++ { ++ type = RANGED_ATTACK; ++ attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; ++ ch.PSendSysMessage("Damage range ranged: min: %.1f, max: %.1f", unit->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE)); ++ ch.PSendSysMessage("Damage mult ranged: %.3f", unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT)); ++ ch.PSendSysMessage("Attack time ranged: %.2f (%.1f DPS)", attSpeed, ++ ((unit->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE)) / 2) * unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT) * unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT) / attSpeed); ++ } ++ } ++ ch.PSendSysMessage("base hp: %u", unit->GetCreateHealth()); ++ ch.PSendSysMessage("total hp: %u", unit->GetMaxHealth()); ++ ch.PSendSysMessage("base mana: %u", unit->GetCreateMana()); ++ ch.PSendSysMessage("total mana: %u", unit->GetMaxPower(POWER_MANA)); ++ if (unit->GetShapeshiftForm() != FORM_NONE && unit->getPowerType() != POWER_MANA) ++ ch.PSendSysMessage("cur mana: %u", unit->GetPower(POWER_MANA)); ++ //DEBUG1 ++ //ch.PSendSysMessage("STATS: "); ++ //ch.PSendSysMessage("Health"); ++ //ch.PSendSysMessage("base value: %f", unit->GetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE)); ++ //ch.PSendSysMessage("base pct: %f", unit->GetModifierValue(UNIT_MOD_HEALTH, BASE_PCT)); ++ //ch.PSendSysMessage("total value: %f", unit->GetModifierValue(UNIT_MOD_HEALTH, TOTAL_VALUE)); ++ //ch.PSendSysMessage("total pct: %f", unit->GetModifierValue(UNIT_MOD_HEALTH, TOTAL_PCT)); ++ //ch.PSendSysMessage("Mana"); ++ //ch.PSendSysMessage("base value: %f", unit->GetModifierValue(UNIT_MOD_MANA, BASE_VALUE)); ++ //ch.PSendSysMessage("base pct: %f", unit->GetModifierValue(UNIT_MOD_MANA, BASE_PCT)); ++ //ch.PSendSysMessage("total value: %f", unit->GetModifierValue(UNIT_MOD_MANA, TOTAL_VALUE)); ++ //ch.PSendSysMessage("total pct: %f", unit->GetModifierValue(UNIT_MOD_MANA, TOTAL_PCT)); ++ //ch.PSendSysMessage("Stamina"); ++ //ch.PSendSysMessage("base value: %f", unit->GetModifierValue(UNIT_MOD_STAT_STAMINA, BASE_VALUE)); ++ //ch.PSendSysMessage("base pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_STAMINA, BASE_PCT)); ++ //ch.PSendSysMessage("total value: %f", unit->GetModifierValue(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE)); ++ //ch.PSendSysMessage("total pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_STAMINA, TOTAL_PCT)); ++ //ch.PSendSysMessage("Intellect"); ++ //ch.PSendSysMessage("base value: %f", unit->GetModifierValue(UNIT_MOD_STAT_INTELLECT, BASE_VALUE)); ++ //ch.PSendSysMessage("base pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_INTELLECT, BASE_PCT)); ++ //ch.PSendSysMessage("total value: %f", unit->GetModifierValue(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE)); ++ //ch.PSendSysMessage("total pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_INTELLECT, TOTAL_PCT)); ++ //ch.PSendSysMessage("Spirit"); ++ //ch.PSendSysMessage("base value: %f", unit->GetModifierValue(UNIT_MOD_STAT_SPIRIT, BASE_VALUE)); ++ //ch.PSendSysMessage("base pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_SPIRIT, BASE_PCT)); ++ //ch.PSendSysMessage("total value: %f", unit->GetModifierValue(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE)); ++ //ch.PSendSysMessage("total pct: %f", unit->GetModifierValue(UNIT_MOD_STAT_SPIRIT, TOTAL_PCT)); ++ //END DEBUG1 ++ if (unit == me) ++ { ++ ch.PSendSysMessage("melee damage mult: %.3f", _mult_dmg_melee); ++ ch.PSendSysMessage("spell damage mult: %.3f", _mult_dmg_spell); ++ ch.PSendSysMessage("healing done mult: %.3f", _mult_healing); ++ ch.PSendSysMessage("spell power: %i", me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC)); ++ ch.PSendSysMessage("mana regen: %.2f", float(regen_mp) + me->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER) * sWorld->getRate(RATE_POWER_MANA) * 0.001f); ++ ch.PSendSysMessage("haste: %s%.2f pct", (haste >= 0 ? "+" : ""), float(haste)); ++ ch.PSendSysMessage("hit: +%.2f pct", hit); ++ ch.PSendSysMessage("expertise: %i (-%.2f pct)", expertise, float(expertise) * 0.25f); ++ ch.PSendSysMessage("spell penetration: %u", spellpen); ++ ++ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) ++ { ++ uint32 curresist = me->GetResistance(SpellSchools(i)); ++ ++ const char* resist = NULL; ++ switch (i) ++ { ++ case 1: resist = "holy"; break; ++ case 2: resist = "fire"; break; ++ case 3: resist = "nature"; break; ++ case 4: resist = "frost"; break; ++ case 5: resist = "shadow"; break; ++ case 6: resist = "arcane"; break; ++ } ++ ch.PSendSysMessage("Resistance %s: %u", resist, curresist); ++ } ++ ch.PSendSysMessage("BotCommandState: %s", m_botCommandState == COMMAND_FOLLOW ? "Follow" : m_botCommandState == COMMAND_ATTACK ? "Attack" : m_botCommandState == COMMAND_STAY ? "Stay" : m_botCommandState == COMMAND_ABANDON ? "Reset" : "none"); ++ if (!IAmFree()) ++ ch.PSendSysMessage("Follow distance: %u", master->GetBotFollowDist()); ++ ++ ch.PSendSysMessage("Boot timer: %i", _bootTimer); ++ ++ //debug ++ //for (uint32 i = 0; i != 148; ++i) ++ //{ ++ // float val = me->GetFloatValue(i); ++ // ch.PSendSysMessage("Float value at %u: %.9f", i, val); ++ //} ++ ++ //ch.PSendSysMessage("healTargetIconFlags: %u", healTargetIconFlags); ++ ++ //ch.PSendSysMessage("Roles:"); ++ //for (uint8 i = BOT_MAX_ROLE; i != BOT_ROLE_NONE; i >>= 1) ++ //{ ++ // if (_roleMask & i) ++ // { ++ // switch (i) ++ // { ++ // case BOT_ROLE_TANK: ++ // ch.PSendSysMessage("BOT_ROLE_TANK"); ++ // break; ++ // case BOT_ROLE_DPS: ++ // ch.PSendSysMessage("BOT_ROLE_DPS"); ++ // break; ++ // case BOT_ROLE_HEAL: ++ // ch.PSendSysMessage("BOT_ROLE_HEAL"); ++ // break; ++ // //case BOT_ROLE_MELEE: ++ // // ch.PSendSysMessage("BOT_ROLE_MELEE"); ++ // // break; ++ // case BOT_ROLE_RANGED: ++ // ch.PSendSysMessage("BOT_ROLE_RANGED"); ++ // break; ++ // } ++ // } ++ //} ++ ++ //ch.PSendSysMessage("Stat bonuses:"); ++ //for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) ++ //{ ++ // int32 val = 0; ++ // uint32 const a = i; ++ // for (uint8 j = 0; j != BOT_INVENTORY_SIZE; ++j) ++ // val += static_cast(_stats[j])[a]; ++ ++ // if (val != 0) ++ // ch.PSendSysMessage("Item mod %u: bonus = %i", i, val); ++ //} ++ } ++} ++//SetStats ++// Health, Armor, Powers, Combat Ratings, and global update setup ++void bot_minion_ai::SetStats(bool force, bool shapeshift) ++{ ++ uint8 myclass = _botclass; ++ uint8 mylevel = std::min(master->getLevel(), 80); ++ if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) ++ myclass = GetBotStance(); ++ if (myclass != DRUID_BEAR_FORM && myclass != DRUID_CAT_FORM && (master->isDead() || (!shouldUpdateStats && !force))) ++ return; ++ /*TC_LOG_ERROR("entities.player", "*etStats(): Updating bot %s, class: %u, race: %u, level %u, master: %s", ++ me->GetName().c_str(), myclass, myrace, mylevel, master->GetName().c_str());*/ ++ ++ switch (me->GetCreatureTemplate()->rank) //TODO: conditions ++ { ++ case CREATURE_ELITE_RARE: mylevel += 1; break; ++ case CREATURE_ELITE_ELITE: mylevel += 2; break; ++ case CREATURE_ELITE_RAREELITE: mylevel += 3; break; ++ default: break; ++ } ++ mylevel = std::min(mylevel, 83); ++ ++ //Do not remove this code under any circumstances! You've been warned. ++ if (myclass == BOT_CLASS_DEATH_KNIGHT) ++ mylevel = std::max(mylevel, 55); ++ ++ //LEVEL ++ if (me->getLevel() != mylevel) ++ { ++ me->SetLevel(mylevel); ++ force = true; //reinit spells/passives/other ++ } ++ if (force) ++ { ++ InitPowers(); ++ InitSpells(); //this must stay before class passives ++ //ApplyPassives(_botclass); ++ ApplyClassPassives(); ++ } ++ ++ //PHASE ++ if (!IsTempBot() && master->GetPhaseMask() != me->GetPhaseMask()) ++ me->SetPhaseMask(master->GetPhaseMask(), true); ++ ++ //INIT STATS ++ //partially receive master's stats and get base class stats, we'll need all this later ++ uint8 tempclass = myclass == uint8(DRUID_BEAR_FORM) || myclass == uint8(DRUID_CAT_FORM) ? uint8(BOT_CLASS_DRUID) : myclass; ++ if (myclass >= BOT_CLASS_NORMAL_END) ++ sObjectMgr->GetPlayerClassLevelInfo(GetPlayerClass(), std::min(mylevel, 80), _classinfo); ++ else ++ sObjectMgr->GetPlayerClassLevelInfo(tempclass, std::min(mylevel, 80), _classinfo); ++ const CreatureBaseStats* const classstats = sObjectMgr->GetCreatureBaseStats(mylevel, me->getClass()); //use creature class ++ ++ if (force) ++ { ++ PlayerLevelInfo info; ++ sObjectMgr->GetPlayerLevelInfo(me->getRace(), GetPlayerClass(), std::min(mylevel, 80), &info); ++ for (uint8 i = STAT_STRENGTH; i != MAX_STATS; i++) ++ me->SetCreateStat(Stats(i), info.stats[i]); ++ } ++ ++ float value; ++ float tempval; ++ ++ //INIT CLASS MODIFIERS ++ switch (myclass) ++ { ++ case BOT_CLASS_WARRIOR: ap_mod = 1.0f; spp_mod = 0.0f; armor_mod = 1.4f; crit_mod = 1.0f; haste_mod = 0.9f; dodge_mod = 0.8f; parry_mod = 1.1f; break; ++ case BOT_CLASS_DEATH_KNIGHT: ap_mod = 1.1f; spp_mod = 0.0f; armor_mod = 1.05f; crit_mod = 0.9f; haste_mod = 1.0f; dodge_mod = 0.7f; parry_mod = 1.25f; break; ++ case BOT_CLASS_PALADIN: ap_mod = 1.3f; spp_mod = 1.2f; armor_mod = 1.2f; crit_mod = 0.8f; haste_mod = 0.85f; dodge_mod = 0.7f; parry_mod = 1.0f; break; ++ case BOT_CLASS_ROGUE: ap_mod = 1.3f; spp_mod = 0.3f; armor_mod = 0.9f; crit_mod = 1.5f; haste_mod = 1.35f; dodge_mod = 1.5f; parry_mod = 0.8f; break; ++ case BOT_CLASS_HUNTER: ap_mod = 1.4f; spp_mod = 0.5f; armor_mod = 1.2f; crit_mod = 1.2f; haste_mod = 1.5f; dodge_mod = 1.1f; parry_mod = 1.15f; break; ++ case BOT_CLASS_SHAMAN: ap_mod = 1.0f; spp_mod = 0.8f; armor_mod = 1.2f; crit_mod = 1.0f; haste_mod = 1.3f; dodge_mod = 1.0f; parry_mod = 0.8f; break; ++ case BOT_CLASS_DRUID: ap_mod = 0.0f; spp_mod = 1.3f; armor_mod = 0.9f; crit_mod = 0.7f; haste_mod = 1.35f; dodge_mod = 0.5f; parry_mod = 0.0f; break; ++ case BOT_CLASS_MAGE: ap_mod = 0.0f; spp_mod = 0.9f; armor_mod = 0.7f; crit_mod = 0.7f; haste_mod = 1.45f; dodge_mod = 0.5f; parry_mod = 0.0f; break; ++ case BOT_CLASS_PRIEST: ap_mod = 0.0f; spp_mod = 1.2f; armor_mod = 0.7f; crit_mod = 0.7f; haste_mod = 1.45f; dodge_mod = 0.5f; parry_mod = 0.0f; break; ++ case BOT_CLASS_WARLOCK: ap_mod = 0.0f; spp_mod = 1.0f; armor_mod = 0.7f; crit_mod = 0.7f; haste_mod = 1.45f; dodge_mod = 0.5f; parry_mod = 0.0f; break; ++ case DRUID_BEAR_FORM: ap_mod = 1.2f; spp_mod = 1.0f; armor_mod = 1.85f; crit_mod = 1.0f; haste_mod = 0.75f; dodge_mod = 1.6f; parry_mod = 0.0f; break; ++ case DRUID_CAT_FORM: ap_mod = 1.5f; spp_mod = 1.0f; armor_mod = 1.2f; crit_mod = 1.5f; haste_mod = 2.25f; dodge_mod = 1.4f; parry_mod = 0.0f; break; ++ ++ case BOT_CLASS_BM: ap_mod = 2.5f; spp_mod = 0.0f; armor_mod = 0.8f; crit_mod = 0.0f; haste_mod = 2.50f; dodge_mod = 0.0f; parry_mod = 0.0f; break; ++ ++ default: ++ TC_LOG_ERROR("entities.player", "minion_ai: *etStats():Init - unknown bot class %u, real class: %u, _botclass: %u", myclass, GetPlayerClass(), _botclass); ++ ap_mod = 0.0f; spp_mod = 0.0f; armor_mod = 0.0f; crit_mod = 0.0f; haste_mod = 0.0f; dodge_mod = 0.0f; parry_mod = 0.0f; break; ++ } ++ ++ //DAMAGE ++ _OnMeleeDamageUpdate(myclass); ++ ++ //ARMOR ++ value = IAmFree() ? classstats->BaseArmor : me->getLevel() * 25; //over9000/2000 at 80 ++ value += 2.f * (me->GetTotalStatValue(STAT_AGILITY) - 18 + _getTotalBotStat(BOT_ITEM_MOD_AGILITY)); ++ value += _getTotalBotStat(BOT_ITEM_MOD_ARMOR); ++ ++ //class-specified ++ if (GetBotStance() == DEATH_KNIGHT_FROST_PRESENCE) ++ armor_mod += 0.6f; ++ if (GetPlayerClass() == BOT_CLASS_DRUID) ++ { ++ armor_mod += 0.1f; ++ if (myclass == DRUID_BEAR_FORM) ++ armor_mod += 0.33f; ++ } ++ ++ value *= armor_mod; ++ me->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, value); ++ me->UpdateArmor(); //buffs will be took in consideration here ++ ++ //RESISTANCES ++ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) ++ { ++ value = IAmFree() ? mylevel + 40 : std::max(int8(mylevel) - 20, 0); ++ value += _getTotalBotStat(BOT_ITEM_MOD_RESIST_HOLY + (i - 1)); ++ me->SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, value); ++ me->UpdateResistances(i); ++ } ++ ++ //DAMAGE TAKEN ++ value = IAmFree() ? 0.65f : 1.f; ++ if (mylevel > 77) ++ value -= ((mylevel - 77) * 0.05f); // +15% dmg reduction at 80 ++ ++ //class-specified ++ //Protector of the Pack ++ if (mylevel >= 45 && myclass == DRUID_BEAR_FORM) ++ value -= 0.12f; ++ ++ dmg_taken = value; ++ ++ //HEALTH ++ _OnHealthUpdate(); ++ ++ //HASTE ++ if (haste) ++ { ++ //unapply old haste ++ for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) ++ me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), false); ++ me->ApplyCastTimePercentMod(float(haste), false); ++ } ++ ++ value = std::max(int32(mylevel) - (IAmFree() ? 60 : 75), 0); //+20%/+5% haste at 80 ++ ++ //25.5 HR = 1% haste at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_HASTE_MELEE_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HASTE_RANGED_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HASTE_SPELL_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HASTE_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_HASTE_MELEE) | (1 << CR_HASTE_RANGED) | (1 << CR_HASTE_SPELL)); ++ value += tempval * (myclass == BOT_CLASS_HUNTER ? _getRatingMultiplier(CR_HASTE_RANGED) : std::max(_getRatingMultiplier(CR_HASTE_MELEE), _getRatingMultiplier(CR_HASTE_SPELL))); ++ //value += (tempval / (25.5f * (mylevel < 11 ? 1.125f : mylevel - 5) / 75.f)); ++ //value += (tempval / (25.5f * (float(mylevel - 5) / 75.f))); ++ ++ value *= haste_mod; ++ ++ if (myclass == DRUID_CAT_FORM) //give cat lots of haste ++ value += (mylevel/16) * 10.f; //or (mylevel/16) (+40...50% haste for cat); ++ if (myclass == CLASS_HUNTER) ++ value += 15.f; //ammo pouch haste bonus 15% for hunters (still applies to all haste types) ++ ++ haste = int32(value); ++ ++ if (haste) ++ { ++ //apply new haste ++ for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) ++ me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), true); ++ me->ApplyCastTimePercentMod(float(haste), true); ++ } ++ ++ //HIT ++ value = IAmFree() ? mylevel / 8 : mylevel / 16; // 10%/5% at 80 ++ ++ //32.5 HR = 1% hit at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_HIT_MELEE_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HIT_RANGED_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HIT_SPELL_RATING) + _getTotalBotStat(BOT_ITEM_MOD_HIT_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_HIT_MELEE) | (1 << CR_HIT_RANGED) | (1 << CR_HIT_SPELL)); ++ value += tempval * (myclass == BOT_CLASS_HUNTER ? _getRatingMultiplier(CR_HIT_RANGED) : std::max(_getRatingMultiplier(CR_HIT_MELEE), _getRatingMultiplier(CR_HIT_SPELL))); ++ //value += (tempval / (32.5f * (mylevel < 11 ? 0.72f : mylevel - 8) / 72.f)); ++ //value += (tempval / (32.5f * (float(mylevel - 5) / 75.f))); ++ ++ hit = value; ++ ++ //EXPERTISE ++ if (IsMelee()) ++ { ++ value = IAmFree() ? mylevel / 2 : mylevel / 20; //-10%/-1% dodge/parry at 80 ++ ++ //~8.0 ER = 1 expertise at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_EXPERTISE_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_EXPERTISE)); ++ value += tempval * _getRatingMultiplier(CR_EXPERTISE); ++ //value += (tempval / (8.0f * (mylevel < 11 ? 0.9f : mylevel - 8) / 72.f)); ++ //value += (tempval / (8.0f * (float(mylevel - 5) / 75.f))); ++ ++ expertise = value; ++ } ++ ++ //CRIT ++ if (crit_mod > 0.0f) ++ { ++ value = IAmFree() ? mylevel / 4 : mylevel / 16; //+20%/+5% at 80 ++ ++ if (GtChanceToMeleeCritEntry const* critRatio = sGtChanceToMeleeCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1)) ++ value += (me->GetTotalStatValue(STAT_AGILITY) - 18 + _getTotalBotStat(BOT_ITEM_MOD_AGILITY)) * critRatio->ratio * 100.0f; ++ ++ //crit from intellect ++ if (GtChanceToSpellCritEntry const* critRatio = sGtChanceToSpellCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1)) ++ value += (me->GetTotalStatValue(STAT_INTELLECT) - 18 + _getTotalBotStat(BOT_ITEM_MOD_INTELLECT)) * critRatio->ratio * 100.f; ++ ++ //45 CR = 1% crit at 80 ++ float tempval = _getTotalBotStat(BOT_ITEM_MOD_CRIT_MELEE_RATING) + _getTotalBotStat(BOT_ITEM_MOD_CRIT_RANGED_RATING) + _getTotalBotStat(BOT_ITEM_MOD_CRIT_SPELL_RATING) + _getTotalBotStat(BOT_ITEM_MOD_CRIT_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_CRIT_MELEE) | (1 << CR_CRIT_RANGED) | (1 << CR_CRIT_SPELL)); ++ value += tempval * (myclass == BOT_CLASS_HUNTER ? _getRatingMultiplier(CR_CRIT_RANGED) : std::max(_getRatingMultiplier(CR_CRIT_MELEE), _getRatingMultiplier(CR_CRIT_SPELL))); ++ //value += (tempval / (45.f * (mylevel < 11 ? 0.8f : mylevel - 8) / 72.f)); ++ //value += (tempval / (45.f * (float(mylevel - 5) / 75.f))); ++ ++ crit = value * crit_mod; ++ } ++ ++ //PARRY ++ if (parry_mod > 0.0f) ++ { ++ value = 5.0f + (IAmFree() ? mylevel / 8 : mylevel / 16); //+10%/+5% at 80 ++ ++ if (mylevel >= 10) ++ { ++ //67 PR = 1% parry at 80 ++ float tempval = _getTotalBotStat(BOT_ITEM_MOD_PARRY_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_PARRY)); ++ value += tempval * _getRatingMultiplier(CR_PARRY); ++ //value += (tempval / (67.f * (mylevel < 35 ? 5.5f : mylevel - 25) / 55.f)); ++ //value += (tempval / (67.f * (float(mylevel - 5) / 75.f))); ++ //125 DR = 1% block/parry/dodge at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_DEFENSE_SKILL_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DEFENSE_SKILL)); ++ value += tempval * _getRatingMultiplier(CR_DEFENSE_SKILL) * 0.04f; ++ value += me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SKILL, SKILL_DEFENSE) * 0.04f; ++ //value += (tempval / (125.f * (mylevel < 35 ? 8.25f : mylevel - 25) / 55.f)); ++ //value += (tempval / (125.f * (float(mylevel - 5) / 75.f))); ++ } ++ ++ //if (IsTank()) //tanking bonus ++ // value += 5.f; ++ ++ //Forceful Deflection: 25% of strength goes to parry rating (~1% parry per 67 rating at 80) ++ if (myclass == BOT_CLASS_DEATH_KNIGHT/* && mylevel >= 55*/) ++ value += ((me->GetTotalStatValue(STAT_STRENGTH) - 18 + _getTotalBotStat(BOT_ITEM_MOD_STRENGTH)) / 4.f) / (float(mylevel) - (13.f / (float(mylevel - 40) / 40.f))); //~20 at 55, ~34 at 60 and 67 at 80 ++ ++ parry = value * parry_mod; ++ } ++ ++ //DODGE ++ if (dodge_mod > 0.0f) ++ { ++ value = 5.0f + (IAmFree() ? mylevel / 8 : mylevel / 16); //+10%/+5% at 80 ++ ++ if (GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1)) ++ value += (me->GetTotalStatValue(STAT_AGILITY) - 18 + _getTotalBotStat(BOT_ITEM_MOD_AGILITY)) * dodgeRatio->ratio * 100.0f; ++ ++ if (mylevel >= 10) ++ { ++ //53 DR = 1% dodge at 80 ++ float tempval = _getTotalBotStat(BOT_ITEM_MOD_DODGE_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DODGE)); ++ value += tempval * _getRatingMultiplier(CR_DODGE); ++ //value += (tempval / (53.f * (mylevel < 35 ? 7.15f : mylevel - 25) / 55.f)); ++ //value += (tempval / (53.f * (float(mylevel - 5) / 75.f))); ++ //125 DR = 1% block/parry/dodge at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_DEFENSE_SKILL_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DEFENSE_SKILL)); ++ value += tempval * _getRatingMultiplier(CR_DEFENSE_SKILL) * 0.04f; ++ value += me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SKILL, SKILL_DEFENSE) * 0.04f; ++ //value += (tempval / (125.f * (mylevel < 35 ? 8.25f : mylevel - 25) / 55.f)); ++ //value += (tempval / (125.f * (float(mylevel - 5) / 75.f))); ++ } ++ ++ //if (IsTank()) ++ // value += 5.f; ++ ++ dodge = value * dodge_mod; ++ } ++ ++ //BLOCK ++ if (!(me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) ++ { ++ value = 5.0f + (IAmFree() ? mylevel / 4 : mylevel/ 16); //+20%/+5% at 80 ++ ++ if (mylevel >= 10) ++ { ++ //16.5 BR = 1% block at 80 ++ float tempval = _getTotalBotStat(BOT_ITEM_MOD_BLOCK_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_BLOCK)); ++ value += tempval * _getRatingMultiplier(CR_BLOCK); ++ //value += (tempval / (16.5f * (mylevel < 35 ? 8.25f : mylevel - 25) / 55.f)); ++ //value += (tempval / (16.5f * (float(mylevel - 5) / 75.f))); ++ //125 DR = 1% block/parry/dodge at 80 ++ tempval = _getTotalBotStat(BOT_ITEM_MOD_DEFENSE_SKILL_RATING); ++ tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DEFENSE_SKILL)); ++ value += tempval * _getRatingMultiplier(CR_DEFENSE_SKILL) * 0.04f; ++ value += me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SKILL, SKILL_DEFENSE) * 0.04f; ++ //value += (tempval / (125.f * (mylevel < 35 ? 8.25f : mylevel - 25) / 55.f)); ++ //value += (tempval / (125.f * (float(mylevel - 5) / 75.f))); ++ ++ value += IsTank() * 10; //tank bonus ++ } ++ ++ block = std::min(value, 75.0f); ++ ++ //BLOCK VALUE ++ value = float(mylevel + (mylevel >> 2)); //100 at 80 ++ value += 0.5f * (me->GetTotalStatValue(STAT_STRENGTH) - 18); ++ value += 0.5f *_getTotalBotStat(BOT_ITEM_MOD_STRENGTH); ++ value += _getTotalBotStat(BOT_ITEM_MOD_BLOCK_VALUE); ++ ++ //Shield Mastery ++ if (mylevel >= 20 && myclass == BOT_CLASS_WARRIOR) ++ value *= 1.3f; ++ //Redoubt ++ if (mylevel >= 45 && myclass == BOT_CLASS_PALADIN) ++ value *= 1.3f; ++ ++ blockvalue = std::max(int32(value) - 10, 1); ++ } ++ ++ //MANA ++ _OnManaUpdate(shapeshift); ++ ++ //MANA REGEN ++ if (me->GetMaxPower(POWER_MANA) > 1) ++ { ++ value = IAmFree() ? mylevel * 5 : 0; //400/0 at 80 ++ value += _getTotalBotStat(BOT_ITEM_MOD_MANA_REGENERATION); ++ //regen from spirit: 15 base, 1 per 5 spirit ++ value += 15.f + 0.2f * (me->GetTotalStatValue(STAT_SPIRIT) - 18 + _getTotalBotStat(BOT_ITEM_MOD_SPIRIT)); ++ //hunters just spend all mana in no time ++ //if (myclass == BOT_CLASS_HUNTER && mylevel > 20) ++ // value += float((mylevel - 20) * 5); //300 ++ if (myclass >= BOT_CLASS_EX_START) ++ { ++ if (myclass == BOT_CLASS_BM) ++ value = std::max(value / 20, 1); //0.2 per sec ++ } ++ ++ //regen from intellect: 1 int = 0.01 mana per second = 0.05 mp5 ++ value += 0.05f * (me->GetTotalStatValue(STAT_INTELLECT) - 18 + _getTotalBotStat(BOT_ITEM_MOD_INTELLECT)); ++ ++ regen_mp = uint32(value); ++ } ++ ++ //SPELL PENETRATION ++ value = IAmFree() ? mylevel : std::max((int8(mylevel) - 20), 0) / 3; //80/20 at 80 ++ //~1 SPPR = 1 spell penetration ++ value += _getTotalBotStat(BOT_ITEM_MOD_SPELL_PENETRATION); ++ spellpen = uint32(value); ++ ++ //SPELL POWER ++ if (spp_mod > 0.f) ++ { ++ value = IAmFree() ? std::max((int8(mylevel) - 30) * 80, 0) : std::max((int8(mylevel) - 30) * 3, 0); //+4000spp/+150spp at 80 ++ value += _getTotalBotStat(BOT_ITEM_MOD_SPELL_POWER); ++ ++ //class-specified mods ++ if (myclass == BOT_CLASS_PALADIN && mylevel >= 50) ++ { ++ //Touched by the Light / Sheath of Light - 60% of strength (30% attack power) to spell power ++ if (HasRole(BOT_ROLE_TANK | BOT_ROLE_DPS)) ++ value += 0.3f * me->GetTotalAttackPowerValue(BASE_ATTACK); ++ //Holy Guidance - 20% Intellect to spell power ++ else if (HasRole(BOT_ROLE_HEAL)) ++ value += 0.2f * me->GetTotalStatValue(STAT_INTELLECT); ++ } ++ if (myclass == BOT_CLASS_PRIEST && mylevel >= 55) ++ { ++ //Spiritual Guidance - 25% Spirit to spell power ++ if (HasRole(BOT_ROLE_HEAL)) ++ value += 0.25f * me->GetTotalStatValue(STAT_SPIRIT); ++ //Twisted Faith - 20% Spirit to spell power ++ else if (HasRole(BOT_ROLE_DPS)) ++ value += 0.2f * me->GetTotalStatValue(STAT_SPIRIT); ++ } ++ if (myclass == BOT_CLASS_SHAMAN && mylevel >= 50) ++ { ++ //Mental Quickness - 30% attack power to spell power (only enhancement) ++ if (HasRole(BOT_ROLE_DPS) && !HasRole(BOT_ROLE_RANGED)) ++ value += 0.3f * me->GetTotalAttackPowerValue(BASE_ATTACK); ++ } ++ if (myclass == BOT_CLASS_DRUID && mylevel >= 30) ++ { ++ //Lunar Guidance - 12% Intellect to spell power (balance and resto possible) ++ if (HasRole(BOT_ROLE_DPS | BOT_ROLE_HEAL)) ++ value += 0.12f * me->GetTotalStatValue(STAT_INTELLECT); ++ } ++ if (myclass == BOT_CLASS_MAGE && mylevel >= 45) ++ { ++ //Mind Mastery - 15% Intellect to spell power ++ //if (HasRole(BOT_ROLE_DPS)) ++ value += 0.15f * me->GetTotalStatValue(STAT_INTELLECT); ++ } ++ ++ spellpower = uint32(value * spp_mod); ++ } ++ ++ //if init ++ if (force) ++ { ++ me->SetFullHealth(); ++ me->SetPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); ++ } ++ ++ //SetStats for pet ++ if (Creature* pet = me->GetBotsPet()) ++ if (bot_pet_ai* petai = pet->GetBotPetAI()) ++ petai->SetStats(force); ++ ++ shouldUpdateStats = false; ++} ++ ++void bot_pet_ai::SetStats(bool force, bool /*unk*/) ++{ ++ uint8 mylevel = m_creatureOwner->getLevel(); ++ uint8 petType = GetPetType(me); ++ if (petType == PET_TYPE_NONE || petType >= MAX_PET_TYPES) return; ++ if (!shouldUpdateStats && !force) return; ++ //TC_LOG_ERROR("entities.player", "*etStats(): Updating pet bot %s, type: %u, level %u, owner: %s, master: %s", me->GetName().c_str(), petType, mylevel, m_creatureOwner->GetName().c_str(), master->GetName().c_str()); ++ ++ //LEVEL ++ if (me->getLevel() != mylevel) ++ { ++ me->SetLevel(mylevel); ++ force = true; //restore powers on lvl update ++ } ++ if (force) ++ { ++ InitPowers(); ++ InitSpells(); ++ //ApplyPassives(_botclass); ++ ApplyClassPassives(); ++ } ++ ++ //PHASE ++ if (master->GetPhaseMask() != me->GetPhaseMask()) ++ me->SetPhaseMask(master->GetPhaseMask(), true); ++ ++ ////INIT STATS ++ if (force) ++ for (uint8 i = STAT_STRENGTH; i != MAX_STATS; i++) ++ me->SetCreateStat(Stats(i), 0.5f * m_creatureOwner->GetCreateStat(Stats(i))); ++ ++ //INIT CLASS MODIFIERS ++ //STAT -- 'mod' -- used stat values to apply ++ //WARLOCK ++ //Stamina x0.3 -- health ++ //Armor x0.35 -- armor ++ //Int x0.3 -- crit/mana ++ //Spd x0.15 -- spd (if has mana) ++ //AP x0.57 -- attack power (if melee pet) ++ //Resist x0.4 -- resistances ++ //MAGE ++ // ++ //SHAMAN ++ // ++ //HUNTER ++ // ++ ++ switch (petType) ++ { ++ case PET_TYPE_VOIDWALKER: ap_mod = 0.57f; spp_mod = 0.15f; crit_mod = 1.0f; break; ++ //case PET_TYPE_FELHUNTER: ap_mod = 0.57f; spp_mod = 0.15f; crit_mod = 1.0f; break;//NYI ++ //case PET_TYPE_FELGUARD: ap_mod = 0.57f; spp_mod = 0.15f; crit_mod = 1.0f; break;//NYI ++ //case PET_TYPE_SUCCUBUS: ap_mod = 0.57f; spp_mod = 0.15f; crit_mod = 1.0f; break;//NYI ++ //case PET_TYPE_IMP: ap_mod = 0.f; spp_mod = 0.15f; crit_mod = 1.0f; break;//NYI ++ ++ //case PET_TYPE_WATER_ELEMENTAL: ap_mod = 0.0f; spp_mod = 0.0f; crit_mod = 0.0f; break;//NYI ++ ++ //case PET_TYPE_FIRE_ELEMENTAL: ap_mod = 0.0f; spp_mod = 0.0f; crit_mod = 0.0f; break;//NYI ++ //case PET_TYPE_EARTH_ELEMENTAL: ap_mod = 0.0f; spp_mod = 0.0f; crit_mod = 0.0f; break;//NYI ++ ++ //case PET_TYPE_VULTURE: ap_mod = 0.9f; spp_mod = 1.0f; crit_mod = 1.2f; break;//NYI ++ default: ++ TC_LOG_ERROR("entities.player", "pet_ai: *etStats():Init - unknown pet type %u", petType); ++ ap_mod = 0.0f; spp_mod = 0.0f; crit_mod = 0.0f; break; ++ } ++ ++ //DAMAGE ++ if (ap_mod > 0.f)//do not bother casters ++ { ++ switch (m_creatureOwner->GetBotClass()) ++ { ++ case BOT_CLASS_WARLOCK: ++ value = float(m_creatureOwner->GetBotAI()->GetBotSpellPower()); ++ break; ++ case BOT_CLASS_DEATH_KNIGHT: ++ value = m_creatureOwner->GetTotalAttackPowerValue(BASE_ATTACK); ++ break; ++ case BOT_CLASS_HUNTER: ++ value = m_creatureOwner->GetTotalAttackPowerValue(RANGED_ATTACK); ++ break; ++ default: //some weird class or NYI ++ TC_LOG_ERROR("entities.player", "*etStats():Damage - unknown bot owner class %u", uint8(m_creatureOwner->GetBotClass())); ++ value = 0.0f; ++ break; ++ } ++ ++ me->SetModifierValue(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, me->GetCreateStat(STAT_STRENGTH) - 9.f); ++ atpower = (me->GetTotalAuraModValue(UNIT_MOD_STAT_STRENGTH) * 2.f + value) * ap_mod; ++ me->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, atpower); ++ me->UpdateAttackPowerAndDamage(); ++ } ++ ++ //ARMOR ++ value = float(basearmor); ++ //get minion's armor and give 35% to pet (just as for real pets) ++ value += m_creatureOwner->GetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE) * 0.35f; ++ me->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, value); ++ me->UpdateArmor(); ++ ++ //RESISTANCES ++ //based on minion's resistances gain x0.4 ++ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) ++ { ++ value = float(m_creatureOwner->GetResistance(SpellSchools(i))); ++ me->SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, 0.4f * value); ++ me->UpdateResistances(i); ++ } ++ ++ //DAMAGE TAKEN ++ dmg_taken = m_creatureOwner->GetBotAI()->GetBotDamageTakenMod(); ++ ++ //HEALTH ++ _OnHealthUpdate(); ++ ++ //HASTE ++ if (haste) ++ { ++ for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) ++ me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), false); ++ me->ApplyCastTimePercentMod(float(haste), false); ++ } ++ ++ haste = m_creatureOwner->GetBotAI()->GetHaste(); ++ ++ if (haste) ++ { ++ for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) ++ me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), true); ++ me->ApplyCastTimePercentMod(float(haste), true); ++ } ++ ++ //HIT ++ hit = m_creatureOwner->GetBotAI()->GetHitRating(); ++ ++ //CRIT ++ if (CanCrit()) ++ { ++ value = m_creatureOwner->GetUnitCriticalChance((BASE_ATTACK), me); ++ crit = value * crit_mod; ++ } ++ ++ //PARRY ++ if (CanParry()) ++ { ++ value = m_creatureOwner->GetUnitParryChance(); ++ parry = value; ++ } ++ ++ //DODGE ++ if (CanDodge()) ++ { ++ value = m_creatureOwner->GetUnitDodgeChance(); ++ value += IsTank() * 10; ++ dodge = value; ++ } ++ ++ //MANA ++ _OnManaUpdate(false); ++ ++ //MANA REGEN ++ regen_mp = m_creatureOwner->GetBotAI()->GetManaRegen(); ++ ++ //SPELL PENETRATION ++ spellpen = m_creatureOwner->GetBotAI()->GetBotSpellPenetration(); ++ ++ //SPELL POWER ++ if (spp_mod > 0.f) ++ { ++ switch (m_creatureOwner->GetBotClass()) ++ { ++ case BOT_CLASS_WARLOCK: ++ value = m_creatureOwner->GetBotAI()->GetBotSpellPower(); ++ break; ++ case BOT_CLASS_DEATH_KNIGHT: ++ value = m_creatureOwner->GetTotalAttackPowerValue(BASE_ATTACK); ++ break; ++ case BOT_CLASS_HUNTER: ++ value = m_creatureOwner->GetTotalAttackPowerValue(RANGED_ATTACK); ++ break; ++ default: //some weird class or NYI ++ TC_LOG_ERROR("entities.player", "*etStats():Spellpower - unknown bot owner class %u", uint8(m_creatureOwner->GetBotClass())); ++ value = 0.f; ++ break; ++ } ++ ++ spellpower = uint32(value * spp_mod); ++ } ++ ++ if (force) ++ { ++ me->SetFullHealth(); ++ me->SetPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); ++ } ++ ++ shouldUpdateStats = false; ++} ++//Emotion-based action ++void bot_ai::ReceiveEmote(Player* player, uint32 emote) ++{ ++ switch (emote) ++ { ++ case TEXT_EMOTE_BONK: ++ _listAuras(player, me); ++ break; ++ case TEXT_EMOTE_SALUTE: ++ _listAuras(player, player); ++ break; ++ case TEXT_EMOTE_STAND: ++ if (!IsMinionAI()) ++ return; ++ if (master != player) ++ { ++ me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE); ++ return; ++ } ++ SetBotCommandState(COMMAND_STAY); ++ BotWhisper("Standing Still.", player); ++ break; ++ case TEXT_EMOTE_WAVE: ++ if (!IsMinionAI()) ++ return; ++ if (master != player) ++ { ++ me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE); ++ return; ++ } ++ SetBotCommandState(COMMAND_FOLLOW, true); ++ BotWhisper("Following!", player); ++ break; ++ default: ++ break; ++ } ++} ++ ++//ISINBOTPARTY ++//Returns group members (and their npcbots too) ++//For now all your puppets are in your group automatically ++bool bot_ai::IsInBotParty(Unit const* unit) const ++{ ++ if (!unit) return false; ++ if (unit == me || unit == master) return true; ++ ++ if (IAmFree()) ++ { ++ if (unit == me->GetBotsPet()) ++ return true; ++ ++ Player const* owner = NULL; ++ ++ Creature const* bot = unit->ToCreature(); ++ if (bot) ++ { ++ //controlled bot case ++ if (bot->GetBotAI() && !bot->IsFreeBot()) ++ owner = bot->GetBotOwner(); ++ ++ //free bot / neutral case ++ if (bot->getFaction() == me->getFaction()) ++ return true; ++ } ++ ++ if (!owner) ++ owner = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); ++ if (owner && (owner->getFaction() == me->getFaction() || me->GetReactionTo(owner) >= REP_FRIENDLY)) ++ return true; ++ ++ if (unit->GetCharmerOrOwnerGUID() == me->GetGUID()) ++ return true; ++ ++ return false; ++ } ++ ++ //cheap check ++ if (Group* gr = master->GetGroup()) ++ { ++ //group member case ++ if (gr->IsMember(unit->GetGUID())) ++ return true; ++ //pointed target case ++ for (uint8 i = 0; i != TARGETICONCOUNT; ++i) ++ if (_healTargetIconFlags & GroupIconsFlags[i]) ++ if (ObjectGuid guid = gr->GetTargetIcons()[i])//check this one ++ if (guid == unit->GetGUID()) ++ if (unit->GetReactionTo(master) >= REP_NEUTRAL && ++ master->GetVictim() != unit && ++ unit->GetVictim() != master) ++ return true; ++ } ++ ++ //Player-controlled creature case ++ if (Creature const* cre = unit->ToCreature()) ++ { ++ //npcbot/npcbot's pet case ++ if (Player* owner = cre->GetBotOwner()) ++ { ++ if (owner == master) ++ return true; ++ } ++ //pets, minions, guardians etc. ++ else ++ { ++ ObjectGuid ownerGuid = unit->GetOwnerGUID(); ++ //controlled by group member ++ if (Group* gr = master->GetGroup()) ++ if (gr->IsMember(ownerGuid)) ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++//REFRESHAURA ++//Applies/reapplies aura stacks ++bool bot_ai::RefreshAura(uint32 spellId, int8 count) const ++{ ++ if (!spellId) ++ return false; ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); ++ if (!spellInfo) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::RefreshAura(): Invalid spellInfo for spell %u! Bot - %s (botclass: %u, entry: %u)", ++ spellId, me->GetName().c_str(), _botclass, me->GetEntry()); ++ return false; ++ } ++ ++ if (me->HasAura(spellId)) ++ me->RemoveAurasDueToSpell(spellId); ++ ++ for (int8 i = 0; i < count; ++i) ++ me->AddAura(spellInfo, MAX_EFFECT_MASK, me); ++ ++ return true; ++} ++//CHECKAURAS ++//Updates bot's condition once a while ++void bot_minion_ai::CheckAuras(bool force) ++{ ++ opponent = me->GetVictim(); //safe ++ ++ if (!force) ++ { ++ Regenerate(); ++ _updateRations(); //safe ++ } ++ ++ if (checkAurasTimer == 0) ++ { ++ checkAurasTimer = 10 + (IAmFree() ? 5 : master->GetNpcBotsCount() / 2); ++ ++ if (needparty) ++ { ++ needparty = false; ++ if (!IAmFree()) //we could lose master ++ master->GetBotMgr()->AddBotToGroup(me); ++ } ++ ++ if (_bootTimer == 0) ++ { ++ //timer will be cancelled at bot removal so we are always free here ++ //_bootTimer = -1; //Set in AbortTeleport() ++ master->m_Controlled.erase(me); ++ BotMgr::TeleportBot(me, master->GetMap(), master); ++ return; ++ } ++ ++ if (m_botCommandState != COMMAND_FOLLOW && m_botCommandState != COMMAND_STAY && opponent && !CCed(me, true)) ++ { ++ if (IsMelee()) ++ { ++ if (me->GetDistance(opponent) > 1.5f) ++ GetInPosition(true); ++ } ++ else ++ { ++ CalculateAttackPos(opponent, attackpos); ++ if (me->GetDistance(attackpos) > 8) ++ GetInPosition(true, opponent, &attackpos); ++ } ++ } ++ if (shouldUpdateStats && me->GetPhaseMask() == master->GetPhaseMask()) ++ SetStats(false); ++ else if (!_powersTimer) ++ { ++ _powersTimer = 2000; ++ UpdateHealth(); ++ UpdateMana(); ++ } ++ if (rezz_cd > 0) ++ --rezz_cd; ++ if (clear_cd > 0) ++ --clear_cd; ++ else ++ clear_cd = 15; ++ if (_atHome && Rand() < 10) ++ _atHome = false; ++ return; ++ } ++ else if (force) ++ { ++ if (!opponent && !IAmFree()) ++ { ++ if (master->isDead()) ++ { ++ //If ghost move to corpse, else move to dead player ++ if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ++ { ++ Corpse* corpse = master->GetCorpse(); ++ if (corpse && me->GetMap() == corpse->FindMap() && !me->IsInCombat() && !me->HasUnitState(UNIT_STATE_MOVING) && !IsCasting() && !CCed(me) && me->GetDistance(corpse) > 5) ++ me->GetMotionMaster()->MovePoint(corpse->GetMapId(), *corpse); ++ } ++ else ++ { ++ if (m_botCommandState != COMMAND_FOLLOW || me->GetDistance(master) > 30 - 20 * (!me->IsWithinLOSInMap(master))) ++ Follow(true); ++ } ++ } ++ else if (m_botCommandState != COMMAND_STAY && !IsCasting()) ++ { ++ _calculatePos(pos); ++ uint8 followdist = master->GetBotFollowDist(); ++ if (me->GetExactDist(&pos) > (followdist > 8 ? 4 + followdist/2*(!master->isMoving()) : 8)) ++ Follow(true, &pos); //check if doing nothing ++ } ++ } ++ if (!IsCasting()) ++ { ++ if (me->IsInCombat() || !CanSheath()) ++ { ++ if (!me->IsStandState()) ++ { ++ if (_botclass == BOT_CLASS_HUNTER) ++ { ++ if (me->GetSheath() != SHEATH_STATE_RANGED) ++ me->SetSheath(SHEATH_STATE_RANGED); ++ } ++ else if (me->GetSheath() != SHEATH_STATE_MELEE) ++ me->SetSheath(SHEATH_STATE_MELEE); ++ } ++ } ++ else if (me->IsStandState() && me->GetSheath() != SHEATH_STATE_UNARMED && Rand() < 50) ++ { ++ me->SetSheath(SHEATH_STATE_UNARMED); ++ if (_botclass == BOT_CLASS_HUNTER) ++ me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER); ++ } ++ } ++ ++ _updateMountedState(); ++ _updateStandState(); ++ ++ //update flags ++ if (!me->IsInCombat() && !_evadeMode && _atHome && !me->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP)) ++ me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); ++ } ++} ++void bot_pet_ai::CheckAuras(bool /*force*/) ++{ ++ Regenerate(); ++ ++ if (checkAurasTimer > 0) return; ++ checkAurasTimer = 10 + IAmFree() ? 5 : master->GetNpcBotsCount() / 2; ++ ++ if (m_botCommandState != COMMAND_FOLLOW && m_botCommandState != COMMAND_STAY) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ switch (GetPetType(me)) ++ { ++ case PET_TYPE_IMP: ++ CalculateAttackPos(opponent, attackpos); ++ if (me->GetDistance(attackpos) > 8) ++ GetInPosition(true, opponent, &attackpos); ++ break; ++ default: ++ if (me->GetDistance(opponent) > 1.5f) ++ GetInPosition(true); ++ break; ++ } ++ } ++ } ++ if (clear_cd > 0) ++ --clear_cd; ++ else ++ clear_cd = 15; ++ ++ return; ++} ++ ++bool bot_ai::CanBotAttack(Unit const* target, int8 byspell) const ++{ ++ if (!target) ++ return false; ++ if (!_botPvP && !IAmFree() && target->IsControlledByPlayer()) ++ return false; ++ uint8 followdist = IAmFree() ? 100 : master->GetBotFollowDist(); ++ float foldist = _getAttackDistance(float(followdist)); ++ return ++ (target->IsAlive() && ++ target->IsVisible() && ++ //me->IsValidAttackTarget(target) && ++ ((me->CanSeeOrDetect(target) && target->InSamePhase(me)) || CanSeeEveryone()) && ++ //!target->HasStealthAura() && !target->HasInvisibilityAura() && ++ (master->isDead() || target->GetTypeId() == TYPEID_PLAYER || target->IsPet() || ++ (target->GetDistance(master) < foldist && me->GetDistance(master) < followdist)) &&//if master is killed pursue to the end ++ target->isTargetableForAttack() && ++ !IsInBotParty(target) && ++ (target->IsHostileTo(master) || ++ (target->GetReactionTo(master) < REP_FRIENDLY/* && master->GetVictim() == target*/ && (master->IsInCombat() || target->IsInCombat())) ||//master has pointed this target ++ target->IsHostileTo(me)) &&//if master is controlled ++ //target->IsWithinLOSInMap(me) && ++ (byspell == -1 || !target->IsImmunedToDamage(byspell ? SPELL_SCHOOL_MASK_MAGIC : SPELL_SCHOOL_MASK_NORMAL))); ++} ++//GETTARGET ++//Returns attack target or 'no target' ++Unit* bot_ai::_getTarget(bool byspell, bool ranged, bool &reset) const ++{ ++ if (_evadeMode) //IAmFree() case only ++ return NULL; ++ ++ Unit* u = master->GetVictim(); ++ Unit* mytar = me->GetVictim(); ++ ++ //check if no need to change target ++ if (!mytar && IsMinionAI()) ++ if (Creature* pet = me->GetBotsPet()) ++ mytar = pet->GetVictim(); ++ if (!mytar && IsPetAI()) ++ mytar = me->GetBotPetAI()->GetCreatureOwner()->GetVictim(); ++ ++ //TC_LOG_ERROR("entities.player", "bot_ai::getTarget(): bot: %s, PvP = %u", me->GetName().c_str(), PvP); ++ ++ if (u && u == mytar && !IAmFree()) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s continues attack common target %s", me->GetName().c_str(), u->GetName().c_str()); ++ return u;//forced ++ } ++ //Follow if... ++ uint8 followdist = IAmFree() ? 100 : master->GetBotFollowDist(); ++ float foldist = _getAttackDistance(float(followdist)); ++ if (!u && master->IsAlive() && (me->GetDistance(master) > foldist || (mytar && master->GetDistance(mytar) > foldist && me->GetDistance(master) > foldist))) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s cannot attack target %s, too far away", me->GetName().c_str(), mytar ? mytar->GetName().c_str() : ""); ++ return NULL; ++ } ++ ++ if (u && !IAmFree() && (master->IsInCombat() || u->IsInCombat()) && !InDuel(u) && !IsInBotParty(u) && !(!_botPvP && !IAmFree() && u->IsControlledByPlayer())) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s starts attack master's target %s", me->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ ++ if (mytar && (!IAmFree() || me->GetDistance(mytar) < BOT_MAX_CHASE_RANGE) && CanBotAttack(mytar, byspell) && !InDuel(mytar)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s continues attack its target %s", me->GetName().c_str(), mytar->GetName().c_str()); ++ if (me->GetDistance(mytar) > (ranged ? 20.f : 5.f) && m_botCommandState != COMMAND_STAY && m_botCommandState != COMMAND_FOLLOW) ++ reset = true; ++ return mytar; ++ } ++ ++ if (followdist == 0 && master->IsAlive()) ++ return NULL; //do not bother ++ ++ //check group ++ if (!IAmFree()) ++ { ++ Group* gr = master->GetGroup(); ++ if (!gr) ++ { ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ Creature* bot = itr->second; ++ if (!bot || !bot->InSamePhase(me) || bot == me) continue; ++ u = bot->GetVictim(); ++ if (u && CanBotAttack(u, byspell) && ++ (bot->IsInCombat() || u->IsInCombat()) && ++ (master->isDead() || master->GetDistance(u) < foldist)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s hooked %s's victim %s", me->GetName().c_str(), bot->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ Creature* pet = bot->GetIAmABot() ? bot->GetBotsPet() : NULL; ++ if (!pet || !pet->InSamePhase(me)) continue; ++ u = pet->GetVictim(); ++ if (u && CanBotAttack(u, byspell) && ++ (pet->IsInCombat() || u->IsInCombat()) && ++ (master->isDead() || master->GetDistance(u) < foldist)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s hooked %s's victim %s", me->GetName().c_str(), pet->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ } ++ } ++ else ++ { ++ for (GroupReference* ref = gr->GetFirstMember(); ref != NULL; ref = ref->next()) ++ { ++ Player* pl = ref->GetSource(); ++ if (!pl || !pl->IsInWorld() || pl->IsBeingTeleported()) continue; ++ if (me->GetMap() != pl->FindMap() || !pl->InSamePhase(me)) continue; ++ u = pl->GetVictim(); ++ if (u && pl != master && CanBotAttack(u, byspell) && ++ (pl->IsInCombat() || u->IsInCombat()) && ++ (master->isDead() || master->GetDistance(u) < foldist)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s hooked %s's victim %s", me->GetName().c_str(), pl->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ if (!pl->HaveBot()) continue; ++ BotMap const* map = pl->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (!bot || !bot->InSamePhase(me) || bot == me) continue; ++ if (!bot->IsInWorld()) continue; ++ if (me->GetMap() != bot->FindMap()) continue; ++ u = bot->GetVictim(); ++ if (u && CanBotAttack(u, byspell) && ++ (bot->IsInCombat() || u->IsInCombat()) && ++ (master->isDead() || master->GetDistance(u) < foldist)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s hooked %s's victim %s", me->GetName().c_str(), bot->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ Creature* pet = bot->GetIAmABot() ? bot->GetBotsPet() : NULL; ++ if (!pet || !pet->InSamePhase(me)) continue; ++ if (!pet->IsInWorld()) continue; ++ if (me->GetMap() != pet->FindMap()) continue; ++ u = pet->GetVictim(); ++ if (u && CanBotAttack(u, byspell) && ++ (pet->IsInCombat() || u->IsInCombat()) && ++ (master->isDead() || master->GetDistance(u) < foldist)) ++ { ++ //TC_LOG_ERROR("entities.player", "bot %s hooked %s's victim %s", me->GetName().c_str(), pet->GetName().c_str(), u->GetName().c_str()); ++ return u; ++ } ++ } ++ } ++ } ++ } ++ ++ //check targets around ++ Unit* t = NULL; ++ float maxdist = InitAttackRange(float(followdist), ranged); ++ //first cycle we search non-cced target, then, if not found, check all ++ for (uint8 i = 0; i != 2; ++i) ++ { ++ if (!t) ++ { ++ bool attackCC = i; ++ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ NearestHostileUnitCheck check(me, maxdist, byspell, this, attackCC); ++ Trinity::UnitLastSearcher searcher(master, t, check); ++ me->VisitNearbyObject(maxdist, searcher); ++ } ++ } ++ ++ if (t && opponent && t != opponent) ++ reset = true; ++ ++ //if (t) ++ // TC_LOG_ERROR("entities.player", "bot %s has Found new target %s", me->GetName().c_str(), t->GetName().c_str()); ++ ++ return t; ++} ++//'CanAttack' function ++bool bot_ai::CheckAttackTarget(uint8 botOrPetType) ++{ ++ if (IsDuringTeleport()/* || _evadeMode*/) ++ { ++ //me->AttackStop(); //already in CombatStop() ++ me->CombatStop(true); ++ return false; ++ } ++ ++ if (IAmFree() && Feasting()) ++ return false; ++ ++ bool byspell = false, ranged = false, reset = false; ++ ++ if (IsMinionAI()) ++ { ++ switch (botOrPetType) ++ { ++ case BOT_CLASS_DRUID: ++ byspell = me->GetShapeshiftForm() == FORM_NONE || ++ me->GetShapeshiftForm() == FORM_TREE || ++ me->GetShapeshiftForm() == FORM_MOONKIN; ++ ranged = byspell; ++ break; ++ case BOT_CLASS_PRIEST: ++ case BOT_CLASS_MAGE: ++ case BOT_CLASS_WARLOCK: ++ case BOT_CLASS_SHAMAN: ++ byspell = true; ++ break; ++ case BOT_CLASS_HUNTER: ++ ranged = true; ++ break; ++ case BOT_CLASS_DEATH_KNIGHT: ++ case BOT_CLASS_PALADIN: ++ case BOT_CLASS_WARRIOR: ++ case BOT_CLASS_ROGUE: ++ case BOT_CLASS_BM: ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - unknown bot class %u", botOrPetType); ++ return false; ++ } ++ } ++ else ++ { ++ switch (botOrPetType) ++ { ++ case PET_TYPE_IMP: ++ byspell = true; ++ ranged = true; ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - unknown pet type %u", botOrPetType); ++ return false; ++ } ++ } ++ ++ opponent = _getTarget(byspell, ranged, reset); ++ ++ if (!opponent) ++ { ++ //TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - bot %s lost target", me->GetName().c_str()); ++ if (me->GetVictim() || me->IsInCombat()/* || !me->getThreatManager().isThreatListEmpty()*/) ++ { ++ //TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - bot %s Evades", me->GetName().c_str()); ++ if (me->GetVictim()) ++ me->AttackStop(); ++ else if (me->IsInCombat()) ++ Evade(true); ++ } ++ ++ return false; ++ } ++ ++ if (reset) ++ m_botCommandState = COMMAND_ABANDON;//reset AttackStart() ++ ++ if (opponent != me->GetVictim()) ++ me->Attack(opponent, !ranged); ++ ++ return true; ++} ++//POSITION ++void bot_ai::CalculateAttackPos(Unit* target, Position& pos) const ++{ ++ uint8 followdist = IAmFree() ? 100 : master->GetBotFollowDist(); ++ float x(0),y(0),z(0), ++ dist = float(6 + urand(followdist/4, followdist/3)), ++ angle = target->GetAngle(me); ++ dist = std::min(dist, 20.f); ++ if (me->GetIAmABotsPet()) ++ dist *= 0.5f; ++ float clockwise = RAND(1.f,-1.f); ++ for (uint8 i = 0; i != 5; ++i) ++ { ++ target->GetNearPoint(me, x, y, z, me->GetObjectSize()/2.f, dist, angle); ++ bool toofaraway = master->GetDistance(x,y,z) > (followdist > 28 ? 28.f : followdist < 20 ? 20.f : float(followdist)); ++ bool outoflos = !target->IsWithinLOS(x,y,z); ++ if (toofaraway || outoflos) ++ { ++ if (toofaraway) ++ angle = target->GetAngle(master) + frand(0.f, M_PI*0.5f) * clockwise; ++ if (outoflos) ++ dist *= 0.5f; ++ } ++ else ++ { ++ dist *= 0.75f; ++ break; ++ } ++ } ++ pos.m_positionX = x; ++ pos.m_positionY = y; ++ pos.m_positionZ = z; ++} ++// Forces bot to chase opponent (if ranged then distance depends on follow distance) ++void bot_ai::GetInPosition(bool force, Unit* newtarget, Position* mypos) ++{ ++ if (CCed(me, true) || JumpingFlyingOrFalling()) ++ return; ++ if (!newtarget) ++ newtarget = me->GetVictim(); ++ if (!newtarget) ++ return; ++ if ((!newtarget->IsInCombat() || m_botCommandState == COMMAND_STAY) && !force) ++ return; ++ if (IsCasting()) ++ return; ++ if (UpdateImpossibleChase(newtarget)) ++ return; ++ bool ranged = !IsMelee(); ++ uint8 followdist = IAmFree() ? 100 : master->GetBotFollowDist(); ++ if (ranged) ++ { ++ if (!force && newtarget->GetTypeId() == TYPEID_PLAYER && ++ me->GetDistance(newtarget) < 6 + urand(followdist/4, followdist/3)) return;//do not allow constant runaway from player ++ if (!mypos) ++ CalculateAttackPos(newtarget, attackpos); ++ else ++ { ++ attackpos.m_positionX = mypos->m_positionX; ++ attackpos.m_positionY = mypos->m_positionY; ++ attackpos.m_positionZ = mypos->m_positionZ; ++ } ++ if (me->GetDistance(attackpos) > (_botclass == BOT_CLASS_HUNTER ? 4 : 8)) ++ me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos); ++ } ++ else if (!me->HasUnitState(UNIT_STATE_CHASE) || !me->HasUnitState(UNIT_STATE_CHASE_MOVE)) ++ me->GetMotionMaster()->MoveChase(newtarget); ++ ++ if (newtarget != me->GetVictim()) ++ me->Attack(newtarget, !ranged); ++} ++ ++void bot_ai::CheckAttackState() ++{ ++ if (me->GetVictim()) ++ { ++ if (HasRole(BOT_ROLE_DPS)) ++ DoMeleeAttackIfReady(); ++ } ++ else ++ Evade(); ++} ++ ++bool bot_ai::MoveBehind(Unit &target) const ++{ ++ if (CCed(me, true)) return false; ++ if (JumpingFlyingOrFalling()) return false; ++ if (target.HasUnitState(UNIT_STATE_CASTING)) return false; ++ if (target.IsWithinCombatRange(me, ATTACK_DISTANCE) && ++ target.HasInArc(M_PI, me) && ++ !IsTank() && ++ (_botclass == BOT_CLASS_ROGUE ? target.GetVictim() != me || CCed(&target) : target.GetVictim() != me && !CCed(&target))) ++ { ++ float x(0),y(0),z(0); ++ target.GetNearPoint(me, x, y, z, me->GetObjectSize()/3, 0.1f, me->GetAngle(&target)); ++ me->GetMotionMaster()->MovePoint(target.GetMapId(), x, y, z); ++ return true; ++ } ++ return false; ++} ++//MOUNT SUPPORT ++void bot_minion_ai::_updateMountedState() ++{ ++ if (IAmFree()) ++ return; ++ if (GetBotCommandState() != COMMAND_FOLLOW) ++ return; ++ ++ bool aura = me->HasAuraType(SPELL_AURA_MOUNTED); ++ bool mounted = me->IsMounted(); ++ ++ //allow dismount ++ if (!CanMount() && !aura && !mounted) ++ return; ++ ++ if ((!master->IsMounted() || aura != mounted || (me->IsInCombat() && opponent)) && (aura || mounted)) ++ { ++ const_cast(me->GetCreatureTemplate())->InhabitType &= ~INHABIT_AIR; ++ me->RemoveAurasByType(SPELL_AURA_MOUNTED); ++ //me->RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER); ++ me->SetCanFly(false); ++ me->SetDisableGravity(false); ++ me->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING); ++ me->Dismount(); ++ return; ++ } ++ if (me->IsInCombat() || IsCasting() || me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) //IsInWater() is too much calculations ++ return; ++ ++ if (master->IsMounted() && !me->IsMounted() && !master->IsInCombat() && !me->IsInCombat() && !me->GetVictim()) ++ { ++ uint32 mount = 0; ++ Unit::AuraEffectList const &mounts = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); ++ if (!mounts.empty()) ++ { ++ //Winter Veil addition ++ if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) ++ mount = master->CanFly() ? REINDEER_FLY : REINDEER; ++ else ++ mount = mounts.front()->GetId(); ++ } ++ if (mount) ++ { ++ if (me->HasAuraType(SPELL_AURA_MOUNTED)) ++ me->RemoveAurasByType(SPELL_AURA_MOUNTED); ++ if (Feasting()) ++ { ++ me->RemoveAurasDueToSpell(DRINK); ++ me->RemoveAurasDueToSpell(EAT); ++ } ++ ++ if (!GetSpell(mount)) ++ InitSpellMap(mount, true); //learn ++ ++ if (doCast(me, mount)) ++ { ++ return; ++ } ++ } ++ } ++} ++//STANDSTATE ++void bot_minion_ai::_updateStandState() const ++{ ++ if (IAmFree()) ++ { ++ //if (!(me->GetInterruptMask() & AURA_INTERRUPT_FLAG_NOT_SEATED) && !me->IsStandState()) ++ // me->SetStandState(UNIT_STAND_STATE_STAND); ++ return; ++ } ++ if (master->getStandState() == UNIT_STAND_STATE_STAND && ++ me->getStandState() == UNIT_STAND_STATE_SIT && ++ !(me->GetInterruptMask() & AURA_INTERRUPT_FLAG_NOT_SEATED)) ++ me->SetStandState(UNIT_STAND_STATE_STAND); ++ if (CanSit() && !me->IsInCombat() && !me->isMoving() && ++ (master->getStandState() == UNIT_STAND_STATE_SIT || (me->GetInterruptMask() & AURA_INTERRUPT_FLAG_NOT_SEATED) || Feasting()) && ++ me->getStandState() == UNIT_STAND_STATE_STAND) ++ me->SetStandState(UNIT_STAND_STATE_SIT); ++} ++//RATIONS ++void bot_minion_ai::_updateRations() ++{ ++ bool noFeast = me->IsInCombat() || CCed(me); ++ if (noFeast || me->IsStandState()) ++ { ++ if (feast_health) ++ { ++ feast_health = false; ++ me->RemoveAurasDueToSpell(EAT); ++ } ++ if (feast_mana) ++ { ++ feast_mana = false; ++ me->RemoveAurasDueToSpell(DRINK); ++ } ++ ++ if (noFeast) ++ return; ++ } ++ ++ //drink ++ if (!feast_mana && me->GetMaxPower(POWER_MANA) > 1 && !me->IsMounted() && !me->isMoving() && CanDrink() && ++ !me->IsInCombat() && !IsCasting() && GetManaPCT(me) < 80 && urand(0, 100) < 20 && ++ !me->HasAura(DRINK)) ++ { ++ feast_mana = true; ++ mana_cd = 0; ++ me->CastSpell(me, DRINK); ++ me->SetStandState(UNIT_STAND_STATE_SIT); ++ } ++ if (feast_mana) ++ { ++ mana_cd += lastdiff; ++ if (mana_cd >= RATIONS_CD && me->GetMaxPower(POWER_MANA) > 1 && me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA) && me->HasAura(DRINK)) ++ { ++ while (mana_cd >= RATIONS_CD) ++ { ++ mana_cd -= RATIONS_CD; ++ //25000 / 1000 = 25: 4% ++ //25000 / 2000 = 12: 8.5% ++ me->ModifyPower(POWER_MANA, me->GetMaxPower(POWER_MANA) / (25000 / RATIONS_CD)); //4% per second if 1000 ++ } ++ } ++ } ++ ++ //eat ++ if (!feast_health && !me->IsMounted() && !me->isMoving() && CanEat() && ++ !me->IsInCombat() && !IsCasting() && GetHealthPCT(me) < 80 && urand(0, 100) < 20 && ++ !me->HasAura(EAT)) ++ { ++ feast_health = true; ++ health_cd = 0; ++ me->CastSpell(me, EAT); ++ me->SetStandState(UNIT_STAND_STATE_SIT); ++ } ++ if (feast_health) ++ { ++ health_cd += lastdiff; ++ if (health_cd >= RATIONS_CD && me->GetHealth() < me->GetMaxHealth() && me->HasAura(EAT)) ++ { ++ while (health_cd >= RATIONS_CD) ++ { ++ health_cd -= RATIONS_CD; ++ //20000 / 1000 = 20: 5% ++ //20000 / 2000 = 10: 10% ++ me->SetHealth(me->GetHealth() + me->GetMaxHealth() / (20000 / RATIONS_CD)); //5% per second if 1000 ++ } ++ } ++ } ++ ++ //check ++ if (feast_mana && me->GetMaxPower(POWER_MANA) > 1 && me->GetPower(POWER_MANA) >= me->GetMaxPower(POWER_MANA)) ++ { ++ feast_mana = false; ++ me->RemoveAurasDueToSpell(DRINK); ++ } ++ if (feast_health && me->GetHealth() >= me->GetMaxHealth()) ++ { ++ feast_health = false; ++ me->RemoveAurasDueToSpell(EAT); ++ } ++} ++ ++void bot_minion_ai::Regenerate() ++{ ++ if ((!me->IsInCombat() || me->IsPolymorphed()) && me->GetHealth() < me->GetMaxHealth()) ++ { ++ regenTimer_hp += lastdiff; ++ while (regenTimer_hp >= 2000) ++ { ++ regenTimer_hp -= 2000; ++ int32 add = me->IsPolymorphed() ? me->GetMaxHealth() / 3 : IAmFree() ? me->GetMaxHealth() / 5 : me->GetCreateHealth() / 50 + me->getLevel() / 3; ++ me->SetHealth(me->GetHealth() + add); ++ } ++ } ++ ++ if (me->GetMaxPower(POWER_MANA) > 1 && me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA)) ++ { ++ regenTimer_mp += lastdiff; ++ while (regenTimer_mp >= 5000) ++ { ++ regenTimer_mp -= 5000; ++ int32 add = (!me->IsInCombat() && IAmFree()) ? me->GetMaxPower(POWER_MANA) / 5 : int32(regen_mp); ++ me->ModifyPower(POWER_MANA, add); ++ } ++ } ++} ++void bot_pet_ai::Regenerate() ++{ ++ if (!regenTimer_hp && (!me->IsInCombat() || me->IsPolymorphed()) && me->GetHealth() < me->GetMaxHealth()) ++ { ++ regenTimer_hp = 2000; ++ int32 add = me->IsPolymorphed() ? me->GetMaxHealth() / 3 : me->GetCreateHealth() / 33 + me->getLevel() / 3; ++ me->SetHealth(me->GetHealth() + add); ++ } ++ ++ if (!regenTimer_mp && me->GetMaxPower(POWER_MANA) > 1 && me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA)) ++ { ++ regenTimer_mp = 5000; ++ me->ModifyPower(POWER_MANA, regen_mp); //mp5 ++ } ++} ++//PASSIVES ++// Used to apply common passives (run once) ++void bot_ai::ApplyPassives() const ++{ ++ //me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); ++ //me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); ++ ++ ////DEPRECATEDmovement speed ++ //if (master->HasAuraType(SPELL_AURA_MOD_SPEED_ALWAYS) || ++ // master->HasAuraType(SPELL_AURA_MOD_SPEED_NOT_STACK) || ++ // master->HasAuraType(SPELL_AURA_MOD_INCREASE_SPEED)) ++ // RefreshAura(BOAR); ++ ++ ////apply +healing taken ++ //RefreshAura(BOR, me->getLevel() >= 40 ? 1 : 0);//+40% ++ ++ //if (IsTempBot()) ++ // return; ++ ++ if (IsMinionAI()) ++ { ++ //apply -threat mod ++ switch (_botclass) ++ { ++ case BOT_CLASS_WARRIOR: ++ case BOT_CLASS_DEATH_KNIGHT: ++ RefreshAura(RCP, 1 * !IsTank()); //-27% ++ break; ++ case BOT_CLASS_WARLOCK: ++ case BOT_CLASS_PRIEST: ++ case BOT_CLASS_MAGE: ++ case BOT_CLASS_ROGUE: ++ case BOT_CLASS_HUNTER: ++ case BOT_CLASS_SHAMAN: ++ RefreshAura(RCP, 3 * !IsTank()); //-87% ++ break; ++ case CLASS_PALADIN: ++ case CLASS_DRUID: ++ case BOT_CLASS_BM: ++ RefreshAura(RCP, 2 * !IsTank()); //-54% ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "bot_ai: ApplyPassives() - unknown bot class %u for bot %s (id: %u)", ++ uint32(_botclass), me->GetName().c_str(), me->GetEntry()); ++ break; ++ } ++ //apply +threat mods (1.43 * 1.45 = 2.0735; 1.0 + 0.43 + 0.45 = 1.88) ++ RefreshAura(THREAT, 1 * IsTank()); //+43% ++ RefreshAura(DEFENSIVE_STANCE_PASSIVE, 1 * IsTank()); //+45% ++ } ++ else ++ { ++ switch (bot_pet_ai::GetPetType(me)) ++ { ++ case PET_TYPE_VOIDWALKER: ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "bot_ai: ApplyPassives() - unknown pet type %u for bot %s (id: %u)", ++ uint32(bot_pet_ai::GetPetType(me)), me->GetName().c_str(), me->GetEntry()); ++ break; ++ } ++ ++ RefreshAura(THREAT, 1 * IsTank()); //+43% ++ RefreshAura(DEFENSIVE_STANCE_PASSIVE, 2 * IsTank()); //+90%/-20% ++ } ++} ++//check if our party players are in duel. if so - ignore them, their opponents and any bots they have ++bool bot_ai::InDuel(Unit const* target) const ++{ ++ if (!target) return false; ++ bool isbot = target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->GetBotAI(); ++ Player const* player = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer() : isbot ? target->ToCreature()->GetBotOwner()->ToPlayer() : NULL; ++ if (!player) ++ { ++ if (!target->IsControlledByPlayer()) ++ return false; ++ player = target->GetCharmerOrOwnerPlayerOrPlayerItself(); ++ } ++ ++ return (player && player->duel && (IsInBotParty(player) || IsInBotParty(player->duel->opponent))); ++} ++//////////////// ++//GRID SEARCHERS ++//////////////// ++//Finds player or it's corpse for resurrection returned as WorldObject* ++WorldObject* bot_minion_ai::GetNearbyRezTarget(float dist) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ WorldObject* target = NULL; ++ ++ NearbyRezTargetCheck check(me, dist, this); ++ Trinity::WorldObjectSearcher searcher(me, target, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return target; ++} ++//Used to find target for priest's dispels, mage's spellsteal and shaman's purge ++//Returns dispellable/stealable 'Any Hostile Unit Attacking BotParty' ++Unit* bot_minion_ai::FindHostileDispelTarget(float dist, bool stealable) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ HostileDispelTargetCheck check(me, dist, stealable, this); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds single target affected by given spell (and given caster if is) ++//Can check: ++// hostile targets (hostile = 0) ++// our party players (hostile = 1) ++// our party members (hostile = 2) ++// any friendly target (hostile = 3) ++// any target in range (hostile = any other value) ++Unit* bot_minion_ai::FindAffectedTarget(uint32 spellId, ObjectGuid caster, float dist, uint8 hostile) const ++{ ++ if (!spellId) ++ return NULL; ++ if ((hostile == 2 || hostile == 1) && IAmFree()) ++ { ++ TC_LOG_ERROR("entities.player", "bot_ai::FindAffectedTarget(): hostile = %u while bot is free! Setting to 3...", hostile); ++ hostile = 3; ++ } ++ if (master->GetMap()->Instanceable()) ++ dist = DEFAULT_VISIBILITY_INSTANCE; ++ ++ CellCoord p(Trinity::ComputeCellCoord(master->GetPositionX(), master->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ AffectedTargetCheck check(caster, dist, spellId, master, hostile); ++ Trinity::UnitLastSearcher searcher(master, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *master->GetMap(), *master, dist); ++ cell.Visit(p, grid_unit_searcher, *master->GetMap(), *master, dist); ++ ++ return unit; ++} ++//Finds target for mage's polymorph or shaman's hex ++Unit* bot_minion_ai::FindPolyTarget(float dist, Unit* currTarget) const ++{ ++ if (!currTarget) ++ return NULL; ++ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ PolyUnitCheck check(me, dist, currTarget); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds target for direct fear (warlock) ++Unit* bot_minion_ai::FindFearTarget(float dist) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ FearUnitCheck check(me, dist); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds target for paladin's repentance ++Unit* bot_minion_ai::FindStunTarget(float dist) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ StunUnitCheck check(me, dist); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds target for priest's shackles ++Unit* bot_minion_ai::FindUndeadCCTarget(float dist, uint32 spellId/* = 0*/) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ UndeadCCUnitCheck check(me, dist, spellId); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds target for druid's Entangling Roots ++Unit* bot_minion_ai::FindRootTarget(float dist, uint32 spellId) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ RootUnitCheck check(me, me->GetVictim(), dist, spellId); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds casting target (friend or enemy) ++Unit* bot_minion_ai::FindCastingTarget(float maxdist, float mindist, bool isFriend, uint32 spellId) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ CastingUnitCheck check(me, mindist, maxdist, isFriend, spellId); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, maxdist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, maxdist); ++ ++ return unit; ++} ++// Returns target for AOE spell (blizzard, hurricane etc.) based on attackers count ++// Cycles through BotParty, first checks player and, if checked, npcbots ++// If checked, can return friendly target as target for AOE spell ++Unit* bot_minion_ai::FindAOETarget(float dist, bool checkbots, bool targetfriend) const ++{ ++ if (IsCasting() || IAmFree()) ++ return NULL; ++ ++ Unit* unit = NULL; ++ Group* pGroup = master->GetGroup(); ++ if (!pGroup) ++ { ++ AttackerSet m_attackers = master->getAttackers(); ++ if (m_attackers.size() > 1) ++ { ++ uint32 mCount = 0; ++ for(AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) ++ { ++ if (!(*iter) || !(*iter)->IsAlive()) continue; ++ if ((*iter)->isMoving()) continue; ++ if ((*iter)->HasBreakableByDamageCrowdControlAura()) ++ continue; ++ if (me->GetDistance(*iter) < dist) ++ ++mCount; ++ } ++ if (mCount > 1) ++ { ++ Unit* u = master->GetVictim(); ++ if (mCount > 3 && targetfriend == true) ++ unit = master; ++ else if (u && FindSplashTarget(dist + 8, u)) ++ unit = u; ++ }//end if ++ }//end if ++ if (!checkbots) ++ return unit; ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ Creature* bot = itr->second; ++ if (!bot || !bot->IsAlive() || !bot->IsInWorld() || me->GetDistance(bot) > dist) continue; ++ ++ AttackerSet b_attackers = bot->getAttackers(); ++ if (b_attackers.size() > 1) ++ { ++ uint32 mCount = 0; ++ for(AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter) || !(*iter)->IsAlive()) continue; ++ if ((*iter)->isMoving()) continue; ++ if ((*iter)->HasBreakableByDamageCrowdControlAura()) ++ continue; ++ if (me->GetDistance(*iter) < dist) ++ ++mCount; ++ } ++ if (mCount > 1) ++ { ++ Unit* u = bot->GetVictim(); ++ if (mCount > 3 && targetfriend == true) ++ unit = bot; ++ else if (u && FindSplashTarget(dist + 8, u)) ++ unit = u; ++ }//end if ++ }//end if ++ if (unit) return unit; ++ }//end for ++ return unit; ++ } ++ bool Bots = false; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer) continue; ++ if (checkbots && tPlayer->HaveBot()) ++ Bots = true; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (!tPlayer->IsAlive() || me->GetMap() != tPlayer->FindMap()) continue; ++ if (me->GetDistance(tPlayer) > 40) continue; ++ ++ AttackerSet m_attackers = tPlayer->getAttackers(); ++ if (m_attackers.size() > 1) ++ { ++ uint32 mCount = 0; ++ for (AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) ++ { ++ if (!(*iter) || !(*iter)->IsAlive()) continue; ++ if ((*iter)->isMoving()) continue; ++ if (me->GetDistance(*iter) < dist) ++ ++mCount; ++ } ++ if (mCount > 1) ++ { ++ Unit* u = tPlayer->GetVictim(); ++ if (mCount > 3 && targetfriend == true) ++ unit = tPlayer; ++ else if (u && FindSplashTarget(dist + 8, u)) ++ unit = u; ++ }//end if ++ }//end if ++ if (unit) return unit; ++ }//end for ++ if (!Bots) return NULL; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL || !tPlayer->HaveBot()) continue; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (me->GetMap() != tPlayer->FindMap()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (!bot || !bot->IsAlive() || me->GetMap() != bot->FindMap()) continue; ++ if (!bot->IsInWorld()) continue; ++ if (me->GetDistance(bot) > 40) continue; ++ ++ AttackerSet b_attackers = bot->getAttackers(); ++ if (b_attackers.size() > 1) ++ { ++ uint32 mCount = 0; ++ for(AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter) || !(*iter)->IsAlive()) continue; ++ if ((*iter)->isMoving()) continue; ++ if (me->GetDistance(*iter) < dist) ++ ++mCount; ++ } ++ if (mCount > 1) ++ { ++ Unit* u = bot->GetVictim(); ++ if (mCount > 3 && targetfriend == true) ++ unit = bot; ++ else if (u && FindSplashTarget(dist + 8, u)) ++ unit = u; ++ }//end if ++ }//end if ++ }//end for ++ if (unit) return unit; ++ }//end for ++ return unit; ++} ++// Finds secondary target for spells like Cleave, Swipe, Mind Sear etc. ++Unit* bot_minion_ai::FindSplashTarget(float dist, Unit* To, float splashdist) const ++{ ++ if (!To) ++ To = me->GetVictim(); ++ if (!To) ++ return NULL; ++ ++ if (me->GetDistance(To) > dist) ++ return NULL; ++ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ SecondEnemyCheck check(me, dist, splashdist, To, this); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, dist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, dist); ++ ++ return unit; ++} ++//Finds target for hunter's Tranquilizing Shot (has dispellable magic or enrage effect) ++Unit* bot_minion_ai::FindTranquilTarget(float mindist, float maxdist) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ Unit* unit = NULL; ++ ++ TranquilTargetCheck check(me, mindist, maxdist, this); ++ Trinity::UnitLastSearcher searcher(me, unit, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, maxdist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, maxdist); ++ ++ return unit; ++} ++//Finds all targets within given range with option for not having CC breaking auras ++//used for finding targets for spells which need reasonable amount of targets (ex. Death Knight AOE spells) ++void bot_minion_ai::GetNearbyTargetsList(std::list &targets, float maxdist, float mindist, bool forCC) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ NearbyHostileUnitCheck check(me, maxdist, mindist, this, forCC); ++ Trinity::UnitListSearcher searcher(me, targets, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, maxdist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, maxdist); ++} ++//Finds all friendly targets within given range ++//used for finding targets to heal/buff for uncontrolled bots ++void bot_minion_ai::GetNearbyFriendlyTargetsList(GuidList &targets, float maxdist) const ++{ ++ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); ++ Cell cell(p); ++ cell.SetNoCreate(); ++ ++ NearbyFriendlyUnitCheck check(me, maxdist, this); ++ UnitListSearcher searcher(me, targets, check); ++ ++ TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); ++ TypeContainerVisitor, GridTypeMapContainer > grid_unit_searcher(searcher); ++ ++ cell.Visit(p, world_unit_searcher, *me->GetMap(), *me, maxdist); ++ cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, maxdist); ++} ++////////// ++//SPELLMAP ++////////// ++//Using first-rank spell as source, returns spell of max rank allowed for given caster ++uint32 bot_ai::InitSpell(Unit const* caster, uint32 spell) ++{ ++ SpellInfo const* info = sSpellMgr->GetSpellInfo(spell); ++ if (!info) ++ { ++ TC_LOG_ERROR("entities.player", "InitSpell(): No SpellInfo found for spell %u", spell); ++ return 0; //weird spell with no info, disable it ++ } ++ ++ uint8 lvl = caster->getLevel(); ++ if (lvl < info->BaseLevel) //only 1st rank spells check ++ return 0; //cannot use this spell ++ ++ if (SpellInfo const* spInfo = info->GetNextRankSpell()) ++ { ++ if (lvl < spInfo->BaseLevel) ++ return spell; //cannot use next rank, use this one ++ else ++ return InitSpell(caster, spInfo->Id); //can use next rank, forward check ++ } ++ ++ return spell; //max rank, use this ++} ++//Using first-rank spell as source, returns spell of max rank allowed for given caster in given spellmap ++void bot_ai::InitSpellMap(uint32 basespell, bool forceadd, bool forwardRank) ++{ ++ SpellInfo const* info = sSpellMgr->GetSpellInfo(basespell); ++ if (!info) ++ { ++ TC_LOG_ERROR("entities.player", "InitSpellMap(): No SpellInfo found for base spell %u", basespell); ++ return; //invalid spell id ++ } ++ ++ uint8 lvl = me->getLevel(); ++ uint32 spellId = 0; ++ ++ while (info != NULL && forwardRank && (forceadd || lvl >= info->BaseLevel)) ++ { ++ spellId = info->Id; //can use this spell ++ info = info->GetNextRankSpell(); //check next rank ++ } ++ ++ BotSpell newSpell; ++ newSpell.first = spellId; ++ newSpell.second = GetSpellCooldown(basespell); ++ spells[basespell] = newSpell; ++} ++//Using first-rank spell as source, return current spell id ++uint32 bot_ai::GetSpell(uint32 basespell) const ++{ ++ BotSpellMap::const_iterator itr = spells.find(basespell); ++ return itr != spells.end() ? itr->second.first : 0; ++} ++//Using first-rank spell as source, returns cooldown on current spell ++uint32 bot_ai::GetSpellCooldown(uint32 basespell) const ++{ ++ BotSpellMap::const_iterator itr = spells.find(basespell); ++ return itr != spells.end() ? itr->second.second : 0; ++} ++bool bot_ai::IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD, uint32 forcedTime) const ++{ ++ BotSpellMap::const_iterator itr = spells.find(basespell); ++ ++ if (itr != spells.end()) ++ { ++ return ++ (itr->second.first != 0 && ++ (!checkGCD || GC_Timer <= diff) && ++ itr->second.second <= (forcedTime ? forcedTime : diff)); ++ } ++ ++ return false; ++} ++//Using first-rank spell as source, sets cooldown for current spell ++void bot_ai::SetSpellCooldown(uint32 basespell, uint32 msCooldown) ++{ ++ //if (!msCooldown) ++ // return; ++ ++ BotSpellMap::iterator itr = spells.find(basespell); ++ if (itr != spells.end()) ++ { ++ itr->second.second = msCooldown; ++ return; ++ } ++ ++ InitSpellMap(basespell, true, false); ++ ++ if (!GetSpell(basespell)) ++ return; ++ ++ SetSpellCooldown(basespell, msCooldown); ++} ++//Using first-rank spell as source, sets cooldown for spells of that category ++void bot_ai::SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown) ++{ ++ if (!msCooldown) ++ return; ++ ++ uint32 category = spellInfo->GetCategory(); ++ if (!category) ++ return; ++ ++ SpellInfo const* info; ++ for (BotSpellMap::iterator itr = spells.begin(); itr != spells.end(); ++itr) ++ { ++ //skip spell which has triggered this category cooldown ++ if (itr->second.first == spellInfo->Id && itr->second.second >= msCooldown) ++ continue; ++ ++ info = sSpellMgr->GetSpellInfo(itr->second.first); ++ if (info && info->GetCategory() == category && itr->second.second < msCooldown) ++ itr->second.second = msCooldown; ++ } ++} ++//Using first-rank spell as source, disables certain spell for this bot ++void bot_ai::RemoveSpell(uint32 basespell) ++{ ++ BotSpellMap::iterator itr = spells.find(basespell); ++ if (itr != spells.end()) ++ { ++ itr->second.first = 0; ++ itr->second.second = 0; //unneeded ++ } ++} ++//Look in Creature::Update() for common timers ++void bot_ai::SpellTimers(uint32 diff) ++{ ++ // spell must be initialized!!! ++ for (BotSpellMap::iterator itr = spells.begin(); itr != spells.end(); ++itr) ++ { ++ if (itr->second.second > diff) ++ itr->second.second -= diff; ++ else if (itr->second.second > 0) ++ itr->second.second = 0; ++ } ++} ++//Health magement for minions ++//Including health calcs, set ++void bot_minion_ai::_OnHealthUpdate() const ++{ ++ if (IsTempBot()) ++ return; ++ ++ uint8 myclass = _botclass; ++ uint8 mylevel = master->getLevel(); ++ if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) ++ myclass = GetBotStance(); ++ //TC_LOG_ERROR("entities.player", "_OnHealthUpdate(): updating bot %s", me->GetName().c_str()); ++ float pct = me->GetHealthPct(); // needs for regeneration ++ uint32 m_basehp = _classinfo->basehealth; ++ //TC_LOG_ERROR("entities.player", "class base health: %u", m_basehp); ++ me->SetCreateHealth(m_basehp); ++ ++ float stamValue = std::max(me->GetTotalStatValue(STAT_STAMINA) - 18.f, 1.f); //remove base stamina (not calculated into health) ++ stamValue += _getTotalBotStat(BOT_ITEM_MOD_STAMINA); ++ ++ //class-specified ++ if (GetPlayerClass() == BOT_CLASS_DRUID && myclass == DRUID_BEAR_FORM) ++ { ++ //Heart of the Wild: 10% stam bonus for bear ++ if (mylevel >= 35) ++ stamValue *= 1.1f; ++ } ++ ++ //TC_LOG_ERROR("entities.player", "bot's stats to health add: Stamina (%f), value: %f", stamValue, stamValue * 10.f); ++ int32 hp_add = int32(stamValue) * 10; ++ hp_add += IAmFree() ? mylevel * 250.f : 0; //+20000/+0 hp at 80 ++ hp_add += _getTotalBotStat(BOT_ITEM_MOD_HEALTH); ++ int32 miscVal = mylevel * 3 - 1; ++ hp_add += miscVal; ++ //TC_LOG_ERROR("entities.player", "health to add after slot mod: %i", hp_add); ++ uint32 m_totalhp = m_basehp + hp_add; //m_totalhp = uint32(float(m_basehp + hp_add) * stammod); ++ //TC_LOG_ERROR("entities.player", "total base health: %u", m_totalhp); ++ uint8 bonuspct = 0; ++ //bonuspct += 35 * IsTank(); ++ bonuspct += 8 * (GetBotStance() == DEATH_KNIGHT_FROST_PRESENCE); ++ if (bonuspct) ++ m_totalhp = (m_totalhp * (100 + bonuspct)) / 100; ++ m_totalhp = float(uint32(m_totalhp) + (10 - (uint32(m_totalhp) % 10))); ++ me->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, float(m_totalhp)); //replaces base hp at max lvl ++ me->UpdateMaxHealth(); //will use our values we just set (update base health and buffs) ++ //TC_LOG_ERROR("entities.player", "overall hp: %u", me->GetMaxHealth()); ++ me->SetHealth(uint32(0.5f + float(me->GetMaxHealth()) * pct / 100.f)); //restore pct ++} ++//Mana management for minions ++//Including calcs and set ++void bot_minion_ai::_OnManaUpdate(bool /*shapeshift*/) ++{ ++ if (me->GetMaxPower(POWER_MANA) <= 1) ++ return; ++ ++ if (IsTempBot()) ++ return; ++ ++ uint8 myclass = _botclass; ++ uint8 mylevel = master->getLevel(); ++ if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) ++ myclass = GetBotStance(); ++ ++ //TC_LOG_ERROR("entities.player", "_OnManaUpdate(): updating bot %s", me->GetName().c_str()); ++ float pct = me->GetMaxPower(POWER_MANA) == 0 ? 100 : (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA)); ++ float m_basemana = _classinfo->basemana; ++ if (myclass == BOT_CLASS_BM) ++ m_basemana = std::max(240 + (int32(mylevel - 20) * 5) - 225, 255); // 240 at 1, 540 at 81 ++ //TC_LOG_ERROR("entities.player", "classinfo base mana = %f", m_basemana); ++ ++ //decrease base mana for bots (allows using more mana) ++ me->SetCreateMana(uint32(m_basemana * 0.667f)); //set base mana, critical ++ ++ float intValue = me->GetTotalStatValue(STAT_INTELLECT) - 18.f; //remove base int (not calculated into mana) ++ intValue += _getTotalBotStat(BOT_ITEM_MOD_INTELLECT); ++ m_basemana += intValue * 15.0f; ++ m_basemana += IAmFree() ? mylevel * 125.f : 0; //+10000/+0 mana at 80 ++ m_basemana += _getTotalBotStat(BOT_ITEM_MOD_MANA); ++ m_basemana = float(uint32(m_basemana) - (uint32(m_basemana) % 5)); ++ me->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, m_basemana); ++ me->UpdateMaxPower(POWER_MANA); ++ me->SetPower(POWER_MANA, uint32(0.5f + float(me->GetMaxPower(POWER_MANA)) * pct / 100.f)); //restore pct ++} ++//Melee damage for minions (melee classes only) ++//Calculation is based on master's attack power if melee/hunter or spellpower ++void bot_minion_ai::_OnMeleeDamageUpdate(uint8 myclass) const ++{ ++ if (IsTempBot()) ++ return; ++ ++ if (ap_mod < 0.1f) return; //do not bother casters ++ //TC_LOG_ERROR("entities.player", "_OnMeleeDamageUpdate: Updating bot %s", me->GetName().c_str()); ++ ++ for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) ++ { ++ float weap_damage_base = _getBotStat(i, BOT_ITEM_MOD_DAMAGE); ++ weap_damage_base += IAmFree() ? me->getLevel() * 3.75f : 0; //+300/+20 dam at 80 ++ me->SetModifierValue(UnitMods(UNIT_MOD_DAMAGE_MAINHAND + i), BASE_VALUE, _getBotStat(i, BOT_ITEM_MOD_DAMAGE)); ++ } ++ ++ float atpower = IAmFree() ? me->getLevel() * 75.f : std::max(me->getLevel() - 40.f, 0.f) * 10.f; //+6000/+400 base ap at 80 ++ atpower += _getTotalBotStat(BOT_ITEM_MOD_ATTACK_POWER) + _getTotalBotStat(BOT_ITEM_MOD_RANGED_ATTACK_POWER); ++ atpower += 2.f * ((me->GetTotalStatValue(STAT_STRENGTH) - 18) + (me->GetTotalStatValue(STAT_AGILITY) - 18)); ++ atpower += 2.f * (_getTotalBotStat(BOT_ITEM_MOD_STRENGTH) + _getTotalBotStat(BOT_ITEM_MOD_AGILITY)); ++ ++ Unit::AuraEffectList const& mAPbyStat = me->GetAuraEffectsByType(SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT); ++ for (Unit::AuraEffectList::const_iterator i = mAPbyStat.begin(); i != mAPbyStat.end(); ++i) ++ atpower += CalculatePct(me->GetStat(Stats((*i)->GetMiscValue())), (*i)->GetAmount()); ++ ++ Unit::AuraEffectList const& mAPbyArmor = me->GetAuraEffectsByType(SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR); ++ for (Unit::AuraEffectList::const_iterator iter = mAPbyArmor.begin(); iter != mAPbyArmor.end(); ++iter) ++ atpower += int32(me->GetArmor() / (*iter)->GetAmount()); ++ ++ if (GetPlayerClass() == CLASS_DRUID && (GetBotStance() == DRUID_BEAR_FORM || GetBotStance() == DRUID_CAT_FORM)) ++ { ++ atpower += _getTotalBotStat(BOT_ITEM_MOD_FERAL_ATTACK_POWER); ++ //Predatory Strikes ++ if (me->getLevel() >= 25) ++ { ++ atpower += me->getLevel() * 2 / 3; ++ atpower += 0.2f * ( ++ _getBotStat(BOT_SLOT_MAINHAND, BOT_ITEM_MOD_FERAL_ATTACK_POWER) ++ + _getBotStat(BOT_SLOT_MAINHAND, BOT_ITEM_MOD_ATTACK_POWER) ++ + _getBotStat(BOT_SLOT_MAINHAND, BOT_ITEM_MOD_RANGED_ATTACK_POWER) ++ ); ++ } ++ } ++ ++ atpower *= ap_mod; ++ me->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, atpower); ++ me->UpdateAttackPowerAndDamage(); ++ if (myclass == BOT_CLASS_HUNTER || myclass == BOT_CLASS_ROGUE) ++ { ++ me->SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, atpower); ++ me->UpdateAttackPowerAndDamage(true); ++ } ++} ++//Health for pets ++//Same as for minions just simplified (modified to match real pets' values) ++void bot_pet_ai::_OnHealthUpdate() const ++{ ++ uint8 mylevel = master->getLevel(); ++ float hp_mult; ++ switch (GetPetType(me)) ++ { ++ case PET_TYPE_VOIDWALKER: hp_mult = 11.f; break; ++ default: hp_mult = 10.f; break; ++ } ++ float pct = me->GetHealthPct(); ++ uint32 m_basehp = me->GetCreateHealth(); ++ float stamValue = me->GetTotalStatValue(STAT_STAMINA) - 18.f; //remove base stamina (not calculated into health) ++ uint32 hp_add = uint32(stamValue * hp_mult); ++ hp_add += (m_creatureOwner->GetMaxHealth() - m_creatureOwner->GetCreateHealth()) / 3; ++ uint8 miscVal = GetPetType(me) * mylevel; ++ hp_add += miscVal; ++ uint32 m_totalhp = m_basehp + hp_add; ++ if (IsTank()) ++ m_totalhp = (m_totalhp * 135) / 100; //35% hp bonus for tanks ++ me->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, float(m_totalhp)); ++ me->UpdateMaxHealth(); //will use values set (update base health and buffs) ++ me->SetHealth(uint32(0.5f + float(me->GetMaxHealth()) * pct / 100.f)); //restore pct ++} ++//Mana for pets ++//Same as for minions just simplified (modified to match real pets' values) ++void bot_pet_ai::_OnManaUpdate(bool /*shapeshift*/) ++{ ++ if (me->GetMaxPower(POWER_MANA) <= 1) ++ return; ++ ++ uint8 mylevel = m_creatureOwner->getLevel(); ++ float mana_mult; ++ switch (GetPetType(me)) ++ { ++ case PET_TYPE_VOIDWALKER: mana_mult = 11.5f; break; ++ default: mana_mult = 15.f; break; ++ } ++ float pct = me->GetMaxPower(POWER_MANA) == 0 ? 100 : (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA)); ++ float m_basemana = float(me->GetCreateMana()); ++ m_basemana += me->GetTotalStatValue(STAT_INTELLECT) * mana_mult; //remove base stamina (not calculated into mana) ++ m_basemana += (m_creatureOwner->GetMaxPower(POWER_MANA) - m_creatureOwner->GetCreateMana()) / 3; ++ m_basemana += (GetPetType(me) * mylevel); ++ me->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, m_basemana); ++ me->UpdateMaxPower(POWER_MANA); ++ me->SetPower(POWER_MANA, uint32(0.5f + float(me->GetMaxPower(POWER_MANA)) * pct / 100.f));//restore pct ++} ++//Sends all master's bots a message to not try to evade for a certain period of time ++//void bot_ai::SendPartyEvadeAbort() const ++//{ ++// ASSERT(!IAmFree()); ++// ++// BotMap const* map = master->GetBotMgr()->GetBotMap(); ++// for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++// if (Creature* bot = itr->second) ++// if (bot_minion_ai* ai = bot->GetBotMinionAI()) ++// ai->SetEvadeTimer(50); ++//} ++//Removes buggy bots' threat from party, so no 'stuck in combat' bugs form bot mod ++//optionally interrupts casted spell if target is dead for bot and it's pet ++void bot_minion_ai::Evade(bool force) ++{ ++ if (me->HasUnitState(UNIT_STATE_CASTING)) ++ { ++ for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) ++ if (Spell* spell = me->GetCurrentSpell(CurrentSpellTypes(i))) ++ if (!spell->GetSpellInfo()->IsChanneled()) ++ if (Unit* u = spell->m_targets.GetUnitTarget()) ++ if (!u->IsAlive() && !IsInBotParty(u)) ++ me->InterruptSpell(CurrentSpellTypes(i), false, false); ++ } ++ ++ Creature* m_botsPet = me->GetBotsPet(); ++ if (m_botsPet && m_botsPet->HasUnitState(UNIT_STATE_CASTING)) ++ { ++ for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) ++ if (Spell* spell = m_botsPet->GetCurrentSpell(CurrentSpellTypes(i))) ++ if (!spell->GetSpellInfo()->IsChanneled()) ++ if (Unit* u = spell->m_targets.GetUnitTarget()) ++ if (!u->IsAlive() && !IsInBotParty(u)) ++ m_botsPet->InterruptSpell(CurrentSpellTypes(i), false, false); ++ } ++ ++ if (CCed(me)) return; ++ if (!force && Rand() > 10) return; ++ EnterEvadeMode(force); ++ if (!force && !master->IsInCombat() && !me->IsInCombat() && (!m_botsPet || !m_botsPet->IsInCombat())) return; ++ if (!force && CheckAttackTarget(_botclass)) return; ++ ++ if (master->IsInCombat() && !IAmFree()) ++ { ++ if (!master->getHostileRefManager().isEmpty()) ++ { ++ GuidSet Set; ++ HostileReference* ref = master->getHostileRefManager().getFirst(); ++ while (ref) ++ { ++ Set.insert(ref->GetSource()->GetOwner()->GetGUID()); ++ Set.insert(ref->getUnitGuid()); ++ ref = ref->next(); ++ } ++ for (GuidSet::const_iterator i = Set.begin(); i != Set.end(); ++i) ++ { ++ Unit* unit = ObjectAccessor::FindConnectedPlayer(*i); ++ if (unit && (unit->IsFriendlyTo(me) || IsInBotParty(unit) || !unit->IsInCombat())) ++ { ++ master->getHostileRefManager().deleteReference(unit); ++ //unit->getHostileRefManager().deleteReference(master); ++ } ++ } ++ } ++ ++ return; ++ } ++ ++ if (!master->IsInCombat() || IAmFree()) ++ { ++ if (IAmFree()) ++ { ++ //me->DeleteThreatList(); ++ if (!me->getHostileRefManager().isEmpty()) ++ { ++ GuidSet Set; ++ HostileReference* ref = me->getHostileRefManager().getFirst(); ++ while (ref) ++ { ++ Set.insert(ref->GetSource()->GetOwner()->GetGUID()); ++ Set.insert(ref->getUnitGuid()); ++ ref = ref->next(); ++ } ++ for (GuidSet::const_iterator i = Set.begin(); i != Set.end(); ++i) ++ { ++ Unit* unit = ObjectAccessor::FindConnectedPlayer(*i); ++ if (!unit || !unit->InSamePhase(me)) continue; ++ if (unit->IsFriendlyTo(me) || IsInBotParty(unit) || !unit->IsInCombat()) ++ { ++ me->getHostileRefManager().deleteReference(unit); ++ //unit->getHostileRefManager().deleteReference(me); ++ } ++ } ++ } ++ if (Creature* m_botsPet = me->GetBotsPet()) ++ { ++ //m_botsPet->DeleteThreatList(); ++ if (!m_botsPet->getHostileRefManager().isEmpty()) ++ { ++ GuidSet Set; ++ HostileReference* ref = m_botsPet->getHostileRefManager().getFirst(); ++ while (ref) ++ { ++ Set.insert(ref->GetSource()->GetOwner()->GetGUID()); ++ Set.insert(ref->getUnitGuid()); ++ ref = ref->next(); ++ } ++ for (GuidSet::const_iterator i = Set.begin(); i != Set.end(); ++i) ++ { ++ Unit* unit = ObjectAccessor::FindConnectedPlayer(*i); ++ if (!unit || !unit->InSamePhase(me)) continue; ++ if (unit->IsFriendlyTo(me) || IsInBotParty(unit) || !unit->IsInCombat()) ++ { ++ m_botsPet->getHostileRefManager().deleteReference(unit); ++ //unit->getHostileRefManager().deleteReference(m_botsPet); ++ } ++ } ++ } ++ } ++ ++ return; ++ } ++ //SendPartyEvadeAbort(); ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre) continue; ++ if (cre->IsInCombat()) ++ { ++ //cre->DeleteThreatList(); ++ if (!cre->getHostileRefManager().isEmpty()) ++ { ++ GuidSet Set; ++ HostileReference* ref = cre->getHostileRefManager().getFirst(); ++ while (ref) ++ { ++ Set.insert(ref->GetSource()->GetOwner()->GetGUID()); ++ Set.insert(ref->getUnitGuid()); ++ ref = ref->next(); ++ } ++ for (GuidSet::const_iterator i = Set.begin(); i != Set.end(); ++i) ++ { ++ Unit* unit = ObjectAccessor::FindConnectedPlayer(*i); ++ if (!unit || !unit->InSamePhase(me)) continue; ++ if (unit->IsFriendlyTo(me) || IsInBotParty(unit) || !unit->IsInCombat()) ++ { ++ cre->getHostileRefManager().deleteReference(unit); ++ //unit->getHostileRefManager().deleteReference(cre); ++ } ++ } ++ } ++ } ++ ++ Creature* m_botsPet = cre->GetBotsPet(); ++ if (!m_botsPet || !m_botsPet->IsInCombat()) continue; ++ //m_botsPet->DeleteThreatList(); ++ if (!m_botsPet->getHostileRefManager().isEmpty()) ++ { ++ GuidSet Set; ++ HostileReference* ref = m_botsPet->getHostileRefManager().getFirst(); ++ while (ref) ++ { ++ Set.insert(ref->GetSource()->GetOwner()->GetGUID()); ++ Set.insert(ref->getUnitGuid()); ++ ref = ref->next(); ++ } ++ for (GuidSet::const_iterator i = Set.begin(); i != Set.end(); ++i) ++ { ++ Unit* unit = ObjectAccessor::FindConnectedPlayer(*i); ++ if (!unit || !unit->InSamePhase(me)) continue; ++ if (unit->IsFriendlyTo(me) || IsInBotParty(unit) || !unit->IsInCombat()) ++ { ++ m_botsPet->getHostileRefManager().deleteReference(unit); ++ //unit->getHostileRefManager().deleteReference(m_botsPet); ++ } ++ } ++ } ++ } ++ } ++} ++//SpellHit()... OnSpellHit() ++void bot_ai::OnSpellHit(Unit* caster, SpellInfo const* spell) ++{ ++ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) ++ { ++ uint32 auraname = spell->Effects[i].ApplyAuraName; ++ //remove pet on mount ++ if (auraname == SPELL_AURA_MOUNTED) ++ { ++ me->SetBotsPetDied(); ++ if (master->HasAuraType(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED) || ++ master->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)) ++ { ++ const_cast(me->GetCreatureTemplate())->InhabitType |= INHABIT_AIR; ++ //me->AddUnitMovementFlag(MOVEMENTFLAG_HOVER); ++ me->SetCanFly(true); ++ me->SetDisableGravity(true); ++ me->SetSpeed(MOVE_FLIGHT, master->GetSpeedRate(MOVE_FLIGHT) * 1.37f); ++ me->SetSpeed(MOVE_RUN, master->GetSpeedRate(MOVE_FLIGHT) * 1.37f); ++ } ++ else ++ me->SetSpeed(MOVE_RUN, master->GetSpeedRate(MOVE_RUN) * 1.25f); ++ } ++ ++ //update stats ++ if (auraname == SPELL_AURA_MOD_STAT || auraname == SPELL_AURA_MOD_PERCENT_STAT || ++ auraname == SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE || auraname == SPELL_AURA_MOD_SKILL || ++ auraname == SPELL_AURA_MOD_ATTACK_POWER || auraname == SPELL_AURA_MOD_ATTACK_POWER_PCT || ++ auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT || auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR || ++ auraname == SPELL_AURA_MOD_RATING/* || auraname == SPELL_AURA_MOD_RATING_FROM_STAT*/) //NYI TODO: ++ { ++ shouldUpdateStats = true; ++ } ++ else ++ { ++ if (auraname == SPELL_AURA_MOD_INCREASE_HEALTH || ++ auraname == SPELL_AURA_MOD_INCREASE_HEALTH_2 || ++ auraname == SPELL_AURA_230 ||//SPELL_AURA_MOD_INCREASE_HEALTH_2 ++ auraname == SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT) ++ doHealth = true; ++ else if (auraname == SPELL_AURA_MOD_INCREASE_ENERGY || ++ auraname == SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT) ++ doMana = true; ++ } ++ } ++ ++ //TODO: ++ if (/*!(spell->AttributesEx & SPELL_ATTR1_NO_THREAT) && ++ !(spell->AttributesEx3 & SPELL_ATTR3_NO_INITIAL_AGGRO) &&*/ ++ IsMinionAI() && /*!CCed(me) && */(me->IsHostileTo(caster) || caster->IsHostileTo(me))) ++ { ++ //_atHome = false; ++ if (!me->CanSeeOrDetect(caster)) ++ { ++ if (_evadeMode) ++ me->BotStopMovement(); ++ } ++ else if (caster->IsInCombat() || me->IsInCombat()) ++ this->OwnerAttackedBy(caster); ++ //if (_evadeMode == true && me->isMoving() && IAmFree()) ++ } ++} ++//Messed up ++//Hp + Mana update ++//target update ++//returns fake wait time between overall AI updates (if it is even understandable) ++uint8 bot_ai::GetWait() ++{ ++ if (doHealth) ++ { ++ doHealth = false; ++ _OnHealthUpdate(); ++ } ++ if (doMana) ++ { ++ doMana = false; ++ _OnManaUpdate(); ++ } ++ CheckAuras(true); ++ FindMaster(); ++ //SavePosition(); ++ //0 to 2 plus 1 for every 3 bots except first one ++ return IAmFree() ? 3 : (1 + (master->GetNpcBotsCount() - 1)/3 + (irand(0,100) <= 50)*int8(RAND(-1,1))); ++} ++//Damage/Healing Mods ++//1) Apply class-specified damage/healing/crit chance/crit damage/crit healing bonuses ++//2) Apply bot damage/healing multipliers ++//Bug with config reload (creatures do not update their damage on reload) is not bot-related but still annoying ++void bot_ai::ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const ++{ ++ //WHITE ATTACKS damage bonus ++ ApplyClassDamageMultiplierMelee(damage, damageinfo); ++ damage = uint32(damage * _mult_dmg_melee); ++} ++void bot_ai::ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++{ ++ //MELEE ABILITIES damage/crit bonus ++ ApplyClassDamageMultiplierMelee(damage, damageinfo, spellInfo, attackType, crit); ++ damage = int32(damage * _mult_dmg_melee); ++} ++void bot_ai::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++{ ++ //DAMAGE SPELLS damage/crit bonus ++ ApplyClassDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit); ++ damage = int32(damage * _mult_dmg_spell); ++} ++void bot_ai::ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const ++{ ++ //HEALING SPELLS amount bonus ++ ApplyClassDamageMultiplierHeal(victim, heal, spellInfo, damagetype, stack); ++ heal = (heal * _mult_healing); ++} ++void bot_ai::ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const ++{ ++ //ALL SPELLS crit base ++ //for base use bot_ai::crit, for healing spells crit bonus use class specified func ++ //bonuses for damage spells are handled in ApplyBotDamageMultiplierSpell() ++ ApplyClassCritMultiplierHeal(victim, crit_chance, spellInfo, schoolMask, attackType); ++ crit_chance += crit; ++} ++void bot_ai::ApplyBotSpellCostMods(SpellInfo const* spellInfo, int32& cost) const ++{ ++ //ALL SPELLS power cost bonus ++ ApplyClassSpellCostMods(spellInfo, cost); ++} ++void bot_ai::ApplyBotSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const ++{ ++ //ALL SPELLS cast time bonus ++ ApplyClassSpellCastTimeMods(spellInfo, casttime); ++} ++void bot_ai::ApplyBotSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const ++{ ++ //ALL SPELLS cooldown bonus ++ ApplyClassSpellCooldownMods(spellInfo, cooldown); ++} ++void bot_ai::ApplyBotSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const ++{ ++ //ALL SPELLS category cooldown bonus ++ ApplyClassSpellCategoryCooldownMods(spellInfo, cooldown); ++} ++void bot_ai::ApplyBotSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const ++{ ++ //ALL SPELLS global cooldown bonus ++ ApplyClassSpellGlobalCooldownMods(spellInfo, cooldown); ++} ++////////// ++//GOSSIP// ++////////// ++//GossipHello (static) ++bool bot_minion_ai::OnGossipHello(Player* player, Creature* creature, uint32 /*option*/) ++{ ++ ASSERT(player); ++ ASSERT(creature); ++ ++ if (!_enableNpcBots || creature->IsInCombat() || bot_ai::CCed(creature) || creature->GetBotAI()->IsDuringTeleport()) ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ if (creature->GetBotAI()->IsTempBot()) //Blademaster illusion etc. ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ if (creature->isMoving()) ++ creature->StopMoving(); ++ ++ uint32 gossipTextId = (player->GetGUID().GetCounter() == creature->GetBotAI()->GetBotOwnerGuid() || !creature->GetBotAI()->IAmFree()) ? GOSSIP_SERVE_MASTER : GOSSIP_NEED_SMTH; ++ ++ bool menus = false; ++ ++ if (player->IsGameMaster() && ++ (creature->IsFreeBot() || player->GetGUID().GetCounter() != creature->GetBotAI()->GetBotOwnerGuid())) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG, GOSSIP_ACTION_INFO_DEF + 1); ++ menus = true; ++ } ++ ++ if (player->GetGUID().GetCounter() != creature->GetBotAI()->GetBotOwnerGuid()) ++ { ++ if (creature->IsFreeBot() && !player->IsGameMaster()) ++ { ++ uint32 cost = BotMgr::GetNpcBotCost(player->getLevel(), creature); ++ ++ int8 reason = 0; ++ if (creature->IsHostileTo(player) || player->IsHostileTo(creature) || ++ creature->HasAura(BERSERK)) ++ reason = -1; ++ if (!reason && creature->GetBotAI()->GetBotOwnerGuid()) ++ reason = 1; ++ if (!reason && player->GetNpcBotsCount() >= BotMgr::GetMaxNpcBots()) ++ reason = 2; ++ if (!reason && !player->HasEnoughMoney(cost)) ++ reason = 3; ++ ++ if (!reason && _maxClassNpcBots && player->HaveBot()) ++ { ++ uint8 count = 0; ++ BotMap const* map = player->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ if (itr->second->GetBotClass() == creature->GetBotClass()) ++ ++count; ++ ++ if (!reason && count >= _maxClassNpcBots) ++ reason = 4; ++ } ++ ++ if (!reason) ++ { ++ std::ostringstream message; ++ message << "Do you wish to hire " << creature->GetName() << '?'; ++ player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, "Will you follow me?", ++ GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + 0, message.str().c_str(), cost, false); ++ } ++ else ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TAXI, "Will you follow me?", GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + reason); ++ ++ if (creature->GetBotClass() >= BOT_CLASS_EX_START) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "", GOSSIP_SENDER_SCAN, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ menus = true; ++ } ++ } ++ ++ if (creature->GetBotAI()->GetBotOwnerGuid()) ++ { ++ Group const* gr = player->GetGroup(); ++ ++ if (player == creature->GetBotOwner()) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Manage equipment...", GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Manage roles...", GOSSIP_SENDER_ROLES, GOSSIP_ACTION_INFO_DEF + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Manage formation...", GOSSIP_SENDER_FORMATION, GOSSIP_ACTION_INFO_DEF + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Use ability...", GOSSIP_SENDER_ABILITIES, GOSSIP_ACTION_INFO_DEF + 1); ++ if (creature->GetBotClass() >= BOT_CLASS_EX_START) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Abilities status...", GOSSIP_SENDER_SCAN_OWNER, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ if (!gr) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 1); ++ else if (!gr->IsMember(creature->GetGUID())) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 2); ++ else ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_LEAVE_GROUP, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ menus = true; ++ } ++ if (player == creature->GetBotOwner() || (gr && gr->IsMember(creature->GetBotOwner()->GetGUID()))) ++ { ++ switch (creature->GetBotClass()) ++ { ++ case BOT_CLASS_MAGE: ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "I need food", GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "I need drink", GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); ++ menus = true; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ if (player == creature->GetBotOwner()) ++ { ++ std::ostringstream astr; ++ astr << "Are you going to abandon " << creature->GetName() << "? You may regret it..."; ++ player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, "You are dismissed", ++ GOSSIP_SENDER_DISMISS, GOSSIP_ACTION_INFO_DEF + 1, astr.str().c_str(), 0, false); ++ ++ menus = true; ++ } ++ } ++ ++ if (menus) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Nevermind", 0, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ player->PlayerTalkClass->SendGossipMenu(gossipTextId, creature->GetGUID()); ++ return true; ++} ++ ++//GossipSelect ++bool bot_minion_ai::OnGossipSelect(Player* player, Creature* creature/* == me*/, uint32 sender, uint32 action) ++{ ++ //if (!IsInBotParty(player)) ++ //{ ++ // player->CLOSE_GOSSIP_MENU(); ++ // return true; ++ //} ++ ++ if (!_enableNpcBots || CCed(me) || IsDuringTeleport()) ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ uint32 gossipTextId = (player->GetGUID().GetCounter() == _ownerGuid || !IAmFree()) ? GOSSIP_SERVE_MASTER : GOSSIP_NEED_SMTH; ++ ++ player->PlayerTalkClass->ClearMenus(); ++ bool subMenu = false; ++ ++ switch (sender) ++ { ++ case 0: //any kind of fail ++ { ++ BotSay("...", player); ++ break; ++ } ++ case 1: //return to main menu ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ case GOSSIP_SENDER_CLASS: //food/drink (classes: MAGE) ++ { ++ switch (_botclass) ++ { ++ case BOT_CLASS_MAGE: ++ { ++ //Prevent high-leveled consumables for low-level characters ++ Unit* checker; ++ if (player->getLevel() < me->getLevel()) ++ checker = player; ++ else ++ checker = me; ++ ++ // Conjure Refreshment rank 1 ++ uint32 food = InitSpell(checker, 42955); ++ bool iswater = (action == GOSSIP_ACTION_INFO_DEF + 2); ++ if (!food) ++ { ++ if (!iswater)// Conjure Food rank 1 ++ food = InitSpell(checker, 587); ++ else// Conjure Water rank 1 ++ food = InitSpell(checker, 5504); ++ } ++ if (!food) ++ { ++ std::string errorstr = "I can't conjure "; ++ errorstr += iswater ? "water" : "food"; ++ errorstr += " yet"; ++ BotWhisper(errorstr.c_str(), player); ++ //player->PlayerTalkClass->ClearMenus(); ++ //return OnGossipHello(player, me); ++ break; ++ } ++ SpellInfo const* Info = sSpellMgr->GetSpellInfo(food); ++ Spell* foodspell = new Spell(me, Info, TRIGGERED_NONE, player->GetGUID()); ++ SpellCastTargets targets; ++ targets.SetUnitTarget(player); ++ //TODO implement checkcast for bots ++ SpellCastResult result = me->IsMounted() || CCed(me) ? SPELL_FAILED_CUSTOM_ERROR : foodspell->CheckPetCast(player); ++ if (result != SPELL_CAST_OK) ++ { ++ foodspell->finish(false); ++ delete foodspell; ++ BotWhisper("I can't do it right now", player); ++ } ++ else ++ { ++ aftercastTargetGuid = player->GetGUID(); ++ foodspell->prepare(&targets); ++ BotWhisper("Here you go...", player); ++ } ++ break; ++ } ++ default: ++ break; ++ } ++ break; ++ } ++ case GOSSIP_SENDER_EQUIPMENT: //equips change s1: send what slots we can use ++ { ++ subMenu = true; ++ ++ //general ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Show me your inventory", GOSSIP_SENDER_EQUIPMENT_LIST, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ //auto-equip ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Auto-equip...", GOSSIP_SENDER_EQUIP_AUTOEQUIP, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ //weapons ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Main hand...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_MAINHAND); ++ if (_canUseOffHand()) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Off-hand...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_OFFHAND); ++ if (_canUseRanged()) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Ranged...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_RANGED); ++ else ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Relic...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_RANGED); ++ ++ //armor ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Head...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_HEAD); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Shoulders...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_SHOULDERS); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Chest...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_CHEST); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Waist...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_WAIST); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Legs...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_LEGS); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Feet...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_FEET); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Wrist...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_WRIST); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Hands...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_HANDS); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Back...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_BACK); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Shirt...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_BODY); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Finger1...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_FINGER1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Finger2...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_FINGER2); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Trinket1...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_TRINKET1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Trinket2...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_TRINKET2); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Neck...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + BOT_SLOT_NECK); ++ ++ //if (player->IsGameMaster()) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Unequip all", GOSSIP_SENDER_UNEQUIP_ALL, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ break; ++ } ++ case GOSSIP_SENDER_EQUIPMENT_LIST: //list inventory ++ { ++ //if (action - GOSSIP_ACTION_INFO_DEF != BOT_SLOT_NONE) ++ // break; ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to send equipment list for bot with no equip info!"); ++ ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ Item* item = _equips[i]; ++ if (!item) continue; ++ std::ostringstream msg; ++ _AddItemLink(player, item, msg); ++ msg << " in slot " << uint32(i) << " (" << _getNameForSlot(i + 1) << ')'; ++ if (i < BOT_SLOT_RANGED && einfo->ItemEntry[i] == item->GetEntry()) ++ msg << " |cffe6cc80|h[!standard item!]|h|r"; ++ BotWhisper(msg.str().c_str(), player); ++ } ++ ++ break; ++ } ++ case GOSSIP_SENDER_EQUIPMENT_INFO: //request equip item info ++ { ++ //GOSSIP ITEMS RESTRICTED ++ //subMenu = true; //needed for return ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to send equipment info for bot with no equip info!"); ++ ++ uint8 slot = action - (GOSSIP_ACTION_INFO_DEF + 1); ++ Item* item = _equips[slot]; ++ ASSERT(item); ++ ++ std::ostringstream msg; ++ _AddItemLink(player, item, msg); ++ ++ if (slot < BOT_SLOT_RANGED && einfo->ItemEntry[slot] == item->GetEntry()) ++ msg << " |cffe6cc80|h[!standard item!]|h|r"; ++ ++ BotWhisper(msg.str().c_str(), player); ++ ++ //break; //no break here - return to menu ++ } ++ case GOSSIP_SENDER_EQUIPMENT_SHOW: //equips change s2: send list of equippable items ++ { ++ subMenu = true; ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to send equipment show for bot with no equip info!"); ++ ++ std::set itemList, idsList; ++ ++ //s2.1: build list ++ //s2.1.1: backpack ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ { ++ bool standard = false; ++ for (uint8 j = 0; j != MAX_EQUIPMENT_ITEMS; ++j) ++ { ++ if (einfo->ItemEntry[j] == pItem->GetEntry()) ++ { ++ standard = true; ++ break; ++ } ++ } ++ if (standard) ++ continue; ++ if (_canEquip(pItem->GetTemplate(), action - GOSSIP_ACTION_INFO_DEF, true) && itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() && idsList.find(pItem->GetEntry()) == idsList.end()) ++ { ++ itemList.insert(pItem->GetGUID().GetCounter()); ++ idsList.insert(pItem->GetEntry()); ++ } ++ } ++ } ++ ++ //s2.1.2: other bags ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ if (Item* pItem = player->GetItemByPos(i, j)) ++ { ++ bool standard = false; ++ for (uint8 k = 0; k != MAX_EQUIPMENT_ITEMS; ++k) ++ { ++ if (einfo->ItemEntry[k] == pItem->GetEntry()) ++ { ++ standard = true; ++ break; ++ } ++ } ++ if (standard) ++ continue; ++ if (_canEquip(pItem->GetTemplate(), action - GOSSIP_ACTION_INFO_DEF, true) && itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() && idsList.find(pItem->GetEntry()) == idsList.end()) ++ { ++ itemList.insert(pItem->GetGUID().GetCounter()); ++ idsList.insert(pItem->GetEntry()); ++ } ++ } ++ } ++ } ++ } ++ ++ //s2.2: add gossips ++ ++ //s2.2.0 add current item (with return) ++ uint8 slot = action - (GOSSIP_ACTION_INFO_DEF + 1); ++ std::ostringstream str; ++ str << "Equipped: "; ++ if (Item* item = _equips[slot]) ++ { ++ _AddItemLink(player, item, str); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, str.str().c_str(), GOSSIP_SENDER_EQUIPMENT_INFO, action); ++ } ++ else ++ { ++ str << "nothing"; ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, str.str().c_str(), GOSSIP_SENDER_EQUIPMENT_SHOW, action); ++ } ++ ++ //s2.2.1 add unequip option if have weapon (GMs only) ++ if (action - GOSSIP_ACTION_INFO_DEF <= BOT_SLOT_RANGED) ++ //if (player->IsGameMaster()) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Use your old equipment", GOSSIP_SENDER_EQUIP_RESET, action); ++ ++ //s2.2.2 add unequip option for non-weapons ++ if (slot >= BOT_SLOT_RANGED && _equips[slot]) ++ //if (player->IsGameMaster()) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Unequip it", GOSSIP_SENDER_UNEQUIP, action); ++ ++ //s2.2.3a: add an empty submenu with info if no items are found ++ if (itemList.empty()) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Hm... I have nothing to give you", 0, GOSSIP_ACTION_INFO_DEF + 1); ++ } ++ else ++ { ++ uint32 counter = 0; ++ uint32 maxcounter = GOSSIP_MAX_MENU_ITEMS - 4; //unequip, reset, current, back ++ uint32 slot = action - GOSSIP_ACTION_INFO_DEF; ++ Item* item; ++ //s2.2.3b: add items as gossip options ++ for (std::set::const_iterator itr = itemList.begin(); itr != itemList.end() && counter < maxcounter; ++itr) ++ { ++ bool found = false; ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); ++ if (item && item->GetGUID().GetCounter() == (*itr)) ++ { ++ std::ostringstream name; ++ _AddItemLink(player, item, name); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP + (slot - 1), GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); ++ ++counter; ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) ++ continue; ++ ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ item = player->GetItemByPos(i, j); ++ if (item && item->GetGUID().GetCounter() == (*itr)) ++ { ++ std::ostringstream name; ++ _AddItemLink(player, item, name); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP + (slot - 1), GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); ++ ++counter; ++ found = true; ++ break; ++ } ++ } ++ } ++ ++ if (found) ++ break; ++ } ++ ++ if (found) ++ continue; ++ } ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 2); ++ ++ //TC_LOG_ERROR("entities.player", "OnGossipSelect(bot): added %u item(s) to list of %s (requester: %s)", ++ // counter, me->GetName().c_str(), player->GetName().c_str()); ++ ++ break; ++ } ++ case GOSSIP_SENDER_UNEQUIP: //equips change s3: Unequip DEPRECATED ++ { ++ if (_unequip(action - (GOSSIP_ACTION_INFO_DEF + 1))) ++ BotSay("Hm...", player); ++ break; ++ } ++ case GOSSIP_SENDER_UNEQUIP_ALL: ++ { ++ bool suc = true; ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ if (!(i < BOT_SLOT_RANGED ? _resetEquipment(i) : _unequip(i))) ++ { ++ suc = false; ++ std::ostringstream estr; ++ estr << "Cannot reset equipment in slot " << uint32(i) << " (" << _getNameForSlot(i + 1) << ")!"; ++ BotWhisper(estr.str().c_str(), player); ++ } ++ ++ if (suc) ++ me->HandleEmoteCommand(EMOTE_ONESHOT_CRY); ++ } ++ break; ++ } ++ //autoequips change s5b: AtoEquip item ++ //base is GOSSIP_SENDER_EQUIP_AUTOEQUIP + 0...1...2... etc. ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_MHAND: //1 - 1 main hand ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_OHAND: //2 - 1 off hand ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_RANGED: //3 - 1 ranged ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_HEAD: //4 - 1 head ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_SHOULDERS: //5 - 1 shoulders ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_CHEST: //6 - 1 chest ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_WAIST: //7 - 1 waist ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_LEGS: //8 - 1 legs ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FEET: //9 - 1 feet ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_WRIST: //10 - 1 wrist ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_HANDS: //11 - 1 hands ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_BACK: //12 - 1 back ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_BODY: //13 - 1 body ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER1: //14 - 1 finger ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER2: //15 - 1 finger ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET1: //16 - 1 trinket ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET2: //17 - 1 trinket ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP_NECK: //18 - 1 neck ++ { ++ Item* item = NULL; ++ uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF; ++ ++ bool found = false; ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); ++ if (item && item->GetGUID().GetCounter() == guidLow) ++ { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ { ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ item = player->GetItemByPos(i, j); ++ if (item && item->GetGUID().GetCounter() == guidLow) ++ { ++ found = true; ++ break; ++ } ++ } ++ } ++ ++ if (found) ++ break; ++ } ++ } ++ ++ if (found && _equip(sender - GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, item)){} ++ ++ //break; ++ } ++ case GOSSIP_SENDER_EQUIP_AUTOEQUIP: ++ { ++ subMenu = true; ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to send auto-equip for bot with no equip info!"); ++ ++ std::set itemList, idsList; ++ ++ //1: build list ++ //1.1: backpack ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ { ++ bool standard = false; ++ for (uint8 j = 0; j != MAX_EQUIPMENT_ITEMS; ++j) ++ { ++ if (einfo->ItemEntry[j] == pItem->GetEntry()) ++ { ++ standard = true; ++ break; ++ } ++ } ++ if (standard) ++ continue; ++ ++ bool canEquip = false; ++ ++ for (uint8 k = 0; k != BOT_INVENTORY_SIZE; ++k) ++ { ++ if (_canEquip(pItem->GetTemplate(), k + 1)) ++ { ++ canEquip = true; ++ break; ++ } ++ } ++ ++ if (canEquip && itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() && idsList.find(pItem->GetEntry()) == idsList.end()) ++ { ++ itemList.insert(pItem->GetGUID().GetCounter()); ++ idsList.insert(pItem->GetEntry()); ++ } ++ } ++ } ++ ++ //1.2: other bags ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ if (Item* pItem = player->GetItemByPos(i, j)) ++ { ++ bool standard = false; ++ for (uint8 k = 0; k != MAX_EQUIPMENT_ITEMS; ++k) ++ { ++ if (einfo->ItemEntry[k] == pItem->GetEntry()) ++ { ++ standard = true; ++ break; ++ } ++ } ++ if (standard) ++ continue; ++ ++ bool canEquip = false; ++ ++ for (uint8 k = 0; k != BOT_INVENTORY_SIZE; ++k) ++ { ++ if (_canEquip(pItem->GetTemplate(), k + 1)) ++ { ++ canEquip = true; ++ break; ++ } ++ } ++ ++ if (canEquip && itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() && idsList.find(pItem->GetEntry()) == idsList.end()) ++ { ++ itemList.insert(pItem->GetGUID().GetCounter()); ++ idsList.insert(pItem->GetEntry()); ++ } ++ } ++ } ++ } ++ } ++ ++ //2: add gossips ++ ++ if (itemList.empty()) ++ { ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Hm... I have nothing to give you", 0, GOSSIP_ACTION_INFO_DEF + 1); ++ } ++ else ++ { ++ uint32 counter = 0; ++ uint32 maxcounter = GOSSIP_MAX_MENU_ITEMS - 1; // back ++ Item* item; ++ //add items as gossip options ++ for (std::set::const_iterator itr = itemList.begin(); itr != itemList.end() && counter < maxcounter; ++itr) ++ { ++ bool found = false; ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); ++ if (item && item->GetGUID().GetCounter() == (*itr)) ++ { ++ uint8 k = 0; ++ for (; k != BOT_INVENTORY_SIZE; ++k) ++ if (_canEquip(item->GetTemplate(), k + 1)) ++ break; ++ ++ std::ostringstream name; ++ _AddItemLink(player, item, name); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP + k, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); ++ ++counter; ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) ++ continue; ++ ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ item = player->GetItemByPos(i, j); ++ if (item && item->GetGUID().GetCounter() == (*itr)) ++ { ++ uint8 k = 0; ++ for (; k != BOT_INVENTORY_SIZE; ++k) ++ if (_canEquip(item->GetTemplate(), k + 1)) ++ break; ++ ++ std::ostringstream name; ++ _AddItemLink(player, item, name); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP + k, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); ++ ++counter; ++ found = true; ++ break; ++ } ++ } ++ } ++ ++ if (found) ++ break; ++ } ++ ++ if (found) ++ continue; ++ } ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 2); ++ break; ++ } ++ case GOSSIP_SENDER_EQUIP_RESET: //equips change s4a: reset equipment ++ { ++ if (_resetEquipment(action - (GOSSIP_ACTION_INFO_DEF + 1))){} ++ break; ++ } ++ //equips change s4b: Equip item ++ //base is GOSSIP_SENDER_EQUIP + 0...1...2... etc. ++ case GOSSIP_SENDER_EQUIP_MHAND: //1 - 1 main hand ++ case GOSSIP_SENDER_EQUIP_OHAND: //2 - 1 off hand ++ case GOSSIP_SENDER_EQUIP_RANGED: //3 - 1 ranged ++ case GOSSIP_SENDER_EQUIP_HEAD: //4 - 1 head ++ case GOSSIP_SENDER_EQUIP_SHOULDERS: //5 - 1 shoulders ++ case GOSSIP_SENDER_EQUIP_CHEST: //6 - 1 chest ++ case GOSSIP_SENDER_EQUIP_WAIST: //7 - 1 waist ++ case GOSSIP_SENDER_EQUIP_LEGS: //8 - 1 legs ++ case GOSSIP_SENDER_EQUIP_FEET: //9 - 1 feet ++ case GOSSIP_SENDER_EQUIP_WRIST: //10 - 1 wrist ++ case GOSSIP_SENDER_EQUIP_HANDS: //11 - 1 hands ++ case GOSSIP_SENDER_EQUIP_BACK: //12 - 1 back ++ case GOSSIP_SENDER_EQUIP_BODY: //13 - 1 body ++ case GOSSIP_SENDER_EQUIP_FINGER1: //14 - 1 finger ++ case GOSSIP_SENDER_EQUIP_FINGER2: //15 - 1 finger ++ case GOSSIP_SENDER_EQUIP_TRINKET1: //16 - 1 trinket ++ case GOSSIP_SENDER_EQUIP_TRINKET2: //17 - 1 trinket ++ case GOSSIP_SENDER_EQUIP_NECK: //18 - 1 neck ++ { ++ Item* item = NULL; ++ uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF; ++ ++ bool found = false; ++ for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; i++) ++ { ++ item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); ++ if (item && item->GetGUID().GetCounter() == guidLow) ++ { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ { ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; i++) ++ { ++ if (Bag* pBag = player->GetBagByPos(i)) ++ { ++ for (uint32 j = 0; j != pBag->GetBagSize(); j++) ++ { ++ item = player->GetItemByPos(i, j); ++ if (item && item->GetGUID().GetCounter() == guidLow) ++ { ++ found = true; ++ break; ++ } ++ } ++ } ++ ++ if (found) ++ break; ++ } ++ } ++ ++ if (found && _equip(sender - GOSSIP_SENDER_EQUIP, item)){} ++ break; ++ } ++ case GOSSIP_SENDER_ROLES_TOGGLE: //ROLES 2: set/unset ++ { ++ ToggleRole(action - GOSSIP_ACTION_INFO_DEF, false); ++ ++ //break; ++ } ++ case GOSSIP_SENDER_ROLES: //ROLES 1: list ++ { ++ subMenu = true; ++ ++ uint8 role = BOT_ROLE_TANK; ++ ++ for (; role != BOT_MAX_ROLE; role <<= 1) ++ { ++ if (role == BOT_ROLE_PARTY) //hidden ++ continue; ++ if (role == BOT_ROLE_HEAL && !CanHeal()) ++ continue; ++ ++ player->ADD_GOSSIP_ITEM(_onOffIcon(role), GetRoleString(role), GOSSIP_SENDER_ROLES_TOGGLE, GOSSIP_ACTION_INFO_DEF + role); ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + role + 1); ++ ++ break; ++ } ++ case GOSSIP_SENDER_ABILITIES_USE: ++ { ++ if (uint32 basespell = action - GOSSIP_ACTION_INFO_DEF) ++ //if (CheckBotCast(me, basespell, me->GetBotClass()) == SPELL_CAST_OK) ++ if (IsSpellReady(basespell, lastdiff, true)) ++ doCast(player, GetSpell(basespell)); ++ ++ //break; ++ } ++ case GOSSIP_SENDER_ABILITIES: ++ { ++ subMenu = true; ++ ++ uint32 basespell; ++ SpellInfo const* spellInfo; ++ BotSpellMap const& myspells = GetSpellMap(); ++ for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) ++ { ++ //if (currentSpell == itr->second.first) continue; //prevent spam ++ basespell = itr->first; //always valid ++ if (!CanUseManually(basespell)) continue; ++ if (!IsSpellReady(basespell, 0, false, 5000)) continue; ++ spellInfo = sSpellMgr->GetSpellInfo(basespell); //always valid ++ ++ std::ostringstream name; ++ _AddSpellLink(player, spellInfo, name); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TRAINER, name.str().c_str(), GOSSIP_SENDER_ABILITIES_USE, GOSSIP_ACTION_INFO_DEF + basespell); ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Update", GOSSIP_SENDER_ABILITIES_USE, GOSSIP_ACTION_INFO_DEF); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 2); ++ ++ break; ++ } ++ case GOSSIP_SENDER_HIRE: ++ { ++ int32 reason = action - GOSSIP_ACTION_INFO_DEF; ++ if (!reason) ++ { ++ if (_ownerGuid) ++ { ++ std::ostringstream ostr; ++ std::string name; ++ ostr << "Go away. I serve my master "; ++ if (sObjectMgr->GetPlayerNameByGUID(ObjectGuid(HighGuid::Player, _ownerGuid), name)) ++ ostr << name; ++ else ++ ostr << "unknown (" << _ownerGuid << ')'; ++ BotSay(ostr.str().c_str(), player); ++ ChatHandler ch(player->GetSession()); ++ ch.PSendSysMessage("%s will not join you until owner dismisses %s", me->GetName().c_str(), (me->getGender() == GENDER_MALE ? "him" : "her")); ++ break; ++ } ++ ++ if (SetBotOwner(player)) ++ BotWhisper("I am ready", player); ++ else ++ BotSay("...", player); ++ } ++ else if (reason == -1) ++ { ++ me->setFaction(14); ++ if (Creature* pet = me->GetBotsPet()) ++ pet->setFaction(14); ++ BotYell("Die!", player); ++ me->Attack(player, IsMelee()); ++ break; ++ } ++ else ++ { ++ ChatHandler ch(player->GetSession()); ++ switch (reason) ++ { ++ case 1: //has owner, unexpected ++ ch.PSendSysMessage("%s will not join you, already has master: %s", ++ //me->GetName().c_str(), master->GetName().c_str()); ++ me->GetName().c_str(), me->GetBotOwner()->GetName().c_str()); ++ break; ++ case 2: //max npcbots exceed ++ ch.PSendSysMessage("You exceed max npcbots (%u)", BotMgr::GetMaxNpcBots()); ++ break; ++ case 3: //not enough money ++ { ++ std::string str = "You don't have enough money ("; ++ str += BotMgr::GetNpcBotCostStr(player->getLevel(), me); ++ str += ")!"; ++ ch.SendSysMessage(str.c_str()); ++ player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); ++ break; ++ } ++ case 4: //class bots exceed ++ { ++ uint8 count = 0; ++ BotMap const* map = player->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ if (itr->second->GetBotClass() == GetBotClass()) ++ ++count; ++ ++ ch.PSendSysMessage("You cannot have more bots of that class! %u of %u", ++ count, _maxClassNpcBots); ++ break; ++ } ++ default: ++ break; ++ } ++ ++ BotSay("...", player); ++ } ++ break; ++ } ++ case GOSSIP_SENDER_DISMISS: ++ { ++ BotMgr* mgr = player->GetBotMgr(); ++ ASSERT(mgr); ++ ++ //send items to owner -- Unequip all ++ bool abort = false; ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ if (!(i < BOT_SLOT_RANGED ? _resetEquipment(i) : _unequip(i))) ++ { ++ std::ostringstream estr; ++ estr << "Cannot reset equipment in slot " << uint32(i) << " (" << _getNameForSlot(i + 1) << ")! Cannot dismiss bot!"; ++ ChatHandler ch(player->GetSession()); ++ ch.SendSysMessage(estr.str().c_str()); ++ abort = true; ++ break; ++ } ++ } ++ ++ if (abort) ++ break; ++ ++ mgr->RemoveBot(me->GetGUID(), BOT_REMOVE_DISMISS); ++ if (Aura* bers = me->AddAura(BERSERK, me)) ++ { ++ uint32 dur = 1 * HOUR * IN_MILLISECONDS; ++ bers->SetDuration(dur); ++ bers->SetMaxDuration(dur); ++ } ++ if (urand(1,100) <= 25) ++ { ++ me->setFaction(14); ++ if (Creature* pet = me->GetBotsPet()) ++ pet->setFaction(14); ++ BotSay("Fool...", player); ++ me->Attack(player, IsMelee()); ++ } ++ else ++ BotSay("...", player); ++ ++ break; ++ } ++ case GOSSIP_SENDER_JOIN_GROUP: ++ { ++ player->GetBotMgr()->AddBotToGroup(me); ++ break; ++ } ++ case GOSSIP_SENDER_LEAVE_GROUP: ++ { ++ player->GetBotMgr()->RemoveBotFromGroup(me); ++ break; ++ } ++ case GOSSIP_SENDER_FORMATION: ++ { ++ subMenu = true; ++ std::ostringstream diststr; ++ diststr << "Set distance (current: " << uint32(player->GetBotFollowDist()) << ')'; ++ player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, diststr.str(), ++ GOSSIP_SENDER_FORMATION_DISTANCE, GOSSIP_ACTION_INFO_DEF + 1, "", 0, true); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 2); ++ break; ++ } ++ case GOSSIP_SENDER_DEBUG_ACTION: ++ { ++ //!!! player != owner !!! ++ bool close = true; ++ switch (action - GOSSIP_ACTION_INFO_DEF) ++ { ++ case 1: //reset owner ++ if (!IAmFree()) ++ master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_DISMISS); ++ else ++ { ++ ResetBotAI(BOTAI_RESET_DISMISS); ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); ++ //"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, uint32(0)); ++ stmt->setUInt32(1, me->GetEntry()); ++ CharacterDatabase.Execute(stmt); ++ } ++ break; ++ case 2: //reset stats ++ spawned = false; ++ DefaultInit(); ++ break; ++ case 3: //list stats ++ close = false; ++ ReceiveEmote(player, TEXT_EMOTE_BONK); ++ break; ++ case 4: //list roles ++ { ++ close = false; ++ ChatHandler ch(player->GetSession()); ++ ch.PSendSysMessage("%s's Roles:", me->GetName().c_str()); ++ for (uint8 i = BOT_MAX_ROLE; i != BOT_ROLE_NONE; i >>= 1) ++ { ++ if (_roleMask & i) ++ { ++ switch (i) ++ { ++ case BOT_ROLE_TANK: ++ ch.PSendSysMessage("BOT_ROLE_TANK"); ++ break; ++ case BOT_ROLE_DPS: ++ ch.PSendSysMessage("BOT_ROLE_DPS"); ++ break; ++ case BOT_ROLE_HEAL: ++ ch.PSendSysMessage("BOT_ROLE_HEAL"); ++ break; ++ //case BOT_ROLE_MELEE: ++ // ch.PSendSysMessage("BOT_ROLE_MELEE"); ++ // break; ++ case BOT_ROLE_RANGED: ++ ch.PSendSysMessage("BOT_ROLE_RANGED"); ++ break; ++ case BOT_ROLE_PARTY: ++ ch.PSendSysMessage("BOT_ROLE_PARTY"); ++ break; ++ } ++ } ++ } ++ break; ++ } ++ case 5: //list spells ++ { ++ close = false; ++ ChatHandler ch(player->GetSession()); ++ ch.PSendSysMessage("%s's Spells:", me->GetName().c_str()); ++ uint32 counter = 0; ++ SpellInfo const* spellInfo; ++ BotSpellMap const& myspells = GetSpellMap(); ++ for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) ++ { ++ ++counter; ++ std::ostringstream sstr; ++ spellInfo = sSpellMgr->GetSpellInfo(itr->first); //always valid ++ _AddSpellLink(player, spellInfo, sstr); ++ sstr << " id: " << itr->second.first << ", base: " << itr->first ++ << ", cd: " << itr->second.second << ", base: " << std::max(spellInfo->RecoveryTime, spellInfo->CategoryRecoveryTime); ++ ch.PSendSysMessage("%u) %s", counter, sstr.str().c_str()); ++ } ++ break; ++ } ++ case 6: //reload config ++ { ++ close = false; ++ ChatHandler ch(player->GetSession()); ++ ++ TC_LOG_INFO("misc", "Re-Loading config settings..."); ++ sWorld->LoadConfigSettings(true); ++ sMapMgr->InitializeVisibilityDistanceInfo(); ++ ch.SendGlobalGMSysMessage("World config settings reloaded."); ++ BotMgr::ReloadConfig(); ++ ch.SendGlobalGMSysMessage("NpcBot config settings reloaded."); ++ ++ break; ++ } ++ default: ++ close = false; ++ break; ++ } ++ ++ if (close) ++ break; ++ } ++ case GOSSIP_SENDER_DEBUG: ++ { ++ //!!! player != owner !!! ++ subMenu = true; ++ ++ std::ostringstream ostr; ++ std::string name; ++ ostr << "Bot: " << me->GetName() ++ << " (Id: " << me->GetEntry() ++ << ", guidlow: " << me->GetGUID().GetCounter() ++ << ", faction: " << me->getFaction() ++ << "). owner: "; ++ if (_ownerGuid && sObjectMgr->GetPlayerNameByGUID(ObjectGuid(HighGuid::Player, _ownerGuid), name)) ++ ostr << name << " (" << _ownerGuid << ')'; ++ else ++ ostr << "none"; ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, ostr.str().c_str(), GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 0); ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 2); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 3); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 4); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 5); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 6); ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 1); ++ break; ++ } ++ case GOSSIP_SENDER_SCAN: ++ { ++ //!!! player != owner !!! ++ subMenu = true; ++ ++ //ListAbilities(true); ++ switch (_botclass) ++ { ++ case BOT_CLASS_BM: ++ gossipTextId = GOSSIP_CLASS_BM; ++ break; ++ default: ++ break; ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ break; ++ } ++ case GOSSIP_SENDER_SCAN_OWNER_ABILITY: ++ { ++ uint32 Id = action - GOSSIP_ACTION_INFO_DEF; ++ SpellInfo const* info = sSpellMgr->GetSpellInfo(Id); ++ ASSERT(info); ++ ++ ChatHandler ch(player->GetSession()); ++ std::ostringstream smsg1, smsg2; ++ switch (Id) ++ { ++ //Blademaster ++ case SPELL_NETHERWALK: ++ _AddSpellLink(player, info, smsg1, false, "ffffff00"/*yellow*/); ++ smsg2 << " Invisibilty: |cff00ff00" << uint32(100 + (me->getLevel() * 5) / 2) << "|r, speed: +|cff00ff00" << uint32(10 + me->getLevel() / 2) << "|r%, |cff00ff00150|r% normal damage"; ++ ch.SendSysMessage(smsg1.str().c_str()); ++ ch.SendSysMessage("Allows Blademaster to become invisible, and move faster for a set amount of time. When the Blademaster attacks a unit to break invisibility, he will deal bonus damage."); ++ ch.SendSysMessage(smsg2.str().c_str()); ++ break; ++ case SPELL_MIRROR_IMAGE_BM: ++ _AddSpellLink(player, info, smsg1, false, "ffffff00"/*yellow*/); ++ smsg2 << " |cff00ff00" << uint32(GetSpellMiscValue(Id)) << "|r " << (GetSpellMiscValue(Id) == 1 ? "illusion" : "illusions"); ++ ch.SendSysMessage(smsg1.str().c_str()); ++ ch.SendSysMessage("Confuses the enemy by creating illusions of the Blademaster and dispelling all magic from the Blademaster."); ++ ch.SendSysMessage(smsg2.str().c_str()); ++ break; ++ case SPELL_CRITICAL_STRIKE: ++ _AddSpellLink(player, info, smsg1, false, "ffff0000"/*red*/); ++ smsg1 << " |cffffff00(Passive)|r"; ++ smsg2 << " |cff00ff0015|r% chance to deal |cff00ff00" << uint32(GetSpellMiscValue(Id)) << "|r times normal damage"; ++ ch.SendSysMessage(smsg1.str().c_str()); ++ ch.SendSysMessage("Gives a 15% chance that the Blademaster will do more damage on his attacks."); ++ ch.SendSysMessage(smsg2.str().c_str()); ++ break; ++ //case SPELL_BLADESTORM_BM: TODO: ++ default: ++ break; ++ } ++ ++ //break; ++ } ++ case GOSSIP_SENDER_SCAN_OWNER: ++ { ++ subMenu = true; ++ ++ std::ostringstream abmsg1, abmsg2, abmsg3/*, abmsg4*/; ++ switch (_botclass) ++ { ++ case BOT_CLASS_BM: ++ if (me->getLevel() >= 10) ++ { ++ _AddSpellLink(player, sSpellMgr->GetSpellInfo(SPELL_NETHERWALK), abmsg1); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, abmsg1.str().c_str(), GOSSIP_SENDER_SCAN_OWNER_ABILITY, GOSSIP_ACTION_INFO_DEF + SPELL_NETHERWALK); ++ } ++ if (me->getLevel() >= 20) ++ { ++ _AddSpellLink(player, sSpellMgr->GetSpellInfo(SPELL_MIRROR_IMAGE_BM), abmsg2); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, abmsg2.str().c_str(), GOSSIP_SENDER_SCAN_OWNER_ABILITY, GOSSIP_ACTION_INFO_DEF + SPELL_MIRROR_IMAGE_BM); ++ } ++ if (me->getLevel() >= 10) ++ { ++ _AddSpellLink(player, sSpellMgr->GetSpellInfo(SPELL_CRITICAL_STRIKE), abmsg3); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, abmsg3.str().c_str(), GOSSIP_SENDER_SCAN_OWNER_ABILITY, GOSSIP_ACTION_INFO_DEF + SPELL_CRITICAL_STRIKE); ++ } ++ //TODO: ++ //_AddSpellLink(player, sSpellMgr->GetSpellInfo(SPELL_CRITICAL_STRIKE), abmsg4); ++ //player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, abmsg4.str().c_str(), GOSSIP_SENDER_SCAN_OWNER_ABILITY, GOSSIP_ACTION_INFO_DEF + SPELL_BLADESTORM_BM); ++ break; ++ default: ++ break; ++ } ++ ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "BACK", 1, GOSSIP_ACTION_INFO_DEF + 1); ++ ++ break; ++ } ++ default: ++ break; ++ } ++ ++ //if we add submenus send them else we should return ++ if (subMenu) ++ player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); ++ else ++ player->CLOSE_GOSSIP_MENU(); ++ ++ return true; ++} ++ ++//GossipSelectCode ++bool bot_minion_ai::OnGossipSelectCode(Player* player, Creature* creature/* == me*/, uint32 sender, uint32 action, char const* code) ++{ ++ if (!*code) ++ return true; ++ ++ if (!_enableNpcBots || CCed(me) || IsDuringTeleport()) ++ { ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++ } ++ ++ uint32 gossipTextId = (player->GetGUID().GetCounter() == _ownerGuid || !IAmFree()) ? GOSSIP_SERVE_MASTER : GOSSIP_NEED_SMTH; ++ ++ player->PlayerTalkClass->ClearMenus(); ++ ++ bool subMenu = false; ++ ++ switch (sender) ++ { ++ case GOSSIP_SENDER_FORMATION_DISTANCE: ++ { ++ char* dist = strtok((char*)code, ""); ++ int8 distance = std::min((uint8)atoi(dist), 75); ++ ++ player->SetBotFollowDist(distance); ++ ++ player->CLOSE_GOSSIP_MENU(); ++ return OnGossipSelect(player, creature, GOSSIP_SENDER_FORMATION, action); ++ } ++ default: ++ break; ++ } ++ ++ if (subMenu) ++ player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); ++ else ++ player->CLOSE_GOSSIP_MENU(); ++ return true; ++} ++//Summons pet for bot ++void bot_minion_ai::SummonBotsPet(uint32 entry) ++{ ++ Creature* m_botsPet = me->GetBotsPet(); ++ if (m_botsPet) ++ me->SetBotsPetDied(); ++ ++ uint8 mylevel = std::min(master->getLevel(), 80); ++ uint32 originalentry = bot_pet_ai::GetPetOriginalEntry(entry); ++ if (!originalentry) ++ { ++ //annoy master ++ if (!IAmFree()) ++ BotWhisper("Why am I trying to summon unknown pet!?", master); ++ return; ++ } ++ float x(0),y(0),z(0); ++ me->GetClosePoint(x, y, z, me->GetObjectSize()); ++ m_botsPet = me->SummonCreature(entry, x, y, z, 0, TEMPSUMMON_DEAD_DESPAWN); ++ ++ if (!m_botsPet) ++ { ++ if (!IAmFree()) ++ BotWhisper("Failed to summon pet!", master); ++ return; ++ } ++ ++ //std::string name = sObjectMgr->GeneratePetName(originalentry);//voidwalker ++ //if (!name.empty()) ++ // m_botsPet->SetName(name); ++ ++ PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_NPCBOT_PET_LEVELSTATS); ++ stmt->setUInt32(0, originalentry); ++ stmt->setUInt8(1, mylevel); ++ PreparedQueryResult result = WorldDatabase.Query(stmt); ++ //QueryResult result = WorldDatabase.PQuery("SELECT hp, mana, armor, str, agi, sta, inte, spi FROM `pet_levelstats` WHERE `creature_entry` = '%u' AND `level` = '%u'", originalentry, mylevel); ++ ++ if (result) ++ { ++ Field* fields = result->Fetch(); ++ uint32 hp = fields[0].GetUInt16(); ++ uint32 mana = fields[1].GetUInt16(); ++ //armor = fields[2].GetUInt32(); ++ uint32 str = fields[3].GetUInt16(); ++ uint32 agi = fields[4].GetUInt16(); ++ uint32 sta = fields[5].GetUInt16(); ++ uint32 inte = fields[6].GetUInt16(); ++ uint32 spi = fields[7].GetUInt16(); ++ ++ m_botsPet->SetCreateHealth(hp); ++ m_botsPet->SetMaxHealth(hp); ++ m_botsPet->SetCreateMana(mana); ++ m_botsPet->SetMaxPower(POWER_MANA, mana); ++ ++ m_botsPet->SetCreateStat(STAT_STRENGTH, str); ++ m_botsPet->SetCreateStat(STAT_AGILITY, agi); ++ m_botsPet->SetCreateStat(STAT_STAMINA, sta); ++ m_botsPet->SetCreateStat(STAT_INTELLECT, inte); ++ m_botsPet->SetCreateStat(STAT_SPIRIT, spi); ++ } ++ ++ m_botsPet->SetBotOwner(master); ++ m_botsPet->SetCreatureOwner(me); ++ //m_botsPet->SetBotClass(bot_pet_ai::GetPetClass(m_botsPet)); ++ master->SetMinion((Minion*)m_botsPet, true); ++ m_botsPet->SetGuidValue(UNIT_FIELD_CREATEDBY, me->GetGUID()); ++ //m_botsPet->DeleteThreatList(); ++ m_botsPet->AddUnitTypeMask(UNIT_MASK_MINION); ++ //m_botsPet->SetLevel(master->getLevel()); ++ m_botsPet->AIM_Initialize(); ++ //m_botsPet->InitBotAI(true); ++ m_botsPet->setFaction(master->getFaction()); ++ //bot_pet_ai* petai = m_botsPet->GetBotPetAI(); ++ //petai->SetCreatureOwner(me); ++ //petai->SetBaseArmor(armor); ++ m_botsPet->SetBotCommandState(COMMAND_FOLLOW, true); ++ ++ me->SetBotsPet(m_botsPet); ++ ++ m_botsPet->SendUpdateToPlayer(master); ++} ++ ++//Returns pet type (maybe unneeded) ++uint8 bot_pet_ai::GetPetType(Creature* pet) ++{ ++ switch (pet->GetEntry()) ++ { ++ case PET_VOIDWALKER: ++ return PET_TYPE_VOIDWALKER; ++ } ++ return PET_TYPE_NONE; ++} ++//Returns pet's class ++uint8 bot_pet_ai::GetPetClass(Creature* pet) ++{ ++ switch (GetPetType(pet)) ++ { ++ case PET_TYPE_IMP: ++ return BOT_CLASS_MAGE; ++ default: ++ return BOT_CLASS_PALADIN; ++ } ++} ++//Return entry used to summon real pets ++uint32 bot_pet_ai::GetPetOriginalEntry(uint32 entry) ++{ ++ switch (entry) ++ { ++ case PET_VOIDWALKER: ++ return ORIGINAL_ENTRY_VOIDWALKER; ++ default: ++ return 0; ++ } ++} ++//PvP trinket for minions ++void bot_minion_ai::BreakCC(uint32 diff) ++{ ++ if (pvpTrinket_cd <= diff && CCed(me, true) && (me->GetVictim() || !me->getAttackers().empty())) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, PVPTRINKET)) ++ { ++ pvpTrinket_cd = 120000; //2 minutes default pvp trinket CD ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++} ++//Returns attack range based on given range ++//If mounted: 20% ++//If ranged: 125% ++//If master is dead: max range ++float bot_ai::InitAttackRange(float origRange, bool ranged) const ++{ ++ if (me->IsMounted()) ++ origRange *= 0.2f; ++ else ++ { ++ if (ranged) ++ origRange *= 1.25f; ++ if (master->isDead()) ++ origRange += sWorld->GetMaxVisibleDistanceOnContinents(); ++ else if (IAmFree()) ++ { ++ origRange = ++ //me->GetMap()->IsBattlegroundOrArena() ? sWorld->GetMaxVisibleDistanceInBGArenas() : ++ //me->GetMap()->Instanceable() ? sWorld->GetMaxVisibleDistanceInInstances() : ++ sWorld->GetMaxVisibleDistanceOnContinents(); ++ } ++ } ++ return origRange; ++} ++//Force bots to start attack anyone who tries to DAMAGE me or master ++//This means that anyone who attacks party will be attacked by whole bot party (see GetTarget()) ++void bot_minion_ai::OnOwnerDamagedBy(Unit* attacker) ++{ ++ if (me->GetVictim() && (!IAmFree() || me->GetDistance(me->GetVictim()) < me->GetDistance(attacker))) ++ return; ++ if (InDuel(attacker)) ++ return; ++ ++ bool byspell = false; ++ bool ranged = !IsMelee(); ++ switch (_botclass) ++ { ++ case BOT_CLASS_DRUID: ++ byspell = GetBotStance() == BOT_STANCE_NONE || GetBotStance() == DRUID_MOONKIN_FORM; ++ break; ++ case BOT_CLASS_PRIEST: ++ case BOT_CLASS_MAGE: ++ case BOT_CLASS_WARLOCK: ++ case BOT_CLASS_SHAMAN: ++ byspell = true; ++ break; ++ default: ++ //TC_LOG_ERROR("entities.player", "minion_ai: OnOwnerDamagedBy() - unknown bot class %u", uint8(_botclass)); ++ break; ++ } ++ float maxdist = InitAttackRange(float(IAmFree() ? 100 : master->GetBotFollowDist()), ranged); //use increased range ++ if (!attacker->IsWithinDist(me, maxdist)) ++ return; ++ if (!CanBotAttack(attacker, byspell)) ++ return; ++ ++ m_botCommandState = COMMAND_ABANDON; //reset AttackStart() ++ me->Attack(attacker, !ranged); ++} ++ ++bool bot_minion_ai::_canUseOffHand() const ++{ ++ if (_botclass == BOT_CLASS_BM) ++ return false; ++ ++ //warriot can wield any offhand with titan's grip ++ if (_botclass == BOT_CLASS_WARRIOR && me->getLevel() >= 60) ++ return true; ++ ++ //no offhand: check we are using one-handed weapon in main hand ++ if (!_equips[1]) ++ { ++ ItemTemplate const* proto = _equips[0] ? _equips[0]->GetTemplate() : NULL; ++ //no mainhand weapon - can use offhand ++ //mainhand is an one-hand weapon ++ if (!proto) ++ return true; ++ else if (proto->Class == ITEM_CLASS_WEAPON && ++ (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_FIST || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD)) ++ return true; ++ } ++ else if (ItemTemplate const* proto = _equips[1]->GetTemplate()) ++ { ++ //Now we have something in off-hand ++ //1 check if it is one-handed weapon ++ if (proto->Class == ITEM_CLASS_WEAPON && ++ (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_FIST || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD)) ++ return true; ++ //2 check of it is a shield ++ if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) ++ return true; ++ //3 check of it is a 'held in off-hand' item ++ if (proto->InventoryType == INVTYPE_HOLDABLE) ++ return true; ++ } ++ ++ //NO ++ return false; ++} ++ ++bool bot_minion_ai::_canUseRanged() const ++{ ++ return (_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || ++ _botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_PRIEST || ++ _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK); ++} ++//slot = BotEquipSlot ++bool bot_minion_ai::_canEquip(ItemTemplate const* item, uint8 slot, bool ignoreItemLevel) const ++{ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ++ if (Item* oldItem = _equips[slot - 1]) ++ { ++ ItemTemplate const* oldProto = oldItem->GetTemplate(); ++ //prevent reequipping same items ++ if (item->ItemId == oldProto->ItemId) ++ return false; ++ //prevent equipping worse items (only standard or not) ++ if (!ignoreItemLevel) ++ if (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot - 1] != oldProto->ItemId) ++ if (IAmFree() || !master->IsGameMaster()) ++ if (oldProto->GetItemLevelIncludingQuality() > item->GetItemLevelIncludingQuality()) ++ return false; ++ } ++ ++ //level requirements ++ if (me->getLevel() < item->RequiredLevel) ++ return false; ++ ++ //class requirements ++ if (!(item->AllowableClass & (1<<(GetPlayerClass()-1)))) ++ return false; ++ ++ //skip race requirements ++ //Weapons requirements ++ if (item->Class == ITEM_CLASS_WEAPON) ++ { ++ if (slot > BOT_SLOT_RANGED) ++ return false; ++ //polearms cannot be equipped into offhand ++ if (slot == BOT_SLOT_OFFHAND && item->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) ++ return false; ++ //only some classes can use offhand ++ if (slot == BOT_SLOT_OFFHAND && _botclass != BOT_CLASS_WARRIOR && ++ _botclass != BOT_CLASS_ROGUE && _botclass != BOT_CLASS_HUNTER && ++ _botclass != BOT_CLASS_SHAMAN && _botclass != BOT_CLASS_DEATH_KNIGHT) ++ return false; ++ //bot rogues only use daggers in mainhand ++ if (slot == BOT_SLOT_MAINHAND && item->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && ++ _botclass == BOT_CLASS_ROGUE) ++ return false; ++ //simple check for ranged weapon ++ if (item->InventoryType == INVTYPE_THROWN || ++ item->InventoryType == INVTYPE_RANGED || ++ item->InventoryType == INVTYPE_RANGEDRIGHT) ++ { ++ if (slot != BOT_SLOT_RANGED || !_canUseRanged()) ++ return false; ++ ++ if (item->SubClass == ITEM_SUBCLASS_WEAPON_GUN || ++ item->SubClass == ITEM_SUBCLASS_WEAPON_BOW || ++ item->SubClass == ITEM_SUBCLASS_WEAPON_CROSSBOW) ++ return (_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || ++ _botclass == BOT_CLASS_WARRIOR/* || _botclass == BOT_CLASS_PRIEST || ++ _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK*/); ++ ++ if (item->SubClass == ITEM_SUBCLASS_WEAPON_THROWN) ++ return (/*_botclass == BOT_CLASS_HUNTER || */_botclass == BOT_CLASS_ROGUE || ++ _botclass == BOT_CLASS_WARRIOR/* || _botclass == BOT_CLASS_PRIEST || ++ _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK*/); ++ ++ if (item->SubClass == ITEM_SUBCLASS_WEAPON_WAND) ++ return (/*_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || ++ _botclass == BOT_CLASS_WARRIOR || */_botclass == BOT_CLASS_PRIEST || ++ _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK); ++ } ++ else if (item->InventoryType == INVTYPE_2HWEAPON) ++ { ++ //warriors can equip any 2H weapon in any hand ++ if (_botclass == BOT_CLASS_WARRIOR && me->getLevel() >= 60 && ++ (slot == BOT_SLOT_MAINHAND || slot == BOT_SLOT_OFFHAND)) ++ return true; ++ //2H weapons for casters - only staves ++ if ((_botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_PRIEST || ++ _botclass == BOT_CLASS_WARLOCK || _botclass == BOT_CLASS_DRUID) && ++ item->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) ++ return false; ++ //can equip 2H only in mainhand ++ return (slot == BOT_SLOT_MAINHAND); ++ } ++ else if (item->InventoryType == INVTYPE_WEAPON || ++ item->InventoryType == INVTYPE_WEAPONMAINHAND || ++ item->InventoryType == INVTYPE_WEAPONOFFHAND) ++ { ++ //separate classes which can equip weapons or shields in offhand ++ return (slot == BOT_SLOT_MAINHAND || ++ (slot == BOT_SLOT_OFFHAND/* && _botclass != BOT_CLASS_PALADIN*/ && _canUseOffHand())); ++ } ++ } ++ else if (item->Class == ITEM_CLASS_ARMOR) ++ { ++ //conditions for inventory slots ++ switch (item->InventoryType) ++ { ++ case INVTYPE_HEAD: ++ if (slot != BOT_SLOT_HEAD) ++ return false; ++ break; ++ case INVTYPE_SHOULDERS: ++ if (slot != BOT_SLOT_SHOULDERS) ++ return false; ++ break; ++ case INVTYPE_BODY: ++ if (slot != BOT_SLOT_BODY) ++ return false; ++ break; ++ case INVTYPE_CHEST: ++ case INVTYPE_ROBE: ++ if (slot != BOT_SLOT_CHEST) ++ return false; ++ break; ++ case INVTYPE_WAIST: ++ if (slot != BOT_SLOT_WAIST) ++ return false; ++ break; ++ case INVTYPE_LEGS: ++ if (slot != BOT_SLOT_LEGS) ++ return false; ++ break; ++ case INVTYPE_FEET: ++ if (slot != BOT_SLOT_FEET) ++ return false; ++ break; ++ case INVTYPE_WRISTS: ++ if (slot != BOT_SLOT_WRIST) ++ return false; ++ break; ++ case INVTYPE_HANDS: ++ if (slot != BOT_SLOT_HANDS) ++ return false; ++ break; ++ case INVTYPE_FINGER: ++ if (slot != BOT_SLOT_FINGER1 && slot != BOT_SLOT_FINGER2) ++ return false; ++ break; ++ case INVTYPE_TRINKET: ++ if (slot != BOT_SLOT_TRINKET1 && slot != BOT_SLOT_TRINKET2) ++ return false; ++ break; ++ case INVTYPE_NECK: ++ if (slot != BOT_SLOT_NECK) ++ return false; ++ break; ++ case INVTYPE_CLOAK: ++ if (slot != BOT_SLOT_BACK) ++ return false; ++ break; ++ case INVTYPE_HOLDABLE: ++ case INVTYPE_SHIELD: ++ if (slot != BOT_SLOT_OFFHAND) ++ return false; ++ break; ++ case INVTYPE_RELIC: ++ if (slot != BOT_SLOT_RANGED) ++ return false; ++ break; ++ default: ++ break; ++ } ++ ++ //Shields ++ if (item->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) ++ { ++ if (slot == BOT_SLOT_OFFHAND) //wtf? mainhand shield? ++ { ++ //Only classes which can use shield ++ return _canUseOffHand() && ++ (_botclass == BOT_CLASS_WARRIOR || ++ _botclass == BOT_CLASS_PALADIN || ++ _botclass == BOT_CLASS_SHAMAN); ++ } ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_PLATE) ++ { ++ //Plate wearers ++ return (me->getLevel() >= 40 && ++ (_botclass == BOT_CLASS_WARRIOR || ++ _botclass == BOT_CLASS_DEATH_KNIGHT || ++ _botclass == BOT_CLASS_PALADIN || ++ _botclass == BOT_CLASS_BM)); ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_MAIL) ++ { ++ //has mail skill by default ++ if (_botclass == BOT_CLASS_WARRIOR || ++ _botclass == BOT_CLASS_DEATH_KNIGHT || ++ _botclass == BOT_CLASS_PALADIN || ++ _botclass == BOT_CLASS_BM) ++ return true; ++ //Mail wearers ++ return (me->getLevel() >= 40 && ++ (_botclass == BOT_CLASS_SHAMAN || ++ _botclass == BOT_CLASS_HUNTER)); ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_LEATHER) ++ { ++ //exclude classes which can never use leather ++ return (_botclass != BOT_CLASS_WARLOCK && ++ _botclass != BOT_CLASS_MAGE && ++ _botclass != BOT_CLASS_PRIEST); ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_CLOTH) ++ { ++ //All classes can wear cloth lol ++ return true; ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_MISC) ++ { ++ if (item->InventoryType == INVTYPE_FEET && slot == BOT_SLOT_FEET) ++ return true; ++ if (item->InventoryType == INVTYPE_BODY && slot == BOT_SLOT_BODY) ++ return true; ++ if (item->InventoryType == INVTYPE_FINGER && ++ (slot == BOT_SLOT_FINGER1 || slot == BOT_SLOT_FINGER2)) ++ return true; ++ if (item->InventoryType == INVTYPE_TRINKET && ++ (slot == BOT_SLOT_TRINKET1 || slot == BOT_SLOT_TRINKET2)) ++ return true; ++ if (item->InventoryType == INVTYPE_NECK && slot == BOT_SLOT_NECK) ++ return true; ++ if (item->InventoryType == INVTYPE_HOLDABLE && slot == BOT_SLOT_OFFHAND && _canUseOffHand()) ++ return true; ++ } ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_LIBRAM) ++ return _botclass == BOT_CLASS_PALADIN && slot == BOT_SLOT_RANGED; ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_IDOL) ++ return _botclass == BOT_CLASS_DRUID && slot == BOT_SLOT_RANGED; ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_TOTEM) ++ return _botclass == BOT_CLASS_SHAMAN && slot == BOT_SLOT_RANGED; ++ else if (item->SubClass == ITEM_SUBCLASS_ARMOR_SIGIL) ++ return _botclass == BOT_CLASS_DEATH_KNIGHT && slot == BOT_SLOT_RANGED; ++ //misc inv items TODO: ++ } ++ ++ return false; ++} ++ ++bool bot_minion_ai::_unequip(uint8 slot) ++{ ++ ASSERT(!IAmFree()); ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to unequip item for bot with no equip info!"); ++ ++ Item* item = _equips[slot]; ++ if (!item) ++ return true; //already unequipped ++ ++ uint32 itemId = item->GetEntry(); ++ ++ //hand old weapon to master ++ if (slot >= BOT_SLOT_RANGED || einfo->ItemEntry[slot] != itemId) ++ { ++ ItemPosCountVec dest; ++ uint32 no_space = 0; ++ InventoryResult msg = master->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, 1, &no_space); ++ if (msg != EQUIP_ERR_OK) ++ { ++ std::ostringstream istr, iistr; ++ istr << "Cannot unequip "; ++ _AddItemLink(master, item, iistr); ++ istr << iistr.str() << " for some stupid reason! Sending through mail"; ++ ChatHandler ch(master->GetSession()); ++ ch.SendSysMessage(istr.str().c_str()); ++ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ MailDraft(iistr.str(), "").AddItem(item).SendMailTo(trans, MailReceiver(master), MailSender(me)); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ //master->SendEquipError(msg, NULL, NULL, itemId); ++ //return false; ++ } ++ else ++ { ++ Item* pItem = master->StoreItem(dest, item, true); ++ master->SendNewItem(pItem, 1, true, false, false); ++ } ++ } ++ else ++ { ++ //slot < BOT_SLOT_RANGED && einfo->ItemEntry[slot] == itemId ++ //we have our standard weapon which we should get rid of ++ //item->SetState(ITEM_REMOVED, master); //delete Item object ++ delete item; //!Invalidated! ++ //item = NULL; //already in "_updateEquips(slot, NULL);" ++ } ++ ++ //only for non-standard items ++ if (slot >= BOT_SLOT_RANGED || einfo->ItemEntry[slot] != itemId) ++ RemoveItemBonuses(slot); ++ ++ if (slot < BOT_SLOT_RANGED && CanChangeEquip(slot + 1)) //weapons ++ { ++ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, 0); ++ me->SetAttackTime(WeaponAttackType(slot), 2000); //without weapon ++ } ++ ++ _updateEquips(slot, NULL); ++ ++ //offhand check ++ if (slot + 1 == BOT_SLOT_OFFHAND) ++ { ++ if (me->CanDualWield()) ++ me->SetCanDualWield(false); ++ if (!(me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) ++ const_cast(me->GetCreatureTemplate())->flags_extra |= CREATURE_FLAG_EXTRA_NO_BLOCK; ++ } ++ ++ return true; ++} ++ ++bool bot_minion_ai::_equip(uint8 slot, Item* newItem) ++{ ++ ASSERT(!IAmFree()); ++ ++ if (!newItem) ++ return true; //nothing to equip ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to equip item for bot with no equip info!"); ++ ++ ItemTemplate const* proto = newItem->GetTemplate(); ++ ++ if (newItem->GetState() == ITEM_REMOVED) ++ { ++ TC_LOG_ERROR("entities.player", ++ "minion_ai::_equip(): player %s (guidLow: %u) is trying to make bot %s (id: %u) equip item: %s (id: %u, guidLow: %u) which has state ITEM_REMOVED!", ++ master->GetName().c_str(), master->GetGUID().GetCounter(), me->GetName().c_str(), me->GetEntry(), proto->Name1.c_str(), proto->ItemId, newItem->GetGUID().GetCounter()); ++ return false; ++ } ++ ++ uint32 newItemId = newItem->GetEntry(); ++ ++ if (Item* oldItem = _equips[slot]) ++ { ++ //same id ++ if (oldItem->GetEntry() == newItemId) ++ return false; ++ } ++ ++ if (!_unequip(slot)) ++ { ++ BotSay("You have no space for my current item", master); ++ return false; ++ } ++ ++ if (slot >= BOT_SLOT_RANGED || einfo->ItemEntry[slot] != newItemId) ++ { ++ //cheating ++ if (newItem->GetOwnerGUID() != master->GetGUID() || !master->HasItemCount(newItemId, 1)) ++ { ++ std::ostringstream msg; ++ msg << "Cannot find "; ++ _AddItemLink(master, newItem, msg); ++ msg << " (id: " << uint32(newItemId) << ")!"; ++ BotWhisper(msg.str().c_str(), master); ++ ++ TC_LOG_ERROR("entities.player", ++ "minion_ai::_equip(): player %s (guidLow: %u) is trying to make bot %s (id: %u) equip item: %s (id: %u, guidLow: %u) but either does not have this item or does not own it", ++ master->GetName().c_str(), master->GetGUID().GetCounter(), me->GetName().c_str(), me->GetEntry(), proto->Name1.c_str(), proto->ItemId, newItem->GetGUID().GetCounter()); ++ return false; ++ } ++ ++ master->MoveItemFromInventory(newItem->GetBagSlot(), newItem->GetSlot(), true); ++ //Item is removed from inventory table in _updateEquips(slot, newItem); ++ newItem->SetGuidValue(ITEM_FIELD_OWNER, ObjectGuid::Empty); ++ } ++ ++ if (slot < BOT_SLOT_RANGED) ++ { ++ if (CanChangeEquip(slot + 1)) ++ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, newItemId); ++ uint32 delay = ++ /*einfo->ItemEntry[slot] != newItemId || */!IgnoreEquipsAttackTime() || slot + 1 == BOT_SLOT_OFFHAND ? proto->Delay : ++ slot + 1 == BOT_SLOT_RANGED ? me->GetCreatureTemplate()->RangeAttackTime : me->GetCreatureTemplate()->BaseAttackTime; ++ me->SetAttackTime(WeaponAttackType(slot), delay); //set attack speed ++ } ++ ++ _updateEquips(slot, newItem); ++ ++ //only for non-standard items ++ if (slot >= BOT_SLOT_RANGED || einfo->ItemEntry[slot] != newItemId) ++ ApplyItemBonuses(slot); ++ ++ if (slot + 1 == BOT_SLOT_OFFHAND) ++ { ++ if (proto->Class == ITEM_CLASS_WEAPON) ++ { ++ if (!me->CanDualWield()) ++ me->SetCanDualWield(true); ++ } ++ else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) ++ { ++ if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) ++ const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; ++ } ++ } ++ else if (slot + 1 == BOT_SLOT_MAINHAND && proto->InventoryType == INVTYPE_2HWEAPON && !(_botclass == BOT_CLASS_WARRIOR && me->getLevel() >= 60)) ++ { ++ //if have incompatible offhand unequip it ++ if (_equips[BOT_SLOT_OFFHAND - 1] != NULL) ++ _unequip(BOT_SLOT_OFFHAND - 1); ++ } ++ ++ return true; ++} ++ ++void bot_minion_ai::_updateEquips(uint8 slot, Item* item) ++{ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to update equips for bot with no equip info!"); ++ ++ _equips[slot] = item; ++ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ ++ //Commit to DB ++ PreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_EQUIP); ++ //"UPDATE character_npcbot SET equipMhEx = ?, equipOhEx = ?, equipRhEx = ?, equipHead = ?, equipShoulders = ?, equipChest = ?, equipWaist = ?, equipLegs = ?, equipFeet = ?, equipWrist = ?, equipHands = ?, equipBack = ?, equipBody = ?, equipFinger1 = ?, equipFinger2 = ?, equipTrinket1 = ?, equipTrinket2 = ?, equipNeck = ? WHERE entry = ?", CONNECTION_ASYNC ++ PreparedStatement* stmt; ++ uint8 k; ++ for (k = 0; k != BOT_INVENTORY_SIZE; ++k) ++ { ++ if (Item* botitem = _equips[k]) ++ { ++ bool standard = false; ++ for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) ++ { ++ if (einfo->ItemEntry[i] == botitem->GetEntry()) ++ { ++ bstmt->setUInt32(k, 0); ++ standard = true; ++ break; ++ } ++ } ++ if (standard) ++ continue; ++ ++ uint8 index = 0; ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_ITEM_INSTANCE); ++ //REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid) ++ //VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC : 0-13 ++ stmt->setUInt32( index, botitem->GetEntry()); ++ stmt->setUInt32(++index, botitem->GetOwnerGUID().GetCounter()); ++ stmt->setUInt32(++index, botitem->GetGuidValue(ITEM_FIELD_CREATOR).GetCounter()); ++ stmt->setUInt32(++index, botitem->GetGuidValue(ITEM_FIELD_GIFTCREATOR).GetCounter()); ++ stmt->setUInt32(++index, botitem->GetCount()); ++ stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_DURATION)); ++ ++ std::ostringstream ssSpells; ++ for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) ++ ssSpells << botitem->GetSpellCharges(i) << ' '; ++ stmt->setString(++index, ssSpells.str()); ++ ++ stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_FLAGS)); ++ ++ std::ostringstream ssEnchants; ++ for (uint8 i = 0; i < MAX_ENCHANTMENT_SLOT; ++i) ++ { ++ ssEnchants << botitem->GetEnchantmentId(EnchantmentSlot(i)) << ' '; ++ ssEnchants << botitem->GetEnchantmentDuration(EnchantmentSlot(i)) << ' '; ++ ssEnchants << botitem->GetEnchantmentCharges(EnchantmentSlot(i)) << ' '; ++ } ++ stmt->setString(++index, ssEnchants.str()); ++ ++ stmt->setInt16 (++index, botitem->GetItemRandomPropertyId()); ++ stmt->setUInt16(++index, botitem->GetUInt32Value(ITEM_FIELD_DURABILITY)); ++ stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME)); ++ stmt->setString(++index, botitem->GetText()); ++ stmt->setUInt32(++index, botitem->GetGUID().GetCounter()); ++ ++ trans->Append(stmt); ++ ++ botitem->DeleteFromInventoryDB(trans); //prevent duplicates ++ ++ bstmt->setUInt32(k, botitem->GetGUID().GetCounter()); ++ } ++ else ++ bstmt->setUInt32(k, uint32(0)); ++ } ++ ++ bstmt->setUInt32(k, me->GetEntry()); ++ ++ trans->Append(bstmt); ++ CharacterDatabase.CommitTransaction(trans); ++} ++//Called from gossip menu only (applies only to weapons) ++bool bot_minion_ai::_resetEquipment(uint8 slot) ++{ ++ ASSERT(!IAmFree()); ++ ASSERT(slot < BOT_SLOT_RANGED); ++ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to reset equipment for bot with no equip info!"); ++ ++ uint32 itemId = einfo->ItemEntry[slot]; ++ if (!itemId) ++ return _unequip(slot); ++ else if (Item* oldItem = _equips[slot]) ++ if (oldItem->GetEntry() == itemId) ++ return true; ++ ++ if (slot + 1 == BOT_SLOT_MAINHAND && !(_botclass == BOT_CLASS_WARRIOR && me->getLevel() >= 60)) ++ { ++ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) ++ { ++ if (proto->Class == ITEM_CLASS_WEAPON && ++ (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || proto->SubClass == ITEM_SUBCLASS_WEAPON_SPEAR)) ++ { ++ if (!_unequip(BOT_SLOT_OFFHAND - 1)) ++ return false; ++ } ++ } ++ } ++ ++ //we have our standard weapon itemId which we should use to create new item ++ Item* stItem = Item::CreateItem(itemId, 1, NULL); ++ ASSERT(stItem && "Failed to create standard Item for bot!"); ++ ++ if (!_equip(slot, stItem)) ++ { ++ TC_LOG_ERROR("entities.player", "minion_ai::_resetEquipment(): player %s (guidLow: %u) failed to reset equipment for bot %s (id: %u) in slot %u", ++ master->GetName().c_str(), master->GetGUID().GetCounter(), me->GetName().c_str(), me->GetEntry(), slot); ++ return false; ++ } ++ return true; ++} ++ ++void bot_minion_ai::ApplyItemBonuses(uint8 slot) ++{ ++ //ensurance to set zeros ++ RemoveItemBonuses(slot); ++ ++ Item* item = _equips[slot]; ++ if (!item) ++ return; ++ ++ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item->GetEntry()); ++ if (!proto) ++ return; ++ ++ ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : NULL; ++ ++ uint32 ssd_level = me->getLevel(); ++ if (ssd && ssd_level > ssd->MaxLevel) ++ ssd_level = ssd->MaxLevel; ++ ++ ScalingStatValuesEntry const* ssv = proto->ScalingStatValue ? sScalingStatValuesStore.LookupEntry(ssd_level) : NULL; ++ ++ for (uint8 i = 0; i != MAX_ITEM_PROTO_STATS; ++i) ++ { ++ uint32 statType = 0; ++ int32 val = 0; ++ if (ssd && ssv) ++ { ++ if (ssd->StatMod[i] < 0) ++ continue; ++ statType = ssd->StatMod[i]; ++ val = (ssv->getssdMultiplier(proto->ScalingStatValue) * ssd->Modifier[i]) / 10000; ++ } ++ else ++ { ++ if (i >= proto->StatsCount) ++ continue; ++ statType = proto->ItemStat[i].ItemStatType; ++ val = proto->ItemStat[i].ItemStatValue; ++ } ++ ++ if (val == 0) ++ continue; ++ ++ _stats[slot][statType] += val; ++ } ++ ++ _stats[slot][BOT_ITEM_MOD_RESIST_HOLY] += proto->HolyRes; ++ _stats[slot][BOT_ITEM_MOD_RESIST_FIRE] += proto->FireRes; ++ _stats[slot][BOT_ITEM_MOD_RESIST_NATURE] += proto->NatureRes; ++ _stats[slot][BOT_ITEM_MOD_RESIST_FROST] += proto->FrostRes; ++ _stats[slot][BOT_ITEM_MOD_RESIST_SHADOW] += proto->ShadowRes; ++ _stats[slot][BOT_ITEM_MOD_RESIST_ARCANE] += proto->ArcaneRes; ++ ++ _stats[slot][BOT_ITEM_MOD_DAMAGE] += (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) / 2; ++ _stats[slot][BOT_ITEM_MOD_ARMOR] += proto->Armor; ++ _stats[slot][BOT_ITEM_MOD_BLOCK_VALUE] += proto->Block; ++ ++ if (GetPlayerClass() == BOT_CLASS_DRUID) ++ { ++ int32 dpsMod = 0; ++ int32 feral_bonus = 0; ++ ++ if (ssv) ++ { ++ dpsMod = ssv->getDPSMod(proto->ScalingStatValue); ++ feral_bonus += ssv->getFeralBonus(proto->ScalingStatValue); ++ } ++ ++ feral_bonus += proto->getFeralBonus(dpsMod); ++ if (feral_bonus) ++ _stats[slot][BOT_ITEM_MOD_FERAL_ATTACK_POWER] += feral_bonus; ++ //ApplyFeralAPBonus(feral_bonus, apply); ++ } ++ ++ ApplyItemEnchantments(item, slot); ++ ApplyItemEquipSpell(item, true); ++ ++ shouldUpdateStats = true; ++} ++ ++void bot_minion_ai::RemoveItemBonuses(uint8 slot) ++{ ++ Item* item = _equips[slot]; ++ if (!item) ++ return; ++ ++ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item->GetEntry()); ++ if (!proto) ++ return; ++ ++ for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) ++ _stats[slot][i] = 0; ++ ++ RemoveItemEnchantments(item, slot); //remove spells ++ ApplyItemEquipSpell(item, false); ++ ++ shouldUpdateStats = true; ++} ++ ++void bot_minion_ai::ApplyItemEnchantments(Item* item, uint8 slot) ++{ ++ for (uint8 i = 0; i != MAX_ENCHANTMENT_SLOT; ++i) ++ ApplyItemEnchantment(item, EnchantmentSlot(i), slot); ++} ++ ++void bot_minion_ai::ApplyItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 slot) ++{ ++ uint32 enchant_id = item->GetEnchantmentId(eslot); ++ if (!enchant_id) ++ return; ++ ++ SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); ++ if (!pEnchant) ++ return; ++ ++ if (pEnchant->requiredLevel > me->getLevel()) ++ return; ++ ++ uint32 enchant_display_type; ++ uint32 enchant_amount; ++ uint32 enchant_spell_id; ++ ++ for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) ++ { ++ enchant_display_type = pEnchant->type[s]; ++ enchant_amount = pEnchant->amount[s]; ++ enchant_spell_id = pEnchant->spellid[s]; ++ ++ switch (enchant_display_type) ++ { ++ case ITEM_ENCHANTMENT_TYPE_DAMAGE: ++ _stats[slot][BOT_ITEM_MOD_DAMAGE] += enchant_amount; ++ break; ++ case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: ++ if (enchant_spell_id) ++ { ++ int32 basepoints = 0; ++ // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor) ++ if (item->GetItemRandomPropertyId()) ++ { ++ ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); ++ if (item_rand) ++ { ++ // Search enchant_amount ++ for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) ++ { ++ if (item_rand->enchant_id[k] == enchant_id) ++ { ++ basepoints = int32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); ++ break; ++ } ++ } ++ } ++ } ++ // Cast custom spell vs all equal basepoints got from enchant_amount ++ if (basepoints) ++ me->CastCustomSpell(me, enchant_spell_id, &basepoints, &basepoints, &basepoints, true, item); ++ else ++ me->CastSpell(me, enchant_spell_id, true, item); ++ } ++ break; ++ case ITEM_ENCHANTMENT_TYPE_RESISTANCE: ++ if (!enchant_amount) ++ { ++ ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); ++ if (item_rand) ++ { ++ for (uint8 k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) ++ { ++ if (item_rand->enchant_id[k] == enchant_id) ++ { ++ enchant_amount = uint32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); ++ break; ++ } ++ } ++ } ++ } ++ _stats[slot][BOT_ITEM_MOD_RESISTANCE_START + enchant_spell_id] += enchant_amount; ++ break; ++ case ITEM_ENCHANTMENT_TYPE_STAT: ++ { ++ if (!enchant_amount) ++ { ++ ItemRandomSuffixEntry const* item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); ++ if (item_rand_suffix) ++ { ++ for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) ++ { ++ if (item_rand_suffix->enchant_id[k] == enchant_id) ++ { ++ enchant_amount = uint32((item_rand_suffix->prefix[k] * item->GetItemSuffixFactor()) / 10000); ++ break; ++ } ++ } ++ } ++ } ++ ++ switch (enchant_spell_id) ++ { ++ case ITEM_MOD_MANA: ++ case ITEM_MOD_HEALTH: ++ case ITEM_MOD_AGILITY: ++ case ITEM_MOD_STRENGTH: ++ case ITEM_MOD_INTELLECT: ++ case ITEM_MOD_SPIRIT: ++ case ITEM_MOD_STAMINA: ++ case ITEM_MOD_DEFENSE_SKILL_RATING: ++ case ITEM_MOD_DODGE_RATING: ++ case ITEM_MOD_PARRY_RATING: ++ case ITEM_MOD_BLOCK_RATING: ++ case ITEM_MOD_HIT_MELEE_RATING: ++ case ITEM_MOD_HIT_RANGED_RATING: ++ case ITEM_MOD_HIT_SPELL_RATING: ++ case ITEM_MOD_CRIT_MELEE_RATING: ++ case ITEM_MOD_CRIT_RANGED_RATING: ++ case ITEM_MOD_CRIT_SPELL_RATING: ++ case ITEM_MOD_HASTE_MELEE_RATING: ++ case ITEM_MOD_HASTE_RANGED_RATING: ++ case ITEM_MOD_HASTE_SPELL_RATING: ++ case ITEM_MOD_HIT_RATING: ++ case ITEM_MOD_CRIT_RATING: ++ case ITEM_MOD_HASTE_RATING: ++ case ITEM_MOD_RESILIENCE_RATING: ++ case ITEM_MOD_EXPERTISE_RATING: ++ case ITEM_MOD_ATTACK_POWER: ++ case ITEM_MOD_RANGED_ATTACK_POWER: ++ case ITEM_MOD_MANA_REGENERATION: ++ case ITEM_MOD_ARMOR_PENETRATION_RATING: ++ case ITEM_MOD_SPELL_POWER: ++ case ITEM_MOD_SPELL_PENETRATION: ++ case ITEM_MOD_BLOCK_VALUE: ++ case ITEM_MOD_SPELL_HEALING_DONE: // deprecated ++ case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated ++ _stats[slot][enchant_spell_id] += enchant_amount; ++ break; ++ default: ++ break; ++ } ++ break; ++ } ++ case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon ++ case ITEM_ENCHANTMENT_TYPE_USE_SPELL: ++ case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: ++ break; ++ default: ++ break; ++ } ++ } ++} ++ ++void bot_minion_ai::RemoveItemEnchantments(Item* item, uint8 slot) ++{ ++ for (uint8 i = 0; i != MAX_ENCHANTMENT_SLOT; ++i) ++ RemoveItemEnchantment(item, EnchantmentSlot(i), slot); ++} ++ ++void bot_minion_ai::RemoveItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 /*slot*/) ++{ ++ uint32 enchant_id = item->GetEnchantmentId(eslot); ++ if (!enchant_id) ++ return; ++ ++ SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); ++ if (!pEnchant) ++ return; ++ ++ ////skip level reqs ++ //if (pEnchant->requiredLevel > me->getLevel()) ++ // return; ++ ++ uint32 enchant_display_type; ++ //uint32 enchant_amount; ++ uint32 enchant_spell_id; ++ ++ for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) ++ { ++ enchant_display_type = pEnchant->type[s]; ++ //enchant_amount = pEnchant->amount[s]; ++ enchant_spell_id = pEnchant->spellid[s]; ++ ++ switch (enchant_display_type) ++ { ++ case ITEM_ENCHANTMENT_TYPE_DAMAGE: ++ //Already removed in RemoveItemBonuses() ++ break; ++ case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: ++ if (enchant_spell_id) ++ me->RemoveAurasDueToItemSpell(enchant_spell_id, item->GetGUID()); ++ break; ++ case ITEM_ENCHANTMENT_TYPE_RESISTANCE: ++ //Already removed in RemoveItemBonuses() ++ break; ++ case ITEM_ENCHANTMENT_TYPE_STAT: ++ //Already removed in RemoveItemBonuses() ++ break; ++ case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon ++ case ITEM_ENCHANTMENT_TYPE_USE_SPELL: ++ case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: ++ break; ++ default: ++ break; ++ } ++ } ++} ++ ++void bot_minion_ai::ApplyItemEquipSpell(Item* item, bool apply) ++{ ++ if (!item) ++ return; ++ ++ ItemTemplate const* proto = item->GetTemplate(); ++ if (!proto) ++ return; ++ ++ for (uint8 i = 0; i != MAX_ITEM_PROTO_SPELLS; ++i) ++ { ++ _Spell const& spellData = proto->Spells[i]; ++ ++ if (!spellData.SpellId) ++ continue; ++ ++ // wrong triggering type ++ if (apply && spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP) ++ continue; ++ ++ // check if it is valid spell ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); ++ if (!spellInfo) ++ continue; ++ ++ //ApplyEquipSpell(spellproto, item, apply); ++ ++ //if (apply) ++ // me->AddAura(spellInfo->Id, me); ++ //else ++ // me->RemoveAura(spellInfo->Id); ++ ++ if (apply) ++ me->CastSpell(me, spellInfo, true, item); ++ else ++ me->RemoveAurasDueToItemSpell(spellInfo->Id, item->GetGUID()); // un-apply all spells, not only at-equipped ++ } ++} ++ ++void bot_minion_ai::ApplyItemsSpells() ++{ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ++ //only for non-standard items ++ for (uint8 slot = 0; slot != BOT_INVENTORY_SIZE; ++slot) ++ if (Item* item = _equips[slot]) ++ if (slot >= BOT_SLOT_RANGED || einfo->ItemEntry[slot] != item->GetEntry()) ++ ApplyItemEquipSpell(item, true); ++} ++ ++inline float bot_minion_ai::_getBotStat(uint8 slot, uint8 stat) const ++{ ++ return float(static_cast(_stats[slot])[stat]); ++} ++ ++inline float bot_minion_ai::_getTotalBotStat(uint8 stat) const ++{ ++ int32 value = 0; ++ for (uint8 slot = 0; slot != BOT_INVENTORY_SIZE; ++slot) ++ value += static_cast(_stats[slot])[stat]; ++ ++ return float(value); ++} ++ ++inline float bot_minion_ai::_getRatingMultiplier(CombatRating cr) const ++{ ++ GtCombatRatingsEntry const* Rating = ++ sGtCombatRatingsStore.LookupEntry(cr*GT_MAX_LEVEL + (me->getLevel()-1)); ++ GtOCTClassCombatRatingScalarEntry const* classRating = ++ sGtOCTClassCombatRatingScalarStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_RATING + cr + 1); ++ if (!Rating || !classRating) ++ return 1.0f; ++ ++ //bots gain 20% increased bonus from rating mods ++ return 1.2f * classRating->ratio / Rating->ratio; ++} ++ ++char const* bot_minion_ai::_getNameForSlot(uint8 slot) const ++{ ++ switch (slot) ++ { ++ case BOT_SLOT_MAINHAND: ++ return "Main Hand Weapon"; ++ case BOT_SLOT_OFFHAND: ++ return "Offhand Weapon"; ++ case BOT_SLOT_RANGED: ++ return "Ranged Weapon"; ++ case BOT_SLOT_HEAD: ++ return "Head"; ++ case BOT_SLOT_SHOULDERS: ++ return "Shoulders"; ++ case BOT_SLOT_CHEST: ++ return "Chest"; ++ case BOT_SLOT_WAIST: ++ return "Waist"; ++ case BOT_SLOT_LEGS: ++ return "Legs"; ++ case BOT_SLOT_FEET: ++ return "Feet"; ++ case BOT_SLOT_WRIST: ++ return "Wrist"; ++ case BOT_SLOT_HANDS: ++ return "Hands"; ++ case BOT_SLOT_BACK: ++ return "Back"; ++ case BOT_SLOT_BODY: ++ return "Body"; ++ case BOT_SLOT_FINGER1: ++ return "Finger1"; ++ case BOT_SLOT_FINGER2: ++ return "Finger2"; ++ case BOT_SLOT_TRINKET1: ++ return "Trinket1"; ++ case BOT_SLOT_TRINKET2: ++ return "Trinket2"; ++ case BOT_SLOT_NECK: ++ return "Neck"; ++ default: ++ return "Unknown"; ++ } ++} ++ ++uint8 bot_minion_ai::_onOffIcon(uint8 role) const ++{ ++ return HasRole(role) ? BOT_ICON_ON : BOT_ICON_OFF; ++} ++ ++bool bot_minion_ai::CanHeal() const ++{ ++ return ++ (_botclass == BOT_CLASS_PRIEST || _botclass == BOT_CLASS_DRUID || ++ _botclass == BOT_CLASS_SHAMAN || _botclass == BOT_CLASS_PALADIN); ++} ++ ++char const* bot_ai::GetRoleString(uint8 role) const ++{ ++ switch (role) ++ { ++ case BOT_ROLE_NONE: ++ return "???"; ++ case BOT_ROLE_TANK: ++ return "Tanking"; ++ case BOT_ROLE_DPS: ++ return "DPS"; ++ case BOT_ROLE_HEAL: ++ return "Heal"; ++ //case BOT_ROLE_MELEE: ++ // return "Melee"; ++ case BOT_ROLE_RANGED: ++ return "Ranged"; ++ default: ++ { ++ std::ostringstream str; ++ str << "role " << uint32(role); ++ return str.str().c_str(); ++ } ++ } ++} ++ ++void bot_ai::DefaultInit() ++{ ++ //only once ++ if (spawned) return; ++ spawned = true; ++ ++ if (!firstspawn) ++ { ++ me->RemoveAllAuras(); ++ if (IsMinionAI()) ++ ToMinionAI()->ApplyItemsSpells(); ++ } ++ ++ //*etStats() has *pplyClassPassives() in it ++ //needed to be before InitEquips for some classes (warrior TG) ++ me->SetPvP(true); ++ InitRoles(); ++ SetStats(true); ++ //InitPowers(); //already in *etStats(); ++ ApplyPassives(); ++ ++ if (firstspawn) ++ { ++ firstspawn = false; ++ ASSERT(!me->GetBotAI()); ++ me->SetBotAI(this); ++ InitFaction(); ++ InitOwner(); ++ InitEquips(); ++ ++ InitSpellMap(PVPTRINKET, true); ++ } ++} ++ ++void bot_minion_ai::InitFaction() ++{ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_FACTION); ++ //"SELECT faction FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, me->GetEntry()); ++ PreparedQueryResult result = CharacterDatabase.Query(stmt); ++ ASSERT(result); ++ ++ Field* field = result->Fetch(); ++ uint32 faction = field[0].GetUInt32(); ++ me->setFaction(faction); ++ const_cast(me->GetCreatureTemplate())->faction = faction; ++} ++ ++void bot_minion_ai::InitOwner() ++{ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_OWNER); ++ //"SELECT owner FROM character_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, me->GetEntry()); ++ PreparedQueryResult result = CharacterDatabase.Query(stmt); ++ ASSERT(result); ++ ++ Field* field = result->Fetch(); ++ _ownerGuid = field[0].GetUInt32(); ++} ++ ++void bot_minion_ai::InitRoles() ++{ ++ if (IAmFree()) ++ { ++ //default roles ++ _roleMask = BOT_ROLE_DPS; ++ if (!IsMeleeClass(_botclass)) ++ _roleMask |= BOT_ROLE_RANGED; ++ if (CanHeal()) ++ _roleMask |= BOT_ROLE_HEAL; ++ ++ return; ++ } ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_ROLES); ++ //"SELECT roles FROM character_npcbot WHERE entry = ?", CONNECTION_SYNC ++ stmt->setUInt32(0, me->GetEntry()); ++ PreparedQueryResult result = CharacterDatabase.Query(stmt); ++ if (!result) ++ { ++ //default roles ++ _roleMask = BOT_ROLE_DPS; ++ if (!IsMeleeClass(_botclass)) ++ _roleMask |= BOT_ROLE_RANGED; ++ if (CanHeal()) ++ _roleMask |= BOT_ROLE_HEAL; ++ ++ return; ++ } ++ ++ Field* field = result->Fetch(); ++ _roleMask = field[0].GetInt8(); ++} ++ ++void bot_pet_ai::InitRoles() ++{ ++ _roleMask = BOT_ROLE_DPS; ++ if (!IsMeleeClass(GetPetClass(me))) ++ _roleMask |= BOT_ROLE_RANGED; ++ if (CanHeal()) ++ _roleMask |= BOT_ROLE_HEAL; ++} ++ ++void bot_minion_ai::InitEquips() ++{ ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ASSERT(einfo && "Trying to spawn bot with no equip info!"); ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP); ++ //"SELECT equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet, equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck ++ //FROM character_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, me->GetEntry()); ++ PreparedQueryResult cnresult = CharacterDatabase.Query(stmt); ++ if (!cnresult) ++ { ++ TC_LOG_ERROR("entities.player", "bot_minion_ai::InitEquips(): Failed to initialize equips for bot %s (id: %u, guidLow: %u), not found in `characters_npcbots table`!!!", ++ me->GetName().c_str(), me->GetEntry(), me->GetGUID().GetCounter()); ++ ASSERT(false); ++ } ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE); ++ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ++ //SELECT ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.guid, ii.itemEntry, ii.owner_guid " ++ // "FROM item_instance ii JOIN characters_npcbot cn ON ii.guid IN " ++ // "(cn.equipMhEx, cn.equipOhEx, cn.equipRhEx, cn.equipHead, cn.equipShoulders, cn.equipChest, cn.equipWaist, cn.equipLegs, cn.equipFeet, cn.equipWrist, cn.equipHands, cn.equipBack, cn.equipBody, cn.equipFinger1, cn.equipFinger2, cn.equipTrinket1, cn.equipTrinket2, cn.equipNeck) " ++ // "WHERE cn.entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, me->GetEntry()); ++ PreparedQueryResult iiresult = CharacterDatabase.Query(stmt); ++ ++ Field* fields1 = cnresult->Fetch(); ++ Field* fields2; ++ uint32 itemId; ++ uint32 itemGuidLow; ++ Item* item; ++ ++ if (!iiresult) //blank bot - fill with standard items ++ { ++ for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) ++ { ++ itemId = einfo->ItemEntry[i]; ++ if (!itemId) ++ continue; ++ ++ item = Item::CreateItem(itemId, 1, NULL); ++ ASSERT(item && "Failed to init standard Item for bot!"); ++ _equips[i] = item; ++ } ++ } ++ else ++ { ++ do ++ { ++ fields2 = iiresult->Fetch(); ++ itemGuidLow = fields2[11].GetUInt32(); ++ itemId = fields2[12].GetUInt32(); ++ item = new Item; ++ ASSERT(item->LoadFromDB(itemGuidLow, ObjectGuid::Empty, fields2, itemId)); ++ //gonna find where to store our new item ++ bool found = false; ++ uint8 i = 0; ++ for (; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ if (fields1[i].GetUInt32() == itemGuidLow && !_equips[i]) ++ { ++ _equips[i] = item; ++ found = true; ++ break; ++ } ++ } ++ ASSERT(found); ++ //ItemTemplate const* proto = item->GetTemplate(); ++ //TC_LOG_ERROR("entities.player", "minion_ai::InitEquips(): bot %s (id: %u): found item: for slot %u: %s (id: %u, guidLow: %u)", ++ // me->GetName().c_str(), me->GetEntry(), i, proto->Name1.c_str(), itemId, itemGuidLow); ++ ++ } while (iiresult->NextRow()); ++ } ++ ++ //visualize ++ for (uint8 i = 0; i != BOT_SLOT_RANGED; ++i) ++ { ++ if (CanChangeEquip(i + 1) && _equips[i]) ++ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, _equips[i]->GetEntry()); ++ else if (einfo->ItemEntry[i]) ++ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]); ++ } ++ ++ //apply weapons' parameters ++ if (Item* MH = _equips[0]) ++ { ++ itemId = MH->GetEntry(); ++ if (einfo->ItemEntry[0] != itemId) ++ { ++ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) ++ { ++ me->SetAttackTime(BASE_ATTACK, proto->Delay); ++ ApplyItemBonuses(0); ++ } ++ } ++ } ++ if (Item* OH = _equips[1]) ++ { ++ itemId = OH->GetEntry(); ++ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) ++ { ++ if (einfo->ItemEntry[1] != itemId) ++ ApplyItemBonuses(1); ++ ++ if (proto->Class == ITEM_CLASS_WEAPON) ++ { ++ me->SetAttackTime(OFF_ATTACK, proto->Delay); ++ me->SetCanDualWield(true); ++ } ++ else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) ++ { ++ if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) ++ const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; ++ } ++ } ++ } ++ if (Item* RH = _equips[2]) ++ { ++ itemId = RH->GetEntry(); ++ if (einfo->ItemEntry[2] != itemId) ++ { ++ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) ++ { ++ if (proto->Class == ITEM_CLASS_WEAPON) ++ me->SetAttackTime(RANGED_ATTACK, proto->Delay); ++ ++ ApplyItemBonuses(2); ++ } ++ } ++ } ++ ++ for (uint8 i = 3; i != BOT_INVENTORY_SIZE; ++i) ++ ApplyItemBonuses(i); ++ ++ for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) ++ { ++ if (_equips[i] == NULL && einfo->ItemEntry[i] != 0) ++ { ++ if (i == 1 && !_canUseOffHand()) ++ continue; ++ ++ //if bot has no equips but equip template then write these to bot map ++ item = Item::CreateItem(einfo->ItemEntry[i], 1, NULL); ++ ASSERT(item && "Failed to init standard Item for bot point 2!"); ++ _equips[i] = item; ++ ++ me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]); ++ if (i == 1) ++ { ++ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(einfo->ItemEntry[i])) ++ { ++ if (proto->Class == ITEM_CLASS_WEAPON) ++ { ++ me->SetAttackTime(OFF_ATTACK, _botclass == BOT_CLASS_ROGUE ? 1400 : 1800); ++ me->SetCanDualWield(true); ++ } ++ else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) ++ { ++ if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) ++ const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; ++ } ++ } ++ } ++ } ++ } ++} ++ ++void bot_ai::ToggleRole(uint8 role, bool force) ++{ ++ if (!force && roleTimer > 0) ++ return; ++ ++ roleTimer = 350; //delay next attempt (prevent abuse) ++ ++ HasRole(role) ? _roleMask &= ~role : _roleMask |= role; ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_ROLES); ++ //"UPDATE character_npcbot SET roles = ? WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt8(0, _roleMask); ++ stmt->setUInt32(1, me->GetEntry()); ++ CharacterDatabase.Execute(stmt); ++ ++ //Update passives ++ ApplyPassives(); ++ shouldUpdateStats = true; ++} ++ ++bool bot_ai::IsTank(Unit* unit) const ++{ ++ if (!unit) ++ unit = me; ++ ++ if (unit == me) ++ return HasRole(BOT_ROLE_TANK); ++ ++ if (Creature* bot = unit->ToCreature()) ++ if (bot->GetIAmABot() || bot->GetIAmABotsPet()) ++ return bot->GetBotAI()->HasRole(BOT_ROLE_TANK); ++ ++ if (Player* player = unit->ToPlayer()) ++ { ++ if (Group* gr = player->GetGroup()) ++ { ++ Group::MemberSlotList const& slots = gr->GetMemberSlots(); ++ for (Group::member_citerator itr = slots.begin(); itr != slots.end(); ++itr) ++ if (itr->guid == player->GetGUID()) ++ return itr->flags & MEMBER_FLAG_MAINTANK; ++ } ++ } ++ ++ return false; ++} ++ ++void bot_ai::FindMaster(bool force) ++{ ++ if (!force) ++ { ++ //totally free ++ if (!_ownerGuid) ++ return; ++ if (!_atHome || _evadeMode) ++ return; ++ ++ //delay ++ if (checkMasterTimer > 0) ++ return; ++ ++ checkMasterTimer = urand(5000, 15000); ++ } ++ ++ //already have master ++ if (!IAmFree()) ++ return; ++ ++ if (Player* player = sObjectMgr->GetPlayerByLowGUID(_ownerGuid)) ++ { ++ //prevent bot being screwed up because of wrong flags ++ if (player->IsGameMaster() || player->GetSession()->isLogingOut()) ++ return; ++ ++ SetBotOwner(player); ++ ++ //fail ++ if (master != player) ++ return; ++ ++ if (!IsTempBot()) ++ BotWhisper("Hey...", master); ++ return; ++ } ++} ++ ++bool bot_minion_ai::IAmFree() const ++{ ++ if (!_ownerGuid) ++ return true; ++ if (_ownerGuid != master->GetGUID()) ++ return true; ++ if (!me->HasUnitTypeMask(UNIT_MASK_MINION)) ++ return true; ++ ++ return false; ++ //return (!_ownerGuid || _ownerGuid != master->GetGUID() || !me->HasUnitTypeMask(UNIT_MASK_MINION)); ++ // //has owner and //owner is found and //bound to owner ++} ++ ++void bot_minion_ai::SavePosition() ++{ ++ if (_saveTimer > 0) return; ++ if (!me->IsPositionValid()) return; ++ if (me->IsInCombat() || !me->IsInWorld()) ++ { ++ _saveTimer = 3000; ++ return; ++ } ++ ++ _saveTimer = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE); ++ ++ uint16 mapid = me->GetMapId(); ++ float x = me->GetPositionX(); ++ float y = me->GetPositionY(); ++ float z = me->GetPositionZ(); ++ float o = me->GetOrientation(); ++ ++ if (CreatureData const* data = sObjectMgr->GetCreatureData(me->GetSpawnId())) ++ { ++ const_cast(data)->mapid = mapid; ++ const_cast(data)->posX = x; ++ const_cast(data)->posY = y; ++ const_cast(data)->posZ = z; ++ const_cast(data)->orientation = o; ++ } ++ ++ PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_NPCBOT_POSITION); ++ //"UPDATE creature SET map = ?, position_x = ?, position_y = ?, position_z = ?, orientation = ? WHERE guid = ?", CONNECTION_ASYNC ++ stmt->setUInt16(0, mapid); ++ stmt->setFloat(1, x); ++ stmt->setFloat(2, y); ++ stmt->setFloat(3, z); ++ stmt->setFloat(4, o); ++ stmt->setUInt32(5, me->GetSpawnId()); ++ ++ WorldDatabase.Execute(stmt); ++} ++ ++//UTILITIES ++void bot_ai::_AddItemTemplateLink(Player* forPlayer, ItemTemplate const* item, std::ostringstream &str) const ++{ ++ //color ++ str << "|c"; ++ switch (item->Quality) ++ { ++ case ITEM_QUALITY_POOR: str << "ff9d9d9d"; break; //GREY ++ case ITEM_QUALITY_NORMAL: str << "ffffffff"; break; //WHITE ++ case ITEM_QUALITY_UNCOMMON: str << "ff1eff00"; break; //GREEN ++ case ITEM_QUALITY_RARE: str << "ff0070dd"; break; //BLUE ++ case ITEM_QUALITY_EPIC: str << "ffa335ee"; break; //PURPLE ++ case ITEM_QUALITY_LEGENDARY:str << "ffff8000"; break; //ORANGE ++ case ITEM_QUALITY_ARTIFACT: str << "ffe6cc80"; break; //LIGHT YELLOW ++ case ITEM_QUALITY_HEIRLOOM: str << "ffe6cc80"; break; //LIGHT YELLOW ++ default: str << "ff000000"; break; //UNK BLACK ++ } ++ str << "|Hitem:" << uint32(item->ItemId) << ':'; ++ ++ //permanent enchantment, 3 gems, 4 unknowns, reporter_level (9) ++ str << "0:0:0:0:0:0:0:0:0"; ++ ++ //name ++ std::string name = item->Name1; ++ _LocalizeItem(forPlayer, name, item->ItemId); ++ str << "|h[" << name << "]|h|r"; ++ ++ //max in stack ++ if (item->BuyCount > 1) ++ str<< "|cff009900x" << item->BuyCount << ".|r"; ++ else ++ str << "|cff009900.|r"; ++} ++// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:0:reporter_level|h[name]|h|r ++// |cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r ++void bot_ai::_AddItemLink(Player* forPlayer, Item const* item, std::ostringstream &str) const ++{ ++ ItemTemplate const* proto = item->GetTemplate(); ++ ++ //color ++ str << "|c"; ++ switch (proto->Quality) ++ { ++ case ITEM_QUALITY_POOR: str << "ff9d9d9d"; break; //GREY ++ case ITEM_QUALITY_NORMAL: str << "ffffffff"; break; //WHITE ++ case ITEM_QUALITY_UNCOMMON: str << "ff1eff00"; break; //GREEN ++ case ITEM_QUALITY_RARE: str << "ff0070dd"; break; //BLUE ++ case ITEM_QUALITY_EPIC: str << "ffa335ee"; break; //PURPLE ++ case ITEM_QUALITY_LEGENDARY:str << "ffff8000"; break; //ORANGE ++ case ITEM_QUALITY_ARTIFACT: str << "ffe6cc80"; break; //LIGHT YELLOW ++ case ITEM_QUALITY_HEIRLOOM: str << "ffe6cc80"; break; //LIGHT YELLOW ++ default: str << "ff000000"; break; //UNK BLACK ++ } ++ str << "|Hitem:" << proto->ItemId << ':'; ++ ++ //gems (3) ++ uint32 g1 = 0, g2 = 0, g3 = 0; ++ for (uint32 slot = SOCK_ENCHANTMENT_SLOT; slot != SOCK_ENCHANTMENT_SLOT + MAX_ITEM_PROTO_SOCKETS; ++slot) ++ { ++ uint32 eId = item->GetEnchantmentId(EnchantmentSlot(slot)); ++ ++// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:0:reporter_level|h[name]|h|r ++ switch (slot - SOCK_ENCHANTMENT_SLOT) ++ { ++ case 0: g1 = eId; break; ++ case 1: g2 = eId; break; ++ case 2: g3 = eId; break; ++ } ++ } ++ //permanent enchantment ++ str << item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT) << ':'; ++ //gems 3 ++ str << g1 << ':' << g2 << ':' << g3 << ':'; ++ //gems bonus - useless ++ //str << item->GetEnchantmentId(BONUS_ENCHANTMENT_SLOT) << ':'; ++ str << 0 << ':'; ++ //random property ++ str << item-> GetItemRandomPropertyId() << ':'; ++ //item suffix ++ //str << item->GetItemSuffixFactor() << ':'; ++ //temp enchantment (i.e. windfury weapon) ++ //str << item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) << ':'; ++ //str << 0 << ':'; ++ ++ ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); ++ uint32 bpoints = 0; ++ if (item_rand) ++ { ++ // Search enchant_amount ++ for (uint8 k = 0; k != 3; ++k) ++ { ++ if (item_rand->enchant_id[k]) ++ { ++ uint32 basepoints = int32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); ++ if (basepoints > bpoints) ++ bpoints = basepoints; ++ } ++ } ++ } ++ ++ str << bpoints << ':'; ++ ++ //reporter level ++ str << proto->RequiredLevel; ++ ++ //name ++ std::string name = proto->Name1; ++ std::string suffix; ++ _LocalizeItem(forPlayer, name, suffix, item); ++ ++ str << "|h[" << name << suffix << "]|h|r"; ++ ++ //quantity ++ if (item->GetCount() > 1) ++ str << "x" << item->GetCount() << ' '; ++} ++ ++void bot_ai::_AddQuestLink(Player* forPlayer, Quest const* quest, std::ostringstream &str) const ++{ ++ std::string questTitle = quest->GetTitle(); ++ _LocalizeQuest(forPlayer, questTitle, quest->GetQuestId()); ++ str << "|cFFEFFD00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; ++} ++ ++void bot_ai::_AddWeaponSkillLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillid) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); ++ str << "|cff00ffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc] << " : " << master->GetSkillValue(skillid) << " /" << master->GetMaxSkillValue(skillid) << "]|h|r"; ++} ++//|cff71d5ff|Hspell:21563|h[Command]|h|r ++void bot_ai::_AddSpellLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, bool color/* = true*/, const std::string& colorstr/* = "ffffffff"*/) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); ++ str << "|c"; ++ ++ if (color) ++ { ++ switch (GetFirstSchoolInMask(spellInfo->GetSchoolMask())) ++ { ++ case SPELL_SCHOOL_NORMAL: str << "ffffff00"; break; //YELLOW ++ case SPELL_SCHOOL_HOLY: str << "ffffe680"; break; //LIGHT YELLOW ++ case SPELL_SCHOOL_FIRE: str << "ffff8000"; break; //ORANGE ++ case SPELL_SCHOOL_NATURE: str << "ff4dff4d"; break; //GREEN ++ case SPELL_SCHOOL_FROST: str << "ff80ffff"; break; //LIGHT BLUE ++ case SPELL_SCHOOL_SHADOW: str << "ff8080ff"; break; //DARK BLUE ++ case SPELL_SCHOOL_ARCANE: str << "ffff80ff"; break; //LIGHT PURPLE ++ default: str << "ffffffff"; break; //UNK WHITE ++ } ++ } ++ else ++ str << colorstr; //explicit color default white ++ ++ ++ str << "|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc] << "]|h|r"; ++} ++ ++void bot_ai::_AddProfessionLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillId) const ++{ ++ ASSERT(master->HasSkill(skillId)); ++ // |cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r ++ uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); ++ SkillLineEntry const* skillInfo = sSkillLineStore.LookupEntry(skillId); ++ if (skillInfo) ++ { ++ uint32 curValue = master->GetPureSkillValue(skillId); ++ uint32 maxValue = master->GetPureMaxSkillValue(skillId); ++ str << "|cffffd000|Htrade:" << spellInfo->Id << ':' << curValue << ':' << maxValue << ':' << master->GetGUID().GetCounter() << ":6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA" << "|h[" << skillInfo->name[loc] << "]|h|r"; ++ } ++} ++//Localization ++void bot_ai::_LocalizeItem(Player* forPlayer, std::string &itemName, uint32 entry) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); ++ std::wstring wnamepart; ++ ++ ItemLocale const* itemInfo = sObjectMgr->GetItemLocale(entry); ++ if (!itemInfo) ++ return; ++ ++ if (itemInfo->Name.size() > loc && !itemInfo->Name[loc].empty()) ++ { ++ const std::string name = itemInfo->Name[loc]; ++ if (Utf8FitTo(name, wnamepart)) ++ itemName = name; ++ } ++} ++ ++void bot_ai::_LocalizeItem(Player* forPlayer, std::string &itemName, std::string &suffix, Item const* item) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); ++ std::wstring wnamepart; ++ ++ ItemLocale const* itemInfo = sObjectMgr->GetItemLocale(item->GetEntry()); ++ if (!itemInfo) ++ return; ++ ++ if (itemInfo->Name.size() > loc && !itemInfo->Name[loc].empty()) ++ { ++ const std::string name = itemInfo->Name[loc]; ++ if (Utf8FitTo(name, wnamepart)) ++ itemName = name; ++ } ++ ++ int32 randomPropId = item->GetItemRandomPropertyId(); ++ if (!randomPropId) ++ return; ++ ++ if (randomPropId > 0) ++ { ++ if (ItemRandomPropertiesEntry const* item_rand = sItemRandomPropertiesStore.LookupEntry(randomPropId)) ++ suffix = item_rand->nameSuffix[loc]; ++ } ++ else ++ { ++ if (ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(-randomPropId)) ++ suffix = item_rand->nameSuffix[loc]; ++ } ++} ++ ++void bot_ai::_LocalizeQuest(Player* forPlayer, std::string &questTitle, uint32 entry) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); ++ std::wstring wnamepart; ++ ++ QuestLocale const* questInfo = sObjectMgr->GetQuestLocale(entry); ++ if (!questInfo) ++ return; ++ ++ if (questInfo->Title.size() > loc && !questInfo->Title[loc].empty()) ++ { ++ const std::string title = questInfo->Title[loc]; ++ if (Utf8FitTo(title, wnamepart)) ++ questTitle = title; ++ } ++} ++ ++void bot_ai::_LocalizeCreature(Player* forPlayer, std::string &creatureName, uint32 entry) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); ++ std::wstring wnamepart; ++ ++ CreatureLocale const* creatureInfo = sObjectMgr->GetCreatureLocale(entry); ++ if (!creatureInfo) ++ return; ++ ++ if (creatureInfo->Name.size() > loc && !creatureInfo->Name[loc].empty()) ++ { ++ const std::string title = creatureInfo->Name[loc]; ++ if (Utf8FitTo(title, wnamepart)) ++ creatureName = title; ++ } ++} ++ ++void bot_ai::_LocalizeGameObject(Player* forPlayer, std::string &gameobjectName, uint32 entry) const ++{ ++ uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); ++ std::wstring wnamepart; ++ ++ GameObjectLocale const* gameObjectInfo = sObjectMgr->GetGameObjectLocale(entry); ++ if (!gameObjectInfo) ++ return; ++ ++ if (gameObjectInfo->Name.size() > loc && !gameObjectInfo->Name[loc].empty()) ++ { ++ const std::string title = gameObjectInfo->Name[loc]; ++ if (Utf8FitTo(title, wnamepart)) ++ gameobjectName = title; ++ } ++} ++ ++void bot_ai::BotSpeak(std::string const& text, uint8 msgtype, uint32 language, ObjectGuid speaker, ObjectGuid receiver) ++{ ++ if (msgtype == CHAT_MSG_WHISPER) ++ language = LANG_UNIVERSAL; ++ ++ std::string _text(text); ++ //sScriptMgr->OnPlayerChat(this, CHAT_MSG_SAY, language, _text); ++ ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ //BuildPlayerChat(&data, msgType, _text, language); ++ data << uint8(msgtype); ++ data << uint32(language); ++ data << uint64(speaker); ++ data << uint32(0); // constant unknown time ++ data << uint64(speaker); ++ data << uint32(text.length() + 1); ++ data << text; ++ data << uint8(0); ++ ++ if (msgtype == CHAT_MSG_WHISPER) ++ { ++ ASSERT(receiver && "BotSpeak(): no receiver for whisper!"); ++ ASSERT(receiver.IsPlayer() && "BotSpeak(): whisper receiver is not a player!"); ++ ++ if (Player* res = ObjectAccessor::FindPlayer(receiver)) ++ res->GetSession()->SendPacket(&data); ++ } ++ else ++ { ++ if (Unit* snd = ObjectAccessor::FindConnectedPlayer(speaker)) ++ { ++ float dist = std::max(sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL) * 0.5f); ++ Trinity::MessageDistDeliverer notifier(snd, &data, dist, false); ++ snd->VisitNearbyWorldObject(dist, notifier); ++ } ++ } ++ //SendMessageToSetInRange(&data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), true); ++} ++ ++void bot_minion_ai::BotJump(Position* pos) ++{ ++ ++_jumpCount; ++ ++ me->BotStopMovement(); ++ me->GetMotionMaster()->MoveJump(*pos, me->GetExactDist2d(pos->m_positionX, pos->m_positionY), 10.0f); ++ ++ //float dx = pos->m_positionX - me->m_positionX; ++ //float dy = pos->m_positionY - me->m_positionY; ++ //float fdx = fabs(dx); float fdy = fabs(dy); ++ //float divider = fdx > 400 || fdy > 400 ? 5.0f : fdx > 200 || fdy > 200 ? 3.0f : fdx > 100 || fdy > 100 ? 2.0f : 1.5f; ++ //dx = dx / divider + me->m_positionX; ++ //dy = dy / divider + me->m_positionY; ++ //float z = me->GetMap()->GetHeight(dx, dy, std::max(me->m_positionZ, pos->m_positionZ)); ++ ++ //if (z > INVALID_HEIGHT) ++ // me->GetMotionMaster()->MoveJump(dx, dy, z, me->GetExactDist2d(dx, dy), 10.0f); ++} ++ ++bool bot_minion_ai::UpdateImpossibleChase(Unit* target) ++{ ++ if (_chaseTimer || me->isMoving() || !IAmFree()) ++ return false; ++ ++ if (me->IsFalling() || JumpingFlyingOrFalling()) ++ return false; ++ ++ if (!me->IsWithinDist(target, IsMelee() ? 50 : 75) || (me->GetDistance(target) < (IsMelee() ? 5 : 25))) ++ { ++ ResetChaseTimer(target); ++ me->GetMotionMaster()->MovePoint(me->GetMapId(), *target, false); ++ return true; ++ } ++ ++ if (_jumpCount >= 3) ++ { ++ me->AttackStop(); ++ Evade(true); ++ return true; ++ } ++ ++ ResetChaseTimer(target); ++ BotJump(target); ++ return true; ++} ++ ++void bot_minion_ai::ResetChaseTimer(Position* /*pos*/) ++{ ++ _chaseTimer = 10000;//std::max(5000, me->GetDistance2d(pos->m_positionX, pos->m_positionY) * 400); ++ //me->GetDistance2d(pos->m_positionX, pos->m_positionY) * 1000 / me->GetSpeed(MOVE_WALK); ++} ++ ++void bot_minion_ai::ResetChase(Position* pos) ++{ ++ if (!IAmFree()) ++ return; ++ ++ ResetChaseTimer(pos); ++ _jumpCount = 0; ++} ++ ++void bot_minion_ai::OnStartAttack(Unit* u) ++{ ++ if (u->GetGUID() != _lastTargetGuid) ++ { ++ ResetChase(u); ++ _lastTargetGuid = u->GetGUID(); ++ } ++} ++ ++void bot_minion_ai::EnterCombat(Unit* u) ++{ ++ _atHome = false; ++ ++ //clear gossip during combat. See CheckAuras() for restore ++ if (me->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP)) ++ me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); ++ //disable evade mode just in case ++ //me->ClearUnitState(UNIT_STATE_EVADE); ++ //if (Creature* pet = me->GetBotsPet()) ++ // pet->ClearUnitState(UNIT_STATE_EVADE); ++ ++ if (!IAmFree()) ++ return; ++ ++ _evadeMode = false; ++ AbortTeleport(); ++ ++ ResetChase(u); ++} ++ ++void bot_minion_ai::JustDied(Unit*) ++{ ++ events.KillAllEvents(false); ++ _reviveTimer = IAmFree() ? 180000 : 30000; //3min/30sec ++ _atHome = false; ++ _evadeMode = false; ++ spawned = false; ++ ++ if (IsTempBot()) ++ { ++ //TC_LOG_ERROR("entities.player", "Unsummoning temp bot %s (guidLow: %u), owner: %s (guidLow: %u)...", ++ // me->GetName().c_str(), me->GetGUID().GetCounter(), master->GetName().c_str(), master->GetGUID().GetCounter()); ++ ++ if (!IAmFree()) ++ master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_UNSUMMON); ++ ++ me->AddObjectToRemoveList(); ++ } ++ else if (!IAmFree()) ++ { ++ if (Group* gr = master->GetGroup()) ++ if (gr->IsMember(me->GetGUID())) ++ gr->SendUpdate(); ++ } ++} ++ ++void bot_minion_ai::MoveInLineOfSight(Unit* /*u*/) ++{ ++} ++ ++void bot_ai::DamageDealt(Unit* victim, uint32& damage, DamageEffectType /*damageType*/) ++{ ++ if (victim == me) ++ return; ++ ++ if (damage) ++ if (Creature* cre = victim->ToCreature()) ++ if (!cre->hasLootRecipient()) ++ cre->SetLootRecipient(master); ++ ++ if (victim->GetTypeId() == TYPEID_PLAYER) ++ ResetChase(victim); ++} ++//This function is called after Spell::SendSpellCooldown() call for players ++void bot_ai::OnBotSpellGo(Spell const* spell) ++{ ++ SpellInfo const* curInfo = spell->GetSpellInfo(); ++ uint32 rec = curInfo->GetRecoveryTime(); ++ uint32 catrec = curInfo->CategoryRecoveryTime; ++ ++ if (rec > 0) ++ ApplyBotSpellCooldownMods(curInfo, rec); ++ if (catrec > 0 && !(curInfo->AttributesEx6 & SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS)) ++ ApplyBotSpellCategoryCooldownMods(curInfo, catrec); ++ ++ //Set cooldown ++ SetSpellCooldown(curInfo->GetFirstRankSpell()->Id, rec); ++ SetSpellCategoryCooldown(curInfo->GetFirstRankSpell(), catrec); ++ ++ OnClassSpellGo(curInfo); ++} ++ ++void bot_ai::OnBotSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs) ++{ ++ SpellInfo const* info; ++ ++ for (BotSpellMap::iterator itr = spells.begin(); itr != spells.end(); ++itr) ++ { ++ info = sSpellMgr->GetSpellInfo(itr->second.first); ++ if (!info || !(info->GetSchoolMask() & schoolMask)) continue; ++ if (info->IsCooldownStartedOnEvent()) continue; ++ if (info->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) continue; ++ ++ itr->second.second += unTimeMs; ++ //TC_LOG_ERROR("entities.player", "OnBotSpellInterrupted(): Adding cooldown (%u, new: %u) to spell %s (id: %u, schoolmask: %u), reqSchoolMask = %u", ++ // unTimeMs, itr->second.second, info->SpellName[0], info->Id, info->SchoolMask, schoolMask); ++ } ++} ++ ++void bot_minion_ai::CastBotItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Spell const* spell/* = NULL*/) ++{ ++ if (!target || !target->IsAlive() || target == me) ++ return; ++ ++ if (!me->CanUseAttackType(attType)) ++ return; ++ ++ Item* item; ++ ItemTemplate const* proto; ++ uint8 slot; ++ int8 id = 1; ++ EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(me->GetEntry(), id); ++ ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ item = _equips[i]; ++ if (!item) ++ continue; ++ ++ //skip standard items ++ if (i < BOT_SLOT_RANGED && einfo->ItemEntry[i] == item->GetEntry()) ++ continue; ++ ++ proto = item->GetTemplate(); ++ if (!proto) ++ continue; ++ ++ // Additional check for weapons ++ if (proto->Class == ITEM_CLASS_WEAPON) ++ { ++ // offhand item cannot proc from main hand hit etc ++ switch (attType) ++ { ++ case BASE_ATTACK: slot = BOT_SLOT_MAINHAND; break; ++ case OFF_ATTACK: slot = BOT_SLOT_OFFHAND; break; ++ case RANGED_ATTACK: slot = BOT_SLOT_RANGED; break; ++ default: slot = BOT_MAX_SLOTS; break; ++ } ++ if (slot - 1 != i) ++ continue; ++ } ++ ++ CastBotItemCombatSpell(target, attType, procVictim, procEx, item, proto, spell); ++ } ++} ++ ++void bot_minion_ai::CastBotItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Item* item, ItemTemplate const* proto, Spell const* /*spell*//* = NULL*/) ++{ ++ //TODO: custom spell triggers maybe? ++ ++ // Can do effect if any damage done to target ++ if (procVictim & PROC_FLAG_TAKEN_DAMAGE) ++ { ++ for (uint8 i = 0; i != MAX_ITEM_PROTO_SPELLS; ++i) ++ { ++ _Spell const& spellData = proto->Spells[i]; ++ ++ // no spell ++ if (!spellData.SpellId) ++ continue; ++ ++ // wrong triggering type ++ if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_CHANCE_ON_HIT) ++ continue; ++ ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); ++ if (!spellInfo) ++ { ++ //TC_LOG_ERROR("entities.player.items", "WORLD: unknown Item spellid %i", spellData.SpellId); ++ continue; ++ } ++ ++ // not allow proc extra attack spell at extra attack ++ if (me->m_extraAttacks && spellInfo->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) ++ return; ++ ++ float chance = float(spellInfo->ProcChance); ++ ++ if (spellData.SpellPPMRate) ++ { ++ uint32 WeaponSpeed = me->GetAttackTime(attType); ++ chance = me->GetPPMProcChance(WeaponSpeed, spellData.SpellPPMRate, spellInfo); ++ } ++ else if (chance > 100.0f) ++ chance = me->GetWeaponProcChance(); ++ ++ if (roll_chance_f(chance)) ++ me->CastSpell(target, spellInfo->Id, true, item); ++ } ++ } ++ ++ // item combat enchantments ++ for (uint8 e_slot = 0; e_slot != MAX_ENCHANTMENT_SLOT; ++e_slot) ++ { ++ uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); ++ SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); ++ if (!pEnchant) ++ continue; ++ ++ for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) ++ { ++ if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) ++ continue; ++ ++ SpellEnchantProcEntry const* entry = sSpellMgr->GetSpellEnchantProcEvent(enchant_id); ++ ++ if (entry && entry->procEx) ++ { ++ // Check hit/crit/dodge/parry requirement ++ if ((entry->procEx & procEx) == 0) ++ continue; ++ } ++ else ++ { ++ // Can do effect if any damage done to target ++ if (!(procVictim & PROC_FLAG_TAKEN_DAMAGE)) ++ continue; ++ } ++ ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->spellid[s]); ++ if (!spellInfo) ++ { ++ //TC_LOG_ERROR("entities.player.items", "Player::CastItemCombatSpell(GUID: %u, name: %s, enchant: %i): unknown spell %i is casted, ignoring...", ++ // GetGUID().GetCounter(), GetName().c_str(), pEnchant->ID, pEnchant->spellid[s]); ++ continue; ++ } ++ ++ float chance = pEnchant->amount[s] != 0 ? float(pEnchant->amount[s]) : me->GetWeaponProcChance(); ++ ++ if (entry) ++ { ++ if (entry->PPMChance) ++ chance = me->GetPPMProcChance(proto->Delay, entry->PPMChance, spellInfo); ++ else if (entry->customChance) ++ chance = float(entry->customChance); ++ } ++ ++ //// Apply spell mods ++ //ApplySpellMod(pEnchant->spellid[s], SPELLMOD_CHANCE_OF_SUCCESS, chance); ++ ++ // Shiv has 100% chance to apply the poison ++ if (me->FindCurrentSpellBySpellId(5938) && e_slot == TEMP_ENCHANTMENT_SLOT) ++ chance = 100.0f; ++ ++ if (roll_chance_f(chance)) ++ { ++ if (spellInfo->IsPositive()) ++ me->CastSpell(me, spellInfo, true, item); ++ else ++ me->CastSpell(target, spellInfo, true, item); ++ } ++ } ++ } ++} ++ ++bool bot_minion_ai::GlobalUpdate(uint32 diff) ++{ ++ lastdiff = diff; ++ ++ if (_updateTimerMedium <= diff) ++ { ++ _updateTimerMedium = 500; ++ ++ //Medium-timed updates ++ ++ //send stats update for group frames ++ if (me->IsInWorld() && !IAmFree()) ++ { ++ if (Group* gr = master->GetGroup()) ++ { ++ if (gr->IsMember(me->GetGUID())) ++ { ++ WorldPacket data; ++ BuildGrouUpdatePacket(&data); ++ ++ Player* member; ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ member = itr->GetSource(); ++ if (member/* && !member->IsWithinDist(me, member->GetSightRange(), false)*/) ++ member->GetSession()->SendPacket(&data); ++ } ++ } ++ } ++ ++ //update pvp state ++ if (me->GetByteValue(UNIT_FIELD_BYTES_2, 1) != master->GetByteValue(UNIT_FIELD_BYTES_2, 1)) ++ me->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); ++ } ++ } ++ ++ if (!me->IsAlive()) ++ return false; ++ ++ return true; ++} ++ ++void bot_minion_ai::CommonTimers(uint32 diff) ++{ ++ events.Update(diff); ++ SpellTimers(diff); ++ ++ if (Potion_cd > diff && (Potion_cd < POTION_CD || !me->IsInCombat())) ++ Potion_cd -= diff; ++ ++ if (pvpTrinket_cd > diff) pvpTrinket_cd -= diff; ++ if (GC_Timer > diff) GC_Timer -= diff; ++ if (temptimer > diff) temptimer -= diff; ++ if (checkAurasTimer != 0) --checkAurasTimer; ++ if (wait != 0) --wait; ++ ++ if (roleTimer > diff) roleTimer -= diff; ++ else if (roleTimer > 0) roleTimer = 0; ++ ++ if (_saveTimer > diff) _saveTimer -= diff; ++ else if (_saveTimer > 0) _saveTimer = 0; ++ ++ if (_powersTimer > diff) _powersTimer -= diff; ++ else if (_powersTimer > 0) _powersTimer = 0; ++ ++ if (_chaseTimer > diff) _chaseTimer -= diff; ++ else if (_chaseTimer > 0) _chaseTimer = 0; ++ ++ if (checkMasterTimer > diff) checkMasterTimer -= diff; ++ else if (checkMasterTimer > 0) checkMasterTimer = 0; ++ ++ if (IAmFree()) ++ UpdateReviveTimer(diff); ++ ++ if (_bootTimer > int32(diff)) _bootTimer -= diff; ++ else if (_bootTimer > 0) _bootTimer = 0; ++ ++ if (_updateTimerMedium > diff) _updateTimerMedium -= diff; ++} ++ ++void bot_pet_ai::CommonTimers(uint32 diff) ++{ ++ events.Update(diff); ++ SpellTimers(diff); ++ ++ if (GC_Timer > diff) GC_Timer -= diff; ++ if (temptimer > diff) temptimer -= diff; ++ if (roleTimer > diff) roleTimer -= diff; ++ if (checkAurasTimer != 0) --checkAurasTimer; ++ if (wait != 0) --wait; ++ ++ if (regenTimer_mp > diff) regenTimer_mp -= diff; ++ else if (regenTimer_mp > 0) regenTimer_mp = 0; ++ if (regenTimer_hp > diff) regenTimer_hp -= diff; ++ else if (regenTimer_hp > 0) regenTimer_hp = 0; ++} ++ ++void bot_minion_ai::UpdateReviveTimer(uint32 diff) ++{ ++ if (me->IsAlive()) ++ return; ++ ++ if (_reviveTimer > diff) _reviveTimer -= diff; ++ else ++ { ++ if (IAmFree()) ++ BotMgr::ReviveBot(me); ++ else ++ if (_reviveTimer > 0) _reviveTimer = 0; ++ } ++} ++ ++void bot_minion_ai::EnterEvadeMode(bool /*force*/) ++{ ++ //if (me->IsInCombat()) ++ // return; ++ if (me->GetVictim()) ++ return; ++ if (IsCasting()) ++ return; ++ if (CCed(me, true)) ++ return; ++ ++ if (_atHome && !_evadeMode) ++ return; ++ ++ _atHome = true; ++ ++ if (!IAmFree()) ++ { ++ _evadeMode = false; ++ return; ++ } ++ ++ Creature* pet = me->GetBotsPet(); ++ ++ //me->CombatStop(); ++ //if (pet) ++ // pet->CombatStop(); ++ ++ //if (!_evadeMode) ++ //{ ++ // ASSERT(!evadeEvent); ++ // evadeEvent = new EvadeEvent(me->GetGUID(), true); ++ // events.AddEvent(evadeEvent, events.CalculateTime(0)); ++ // events.Update(0); ++ //} ++ ++ uint16 mapid; ++ Position pos; ++ GetHomePosition(mapid, &pos); ++ ++ if (mapid != me->GetMapId() || me->GetDistance(pos) > 30.f || _evadeCount >= 3) ++ { ++ //TeleportHome(); ++ ++ if (!teleHomeEvent || teleHomeEvent->to_Abort) ++ { ++ teleHomeEvent = new TeleportHomeEvent(this); ++ events.AddEvent(teleHomeEvent, events.CalculateTime(1000)); ++ me->CastSpell(me, COSMETIC_TELEPORT_EFFECT, true); ++ } ++ _evadeMode = false; ++ return; ++ } ++ ++ float dist = me->GetDistance(pos); ++ if (dist > 1.5f) ++ { ++ if (!_evadeMode) ++ ++_evadeCount; ++ else if (me->isMoving() && Rand() > 30) ++ return; ++ ++ _evadeMode = true; ++ ++ //me->AddUnitState(UNIT_STATE_EVADE); ++ //if (pet) ++ // pet->AddUnitState(UNIT_STATE_EVADE); ++ ++ me->BotStopMovement(); ++ if (pet) ++ pet->BotStopMovement(); ++ ++ bool farpoint = true; ++ if (dist > 50) ++ { ++ float dx = pos.m_positionX - me->m_positionX; ++ float dy = pos.m_positionY - me->m_positionY; ++ float fdx = fabs(dx); float fdy = fabs(dy); ++ float divider = ++ fdx > 1800 || fdy > 1800 ? 120.0f : ++ fdx > 900 || fdy > 900 ? 60.0f : ++ fdx > 600 || fdy > 600 ? 30.0f : ++ fdx > 400 || fdy > 400 ? 20.0f : ++ fdx > 200 || fdy > 200 ? 10.0f : ++ fdx > 100 || fdy > 100 ? 7.0f : 3.0f; ++ dx = dx / divider + me->m_positionX; ++ dy = dy / divider + me->m_positionY; ++ float z = me->GetMap()->GetHeight(dx, dy, me->m_positionZ); ++ ++ if (z > INVALID_HEIGHT && fabs(me->m_positionZ - z) > 0.05f) ++ { ++ me->GetMotionMaster()->MovePoint(mapid, dx, dy, z + 0.1f, true); ++ farpoint = false; ++ } ++ } ++ ++ if (farpoint) ++ { ++ me->GetMotionMaster()->MovePoint(mapid, pos); ++ if (pet) ++ pet->SetBotCommandState(COMMAND_FOLLOW, true); ++ } ++ ++ return; ++ } ++ ++ if (me->isMoving()) ++ return; ++ ++ _evadeMode = false; ++ _evadeCount = 0; ++ ++ me->SetFacingTo(pos.GetOrientation()); ++ me->SetStandState(UNIT_STAND_STATE_SIT); ++ ++ //if (evadeEvent) ++ //{ ++ // evadeEvent->to_Abort = true; ++ // evadeEvent->Execute(evadeEvent->m_addTime, evadeEvent->m_execTime); ++ //} ++ ++ //me->ClearUnitState(UNIT_STATE_EVADE); ++ //if (pet) ++ // pet->ClearUnitState(UNIT_STATE_EVADE); ++ ++ me->setFaction(me->GetCreatureTemplate()->faction); ++ if (pet) ++ pet->setFaction(pet->GetCreatureTemplate()->faction); ++ ++ //RestorePositionMods(); ++} ++//TeleportHome() ONLY CALLED THROUGH EVENTPROCESSOR ++void bot_minion_ai::TeleportHome() ++{ ++ ASSERT(teleHomeEvent); ++ //ASSERT(IAmFree()); ++ ++ AbortTeleport(); ++ ++ uint16 mapid; ++ Position pos; ++ GetHomePosition(mapid, &pos); ++ ++ Map* map = sMapMgr->CreateBaseMap(mapid); ++ ASSERT(map && !map->Instanceable()); ++ BotMgr::TeleportBot(me, map, &pos); ++ ++ spawned = false; ++ _evadeCount = 0; ++ ++ //Reset(); ++} ++//FinishTeleport(uint32, float, float, float, float) ONLY CALLED THROUGH EVENTPROCESSOR ++bool bot_minion_ai::FinishTeleport(/*uint32 mapId, uint32 instanceId, float x, float y, float z, float o*/) ++{ ++ ASSERT(teleFinishEvent); ++ //ASSERT(!IAmFree()); ++ ASSERT(!me->IsInWorld()); ++ ++ AbortTeleport(); ++ ++ //1) Cannot teleport: master disappeared - return home ++ if (IAmFree()/* || master->GetSession()->isLogingOut()*/) ++ { ++ uint16 mapid; ++ Position pos; ++ GetHomePosition(mapid, &pos); ++ ++ teleHomeEvent = new TeleportHomeEvent(this); ++ events.AddEvent(teleHomeEvent, events.CalculateTime(0)); //make sure event will be deleted ++ teleHomeEvent->to_Abort = true; //make sure event will not be executed twice ++ teleHomeEvent->Execute(0,0); ++ _evadeMode = false; ++ ++ return false; ++ } ++ ++ Map* map = master->FindMap(); ++ //2) Cannot teleport: map not found or forbidden - delay teleport ++ if (!map || master->GetBotMgr()->RestrictBots(me, true)) ++ { ++ //ChatHandler ch(master->GetSession()); ++ //ch.PSendSysMessage("Your bot %s cannot teleport to you. Restricted bot access on this map...", me->GetName().c_str()); ++ teleFinishEvent = new TeleportFinishEvent(this/*, master->GetMapId(), x, y, z, o*/); ++ events.AddEvent(teleFinishEvent, events.CalculateTime(5000)); ++ return false; ++ } ++ ++ me->SetMap(map); ++ me->Relocate(master); ++ map->AddToMap(me); ++ me->BotStopMovement(); ++ //bot->SetAI(oldAI); ++ me->IsAIEnabled = true; ++ ++ master->m_Controlled.insert(me); ++ me->CastSpell(me, COSMETIC_TELEPORT_EFFECT, true); ++ ++ //update group member online state ++ if (Group* gr = master->GetGroup()) ++ if (gr->IsMember(me->GetGUID())) ++ gr->SendUpdate(); ++ ++ return true; ++} ++ ++void bot_minion_ai::AbortTeleport() ++{ ++ if (teleHomeEvent) ++ { ++ teleHomeEvent->to_Abort = true; ++ teleHomeEvent = NULL; ++ } ++ ++ if (teleFinishEvent) ++ { ++ teleFinishEvent->to_Abort = true; ++ teleFinishEvent = NULL; ++ } ++ ++ CancelBoot(); ++} ++ ++void bot_ai::GetHomePosition(uint16& mapid, Position* pos) ++{ ++ CreatureData const* data = me->GetCreatureData(); ++ mapid = data->mapid; ++ pos->Relocate(data->posX, data->posY, data->posZ, data->orientation); ++} ++ ++void bot_ai::KillEvents(bool force) ++{ ++ events.KillAllEvents(force); ++} ++ ++bool bot_ai::IsBotImmuneToSpell(SpellInfo const* spellInfo) const ++{ ++ if (spellInfo->_IsPositiveSpell()) ++ return false; ++ ++ if (_botclass >= BOT_CLASS_EX_START) ++ { ++ //bots of W3 classes will not be easily CCed ++ if (spellInfo->GetDuration() > 0 && spellInfo->GetDuration() <= 3000 && ++ (spellInfo->HasAura(SPELL_AURA_MOD_STUN) || ++ spellInfo->HasAura(SPELL_AURA_MOD_CONFUSE) || ++ spellInfo->HasAura(SPELL_AURA_MOD_CHARM) || ++ spellInfo->HasAura(SPELL_AURA_MOD_FEAR) || ++ spellInfo->HasAura(SPELL_AURA_MOD_PACIFY) || ++ spellInfo->HasAura(SPELL_AURA_MOD_ROOT) || ++ spellInfo->HasAura(SPELL_AURA_AOE_CHARM))) ++ return true; ++ } ++ return false; ++} ++ ++MeleeHitOutcome bot_ai::BotRollCustomMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const ++{ ++ if (GetNextAttackMeleeOutCome() != MELEE_HIT_CRUSHING) ++ return GetNextAttackMeleeOutCome(); ++ return me->RollMeleeOutcomeAgainst(victim, attType); ++} ++ ++void bot_ai::BotJumpInPlaceInFrontOf(Position* pos, float speedXY, float maxHeight) ++{ ++ float sign = (me->GetPositionX() < pos->GetPositionX()) ? 1.f : -1.f; ++ float x = me->GetPositionX() + 0.14f * sign; ++ sign = (me->GetPositionY() < pos->GetPositionY()) ? 1.f : -1.f; ++ float y = me->GetPositionY() + 0.14f * sign; ++ float z = me->GetPositionZ() - 0.01f; ++ //float floorz = Map::GetHeight(x, y, z, true, 5.f); ++ ++ //me->AttackStop(); ++ me->BotStopMovement(); ++ me->GetMotionMaster()->MoveJump(x, y, z, speedXY, maxHeight); ++} ++ ++void bot_ai::BuildGrouUpdatePacket(WorldPacket* data) ++{ ++ uint32 mask = GROUP_UPDATE_FULL; ++ ++ if (mask & GROUP_UPDATE_FLAG_POWER_TYPE) // if update power type, update current/max power also ++ mask |= (GROUP_UPDATE_FLAG_CUR_POWER | GROUP_UPDATE_FLAG_MAX_POWER); ++ ++ if (mask & GROUP_UPDATE_FLAG_PET_POWER_TYPE) // same for pets ++ mask |= (GROUP_UPDATE_FLAG_PET_CUR_POWER | GROUP_UPDATE_FLAG_PET_MAX_POWER); ++ ++ uint32 byteCount = 0; ++ for (uint8 i = 1; i < GROUP_UPDATE_FLAGS_COUNT; ++i) ++ if (mask & (1 << i)) ++ byteCount += GroupUpdateLength[i]; ++ ++ data->Initialize(SMSG_PARTY_MEMBER_STATS, 8 + 4 + byteCount); ++ *data << me->GetPackGUID(); ++ *data << uint32(mask); ++ ++ if (mask & GROUP_UPDATE_FLAG_STATUS) ++ { ++ uint16 playerStatus = MEMBER_STATUS_ONLINE; ++ if (me->IsPvP()) ++ playerStatus |= MEMBER_STATUS_PVP; ++ ++ if (!me->IsAlive()) ++ playerStatus |= MEMBER_STATUS_DEAD; ++ ++ if (me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) ++ playerStatus |= MEMBER_STATUS_PVP_FFA; ++ ++ *data << uint16(playerStatus); ++ } ++ ++ if (mask & GROUP_UPDATE_FLAG_CUR_HP) ++ *data << uint32(me->GetHealth()); ++ ++ if (mask & GROUP_UPDATE_FLAG_MAX_HP) ++ *data << uint32(me->GetMaxHealth()); ++ ++ Powers powerType = me->getPowerType(); ++ if (mask & GROUP_UPDATE_FLAG_POWER_TYPE) ++ *data << uint8(powerType); ++ ++ if (mask & GROUP_UPDATE_FLAG_CUR_POWER) ++ *data << uint16(me->GetPower(powerType)); ++ ++ if (mask & GROUP_UPDATE_FLAG_MAX_POWER) ++ *data << uint16(me->GetMaxPower(powerType)); ++ ++ if (mask & GROUP_UPDATE_FLAG_LEVEL) ++ *data << uint16(me->getLevel()); ++ ++ if (mask & GROUP_UPDATE_FLAG_ZONE) ++ *data << uint16(me->GetZoneId()); ++ ++ if (mask & GROUP_UPDATE_FLAG_POSITION) ++ { ++ *data << uint16(me->GetPositionX()); ++ *data << uint16(me->GetPositionY()); ++ } ++ ++ //TODO: ...? ++ //if (mask & GROUP_UPDATE_FLAG_AURAS) ++ //{ ++ // uint64 auramask = player->GetAuraUpdateMaskForRaid(); ++ // *data << uint64(auramask); ++ // for (uint32 i = 0; i < MAX_AURAS; ++i) ++ // { ++ // if (auramask & (uint64(1) << i)) ++ // { ++ // AuraApplication const* aurApp = player->GetVisibleAura(i); ++ // *data << uint32(aurApp ? aurApp->GetBase()->GetId() : 0); ++ // *data << uint8(1); ++ // } ++ // } ++ //} ++ ++ //if (mask & GROUP_UPDATE_FLAG_VEHICLE_SEAT) ++ //{ ++ // if (Vehicle* veh = me->GetVehicle()) ++ // *data << uint32(veh->GetVehicleInfo()->m_seatID[me->m_movementInfo.transport.seat]); ++ // else ++ // *data << uint32(0); ++ //} ++} ++ ++bool bot_ai::IsBotCustomSpell(uint32 spellId) ++{ ++ return BotCustomSpells.find(spellId) != BotCustomSpells.end(); ++} ++ ++void bot_ai::InitBotCustomSpells() ++{ ++ if (SPELLS_DEFINED) ++ { ++ //TC_LOG_ERROR("entities.player", "Bot custom spells initialization... fail..."); ++ return; ++ } ++ //TC_LOG_ERROR("entities.player", "Bot custom spells initialization... success..."); ++ SPELLS_DEFINED = true; ++ ++ //see bot_ai.h::CommonValues::CUSTOM_SPELLS ++ //all ids must be here ++ //BotCustomSpells.insert(BLIZZARD_VISUAL_PERSISTENT_AURA); ++ //BotCustomSpells.insert(BLIZZARD_VISUAL_PROC); ++ ++ //BotCustomSpells.insert(SPELL_COMBAT_SPECIAL_2H_ATTACK); //exclusive ++ BotCustomSpells.insert(SPELL_TRANSPARENCY_50);//3.1 ++ BotCustomSpells.insert(SPELL_NETHERWALK);//3 ++ BotCustomSpells.insert(SPELL_MIRROR_IMAGE_BM);//4 ++ ++ uint32 trig; ++ SpellInfo* trigInfo; ++ uint32 spellId; ++ SpellInfo* sinfo; ++ ++ //1) BLIZZARD ++ //1.1) BLIZZARD PROC ++ //trig = BLIZZARD_VISUAL_PROC; //rain ++ //trigInfo = const_cast(sSpellMgr->GetSpellInfo(trig)); ++ ++ //trigInfo->Dispel = DISPEL_NONE; ++ //trigInfo->Mechanic = MECHANIC_NONE; ++ //trigInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //6 - 100 yds ++ ++ //trigInfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //1 - instant //3 - 0.5 sec ++ //trigInfo->ManaCost = 0; ++ //trigInfo->ManaCostPercentage = 0; ++ //trigInfo->ManaCostPerlevel = 0; ++ ++ //trigInfo->Effects[0].Effect = SPELL_EFFECT_DUMMY; ++ //trigInfo->Effects[0].BasePoints = 1; ++ //trigInfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CHANNEL_TARGET); ++ //trigInfo->Effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ENEMY); ++ //trigInfo->Effects[0].ApplyAuraName = SPELL_AURA_NONE; ++ //trigInfo->Effects[0].Amplitude = 0; ++ //trigInfo->Effects[0].TriggerSpell = 0; ++ //trigInfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS); //14 ++ ////1.1) END BLIZZARD PROC ++ ++ //spellId = BLIZZARD_VISUAL_PERSISTENT_AURA; //34167, 34183 ++ //sinfo = const_cast(sSpellMgr->GetSpellInfo(spellId)); ++ ++ //sinfo->Dispel = DISPEL_NONE; ++ //sinfo->Mechanic = MECHANIC_NONE; ++ //sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //6 - 100 yds ++ ++ //sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //3 - 0.5 sec ++ //sinfo->DurationEntry = sSpellDurationStore.LookupEntry(1); //1 - 10 sec //32 - 6 seconds ++ //sinfo->ManaCost = 0; ++ //sinfo->ManaCostPercentage = 74; ++ //sinfo->ManaCostPerlevel = 0; ++ //sinfo->AttributesEx2 |= SPELL_ATTR2_UNK22; ++ //sinfo->AttributesEx5 |= SPELL_ATTR5_HASTE_AFFECT_DURATION; ++ //sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; ++ //sinfo->InterruptFlags = 0x0000000F; //15 ++ //sinfo->ChannelInterruptFlags = 0x00007C3C; //31788 ++ ++ //sinfo->Effects[0].Effect = SPELL_EFFECT_PERSISTENT_AREA_AURA; ++ //sinfo->Effects[0].BasePoints = 1; ++ //sinfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DYNOBJ_ENEMY); ++ //sinfo->Effects[0].TargetB = SpellImplicitTargetInfo(0); ++ //sinfo->Effects[0].ApplyAuraName = SPELL_AURA_DUMMY; ++ //sinfo->Effects[0].Amplitude = 0; ++ //sinfo->Effects[0].TriggerSpell = 0; ++ //sinfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS); //14 ++ ++ //sinfo->Effects[1].Effect = SPELL_EFFECT_APPLY_AURA; ++ //sinfo->Effects[1].BasePoints = 1; ++ //sinfo->Effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ //sinfo->Effects[1].TargetB = SpellImplicitTargetInfo(0); ++ //sinfo->Effects[1].ApplyAuraName = SPELL_AURA_PERIODIC_TRIGGER_SPELL; ++ //sinfo->Effects[1].Amplitude = 2000; ++ //sinfo->Effects[1].TriggerSpell = trig; ++ //sinfo->Effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS); //14 ++ //1) END BLIZZARD ++ {} ++ //2) SPELL_COMBAT_SPECIAL_2H_ATTACK ++ spellId = SPELL_COMBAT_SPECIAL_2H_ATTACK; //1132 ++ sinfo = const_cast(sSpellMgr->GetSpellInfo(spellId)); ++ sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //6 - 100 yds ++ sinfo->Attributes &= ~(SPELL_ATTR0_CANT_USED_IN_COMBAT); ++ sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_DEAD; ++ //2) END SPELL_COMBAT_SPECIAL_2H_ATTACK ++ ++ //3) WINDWALK ++ //3.1) TRANSPARENCY ++ trig = SPELL_TRANSPARENCY_50; //44816 ++ trigInfo = const_cast(sSpellMgr->GetSpellInfo(trig)); ++ trigInfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_CASTABLE_WHILE_SITTING); ++ trigInfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH); ++ trigInfo->AuraInterruptFlags = ++ AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | ++ AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish ++ trigInfo->CasterAuraStateNot = 0; ++ //3.1) END TRANSPARENCY ++ ++ ++ spellId = SPELL_NETHERWALK; //31599 ++ sinfo = const_cast(sSpellMgr->GetSpellInfo(spellId)); ++ ++ sinfo->SpellLevel = 0; ++ sinfo->MaxLevel = 80; ++ sinfo->RecoveryTime = 5000; ++ sinfo->PowerType = POWER_MANA; ++ sinfo->ManaCost = 75; ++ sinfo->ManaCostPercentage = 0; ++ sinfo->ManaCostPerlevel = 0; ++ sinfo->Attributes &= ~(SPELL_ATTR0_UNK11); ++ sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_CASTABLE_WHILE_SITTING | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY); ++ sinfo->AttributesEx &= ~SPELL_ATTR1_UNK11; ++ sinfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH | SPELL_ATTR1_NO_THREAT); ++ sinfo->AttributesEx2 |= SPELL_ATTR2_UNK1; ++ sinfo->AuraInterruptFlags = ++ AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | ++ AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish ++ sinfo->CasterAuraStateNot = 0; ++ ++ sinfo->Effects[0].Effect = SPELL_EFFECT_APPLY_AURA; ++ sinfo->Effects[0].BasePoints = 100; ++ sinfo->Effects[0].RealPointsPerLevel = 2.5f; ++ sinfo->Effects[0].ValueMultiplier = 1.0f; ++ sinfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ sinfo->Effects[0].ApplyAuraName = SPELL_AURA_MOD_INVISIBILITY; ++ sinfo->Effects[0].Amplitude = 0; ++ sinfo->Effects[0].TriggerSpell = 0; ++ sinfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); ++ ++ sinfo->Effects[1].Effect = SPELL_EFFECT_APPLY_AURA; ++ sinfo->Effects[1].BasePoints = 10; ++ sinfo->Effects[1].RealPointsPerLevel = 0.5f; ++ sinfo->Effects[1].ValueMultiplier = 1.0f; ++ sinfo->Effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ sinfo->Effects[1].TargetB = SpellImplicitTargetInfo(0); ++ sinfo->Effects[1].ApplyAuraName = SPELL_AURA_MOD_INCREASE_SPEED; ++ sinfo->Effects[1].Amplitude = 0; ++ sinfo->Effects[1].TriggerSpell = 0; ++ sinfo->Effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 ++ ++ sinfo->Effects[2].Effect = SPELL_EFFECT_TRIGGER_SPELL; ++ sinfo->Effects[2].BasePoints = 0; ++ sinfo->Effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ sinfo->Effects[2].TargetB = SpellImplicitTargetInfo(0); ++ sinfo->Effects[2].ApplyAuraName = SPELL_AURA_NONE; ++ sinfo->Effects[2].Amplitude = 0; ++ sinfo->Effects[2].TriggerSpell = trig; ++ sinfo->Effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 ++ //3) END WINDWALK ++ ++ //4) MIRROR IMAGE (BLADEMASTER) ++ spellId = SPELL_MIRROR_IMAGE_BM; //69936 ++ sinfo = const_cast(sSpellMgr->GetSpellInfo(spellId)); ++ ++ sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //1 - self only //6 - 100 yds ++ sinfo->DurationEntry = sSpellDurationStore.LookupEntry(566); //566 - 0 sec //3 - 60 sec //1 - 10 sec //32 - 6 seconds ++ sinfo->RecoveryTime = 8000; ++ sinfo->PowerType = POWER_MANA; ++ sinfo->ManaCost = 125; ++ sinfo->ManaCostPercentage = 0; ++ sinfo->ManaCostPerlevel = 0; ++ sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT/* | SPELL_ATTR0_CASTABLE_WHILE_SITTING | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY*/); ++ sinfo->AttributesEx2 &= ~(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS); ++ sinfo->AttributesEx3 |= SPELL_ATTR3_DONT_DISPLAY_RANGE; ++ ++ sinfo->Effects[0].Effect = SPELL_EFFECT_DUMMY; ++ sinfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ sinfo->Effects[0].MiscValue = 0; ++ sinfo->Effects[0].MiscValueB = 0; ++ sinfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); ++ //4) END MIRROR IMAGE (BLADEMASTER) ++} +diff --git a/src/server/game/AI/NpcBots/bot_ai.h b/src/server/game/AI/NpcBots/bot_ai.h +new file mode 100644 +index 0000000..d594cdf +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_ai.h +@@ -0,0 +1,908 @@ ++#ifndef _BOT_AI_H ++#define _BOT_AI_H ++ ++#include "ScriptedCreature.h" ++/* ++NpcBot System by Graff (onlysuffering@gmail.com) ++Original patch from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch ++*/ ++ ++struct PlayerClassLevelInfo; ++ ++//class VisibilityUpdateEvent; ++class TeleportHomeEvent; ++//class EvadeEvent; ++class TeleportFinishEvent; ++ ++enum CommonValues ++{ ++//MISC ++ BOT_ENTRY_BEGIN = 70001, ++ BOT_ENTRY_END = 71000, ++ BOT_ICON_ON = 9, //GOSSIP_ICON_BATTLE, ++ BOT_ICON_OFF = 7, //GOSSIP_ICON_TALK, ++ BOT_MAX_CHASE_RANGE = 120, //yds ++ BOT_EVADE_TIME = 3000, //ms ++//COMMON SPELLS ++ MANAPOTION = 32453,//"Uses a Holy elixir to heal the caster for 32000" ++ HEALINGPOTION = 15504,//"Drinks Holy Elixir to heal the caster" ++ DRINK = 66041,//"Restores 4% mana per sec for 30 sec" ++ EAT = 66478,//"Restores Health" ++ PVPTRINKET = 42292,//PvP Trinket no CD ++ BERSERK = 46587,//68378,//900%/150% ++//COMMON CDs ++ POTION_CD = 60000,//default 60sec potion cd ++ RATIONS_CD = 1000, //update rations every X milliseconds ++//COMMON PASSIVES ++ //1) DEPRECATED"Increase(d) @whatever" ++ //SPELL_BONUS_10 = 33021,//10spp ++ //SPELL_BONUS_50 = 45011,//50spp ++ //SPELL_BONUS_150 = 28141,//150spp ++ //SPELL_BONUS_250 = 69709,//250spp ++ //FIREDAM_86 = 33816,//86 fire spp ++ //MANAREGEN45 = 35867,//45 mp5 ++ //MANAREGEN100 = 45216,//100 mp5 ++ //SPELL_PEN_5 = 31921,//5 sppen ++ //SPELL_PEN_20 = 26283,//20 sppen ++ //2) DEPRECATEDTalents ++ //HASTE /*Gift of the EarthMother*/= 51183,//rank 5 10% spell haste ++ //HASTE2 /*Blood Frenzy - warrior*/ = 29859,//rank 2 10% melee haste, bonus for rend (warriors only) ++ //HASTE3 /* "Haste" */ = 29418,//rank 0 10% increased ranged attack speed ++ //CRITS /*Thundering Strikes-sham*/= 16305,//rank 5 5% crit melee/spell ++ //HOLYCRIT /*Holy Spec - priest*/ = 15011,//rank 5 5% holy crit ++ //DODGE /*Anticipation - paladin*/ = 20100,//rank 5 5% dodge ++ //PARRY /*Deflection - warrior*/ = 16466,//rank 5 5% parry ++ //BLOCK/*zzOLD Shield Specialization*/= 16253,//rank 1 5% block 10% amount, 3.3.5 & 4.3.4 deprecated ++ //PRECISION /*Precision - rogue*/ = 13843,//rank 3 3% melee/spell hit ++ //PRECISION /*Precision - warrior*/ = 29592,//rank 3 3% melee hit ++ //PRECISION2/*Precision - mage*/ = 29440,//rank 3 3% spell hit, -3% mana cost ++ //DMG_TAKEN/*Deadened Nerves - rogue*/= 31383,//rank 3 6% reduced damage taken all ++ //EXPERTISE /*Weapon Expertise-rogue*/= 30919,//rank 1 5 expertise ++ //EXPERTISE2/*Weapon Expertise-rogue*/= 30920,//rank 2 10 expertise ++ //3) Pet/Special ++ THREAT /**** (unused) ****/ = 57339,//+43% threat generated ++ //BOR /*Blood of Rhino - pet*/ = 53482,//rank 2 +40% healing taken ++ //BOAR /*Boar's Speed - pet*/ = 19596,//rank 1 +30% movement speed ++ RCP /*Rogue Class Passive*/ = 21184,//-27% threat caused ++ DEFENSIVE_STANCE_PASSIVE /*Warrior*/= 7376, //+45% threat -10% damage taken -5% damage done ++//COMMON GOSSIPS ++ GOSSIP_SERVE_MASTER = 70001, //"I live only to serve the master." ++ GOSSIP_NEED_SMTH = 70002, //"You need something?" ++ GOSSIP_MURDER = 70003,//"Mortals... usually I kill wretches like you at sight" ++ GOSSIP_CLASS_BM = 70004, ++ GOSSIP_SENDER_BEGIN = 6000, ++ GOSSIP_SENDER_CLASS, ++ GOSSIP_SENDER_EQUIPMENT, ++ GOSSIP_SENDER_EQUIPMENT_LIST, ++ GOSSIP_SENDER_EQUIPMENT_SHOW, ++ GOSSIP_SENDER_EQUIPMENT_INFO, ++ GOSSIP_SENDER_UNEQUIP, ++ GOSSIP_SENDER_UNEQUIP_ALL, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_BEGIN = GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_MHAND = GOSSIP_SENDER_EQUIP_AUTOEQUIP_BEGIN, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_OHAND, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_RANGED, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_HEAD, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_SHOULDERS, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_CHEST, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_WAIST, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_LEGS, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_FEET, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_WRIST, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_HANDS, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_BACK, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_BODY, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER1, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER2, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET1, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET2, ++ GOSSIP_SENDER_EQUIP_AUTOEQUIP_NECK, ++ GOSSIP_SENDER_EQUIP_RESET, ++ GOSSIP_SENDER_EQUIP, ++ GOSSIP_SENDER_EQUIP_BEGIN = GOSSIP_SENDER_EQUIP, ++ GOSSIP_SENDER_EQUIP_MHAND = GOSSIP_SENDER_EQUIP_BEGIN, ++ GOSSIP_SENDER_EQUIP_OHAND, ++ GOSSIP_SENDER_EQUIP_RANGED, ++ GOSSIP_SENDER_EQUIP_HEAD, ++ GOSSIP_SENDER_EQUIP_SHOULDERS, ++ GOSSIP_SENDER_EQUIP_CHEST, ++ GOSSIP_SENDER_EQUIP_WAIST, ++ GOSSIP_SENDER_EQUIP_LEGS, ++ GOSSIP_SENDER_EQUIP_FEET, ++ GOSSIP_SENDER_EQUIP_WRIST, ++ GOSSIP_SENDER_EQUIP_HANDS, ++ GOSSIP_SENDER_EQUIP_BACK, ++ GOSSIP_SENDER_EQUIP_BODY, ++ GOSSIP_SENDER_EQUIP_FINGER1, ++ GOSSIP_SENDER_EQUIP_FINGER2, ++ GOSSIP_SENDER_EQUIP_TRINKET1, ++ GOSSIP_SENDER_EQUIP_TRINKET2, ++ GOSSIP_SENDER_EQUIP_NECK, ++ GOSSIP_SENDER_ROLES, ++ GOSSIP_SENDER_ROLES_TOGGLE, ++ GOSSIP_SENDER_ABILITIES, ++ GOSSIP_SENDER_ABILITIES_USE, ++ GOSSIP_SENDER_HIRE, ++ GOSSIP_SENDER_DISMISS, ++ GOSSIP_SENDER_JOIN_GROUP, ++ GOSSIP_SENDER_LEAVE_GROUP, ++ GOSSIP_SENDER_FORMATION, ++ GOSSIP_SENDER_FORMATION_DISTANCE, ++ GOSSIP_SENDER_SCAN, ++ GOSSIP_SENDER_SCAN_OWNER, ++ GOSSIP_SENDER_SCAN_OWNER_ABILITY, ++ GOSSIP_SENDER_DEBUG, ++ GOSSIP_SENDER_DEBUG_ACTION, ++//COMMON NPCS ++ NPC_WORLD_TRIGGER = 22515, ++//COMMON GAMEEVENTS ++ GAME_EVENT_WINTER_VEIL = 2, ++//COMMON MOUNTS SPELLS ++ REINDEER = 25859, ++ REINDEER_FLY = 44827, ++//ADVANCED ++ COSMETIC_TELEPORT_EFFECT = 52096,//visual instant cast omni ++ //COSMETIC_SMOKING_CORPSE_AURA = 51201,//black model + fire step (permanent) ++ ++ ++////CUSTOM SPELLS ++//ARCHMAGE ++ //modify ++/**/BLIZZARD_VISUAL_PERSISTENT_AURA = 34167, ++ BLIZZARD_VISUAL_PROC = 29969, ++ ++//BLADEMASTER ++/**/ //- used explicitly within the script ++ //SPELLS ++ //unmodify ++ SPELL_DEATH_GRIP_JUMP = 49575, ++/**/SPELL_CRITICAL_STRIKE = 1132, ++ SPELL_BURNING_BLADE_BLADEMASTER = 32281,//horde flag visual ++ SPELL_POSESS = 17250,//immunity,invis,stun ++ //SPELL_SELFSTUN = 24883,//green smoke, transparency, stun ++ SPELL_STUN_FREEZE_ANIM = 59123,//stun forever, full stop ++ //modify ++ SPELL_TRANSPARENCY_50 = 44816, ++/**/SPELL_NETHERWALK = 31599, ++/**/SPELL_MIRROR_IMAGE_BM = 69936,//blank spell ++ //SOUNDS ++ SOUND_FREEZE_IMPACT_WINDWALK = 29, ++ SOUND_AXE_2H_IMPACT_FLESH_CRIT = 158, ++ SOUND_ABSORB_GET_HIT = 3334, ++ SOUND_MISS_WHOOSH_2H = 7081, ++ ++//OTHER ++ //unmodify ++ SPELL_VERTEX_COLOR_BLACK = 39662,//black color model full ++ //SPELL_NIGHTMARE_VULNERABILITY = 54199,//100% dmg taken, 100% crit chance taken (x4 dmg taken) ++ //modify ++ SPELL_COMBAT_SPECIAL_2H_ATTACK = 44079 ++}; ++ ++enum BotClasses ++{ ++ BOT_CLASS_NONE = CLASS_NONE, ++ BOT_CLASS_WARRIOR = CLASS_WARRIOR, ++ BOT_CLASS_PALADIN = CLASS_PALADIN, ++ BOT_CLASS_HUNTER = CLASS_HUNTER, ++ BOT_CLASS_ROGUE = CLASS_ROGUE, ++ BOT_CLASS_PRIEST = CLASS_PRIEST, ++ BOT_CLASS_DEATH_KNIGHT = CLASS_DEATH_KNIGHT, ++ BOT_CLASS_SHAMAN = CLASS_SHAMAN, ++ BOT_CLASS_MAGE = CLASS_MAGE, ++ BOT_CLASS_WARLOCK = CLASS_WARLOCK, ++ BOT_CLASS_DRUID = CLASS_DRUID, ++ ++ BOT_CLASS_BM, ++ ++ BOT_CLASS_END, ++ ++ BOT_CLASS_NORMAL_START = BOT_CLASS_WARRIOR, ++ BOT_CLASS_NORMAL_END = BOT_CLASS_BM, ++ BOT_CLASS_EX_START = BOT_CLASS_BM, ++ BOT_CLASS_EX_END ++}; ++ ++enum BotStances ++{ ++ BOT_STANCE_NONE = 0, ++ WARRIOR_BATTLE_STANCE = BOT_CLASS_EX_END, ++ WARRIOR_DEFENSIVE_STANCE, ++ WARRIOR_BERSERKER_STANCE, ++ DEATH_KNIGHT_BLOOD_PRESENCE, ++ DEATH_KNIGHT_FROST_PRESENCE, ++ DEATH_KNIGHT_UNHOLY_PRESENCE, ++ DRUID_BEAR_FORM, ++ DRUID_CAT_FORM, ++ //DRUID_TRAVEL_FORM, //NYI ++ //DRUID_FLY_FORM, //NYI ++ DRUID_MOONKIN_FORM //NYI ++}; ++ ++enum BotRoles ++{ ++ BOT_ROLE_NONE = 0x00, ++ BOT_ROLE_TANK = 0x01, ++ BOT_ROLE_DPS = 0x02, ++ BOT_ROLE_HEAL = 0x04, ++ BOT_ROLE_RANGED = 0x08, ++ ++ BOT_ROLE_PARTY = 0x10, //hidden ++ ++ //BOT_ROLE_TANK_MELEE = (BOT_ROLE_TANK | BOT_ROLE_DPS), ++ //BOT_ROLE_TANK_RANGED = (BOT_ROLE_TANK | BOT_ROLE_DPS | BOT_ROLE_RANGED), ++ ++ BOT_MAX_ROLE = 0x20 ++}; ++ ++enum BotPetTypes ++{ ++ PET_TYPE_NONE, ++//Warlock ++ PET_TYPE_IMP, ++ PET_TYPE_VOIDWALKER, ++ PET_TYPE_SUCCUBUS, ++ PET_TYPE_FELHUNTER, ++ PET_TYPE_FELGUARD, ++//Mage ++ PET_TYPE_WATER_ELEMENTAL, ++//Shaman ++ //PET_TYPE_GHOSTLY_WOLF, ++ PET_TYPE_FIRE_ELEMENTAL, ++ PET_TYPE_EARTH_ELEMENTAL, ++//Hunter ++ PET_TYPE_VULTURE, ++ ++ MAX_PET_TYPES ++}; ++ ++enum WarlockBotPets ++{ ++ //PET_IMP = , ++ PET_VOIDWALKER = 70247 ++ //PET_SUCCUBUS = ++}; ++ ++enum HunterBotPets ++{ ++ PET_VULTURE = 70248 ++}; ++ ++enum BotPetsOriginalEntries ++{ ++ ORIGINAL_ENTRY_VOIDWALKER = 1860 ++}; ++ ++enum BotEquipSlot ++{ ++ BOT_SLOT_NONE = 0, ++ BOT_SLOT_MAINHAND = 1, ++ BOT_SLOT_OFFHAND = 2, ++ BOT_SLOT_RANGED = 3, ++ BOT_SLOT_HEAD = 4, ++ BOT_SLOT_SHOULDERS = 5, ++ BOT_SLOT_CHEST = 6, ++ BOT_SLOT_WAIST = 7, ++ BOT_SLOT_LEGS = 8, ++ BOT_SLOT_FEET = 9, ++ BOT_SLOT_WRIST = 10, ++ BOT_SLOT_HANDS = 11, ++ BOT_SLOT_BACK = 12, ++ BOT_SLOT_BODY = 13, ++ BOT_SLOT_FINGER1 = 14, ++ BOT_SLOT_FINGER2 = 15, ++ BOT_SLOT_TRINKET1 = 16, ++ BOT_SLOT_TRINKET2 = 17, ++ BOT_SLOT_NECK = 18, ++ BOT_MAX_SLOTS, ++ BOT_INVENTORY_SIZE = BOT_MAX_SLOTS - 1 ++}; ++ ++enum BotItemStat ++{ ++ //ItemProtoType.h ++ BOT_ITEM_MOD_MANA = 0, ++ BOT_ITEM_MOD_HEALTH = 1, ++ BOT_ITEM_MOD_AGILITY = 3, ++ BOT_ITEM_MOD_STRENGTH = 4, ++ BOT_ITEM_MOD_INTELLECT = 5, ++ BOT_ITEM_MOD_SPIRIT = 6, ++ BOT_ITEM_MOD_STAMINA = 7, ++ BOT_ITEM_MOD_DEFENSE_SKILL_RATING = 12, ++ BOT_ITEM_MOD_DODGE_RATING = 13, ++ BOT_ITEM_MOD_PARRY_RATING = 14, ++ BOT_ITEM_MOD_BLOCK_RATING = 15, ++ BOT_ITEM_MOD_HIT_MELEE_RATING = 16, ++ BOT_ITEM_MOD_HIT_RANGED_RATING = 17, ++ BOT_ITEM_MOD_HIT_SPELL_RATING = 18, ++ BOT_ITEM_MOD_CRIT_MELEE_RATING = 19, ++ BOT_ITEM_MOD_CRIT_RANGED_RATING = 20, ++ BOT_ITEM_MOD_CRIT_SPELL_RATING = 21, ++ BOT_ITEM_MOD_HIT_TAKEN_MELEE_RATING = 22, ++ BOT_ITEM_MOD_HIT_TAKEN_RANGED_RATING = 23, ++ BOT_ITEM_MOD_HIT_TAKEN_SPELL_RATING = 24, ++ BOT_ITEM_MOD_CRIT_TAKEN_MELEE_RATING = 25, ++ BOT_ITEM_MOD_CRIT_TAKEN_RANGED_RATING = 26, ++ BOT_ITEM_MOD_CRIT_TAKEN_SPELL_RATING = 27, ++ BOT_ITEM_MOD_HASTE_MELEE_RATING = 28, ++ BOT_ITEM_MOD_HASTE_RANGED_RATING = 29, ++ BOT_ITEM_MOD_HASTE_SPELL_RATING = 30, ++ BOT_ITEM_MOD_HIT_RATING = 31, ++ BOT_ITEM_MOD_CRIT_RATING = 32, ++ BOT_ITEM_MOD_HIT_TAKEN_RATING = 33, ++ BOT_ITEM_MOD_CRIT_TAKEN_RATING = 34, ++ BOT_ITEM_MOD_RESILIENCE_RATING = 35, ++ BOT_ITEM_MOD_HASTE_RATING = 36, ++ BOT_ITEM_MOD_EXPERTISE_RATING = 37, ++ BOT_ITEM_MOD_ATTACK_POWER = 38, ++ BOT_ITEM_MOD_RANGED_ATTACK_POWER = 39, ++ BOT_ITEM_MOD_FERAL_ATTACK_POWER = 40, ++ BOT_ITEM_MOD_SPELL_HEALING_DONE = 41, // deprecated ++ BOT_ITEM_MOD_SPELL_DAMAGE_DONE = 42, // deprecated ++ BOT_ITEM_MOD_MANA_REGENERATION = 43, ++ BOT_ITEM_MOD_ARMOR_PENETRATION_RATING = 44, ++ BOT_ITEM_MOD_SPELL_POWER = 45, ++ BOT_ITEM_MOD_HEALTH_REGEN = 46, ++ BOT_ITEM_MOD_SPELL_PENETRATION = 47, ++ BOT_ITEM_MOD_BLOCK_VALUE = 48, ++ //END ItemProtoType.h ++ ++ BOT_ITEM_MOD_DAMAGE = MAX_ITEM_MOD, ++ BOT_ITEM_MOD_ARMOR, ++ BOT_ITEM_MOD_RESIST_HOLY, ++ BOT_ITEM_MOD_RESIST_FIRE, ++ BOT_ITEM_MOD_RESIST_NATURE, ++ BOT_ITEM_MOD_RESIST_FROST, ++ BOT_ITEM_MOD_RESIST_SHADOW, ++ BOT_ITEM_MOD_RESIST_ARCANE, ++ BOT_ITEM_MOD_EX, ++ MAX_BOT_ITEM_MOD, ++ ++ BOT_ITEM_MOD_RESISTANCE_START = BOT_ITEM_MOD_ARMOR ++}; ++ ++enum BotAIResetType ++{ ++ BOTAI_RESET_INIT = 0x01, ++ BOTAI_RESET_DISMISS = 0x02, ++ BOTAI_RESET_LOST = 0x04, ++ BOTAI_RESET_LOGOUT = 0x08, //NYI ++ ++ BOTAI_RESET_ABANDON_MASTER = (BOTAI_RESET_INIT | BOTAI_RESET_DISMISS) ++}; ++ ++class bot_ai : public ScriptedAI ++{ ++ friend class BotMgr; ++ friend class script_bot_commands; ++ private: ++ void SetBotOwnerGUID(uint32 guidlow) { _ownerGuid = guidlow; } ++ public: ++ virtual ~bot_ai(); ++ bot_ai(Creature* creature); ++ //void OnCharmed(bool /*apply*/) { } ++ EventProcessor* GetEvents() { return &events; } ++ uint32 GetBotOwnerGuid() const { return _ownerGuid; } ++ Player* GetBotOwner() const { return master; } ++ bool SetBotOwner(Player* newowner); ++ uint8 GetBotClass() const { return _botclass; } ++ uint32 GetLastDiff() const { return lastdiff; } ++ virtual void Reset() {} ++ virtual void EnterEvadeMode() {} ++ virtual void JustDied(Unit*) {} ++ virtual void EnterCombat(Unit*) {} ++ virtual void MoveInLineOfSight(Unit*) {} ++ virtual void ReturnHome() {} ++ virtual void CommonTimers(uint32 /*diff*/) = 0; ++ void ResetBotAI(uint8 resetType = BOTAI_RESET_INIT); ++ void KillEvents(bool force); ++ void FindMaster(bool force = false); ++ virtual bool IsMinionAI() const = 0; ++ virtual bool IsPetAI() const = 0; ++ virtual bool CanRespawn() = 0; ++ virtual void SetBotCommandState(CommandStates /*st*/, bool /*force*/ = false, Position* /*newpos*/ = NULL) = 0; ++ virtual const bot_minion_ai* GetMinionAI() const { return NULL; } ++ virtual const bot_pet_ai* GetPetAI() const { return NULL; } ++ bot_minion_ai const* ToMinionAI() const { return IsMinionAI() ? GetMinionAI() : NULL; } ++ bot_minion_ai* ToMinionAI() { return IsMinionAI() ? const_cast(GetMinionAI()) : NULL; } ++ bot_pet_ai const* ToPetAI() const { return IsPetAI() ? GetPetAI() : NULL; } ++ bot_pet_ai* ToPetAI() { return IsPetAI() ? const_cast(GetPetAI()) : NULL; } ++ bool IsInBotParty(Unit const* unit) const; ++ bool CanBotAttack(Unit const* target, int8 byspell = 0) const; ++ bool InDuel(Unit const* target) const; ++ CommandStates GetBotCommandState() const { return m_botCommandState; } ++ void ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const; ++ void ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const; ++ void ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const; ++ void ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const; ++ void ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const; ++ void ApplyBotSpellCostMods(SpellInfo const* spellInfo, int32& cost) const; ++ void ApplyBotSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const; ++ void ApplyBotSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const; ++ void ApplyBotSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const; ++ void ApplyBotSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const; ++ //inline void SendPartyEvadeAbort() const; ++ inline void SetNeedParty(bool need) { needparty = need; } ++ inline void SetShouldUpdateStats() { shouldUpdateStats = true; } ++ inline void UpdateHealth() { doHealth = true; } ++ inline void UpdateMana() { doMana = true; } ++ inline uint32 GetManaRegen() const { return regen_mp; } ++ inline float GetHitRating() const { return hit; } ++ inline int32 GetHaste() const { return haste; } ++ inline float GetShieldBlockValue() const { return blockvalue; } ++ virtual uint8 GetBotStance() const { return BOT_STANCE_NONE; } ++ inline uint8 GetBotRoles() const { return _roleMask; } ++ inline bool HasRole(uint8 role) const { return _roleMask & role; } ++ void ToggleRole(uint8 role, bool force); ++ char const* GetRoleString(uint8 role) const; ++ ++ virtual void OnBotSummon(Creature* /*summon*/) {} ++ virtual void OnBotDespawn(Creature* /*summon*/) {} ++ virtual void UnsummonAll() {} ++ ++ void ReceiveEmote(Player* player, uint32 emote); ++ void ApplyPassives() const; ++ ++ virtual void RemoveItemBonuses(uint8 /*slot*/) {} ++ virtual void ApplyItemBonuses(uint8 /*slot*/) {} ++ ++ static inline bool CCed(Unit* target, bool root = false) ++ { ++ return target ? target->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE) || (root && (target->HasUnitState(UNIT_STATE_ROOT) || target->isFrozen() || target->isInRoots())) : true; ++ } ++ ++ //virtual bool CanUseOffHand() const { return false; } ++ //virtual bool CanUseRanged() const { return false; } ++ //virtual bool CanEquip(ItemTemplate const* /*item*/, uint8 /*slot*/) const { return false; } ++ //virtual bool Unequip(uint8 /*slot*/) { return false; } ++ //virtual bool Equip(uint8 /*slot*/, Item* /*item*/) { return false; } ++ //virtual bool ResetEquipment(uint8 /*slot*/) { return false; } ++ ++ static void BotSpeak(std::string const& text, uint8 msgtype, uint32 language, ObjectGuid sender, ObjectGuid receiver); ++ ++ virtual void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType); ++ virtual bool IAmFree() const { return false; } ++ ++ virtual void SetStats(bool, bool = false) = 0; ++ void DefaultInit(); ++ ++ void GetHomePosition(uint16& mapid, Position* pos); ++ ++ virtual bool UpdateImpossibleChase(Unit* /*target*/) { return false; } ++ virtual bool IsDuringTeleport() const { return false; } ++ virtual void AbortTeleport() {} ++ virtual void ResetChase(Position* /*pos*/) {} ++ ++ virtual uint8 GetPlayerClass() const { ASSERT(_botclass < BOT_CLASS_EX_START); return _botclass; } ++ ++ virtual float GetBotParryChance() const { return CanParry() ? parry : 0.0f; } ++ virtual bool CanParry() const = 0; ++ virtual float GetBotDodgeChance() const { return CanDodge() ? dodge : 0.0f; } ++ virtual bool CanDodge() const = 0; ++ virtual float GetBotBlockChance() const { return CanBlock() ? block : 0.0f; } ++ virtual bool CanBlock() const = 0; ++ virtual float GetBotCritChance() const { return CanCrit() ? crit : 0.0f; } ++ virtual bool CanCrit() const = 0; ++ virtual float GetBotMissChance() const { return -hit; } ++ virtual bool CanMiss() const = 0; ++ virtual float GetBotEvasion() const { return 0.0f; } ++ virtual float GetBotArmorPenetrationCoef() const { return 0.0f; } ++ virtual float GetSpellMiscValue(uint32 /*basespell*/, uint8 /*offset*/ = 0) const { return 0.0f; } ++ virtual float GetBotDamageTakenMod() const { return dmg_taken; } ++ virtual uint32 GetBotExpertise() const { return expertise; } ++ virtual uint32 GetBotSpellPenetration() const { return spellpen; } ++ virtual uint32 GetBotSpellPower() const { return spellpower; } ++ ++ virtual bool IsBotImmuneToSpell(SpellInfo const* spellInfo) const; ++ ++ MeleeHitOutcome BotRollCustomMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const; ++ ++ virtual void CastBotItemCombatSpell(Unit* /*target*/, WeaponAttackType /*attType*/, uint32 /*procVictim*/, uint32 /*procEx*/, Spell const* /*spell = NULL*/) {} ++ virtual void OnBotSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs); ++ virtual void OnBotSpellGo(Spell const* spell); ++ virtual void OnClassSpellGo(SpellInfo const* /*spellInfo*/) {} ++ ++ static void InitBotCustomSpells(); ++ static bool IsBotCustomSpell(uint32 spellId); ++ ++ bool IsTempBot() const { return _temp; } ++ void SetBotIsTemp() { _temp = true; } ++ ++ void StartBoot() { _bootTimer = 60000; } ++ void CancelBoot() { _bootTimer = -1; } ++ ++ bool IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD = true, uint32 forcedTime = 0) const; ++ void SetSpellCooldown(uint32 basespell, uint32 msCooldown); ++ void SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown); ++ virtual void InitFaction() {} ++ protected: ++ static uint32 InitSpell(Unit const* caster, uint32 spell); ++ void InitSpellMap(uint32 basespell, bool forceadd = false, bool forwardRank = true); ++ uint32 GetSpell(uint32 basespell) const; ++ uint32 GetSpellCooldown(uint32 basespell) const; ++ void ResetSpellCooldown(uint32 basespell) { SetSpellCooldown(basespell, 0); } ++ void RemoveSpell(uint32 basespell); ++ void SpellTimers(uint32 diff); ++ ++ virtual void InitRoles() = 0; ++ bool IsTank(Unit* unit = NULL) const; ++ ++ bool HasAuraName(Unit* unit, uint32 spellId, ObjectGuid casterGuid = ObjectGuid::Empty, bool exclude = false) const; ++ bool RefreshAura(uint32 spellId, int8 count = 1) const; ++ bool CheckAttackTarget(uint8 botOrPetType); ++ bool MoveBehind(Unit &target) const; ++ bool CheckImmunities(uint32 spell, Unit* target = NULL) const { return (spell && target && !target->ToCorpse() && target->IsHostileTo(me) ? !target->IsImmunedToDamage(sSpellMgr->GetSpellInfo(spell)) : true); } ++ ++ //everything cast-related ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false, ObjectGuid originalCaster = ObjectGuid::Empty); ++ SpellCastResult CheckBotCast(Unit* victim, uint32 spellId, uint8 botclass) const; ++ virtual void removeFeralForm(bool /*force*/ = false, bool /*init*/ = true, uint32 /*diff*/ = 0) {} ++ ++ //inline bool JumpingFlyingOrFalling() const { return me->IsFalling() || me->HasUnitMovementFlag(MOVEMENTFLAG_PITCH_UP|MOVEMENTFLAG_PITCH_DOWN|MOVEMENTFLAG_SPLINE_ELEVATION|MOVEMENTFLAG_FALLING_SLOW); } ++ inline bool JumpingFlyingOrFalling() const { return me->HasUnitMovementFlag(MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN | MOVEMENTFLAG_SPLINE_ELEVATION | MOVEMENTFLAG_FALLING_SLOW | MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR | MOVEMENTFLAG_DISABLE_GRAVITY); } ++ inline bool Feasting() const { return (me->HasAura(EAT) || me->HasAura(DRINK)); } ++ inline bool IsMeleeClass(uint8 m_class) const { return (m_class == CLASS_WARRIOR || m_class == CLASS_ROGUE || m_class == CLASS_PALADIN || m_class == CLASS_DEATH_KNIGHT || m_class == BOT_CLASS_BM); } ++ inline bool IsTankingClass(uint8 m_class) const { return (m_class == CLASS_WARRIOR || m_class == CLASS_PALADIN || m_class == CLASS_DEATH_KNIGHT); } ++ inline bool IsChanneling(Unit* u = NULL) const { if (!u) u = me; return u->GetCurrentSpell(CURRENT_CHANNELED_SPELL); } ++ inline bool IsCasting(Unit* u = NULL) const { if (!u) u = me; return (u->HasUnitState(UNIT_STATE_CASTING) || IsChanneling(u) || u->IsNonMeleeSpellCast(false, false, true)); } ++ ++ void GetInPosition(bool force = false, Unit* newtarget = NULL, Position* pos = NULL); ++ void OnSpellHit(Unit* caster, SpellInfo const* spell); ++ void CalculateAttackPos(Unit* target, Position &pos) const; ++ virtual void CheckAttackState(); ++ virtual void Evade(bool = false) {} ++ virtual void OnStartAttack(Unit* /*u*/) {} ++ ++ virtual void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} ++ virtual void ApplyClassDamageMultiplierMelee(int32& /*damage*/, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool& /*crit*/) const {} ++ virtual void ApplyClassDamageMultiplierSpell(int32& /*damage*/, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool& /*crit*/) const {} ++ virtual void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& /*heal*/, SpellInfo const* /*spellInfo*/, DamageEffectType /*damagetype*/, uint32 /*stack*/) const {} ++ virtual void ApplyClassCritMultiplierHeal(Unit const* /*victim*/, float& /*crit_chance*/, SpellInfo const* /*spellInfo*/, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const {} ++ virtual void ApplyClassSpellCostMods(SpellInfo const* /*spellInfo*/, int32& /*cost*/) const {} ++ virtual void ApplyClassSpellCastTimeMods(SpellInfo const* /*spellInfo*/, int32& /*casttime*/) const {} ++ virtual void ApplyClassSpellCooldownMods(SpellInfo const* /*spellInfo*/, uint32& /*cooldown*/) const {} ++ virtual void ApplyClassSpellCategoryCooldownMods(SpellInfo const* /*spellInfo*/, uint32& /*cooldown*/) const {} ++ virtual void ApplyClassSpellGlobalCooldownMods(SpellInfo const* /*spellInfo*/, float& /*cooldown*/) const {} ++ virtual void CureGroup(Player* /*pTarget*/, uint32 /*cureSpell*/, uint32 /*diff*/) {} ++ virtual void CheckAuras(bool /*force*/ = false) {} ++ virtual void BuffAndHealGroup(Player* /*gPlayer*/, uint32 /*diff*/) {} ++ virtual void RezGroup(uint32 /*REZZ*/, Player* /*gPlayer*/) {} ++ //virtual void DoNonCombatActions(uint32 /*diff*/) {} ++ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {} ++ virtual void InitSpells() {} ++ virtual void InitPowers() {} ++ virtual void InitEquips() {} ++ virtual void InitOwner() {} ++ virtual void SavePosition() {} ++ virtual void ApplyClassPassives() = 0; ++ virtual void _OnHealthUpdate() const = 0; ++ virtual void _OnManaUpdate(bool /*shapeshift*/ = false) = 0; ++ //virtual void _OnMeleeDamageUpdate(uint8 /*myclass*/) const = 0; ++ virtual void Regenerate() = 0; ++ ++ //virtual void ReceiveEmote(Player* /*player*/, uint32 /*emote*/) {} ++ //virtual void CommonTimers(uint32 diff) = 0; ++ ++ virtual bool HealTarget(Unit* /*target*/, uint8 /*pct*/, uint32 /*diff*/) { return false; } ++ virtual bool BuffTarget(Unit* /*target*/, uint32 /*diff*/) { return false; } ++ virtual bool CureTarget(Unit* /*target*/, uint32 /*cureSpell*/, uint32 /*diff*/) { return false; } ++ virtual bool IsMelee() const { return HasRole(BOT_ROLE_DPS) && !HasRole(BOT_ROLE_RANGED); } ++ virtual bool CanHeal() const { return false; } ++ virtual bool CanSheath() const { return true; } ++ virtual bool CanSit() const { return true; } ++ virtual bool CanDrink() const { return true; } ++ virtual bool CanEat() const { return true; } ++ virtual bool CanMount() const { return true; } ++ virtual bool CanChangeEquip(uint8 /*slot*/) const { return true; } ++ virtual bool IgnoreEquipsAttackTime() const { return false; } ++ virtual bool CanSeeEveryone() const { return false; } ++ ++ uint8 GetWait(); ++ inline float InitAttackRange(float origRange, bool ranged) const; ++ uint16 Rand() const; ++ static inline uint32 GetLostHP(Unit const* unit) { return unit->GetMaxHealth() - unit->GetHealth(); } ++ static inline uint8 GetHealthPCT(Unit const* hTarget) { if (!hTarget || hTarget->isDead()) return 100; return (hTarget->GetHealth()*100/hTarget->GetMaxHealth()); } ++ static inline uint8 GetManaPCT(Unit const* hTarget) { if (!hTarget || hTarget->isDead() || hTarget->GetMaxPower(POWER_MANA) <= 1) return 100; return (hTarget->GetPower(POWER_MANA)*100/(hTarget->GetMaxPower(POWER_MANA))); } ++ ++ typedef std::set AttackerSet; ++ ++ virtual MeleeHitOutcome GetNextAttackMeleeOutCome() const { return MELEE_HIT_CRUSHING; } ++ ++ //event helpers ++ void BotJumpInPlaceInFrontOf(Position* pos, float speedXY, float maxHeight); ++ ++ //utilities ++ void _AddItemTemplateLink(Player* forPlayer, ItemTemplate const* item, std::ostringstream &str) const; ++ void _AddItemLink(Player* forPlayer, Item const* item, std::ostringstream &str) const; ++ void _AddQuestLink(Player* forPlayer, Quest const* quest, std::ostringstream &str) const; ++ void _AddWeaponSkillLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillid) const; ++ void _AddSpellLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, bool color = true, const std::string& colorstr = "ffffffff") const; ++ void _AddProfessionLink(Player* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillId) const; ++ void _LocalizeItem(Player* forPlayer, std::string &itemName, uint32 entry) const; ++ void _LocalizeItem(Player* forPlayer, std::string &itemName, std::string &suffix, Item const* item) const; ++ void _LocalizeQuest(Player* forPlayer, std::string &questTitle, uint32 entry) const; ++ void _LocalizeCreature(Player* forPlayer, std::string &creatureName, uint32 entry) const; ++ void _LocalizeGameObject(Player* forPlayer, std::string &gameobjectName, uint32 entry) const; ++ ++ void BuildGrouUpdatePacket(WorldPacket* data); ++ ++ void BotSay(char const* text, Player const* target = NULL) const; ++ void BotWhisper(char const* text, Player* target = NULL) const; ++ void BotYell(char const* text, Player const* target = NULL) const; ++ ++ typedef std::pair BotSpell; ++ typedef std::unordered_map BotSpellMap; ++ ++ BotSpellMap const& GetSpellMap() const { return spells; } ++ ++ Player* master; ++ Unit* opponent; ++ CommandStates m_botCommandState; ++ SpellInfo const* m_botSpellInfo; ++ Position pos, attackpos; ++ float atpower, maxdist, ap_mod, spp_mod, crit_mod; ++ ObjectGuid aftercastTargetGuid; ++ int32 cost, value, sppower; ++ uint32 GC_Timer, temptimer, checkAurasTimer, checkMasterTimer, roleTimer, wait, regenTimer_hp, regenTimer_mp, ++ currentSpell; ++ uint8 clear_cd; ++ bool doHealth, doMana, shouldUpdateStats; ++ ++ //stats ++ float hit, parry, dodge, block, blockvalue, crit, dmg_taken; ++ uint32 expertise, spellpower, spellpen, regen_hp, regen_mp; ++ int32 haste; ++ uint32 lastdiff; ++ ++ uint8 _botclass; ++ uint8 _roleMask; ++ uint32 _ownerGuid; ++ bool needparty; ++ bool spawned; ++ bool firstspawn; ++ bool _evadeMode; ++ bool _atHome; ++ ++ bool _temp; ++ ++ int32 _bootTimer; ++ uint32 _updateTimerMedium; ++ ++ EventProcessor events; ++ ++ //VisibilityUpdateEvent* visUpEvent; ++ TeleportHomeEvent* teleHomeEvent; ++ //EvadeEvent* evadeEvent; ++ TeleportFinishEvent* teleFinishEvent; ++ ++ private: ++ Unit* _getTarget(bool byspell, bool ranged, bool &reset) const; ++ bool _hasAuraName(Unit* unit, const std::string spell, ObjectGuid casterGuid = ObjectGuid::Empty, bool exclude = false) const; ++ void _listAuras(Player* player, Unit* unit) const; ++ static inline float _getAttackDistance(float distance) { return distance > 0.0f ? distance*0.72f : 0.0f; } ++ ++ BotSpellMap spells; ++}; ++ ++class bot_minion_ai : public bot_ai ++{ ++ public: ++ virtual ~bot_minion_ai(); ++ bot_minion_ai(Creature* creature); ++ virtual void Reset() {} ++ virtual void EnterEvadeMode() { EnterEvadeMode(false); } ++ void EnterEvadeMode(bool force); ++ virtual void JustDied(Unit*); ++ virtual void EnterCombat(Unit* u); ++ virtual void MoveInLineOfSight(Unit* u); ++ virtual void ReturnHome() { _atHome = false; } ++ void CommonTimers(uint32 diff); ++ const bot_minion_ai* GetMinionAI() const { return this; } ++ bool IsMinionAI() const { return true; } ++ bool IsPetAI() const { return false; } ++ bool CanRespawn() { return IAmFree(); } ++ void SummonBotsPet(uint32 entry); ++ void SetBotCommandState(CommandStates st, bool force = false, Position* newpos = NULL); ++ //virtual bool HealTarget(Unit* /*target*/, uint8 /*pct*/, uint32 /*diff*/) { return false; } ++ //virtual bool BuffTarget(Unit* /*target*/, uint32 /*diff*/) { return false; } ++ //virtual bool doCast(Unit* /*victim*/, uint32 /*spellId*/, bool /*triggered*/ = false) { return false; } ++ void CureGroup(Player* pTarget, uint32 cureSpell, uint32 diff); ++ bool CureTarget(Unit* target, uint32 cureSpell, uint32 diff); ++ void CheckAuras(bool force = false); ++ //virtual void DoNonCombatActions(uint32 /*diff*/) {} ++ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {} ++ void SetStats(bool force, bool shapeshift = false); ++ ++ static bool OnGossipHello(Player* player, Creature* creature, uint32 option); ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action); ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code); ++ ++ void InitSpells() {} ++ void InitPowers() {} ++ void _OnHealthUpdate() const; ++ void _OnManaUpdate(bool shapeshift = false); ++ void _OnMeleeDamageUpdate(uint8 myclass) const; ++ void Regenerate(); ++ ++ void RemoveItemBonuses(uint8 slot); ++ void ApplyItemBonuses(uint8 slot); ++ void RemoveItemEnchantments(Item* item, uint8 slot); ++ void RemoveItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 slot); ++ void ApplyItemEnchantments(Item* item, uint8 slot); ++ void ApplyItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 slot); ++ void ApplyItemEquipSpell(Item* item, bool apply); ++ void ApplyItemsSpells(); ++ ++ void OnOwnerDamagedBy(Unit* attacker); ++ ++ //inline void SetEvadeTimer(uint8 time) { evade_cd = time; } ++ ++ bool CanHeal() const; ++ ++ uint32 GetReviveTimer() const { return _reviveTimer; } ++ void SetReviveTimer(uint32 newtime) { _reviveTimer = newtime; } ++ void UpdateReviveTimer(uint32 diff); ++ ++ bool IAmFree() const; ++ void SavePosition(); ++ void TeleportHome(); ++ bool FinishTeleport(/*uint32 mapId, uint32 instanceId, float x, float y, float z, float o*/); ++ ++ bool IsDuringTeleport() const { return teleFinishEvent || teleHomeEvent; } ++ void SetTeleportFinishEvent(TeleportFinishEvent* tfevent) { ASSERT(!teleFinishEvent); teleFinishEvent = tfevent; } ++ void AbortTeleport(); ++ ++ void ResetChase(Position* pos); ++ void ResetChaseTimer(Position* pos); ++ bool UpdateImpossibleChase(Unit* target); ++ void BotJump(Position* pos); ++ ++ virtual bool CanParry() const { return me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) != 0 && parry_mod > 0.0f && me->CanUseAttackType(BASE_ATTACK); } ++ virtual bool CanDodge() const { return dodge_mod > 0.0f; } ++ virtual bool CanBlock() const { return !(me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) && me->CanUseAttackType(OFF_ATTACK); } ++ virtual bool CanCrit() const { return crit_mod > 0.0f; } ++ virtual bool CanMiss() const { return true; } ++ ++ void CastBotItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Spell const* spell = NULL); ++ void CastBotItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Item* item, ItemTemplate const* proto, Spell const* spell = NULL); ++ ++ float GetTotalBotStat(uint8 stat) const { return _getTotalBotStat(stat); } ++ void InitRoles(); ++ void InitEquips(); ++ void InitOwner(); ++ void InitFaction(); ++ protected: ++ bool GlobalUpdate(uint32 diff); ++ ++ Item const* GetEquips(uint8 slot) const { return _equips[slot]; } ++ ++ virtual bool CanUseManually(uint32 /*basespell*/) const { return false; } ++ void BuffAndHealGroup(Player* gPlayer, uint32 diff); ++ void RezGroup(uint32 REZZ, Player* gPlayer); ++ ++ void Follow(bool force = false, Position* newpos = NULL) ++ { ++ if (force || ++ (me->IsAlive() && (!me->IsInCombat() || !opponent) && m_botCommandState != COMMAND_STAY)) ++ SetBotCommandState(COMMAND_FOLLOW, force, newpos); ++ } ++ ++ //void CheckAttackState(); ++ void Evade(bool force = false); ++ void OnStartAttack(Unit* u); ++ ++ virtual void BreakCC(uint32 diff); ++ ++ ++ ++ WorldObject* GetNearbyRezTarget(float dist = 30) const; ++ Unit* FindHostileDispelTarget(float dist = 30, bool stealable = false) const; ++ Unit* FindAffectedTarget(uint32 spellId, ObjectGuid caster = ObjectGuid::Empty, float dist = DEFAULT_VISIBILITY_DISTANCE, uint8 hostile = 0) const; ++ Unit* FindPolyTarget(float dist = 30, Unit* currTarget = NULL) const; ++ Unit* FindFearTarget(float dist = 30) const; ++ Unit* FindStunTarget(float dist = 20) const; ++ Unit* FindUndeadCCTarget(float dist = 30, uint32 spellId = 0) const; ++ Unit* FindRootTarget(float dist = 30, uint32 spellId = 0) const; ++ Unit* FindCastingTarget(float maxdist = 10, float mindist = 0, bool isFriend = false, uint32 spellId = 0) const; ++ Unit* FindAOETarget(float dist = 30, bool checkbots = false, bool targetfriend = true) const; ++ Unit* FindSplashTarget(float dist = 5, Unit* To = NULL, float splashdist = 4) const; ++ Unit* FindTranquilTarget(float mindist = 5, float maxdist = 35) const; ++ void GetNearbyTargetsList(std::list &targets, float maxdist = 10, float mindist = 0, bool forCC = false) const; ++ void GetNearbyFriendlyTargetsList(GuidList &targets, float maxdist = 30) const; ++ ++ uint32 Potion_cd; ++ ++ private: ++ bool _canCureTarget(Unit* target, uint32 cureSpell, uint32 diff) const; ++ void _getBotDispellableAuraList(Unit* target, Unit* caster, uint32 dispelMask, DispelChargesList& dispelList) const; ++ void _calculatePos(Position& pos); ++ void _updateMountedState(); ++ void _updateStandState() const; ++ void _updateRations(); ++ char const* _getNameForSlot(uint8 slot) const; ++ uint8 _onOffIcon(uint8 role) const; ++ void _updateEquips(uint8 slot, Item* item); ++ ++ bool _canUseOffHand() const; ++ bool _canUseRanged() const; ++ bool _canEquip(ItemTemplate const* item, uint8 slot, bool ignoreItemLevel = false) const; ++ bool _unequip(uint8 slot); ++ bool _equip(uint8 slot, Item* newItem); ++ bool _resetEquipment(uint8 slot); ++ ++ typedef std::unordered_map BotStat; ++ BotStat _stats[BOT_INVENTORY_SIZE]; ++ float _getBotStat(uint8 slot, uint8 stat) const; ++ float _getTotalBotStat(uint8 stat) const; ++ float _getRatingMultiplier(CombatRating cr) const; ++ ++ Item* _equips[BOT_INVENTORY_SIZE]; ++ PlayerClassLevelInfo* _classinfo; ++ float myangle, armor_mod, haste_mod, dodge_mod, parry_mod; ++ uint32 mana_cd, health_cd, pvpTrinket_cd; ++ bool feast_health, feast_mana; ++ uint8 rezz_cd; ++ uint32 _reviveTimer, _saveTimer, _powersTimer, _chaseTimer; ++ uint8 _jumpCount; ++ uint8 _evadeCount; ++ ObjectGuid _lastTargetGuid; //unused ++}; ++ ++class bot_pet_ai : public bot_ai ++{ ++ public: ++ virtual ~bot_pet_ai(); ++ bot_pet_ai(Creature* creature); ++ virtual void Reset() {} ++ const bot_pet_ai* GetPetAI() const { return this; } ++ Creature* GetCreatureOwner() const { return m_creatureOwner; } ++ bool IsMinionAI() const { return false; } ++ bool IsPetAI() const { return true; } ++ bool CanRespawn() { return false; } ++ void CommonTimers(uint32 diff); ++ inline bool IAmDead() const { return (!master || !m_creatureOwner || me->isDead()); } ++ //void SetCreatureOwner(Creature* newowner) { m_creatureOwner = newowner; } ++ void SetBotCommandState(CommandStates st, bool force = false, Position* newpos = NULL); ++ //virtual bool HealTarget(Unit* /*target*/, uint8 /*pct*/, uint32 /*diff*/) { return false; } ++ //virtual bool BuffTarget(Unit* /*target*/, uint32 /*diff*/) { return false; } ++ //void BuffAndHealGroup(Player* /*gPlayer*/, uint32 /*diff*/) {} ++ //void RezGroup(uint32 /*REZZ*/, Player* /*gPlayer*/) {} ++ //virtual bool doCast(Unit* /*victim*/, uint32 /*spellId*/, bool /*triggered*/ = false) { return false; } ++ //void CureGroup(Player* /*pTarget*/, uint32 /*cureSpell*/, uint32 /*diff*/) {} ++ //bool CureTarget(Unit* /*target*/, uint32 /*cureSpell*/, uint32 /*diff*/) { return false; } ++ void CheckAuras(bool force = false); ++ //virtual void DoNonCombatActions(uint32 /*diff*/) {} ++ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {} ++ void SetStats(bool force, bool = false); ++ ++ static uint8 GetPetType(Creature* pet); ++ static uint8 GetPetClass(Creature* pet); ++ static uint32 GetPetOriginalEntry(uint32 entry); ++ ++ //debug ++ //virtual void ListSpells(ChatHandler* /*handler*/) const {} ++ ++ void InitSpells() {} ++ void _OnHealthUpdate() const; ++ void _OnManaUpdate(bool shapeshift = false); ++ //void _OnMeleeDamageUpdate(uint8 /*myclass*/) const {} ++ void Regenerate(); ++ void SetBaseArmor(uint32 armor) { basearmor = armor; } ++ ++ virtual bool CanParry() const { return false; } ++ virtual bool CanDodge() const { return true; } ++ virtual bool CanBlock() const { return false; } ++ virtual bool CanCrit() const { return true; } ++ virtual bool CanMiss() const { return true; } ++ ++ protected: ++ void InitRoles(); ++ ++ Creature* m_creatureOwner; ++ private: ++ uint32 basearmor; ++}; ++ ++#endif +diff --git a/src/server/game/AI/NpcBots/bot_bm_ai.cpp b/src/server/game/AI/NpcBots/bot_bm_ai.cpp +new file mode 100644 +index 0000000..f55639b +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_bm_ai.cpp +@@ -0,0 +1,968 @@ ++#include "bot_ai.h" ++//#include "botmgr.h" ++//#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++//#include "WorldSession.h" ++/* ++Blademaster NpcBot (by Graff onlysuffering@gmail.com) ++Complete - Around 30% ++TODO: MIRROR IMAGE (ILLUSION), BLADESTORM ++*/ ++ ++#define MAX_ILLUSION_POSITIONS 4 ++ ++class blademaster_bot : public CreatureScript ++{ ++public: ++ blademaster_bot() : CreatureScript("blademaster_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new blademaster_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct blademaster_botAI : public bot_minion_ai ++ { ++ private: ++ //DelayedMeleeDamageEvent - Blademaster ++ //deals critical damage, resets attack timer and sends fake log ++ class DelayedMeleeDamageEvent : public BasicEvent ++ { ++ public: ++ DelayedMeleeDamageEvent(ObjectGuid botGuid, ObjectGuid targetGuid, bool windwalk) : ++ _botGuid(botGuid), _targetGuid(targetGuid), _windwalk(windwalk), _dinfo(NULL) { } ++ ~DelayedMeleeDamageEvent() { } ++ ++ void SetDamageInfo(CalcDamageInfo* dinfo) ++ { ++ _dinfo = dinfo; ++ } ++ ++ protected: ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ bool suc = false; ++ /*if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(_botGuid, (Creature*)NULL)) ++ { ++ ASSERT(bot->GetBotClass() == BOT_CLASS_BM); ++ (dynamic_cast(bot->GetBotMinionAI()))->CriticalStrikeFinish(_targetGuid, _dinfo, _windwalk); ++ suc = true; ++ }*/ ++ ++ delete _dinfo; ++ return suc; ++ } ++ ++ private: ++ ObjectGuid _botGuid; ++ ObjectGuid _targetGuid; ++ bool _windwalk; ++ CalcDamageInfo* _dinfo; ++ DelayedMeleeDamageEvent(DelayedMeleeDamageEvent const&); ++ }; ++ ++ class EventTerminateEvent : public BasicEvent ++ { ++ public: ++ EventTerminateEvent(ObjectGuid botGuid) : _botGuid(botGuid) { } ++ ~EventTerminateEvent() { } ++ ++ protected: ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ /*if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(_botGuid, (Creature*)NULL)) ++ { ++ (dynamic_cast(bot->GetBotMinionAI()))->TerminateEvent(); ++ return true; ++ }*/ ++ return false; ++ } ++ ++ private: ++ ObjectGuid _botGuid; ++ EventTerminateEvent(EventTerminateEvent const&); ++ }; ++ ++ class DelayedIllusionSummonEvent : public BasicEvent ++ { ++ public: ++ DelayedIllusionSummonEvent(ObjectGuid botGuid) : _botGuid(botGuid) { } ++ ~DelayedIllusionSummonEvent() { } ++ ++ protected: ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ /*if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(_botGuid, (Creature*)NULL)) ++ { ++ ASSERT(bot->GetBotClass() == BOT_CLASS_BM); ++ (dynamic_cast(bot->GetBotMinionAI()))->MirrorImageFinish(); ++ return true; ++ }*/ ++ ++ return false; ++ } ++ ++ private: ++ ObjectGuid _botGuid; ++ DelayedIllusionSummonEvent(DelayedIllusionSummonEvent const&); ++ }; ++ ++ class DisappearEvent : public BasicEvent ++ { ++ public: ++ DisappearEvent(ObjectGuid botGuid) : _botGuid(botGuid) { } ++ ~DisappearEvent() { } ++ ++ protected: ++ bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) ++ { ++ /*if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(_botGuid, (Creature*)NULL)) ++ { ++ ASSERT(bot->GetBotClass() == BOT_CLASS_BM); ++ (dynamic_cast(bot->GetBotMinionAI()))->MirrorImageMid(); ++ return true; ++ }*/ ++ ++ return false; ++ } ++ ++ private: ++ ObjectGuid _botGuid; ++ DisappearEvent(DisappearEvent const&); ++ }; ++ ++ void _calcIllusionPositions() ++ { ++ float x = me->m_positionX; ++ float y = me->m_positionY; ++ float o = me->m_orientation; ++ ++ //X X ++ // C ++ //X X ++ // ++ //C - caster (Blademaster) ++ //X - new positions (1-3 illusions + blademaster) ++ ++ float dist = 2.f; //not too far - 2 for x and y seems to be way to go ++ for (uint8 i = 0; i != MAX_ILLUSION_POSITIONS; ++i) ++ { ++ _illusPos[i].m_positionX = x + ((i == 0 || i == 1) ? +dist : -dist); // +2+2-2-2 ++ _illusPos[i].m_positionY = y + (!(i & 1) ? +dist : -dist); // +2-2+2-2 ++ _illusPos[i].m_orientation = o; ++ } ++ } ++ ++ public: ++ blademaster_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_BM; ++ //Blademaster cannot be disarmed ++ me->ApplySpellImmune(0, IMMUNITY_MECHANIC, SPELL_AURA_MOD_DISARM, true); ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_BM) != SPELL_CAST_OK) ++ return false; ++ ++ //custom ++ if (_dmdevent) ++ return false; ++ if (IsTempBot()) //Illusion etc. ++ return false; ++ ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && me->GetPower(POWER_MANA) < 125) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ CheckWindWalk(diff); ++ CheckMirrorImage(diff); ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_BM)) ++ return; ++ ++ Attack(diff); ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit* /*u*/) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ void DoNonCombatActions(uint32 /*diff*/) { } ++ uint8 GetPlayerClass() const { return BOT_CLASS_WARRIOR; } ++ ++ bool CanSheath() const { return false; } ++ bool CanSit() const { return false; } ++ bool CanDrink() const { return false; } ++ bool CanEat() const { return Windwalk_Timer == 0 && !illusion_Fade; } ++ bool CanMount() const { return false; } ++ bool CanChangeEquip(uint8 slot) const { return slot > BOT_SLOT_RANGED; } ++ bool IgnoreEquipsAttackTime() const { return true; } ++ bool CanSeeEveryone() const { return Windwalk_Timer > 0; } ++ bool CanParry() const { return false; } ++ bool CanCrit() const { return false; } ++ bool CanDodge() const { return false; } ++ bool CanMiss() const { return false; } ++ ++ void BreakCC(uint32 diff) ++ { ++ if (me->HasAuraWithMechanic((1<GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ //AttackerSet m_attackers = master->getAttackers(); ++ //AttackerSet b_attackers = me->getAttackers(); ++ //float dist = me->GetExactDist(opponent); ++ //float meleedist = me->GetDistance(opponent); ++ ////charge + warbringer ++ //if (IsSpellReady(CHARGE_1, diff, false) && dist > 11 && dist < 25 && me->HasInArc(M_PI, opponent) && ++ // (me->getLevel() >= 50 || ++ // (!me->IsInCombat() && (battleStance || stanceChange(diff, 1))))) ++ //{ ++ // temptimer = GC_Timer; ++ // if (me->getLevel() >= 29) ++ // me->RemoveMovementImpairingAuras(); ++ // if (doCast(opponent, GetSpell(CHARGE_1), me->IsInCombat())) ++ // { ++ // SetSpellCooldown(CHARGE_1, 12000); ++ // GC_Timer = temptimer; ++ // return; ++ // } ++ //} ++ ////OVERPOWER ++ //if (IsSpellReady(OVERPOWER_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 50 && getrage() > 50 && meleedist <= 5 && ++ // (battleStance || stancetimer <= diff)) ++ //{ ++ // if (me->HasAura(TASTE_FOR_BLOOD_BUFF)) ++ // { ++ // if (battleStance || stanceChange(diff, 1)) ++ // { ++ // if (doCast(opponent, GetSpell(OVERPOWER_1))) ++ // { ++ // me->RemoveAura(TASTE_FOR_BLOOD_BUFF); ++ // return; ++ // } ++ // } ++ // } ++ //} ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //////PLACEHOLDER - BLADESTORM ++ ////BLADESTORM ++ //if (IsSpellReady(BLADESTORM_1, diff) && HasRole(BOT_ROLE_DPS) && ++ // getrage() >= 250 && (Rand() < 20 || me->HasAura(RECKLESSNESS_1)) && ++ // (b_attackers.size() > 1 || opponent->GetHealth() > me->GetMaxHealth())) ++ //{ ++ // if (doCast(me, GetSpell(BLADESTORM_1))) ++ // { ++ // SetSpellCooldown(BLADESTORM_1, 60000); ++ // return; ++ // } ++ //} ++ //////PLACEHOLDER - SPELLCAST INTERRUPT POSSIBLE ++ ////PUMMEL ++ //if (IsSpellReady(PUMMEL_1, diff, false) && Rand() < 80 && getrage() > 100 && meleedist <= 5 && ++ // opponent->IsNonMeleeSpellCast(false) && ++ // (berserkerStance || stancetimer <= diff)) ++ //{ ++ // temptimer = GC_Timer; ++ // if ((berserkerStance || stanceChange(diff, 3)) && ++ // doCast(opponent, GetSpell(PUMMEL_1))) ++ // { ++ // SetSpellCooldown(PUMMEL_1, 10000); ++ // GC_Timer = temptimer; ++ // return; ++ // } ++ //} ++ } ++ ++ void DoBMMeleeAttackIfReady() ++ { ++ //Copied from UnitAI::DoMeleeAttackIfReady() with modifications ++ //cannot attack while casting or jumping ++ if (me->HasUnitState(UNIT_STATE_CASTING) || _dmdevent) ++ return; ++ ++ Unit* victim = me->GetVictim(); ++ //Make sure our attack is ready and we aren't currently casting before checking distance ++ if (me->isAttackReady() && me->IsWithinMeleeRange(victim)) ++ { ++ if (!CCed(me, true) && !JumpingFlyingOrFalling()) ++ { ++ //Windwalk strike ++ if (Windwalk_Timer) ++ { ++ CriticalStrike(victim, true); ++ return; ++ } ++ ++ //Critical Strike: 15% to deal x2,x3, etc... damage ++ if (criticalStikeMult >= 2 && !CCed(me, true) && !JumpingFlyingOrFalling() && ++ roll_chance_f(15.f)) ++ { ++ CriticalStrike(victim); ++ return; ++ } ++ } ++ ++ me->AttackerStateUpdate(victim); ++ me->resetAttackTimer(); ++ return; ++ } ++ } ++ ++ void CheckAttackState() ++ { ++ if (me->GetVictim()) ++ { ++ if (HasRole(BOT_ROLE_DPS)) ++ DoBMMeleeAttackIfReady(); ++ } ++ else ++ Evade(); ++ } ++ ++ void CheckWindWalk(uint32 diff) ++ { ++ if (!IsSpellReady(WINDWALK_1, diff) || Windwalk_Timer || illusion_Fade || IsCasting() || ++ Rand() > (10 + 20 * (me->IsInCombat() || master->IsInCombat()))) ++ return; ++ ++ if (!IAmFree() && master->isMoving()) ++ { ++ if (me->GetDistance(master) > 30 && ++ doCast(me, GetSpell(WINDWALK_1))) ++ return; ++ ++ return; ++ } ++ ++ if (!IsTank(me)) ++ { ++ //unit to strike ++ Unit* u = IsMelee() ? me->GetVictim() : NULL; ++ ++ if ((u && u->isMoving() && me->GetDistance(u) > 18 && ++ (u->GetVictim() != me || u->getAttackers().size() > uint8(u->IsControlledByPlayer() ? 0 : 1))) || ++ me->getAttackers().size() > 2) ++ { ++ if (doCast(me, GetSpell(WINDWALK_1))) ++ return; ++ } ++ } ++ } ++ ++ void CheckMirrorImage(uint32 diff) ++ { ++ //only for controlled bot ++ if (IAmFree()) ++ return; ++ if (!IsSpellReady(MIRROR_IMAGE_1, diff) || !me->IsInCombat() || !illusionsCount || illusion_Fade || ++ IsCasting() || Rand() > 20) ++ return; ++ ++ uint8 pct = GetHealthPCT(me); ++ uint8 size = uint8(me->getAttackers().size()); ++ if (!size) ++ return; ++ ++ if (pct > 25 && (size > 3 || pct < (80 + size * 5))) ++ if (doCast(me, GetSpell(MIRROR_IMAGE_1))) ++ return; ++ } ++ ++ void MirrorImageStart() ++ { ++ if (!illusionsCount) ++ return; ++ ++ ASSERT(!illusion_Fade); //prevent double casts ++ illusion_Fade = true; ++ ++ //OKAY ++ ++ //there is a restiction for illusions count - cannot summon more than 3 of them ++ while (int8(_illusionGuids.size()) > (3 - illusionsCount)) ++ { ++ GuidList::iterator itr = _illusionGuids.begin(); ++ if (Creature* illusion = ObjectAccessor::GetCreatureOrPetOrVehicle(*me, *itr)) ++ if (!illusion->IsDuringRemoveFromWorld()) ++ illusion->ToCreature()->GetBotAI()->JustDied(NULL); ++ ++ _illusionGuids.erase(itr); ++ } ++ ++ //mirror image renders BM invulnerable for a short period of time, ++ //removing all auras. We exclude passive or invisible auras for they don't exist in W3 ++ Unit::AuraMap const auras = me->GetOwnedAuras(); //copy ++ for (Unit::AuraMap::const_iterator iter = auras.begin(); iter != auras.end(); ++iter) ++ { ++ Aura* aura = iter->second; ++ if ((aura->GetSpellInfo()->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) || ++ (aura->GetSpellInfo()->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR)) ++ continue; ++ AuraApplication* aurApp = aura->GetApplicationOfTarget(me->GetGUID()); ++ if (!aurApp) ++ continue; ++ me->RemoveAura(aurApp, AURA_REMOVE_BY_DEFAULT); ++ } ++ ++ me->BotStopMovement(); ++ me->AttackStop(); ++ me->HandleEmoteCommand(EMOTE_ONESHOT_NONE); ++ me->AddAura(BLACK_COLOR, me);//color ++ me->AddAura(STUN_FREEZE, me);//stop/immunity ++ ++ //prepare to disappear ++ DisappearEvent* devent = new DisappearEvent(me->GetGUID()); ++ events.AddEvent(devent, events.CalculateTime(300)); //immediatelly (almost) ++ } ++ ++ void MirrorImageMid() ++ { ++ if (!me->IsInWorld() || ++ !me->IsAlive()/* || CCed(me)*/) //this is just ensurance ++ { ++ me->RemoveAura(BLACK_COLOR); ++ me->RemoveAura(STUN_FREEZE); ++ illusion_Fade = false; ++ return; ++ } ++ //disappear ++ me->SetPhaseMask(0, true); ++ ++ //INVISIBLE! ++ //EVENT ++ DelayedIllusionSummonEvent* disevent = new DelayedIllusionSummonEvent(me->GetGUID()); ++ events.AddEvent(disevent, events.CalculateTime(1250)); //1000 ms disappear time + 250 ms buffer ++ } ++ ++ void MirrorImageFinish() ++ { ++ illusion_Fade = false; ++ me->RemoveAura(BLACK_COLOR); ++ me->RemoveAura(STUN_FREEZE); ++ if (!me->IsInWorld() || ++ !me->IsAlive()/* || CCed(me)*/) //this is just ensurance ++ return; ++ ++ _calcIllusionPositions(); ++ ++ std::set usedposs; ++ ++ for (uint8 i = 0; i != illusionsCount; ++i) ++ { ++ Creature* illusion = master->SummonCreature(me->GetEntry(), *me, TEMPSUMMON_TIMED_DESPAWN, 1 * MINUTE * IN_MILLISECONDS); ++ if (!illusion) ++ continue; ++ ++ illusion->GetBotAI()->SetBotIsTemp(); ++ illusion->GetBotAI()->FindMaster(true); ++ illusion->SetMaxHealth(me->GetMaxHealth()); ++ illusion->SetHealth(me->GetHealth()); ++ illusion->SetMaxPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); ++ illusion->SetPower(POWER_MANA, me->GetPower(POWER_MANA)); ++ illusion->SetFloatValue(UNIT_FIELD_MINDAMAGE, me->GetFloatValue(UNIT_FIELD_MINDAMAGE)); ++ illusion->SetFloatValue(UNIT_FIELD_MAXDAMAGE, me->GetFloatValue(UNIT_FIELD_MAXDAMAGE)); ++ ++ illusion->BotStopMovement(); ++ while (true) ++ { ++ uint8 j = urand(0, MAX_ILLUSION_POSITIONS - 1); ++ if (usedposs.find(j) == usedposs.end()) ++ { ++ illusion->GetMotionMaster()->MovePoint(me->GetMapId(), _illusPos[j]); ++ //illusion->Relocate(_illusPos[j]); ++ usedposs.insert(j); ++ break; ++ } ++ } ++ ++ illusion->SetBotCommandState(COMMAND_ABANDON); ++ ++ _illusionGuids.push_back(illusion->GetGUID()); ++ } ++ ++ me->SetBotCommandState(COMMAND_ABANDON); ++ ++ for (uint8 i = 0; i != MAX_ILLUSION_POSITIONS; ++i) ++ { ++ if (usedposs.find(i) == usedposs.end()) ++ { ++ me->BotStopMovement(); ++ me->GetMotionMaster()->MovePoint(me->GetMapId(), _illusPos[i]); ++ //me->Relocate(_illusPos[i]); ++ //usedposs.insert(i); ++ break; ++ } ++ } ++ ++ uint8 counter = 0; ++ uint8 r = urand(0, uint8(_illusionGuids.size() - 1)); ++ ++ for (GuidList::const_iterator itr = _illusionGuids.begin(); itr != _illusionGuids.end(); ++itr) ++ { ++ if (Creature* illusion = ObjectAccessor::GetCreatureOrPetOrVehicle(*me, *itr)) ++ illusion->SetPhaseMask(master->GetPhaseMask(), true); ++ ++ if (counter == r) ++ me->SetPhaseMask(master->GetPhaseMask(), true); ++ else ++ ++counter; ++ } ++ ++ me->getHostileRefManager().deleteReferences(); ++ ++ if (me->GetPhaseMask() != master->GetPhaseMask()) ++ me->SetPhaseMask(master->GetPhaseMask(), true); ++ ++ //me->setAttackTimer(BASE_ATTACK, 3000); ++ wait = 18; ++ SetSpellCooldown(MIRROR_IMAGE_1, 8000); ++ Potion_cd = std::max(Potion_cd, 10000); ++ } ++ ++ void CriticalStrike(Unit* target, bool windwalk = false) ++ { ++ //Okay critical strike must have jump and strike animation, doing delayed damage ++ DelayedMeleeDamageEvent* dmdevent = new DelayedMeleeDamageEvent(me->GetGUID(), target->GetGUID(), windwalk); ++ SetDelayedMeleeDamageEvent(dmdevent); ++ ++ //hack temp attack damage calc ++ float mindam = me->GetFloatValue(UNIT_FIELD_MINDAMAGE); ++ float maxdam = me->GetFloatValue(UNIT_FIELD_MAXDAMAGE); ++ ++ if (windwalk) ++ { ++ me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam * 1.5f); ++ me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam * 1.5f); ++ me->RemoveAura(GetSpell(WINDWALK_1)); ++ me->RemoveAura(TRANSPARENCY); ++ } ++ else ++ { ++ me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam * criticalStikeMult); ++ me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam * criticalStikeMult); ++ } ++ ++ CalcDamageInfo* dinfo = new CalcDamageInfo(); ++ me->CalculateMeleeDamage(target, 0, dinfo, BASE_ATTACK); ++ ++ me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam); ++ me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam); ++ ++ dmdevent->SetDamageInfo(dinfo); ++ events.AddEvent(dmdevent, events.CalculateTime(450)); ++ ++ BotJumpInPlaceInFrontOf(target, 0.32f, 4.1f); //jump - DO NOT CHANGE ++ me->CastSpell(target, SPELL_COMBAT_SPECIAL_2H_ATTACK, true); //strike anim ++ me->resetAttackTimer(BASE_ATTACK); ++ } ++ ++ void CriticalStrikeFinish(ObjectGuid targetGuid, CalcDamageInfo* dinfo, bool /*windwalk*/) ++ { ++ EventTerminateEvent* etevent = new EventTerminateEvent(me->GetGUID()); ++ events.AddEvent(etevent, events.CalculateTime(750)); ++ ++ if (!me->IsInWorld() || !me->IsAlive() || CCed(me)) ++ { ++ Windwalk_Timer = 0; ++ return; ++ } ++ ++ Unit* target = ObjectAccessor::GetUnit(*me, targetGuid); ++ if (!target || !target->IsAlive()) ++ { ++ me->PlayDistanceSound(SOUND_MISS_WHOOSH_2H); ++ Windwalk_Timer = 0; ++ return; ++ } ++ ++ if (target->IsImmunedToDamage(SPELL_SCHOOL_MASK_NORMAL)) ++ { ++ //target became immune ++ me->SendSpellMiss(target, CRITICAL_STRIKE_1, SPELL_MISS_IMMUNE); ++ target->PlayDistanceSound(SOUND_ABSORB_GET_HIT); ++ Windwalk_Timer = 0; ++ return; ++ } ++ else if (!CanSeeEveryone() && !me->CanSeeOrDetect(target, false, false)) ++ { ++ //target disappeared ++ me->SendSpellMiss(target, CRITICAL_STRIKE_1, SPELL_MISS_MISS/*SPELL_MISS_EVADE*/); ++ me->PlayDistanceSound(SOUND_MISS_WHOOSH_2H); ++ Windwalk_Timer = 0; ++ return; ++ } ++ ++ target->PlayDistanceSound(SOUND_AXE_2H_IMPACT_FLESH_CRIT); ++ ++ me->SendSpellNonMeleeDamageLog(target, CRITICAL_STRIKE_1, ++ dinfo->damage + dinfo->absorb + dinfo->resist + dinfo->blocked_amount, ++ SPELL_SCHOOL_MASK_NORMAL, dinfo->absorb, dinfo->resist, true, dinfo->blocked_amount, true); ++ CleanDamage cl(0, 0, BASE_ATTACK, MELEE_HIT_CRIT); ++ me->DealDamage(target, dinfo->damage, &cl); ++ me->ProcDamageAndSpell(dinfo->target, dinfo->procAttacker, dinfo->procVictim, dinfo->procEx, dinfo->damage, dinfo->attackType); ++ me->CombatStart(target); ++ ++ Windwalk_Timer = 0; ++ } ++ ++ void SetDelayedMeleeDamageEvent(DelayedMeleeDamageEvent* dmdevent) ++ { _dmdevent = dmdevent; } ++ ++ void TerminateEvent() ++ { _dmdevent = NULL; } ++ ++ MeleeHitOutcome GetNextAttackMeleeOutCome() const ++ { ++ return _dmdevent ? MELEE_HIT_NORMAL : bot_ai::GetNextAttackMeleeOutCome(); ++ } ++ ++ bool IsBotImmuneToSpell(SpellInfo const* spellInfo) const ++ { ++ //PLACEHOLDER BLADESTORM ++ //return !spellInfo->HasEffect(SPELL_EFFECT_HEAL); ++ return bot_ai::IsBotImmuneToSpell(spellInfo); ++ } ++ ++ float GetBotArmorPenetrationCoef() const ++ { ++ //return _dmdevent ? 0.5f : bot_ai::GetBotArmorPenetrationCoef(); ++ return 0.5f; ++ } ++ ++ float GetSpellMiscValue(uint32 basespell, uint8 offset) const ++ { ++ switch (basespell) ++ { ++ case CRITICAL_STRIKE_1: ++ return offset == 0 ? criticalStikeMult : 0; ++ case MIRROR_IMAGE_1: ++ return offset == 0 ? illusionsCount : 0; ++ default: ++ return 0; ++ } ++ } ++ ++ //void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ //{ ++ // uint32 spellId = spellInfo->Id; ++ // uint8 lvl = me->getLevel(); ++ // float fdamage = float(damage); ++ // //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ // if (!crit) ++ // { ++ // float aftercrit = 0.f; ++ // ////Incite: 15% additional critical chance for Cleave, Heroic Strike and Thunder Clap ++ // //if (lvl >= 15 && spellId == GetSpell(CLEAVE_1) /*|| spellId == HEROICSTRIKE || spellId == THUNDERCLAP*/) ++ // // aftercrit += 15.f; ++ ++ // //second roll (may be illogical) ++ // if (aftercrit > 0.f) ++ // crit = roll_chance_f(aftercrit); ++ // } ++ ++ // //2) apply bonus damage mods ++ // float pctbonus = 0.0f; ++ // if (crit) ++ // { ++ // //!!!Melee spell damage is not yet critical, all reduced by half ++ // //Impale: 20% crit damage bonus for all abilities ++ // if (lvl >= 20) ++ // pctbonus += 0.10f; ++ // } ++ ++ // ////Improved Rend: 20% bonus damage for Rend ++ // //if (spellId == GetSpell(REND_1)) ++ // // pctbonus += 0.2f; ++ ++ // damage = int32(fdamage * (1.0f + pctbonus)); ++ //} ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if (spellId == GetSpell(WINDWALK_1)) ++ { ++ Windwalk_Timer = 30000; //TODO: ++ me->RemoveMovementImpairingAuras(); ++ me->PlayDistanceSound(SOUND_FREEZE_IMPACT_WINDWALK, !IAmFree() ? master : NULL); ++ ++ uint32 dur = 30000; ++ if (Aura* aura = me->GetAura(spellId)) ++ { ++ aura->SetDuration(dur); ++ aura->SetMaxDuration(dur); ++ } ++ if (Aura* aura = me->GetAura(TRANSPARENCY)) ++ { ++ aura->SetDuration(dur); ++ aura->SetMaxDuration(dur); ++ } ++ ++ if (GetHealthPCT(me) < 25 || !HasRole(BOT_ROLE_DPS)) ++ me->AttackStop(); ++ ++ //SpellEffectSanctuary ++ me->getHostileRefManager().UpdateVisibility(); ++ AttackerSet attackers = me->getAttackers(); ++ for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) ++ { ++ if (!(*itr)->CanSeeOrDetect(me)) ++ (*(itr++))->AttackStop(); ++ else ++ ++itr; ++ } ++ ++ me->m_lastSanctuaryTime = getMSTime(); ++ } ++ ++ if (spellId == GetSpell(MIRROR_IMAGE_1)) ++ { ++ MirrorImageStart(); ++ } ++ ++ OnSpellHit(caster, spell); ++ } ++ ++ void SpellHitTarget(Unit* /*target*/, SpellInfo const* /*spell*/) { } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ //Illusions deal no damage ++ if (IsTempBot()) ++ { ++ //manually add threat as if damage was done ++ if (victim->GetTypeId() != TYPEID_PLAYER) ++ victim->AddThreat(me, float(damage * 2)); ++ ++ damage = 0; ++ } ++ ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& damage) ++ { ++ //illusions take twice damage ++ if (IsTempBot()) ++ { ++ damage *= 2; ++ //return; ++ } ++ if (illusion_Fade) ++ { ++ damage = 0; ++ return; ++ } ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OnBotDespawn(Creature* /*summon*/) ++ {} ++ ++ void UnsummonAll() ++ { ++ while (!_illusionGuids.empty()) ++ { ++ GuidList::iterator itr = _illusionGuids.begin(); ++ if (Creature* illusion = ObjectAccessor::GetCreatureOrPetOrVehicle(*me, *itr)) ++ if (illusion->GetBotAI()) ++ illusion->GetBotAI()->JustDied(NULL); ++ ++ _illusionGuids.erase(itr); ++ } ++ } ++ ++ void Reset() ++ { ++ _dmdevent = NULL; ++ Windwalk_Timer = 0; ++ criticalStikeMult = 1; ++ illusionsCount = 0; ++ illusion_Fade = false; ++ ++ me->setPowerType(POWER_MANA); ++ me->SetMaxPower(POWER_MANA, 75); ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (Windwalk_Timer > diff) Windwalk_Timer -= diff; ++ else if (Windwalk_Timer > 0) Windwalk_Timer = 0; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ ++ /*Special*/lvl >= 10 ? InitSpellMap(WINDWALK_1) : RemoveSpell(WINDWALK_1); ++ /*Special*/lvl >= 20 ? InitSpellMap(MIRROR_IMAGE_1) : RemoveSpell(MIRROR_IMAGE_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = me->getLevel(); ++ ++ RefreshAura(SPELL_BURNING_BLADE_BLADEMASTER); ++ ++ criticalStikeMult = ++ level < 10 ? 1 : ++ level < 30 ? 2 : ++ level < 50 ? 3 : ++ level < 82 ? 4 : 5; ++ ++ illusionsCount = ++ level < 20 ? 0 : ++ level < 40 ? 1 : ++ level < 70 ? 2 : 3; ++ //level < 83 ? 3 : 4; ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case WINDWALK_1: ++ case MIRROR_IMAGE_1: ++ return true; ++ default: ++ break; ++ } ++ ++ return false; ++ } ++ ++ private: ++ DelayedMeleeDamageEvent* _dmdevent; ++ GuidList _illusionGuids; ++ Position _illusPos[MAX_ILLUSION_POSITIONS]; ++ ++ uint32 Windwalk_Timer; ++ uint8 criticalStikeMult; ++ uint8 illusionsCount; ++ bool illusion_Fade; ++ ++ enum BlademasterBaseSpells ++ { ++ WINDWALK_1 = SPELL_NETHERWALK, ++ MIRROR_IMAGE_1 = SPELL_MIRROR_IMAGE_BM, ++ CRITICAL_STRIKE_1 = SPELL_CRITICAL_STRIKE ++ }; ++ enum BlademasterPassives ++ { ++ //Talents ++ //other ++ }; ++ enum BlademasterSpecial ++ { ++ TRANSPARENCY = SPELL_TRANSPARENCY_50, ++ BLACK_COLOR = SPELL_VERTEX_COLOR_BLACK, ++ STUN_FREEZE = SPELL_STUN_FREEZE_ANIM ++ }; ++ }; ++}; ++ ++void AddSC_blademaster_bot() ++{ ++ new blademaster_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp b/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp +new file mode 100644 +index 0000000..126e41d +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp +@@ -0,0 +1,1622 @@ ++#include "bot_ai.h" ++//#include "botmgr.h" ++#include "GameEventMgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "Spell.h" ++#include "SpellAuras.h" ++/* ++Death Knight NpcBot by Graff onlysuffering@gmail.com ++Complete - around 55% ++Note: Rune system adapted from TC ++TODO: REMEMBER ALREADY DK HAS MINIMUM LEVEL 55! ++*/ ++const RuneType runeSlotTypes[MAX_RUNES] = ++{ ++ RUNE_BLOOD, ++ RUNE_BLOOD, ++ RUNE_UNHOLY, ++ RUNE_UNHOLY, ++ RUNE_FROST, ++ RUNE_FROST ++}; ++struct BotRuneInfo ++{ ++ uint8 BaseRune; ++ uint8 CurrentRune; ++ uint32 Cooldown; ++ //AuraEffect const* ConvertAura; ++}; ++ ++struct BotRunes ++{ ++ BotRuneInfo runes[MAX_RUNES]; ++ //uint8 runeState; //UNUSED ++ //uint8 lastUsedRune; //UNUSED ++ ++ //void SetRuneState(uint8 index, bool set = true) ++ //{ ++ // if (set) ++ // runeState |= (1 << index); // usable ++ // else ++ // runeState &= ~(1 << index); // on cooldown ++ //} ++}; ++class death_knight_bot : public CreatureScript ++{ ++public: ++ death_knight_bot() : CreatureScript("death_knight_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new death_knight_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct death_knight_botAI : public bot_minion_ai ++ { ++ death_knight_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_DEATH_KNIGHT; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_DEATH_KNIGHT) != SPELL_CAST_OK) ++ return false; ++ ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); ++ int32 runecost[NUM_RUNE_TYPES]; ++ for (uint8 i = 0; i != NUM_RUNE_TYPES; ++i) ++ runecost[i] = 0; ++ ++ if (!triggered) ++ { ++ if (!HaveRunes(spellInfo, runecost)) ++ return false; ++ } ++ ++ bool result = bot_ai::doCast(victim, spellId, triggered); ++ ++ if (result) ++ { ++ //std::ostringstream str; ++ //str << "Casting " << spellInfo->SpellName[0] << " on " << victim->GetName(); ++ //me->Whisper(str.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ //Set cooldown for runes ++ if (!triggered) ++ { ++ SpendRunes(runecost); ++ ////debug ++ //for (uint8 i = 0; i != NUM_RUNE_TYPES; ++i) ++ // if (runecost[i]) ++ // TC_LOG_FATAL("entities.player", "doCast():: DK bot %s has casted spell %u (%s) without %u rune(s) (type %u)!", ++ // me->GetName().c_str(), spellId, spellInfo->SpellName[0], runecost[i], i); ++ } ++ //runic power gain: all dk spells are instant but some have no unit target so ++ //we gain runic power here instead of SpellHitTarget() ++ if (SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(spellInfo->RuneCostID)) ++ if (int32 rp = int32(src->runePowerGain * runicpowerIncomeMult)) ++ me->ModifyPower(POWER_RUNIC_POWER, int32(rp)); ++ } ++ ++ return result; ++ } ++ ++ bool HaveRunes(SpellInfo const* spellInfo, int32 *runecost) const ++ { ++ if (spellInfo->PowerType != POWER_RUNE || !spellInfo->RuneCostID) ++ return true; ++ ++ CalcRuneCost(spellInfo, runecost); ++ ++ if (runecost[RUNE_DEATH] != 0 && runecost[RUNE_DEATH] > GetDeathRunesCount()) ++ return false; ++ ++ return true; ++ } ++ ++ void CalcRuneCost(SpellInfo const* spellInfo, int32 *runecost) const ++ { ++ SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(spellInfo->RuneCostID); ++ if (!src) ++ return; ++ ++ if (src->NoRuneCost()) ++ return; ++ ++ for (uint8 i = 0; i != RUNE_DEATH; ++i) ++ runecost[i] = src->RuneCost[i]; ++ ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ uint8 rune = _runes.runes[i].BaseRune; ++ if (_runes.runes[i].CurrentRune == rune && _runes.runes[i].Cooldown == 0 && runecost[rune] > 0) ++ runecost[rune]--; ++ } ++ ++ for (uint8 i = 0; i != RUNE_DEATH; ++i) ++ if (runecost[i] > 0) ++ runecost[RUNE_DEATH] += runecost[i]; ++ ++ ////restore cost to allow cooldown set ++ //for (uint8 i = 0; i != RUNE_DEATH; ++i) ++ // runecost[i] = src->RuneCost[i]; ++ } ++ ++ int32 GetDeathRunesCount() const ++ { ++ int32 count = 0; ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ if (_runes.runes[i].CurrentRune == RUNE_DEATH && _runes.runes[i].Cooldown == 0) ++ ++count; ++ ++ return count; ++ } ++ ++ uint8 GetCooledRunesCount(uint8 runetype) const ++ { ++ uint8 count = 0; ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ if (_runes.runes[i].BaseRune == runetype && _runes.runes[i].Cooldown > 0) ++ ++count; ++ ++ return count; ++ } ++ ++ void SpendRunes(int32* runecost) ++ { ++ for (uint8 i = 0; i != NUM_RUNE_TYPES; ++i) ++ { ++ if (runecost[i] <= 0) ++ continue; ++ ++ for (uint8 j = 0; j != MAX_RUNES && runecost[i] > 0; ++j) ++ { ++ if (SpendRune(i)) ++ runecost[i]--; ++ } ++ } ++ ++ if (GetCooledRunesCount(RUNE_BLOOD) > 1) ++ { ++ me->CastSpell(me, BLADE_BARRIER_AURA, true); ++ } ++ } ++ ++ bool SpendRune(uint8 runetype) ++ { ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ if (_runes.runes[i].CurrentRune == runetype && _runes.runes[i].Cooldown == 0) ++ { ++ _runes.runes[i].CurrentRune = _runes.runes[i].BaseRune; ++ //_runes.lastUsedRune = _runes.runes[i].CurrentRune; //UNUSED ++ //_runes.SetRuneState(i, false); //UNUSED ++ //DK receives rune regen bonus from mana regen ++ uint32 cooldown = RUNE_BASE_COOLDOWN - std::min(uint32(GetManaRegen() * 10), RUNE_BASE_COOLDOWN); ++ _runes.runes[i].Cooldown = cooldown; ++ //std::ostringstream str; ++ //str << "Spent rune " << uint32(i) << " (type: " << uint32(runetype) << ')'; ++ //me->Whisper(str.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ bool HaveRune(uint8 runetype) const ++ { ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ if ((_runes.runes[i].CurrentRune == runetype || _runes.runes[i].CurrentRune == RUNE_DEATH) && ++ _runes.runes[i].Cooldown == 0) ++ { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ void ConvertRune(uint8 runetype, uint8 count) ++ { ++ if (runetype == RUNE_DEATH) ++ return; ++ ++ uint8 failcount = 0; ++ for (uint8 i = 0; i != MAX_RUNES && count > 0; ++i) ++ { ++ if (_runes.runes[i].BaseRune == runetype) ++ { ++ if (_runes.runes[i].CurrentRune == RUNE_DEATH) ++ { ++ ++failcount; ++ continue; ++ } ++ ++ if (_runes.runes[i].Cooldown > 3000) ++ _runes.runes[i].Cooldown -= 3000; ++ ++ _runes.runes[i].CurrentRune = RUNE_DEATH; ++ --count; ++ } ++ } ++ ++ if (!count && !failcount) ++ return; ++ ++ //std::ostringstream str; ++ //str << "Failed to convert rune of type: " << uint32(runetype) << ")!"; ++ //me->Whisper(str.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ } ++ ++ void ActivateAllRunes() ++ { ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ _runes.runes[i].Cooldown = 0; ++ //_runes.SetRuneState(i, true); //UNUSED ++ } ++ } ++ ++ void InitRunes() ++ { ++ //_runes.runeState = 0; //UNUSED ++ //_runes.lastUsedRune = RUNE_BLOOD; //UNUSED ++ ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ _runes.runes[i].BaseRune = runeSlotTypes[i]; ++ _runes.runes[i].CurrentRune = _runes.runes[i].BaseRune; ++ _runes.runes[i].Cooldown = 0; ++ //_runes.runes[i].ConvertAura = NULL; //UNUSED ++ //_runes.SetRuneState(i, true); //UNUSED ++ } ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void RuneTimers(uint32 diff) ++ { ++ for (uint8 i = 0; i != MAX_RUNES; ++i) ++ { ++ if (_runes.runes[i].Cooldown <= diff) ++ { ++ _runes.runes[i].Cooldown = 0; ++ //_runes.SetRuneState(i, true); //UNUSED ++ } ++ else ++ _runes.runes[i].Cooldown -= diff; ++ } ++ } ++ ++ void modpower(int32 mod, bool set = false) ++ { ++ if (set && mod < 0) ++ return; ++ if (mod < 0 && runicpower < uint32(abs(mod))) ++ { ++ //debug set runic power to 0 ++ mod = 0; ++ set = true; ++ return; ++ } ++ ++ if (set) ++ runicpower = mod ? mod * 10 : 0; ++ else ++ runicpower += mod * 10; ++ ++ me->SetPower(POWER_RUNIC_POWER, runicpower); ++ } ++ ++ uint32 getpower() ++ { ++ runicpower = me->GetPower(POWER_RUNIC_POWER); ++ return runicpower; ++ } ++ ++ uint8 GetBotStance() const { return Presence; } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ void KilledUnit(Unit*) { } ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || IsCasting() || Feasting() || Rand() > 20) ++ return; ++ ++ //PATH OF FROST ++ if (GetSpell(PATH_OF_FROST_1) && HaveRune(RUNE_FROST)/* && !me->IsMounted()*/) //works while mounted ++ { ++ if ((me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !me->HasAuraType(SPELL_AURA_WATER_WALK)) || ++ (master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !master->HasAuraType(SPELL_AURA_WATER_WALK) && me->GetDistance(master) < 50)) ++ { ++ if (doCast(me, GetSpell(PATH_OF_FROST_1))) ++ return; ++ } ++ } ++ } ++ ++ void CheckHysteria(uint32 diff) ++ { ++ if (!IsSpellReady(HYSTERIA_1, diff) || IsCasting() || Rand() > 15) ++ return; ++ ++ Unit* target = NULL; ++ ++ if (master->IsAlive() && IsMeleeClass(master->getClass()) && master->IsInCombat() && ++ GetHealthPCT(master) > 80 && me->GetDistance(master) < 30 && ++ master->getAttackers().empty() && !CCed(master, true)) ++ { ++ if (Unit* u = master->GetVictim()) ++ if (u->GetHealth() > me->GetMaxHealth() / 2) ++ target = master; ++ } ++ ++ if (!target && IsMeleeClass(_botclass) && GetHealthPCT(me) > 80 && ++ me->getAttackers().empty() && !CCed(me, true)) ++ { ++ if (Unit* u = me->GetVictim()) ++ if (u->GetHealth() > me->GetMaxHealth() / 2) ++ target = me; ++ } ++ ++ if (!target && !IAmFree()) ++ { ++ Group* gr = master->GetGroup(); ++ if (gr) ++ { ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == master) continue; ++ if (!tPlayer || !tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (!tPlayer->IsAlive() || me->GetMap() != tPlayer->FindMap()) continue; ++ if (!IsMeleeClass(tPlayer->getClass()) || !tPlayer->IsInCombat()) continue; ++ if (GetHealthPCT(tPlayer) < 80 || me->GetDistance(tPlayer) > 30) continue; ++ if (!tPlayer->getAttackers().empty() || CCed(tPlayer, true)) continue; ++ if (Unit* u = tPlayer->GetVictim()) ++ { ++ if (u->GetHealth() > (me->GetMaxHealth() * 2) / 3) ++ { ++ target = tPlayer; ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (target && doCast(target, GetSpell(HYSTERIA_1))) ++ { ++ if (target->GetTypeId() == TYPEID_PLAYER) ++ BotWhisper("Hysteria on You!", target->ToPlayer()); ++ ++ GC_Timer = 800; ++ return; ++ } ++ ++ SetSpellCooldown(HYSTERIA_1, 2000); //fail ++ } ++ ++ void CheckAntiMagicShell(uint32 diff) ++ { ++ if (!IsSpellReady(ANTI_MAGIC_SHELL_1, diff, false) || GetHealthPCT(me) > 55 || ++ getpower() < 200 || IsCasting() || Rand() > 50) ++ return; ++ ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ if (b_attackers.empty()) ++ return; ++ ++ bool cast = false; ++ uint8 count = 0; ++ ++ for (AttackerSet::const_iterator itr = b_attackers.begin(); itr != b_attackers.end(); ++itr) ++ { ++ if (!(*itr) || !(*itr)->IsAlive()) continue; ++ if (Spell* spell = (*itr)->GetCurrentSpell(CURRENT_GENERIC_SPELL)) ++ { ++ if (spell->m_targets.GetUnitTargetGUID() == me->GetGUID()) ++ { ++ if ((*itr)->ToCreature() && (*itr)->ToCreature()->isWorldBoss()) ++ { ++ cast = true; ++ break; ++ } ++ ++ if (++count >= 3) ++ { ++ cast = true; ++ break; ++ } ++ } ++ } ++ } ++ ++ if (cast) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(ANTI_MAGIC_SHELL_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(ANTI_MAGIC_SHELL_1, 1500); //fail ++ } ++ ++ void CheckPresence(uint32 diff) ++ { ++ if (presencetimer > diff || IsCasting() || Rand() > 30) //no GCD ++ return; ++ ++ uint8 newpresence = IsTank() ? DEATH_KNIGHT_FROST_PRESENCE : DEATH_KNIGHT_BLOOD_PRESENCE; ++ if (Presence == newpresence) ++ { ++ presencetimer = 500; ++ return; ++ } ++ ++ Presence = newpresence; ++ ++ if (Presence == DEATH_KNIGHT_FROST_PRESENCE && HaveRune(RUNE_FROST)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, FROST_PRESENCE_1)) ++ { ++ GC_Timer = temptimer; ++ presencetimer = 1000; ++ return; ++ } ++ } ++ else if (Presence == DEATH_KNIGHT_BLOOD_PRESENCE && HaveRune(RUNE_BLOOD)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, BLOOD_PRESENCE_1)) ++ { ++ GC_Timer = temptimer; ++ presencetimer = 1000; ++ return; ++ } ++ } ++ ++ presencetimer = 500; //fail ++ } ++ ++ void BreakCC(uint32 diff) ++ { ++ if (IsSpellReady(LICHBORNE_1, diff, false) &&/* Rand() < 75 &&*/ ++ me->HasAuraWithMechanic((1<getPowerType() != POWER_RUNIC_POWER) ++ InitPowers(); ++ ++ if (runicpowertimer <= diff) ++ { ++ if (!me->IsInCombat()) ++ { ++ if (getpower() > uint32(30 * runicpowerLossMult)) ++ me->SetPower(POWER_RUNIC_POWER, runicpower - uint32(30 * runicpowerLossMult)); //-3 runic power every 2 sec ++ else ++ me->SetPower(POWER_RUNIC_POWER, 0); ++ } ++ runicpowertimer = 2000; ++ } ++ if (runicpowertimer2 <= diff) ++ { ++ if (me->IsInCombat()) ++ { ++ if (getpower() < me->GetMaxPower(POWER_RUNIC_POWER)) ++ me->SetPower(POWER_RUNIC_POWER, runicpower + uint32(20 * runicpowerIncomeMult)); //+2 runic power every 5 sec ++ else ++ me->SetPower(POWER_RUNIC_POWER, me->GetMaxPower(POWER_RUNIC_POWER)); ++ } ++ runicpowertimer2 = 5000; ++ } ++ ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ ++ CheckPresence(diff); ++ ++ //HORN OF WINTER ++ if (IsSpellReady(HORN_OF_WINTER_1, diff, false, (me->IsInCombat() ? 45000 : 0)) && Rand() < 30 && ++ (me->IsInCombat() || (me->GetDistance(master) < 28 && master->IsWithinLOSInMap(me)))) ++ { ++ Aura* horn = master->GetAura(GetSpell(HORN_OF_WINTER_1)); ++ if (!horn || horn->GetDuration() < 5000) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(HORN_OF_WINTER_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ //BONE SHIELD ++ if (IsSpellReady(BONE_SHIELD_1, diff) && HaveRune(RUNE_UNHOLY) && Rand() < 25) ++ { ++ Aura* bone = me->GetAura(GetSpell(BONE_SHIELD_1)); ++ if (!bone || bone->GetCharges() < 2 || (!me->IsInCombat() && bone->GetDuration() < 60000)) ++ { ++ if (doCast(me, GetSpell(BONE_SHIELD_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(BONE_SHIELD_1, 1000); //fail ++ } ++ ++ if (me->IsInCombat()) ++ { ++ //ICEBOUND FORTITUDE ++ if (IsSpellReady(ICEBOUND_FORTITUDE_1, diff, false) && getpower() >= 200 && ++ GetHealthPCT(me) < std::min(85, 45 + uint8(me->getAttackers().size()) * 7) && ++ Rand() < 40 + IsTank() * 50) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(ICEBOUND_FORTITUDE_1))) ++ GC_Timer = temptimer; ++ } ++ ++ CheckAntiMagicShell(diff); ++ CheckHysteria(diff); ++ } ++ ++ if (!CheckAttackTarget(BOT_CLASS_DEATH_KNIGHT)) ++ return; ++ ++ Attack(diff); ++ } ++ ++ void Attack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ //SELFHEAL ++ ++ //RUNE TAP ++ if (IsSpellReady(RUNE_TAP_1, diff) && GetHealthPCT(me) < 40 && Rand() < 50) ++ { ++ if (!HaveRune(RUNE_BLOOD) && IsSpellReady(EMPOWER_RUNE_WEAPON_1, diff, false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(EMPOWER_RUNE_WEAPON_1))) ++ { ++ ActivateAllRunes(); ++ GC_Timer = temptimer; ++ } ++ } ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(RUNE_TAP_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //VAMPIRIC BLOOD ++ if (IsSpellReady(VAMPIRIC_BLOOD_1, diff, false) && GetHealthPCT(me) < 26/* && Rand() < 75*/) ++ { ++ if (!HaveRune(RUNE_BLOOD) && IsSpellReady(EMPOWER_RUNE_WEAPON_1, diff, false, 40000)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(EMPOWER_RUNE_WEAPON_1))) ++ { ++ ActivateAllRunes(); ++ GC_Timer = temptimer; ++ } ++ } ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(VAMPIRIC_BLOOD_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //END SELFHEAL ++ ++ //MARK OF BLOOD ++ Unit* u = opponent->GetVictim(); ++ if (IsSpellReady(MARK_OF_BLOOD_1, diff) && HaveRune(RUNE_BLOOD) && ++ u && GetHealthPCT(u) < 85 && opponent->GetHealth() > u->GetMaxHealth() / 3 && ++ (IsTank(u) || u->GetTypeId() == TYPEID_PLAYER) && ++ Rand() < 35 && !opponent->HasAura(MARK_OF_BLOOD_1) && IsInBotParty(u)) ++ { ++ if (doCast(opponent, GetSpell(MARK_OF_BLOOD_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ //AttackerSet m_attackers = master->getAttackers(); ++ //AttackerSet b_attackers = me->getAttackers(); ++ float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ ++ //NON-DISEASE SECTION ++ ++ //PLACEHOLDER: ARMY OF THE DEAD ++ ++ //RANGED SECTION ++ ++ //STRANGULATE ++ if (IsSpellReady(STRANGULATE_1, diff) && meleedist <= 30 && HaveRune(RUNE_BLOOD) && ++ opponent->IsNonMeleeSpellCast(false) && Rand() < 40) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ ++ if (doCast(opponent, GetSpell(STRANGULATE_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ ++ SetSpellCooldown(STRANGULATE_1, 500); //fail ++ } ++ ++ //DARK COMMAND ++ if (IsSpellReady(DARK_COMMAND_1, diff, false) && dist < 30 && IsTank() && ++ opponent->GetVictim() != me && Rand() < 70) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(DARK_COMMAND_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ////DEATH GRIP - DISABLED ++ //if (DEATH_GRIP && DeathGrip_cd <= diff && dist < 30 && ++ // (tank == me && opponent->GetVictim() != me) || ++ // (opponent->GetVictim() == me && opponent->ToPlayer() && opponent->IsNonMeleeSpellCast(false)) && ++ // Rand() < 75) ++ //{ ++ // temptimer = GC_Timer; ++ // if (doCast(opponent, DEATH_GRIP)) ++ // { ++ // DeathGrip_cd = 25000; ++ // GC_Timer = temptimer; ++ // return; ++ // } ++ ++ // DeathGrip_cd = 1000; //fail ++ //} ++ //CHAINS OF ICE ++ if (uint32 CHAINS_OF_ICE = GetSpell(CHAINS_OF_ICE_1)) ++ { ++ if (GC_Timer <= diff && dist < 20 && HaveRune(RUNE_FROST) && opponent->isMoving() && ++ !CCed(opponent) && !IsTank(opponent->GetVictim()) && IsInBotParty(opponent->GetVictim()) && Rand() < 25) ++ { ++ Aura* chains = opponent->GetAura(CHAINS_OF_ICE); ++ if (!chains || chains->GetDuration() < chains->GetMaxDuration() / 4) ++ { ++ if (doCast(opponent, CHAINS_OF_ICE)) ++ { ++ //Improved Chains of Ice: convert frost rune into death rune ++ ConvertRune(RUNE_FROST, 1); ++ return; ++ } ++ } ++ } ++ } ++ ++ //AOE SECTION ++ ++ //HOWLING BLAST ++ if (IsSpellReady(HOWLING_BLAST_1, diff) && IsTank() && meleedist < 8 && HasRole(BOT_ROLE_DPS) && ++ me->getAttackers().size() > 2 && HaveRune(RUNE_UNHOLY) && HaveRune(RUNE_FROST) && Rand() < 50) ++ { ++ if (doCast(me, GetSpell(HOWLING_BLAST_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ ++ SetSpellCooldown(HOWLING_BLAST_1, 500); //fail ++ } ++ //BLOOD BOIL ++ if (IsSpellReady(BLOOD_BOIL_1, diff) && HasRole(BOT_ROLE_DPS) && HaveRune(RUNE_BLOOD) && Rand() < (10 + 40 * IsTank())) ++ { ++ std::list targets; ++ GetNearbyTargetsList(targets, 9.5f); ++ if (targets.size() >= 5) ++ if (doCast(me, GetSpell(BLOOD_BOIL_1))) ++ return; ++ } ++ //DEATH AND DECAY ++ if (IsSpellReady(DEATH_AND_DECAY_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < (30 + 30 * IsTank()) && ++ HaveRune(RUNE_BLOOD) && HaveRune(RUNE_UNHOLY) && HaveRune(RUNE_FROST)) ++ { ++ if (Unit* target = FindAOETarget(30, true)) ++ { ++ if (doCast(target, GetSpell(DEATH_AND_DECAY_1))) ++ return; ++ } ++ ++ SetSpellCooldown(DEATH_AND_DECAY_1, 500); //fail ++ } ++ ++ //END AOE SECTION ++ ++ //ICY TOUCH ++ if (IsSpellReady(ICY_TOUCH_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 20 && HaveRune(RUNE_FROST) && Rand() < 25 && ++ !opponent->HasAura(FROST_FEVER_AURA, me->GetGUID())) ++ { ++ if (doCast(opponent, GetSpell(ICY_TOUCH_1))) ++ return; ++ } ++ //DEATH COIL //custom cd condition ++ if (GetSpell(DEATH_COIL_1) && GC_Timer <= 600 && dist < 20 && HasRole(BOT_ROLE_DPS) && ++ int32(getpower()) >= (400 + 200 * (GetSpell(RUNE_STRIKE_1) != 0 || GetSpell(MIND_FREEZE_1) != 0 || GetSpell(ANTI_MAGIC_SHELL_1) != 0) + 400 * (GetSpell(HUNGERING_COLD_1) != 0)) && ++ Rand() < 60) ++ { ++ if (doCast(opponent, GetSpell(DEATH_COIL_1))) ++ return; ++ } ++ ++ //MELEE SECTION ++ ++ //MIND FREEZE ++ if (IsSpellReady(MIND_FREEZE_1, diff, false) && meleedist <= 5 && getpower() >= 200 && ++ opponent->IsNonMeleeSpellCast(false) && Rand() < 60) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(MIND_FREEZE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //HUNGERING COLD ++ if (IsSpellReady(HUNGERING_COLD_1, diff) && HasRole(BOT_ROLE_DPS) && getpower() >= 400 && Rand() < 20) ++ { ++ std::list targets; ++ GetNearbyTargetsList(targets, 9.f, 0, true); ++ if (targets.size() >= 3) ++ { ++ if (doCast(me, GetSpell(HUNGERING_COLD_1))) ++ return; ++ } ++ ++ SetSpellCooldown(HUNGERING_COLD_1, 500); //fail ++ } ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //RUNE STRIKE ++ if (IsSpellReady(RUNE_STRIKE_1, diff, false) && runestriketimer > me->getAttackTimer(BASE_ATTACK) && ++ HasRole(BOT_ROLE_DPS) && meleedist <= 5 && getpower() >= 200/* && Rand() < 75*/) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(RUNE_STRIKE_1))) ++ { ++ runestriketimer = 0; //do not remove aura, just disable ability ++ GC_Timer = temptimer; ++ } ++ } ++ //PLAGUE STRIKE ++ if (IsSpellReady(PLAGUE_STRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && HaveRune(RUNE_UNHOLY) && Rand() < 35 && ++ !opponent->HasAura(BLOOD_PLAGUE_AURA, me->GetGUID())) ++ { ++ if (doCast(opponent, GetSpell(PLAGUE_STRIKE_1))) ++ return; ++ } ++ ++ //DISEASE SECTION ++ uint32 diseases = opponent->GetDiseasesByCaster(me->GetGUID()); ++ ++ //PESTILENCE //custom cd condition ++ if (GetSpell(PESTILENCE_1) && pestilencetimer == 0 && HasRole(BOT_ROLE_DPS) && GC_Timer <= 600 && ++ diseases > 1 && meleedist <= 5 && ++ HaveRune(RUNE_BLOOD) && Rand() < 15) ++ { ++ std::list targets; ++ GetNearbyTargetsList(targets, 9.f); ++ if (targets.size() > 2) ++ { ++ if (doCast(opponent, GetSpell(PESTILENCE_1))) ++ { ++ pestilencetimer = 10000; ++ return; ++ } ++ } ++ ++ pestilencetimer = 1000; //fail ++ } ++ //DEATH STRIKE ++ if (IsSpellReady(DEATH_STRIKE_1, diff) && diseases > 0 && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && ++ HaveRune(RUNE_UNHOLY) && HaveRune(RUNE_FROST) && ++ GetHealthPCT(me) < (91 - 10 * diseases) && Rand() < 70) ++ { ++ if (doCast(opponent, GetSpell(DEATH_STRIKE_1))) ++ return; ++ } ++ //OBLITERATE ++ if (IsSpellReady(OBLITERATE_1, diff) && diseases > 2 && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && ++ HaveRune(RUNE_UNHOLY) && HaveRune(RUNE_FROST) && Rand() < 20) ++ { ++ if (doCast(opponent, GetSpell(OBLITERATE_1))) ++ return; ++ } ++ //BLOOD STRIKE //custom ++ if (BLOOD_STRIKE && GC_Timer <= diff && HasRole(BOT_ROLE_DPS) && diseases > 1 && meleedist <= 5 && ++ HaveRune(RUNE_BLOOD) && Rand() < 25) ++ { ++ if (doCast(opponent, BLOOD_STRIKE)) ++ return; ++ } ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ ++ //Increased Plague Strike Crit (id 60130): 10% additional critical chance for Plague Strike ++ if (spellId == GetSpell(PLAGUE_STRIKE_1)) ++ aftercrit += 0.1f; ++ //Glyph of Rune Strike: 10% additional critical chance for Rune Strike ++ if (spellId == GetSpell(RUNE_STRIKE_1)) ++ aftercrit += 0.1f; ++ //Subversion: 9% additional critical chance for Blood Strike, Scourge Strike, Heart Strike and Obliterate ++ if (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1) || ++ /*spellId == GetSpell(SCOURGE_STRIKE_1) || */spellId == GetSpell(OBLITERATE_1)) ++ aftercrit += 0.09f; ++ //Improved Death Strike (part 2): 6% additional critical chance for Death Strike ++ if (spellId == GetSpell(DEATH_STRIKE_1)) ++ aftercrit += 0.06f; ++ //Rime (part 1 melee): 15% additional critical chance for Obliterate ++ if (lvl >= 68 && spellId == GetSpell(OBLITERATE_1)) ++ aftercrit += 15.f; ++ //Vicious Strikes (part 1): 6% additional critical chance for Plague Strike and Scourge Strike ++ if (lvl >= 57 && (spellId == GetSpell(PLAGUE_STRIKE_1)/* || spellId == GetSpell(SCOURGE_STRIKE_1)*/)) ++ aftercrit += 6.f; ++ ++ //Annihilation: 3% additional critical chance for melee special abilities ++ if (lvl >= 57) ++ aftercrit += 0.03f; ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ ++ //Might of Mograine: 45% crit damage bonus for Blood Boil, Blood Strike, Death Strike and Heart Strike ++ if (lvl >= 68 && ++ (spellId == GetSpell(BLOOD_BOIL_1) || spellId == BLOOD_STRIKE || ++ spellId == GetSpell(DEATH_STRIKE_1) || spellId == GetSpell(HEART_STRIKE_1))) ++ pctbonus += 0.45f / 2.f; ++ //Guile of Gorefiend (part 1 melee): 45% crit damage bonus for Blood Strike, Frost Strike and Obliterate ++ if (lvl >= 69 && ++ (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1) || ++ spellId == GetSpell(OBLITERATE_1)/* || spellId == GetSpell(FROST_STRIKE_1)*/)) ++ pctbonus += 0.45f / 2.f; ++ //Vicious Strikes (part 2): 30% crit damage bonus for Plague Strike and Scourge Strike ++ if (lvl >= 57 && (spellId == GetSpell(PLAGUE_STRIKE_1)/* || spellId == GetSpell(SCOURGE_STRIKE_1)*/)) ++ pctbonus += 0.3f / 2.f; ++ } ++ ++ //Glypg of Plague Strike: 20% bonus damage for Plague Strike ++ if (spellId == GetSpell(PLAGUE_STRIKE_1)) ++ pctbonus += 0.2f; ++ //Glyph of Blood Strike: 20% bonus damage for Blood Strike on snared targets (Heart Strike too for bots) ++ //warning unsafe ++ if (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1)) ++ if (damageinfo.target->HasAuraWithMechanic((1<GetPower(POWER_RUNIC_POWER) >= 10) ++ { ++ //10 to 250 * 0.001 = 10 to 250 / 1000 = 0.01 to 0.25 ++ pctbonus += float(std::min(me->GetPower(POWER_RUNIC_POWER), 250)) * 0.001f; ++ } ++ //Glyph of Obliterate: 25% bonus damage for Obliterate ++ if (spellId == GetSpell(OBLITERATE_1)) ++ pctbonus += 0.25f; ++ //Bloody Strikes: 15% bonus damage for Blood Strike, 45% for Heart Strike and 30% for Blood Boil ++ if (lvl >= 60) ++ { ++ if (spellId == BLOOD_STRIKE) ++ pctbonus += 0.15f; ++ else if (spellId == GetSpell(HEART_STRIKE_1)) ++ pctbonus += 0.45f; ++ else if (spellId == GetSpell(BLOOD_BOIL_1)) ++ pctbonus += 0.3f; ++ } ++ //Improved Death Strike (part 1): 30% bonus damage for Death Strike ++ if (spellId == GetSpell(DEATH_STRIKE_1)) ++ pctbonus += 0.3f; ++ //Merciless Combat (melee): 12% bonus damage for Obliterate on targets with less than 35% hp ++ //warning unsafe ++ if (lvl >= 67 && spellId == GetSpell(OBLITERATE_1) && damageinfo.target->GetHealthPct() < 35) ++ pctbonus += 0.12f; ++ //Blood of the North (part 1): 10% bonus damage for Blood Strike and Frost Strike (make Heart strike too) ++ if (lvl >= 69 && (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1)/* || spellId == GetSpell(FROST_STRIKE_1)*/)) ++ pctbonus += 0.1f; ++ //Tundra Stalker (melee): 40% damage bonus on targets affected with Frost Fever (20% for bot, regardless of caster) ++ //warning unsafe ++ if (lvl >= 70 && damageinfo.target->HasAura(FROST_FEVER_AURA)) ++ pctbonus += 0.2f; ++ //Outbreak: 30% bonus damage for Plague Strike and 20% for Scourge Strike ++ if (lvl >= 59) ++ { ++ if (spellId == GetSpell(PLAGUE_STRIKE_1)) ++ pctbonus += 0.3f; ++ //else if (spellId == GetSpell(SCOURGE_STRIKE_1)) ++ // pctbonus += 0.2f; ++ } ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Rime (part 1 spell): 15% additional critical chance for Icy Touch ++ if (lvl >= 68 && spellId == GetSpell(ICY_TOUCH_1)) ++ aftercrit += 15.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!spell damage is not yet critical and will be multiplied by 1.5 ++ //so we should put here bonus damage mult /1.5 ++ ++ //Guile of Gorefiend (part 1 spell): 45% crit damage bonus for Howling Blast ++ if (lvl >= 69 && spellId == GetSpell(HOWLING_BLAST_1)) ++ pctbonus += 0.45f / 1.5f; ++ ++ //Runic Focus: 50% crit damage bonus for all spells ++ pctbonus += 0.5f / 1.5f; ++ } ++ ++ //Improved Icy Touch: 15% bonus damage for Icy Touch ++ if (spellId == GetSpell(ICY_TOUCH_1)) ++ pctbonus += 0.15f; ++ //Increased Icy Touch Damage (id 54800): 111 bonus damage for Icy Touch ++ if (spellId == GetSpell(ICY_TOUCH_1)) ++ fdamage += 111.f; ++ //Increased Death Coil Damage (id 54807): 80 bonus damage for Death Coil ++ if (spellId == GetSpell(DEATH_COIL_1)) ++ fdamage += 80.f; ++ //Black Ice: 10% bonus damage for all Shadow and Frost spells ++ if (lvl >= 58 && ++ ((SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask()) || ++ (SPELL_SCHOOL_MASK_SHADOW & spellInfo->GetSchoolMask()))) ++ pctbonus += 0.1f; ++ //Glacier Rot: 20% bonus damage for Icy Touch, Howling Blast and Frost Strike ++ //warning unsafe ++ if (lvl >= 63 && (spellId == GetSpell(ICY_TOUCH_1) || spellId == GetSpell(HOWLING_BLAST_1)/* || spellId == GetSpell(FROST_STRIKE_1)*/) && ++ damageinfo.target->GetDiseasesByCaster(me->GetGUID()) > 0) ++ pctbonus += 0.2f; ++ //Merciless Combat (spell): 12% bonus damage for Icy Touch, Howling Blast and Frost Strike on targets with less than 35% hp ++ //warning unsafe ++ if (lvl >= 67 && ++ (spellId == GetSpell(ICY_TOUCH_1) || spellId == GetSpell(HOWLING_BLAST_1)/* || spellId == GetSpell(FROST_STRIKE_1)*/) && ++ damageinfo.target->GetHealthPct() < 35) ++ pctbonus += 0.12f; ++ //Tundra Stalker (spell): 40% damage bonus on targets affected with Frost Fever (20% for bot, regardless of caster) ++ //warning unsafe ++ if (lvl >= 70 && damageinfo.target->HasAura(FROST_FEVER_AURA)) ++ pctbonus += 0.2f; ++ //Morbidity: 15% damage bonus for Death Coil ++ if (lvl >= 58 && spellId == GetSpell(DEATH_COIL_1)) ++ pctbonus += 0.15f; ++ ++ //temp ++ if (spellId == GetSpell(RUNE_TAP_1)) ++ pctbonus += 1.f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierEffect(SpellInfo const* spellInfo, uint8 effect_index, float& value) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float pct_mod = 1.f; ++ ++ //Periodic damage bonuses ++ if (spellInfo->Effects[effect_index].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE) ++ { ++ //float ticksnum = float(spellInfo->GetMaxDuration() / spellInfo->Effects[effect_index].Amplitude); ++ ++ //Increased Plague Strike DoT Damage (id 54802): increased DoT damage by 100 ++ if (spellId == BLOOD_PLAGUE_AURA) ++ value += 100.f; ++ //Glyph of Icy Touch: 20% bonus damage for Frost Fever ++ if (spellId == FROST_FEVER_AURA) ++ pct_mod += 0.2f; ++ //Black Ice: 10% bonus damage for all Shadow and Frost spells ++ if (lvl >= 58 && ++ ((SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask()) || ++ (SPELL_SCHOOL_MASK_SHADOW & spellInfo->GetSchoolMask()))) ++ pct_mod += 0.1f; ++ //Glyph of Unholy Blight: 40% damage bonus for Unholy Blight (100% for bot) ++ if (spellId == UNHOLY_BLIGHT_AURA) ++ pct_mod += 1.f; ++ } ++ //Heal bonuses ++ if (spellInfo->Effects[effect_index].Effect == SPELL_EFFECT_HEAL) ++ { ++ //Improved Rune Tap: 100% bonus healing from Rune Tap ++ if (spellId == GetSpell(RUNE_TAP_1)) ++ pct_mod += 1.f; ++ } ++ ++ value *= pct_mod; ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ //Glyph of Horn of Winter: 1 minute bonus duration (3 for bot) ++ if (spellId == GetSpell(HORN_OF_WINTER_1)) ++ { ++ if (Aura* horn = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = horn->GetDuration() + 180000; ++ horn->SetDuration(dur); ++ horn->SetMaxDuration(dur); ++ } ++ ++ //Winter Veil addition ++ if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) ++ me->AddAura(44755, target); //snowflakes ++ } ++ ++ if (target == me) ++ return; ++ ++ //Epidemic: 10 sec bonus duration for all diseases ++ if (spellId == FROST_FEVER_AURA || spellId == BLOOD_PLAGUE_AURA || ++ spellId == CRYPT_FEVER_AURA || spellId == EBON_PLAGUE_AURA) ++ { ++ if (Aura* fever = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = fever->GetDuration() + 10000; ++ fever->SetDuration(dur); ++ fever->SetMaxDuration(dur); ++ } ++ } ++ //Sudden Doom: 15% ctc Death Coil on Blood Strike or Heart Strike (up to 30% for bot) ++ if (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1)) ++ { ++ if (GetSpell(DEATH_COIL_1) && me->getLevel() >= 65 && irand(1,100) <= (me->getLevel() - 50)) ++ { ++ //debug: dk bot cannot cast without runic power even triggered spells ++ modpower(40); ++ me->CastSpell(target, GetSpell(DEATH_COIL_1), true); ++ } ++ } ++ //Rime (part 2): Obliterate has 15% chance to reset Howling Blast cooldown (25% for bot, screw runes part) ++ if (spellId == GetSpell(OBLITERATE_1)) ++ { ++ if (me->getLevel() >= 67 && urand(1,100) <= 25) ++ ResetSpellCooldown(HOWLING_BLAST_1); ++ } ++ //Chillblains Improved: increase duration by 10 sec (disable on players) ++ if (spellId == ICY_CLUTCH) ++ { ++ if (target->GetTypeId() != TYPEID_PLAYER) ++ { ++ if (Aura* chill = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = chill->GetDuration() + 10000; ++ chill->SetDuration(dur); ++ chill->SetMaxDuration(dur); ++ } ++ } ++ } ++ //Blood of the North (part 2): Blood Strike and Pestilence convert Blood Rune to Dark Rune (make Heart Strike too) ++ if (spellId == BLOOD_STRIKE || spellId == GetSpell(HEART_STRIKE_1) || GetSpell(spellId == PESTILENCE_1)) ++ { ++ if (me->getLevel() >= 69) ++ ConvertRune(RUNE_BLOOD, 1); ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if (spellId == RUNE_STRIKE_ACIVATION_AURA) ++ { ++ //Rune Strike activation and timer set ++ runestriketimer = 10000; ++ } ++ if (spellId == GetSpell(ANTI_MAGIC_SHELL_1)) ++ { ++ //Glyph of Anti-Magic Shell: 2 sec increased duration (5 for bot) ++ if (Aura* shell = me->GetAura(spellId)) ++ { ++ uint32 dur = shell->GetDuration() + 5000; ++ shell->SetDuration(dur); ++ shell->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(VAMPIRIC_BLOOD_1)) ++ { ++ //Glyph of Vampiric Blood: 5 sec increased duration ++ if (Aura* blood = me->GetAura(spellId)) ++ { ++ uint32 dur = blood->GetDuration() + 5000; ++ blood->SetDuration(dur); ++ blood->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(BONE_SHIELD_1)) ++ { ++ //Glyph of Bone Shield: 1 bonus charge (2 for bot, 7 for tank) ++ if (Aura* bone = me->GetAura(spellId)) ++ { ++ bone->SetCharges(bone->GetCharges() + (IsTank() ? 3 : 1)); ++ } ++ } ++ if (spellId == ICY_TALONS_AURA1 || spellId == ICY_TALONS_AURA2 || ++ spellId == ICY_TALONS_AURA3 || spellId == ICY_TALONS_AURA4 || spellId == ICY_TALONS_AURA5) ++ { ++ //Icy Talons: Synchronize with Epidemic, add 10 sec duration ++ if (Aura* talons = me->GetAura(spellId)) ++ { ++ uint32 dur = talons->GetDuration() + 10000; ++ talons->SetDuration(dur); ++ talons->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(DEATH_STRIKE_1) || spellId == GetSpell(OBLITERATE_1)) ++ { ++ //Death Rune Mastery: convert Unholy and Frost Runes into Death Runes ++ ConvertRune(RUNE_UNHOLY, 1); ++ ConvertRune(RUNE_FROST, 1); ++ } ++ if (spellId == GetSpell(ICEBOUND_FORTITUDE_1)) ++ { ++ //Guile of Gorefiend (part 2): Icebound Fortitude 6 sec increased duration (18 for bot) ++ if (Aura* fort = me->GetAura(spellId)) ++ { ++ uint32 dur = fort->GetDuration() + 18000; ++ fort->SetDuration(dur); ++ fort->SetMaxDuration(dur); ++ } ++ } ++ ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ if (victim == me) ++ return; ++ ++ if (damageType == DIRECT_DAMAGE || damageType == SPELL_DIRECT_DAMAGE) ++ { ++ //Blood Presence Heal ++ if (me->HasAura(IMPROVED_BLOOD_PRESENCE_AURA)) ++ { ++ int32 bp0 = int32(damage / 25); //4% ++ me->CastCustomSpell(me, BLOOD_PRESENCE_HEAL_EFFECT, &bp0, NULL, NULL, true); ++ } ++ } ++ ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ presencetimer = 0; ++ runicpowertimer = 2000; ++ runicpowertimer2 = 5000; ++ runestriketimer = 0; ++ pestilencetimer = 0; ++ ++ Presence = BOT_STANCE_NONE; ++ ++ runicpowerIncomeMult = sWorld->getRate(RATE_POWER_RUNICPOWER_INCOME); ++ runicpowerLossMult = sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS); ++ me->setPowerType(POWER_RUNIC_POWER); ++ me->SetMaxPower(POWER_RUNIC_POWER, me->GetCreatePowers(POWER_RUNIC_POWER)); ++ ++ DefaultInit(); ++ InitRunes(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ RuneTimers(diff); ++ ++ if (presencetimer > diff) presencetimer -= diff; ++ if (runicpowertimer > diff) runicpowertimer -= diff; ++ if (runicpowertimer2 > diff) runicpowertimer2 -= diff; ++ ++ if (runestriketimer > diff) runestriketimer -= diff; ++ else runestriketimer = 0; ++ if (pestilencetimer > diff) pestilencetimer -= diff; ++ else pestilencetimer = 0; ++ } ++ ++ void InitPowers() ++ { ++ if (master->getLevel() >= 70) ++ RefreshAura(RUNIC_POWER_MASTERY,5); ++ else if (master->getLevel() >= 58) ++ RefreshAura(RUNIC_POWER_MASTERY,4); ++ else ++ { ++ RefreshAura(RUNIC_POWER_MASTERY,0); ++ me->SetMaxPower(POWER_RUNIC_POWER, me->GetCreatePowers(POWER_RUNIC_POWER)); ++ } ++ ++ if (runicpower) ++ me->SetPower(POWER_RUNIC_POWER, runicpower); ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ //InitSpellMap(BLOOD_STRIKE_1); ++ InitSpellMap(ICY_TOUCH_1); ++ InitSpellMap(PLAGUE_STRIKE_1); ++ InitSpellMap(DEATH_STRIKE_1); ++ InitSpellMap(OBLITERATE_1); ++ InitSpellMap(RUNE_STRIKE_1); ++ /*Talent*/InitSpellMap(HEART_STRIKE_1); ++ ++ InitSpellMap(BLOOD_BOIL_1); ++ InitSpellMap(DEATH_AND_DECAY_1); ++ /*Talent*/lvl >= 63 ? InitSpellMap(HOWLING_BLAST_1) : RemoveSpell(HOWLING_BLAST_1); ++ ++ InitSpellMap(DEATH_COIL_1); ++ InitSpellMap(DEATH_GRIP_1, true); ++ InitSpellMap(PESTILENCE_1); ++ InitSpellMap(MIND_FREEZE_1); ++ InitSpellMap(STRANGULATE_1); ++ InitSpellMap(CHAINS_OF_ICE_1); ++ InitSpellMap(ICEBOUND_FORTITUDE_1); ++ InitSpellMap(DARK_COMMAND_1); ++ InitSpellMap(ANTI_MAGIC_SHELL_1); ++ InitSpellMap(ARMY_OF_THE_DEAD_1); ++ /*Talent*/InitSpellMap(LICHBORNE_1, true); ++ /*Talent*/lvl >= 60 ? InitSpellMap(HUNGERING_COLD_1) : RemoveSpell(HUNGERING_COLD_1); ++ ++ InitSpellMap(PATH_OF_FROST_1); ++ InitSpellMap(HORN_OF_WINTER_1); ++ /*Talent*/InitSpellMap(RUNE_TAP_1, true); ++ /*Talent*/lvl >= 58 ? InitSpellMap(BONE_SHIELD_1) : RemoveSpell(BONE_SHIELD_1); ++ InitSpellMap(EMPOWER_RUNE_WEAPON_1); ++ /*Talent*/InitSpellMap(MARK_OF_BLOOD_1, true); ++ /*Talent*/InitSpellMap(VAMPIRIC_BLOOD_1, true); ++ /*Talent*/lvl >= 59 ? InitSpellMap(HYSTERIA_1) : RemoveSpell(HYSTERIA_1); ++ ++ InitSpellMap(BLOOD_PRESENCE_1, true); ++ InitSpellMap(FROST_PRESENCE_1, true); ++ //InitSpellMap(UNHOLY_PRESENCE_1, true); ++ ++ /*Custom*/BLOOD_STRIKE = lvl >= 65 ? GetSpell(HEART_STRIKE_1) : InitSpell(me, BLOOD_STRIKE_1); ++ InitSpellMap(BLOOD_STRIKE); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(GLYPH_OF_CHAINS_OF_ICE, level >= 58 ? 1 : 0); ++ RefreshAura(CHAINS_OF_ICE_FROST_RUNE_REFRESH, level >= 80 ? 4 : level >= 77 ? 3 : level >= 68 ? 2 : level >= 58 ? 1 : 0); ++ RefreshAura(GLYPH_OF_HEART_STRIKE, level >= 65 ? 1 : 0); ++ RefreshAura(GLYPH_OF_RUNE_TAP, level >= 68 ? 2 : level >= 60 ? 1 : 0); ++ RefreshAura(GLYPH_OF_HOWLING_BLAST, level >= 63 ? 1 : 0); ++ RefreshAura(BUTCHERY, level >= 57 ? 1 : 0); ++ RefreshAura(SCENT_OF_BLOOD, level >= 58 ? 1 : 0); ++ RefreshAura(VENDETTA, level >= 59 ? 1 : 0); ++ RefreshAura(BLOODY_VENGEANCE3, level >= 65 ? 1 : 0); ++ RefreshAura(BLOODY_VENGEANCE2, level >= 60 && level < 65 ? 1 : 0); ++ RefreshAura(BLOODY_VENGEANCE1, level >= 57 && level < 60 ? 1 : 0); ++ RefreshAura(ABOMINATIONS_MIGHT, level >= 60 ? 1 : 0); ++ RefreshAura(IMPROVED_BLOOD_PRESENCE, level >= 67 ? 1 : 0); ++ RefreshAura(BLOODWORMS, level >= 65 ? 2 : 0); ++ //RefreshAura(IMPROVED_DEATH_STRIKE, level >= 66 ? 1 : 0); ++ RefreshAura(TOUGHNESS, level >= 57 ? 1 : 0); ++ RefreshAura(ANNIHILATION, level >= 57 ? 1 : 0); ++ RefreshAura(ICY_TALONS, level >= 60 ? 1 : 0); ++ RefreshAura(CHILL_OF_THE_GRAVE, level >= 68 ? 2 : level >= 58 ? 1 : 0); ++ RefreshAura(IMPROVED_ICY_TALONS, level >= 64 ? 1 : 0); ++ RefreshAura(CHILBLAINS, level >= 68 ? 1 : 0); ++ RefreshAura(ACCLIMATION, level >= 69 ? 1 : 0); ++ RefreshAura(NECROSIS5, level >= 63 ? 1 : 0); ++ RefreshAura(NECROSIS4, level >= 62 && level < 63 ? 1 : 0); ++ RefreshAura(NECROSIS3, level >= 61 && level < 62 ? 1 : 0); ++ RefreshAura(NECROSIS2, level >= 60 && level < 61 ? 1 : 0); ++ RefreshAura(NECROSIS1, level >= 59 && level < 60 ? 1 : 0); ++ RefreshAura(BLOOD_CAKED_BLADE3, level >= 65 ? 1 : 0); ++ RefreshAura(BLOOD_CAKED_BLADE2, level >= 62 && level < 65 ? 1 : 0); ++ RefreshAura(BLOOD_CAKED_BLADE1, level >= 60 && level < 62 ? 1 : 0); ++ RefreshAura(DIRGE, level >= 67 ? 2 : level >= 61 ? 1 : 0); ++ RefreshAura(UNHOLY_BLIGHT, level >= 61 ? 1 : 0); ++ RefreshAura(DESECRATION, level >= 62 ? 1 : 0); ++ RefreshAura(CRYPT_FEVER, level >= 64 ? 1 : 0); ++ RefreshAura(EBON_PLAGUEBRINGER, level >= 68 ? 1 : 0); ++ RefreshAura(WANDERING_PLAGUE, level >= 67 ? 1 : 0); ++ ++ RefreshAura(FROST_FEVER); ++ RefreshAura(BLOOD_PLAGUE); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case LICHBORNE_1: ++ case PATH_OF_FROST_1: ++ case HORN_OF_WINTER_1: ++ case BONE_SHIELD_1: ++ case RUNE_TAP_1: ++ case EMPOWER_RUNE_WEAPON_1: ++ case VAMPIRIC_BLOOD_1: ++ case HYSTERIA_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ uint32 BLOOD_STRIKE; ++/*tmrs*/uint32 presencetimer, runicpowertimer, runicpowertimer2, runestriketimer, pestilencetimer; ++/*misc*/uint32 runicpower; ++/*misc*/float runicpowerIncomeMult, runicpowerLossMult; ++/*Chck*/uint8 Presence; ++ ++ BotRunes _runes; ++ ++ enum DeathKnightBaseSpells ++ { ++ BLOOD_STRIKE_1 = 45902, ++ ICY_TOUCH_1 = 45477, ++ PLAGUE_STRIKE_1 = 45462, ++ DEATH_STRIKE_1 = 49998, ++ OBLITERATE_1 = 49020, ++ RUNE_STRIKE_1 = 56815, ++ HEART_STRIKE_1 = 55050, ++ ++ BLOOD_BOIL_1 = 48721, ++ DEATH_AND_DECAY_1 = 43265, ++ HOWLING_BLAST_1 = 49184, ++ ++ DEATH_COIL_1 = 47541, ++ DEATH_GRIP_1 = 49576, ++ PESTILENCE_1 = 50842, ++ MIND_FREEZE_1 = 47528, ++ STRANGULATE_1 = 47476, ++ CHAINS_OF_ICE_1 = 45524, ++ ICEBOUND_FORTITUDE_1 = 48792, ++ DARK_COMMAND_1 = 56222, ++ ANTI_MAGIC_SHELL_1 = 48707, ++ ARMY_OF_THE_DEAD_1 = 42650, ++ LICHBORNE_1 = 49039, ++ HUNGERING_COLD_1 = 49203, ++ ++ PATH_OF_FROST_1 = 3714, ++ HORN_OF_WINTER_1 = 57330, ++ BONE_SHIELD_1 = 49222, ++ RUNE_TAP_1 = 48982, ++ EMPOWER_RUNE_WEAPON_1 = 47568, ++ MARK_OF_BLOOD_1 = 49005, ++ VAMPIRIC_BLOOD_1 = 55233, ++ HYSTERIA_1 = 49016, ++ ++ BLOOD_PRESENCE_1 = 48266, ++ FROST_PRESENCE_1 = 48263 ++ //UNHOLY_PRESENCE_1 = 48265 ++ }; ++ enum DeathKnightPassives ++ { ++ //Talents ++ BUTCHERY = 49483,//rank 2 ++ SCENT_OF_BLOOD = 49509,//rank 3 ++ VENDETTA = 55136,//rank 3 ++ BLOODY_VENGEANCE1 = 48988, ++ BLOODY_VENGEANCE2 = 49503, ++ BLOODY_VENGEANCE3 = 49504, ++ ABOMINATIONS_MIGHT = 53138,//rank 2 ++ IMPROVED_BLOOD_PRESENCE = 50371,//rank 2 ++ BLOODWORMS = 49543,//rank 3 ++ IMPROVED_DEATH_STRIKE = 62908,//rank 2 ++ TOUGHNESS = 49789,//rank 5 ++ ANNIHILATION = 51473,//rank 3 ++ ICY_TALONS = 50887,//rank 5 ++ CHILL_OF_THE_GRAVE = 50115,//rank 2 ++ IMPROVED_ICY_TALONS = 55610, ++ CHILBLAINS = 50043,//rank 3 ++ ACCLIMATION = 50152,//rank 3 ++ NECROSIS1 = 51459, ++ NECROSIS2 = 51462, ++ NECROSIS3 = 51463, ++ NECROSIS4 = 51464, ++ NECROSIS5 = 51465, ++ BLOOD_CAKED_BLADE1 = 49219, ++ BLOOD_CAKED_BLADE2 = 49627, ++ BLOOD_CAKED_BLADE3 = 49628, ++ DIRGE = 51206,//rank 2 ++ UNHOLY_BLIGHT = 49194, ++ DESECRATION = 55667,//rank 2 ++ CRYPT_FEVER = 49632,//rank 3 ++ EBON_PLAGUEBRINGER = 51161,//rank 3 ++ WANDERING_PLAGUE = 49655,//rank 3 ++ //Special ++ /*Talent*/RUNIC_POWER_MASTERY = 50147,//rank 2 ++ FROST_FEVER = 59921, ++ BLOOD_PLAGUE = 59879, ++ //Other ++ GLYPH_OF_CHAINS_OF_ICE = 58620,//damage proc ++ CHAINS_OF_ICE_FROST_RUNE_REFRESH = 62459,//5 runic power gain ++ GLYPH_OF_HEART_STRIKE = 58616,//snare 50% for 10 sec ++ GLYPH_OF_RUNE_TAP = 59327,//10% heal for party ++ GLYPH_OF_HOWLING_BLAST = 63335 //frost fever on targets ++ }; ++ enum DeathKnightSpecial ++ { ++ FROST_FEVER_AURA = 55095, ++ BLOOD_PLAGUE_AURA = 55078, ++ CRYPT_FEVER_AURA = 50510,//rank 3 ++ EBON_PLAGUE_AURA = 51735,//rank 3 ++ ++ RUNE_STRIKE_ACIVATION_AURA = 56817, ++ ++ IMPROVED_BLOOD_PRESENCE_AURA = 63611, ++ BLOOD_PRESENCE_HEAL_EFFECT = 50475, ++ BLADE_BARRIER_AURA = 64859,//rank 5 ++ UNHOLY_BLIGHT_AURA = 50536, ++ ++ ICY_TALONS_AURA1 = 50882,//rank 1 ++ ICY_TALONS_AURA2 = 58575,//rank 2 ++ ICY_TALONS_AURA3 = 58576,//rank 3 ++ ICY_TALONS_AURA4 = 58577,//rank 4 ++ ICY_TALONS_AURA5 = 58578,//rank 5 ++ ++ DEATH_COIL_ENEMY = 47632, ++ ICY_CLUTCH = 50436 //rank 3 Chilblains proc ++ }; ++ //enum RunePlacing ++ //{ ++ // RUNE_BLOOD_FIRST, ++ // RUNE_BLOOD_SECOND, ++ // RUNE_UNHOLY_FIRST, ++ // RUNE_UNHOLY_SECOND, ++ // RUNE_FROST_FIRST, ++ // RUNE_FROST_SECOND, ++ // NO_RUNE ++ //}; ++ }; ++}; ++ ++void AddSC_death_knight_bot() ++{ ++ new death_knight_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_druid_ai.cpp b/src/server/game/AI/NpcBots/bot_druid_ai.cpp +new file mode 100644 +index 0000000..5802780 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_druid_ai.cpp +@@ -0,0 +1,1389 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++//#include "WorldSession.h" ++/* ++Druid NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - Maybe 30% ++TODO: Feral Spells (from scratch), More Forms, Balance Spells + treants... ++*/ ++class druid_bot : public CreatureScript ++{ ++public: ++ druid_bot() : CreatureScript("druid_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new bot_druid_ai(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct bot_druid_ai : public bot_minion_ai ++ { ++ bot_druid_ai(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_DRUID; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_DRUID) != SPELL_CAST_OK) ++ return false; ++ ++ m_botSpellInfo = sSpellMgr->GetSpellInfo(spellId); ++ if (me->GetShapeshiftForm() == FORM_NONE && swiftness && m_botSpellInfo->CalcCastTime() > 0) ++ { ++ DoCast(victim, spellId, true); ++ me->RemoveAurasDueToSpell(NATURES_SWIFTNESS_1, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE); ++ me->RemoveAurasDueToSpell(CRIT_50, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE); ++ swiftness = false; ++ return true; ++ } ++ if (spellId == GetSpell(BEAR_FORM_1) || GetSpell(spellId == CAT_FORM_1)) ++ { ++ //me->ModifyPower(POWER_MANA, - int32(m_botSpellInfo->CalcPowerCost(me, m_botSpellInfo->GetSchoolMask()))); ++ if (me->GetVictim()) ++ GetInPosition(true); ++ } ++ ++ bool result = bot_ai::doCast(victim, spellId, triggered); ++ ++ if (result && ++ spellId != MANAPOTION && spellId != WARSTOMP_1 && ++ me->HasAura(OMEN_OF_CLARITY_BUFF)) ++ { ++ cost = m_botSpellInfo->CalcPowerCost(me, m_botSpellInfo->GetSchoolMask()); ++ clearcast = true; ++ power = me->getPowerType(); ++ } ++ return result; ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { removeFeralForm(true, false); bot_minion_ai::JustDied(u); } ++ ++ uint8 GetBotStance() const ++ { ++ return Form; ++ } ++ ++ void warstomp(uint32 diff) ++ { ++ if (me->getRace() != RACE_TAUREN) return; ++ if (!IsSpellReady(WARSTOMP_1, diff, false)) return; ++ if (me->GetShapeshiftForm() != FORM_NONE) ++ return; ++ ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ if (b_attackers.empty()) ++ { ++ Unit* u = me->SelectNearestTarget(5); ++ if (u && u->IsInCombat() && u->isTargetableForAttack()) ++ { ++ if (doCast(me, WARSTOMP_1)) ++ return; ++ } ++ } ++ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter) || (*iter)->isDead()) continue; ++ if (!(*iter)->isTargetableForAttack()) continue; ++ if (me->GetDistance((*iter)) <= 5) ++ { ++ if (doCast(me, WARSTOMP_1)) ++ return; ++ } ++ } ++ } ++ ++ bool DamagePossible() ++ { ++ return true; ++ //return (GetManaPCT(me) < 30 || GetHealthPCT(master) < 50); ++ /*if (GetHealthPCT(master) < 75 || GetHealthPCT(me) < 75) return false; ++ ++ if (Group* pGroup = master->GetGroup()) ++ { ++ uint8 LHPcount = 0; ++ uint8 DIScount = 0; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || tPlayer->isDead()) continue; ++ if (me->GetExactDist(tPlayer) > 30) continue; ++ if (tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth() < 75) ++ ++LHPcount; ++ Unit::AuraApplicationMap const& auras = tPlayer->GetAppliedAuras(); ++ for (Unit::AuraApplicationMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) ++ if (itr->second->GetBase()->GetSpellInfo()->Dispel == DISPEL_POISON) ++ ++DIScount; ++ } ++ uint8 members = master->GetGroup()->GetMembersCount(); ++ ++ if (members > 10) ++ { ++ if (LHPcount > 1 || DIScount > 2) return false; ++ } ++ if (members > 4) ++ { ++ if (LHPcount > 0 || DIScount > 1) return false; ++ } ++ if (members < 5) ++ { ++ if (LHPcount > 0 || DIScount > 0) return false; ++ } ++ }//endif unitlist ++ ++ Unit* u = master->GetVictim(); ++ if (master->getAttackers().size() > 4 || ++ (!master->getAttackers().empty() && ++ u != NULL && u->GetHealth() > me->GetMaxHealth()*17)) ++ return false; ++ ++ return true;*/ ++ } ++ ++ void removeFeralForm(bool force = false, bool /*init*/ = true, uint32 diff = 0) ++ { ++ if (!force && formtimer > diff) ++ return; ++ ++ ShapeshiftForm form = me->GetShapeshiftForm(); ++ if (form != FORM_NONE) ++ { ++ switch (form) ++ { ++ case FORM_DIREBEAR: ++ case FORM_BEAR: ++ me->RemoveAurasDueToSpell(GetSpell(BEAR_FORM_1)); ++ break; ++ case FORM_CAT: ++ me->RemoveAurasDueToSpell(GetSpell(CAT_FORM_1)); ++ me->RemoveAurasDueToSpell(ENERGIZE); ++ break; ++ default: ++ break; ++ } ++ ++ setStats(BOT_CLASS_DRUID); ++ } ++ else if (Form != BOT_STANCE_NONE) ++ Form = BOT_STANCE_NONE; ++ } ++ ++ bool IsMelee() const ++ { ++ if (GetBotStance() == DRUID_BEAR_FORM || GetBotStance() == DRUID_CAT_FORM) ++ return true; ++ ++ return bot_ai::IsMelee(); ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void doBearActions(uint32 diff) ++ { ++ if (me->getPowerType() != POWER_RAGE) return; ++ ++ if (GetHealthPCT(me) < 75) ++ if (HealTarget(me, GetHealthPCT(me), diff)) ++ return; ++ opponent = me->GetVictim(); ++ if (opponent) ++ StartAttack(opponent, true); ++ else ++ return; ++ ++ //range check (melee) to prevent fake casts ++ if (me->GetDistance(opponent) > 5) return; ++ ++ if (IsSpellReady(MANGLE_BEAR_1, diff) && HasRole(BOT_ROLE_DPS) && rage >= 150 && Rand() < 35 && ++ doCast(opponent, GetSpell(MANGLE_BEAR_1))) ++ return; ++ ++ if (IsSpellReady(SWIPE_1, diff) && HasRole(BOT_ROLE_DPS) && rage >= 150 && Rand() < 75 && ++ doCast(opponent, GetSpell(SWIPE_1))) ++ return; ++ ++ }//end doBearActions ++ ++ void doCatActions(uint32 diff) ++ { ++ if (me->getPowerType() != POWER_ENERGY) return; ++ ++ if (GetHealthPCT(me) < 75) ++ if (HealTarget(me, GetHealthPCT(me), diff)) ++ return; ++ opponent = me->GetVictim(); ++ if (opponent) ++ StartAttack(opponent, true); ++ else ++ return; ++ ++ uint32 energy = me->GetPower(POWER_ENERGY); ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //range check (melee) to prevent fake casts ++ if (me->GetDistance(opponent) > 5) return; ++ ++ if (IsSpellReady(MANGLE_CAT_1, diff) && energy > 45 && HasRole(BOT_ROLE_DPS) && Rand() < 35 && ++ doCast(opponent, GetSpell(MANGLE_CAT_1))) ++ return; ++ if (IsSpellReady(RAKE_1, diff) && energy > 40 && HasRole(BOT_ROLE_DPS) && Rand() < 30 && ++ doCast(opponent, GetSpell(RAKE_1))) ++ return; ++ if (IsSpellReady(SHRED_1, diff) && energy > 60 && HasRole(BOT_ROLE_DPS) && !opponent->HasInArc(M_PI, me) && Rand() < 50 && ++ doCast(opponent, GetSpell(SHRED_1))) ++ return; ++ if (IsSpellReady(RIP_1, diff) && energy > 30 && HasRole(BOT_ROLE_DPS) && Rand() < 30 && ++ doCast(opponent, GetSpell(RIP_1))) ++ return; ++ if (IsSpellReady(CLAW_1, diff) && energy > 45 && HasRole(BOT_ROLE_DPS) && Rand() < 80 && ++ doCast(opponent, GetSpell(CLAW_1))) ++ return; ++ }//end doCatActions ++ ++ void doBalanceActions(uint32 diff) ++ { ++ removeFeralForm(true, true); ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent); ++ } ++ else ++ return; ++ ++ AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ //range check to prevent fake casts ++ if (me->GetExactDist(opponent) > 30 || !DamagePossible()) return; ++ ++ if (IsSpellReady(HURRICANE_1, diff) && !me->isMoving() && HasRole(BOT_ROLE_DPS) && Rand() < 35) ++ { ++ Unit* target = FindAOETarget(30, true); ++ if (target && doCast(target, GetSpell(HURRICANE_1))) ++ return; ++ SetSpellCooldown(HURRICANE_1, 2000); //fail ++ } ++ if (uint32 FAERIE_FIRE = GetSpell(FAERIE_FIRE_1)) ++ { ++ if (GC_Timer <= diff && ++ opponent->getAttackers().size() > 1 &&//check if faerie fire is not useless 50/50 ++ Rand() < 20 && !HasAuraName(opponent, FAERIE_FIRE_1)) ++ { ++ if (doCast(opponent, FAERIE_FIRE)) ++ return; ++ } ++ } ++ if (IsSpellReady(MOONFIRE_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 20 && ++ !HasAuraName(opponent, MOONFIRE_1, me->GetGUID())) ++ { ++ if (doCast(opponent, GetSpell(MOONFIRE_1))) ++ return; ++ } ++ if (IsSpellReady(STARFIRE_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 50) ++ { ++ if (doCast(opponent, GetSpell(STARFIRE_1))) ++ return; ++ } ++ if (IsSpellReady(WRATH_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 40) ++ { ++ if (doCast(opponent, GetSpell(WRATH_1))) ++ return; ++ } ++ } ++ ++ bool MassGroupHeal(Player* gPlayer, uint32 diff) ++ { ++ if (!gPlayer || GC_Timer > diff || IAmFree()) return false; ++ if (IsCasting()) return false; // if I'm already casting ++ bool tranq = IsSpellReady(TRANQUILITY_1, diff, false); ++ bool growt = IsSpellReady(WILD_GROWTH_1, diff, false); ++ if (!tranq && !growt) return false; ++ if (Rand() > 30) return false; ++ Group* pGroup = gPlayer->GetGroup(); ++ if (!pGroup) return false; ++ uint8 LHPcount = 0; ++ uint8 pct = 100; ++ Unit* healTarget = NULL; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->IsInWorld() || tPlayer->GetMapId() != me->GetMapId() || ++ (tPlayer->isDead() && !tPlayer->HaveBot())) continue; ++ if (me->GetExactDist(tPlayer) > 39) continue; ++ if (GetHealthPCT(tPlayer) < 80) ++ { ++ if (GetHealthPCT(tPlayer) < pct) ++ { ++ pct = GetHealthPCT(tPlayer); ++ healTarget = tPlayer; ++ } ++ ++LHPcount; ++ if (LHPcount > 2) break; ++ } ++ if (tPlayer->HaveBot()) ++ { ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (bot && bot->IsInWorld() && bot->GetExactDist(me) < 40 && GetHealthPCT(bot) < 80) ++ { ++ if (GetHealthPCT(bot) < pct) ++ { ++ pct = GetHealthPCT(bot); ++ healTarget = bot; ++ } ++ ++LHPcount; ++ if (LHPcount > 2) break; ++ } ++ } ++ } ++ } ++ if (LHPcount > 2 && tranq && ++ doCast(me, GetSpell(TRANQUILITY_1))) ++ return true; ++ if (LHPcount > 0 && growt && healTarget && ++ doCast(healTarget, GetSpell(WILD_GROWTH_1))) ++ return true; ++ ++ return false; ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ ++ if (me->getPowerType() == POWER_RAGE) ++ { ++ rage = me->GetPower(POWER_RAGE); ++ if (ragetimer2 <= diff) ++ { ++ if (me->IsInCombat() && me->getLevel() >= 30) ++ { ++ if (rage < 990) ++ me->SetPower(POWER_RAGE, rage + uint32(10.f*rageIncomeMult));//1 rage per 2 sec ++ else ++ me->SetPower(POWER_RAGE, 1000); ++ } ++ ragetimer2 = 2000; ++ } ++ if (ragetimer <= diff) ++ { ++ if (!me->IsInCombat()) ++ { ++ if (rage > 10.f*rageLossMult) ++ me->SetPower(POWER_RAGE, rage - uint32(10.f*rageLossMult)); //-1 rage per 1.5 sec ++ else ++ me->SetPower(POWER_RAGE, 0); ++ } ++ ragetimer = 1500; ++ if (rage > 1000) me->SetPower(POWER_RAGE, 1000); ++ if (rage < 10) me->SetPower(POWER_RAGE, 0); ++ } ++ } ++ if (clearcast && me->HasAura(OMEN_OF_CLARITY_BUFF) && !me->IsNonMeleeSpellCast(false)) ++ { ++ me->ModifyPower(power, cost); ++ me->RemoveAurasDueToSpell(OMEN_OF_CLARITY_BUFF, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE); ++ clearcast = false; ++ } ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ warstomp(diff); ++ ++ if (Potion_cd <= diff && me->getPowerType() == POWER_MANA && GetManaPCT(me) < 20) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ //Heal master ++ if (GetHealthPCT(master) < 85) ++ HealTarget(master, GetHealthPCT(master), diff); ++ //Innervate ++ doInnervate(diff); ++ ++ MassGroupHeal(master, diff); ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ else ++ CheckBattleRez(diff); ++ BuffAndHealGroup(master, diff); ++ //CureTarget(master, GetSpell(CURE_POISON_1), diff); ++ CureGroup(master, GetSpell(CURE_POISON_1), diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_DRUID)) ++ return; ++ ++ //debug ++ opponent = me->GetVictim(); ++ ++ if (GetHealthPCT(me) < 75) ++ { ++ HealTarget(me, GetHealthPCT(me), diff); ++ return; ++ } ++ ++ if (IsCasting()) ++ return; //Casting heal or something ++ ++ CheckRoots(diff); ++ ++ if (DamagePossible() && opponent != NULL) ++ { ++ Unit* u = opponent->GetVictim(); ++ //if the target is attacking us, we want to go bear ++ if (GetSpell(BEAR_FORM_1) && !CCed(opponent) && ++ ((u == me || (IsTank() && IsInBotParty(u))) || ++ (!me->getAttackers().empty() && (*me->getAttackers().begin()) == opponent && opponent->GetMaxHealth() > me->GetMaxHealth()*2))) ++ { ++ //if we don't have bear yet ++ if (me->GetShapeshiftForm() != FORM_DIREBEAR && ++ me->GetShapeshiftForm() != FORM_BEAR && ++ formtimer <= diff && ++ doCast(me, GetSpell(BEAR_FORM_1))) ++ { ++ formtimer = 1500; ++ } ++ if (me->GetShapeshiftForm() == FORM_DIREBEAR || ++ me->GetShapeshiftForm() == FORM_BEAR) ++ doBearActions(diff); ++ } ++ else ++ if (GetSpell(CAT_FORM_1) && master->GetVictim() != opponent && u && ++ IsTank(u) && u != me && ++ opponent->GetMaxHealth() < u->GetMaxHealth()*3) ++ { ++ //if we don't have cat yet ++ if (me->GetShapeshiftForm() != FORM_CAT && formtimer <= diff) ++ { ++ if (doCast(me, GetSpell(CAT_FORM_1))) ++ { ++ formtimer = 1500; ++ } ++ } ++ if (me->GetShapeshiftForm() == FORM_CAT) ++ doCatActions(diff); ++ } ++ else if (!IsTank()) ++ doBalanceActions(diff); ++ } ++ else if (!IsTank()) ++ doBalanceActions(diff); ++ } ++ ++ bool HealTarget(Unit* target, uint8 hp, uint32 diff) ++ { ++ if (!HasRole(BOT_ROLE_HEAL)) return false; ++ if (hp > 95) return false; ++ if (!target || target->isDead()) return false; ++ if (IsTank() && hp > 35) return false; ++ if (hp > 50 && me->GetShapeshiftForm() != FORM_NONE) return false; //do not waste heal if in feral or so ++ if (Rand() > 50 + 20*target->IsInCombat() + 50*master->GetMap()->IsRaid() - 50*me->GetShapeshiftForm()) return false; ++ if (me->GetExactDist(target) > 40) return false; ++ ++ if (IsSpellReady(NATURES_SWIFTNESS_1, diff, false) && ++ (hp < 15 || (hp < 35 && target->getAttackers().size() > 2)) && ++ (target->IsInCombat() || !target->getAttackers().empty())) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (doCast(me, GetSpell(NATURES_SWIFTNESS_1)) && RefreshAura(CRIT_50, 2)) ++ { ++ swiftness = true; ++ if (doCast(target, GetSpell(HEALING_TOUCH_1), true)) ++ Heal_Timer = 3000; ++ return true; ++ } ++ } ++ if (IsSpellReady(SWIFTMEND_1, diff, false, 3000) && ++ (hp < 25 || GetLostHP(target) > 5000) && ++ (HasAuraName(target, REGROWTH_1) || HasAuraName(target, REJUVENATION_1))) ++ { ++ if (doCast(target, GetSpell(SWIFTMEND_1))) ++ { ++ if (GetHealthPCT(target) > 75) ++ return true; ++ else if (!target->getAttackers().empty()) ++ { ++ if (doCast(target, GetSpell(REGROWTH_1))) ++ { ++ GC_Timer = 300; ++ return true; ++ } ++ } ++ } ++ } ++ if (hp > 35 && (hp < 75 || GetLostHP(target) > 3000) && Heal_Timer <= diff && GetSpell(NOURISH_1)) ++ { ++ switch (urand(1,3)) ++ { ++ case 1: ++ case 2: ++ if (doCast(target, GetSpell(NOURISH_1))) ++ { ++ Heal_Timer = 3000; ++ return true; ++ } ++ break; ++ case 3: ++ if (doCast(target, GetSpell(HEALING_TOUCH_1))) ++ { ++ Heal_Timer = 3000; ++ return true; ++ } ++ break; ++ } ++ } ++ //maintain HoTs ++ Unit* u = target->GetVictim(); ++ Creature* boss = u && u->ToCreature() && u->ToCreature()->isWorldBoss() ? u->ToCreature() : NULL; ++ bool tanking = IsTank(target) && boss; ++ bool regrowth = IsSpellReady(REGROWTH_1, diff); ++ if ( ( (hp < 80 || GetLostHP(target) > 3500 || tanking) && ++ regrowth && !HasAuraName(target, REGROWTH_1, me->GetGUID()) ) ++ || ++ (HasAuraName(target, REGROWTH_1, me->GetGUID()) && HasAuraName(target, REJUVENATION_1, me->GetGUID()) && ++ (hp < 70 || GetLostHP(target) > 3000) && regrowth)) ++ { ++ if (doCast(target, GetSpell(REGROWTH_1))) ++ return true; ++ } ++ if (GetSpell(REJUVENATION_1) && GC_Timer <= diff && hp > 25 && ++ (hp < 90 || GetLostHP(target) > 2000 || tanking) && ++ !HasAuraName(target, REJUVENATION_1, me->GetGUID())) ++ { ++ if (doCast(target, GetSpell(REJUVENATION_1))) ++ { ++ if (!target->getAttackers().empty() && (hp < 75 || GetLostHP(target) > 4000)) ++ if (IsSpellReady(SWIFTMEND_1, diff, false) && doCast(target, GetSpell(SWIFTMEND_1))) ++ {} ++ GC_Timer = 500; ++ return true; ++ } ++ } ++ if (IsSpellReady(LIFEBLOOM_1, diff) && ++ ((hp < 85 && hp > 40) || (hp > 70 && tanking) || ++ (hp < 70 && hp > 25 && HasAuraName(target, REGROWTH_1) && HasAuraName(target, REJUVENATION_1)) || ++ (GetLostHP(target) > 1500 && hp > 35))) ++ { ++ Aura* bloom = target->GetAura(GetSpell(LIFEBLOOM_1), me->GetGUID()); ++ if ((!bloom || bloom->GetStackAmount() < 3) && doCast(target, GetSpell(LIFEBLOOM_1))) ++ return true; ++ } ++ if (hp > 30 && (hp < 70 || GetLostHP(target) > 3000) && Heal_Timer <= diff && ++ doCast(target, GetSpell(HEALING_TOUCH_1))) ++ { ++ Heal_Timer = 3000; ++ return true; ++ } ++ return false; ++ } ++ ++ bool BuffTarget(Unit* target, uint32 diff) ++ { ++ if (GC_Timer > diff || Rand() > 20) return false; ++ if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; ++ if (target && target->IsAlive() && me->GetExactDist(target) < 30) ++ { ++ if (uint32 MARK_OF_THE_WILD = GetSpell(MARK_OF_THE_WILD_1)) ++ if (!HasAuraName(target, MARK_OF_THE_WILD_1)) ++ if (doCast(target, MARK_OF_THE_WILD)) ++ return true; ++ if (uint32 THORNS = GetSpell(THORNS_1)) ++ if (!HasAuraName(target, THORNS_1)) ++ if (doCast(target, THORNS)) ++ return true; ++ } ++ return false; ++ } ++ ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || me->IsMounted() || IsCasting()) ++ return; ++ ++ RezGroup(GetSpell(REVIVE_1), master); ++ ++ //if (Feasting()) return; ++ ++ //if (BuffTarget(master, diff)) ++ //{ ++ // /*GC_Timer = 800;*/ ++ // return; ++ //} ++ //if (BuffTarget(me, diff)) ++ //{ ++ // /*GC_Timer = 800;*/ ++ // return; ++ //} ++ } ++ ++ void doInnervate(uint32 diff, uint8 minmanaval = 30) ++ { ++ if (!IsSpellReady(INNERVATE_1, diff) || Rand() > 15) ++ return; ++ if (me->GetShapeshiftForm() != FORM_NONE && (IsTank() || me->getAttackers().size() > 3)) ++ return; ++ ++ uint32 INNERVATE = GetSpell(INNERVATE_1); ++ Unit* iTarget = NULL; ++ ++ if (master->IsInCombat() && master->getPowerType() == POWER_MANA && ++ GetManaPCT(master) < 20 && !master->HasAura(INNERVATE)) ++ iTarget = master; ++ else if (me->IsInCombat() && me->getPowerType() == POWER_MANA && ++ GetManaPCT(me) < 20 && !me->HasAura(INNERVATE)) ++ iTarget = me; ++ ++ if (!IAmFree()) ++ { ++ Group* group = master->GetGroup(); ++ if (!iTarget && !group) //first check master's bots ++ { ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ Creature* bot = itr->second; ++ if (!bot || !bot->IsInCombat() || bot->isDead()) continue; ++ if (me->GetExactDist(bot) > 30) continue; ++ if (bot->getPowerType() != POWER_MANA) continue; ++ if (GetManaPCT(bot) < minmanaval && !bot->HasAura(INNERVATE)) ++ { ++ iTarget = bot; ++ break; ++ } ++ } ++ } ++ if (!iTarget && group) //cycle through player members... ++ { ++ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL || !tPlayer->IsInWorld() || !tPlayer->IsInCombat() || tPlayer->isDead()) continue; ++ if (me->GetExactDist(tPlayer) > 30) continue; ++ if (tPlayer->getPowerType() != POWER_MANA) continue; ++ if (GetManaPCT(tPlayer) < minmanaval && !tPlayer->HasAura(INNERVATE)) ++ { ++ iTarget = tPlayer; ++ break; ++ } ++ if (iTarget) ++ break; ++ } ++ } ++ if (!iTarget && group) //... and their bots. ++ { ++ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (tPlayer == NULL || !tPlayer->HaveBot()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (!bot || bot->isDead()) continue; ++ if (me->GetExactDist(bot) > 30) continue; ++ if (bot->getPowerType() != POWER_MANA) continue; ++ if (GetManaPCT(bot) < minmanaval && !bot->HasAura(INNERVATE)) ++ { ++ iTarget = bot; ++ break; ++ } ++ } ++ if (iTarget) ++ break; ++ } ++ } ++ } ++ ++ if (iTarget && doCast(iTarget, INNERVATE)) ++ { ++ if (iTarget->GetTypeId() == TYPEID_PLAYER) ++ BotWhisper("Innervate on You!", iTarget->ToPlayer()); ++ else if (!IAmFree()) ++ { ++ std::ostringstream msg; ++ msg << "Innervate on " << (iTarget == me ? "myself" : iTarget->GetName()) << '!'; ++ BotWhisper(msg.str().c_str(), master); ++ } ++ ++ return; ++ } ++ ++ SetSpellCooldown(INNERVATE_1, 3000); //fail ++ } ++ ++ void CheckRoots(uint32 diff) ++ { ++ if (GC_Timer > diff || Rand() > 35) return; ++ if (me->GetShapeshiftForm() != FORM_NONE) return; ++ uint32 ENTANGLING_ROOTS = GetSpell(ENTANGLING_ROOTS_1); ++ if (!ENTANGLING_ROOTS) return; ++ if (FindAffectedTarget(ENTANGLING_ROOTS, me->GetGUID(), 60)) return; ++ if (Unit* target = FindRootTarget(30, ENTANGLING_ROOTS)) ++ if (doCast(target, ENTANGLING_ROOTS)) ++ return; ++ } ++ ++ void CheckBattleRez(uint32 diff) ++ { ++ if (!IsSpellReady(REBIRTH_1, diff, false) || IAmFree() || me->IsMounted() || IsCasting() || Rand() > 10) return; ++ ++ Group* gr = master->GetGroup(); ++ if (!gr) ++ { ++ Unit* target = master; ++ if (master->IsAlive()) return; ++ if (master->isResurrectRequested()) return; //ressurected ++ if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ++ target = (Unit*)master->GetCorpse(); ++ if (!target || !target->IsInWorld()) ++ return; ++ if (me->GetExactDist(target) > 30) ++ { ++ me->GetMotionMaster()->MovePoint(master->GetMapId(), *target); ++ SetSpellCooldown(REBIRTH_1, 1500); ++ return; ++ } ++ else if (!target->IsWithinLOSInMap(me)) ++ me->Relocate(*target); ++ ++ if (doCast(target, GetSpell(REBIRTH_1))) //rezzing ++ BotWhisper("Rezzing You", master); ++ ++ return; ++ } ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ Unit* target = tPlayer; ++ if (!tPlayer || tPlayer->IsAlive()) continue; ++ if (tPlayer->isResurrectRequested()) continue; //ressurected ++ if (tPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ++ target = (Unit*)tPlayer->GetCorpse(); ++ if (!target || !target->IsInWorld()) continue; ++ if (master->GetMap() != target->FindMap()) continue; ++ if (me->GetExactDist(target) > 30) ++ { ++ me->GetMotionMaster()->MovePoint(target->GetMapId(), *target); ++ SetSpellCooldown(REBIRTH_1, 1500); ++ return; ++ } ++ else if (!target->IsWithinLOSInMap(me)) ++ me->Relocate(*target); ++ ++ if (doCast(target, GetSpell(REBIRTH_1))) //rezzing ++ { ++ BotWhisper("Rezzing You", tPlayer); ++ return; ++ } ++ } ++ } ++ ++ void setStats(uint8 form) ++ { ++ switch (form) ++ { ++ case DRUID_BEAR_FORM: ++ Form = DRUID_BEAR_FORM; ++ break; ++ case DRUID_CAT_FORM: ++ Form = DRUID_CAT_FORM; ++ RefreshAura(ENERGIZE, me->getLevel()/40); ++ break; ++ case BOT_CLASS_DRUID: ++ Form = BOT_STANCE_NONE; ++ SetShouldUpdateStats(); ++ me->setPowerType(POWER_MANA); ++ me->RemoveMovementImpairingAuras(); ++ break; ++ default: ++ return; ++ } ++ ++ SetStats(false); ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const ++ { ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ ++ if (damageinfo.hitOutCome == MELEE_HIT_CRIT) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Primal Fury (white attacks): 100% to gain 5 rage at crit in (Dire) Bear Form ++ if (lvl >= 25 && GetBotStance() == DRUID_BEAR_FORM) ++ me->CastSpell(me, PRIMAL_FURY_EFFECT_ENERGIZE, true); ++ //Predatory Instincts (part 1): 10% additional crit damage bonus for melee attacks in Cat form ++ if (lvl >= 45 && GetBotStance() == DRUID_CAT_FORM) ++ pctbonus += 0.05f; ++ } ++ ++ //Naturalist: 10% bonus damage for all melee attacks ++ if (lvl >= 15) ++ pctbonus += 0.1f; ++ ++ damage = damage * (1.0f + pctbonus); ++ } ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Rend and Tear (part 2): 25% additional critical chance on bleeding targets for Ferocious Bite ++ if (lvl >= 55 && damageinfo.target && damageinfo.target->HasAuraState(AURA_STATE_BLEEDING) && spellId == GetSpell(FEROCIOUS_BITE_1)) ++ aftercrit += 25.f; ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ ////Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost (all) spells ++ //if (lvl >= 21) ++ // pctbonus += 0.25f; ++ } ++ ++ //Feral Instinct: 30% bonus damage for Swipe (Bear) ++ if (lvl >= 15 && spellId == GetSpell(SWIPE_1)) ++ pctbonus += 0.3f; ++ //Savage Fury: 20% bonus damage for Claw, Rake, Mangle (Cat), Mangle (Bear) and Maul ++ if (lvl >= 15 && ++ (spellId == GetSpell(CLAW_1) || ++ spellId == GetSpell(RAKE_1) || ++ spellId == GetSpell(MANGLE_CAT_1) || ++ spellId == GetSpell(MANGLE_BEAR_1) || ++ spellId == GetSpell(MAUL_1))) ++ pctbonus += 0.2f; ++ //Rend and Tear: 20% bonus damage on bleeding targets for Maul and Shred ++ if (lvl >= 55 && damageinfo.target && damageinfo.target->HasAuraState(AURA_STATE_BLEEDING) && ++ (spellId == GetSpell(MAUL_1) || spellId == GetSpell(SHRED_1))) ++ pctbonus += 0.2f; ++ //Naturalist: 10% bonus damage for all melee attacks ++ if (lvl >= 15) ++ pctbonus += 0.1f; ++ ++ //Primal Fury (yellow attacks): 100% to gain 5 rage at crit in (Dire) Bear Form ++ if (lvl >= 25 && crit && GetBotStance() == DRUID_BEAR_FORM) ++ me->CastSpell(me, PRIMAL_FURY_EFFECT_ENERGIZE, true); ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Improved Moonfire ++ if (lvl >= 15 && spellId == GetSpell(MOONFIRE_1)) ++ aftercrit += 10.f; ++ //Nature's Majesty: 4% additional critical chance for Wrath, Starfire and Starfall ++ if (lvl >= 15 && ++ (spellId == GetSpell(WRATH_1) || ++ spellId == GetSpell(STARFIRE_1)/* || ++ spellId == GetSpell(STARFALL_1)*/)) ++ aftercrit += 4.f; ++ //Eclipse (Lunar): 40% additional critical chance for Starfire ++ if (lvl >= 50 && spellId == GetSpell(STARFIRE_1) && me->HasAura(ECLIPSE_LUNAR_BUFF)) ++ aftercrit += 40.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!spell damage is not yet critical and will be multiplied by 1.5 ++ //so we should put here bonus damage mult /1.5 ++ //Vengeance: 100% additional crit damage bonus for Starfire, Starfall, Moonfire and Wrath ++ if (lvl >= 25 && ++ (spellId == GetSpell(STARFIRE_1) || ++ //spellId == GetSpell(STARFALL_1) || ++ spellId == GetSpell(MOONFIRE_1) || ++ spellId == GetSpell(WRATH_1))) ++ pctbonus += 0.333f; ++ } ++ //Brambles: 75% bonus damage for Throns and Entangling Roots ++ if (lvl >= 20 && ++ (spellId == GetSpell(THORNS_1) || ++ spellId == GetSpell(ENTANGLING_ROOTS_1))) ++ pctbonus += 0.75f; ++ //Moonfury: 10% bonus damage for Starfire, Moonfire and Wrath ++ if (lvl >= 35 && ++ (spellId == GetSpell(STARFIRE_1) || ++ spellId == GetSpell(MOONFIRE_1) || ++ spellId == GetSpell(WRATH_1))) ++ pctbonus += 0.1f; ++ //Wrath of Cenarius: 20%/10% Increased spellpower bonus for Starfire/Wrath ++ if (lvl >= 45) ++ { ++ if (spellId == GetSpell(STARFIRE_1)) ++ fdamage += spellpower * 0.2f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * 1.88f * me->CalculateLevelPenalty(spellInfo); ++ if (spellId == GetSpell(WRATH_1)) ++ fdamage += spellpower * 0.1f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * 1.88f * me->CalculateLevelPenalty(spellInfo); ++ } ++ //Eclipse (Solar): 40% bonus damage for Wrath ++ if (lvl >= 50 && spellId == GetSpell(WRATH_1) && me->HasAura(ECLIPSE_SOLAR_BUFF)) ++ pctbonus += 0.4f; ++ //Gale Winds: 30% bonus damage for Hurricane (no Typhon support yet) ++ if (lvl >= 50 && spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(HURRICANE_DAMAGE_1))) ++ pctbonus += 0.3f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ float flat_mod = 0.0f; ++ ++ //Improved Rejuvenation: 15% bonus healing for Rejuvenation ++ if (lvl >= 25 && spellId == GetSpell(REJUVENATION_1)) ++ pctbonus += 0.15f; ++ //Gift of Nature: 10% bonus healing for all spells ++ if (lvl >= 30) ++ pctbonus += 0.1f; ++ //Empowered Touch: 40% bonus (from spellpower) for Healing Touch and 20% bonus (from spellpower) for Nourish ++ if (lvl >= 35) ++ { ++ if (spellId == GetSpell(HEALING_TOUCH_1)) ++ flat_mod += spellpower * 0.4f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ else if (spellId == GetSpell(NOURISH_1)) ++ flat_mod += spellpower * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ } ++ //Empowered Rejuvenation: 20% bonus healing for healing over time effects (20% increase in our case) ++ if (lvl >= 45 && ++ (spellId == GetSpell(TRANQUILITY_1) || ++ spellId == GetSpell(REJUVENATION_1) || ++ spellId == GetSpell(REGROWTH_1) || ++ spellId == GetSpell(LIFEBLOOM_1) || ++ spellId == GetSpell(WILD_GROWTH_1) || ++ spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(TRANQUILITY_HEAL_1)))) ++ pctbonus += 0.2f; ++ //Empowered Touch: 15% bonus (from spirit) for healing spells (taking in consideration increased spirit (Living Spirit: 15%)) ++ if (lvl >= 50) ++ flat_mod += me->GetTotalStatValue(STAT_SPIRIT) * 0.15f * 1.15f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ ++ heal = heal * (1.0f + pctbonus) + flat_mod; ++ } ++ ++ void ApplyClassCritMultiplierHeal(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float aftercrit = 0.0f; ++ ++ //Nature's bounty: 25% additional critical chance for Regrowth ++ if (lvl >= 35 && spellId == GetSpell(REGROWTH_1)) ++ aftercrit += 25.f; ++ ++ crit_chance += aftercrit; ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ //Nature's Splendor: Increased duraion for ++ //Moonfire (3 sec), Rejuvenation (3 sec, let 6), Regrowth (6 sec, let 9), ++ //Insect Swarm (2 sec) and Lifebloom (2 sec, let 4) ++ if (spellId == GetSpell(MOONFIRE_1) || spellId == GetSpell(REJUVENATION_1) || ++ spellId == GetSpell(REGROWTH_1)/* || spellId == GetSpell(INSECT_SWARM_1)*/ || ++ spellId == GetSpell(LIFEBLOOM_1)) ++ { ++ if (me->getLevel() >= 20) ++ { ++ if (Aura* aur = target->GetAura(spellId)) ++ { ++ uint32 dur = aur->GetDuration(); ++ ++ switch (sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id) ++ { ++ case MOONFIRE_1: ++ dur += 3000; ++ break; ++ case REJUVENATION_1: ++ dur += 6000; ++ break; ++ case REGROWTH_1: ++ dur += 9000; ++ break; ++ //case INSECT_SWARM_1: ++ // dur += 3000; ++ // break; ++ case LIFEBLOOM_1: ++ dur += 4000; ++ break; ++ default: ++ break; ++ } ++ ++ aur->SetDuration(dur); ++ aur->SetMaxDuration(dur); ++ } ++ } ++ } ++ ++ if (spellId == GetSpell(THORNS_1)) ++ { ++ //30 min duration for Thorns ++ if (Aura* thorn = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = 30 * MINUTE * IN_MILLISECONDS; ++ thorn->SetDuration(dur); ++ thorn->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(MARK_OF_THE_WILD_1)) ++ { ++ //1 hour duration for Mark of the Wild ++ if (Aura* mark = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = 1 * HOUR * IN_MILLISECONDS; ++ mark->SetDuration(dur); ++ mark->SetMaxDuration(dur); ++ } ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ //Eclipse (helper): cooldown ++ if (spellId == ECLIPSE_SOLAR_BUFF || spellId == ECLIPSE_LUNAR_BUFF) ++ SetSpellCooldown(spellId, 30000); ++ //Forms helper ++ if (spellId == GetSpell(CAT_FORM_1) || spellId == GetSpell(BEAR_FORM_1)) ++ { ++ if (spellId == GetSpell(BEAR_FORM_1)) ++ setStats(DRUID_BEAR_FORM); ++ if (spellId == GetSpell(CAT_FORM_1)) ++ setStats(DRUID_CAT_FORM); ++ ++ me->CastSpell(me, LEADER_OF_THE_PACK_BUFF, true); ++ } ++ ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ Heal_Timer = 0; ++ formtimer = 0; ++ Form = BOT_STANCE_NONE; ++ ++ clearcast = false; ++ swiftness = false; ++ ++ power = POWER_MANA; ++ ++ rage = 0; ++ ++ rageIncomeMult = sWorld->getRate(RATE_POWER_RAGE_INCOME); ++ rageLossMult = sWorld->getRate(RATE_POWER_RAGE_LOSS); ++ ++ ragetimer = 0; ++ ragetimer2 = 0; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (Heal_Timer > diff) Heal_Timer -= diff; ++ if (formtimer > diff) formtimer -= diff; ++ if (ragetimer > diff) ragetimer -= diff; ++ if (ragetimer2 > diff) ragetimer2 -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ ++ InitSpellMap(WARSTOMP_1, true); ++ ++ InitSpellMap(MARK_OF_THE_WILD_1); ++ InitSpellMap(THORNS_1); ++ InitSpellMap(HEALING_TOUCH_1); ++ InitSpellMap(REGROWTH_1); ++ InitSpellMap(REJUVENATION_1); ++ InitSpellMap(LIFEBLOOM_1); ++ InitSpellMap(NOURISH_1); ++ /*tal*/lvl >= 60 ? InitSpellMap(WILD_GROWTH_1) : RemoveSpell(WILD_GROWTH_1); ++ /*tal*/lvl >= 40 ? InitSpellMap(SWIFTMEND_1) : RemoveSpell(SWIFTMEND_1); ++ InitSpellMap(TRANQUILITY_1); ++ InitSpellMap(REVIVE_1); ++ InitSpellMap(REBIRTH_1); ++ InitSpellMap(BEAR_FORM_1); ++ InitSpellMap(SWIPE_1); ++ /*tal*/lvl >= 50 ? InitSpellMap(MANGLE_BEAR_1) : RemoveSpell(MANGLE_BEAR_1); ++ InitSpellMap(BASH_1); ++ InitSpellMap(CAT_FORM_1); ++ InitSpellMap(CLAW_1); ++ InitSpellMap(RAKE_1); ++ InitSpellMap(SHRED_1); ++ InitSpellMap(RIP_1); ++ /*tal*/lvl >= 50 ? InitSpellMap(MANGLE_CAT_1) : RemoveSpell(MANGLE_CAT_1); ++ InitSpellMap(MOONFIRE_1); ++ InitSpellMap(STARFIRE_1); ++ InitSpellMap(WRATH_1); ++ InitSpellMap(HURRICANE_1); ++ InitSpellMap(FAERIE_FIRE_1); ++ InitSpellMap(CURE_POISON_1); ++ InitSpellMap(INNERVATE_1); ++ InitSpellMap(ENTANGLING_ROOTS_1); ++ /*tal*/lvl >= 30 ? InitSpellMap(NATURES_SWIFTNESS_1) : RemoveSpell(NATURES_SWIFTNESS_1); ++ ++ /*SPECIAL*/InitSpellMap(ECLIPSE_SOLAR_BUFF, true); ++ /*SPECIAL*/InitSpellMap(ECLIPSE_LUNAR_BUFF, true); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ //RefreshAura(SPELLDMG2, level >= 78 ? 3 : level >= 65 ? 2 : level >= 50 ? 1 : 0); ++ RefreshAura(NATURAL_PERFECTION3, level >= 45 ? 1 : 0); ++ RefreshAura(NATURAL_PERFECTION2, level >= 43 && level < 45 ? 1 : 0); ++ RefreshAura(NATURAL_PERFECTION1, level >= 41 && level < 43 ? 1 : 0); ++ RefreshAura(LIVING_SEED3, level >= 50 ? 1 : 0); ++ RefreshAura(LIVING_SEED2, level >= 48 && level < 50 ? 1 : 0); ++ RefreshAura(LIVING_SEED1, level >= 46 && level < 48 ? 1 : 0); ++ RefreshAura(REVITALIZE3, level >= 55 ? 3 : 0); ++ RefreshAura(REVITALIZE2, level >= 53 && level < 55 ? 2 : 0); ++ RefreshAura(REVITALIZE1, level >= 51 && level < 53 ? 2 : 0); ++ RefreshAura(GIFT_OF_THE_EARTHMOTHER, level >= 55 ? 1 : 0); ++ RefreshAura(OMEN_OF_CLARITY, level >= 70 ? 3 : level >= 40 ? 2 : level >= 20 ? 1 : 0); ++ RefreshAura(GLYPH_SWIFTMEND, level >= 45 ? 1 : 0); ++ RefreshAura(GLYPH_INNERVATE, level >= 40 ? 1 : 0); ++ RefreshAura(NATURESGRACE, level >= 20 ? 1 : 0); ++ RefreshAura(ECLIPSE, level >= 50 ? 1 : 0); ++ RefreshAura(EARTH_AND_MOON, level >= 55 ? 1 : 0); ++ RefreshAura(SURVIVAL_OF_THE_FITTEST, level >= 55 ? 1 : 0); ++ RefreshAura(HEART_OF_THE_WILD, level >= 35 ? 1 : 0); ++ RefreshAura(NATURAL_REACTION, level >= 35 ? 1 : 0); ++ RefreshAura(INFECTED_WOUNDS, level >= 45 ? 1 : 0); ++ RefreshAura(FUROR, level >= 10 ? 1 : 0); ++ RefreshAura(T9_RESTO_P4_BONUS, level >= 78 ? 1 : 0); ++ RefreshAura(T8_RESTO_P4_BONUS, level >= 78 ? 1 : 0); ++ RefreshAura(T9_BALANCE_P2_BONUS, level >= 78 ? 1 : 0); ++ RefreshAura(T10_BALANCE_P2_BONUS, level >= 78 ? 1 : 0); ++ RefreshAura(T10_BALANCE_P4_BONUS, level >= 78 ? 1 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case MARK_OF_THE_WILD_1: ++ case THORNS_1: ++ case HEALING_TOUCH_1: ++ case REJUVENATION_1: ++ case LIFEBLOOM_1: ++ case REGROWTH_1: ++ case NOURISH_1: ++ case WILD_GROWTH_1: ++ case SWIFTMEND_1: ++ case TRANQUILITY_1: ++ case CURE_POISON_1: ++ case INNERVATE_1: ++ case BEAR_FORM_1: ++ case CAT_FORM_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ //Timers/other ++/*Heal*/uint32 Heal_Timer; ++/*Misc*/uint32 formtimer, ragetimer, ragetimer2; ++/*Form*/uint8 Form; ++/*Chck*/bool clearcast, swiftness; ++/*Misc*/Powers power; uint32 rage; ++/*Misc*/float rageIncomeMult, rageLossMult; ++ ++ enum DruidBaseSpells ++ { ++ MARK_OF_THE_WILD_1 = 1126, ++ THORNS_1 = 467, ++ HEALING_TOUCH_1 = 5185, ++ REGROWTH_1 = 8936, ++ REJUVENATION_1 = 774, ++ LIFEBLOOM_1 = 33763, ++ NOURISH_1 = 50464, ++ /*tal*/WILD_GROWTH_1 = 48438, ++ /*tal*/SWIFTMEND_1 = 18562, ++ TRANQUILITY_1 = 740, ++ REVIVE_1 = 50769, ++ REBIRTH_1 = 20484, ++ BEAR_FORM_1 = 5487, ++ SWIPE_1 = 779, ++ /*tal*/MANGLE_BEAR_1 = 33878, ++ BASH_1 = 5211, //NYI ++ MAUL_1 = 6807, //NYI ++ CAT_FORM_1 = 768, ++ CLAW_1 = 1082, ++ RAKE_1 = 1822, ++ SHRED_1 = 5221, ++ RIP_1 = 1079, ++ /*tal*/MANGLE_CAT_1 = 33876, ++ FEROCIOUS_BITE_1 = 22568, //NYI ++ MOONFIRE_1 = 8921, ++ STARFIRE_1 = 2912, ++ WRATH_1 = 5176, ++ HURRICANE_1 = 16914, ++ FAERIE_FIRE_1 = 770, ++ CURE_POISON_1 = 8946, ++ INNERVATE_1 = 29166, ++ ENTANGLING_ROOTS_1 = 339, ++ /*tal*/NATURES_SWIFTNESS_1 = 17116, ++ WARSTOMP_1 = 20549 ++ }; ++ enum DruidPassives ++ { ++ //Talents ++ OMEN_OF_CLARITY = 16864,//clearcast ++ NATURESGRACE = 61346,//haste 20% for 3 sec ++ NATURAL_PERFECTION1 = 33881, ++ NATURAL_PERFECTION2 = 33882, ++ NATURAL_PERFECTION3 = 33883, ++ LIVING_SEED1 = 48496,//rank 1 ++ LIVING_SEED2 = 48499,//rank 2 ++ LIVING_SEED3 = 48500,//rank 3 ++ REVITALIZE1 = 48539,//rank 1 ++ REVITALIZE2 = 48544,//rank 2 ++ REVITALIZE3 = 48545,//rank 3 ++ GIFT_OF_THE_EARTHMOTHER = 51183,//rank 5 ++ ECLIPSE = 48525,//rank 3 ++ EARTH_AND_MOON = 48511,//rank 3 ++ SURVIVAL_OF_THE_FITTEST = 33856,//rank 3 ++ HEART_OF_THE_WILD = 24894,//rank 5 ++ FUROR = 17061,//rank 5 ++ NATURAL_REACTION = 57881,//rank 3 ++ INFECTED_WOUNDS = 48485,//rank 3 ++ //Glyphs ++ GLYPH_SWIFTMEND = 54824,//no consumption ++ GLYPH_INNERVATE = 54832,//self regen ++ //other ++ T9_RESTO_P4_BONUS = 67128,//rejuve crits ++ T8_RESTO_P4_BONUS = 64760,//rejuve init heal ++ T9_BALANCE_P2_BONUS = 67125,//moonfire crits ++ T10_BALANCE_P2_BONUS = 70718,//omen of doom (15%) ++ T10_BALANCE_P4_BONUS = 70723,//Languish(DOT) ++ //SPELLDMG/*Arcane Instability-mage*/ = 15060,//rank3 3% dam/crit ++ //SPELLDMG2/*Earth and Moon - druid*/ = 48511,//rank3 6% dam ++ ENERGIZE = 27787,//Rogue Armor Energize (chance: +35 energy on hit) ++ CRIT_50 = 23434 //50% spell crit ++ }; ++ enum DruidSpecial ++ { ++ HURRICANE_DAMAGE_1 = 42231, ++ TRANQUILITY_HEAL_1 = 44203, ++ /*Talent*/LEADER_OF_THE_PACK_BUFF = 24932, ++ //NATURESGRACEBUFF = 16886, ++ ECLIPSE_SOLAR_BUFF = 48517,// from Starfire to Wrath ++ ECLIPSE_LUNAR_BUFF = 48518,// from Wrath to Starfire ++ OMEN_OF_CLARITY_BUFF = 16870,//434 deprecated ++ ++ PRIMAL_FURY_EFFECT_ENERGIZE = 16959 //5 rage ++ }; ++ }; ++}; ++ ++void AddSC_druid_bot() ++{ ++ new druid_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_hunter_ai.cpp b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp +new file mode 100644 +index 0000000..8fa05af +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp +@@ -0,0 +1,1086 @@ ++#include "bot_ai.h" ++//#include "botmgr.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "Spell.h" ++#include "SpellAuras.h" ++/* ++Hunter NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - around 35% ++TODO: ++*/ ++class hunter_bot : public CreatureScript ++{ ++public: ++ hunter_bot() : CreatureScript("hunter_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new hunter_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct hunter_botAI : public bot_minion_ai ++ { ++ hunter_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_HUNTER; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_HUNTER) != SPELL_CAST_OK) ++ return false; ++ ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ void DoNonCombatActions(uint32 /*diff*/) { } ++ ++ void Counter(uint32 diff) ++ { ++ if (IsCasting() || Rand() > 35) ++ return; ++ ++ Unit* target = NULL; ++ ++ if (IsSpellReady(SCATTER_SHOT_1, diff, false, 10000) && HasRole(BOT_ROLE_DPS) && Rand() < 40) ++ { ++ target = FindCastingTarget(15, 0, false, GetSpell(SCATTER_SHOT_1)); ++ temptimer = GC_Timer; ++ if (target && doCast(target, GetSpell(SCATTER_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ if (!target && IsSpellReady(WYVERN_STING_1, diff, true, 10000) && HasRole(BOT_ROLE_DPS) && Rand() < 70) ++ { ++ target = FindCastingTarget(35, 5, false, GetSpell(WYVERN_STING_1)); ++ if (target && doCast(target, GetSpell(WYVERN_STING_1))) ++ return; ++ } ++ //if (!target && FREEZING_ARROW && Trap_cd <= 10000 && Rand() < 40) ++ //{ ++ // target = FindCastingTarget(40, 0, false, FREEZING_ARROW); ++ // temptimer = GC_Timer; ++ // if (target && doCast(target, FREEZING_ARROW)) ++ // { ++ // Trap_cd = 20000; ++ // GC_Timer = temptimer; ++ // return; ++ // } ++ //} ++ if (!target && IsSpellReady(SCARE_BEAST_1, diff, true, 7500) && Rand() < 35) ++ { ++ target = FindCastingTarget(30, 0, false, GetSpell(SCARE_BEAST_1)); ++ if (target && doCast(target, GetSpell(SCARE_BEAST_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ if (!target && IsSpellReady(SILENCING_SHOT_1, diff, false, 7500) && Rand() < 30) ++ { ++ target = FindCastingTarget(35, 5, false, GetSpell(SILENCING_SHOT_1)); ++ temptimer = GC_Timer; ++ if (target && doCast(target, GetSpell(SILENCING_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ void CheckScatter(uint32 diff) ++ { ++ if (!IsSpellReady(SCATTER_SHOT_1, diff, false) || !HasRole(BOT_ROLE_DPS) || IsCasting() || Rand() > 50) ++ return; ++ ++ if (Unit* target = FindStunTarget(15)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(SCATTER_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(SCATTER_SHOT_1, 1000); //fail ++ } ++ ++ void CheckWyvernSting(uint32 diff) ++ { ++ if (!IsSpellReady(WYVERN_STING_1, diff, false) || !HasRole(BOT_ROLE_DPS) || IsCasting() || Rand() > 50) ++ return; ++ ++ if (Unit* target = FindStunTarget(35)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(WYVERN_STING_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(WYVERN_STING_1, 1000); //fail ++ } ++ ++ void CheckFreezingArrow(uint32 diff) ++ { ++ //Freezing Trap shares cooldown with traps ++ if (!GetSpell(FREEZING_ARROW_1) || Trap_cd > diff || IsCasting() || Rand() > 75) ++ return; ++ ++ if (Unit* target = FindStunTarget(40)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(FREEZING_ARROW_1))) ++ { ++ GC_Timer = temptimer; ++ Trap_cd = 20000; ++ return; ++ } ++ } ++ ++ Trap_cd = 1000; //fail ++ } ++ ++ void CheckTraps(uint32 diff) ++ { ++ if (!GetSpell(FROST_TRAP_1) || Trap_cd > diff || IsCasting() || Rand() > 90) ++ return; ++ ++ Unit* target = me->GetVictim(); ++ if (!(target && me->GetDistance(target) < 6 && me->HasInArc(M_PI, target) && ++ IsInBotParty(target->GetVictim()))) ++ target = NULL; ++ if (!target) ++ target = FindAOETarget(3.f, true); ++ ++ if (target) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(FROST_TRAP_1))) ++ { ++ GC_Timer = temptimer; ++ Trap_cd = 20000; ++ return; ++ } ++ } ++ ++ Trap_cd = 500; //fail ++ } ++ ++ void CheckScare(uint32 diff) ++ { ++ if (!IsSpellReady(SCARE_BEAST_1, diff) || IsCasting() || Rand() > 35) ++ return; ++ ++ if (Unit* scareTarget = FindFearTarget()) ++ { ++ if (doCast(scareTarget, GetSpell(SCARE_BEAST_1), true)) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(SCARE_BEAST_1, 1500); //fail ++ } ++ ++ void CheckAspects(uint32 diff) ++ { ++ if (Aspect_Timer > diff || GC_Timer > diff || IsCasting() || Feasting() || Rand() > 35) ++ return; ++ ++ uint32 ASPECT_OF_THE_VIPER = GetSpell(ASPECT_OF_THE_VIPER_1); ++ uint32 ASPECT_OF_THE_PACK = GetSpell(ASPECT_OF_THE_PACK_1); ++ if (!ASPECT_OF_THE_VIPER && !ASPECT_OF_THE_PACK) ++ return; ++ ++ uint32 needaspect = 0; ++ uint8 pct = GetManaPCT(me); ++ ++ if (ASPECT_OF_THE_VIPER && pct < 25) ++ needaspect = ASPECT_OF_THE_VIPER; ++ else if (ASPECT_OF_THE_PACK && (pct > 70 || !Aspect)) ++ needaspect = ASPECT_OF_THE_PACK; ++ ++ if (!needaspect || (needaspect == Aspect && HasAuraName(me, needaspect))) ++ { ++ Aspect_Timer = 2000; ++ return; ++ } ++ ++ if (Aspect) ++ me->RemoveAurasDueToSpell(Aspect); ++ ++ if (doCast(me, needaspect)) ++ { ++ Aspect = needaspect; ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ void doDefend(uint32 diff) ++ { ++ //No GCD abilities ++ if (!IsSpellReady(FEIGN_DEATH_1, diff, false) && !IsSpellReady(DETERRENCE_1, diff, false)) ++ return; ++ if (IsTank() || Rand() > 35) ++ return; ++ ++ AttackerSet b_attackers = me->getAttackers(); ++ bool cast = false; ++ ++ if (b_attackers.size() == 1) ++ { ++ if (Creature* cre = (*b_attackers.begin())->ToCreature()) ++ { ++ if (cre->isWorldBoss() || cre->IsDungeonBoss() || cre->GetMaxHealth() > me->GetMaxHealth() * 10) ++ { ++ cast = true; ++ //need feign death ++ SetSpellCooldown(DETERRENCE_1, std::max(GetSpellCooldown(DETERRENCE_1), diff + 500)); ++ } ++ } ++ } ++ else ++ cast = (uint8(b_attackers.size()) > (GetHealthPCT(me) > 20 ? 1 : 0)); ++ ++ if (!cast || b_attackers.empty()) ++ { ++ //delay next try ++ SetSpellCooldown(FEIGN_DEATH_1, std::max(GetSpellCooldown(FEIGN_DEATH_1), 1000)); ++ SetSpellCooldown(DETERRENCE_1, std::max(GetSpellCooldown(DETERRENCE_1), 1000)); ++ return; ++ } ++ ++ if (IsSpellReady(FEIGN_DEATH_1, diff, false) && (*b_attackers.begin())->getAttackers().size() > 1) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(FEIGN_DEATH_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ if (IsSpellReady(DETERRENCE_1, diff, false)) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(DETERRENCE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ void CheckAutoShot() ++ { ++ //Auto Shot is always present ++ //if (!AUTO_SHOT) ++ // return; ++ ++ Unit* target = me->GetVictim(); ++ if (!target) ++ return; ++ ++ if (!HasRole(BOT_ROLE_DPS)) ++ return; ++ ++ if (IsCasting()) ++ return; ++ ++ if (Spell* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) ++ { ++ if (shot->GetSpellInfo()->Id != AUTO_SHOT_1) ++ { ++ ////casting some other autorepeat spell, wtf? ++ //TC_LOG_ERROR("entities.player", "CheckAutoShot(): Bot %s is casting autorepeatable spell %u (%s) during check!", ++ // me->GetName().c_str(), shot->GetSpellInfo()->Id, shot->GetSpellInfo()->SpellName[0]); ++ return; ++ } ++ else if (shot->m_targets.GetUnitTarget() != target) ++ me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); ++ } ++ ++ float dist = me->GetExactDist(target); ++ if (dist < 5 || dist > 35) ++ return; ++ ++ temptimer = GC_Timer; ++ if (doCast(target, AUTO_SHOT_1)) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ void CheckTranquil(uint32 diff) ++ { ++ if (!IsSpellReady(TRANQ_SHOT_1, diff, false) || Rand() > 35) //No GCD ++ return; ++ ++ //First check current target ++ Unit* target = me->GetVictim(); ++ if (target) ++ { ++ Unit::AuraMap const &auras = target->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) ++ { ++ SpellInfo const* spellInfo = itr->second->GetSpellInfo(); ++ if (spellInfo->Dispel != DISPEL_MAGIC && spellInfo->Dispel != DISPEL_ENRAGE) continue; ++ if (spellInfo->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; ++ //if (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; ++ AuraApplication const* aurApp = itr->second->GetApplicationOfTarget(target->GetGUID()); ++ if (aurApp && aurApp->IsPositive()) ++ { ++ temptimer = GC_Timer; ++ //me->InterruptNonMeleeSpells(true, AUTO_SHOT); ++ if (doCast(target, GetSpell(TRANQ_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ ++ target = FindTranquilTarget(); ++ if (target) ++ { ++ temptimer = GC_Timer; ++ //me->InterruptNonMeleeSpells(true, AUTO_SHOT); ++ if (doCast(target, GetSpell(TRANQ_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(TRANQ_SHOT_1, 2000); //fail ++ } ++ ++ void CheckSilence(uint32 diff) ++ { ++ if (!IsSpellReady(SILENCING_SHOT_1, diff, false) || IsCasting() || Rand() > 50) //No GCD ++ return; ++ ++ Unit* target = me->GetVictim(); ++ if (target && target->IsNonMeleeSpellCast(false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(SILENCING_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ //return; ++ } ++ ++ return; //succeed or failed, our hightest priority target casts spell and it must be interrupted above all else ++ } ++ ++ target = FindCastingTarget(35, 5); ++ if (target) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(SILENCING_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(SILENCING_SHOT_1, 1000); //fail ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ if (Potion_cd <= diff && GetManaPCT(me) < 40) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ //Deterrence check ++ if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) ++ { ++ if (!me->isMoving()) ++ GetInPosition(true); ++ return; ++ } ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ else ++ doDefend(diff); ++ ++ CheckAspects(diff); ++ ++ if (master->IsInCombat() || me->IsInCombat()) ++ { ++ CheckTranquil(diff); ++ CheckSilence(diff); ++ } ++ ++ if (!CheckAttackTarget(BOT_CLASS_HUNTER)) ++ { ++ me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); ++ return; ++ } ++ ++ DoRangedAttack(diff); ++ } ++ ++ void DoRangedAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ Counter(diff); ++ ++ CheckAutoShot(); ++ CheckScare(diff); ++ CheckScatter(diff); ++ ++ //AttackerSet m_attackers = master->getAttackers(); ++ //AttackerSet b_attackers = me->getAttackers(); ++ //float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ ++ //special ++ if (IsSpellReady(SCATTER_SHOT_1, diff, false) && HasRole(BOT_ROLE_DPS) && meleedist < 15 && Rand() < 60) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(SCATTER_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); ++ me->AttackStop(); ++ return; ++ } ++ } ++ ++ //MELEE SECTION ++ if (!(meleedist > 5)) ++ { ++ //TRAPS ++ CheckTraps(diff); ++ ++ //RAPTOR STRIKE ++ if (IsSpellReady(RAPTOR_STRIKE_1, diff, false) && HasRole(BOT_ROLE_DPS) && Rand() < 50) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(RAPTOR_STRIKE_1), true)) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //WING CLIP ++ if (IsSpellReady(WING_CLIP_1, diff) && (!IsTank() || opponent->isMoving()) && ++ Rand() < 80 && !opponent->HasAuraWithMechanic(/*(1<IsInCombat() && !IsTank() && ++ !me->getAttackers().empty() && me->HasInArc(M_PI, *me->getAttackers().begin()) && Rand() < 30) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(DISENGAGE_1))) ++ GC_Timer = temptimer; ++ } ++ ++ return; //don't try to do anything else in melee ++ } ++ ++ //RANGED SECTION ++ ++ //HUNTERS MARK ++ if (IsSpellReady(HUNTERS_MARK_1, diff, false) && Rand() < 25 && ++ !HasAuraName(opponent, HUNTERS_MARK_1)) //100 yd range so don't check it ++ { ++ //Hunter's Mark has exclusive GCD ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(HUNTERS_MARK_1))) ++ { ++ markTarget = opponent; ++ GC_Timer = temptimer; ++ //return; ++ } ++ } ++ ++ //FREEZING ARROW ++ CheckFreezingArrow(diff); ++ CheckWyvernSting(diff); ++ ++ //attack range check 1 ++ if (!(meleedist < 45)) ++ return; ++ ++ //KILL SHOT //No GCD ++ if (IsSpellReady(KILL_SHOT_1, diff, false) && HasRole(BOT_ROLE_DPS) && GetHealthPCT(opponent) < 20 && Rand() < 95) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(KILL_SHOT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ //attack range check 2 ++ if (!(meleedist < 35)) ++ return; ++ ++ //CONCUSSIVE SHOT ++ if (IsSpellReady(CONCUSSIVE_SHOT_1, diff) && Rand() < 80) ++ { ++ if (doCast(opponent, GetSpell(CONCUSSIVE_SHOT_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ //DISTRACTING SHOT ++ if (Unit* u = opponent->GetVictim()) ++ { ++ if (IsSpellReady(DISTRACTING_SHOT_1, diff, false) && IsTank() && u != me && !CCed(opponent) && ++ Rand() < 75 && IsInBotParty(u)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(DISTRACTING_SHOT_1))) ++ GC_Timer = temptimer; ++ } ++ } ++ //BLACK ARROW //custom cd condition ++ //Black Arrow shares cooldown with traps, but we'll have it only partially ++ if (IsSpellReady(BLACK_ARROW_1, diff) && Trap_cd <= 10000 && HasRole(BOT_ROLE_DPS) && ++ opponent->GetHealth() > me->GetMaxHealth()/3 && Rand() < 75) ++ { ++ if (doCast(opponent, GetSpell(BLACK_ARROW_1))) ++ { ++ Trap_cd = 20000; ++ return; ++ } ++ } ++ //RAPID FIRE ++ if (IsSpellReady(RAPID_FIRE_1, diff, false) && HasRole(BOT_ROLE_DPS) && ++ opponent->GetHealth() > me->GetMaxHealth() / 2 && Rand() < 25) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(RAPID_FIRE_1))) ++ GC_Timer = temptimer; ++ } ++ //EXPLOSIVE SHOT ++ if (IsSpellReady(EXPLOSIVE_SHOT_1, diff) && HasRole(BOT_ROLE_DPS)) ++ { ++ if (doCast(opponent, GetSpell(EXPLOSIVE_SHOT_1))) ++ return; ++ ++ SetSpellCooldown(EXPLOSIVE_SHOT_1, 500); //fail ++ } ++ //SCORPID STING //custom cd condition ++ if (uint32 SCORPID_STING = GetSpell(SCORPID_STING_1)) ++ { ++ if (GC_Timer <= diff && (ScorpidSting_Timer <= diff || stingTargetGuid != opponent->GetGUID())) ++ { ++ Aura* sSting = opponent->GetAura(SCORPID_STING); ++ if (sSting && sSting->GetDuration() > 3000) ++ { ++ stingTargetGuid = opponent->GetGUID(); ++ SetSpellCooldown(SCORPID_STING_1, 2000); ++ } ++ else if (Rand() < 40 && doCast(opponent, SCORPID_STING)) ++ { ++ stingTargetGuid = opponent->GetGUID(); ++ GC_Timer = 800; ++ return; ++ } ++ } ++ } ++ //CHIMERA SHOT TODO: ++ if (IsSpellReady(CHIMERA_SHOT_1, diff) && HasRole(BOT_ROLE_DPS) && stingTargetGuid == opponent->GetGUID() && ++ !opponent->HasAuraType(SPELL_AURA_MOD_DISARM) && Rand() < 30) ++ { ++ if (doCast(opponent, GetSpell(CHIMERA_SHOT_1))) ++ return; ++ } ++ //MULTI-SHOT ++ if (IsSpellReady(MULTISHOT_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 60) ++ { ++ if (Unit* target = FindSplashTarget(35, opponent)) ++ { ++ if (doCast(target, GetSpell(MULTISHOT_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(MULTISHOT_1, 1000); //fail ++ } ++ //VOLLEY ++ if (IsSpellReady(VOLLEY_1, diff) && HasRole(BOT_ROLE_DPS) && !me->isMoving() && Rand() < 25) ++ { ++ if (Unit* target = FindAOETarget(35, true, false)) ++ { ++ if (doCast(target, GetSpell(VOLLEY_1))) ++ return; ++ } ++ ++ SetSpellCooldown(VOLLEY_1, 1000); //fail ++ } ++ //AIMED SHOT ++ if (IsSpellReady(AIMED_SHOT_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 80) ++ { ++ if (doCast(opponent, GetSpell(AIMED_SHOT_1))) ++ return; ++ } ++ //ARCANE SHOT ++ if (IsSpellReady(ARCANE_SHOT_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 50) ++ { ++ if (doCast(opponent, GetSpell(ARCANE_SHOT_1))) ++ return; ++ } ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ ++ //Glyph of TrueShot Aura (req lvl 40): 10% additional critical chance for Aimed Shot ++ if (lvl >= 40 && spellId == GetSpell(AIMED_SHOT_1)) ++ aftercrit += 10.f; ++ //Improved Barrage: 12% additional critical chance for Multi-Shot and Aimed Shot ++ if (lvl >= 40 && (spellId == GetSpell(AIMED_SHOT_1) || spellId == GetSpell(MULTISHOT_1))) ++ aftercrit += 12.f; ++ //Survival Instincts: 4% additional critical chance for Arcane Shot, Steady Shot and Explosive Shot ++ if (lvl >= 15 && (spellId == GetSpell(ARCANE_SHOT_1)/* || spellId == GetSpell(STEADY_SHOT_1)*/ || spellId == GetSpell(EXPLOSIVE_SHOT_1))) ++ aftercrit += 4.f; ++ //Sniper Training (part 1): 15% additional critical chance for Kill Shot ++ if (lvl >= 50 && spellId == GetSpell(KILL_SHOT_1)) ++ aftercrit += 15.f; ++ //Point of No Escape: 6% additional critical chance on victims of any kind of frost trap (crew this condition) ++ if (lvl >= 50) ++ aftercrit += 6.f; ++ ++ //Savage Strikes: 20% additional critical chance for Raptor Strike, Mongoose Bite and Counterattack ++ if (lvl >= 10 && (spellId == GetSpell(RAPTOR_STRIKE_1)/* || spellId == GetSpell(MONGOOSE_BITE_1) || spellId == GetSpell(COUNTERATTACK_1)*/)) ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Mortal Shots: 30% crit damage bonus for all ranged abilities ++ if (lvl >= 15 && attackType == RANGED_ATTACK) ++ pctbonus += 0.15f; ++ //Marked for Death (part 2): 10% crit damage bonus for Aimed Shot, Arcane Shot, Steady Shot, Kill Shot and Chimera Shot ++ if (lvl >= 55 && ++ (spellId == GetSpell(AIMED_SHOT_1) || ++ spellId == GetSpell(ARCANE_SHOT_1) || ++ //spellId == GetSpell(STEADY_SHOT_1) || ++ spellId == GetSpell(KILL_SHOT_1) || ++ spellId == GetSpell(CHIMERA_SHOT_1))) ++ pctbonus += 0.05f; ++ } ++ ++ //Ranged Weapon Specialization: 5% bonus damage for ranged attacks ++ if (lvl >= 35 && attackType == RANGED_ATTACK) ++ pctbonus += 0.05f; ++ //Improved Arcane Shot: 15% bonus damage for Arcane Shot ++ if (lvl >= 20 && spellId == GetSpell(ARCANE_SHOT_1)) ++ pctbonus += 0.15f; ++ //Rapid Killing (buff): 20% bonus damage for Aimed Shot, Arcane Shot or Chimera Shot (removed in SpellHitTarget()) ++ if (lvl >= 20 && (spellId == GetSpell(AIMED_SHOT_1) || spellId == GetSpell(ARCANE_SHOT_1) || spellId == GetSpell(CHIMERA_SHOT_1)) && ++ me->HasAura(RAPID_KILLING_BUFF)) ++ pctbonus += 0.2f; ++ //Barrage: 12% bonus damage for Aimed Shot, Multi-Shot or Volley (removed in SpellHitTarget()) ++ if (lvl >= 30 && (spellId == GetSpell(AIMED_SHOT_1) || spellId == GetSpell(MULTISHOT_1) || ++ spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(VOLLEY_DAMAGE_1)))) ++ pctbonus += 0.12f; ++ //Marked for Death (part 1): 5% bonus damage for all ranged shots on marked target ++ if (lvl >= 55 && attackType == RANGED_ATTACK && damageinfo.target == markTarget) ++ pctbonus += 0.05f; ++ //T.N.T: 6% bonus damage for Explosive Shot (handled here) and Black Arrow (can be handler in ApplyClassDamageMultiplierEffect()) ++ if (lvl >= 25 && spellId == GetSpell(EXPLOSIVE_SHOT_1)) ++ pctbonus += 0.06f; ++ //Sniper Training (part 2): 6% bonus damage for Steady Shot, Aimed Shot, Black Arrow and Explosive Shot (screw aura stuff, just increase) ++ if (lvl >= 50 && ++ (spellId == GetSpell(AIMED_SHOT_1) || ++ //spellId == GetSpell(STEADY_SHOT_1) || ++ //spellId == GetSpell(BLACK_ARROW_1) ||//cannot be handled here ++ spellId == GetSpell(EXPLOSIVE_SHOT_1))) ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ ++ //Thrill of the Hunt additive (stage 1): store mana restore value (50%) while ability crits ++ if (lvl >= 40) ++ (static_cast(TotH))[spellId] = crit && attackType == RANGED_ATTACK ? spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()) / 2 : 0; ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ if (target == me) ++ return; ++ ++ uint32 spellId = spell->Id; ++ ++ //Thrill of the Hunt additive (stage 1): store mana restore value while ability crits ++ if (me->getLevel() >= 40 && TotH[spellId] > 0 && uint32(TotH[spellId]) < me->GetMaxPower(POWER_MANA)) ++ { ++ me->EnergizeBySpell(me, THRILL_OF_THE_HUNT_EFFECT, TotH[spellId], POWER_MANA); ++ TotH[spellId] = 0; ++ } ++ ++ if (spellId == GetSpell(WING_CLIP_1)) ++ { ++ //zzzOLD Improved Wing Clip (only on creatures): 30% to root target with Wing Clip ++ //normal creatures are rooted for 10 sec, elites+ for 6 sec ++ if (target->GetTypeId() == TYPEID_UNIT) ++ { ++ if (urand(1,100) <= 30) ++ { ++ uint32 clip = target->ToCreature()->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL ? IMPROVED_WING_CLIP_NORMAL : IMPROVED_WING_CLIP_EX; ++ me->CastSpell(target, clip, true); ++ } ++ } ++ } ++ if (spellId == GetSpell(CONCUSSIVE_SHOT_1)) ++ { ++ //Improved Concussion Shot rank 2: 2 sec increased daze duration ++ if (Aura* concus = target->GetAura(spellId, me->GetGUID())) ++ { ++ int32 dur = concus->GetDuration() + 2000; ++ concus->SetDuration(dur); ++ concus->SetMaxDuration(dur); ++ } ++ ++ //zzzOLD Improved Concussion Shot: chance to stun target for 3 sec ++ if (urand(1,100) <= 15) ++ { ++ me->CastSpell(target, IMPROVED_CONCUSSION, true); ++ } ++ } ++ ++ //Rapid Killing: use up buff manually ++ if (spellId == GetSpell(AIMED_SHOT_1) || spellId == GetSpell(ARCANE_SHOT_1) || spellId == GetSpell(CHIMERA_SHOT_1)) ++ { ++ if (me->HasAura(RAPID_KILLING_BUFF)) ++ me->RemoveAura(RAPID_KILLING_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if (spellId == GetSpell(RAPID_FIRE_1)) ++ { ++ //Rapid Fire (id 28755): 4 sec increased duration ++ if (Aura* rapid = me->GetAura(spellId)) ++ { ++ uint32 dur = rapid->GetDuration() + 4000; ++ rapid->SetDuration(dur); ++ rapid->SetMaxDuration(dur); ++ } ++ } ++ ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ Trap_cd = 0; ++ ++ ScorpidSting_Timer = 0; ++ Aspect_Timer = 0; ++ ++ Aspect = 0; ++ ++ stingTargetGuid.Clear(); ++ ++ markTarget = NULL; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (Trap_cd > diff) Trap_cd -= diff; ++ ++ if (ScorpidSting_Timer > diff) ScorpidSting_Timer -= diff; ++ if (Aspect_Timer > diff) Aspect_Timer -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(AUTO_SHOT_1, true); ++ InitSpellMap(TRANQ_SHOT_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(SILENCING_SHOT_1) : RemoveSpell(SILENCING_SHOT_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(CHIMERA_SHOT_1) : RemoveSpell(CHIMERA_SHOT_1); ++ InitSpellMap(ARCANE_SHOT_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(AIMED_SHOT_1) : RemoveSpell(AIMED_SHOT_1); ++ InitSpellMap(KILL_SHOT_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(EXPLOSIVE_SHOT_1) : RemoveSpell(EXPLOSIVE_SHOT_1); ++ InitSpellMap(MULTISHOT_1); ++ InitSpellMap(VOLLEY_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(SCATTER_SHOT_1) : RemoveSpell(SCATTER_SHOT_1); ++ InitSpellMap(CONCUSSIVE_SHOT_1); ++ InitSpellMap(DISTRACTING_SHOT_1); ++ InitSpellMap(SCORPID_STING_1); ++ //InitSpellMap(VIPER_STING_1); ++ InitSpellMap(RAPID_FIRE_1); ++ InitSpellMap(WING_CLIP_1); ++ InitSpellMap(RAPTOR_STRIKE_1); ++ InitSpellMap(DISENGAGE_1); ++ InitSpellMap(FROST_TRAP_1); ++ InitSpellMap(FREEZING_ARROW_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(WYVERN_STING_1) : RemoveSpell(WYVERN_STING_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(BLACK_ARROW_1) : RemoveSpell(BLACK_ARROW_1); ++ InitSpellMap(HUNTERS_MARK_1); ++ InitSpellMap(SCARE_BEAST_1); ++ InitSpellMap(FEIGN_DEATH_1); ++ InitSpellMap(DETERRENCE_1); ++ //InitSpellMap(ASPECT_OF_THE_VIPER_1); ++ /*Custom*///ASPECT_OF_THE_PACK = ASPECT_OF_THE_VIPER ? ASPECT_OF_THE_PACK_1 : 0; ++ /*Custom*/lvl >= 20 ? InitSpellMap(ASPECT_OF_THE_PACK_1, true) : RemoveSpell(ASPECT_OF_THE_PACK_1); ++ //InitSpellMap(ASPECT_OF_THE_DRAGONHAWK_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(RAPID_KILLING, level >= 20 ? 1 : 0); ++ RefreshAura(CONCUSSIVE_BARRAGE, level >= 30 ? 1 : 0); ++ RefreshAura(PIERCING_SHOTS, level >= 40 ? 1 : 0); ++ RefreshAura(TRUESHOT_AURA, level >= 40 ? 1 : 0); ++ RefreshAura(RAPID_RECUPERATION, level >= 45 ? 1 : 0); ++ RefreshAura(MASTER_MARKSMAN, level >= 45 ? 1 : 0); ++ RefreshAura(WILD_QUIVER, level >= 70 ? 3 : level >= 60 ? 2 : level >= 50 ? 1 : 0); ++ RefreshAura(SUREFOOTED, level >= 15 ? 1 : 0); ++ RefreshAura(ENTRAPMENT, level >= 15 ? 1 : 0); ++ RefreshAura(MASTER_TACTICIAN5, level >= 67 ? 3 : level >= 58 ? 2 : level >= 50 ? 1 : 0); ++ RefreshAura(MASTER_TACTICIAN4, level >= 49 && level < 50 ? 1 : 0); ++ RefreshAura(MASTER_TACTICIAN3, level >= 48 && level < 49 ? 1 : 0); ++ RefreshAura(MASTER_TACTICIAN2, level >= 47 && level < 48 ? 1 : 0); ++ RefreshAura(MASTER_TACTICIAN1, level >= 46 && level < 47 ? 1 : 0); ++ RefreshAura(NOXIOUS_STINGS, level >= 45 ? 1 : 0); ++ RefreshAura(HUNTING_PARTY, level >= 55 ? 1 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case RAPID_FIRE_1: ++ case FROST_TRAP_1: ++ case ASPECT_OF_THE_PACK_1: ++ case ASPECT_OF_THE_VIPER_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ typedef std::unordered_map ManaRestoreList; ++ ManaRestoreList TotH; ++ uint32 Trap_cd; ++ uint32 ScorpidSting_Timer, Aspect_Timer; ++ uint32 Aspect; ++ ObjectGuid stingTargetGuid; ++ Unit* markTarget; ++ ++ enum HunterBaseSpells ++ { ++ AUTO_SHOT_1 = 75, ++ TRANQ_SHOT_1 = 19801, ++ SILENCING_SHOT_1 = 34490, ++ CHIMERA_SHOT_1 = 53209, ++ ARCANE_SHOT_1 = 3044, ++ AIMED_SHOT_1 = 19434, ++ KILL_SHOT_1 = 53351, ++ EXPLOSIVE_SHOT_1 = 53301, ++ MULTISHOT_1 = 2643, ++ VOLLEY_1 = 1510, ++ SCATTER_SHOT_1 = 1991, ++ CONCUSSIVE_SHOT_1 = 5116, ++ DISTRACTING_SHOT_1 = 20736, ++ SCORPID_STING_1 = 3043, ++ //VIPER_STING_1 = 3034, ++ RAPID_FIRE_1 = 3045, ++ WING_CLIP_1 = 2974, ++ RAPTOR_STRIKE_1 = 2973, ++ DISENGAGE_1 = 781, ++ FROST_TRAP_1 = 13809, ++ FREEZING_ARROW_1 = 60192, ++ WYVERN_STING_1 = 19386, ++ BLACK_ARROW_1 = 3674, ++ HUNTERS_MARK_1 = 1130, ++ SCARE_BEAST_1 = 1513, ++ FEIGN_DEATH_1 = 5384, ++ DETERRENCE_1 = 19263, ++ ASPECT_OF_THE_PACK_1 = 36613,//Aspect of the Spirit Hunter ++ ASPECT_OF_THE_VIPER_1 = 34074 ++ //ASPECT_OF_THE_DRAGONHAWK_1 = 61846 ++ }; ++ ++ enum HunterPassives ++ { ++ //Talents ++ RAPID_KILLING = 34949,//rank 2 ++ CONCUSSIVE_BARRAGE = 35102,//rank 2 ++ PIERCING_SHOTS = 53238,//rank 3 ++ TRUESHOT_AURA = 19506, ++ RAPID_RECUPERATION = 53232,//rank 2 ++ MASTER_MARKSMAN = 34489,//rank 5 ++ WILD_QUIVER = 53217,//rank 3 ++ SUREFOOTED = 24283,//rank 3 ++ ENTRAPMENT = 19388,//rank 3 ++ MASTER_TACTICIAN1 = 34506, ++ MASTER_TACTICIAN2 = 34507, ++ MASTER_TACTICIAN3 = 34508, ++ MASTER_TACTICIAN4 = 34838, ++ MASTER_TACTICIAN5 = 34839, ++ NOXIOUS_STINGS = 53297,//rank 3 ++ HUNTING_PARTY = 53292 //rank 3 ++ }; ++ ++ enum HunterSpecial ++ { ++ IMPROVED_CONCUSSION = 28445, ++ IMPROVED_WING_CLIP_NORMAL = 47168, ++ IMPROVED_WING_CLIP_EX = 35963, ++ ++ RAPID_KILLING_BUFF = 35099,//rank 2 ++ THRILL_OF_THE_HUNT_EFFECT = 34720, ++ FROST_TRAP_AURA = 13810, ++ FREEZING_ARROW_EFFECT = 60210, ++ //FREEZING_TRAP_EFFECT_1 = 3355, ++ //FREEZING_TRAP_EFFECT_2 = 14308, ++ //FREEZING_TRAP_EFFECT_3 = 14309, ++ VOLLEY_DAMAGE_1 = 42243 //rank 1 ++ }; ++ }; ++}; ++ ++void AddSC_hunter_bot() ++{ ++ new hunter_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_mage_ai.cpp b/src/server/game/AI/NpcBots/bot_mage_ai.cpp +new file mode 100644 +index 0000000..d28177b +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_mage_ai.cpp +@@ -0,0 +1,910 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "GameEventMgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++//#include "WorldSession.h" ++/* ++Mage NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - Around 45% ++TODO: Ice Lance, Deep Freeze, Mana Gems, Pet etc... ++*/ ++class mage_bot : public CreatureScript ++{ ++public: ++ mage_bot() : CreatureScript("mage_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new mage_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct mage_botAI : public bot_minion_ai ++ { ++ mage_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_MAGE; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_MAGE) != SPELL_CAST_OK) ++ return false; ++ ++ bool result = bot_ai::doCast(victim, spellId, triggered); ++ ++ if (result && spellId != MANAPOTION && me->HasAura(CLEARCASTBUFF)) ++ { ++ cost = m_botSpellInfo->CalcPowerCost(me, m_botSpellInfo->GetSchoolMask()); ++ if (cost) ++ clearcast = true; ++ } ++ ++ return result; ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void Counter() ++ { ++ Unit* u = me->GetVictim(); ++ bool cSpell = IsSpellReady(COUNTERSPELL_1, 0, false, 5000); ++ bool blast = IsSpellReady(FIREBLAST_1, 0, false, 3000) && HasRole(BOT_ROLE_DPS) && !(u && u->ToCreature() && (u->ToCreature()->isWorldBoss() || u->ToCreature()->IsDungeonBoss())) && me->HasAura(IMPACT_BUFF); ++ if (!cSpell && !blast) return; ++ if (u && u->IsNonMeleeSpellCast(false) && ++ ((cSpell && me->GetDistance(u) < 30) || (blast && me->GetDistance(u) < 30))) ++ { ++ temptimer = GC_Timer; ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (cSpell && doCast(u, GetSpell(COUNTERSPELL_1))) ++ {} ++ else if (blast && doCast(u, GetSpell(FIREBLAST_1))) ++ {} ++ GC_Timer = temptimer; ++ } ++ else if (cSpell) ++ { ++ if (Unit* target = FindCastingTarget(30)) ++ { ++ temptimer = GC_Timer; ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (doCast(target, GetSpell(COUNTERSPELL_1))) ++ GC_Timer = temptimer; ++ } ++ } ++ } ++ ++ void CheckSpellSteal(uint32 diff) ++ { ++ if (!IsSpellReady(SPELLSTEAL_1, diff) || IsCasting() || Rand() > 25) return; ++ Unit* target = FindHostileDispelTarget(30, true); ++ if (target && doCast(target, GetSpell(SPELLSTEAL_1))) ++ GC_Timer = 800; ++ } ++ ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || me->IsMounted() || Feasting()) ++ return; ++ ++ if (uint32 DAMPENMAGIC = GetSpell(DAMPENMAGIC_1)) ++ { ++ if (!HasAuraName(me, DAMPENMAGIC) && ++ doCast(me, DAMPENMAGIC)) ++ return; ++ } ++ ++ if (ICEARMOR && !HasAuraName(me, ICEARMOR) && ++ doCast(me, ICEARMOR)) ++ return; ++ } ++ ++ bool BuffTarget(Unit* target, uint32 diff) ++ { ++ if (GC_Timer > diff || Rand() > 20) return false; ++ if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; ++ if (me->GetExactDist(target) > 30) return false; ++ ++ if (uint32 ARCANEINTELLECT = GetSpell(ARCANEINTELLECT_1)) ++ { ++ if (target->getPowerType() == POWER_MANA && !HasAuraName(target, ARCANEINTELLECT) && ++ doCast(target, ARCANEINTELLECT)) ++ return true; ++ } ++ ++ return false; ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ if (clearcast && me->HasAura(CLEARCASTBUFF) && !me->IsNonMeleeSpellCast(false)) ++ { ++ me->ModifyPower(POWER_MANA, cost); ++ me->RemoveAurasDueToSpell(CLEARCASTBUFF,me->GetGUID(),0,AURA_REMOVE_BY_EXPIRE); ++ if (me->HasAura(ARCANE_POTENCY_BUFF1)) ++ me->RemoveAurasDueToSpell(ARCANE_POTENCY_BUFF1,me->GetGUID(),0,AURA_REMOVE_BY_EXPIRE); ++ if (me->HasAura(ARCANE_POTENCY_BUFF2)) ++ me->RemoveAurasDueToSpell(ARCANE_POTENCY_BUFF2,me->GetGUID(),0,AURA_REMOVE_BY_EXPIRE); ++ clearcast = false; ++ } ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me) && (!GetSpell(ICEBLOCK_1) || !me->HasAura(GetSpell(ICEBLOCK_1)))) return; //TODO ++ ++ CheckBlink(diff); ++ CheckPoly(diff); ++ CheckPots(diff); ++ CureGroup(master, GetSpell(REMOVE_CURSE_1), diff); ++ ++ FocusMagic(diff); ++ BuffAndHealGroup(master, diff); ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_MAGE)) ++ return; ++ ++ CheckPoly2();//this should go AFTER getting opponent ++ ++ Counter(); ++ CheckSpellSteal(diff); ++ DoNormalAttack(diff); ++ } ++ ++ void DoNormalAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent); ++ } ++ else ++ return; ++ ++ AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ Unit* u = me->SelectNearestTarget(20); ++ //ICE_BARRIER ++ uint32 ICE_BARRIER = GetSpell(ICE_BARRIER_1); ++ if (IsSpellReady(ICE_BARRIER_1, diff, false) && u && u->GetVictim() == me && u->GetDistance(me) < 8 && ++ !me->HasAura(ICE_BARRIER)) ++ { ++ if (me->IsNonMeleeSpellCast(true)) ++ me->InterruptNonMeleeSpells(true); ++ if (doCast(me, ICE_BARRIER)) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ if (!IsSpellReady(ICE_BARRIER_1, diff, false) && ++ IsSpellReady(BLINK_1, diff, false, 3000) && u && u->GetVictim() == me && ++ u->GetDistance(me) < 6 && !me->HasAura(ICE_BARRIER)) ++ { ++ if (me->IsNonMeleeSpellCast(true)) ++ me->InterruptNonMeleeSpells(true); ++ if (doCast(me, GetSpell(BLINK_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ //ICEBLOCK ++ if (uint32 ICEBLOCK = GetSpell(ICEBLOCK_1)) ++ { ++ if (IsSpellReady(ICEBLOCK_1, diff, false, 57000)) ++ { ++ if (((GetManaPCT(me) > 45 && GetHealthPCT(me) > 80) || b_attackers.empty()) && ++ me->HasAura(ICEBLOCK)) ++ me->RemoveAurasDueToSpell(ICEBLOCK); ++ } ++ ++ if (IsSpellReady(ICEBLOCK_1, diff, false) && !b_attackers.empty() && Rand() < 50 && ++ (GetManaPCT(me) < 15 || GetHealthPCT(me) < 45 || b_attackers.size() > 4) && ++ !me->HasAura(ICEBLOCK)) ++ { ++ if (me->IsNonMeleeSpellCast(true)) ++ me->InterruptNonMeleeSpells(true); ++ if (doCast(me, ICEBLOCK)) ++ { ++ Nova_cd = 0; //Glyph of Iceblock ++ return; ++ } ++ } ++ } ++ ++ if (IsCasting()) return; ++ ++ float dist = me->GetExactDist(opponent); ++ ++ uint32 FROSTBOLT = GetSpell(FROSTBOLT_1); ++ uint32 FIREBALL = GetSpell(FIREBALL_1); ++ uint32 BLASTWAVE = GetSpell(BLASTWAVE_1); ++ uint32 FROSTNOVA = GetSpell(FROSTNOVA_1); ++ BOLT = (CCed(opponent, true) || !FROSTBOLT) ? FIREBALL : FROSTBOLT; ++ NOVA = BOLT == FROSTBOLT && BLASTWAVE && dist > 5 ? BLASTWAVE : FROSTNOVA ? FROSTNOVA : 0; ++ ++ if (IsSpellReady(COMBUSTION_1, diff, false) && HasRole(BOT_ROLE_DPS) && ++ (opponent->GetMaxHealth() > master->GetMaxHealth()*6 || ++ m_attackers.size() > 1 || b_attackers.size() > 2) && ++ Rand() < 15 && !HasAuraName(me, COMBUSTION_1)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(COMBUSTION_1))) ++ { ++ GC_Timer = temptimer; ++ //Reset timers for fun ++ ResetSpellCooldown(FIREBLAST_1); ++ ResetSpellCooldown(DRAGONBREATH_1); ++ Nova_cd = 0; ++ } ++ } ++ //DAMAGE ++ //PYROBLAST ++ if (IsSpellReady(PYROBLAST_1, diff) && opponent->IsPolymorphed() && HasRole(BOT_ROLE_DPS) && ++ (b_attackers.size() < 2 || (*b_attackers.begin()) == opponent) && ++ dist < 35 && Rand() < 75 && ++ doCast(opponent, GetSpell(PYROBLAST_1))) ++ { ++ SetSpellCooldown(PYROBLAST_1, 7500); //no initial cooldown ++ //debug ++ SetSpellCooldown(DRAGONBREATH_1, std::max(GetSpellCooldown(DRAGONBREATH_1), uint32(float(sSpellMgr->GetSpellInfo(GetSpell(PYROBLAST_1))->CalcCastTime()/100) * me->GetFloatValue(UNIT_MOD_CAST_SPEED) + 400))); ++ Nova_cd = std::max(Nova_cd, 450); ++ return; ++ } ++ //nova //TODO: SEPARATE ++ u = me->SelectNearestTarget(6.3f); ++ if (NOVA && Nova_cd <= diff && HasRole(BOT_ROLE_DPS) && u && Rand() < 75 && !CCed(u, true) && IsInBotParty(u->GetVictim())) ++ { ++ if (doCast(me, NOVA)) ++ { ++ Nova_cd = 15000; ++ GetInPosition(true); ++ return; ++ } ++ } ++ //living bomb ++ if (IsSpellReady(LIVINGBOMB_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 35 && opponent->GetHealth() > me->GetHealth()/2 && ++ Rand() < 45 && !HasAuraName(opponent, LIVINGBOMB_1, me->GetGUID()) && ++ doCast(opponent, GetSpell(LIVINGBOMB_1))) ++ { ++ GC_Timer = 500; ++ return; ++ } ++ //cone of cold ++ if (IsSpellReady(CONEOFCOLD_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 7 && Rand() < 50 && ++ me->HasInArc(M_PI*0.75f, opponent) && ++ doCast(opponent, GetSpell(CONEOFCOLD_1))) ++ { ++ GC_Timer = 500; ++ GetInPosition(true); ++ return; ++ } ++ //dragon's breath ++ if (IsSpellReady(DRAGONBREATH_1, diff) && HasRole(BOT_ROLE_DPS) && !CCed(opponent, true) && ++ ((me->HasInArc(M_PI*0.75f, opponent) && dist < 7) || ++ (u && u != opponent && me->HasInArc(M_PI*0.75f, u) && IsInBotParty(u->GetVictim()))) && ++ doCast(me, GetSpell(DRAGONBREATH_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ /*//blast wave //TODO Separate again ++ u = me->SelectNearestTarget(8); ++ if (BLASTWAVE != 0 && u && isTimerReady(BlastWave_cd) && ++ !HasAuraName(u, FROSTNOVA) && !HasAuraName(u, DRAGONBREATH) && ++ doCast(me, BLASTWAVE)) ++ { ++ BlastWave_cd = BLASTWAVE_CD; ++ GC_Timer = 800; ++ }*/ ++ //fire blast ++ if (IsSpellReady(FIREBLAST_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 25 && ++ Rand() < 20 + 80*(!opponent->isFrozen() && !opponent->HasAuraType(SPELL_AURA_MOD_STUN) && me->HasAura(IMPACT_BUFF))) ++ { ++ if (doCast(opponent, GetSpell(FIREBLAST_1))) ++ { ++ GC_Timer = 500; ++ return; ++ } ++ } ++ //flamestrike - use Improved Flamestrike for instant cast ++ if (IsSpellReady(FLAMESTRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && me->HasAura(FIRESTARTERBUFF) && Rand() < 25) ++ { ++ Unit* FStarget = FindAOETarget(30, true, false); ++ if (FStarget && doCast(FStarget, GetSpell(FLAMESTRIKE_1), true)) ++ { ++ me->RemoveAurasDueToSpell(FIRESTARTERBUFF); ++ GC_Timer = 300; ++ return; ++ } ++ } ++ //blizzard ++ if (IsSpellReady(BLIZZARD_1, diff, false) && HasRole(BOT_ROLE_DPS) && Rand() < 40) ++ { ++ Unit* blizztarget = FindAOETarget(30, true); ++ if (blizztarget && doCast(blizztarget, GetSpell(BLIZZARD_1))) ++ return; ++ SetSpellCooldown(BLIZZARD_1, 1500); //fail ++ } ++ //Frost or Fire Bolt ++ if (BOLT && Bolt_cd <= diff && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 75 && ++ doCast(opponent, BOLT)) ++ { ++ Bolt_cd = uint32(float(sSpellMgr->GetSpellInfo(BOLT)->CalcCastTime()/100) * me->GetFloatValue(UNIT_MOD_CAST_SPEED) + 200); ++ //debug ++ SetSpellCooldown(DRAGONBREATH_1, std::max(GetSpellCooldown(DRAGONBREATH_1), Bolt_cd + 200)); ++ Nova_cd = std::max(Nova_cd, 450); ++ return; ++ } ++ //Arcane Missiles ++ if (IsSpellReady(ARCANEMISSILES_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 20 && Rand() < 15 && ++ doCast(opponent, GetSpell(ARCANEMISSILES_1))) ++ return; ++ } ++ ++ void CheckPoly(uint32 diff) ++ { ++ if (polyCheckTimer <= diff) ++ { ++ Polymorph = FindAffectedTarget(GetSpell(POLYMORPH_1), me->GetGUID()); ++ polyCheckTimer = 2000; ++ } ++ } ++ ++ void CheckPoly2() ++ { ++ if (Polymorph == false && GC_Timer < 500 && GetSpell(POLYMORPH_1)) ++ { ++ if (Unit* target = FindPolyTarget(30, me->GetVictim())) ++ { ++ if (doCast(target, GetSpell(POLYMORPH_1))) ++ { ++ Polymorph = true; ++ polyCheckTimer += 2000; ++ } ++ } ++ } ++ } ++ ++ void CheckPots(uint32 diff) ++ { ++ if (me->IsMounted() || IsCasting()) ++ return; ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (GetManaPCT(me) < 35 && Rand() < 35) ++ { ++ if (IsSpellReady(EVOCATION_1, diff, false) && me->getAttackers().empty() && ++ doCast(me, GetSpell(EVOCATION_1))) ++ return; ++ if (Potion_cd <= diff) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ } ++ } ++ ++ void CheckBlink(uint32 diff) ++ { ++ if (GetBotCommandState() == COMMAND_STAY || me->IsMounted() || IAmFree()) return; ++ if (!IsSpellReady(BLINK_1, diff, false) || me->getLevel() < 20 || IsCasting()) return; ++ ++ if (!me->IsInCombat() && me->GetExactDist2d(master) > std::max(master->GetBotFollowDist(), 35) && ++ me->HasInArc(M_PI*0.75f, master)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLINK_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ if (!me->getAttackers().empty() && me->GetExactDist2d(master) > 15) ++ { ++ if (Unit* op = me->SelectNearestTarget(7)) ++ { ++ if (op->GetVictim() == me) ++ { ++ me->SetFacingTo(me->GetAngle(master)); ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLINK_1))) ++ GC_Timer = temptimer; ++ } ++ } ++ } ++ } ++ ++ void FocusMagic(uint32 diff) ++ { ++ if (fmCheckTimer > diff || GC_Timer > diff || IAmFree() || me->getLevel() < 20 || IsCasting() || Rand() < 50) ++ return; ++ ++ uint32 FOCUSMAGIC = GetSpell(FOCUSMAGIC_1); ++ if (!FOCUSMAGIC) ++ return; ++ ++ if (Unit* target = FindAffectedTarget(FOCUSMAGIC, me->GetGUID(), 70, 3)) ++ { ++ fmCheckTimer = 15000; ++ return; ++ } ++ else ++ { ++ Group* pGroup = master->GetGroup(); ++ if (!pGroup) ++ { ++ if (master->getPowerType() == POWER_MANA && me->GetExactDist(master) < 30 && ++ !master->HasAura(FOCUSMAGIC)) ++ target = master; ++ } ++ else ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pPlayer = itr->GetSource(); ++ if (!pPlayer || !pPlayer->IsInWorld() || pPlayer->isDead()) continue; ++ if (me->GetMapId() != pPlayer->GetMapId()) continue; ++ if (pPlayer->getPowerType() == POWER_MANA && me->GetExactDist(pPlayer) < 30 && ++ !pPlayer->HasAura(FOCUSMAGIC)) ++ { ++ target = pPlayer; ++ break; ++ } ++ } ++ if (!target) ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pPlayer = itr->GetSource(); ++ if (!pPlayer || !pPlayer->IsInWorld() || !pPlayer->HaveBot()) continue; ++ if (me->GetMapId() != pPlayer->GetMapId()) continue; ++ BotMap const* map = pPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre || !cre->IsInWorld() || cre == me || cre->isDead()) continue; ++ if (cre->getPowerType() == POWER_MANA && me->GetExactDist(cre) < 30 && ++ !cre->HasAura(FOCUSMAGIC)) ++ { ++ target = cre; ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (target && doCast(target, FOCUSMAGIC)) ++ { ++ GC_Timer = 500; ++ fmCheckTimer = 30000; ++ return; ++ } ++ } ++ ++ fmCheckTimer = 5000; ++ } ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Combustion: 10% per stack ++ if (SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask()) ++ if (Aura* combustion = me->GetAura(COMBUSTION_BUFF)) ++ aftercrit += float(combustion->GetStackAmount()*10); ++ //Incineration: 6% additional critical chance for Fire Blast, Scorch, Arcane Blast and Cone of Cold ++ if (lvl >= 10 && ++ (spellId == GetSpell(FIREBLAST_1) || ++ spellId == GetSpell(CONEOFCOLD_1)/* || ++ spellId == ARCANEBLAST || ++ spellId == SCORCH*/)) ++ aftercrit += 6.f; ++ //World In Flames: 6% additional critical chance for ++ //Flamestrike, Pyroblast, Blast Wave, Dragon's Breath, Living Bomb, Blizzard and Arcane Explosion ++ if (lvl >= 15 && ++ (spellId == GetSpell(FLAMESTRIKE_1) || ++ spellId == GetSpell(PYROBLAST_1) || ++ spellId == GetSpell(BLASTWAVE_1) || ++ spellId == GetSpell(DRAGONBREATH_1)/* || ++ spellId == ARCANEXPLOSION || ++ spellId == LIVINGBOMB || //cannot be handled here ++ spellId == BLIZZARD*/)) //cannot be handled here ++ aftercrit += 6.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!spell damage is not yet critical and will be multiplied by 1.5 ++ //so we should put here bonus damage mult /1.5 ++ //Spell Power: 50% additional crit damage bonus for All spells ++ if (lvl >= 55) ++ pctbonus += 0.333f; ++ //Ice Shards: 50% additional crit damage bonus for Frost spells ++ else if (lvl >= 15 && (SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask())) ++ pctbonus += 0.333f; ++ } ++ //Improved Cone of Cold: 35% bonus damage for Cone of Cold ++ if (lvl >= 30 && spellId == GetSpell(CONEOFCOLD_1)) ++ pctbonus += 0.35f; ++ //Fire Power: 10% bonus damage for Fire spells ++ if (lvl >= 35 && (SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask())) ++ pctbonus += 0.1f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ if (!aftercastTargetGuid) ++ { ++ //only players for now ++ if (!aftercastTargetGuid.IsPlayer()) ++ { ++ aftercastTargetGuid.Clear(); ++ return; ++ } ++ ++ Player* pTarget = ObjectAccessor::FindPlayer(aftercastTargetGuid); ++ aftercastTargetGuid.Clear(); ++ ++ if (!pTarget/* || me->GetDistance(pTarget) > 15*/) ++ return; ++ ++ //handle effects ++ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) ++ { ++ switch (spell->Effects[i].Effect) ++ { ++ case SPELL_EFFECT_CREATE_ITEM: ++ case SPELL_EFFECT_CREATE_ITEM_2: ++ { ++ uint32 newitemid = spell->Effects[i].ItemType; ++ if (newitemid) ++ { ++ ItemPosCountVec dest; ++ ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(newitemid); ++ if (!pProto) ++ return; ++ uint32 count = pProto->GetMaxStackSize(); ++ uint32 no_space = 0; ++ InventoryResult msg = pTarget->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, newitemid, count, &no_space); ++ if (msg != EQUIP_ERR_OK) ++ { ++ if (msg == EQUIP_ERR_INVENTORY_FULL || msg == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) ++ count -= no_space; ++ else ++ { ++ // if not created by another reason from full inventory or unique items amount limitation ++ pTarget->SendEquipError(msg, NULL, NULL, newitemid); ++ continue; ++ } ++ } ++ if (count) ++ { ++ Item* pItem = pTarget->StoreNewItem(dest, newitemid, true, Item::GenerateItemRandomPropertyId(newitemid)); ++ if (!pItem) ++ { ++ pTarget->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL); ++ continue; ++ } ++ //unsafe possible ++ pItem->SetUInt32Value(ITEM_FIELD_CREATOR, me->GetGUID().GetCounter()); ++ ++ pTarget->SendNewItem(pItem, count, true, false, true); ++ } ++ } ++ break; ++ } ++ default: ++ break; ++ } ++ } ++ ++ return; ++ } ++ ++ //Winter Veil addition ++ uint32 spellId = spell->Id; ++ if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) ++ { ++ if (SPELL_SCHOOL_MASK_FROST & spell->GetSchoolMask()) ++ me->AddAura(44755, target); //snowflakes ++ ++ if (spellId == GetSpell(FROSTBOLT_1) && urand(1,100) <= 10) ++ me->CastSpell(target, 25686, true); //10% super snowball ++ } ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ Bolt_cd = 0; ++ Nova_cd = 0; ++ polyCheckTimer = 0; ++ fmCheckTimer = 0; ++ Polymorph = false; ++ clearcast = false; ++ BOLT = 0; ++ NOVA = 0; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (Bolt_cd > diff) Bolt_cd -= diff; ++ if (Nova_cd > diff) Nova_cd -= diff; ++ ++ if (polyCheckTimer > diff) polyCheckTimer -= diff; ++ if (fmCheckTimer > diff) fmCheckTimer -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(DAMPENMAGIC_1); ++ InitSpellMap(ARCANEINTELLECT_1); ++ InitSpellMap(ARCANEMISSILES_1); ++ InitSpellMap(POLYMORPH_1); ++ InitSpellMap(COUNTERSPELL_1); ++ InitSpellMap(SPELLSTEAL_1); ++ InitSpellMap(EVOCATION_1); ++ InitSpellMap(BLINK_1); ++ InitSpellMap(REMOVE_CURSE_1); ++ //InitSpellMap(INVISIBILITY_1); ++ InitSpellMap(FIREBALL_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(BLASTWAVE_1) : RemoveSpell(BLASTWAVE_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(DRAGONBREATH_1) : RemoveSpell(DRAGONBREATH_1); ++ InitSpellMap(FIREBLAST_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(PYROBLAST_1) : RemoveSpell(PYROBLAST_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(LIVINGBOMB_1) : RemoveSpell(LIVINGBOMB_1); ++ InitSpellMap(DAMPENMAGIC_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(COMBUSTION_1) : RemoveSpell(COMBUSTION_1); ++ InitSpellMap(FROSTBOLT_1); ++ InitSpellMap(FROSTNOVA_1); ++ InitSpellMap(CONEOFCOLD_1); ++ InitSpellMap(BLIZZARD_1); ++ /*Special*/ICEARMOR = lvl >= 20 ? InitSpell(me, ICEARMOR_1) : InitSpell(me, FROSTARMOR_1); ++ InitSpellMap(ICEARMOR); ++ /*Talent*/lvl >= 40 ? InitSpellMap(ICE_BARRIER_1) : RemoveSpell(ICE_BARRIER_1); ++ InitSpellMap(ICEBLOCK_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(FOCUSMAGIC_1) : RemoveSpell(FOCUSMAGIC_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(ARCTIC_WINDS, level >= 35 ? 2 : level >= 10 ? 1 : 0); ++ RefreshAura(WINTERS_CHILL3, level >= 30 ? 1 : 0); ++ RefreshAura(WINTERS_CHILL2, level >= 25 && level < 30 ? 1 : 0); ++ RefreshAura(WINTERS_CHILL1, level >= 20 && level < 25 ? 1 : 0); ++ RefreshAura(IMPROVED_BLIZZARD, level >= 45 ? 1 : 0); ++ RefreshAura(FROSTBITE3, level >= 80 ? level >= 60 ? 3 : level >= 30 ? 2 : level >= 10 ? 1 : 0 : 0); ++ RefreshAura(FROSTBITE2, level >= 50 && level < 80 ? level >= 60 ? 3 : level >= 30 ? 2 : level >= 10 ? 1 : 0 : 0); ++ RefreshAura(FROSTBITE1, level >= 10 && level < 50 ? level >= 60 ? 3 : level >= 30 ? 2 : level >= 10 ? 1 : 0 : 0); ++ RefreshAura(SHATTERED_BARRIER, level >= 45 ? 1 : 0); ++ RefreshAura(ARCANE_INSTABILITY, level >= 65 ? 4 : level >= 55 ? 3 : level >= 45 ? 2 : level >= 35 ? 1 : 0); ++ RefreshAura(INCANTERS_ABSORPTION3, level >= 50 ? 1 : 0); ++ RefreshAura(INCANTERS_ABSORPTION2, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(INCANTERS_ABSORPTION1, level >= 40 && level < 45 ? 1 : 0); ++ RefreshAura(SHATTER3, level >= 35 ? 1 : 0); ++ RefreshAura(SHATTER2, level >= 30 && level < 35 ? 1 : 0); ++ RefreshAura(SHATTER1, level >= 25 && level < 30 ? 1 : 0); ++ RefreshAura(CLEARCAST, level >= 75 ? 3 : level >= 40 ? 2 : level >= 15 ? 1 : 0); ++ RefreshAura(FINGERS_OF_FROST, level >= 45 ? 1 : 0); //15% ++ RefreshAura(ARCANE_POTENCY2, level >= 40 ? 1 : 0); ++ RefreshAura(ARCANE_POTENCY1, level >= 35 && level < 40 ? 1 : 0); ++ RefreshAura(IGNITE, level >= 15 ? 1 : 0); ++ RefreshAura(IMPACT, level >= 60 ? 2 : level >= 20 ? 1 : 0); ++ RefreshAura(IMPROVED_COUNTERSPELL2, level >= 35 ? 1 : 0); ++ RefreshAura(IMPROVED_COUNTERSPELL1, level >= 25 && level < 35 ? 1 : 0); ++ RefreshAura(FIRESTARTER2, level >= 55 ? 1 : 0); ++ RefreshAura(FIRESTARTER1, level >= 45 && level < 55 ? 1 : 0); ++ RefreshAura(GLYPH_LIVING_BOMB, GetSpell(LIVINGBOMB_1) ? 1 : 0); ++ RefreshAura(GLYPH_POLYMORPH, GetSpell(POLYMORPH_1) ? 1 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case DAMPENMAGIC_1: ++ case ARCANEINTELLECT_1: ++ case EVOCATION_1: ++ case REMOVE_CURSE_1: ++ case FOCUSMAGIC_1: ++ case FROSTARMOR_1: ++ case ICEARMOR_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ //Spells ++/*frst*/uint32 ICEARMOR; ++/*exc.*/uint32 BOLT, NOVA; ++ //Timers ++/*exc.*/uint32 Bolt_cd, Nova_cd; ++/*exc.*/uint32 polyCheckTimer, fmCheckTimer; ++ //Check ++/*exc.*/bool Polymorph, clearcast; ++ ++ enum MageBaseSpells ++ { ++ DAMPENMAGIC_1 = 604, ++ ARCANEINTELLECT_1 = 1459, ++ ARCANEMISSILES_1 = 5143, ++ POLYMORPH_1 = 118, ++ COUNTERSPELL_1 = 2139, ++ SPELLSTEAL_1 = 30449, ++ EVOCATION_1 = 12051, ++ BLINK_1 = 1953, ++ REMOVE_CURSE_1 = 475, ++ //INVISIBILITY_1 = 0, ++ FIREBALL_1 = 133, ++ BLASTWAVE_1 = 11113, ++ DRAGONBREATH_1 = 31661, ++ FIREBLAST_1 = 2136, ++ PYROBLAST_1 = 11366, ++ LIVINGBOMB_1 = 44457, ++ FLAMESTRIKE_1 = 2120, ++ COMBUSTION_1 = 11129, ++ FROSTBOLT_1 = 116, ++ FROSTNOVA_1 = 122, ++ CONEOFCOLD_1 = 120, ++ BLIZZARD_1 = 10, ++ FROSTARMOR_1 = 168, ++ ICEARMOR_1 = 7302, ++ ICE_BARRIER_1 = 11426, ++ ICEBLOCK_1 = 45438, ++ FOCUSMAGIC_1 = 54646 ++ }; ++ ++ enum MagePassives ++ { ++ //Talents ++ SHATTERED_BARRIER = 54787,//rank 2 ++ ARCTIC_WINDS = 31678,//rank 5 ++ WINTERS_CHILL1 = 11180, ++ WINTERS_CHILL2 = 28592, ++ WINTERS_CHILL3 = 28593, ++ FROSTBITE1 = 11071, ++ FROSTBITE2 = 12496, ++ FROSTBITE3 = 12497, ++ IMPROVED_BLIZZARD = 12488,//rank 3 ++ CLEARCAST /*Arcane Concentration*/ = 12577,//rank 5 ++ ARCANE_POTENCY1 = 31571, ++ ARCANE_POTENCY2 = 31572, ++ SHATTER1 = 11170, ++ SHATTER2 = 12982, ++ SHATTER3 = 12983, ++ INCANTERS_ABSORPTION1 = 44394, ++ INCANTERS_ABSORPTION2 = 44395, ++ INCANTERS_ABSORPTION3 = 44396, ++ FINGERS_OF_FROST = 44545,//rank 2 ++ ARCANE_INSTABILITY = 15060,//rank 3 ++ IMPROVED_COUNTERSPELL1 = 11255, ++ IMPROVED_COUNTERSPELL2 = 12598, ++ IGNITE = 12848, ++ FIRESTARTER1 = 44442, ++ FIRESTARTER2 = 44443, ++ IMPACT = 12358, ++ GLYPH_LIVING_BOMB = 63091, ++ //Special ++ GLYPH_POLYMORPH = 56375 ++ }; ++ enum MageSpecial ++ { ++ CLEARCASTBUFF = 12536, ++ IMPACT_BUFF = 64343, ++ FIRESTARTERBUFF = 54741, ++ ARCANE_POTENCY_BUFF1 = 57529, ++ ARCANE_POTENCY_BUFF2 = 57531, ++ COMBUSTION_BUFF = 28682 ++ }; ++ }; ++}; ++ ++void AddSC_mage_bot() ++{ ++ new mage_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_paladin_ai.cpp b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp +new file mode 100644 +index 0000000..ea9a8f3 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp +@@ -0,0 +1,1173 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuraEffects.h" ++//#include "WorldSession.h" ++/* ++Paladin NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - Around 45-50% ++TODO: Tanking, Shield Abilities, Auras ++*/ ++class paladin_bot : public CreatureScript ++{ ++public: ++ paladin_bot() : CreatureScript("paladin_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new paladin_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct paladin_botAI : public bot_minion_ai ++ { ++ paladin_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_PALADIN; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_PALADIN) != SPELL_CAST_OK) ++ return false; ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void HOFGroup(Player* pTarget, uint32 diff) ++ { ++ if (!IsSpellReady(HOF_1, diff) || IAmFree() || IsCasting() || Rand() > 60) ++ return; ++ ++ if (Group* pGroup = pTarget->GetGroup()) ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer) continue; ++ if (HOFTarget(tPlayer, diff)) ++ return; ++ } ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->HaveBot()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre || !cre->IsInWorld()) continue; ++ if (HOFTarget(cre, diff)) ++ return; ++ } ++ } ++ } ++ } ++ ++ bool HOFTarget(Unit* target, uint32 diff) ++ { ++ if (!target || target->isDead()) return false; ++ if (!IsSpellReady(HOF_1, diff)) return false; ++ if (target->ToCreature() && Rand() > 25) return false; ++ if (me->GetExactDist(target) > 30) return false;//too far away ++ if (HasAuraName(target, HOF_1)) return false; //Alredy has HOF ++ ++ Unit::AuraMap const &auras = target->GetOwnedAuras(); ++ for (Unit::AuraMap::const_iterator i = auras.begin(); i != auras.end(); ++i) ++ { ++ Aura* aura = i->second; ++ if (aura->IsPassive()) continue;//most ++ if (aura->GetDuration() < 2000) continue; ++ if (AuraApplication* app = aura->GetApplicationOfTarget(target->GetGUID())) ++ if (app->IsPositive()) continue; ++ SpellInfo const* spellInfo = aura->GetSpellInfo(); ++ if (spellInfo->AttributesEx & SPELL_ATTR0_HIDDEN_CLIENTSIDE) continue; ++ //if (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; ++ if (me->getLevel() >= 40 && (spellInfo->GetAllEffectsMechanicMask() & (1<GetAllEffectsMechanicMask() & (1<GetAllEffectsMechanicMask() & (1<Dispel == DISPEL_MAGIC || ++ spellInfo->Dispel == DISPEL_DISEASE || ++ spellInfo->Dispel == DISPEL_POISON) ? GetSpell(CLEANSE_1) : GetSpell(HOF_1); ++ ++ if (doCast(target, spell)) ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ void HOSGroup(Player* hTarget, uint32 diff) ++ { ++ if (!IsSpellReady(HOS_1, diff) || IAmFree() || IsCasting() || Rand() > 30) ++ return; ++ ++ if (Group* pGroup = hTarget->GetGroup()) ++ { ++ bool bots = false; ++ float threat; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* HOSPlayer = itr->GetSource(); ++ if (!HOSPlayer) continue; ++ if (HOSPlayer->HaveBot()) ++ bots = true; ++ if (HOSPlayer->isDead()) continue; ++ if (IsTank(HOSPlayer)) continue; //tanks do not need it ++ if (!HOSPlayer->IsInWorld() || master->GetMap() != HOSPlayer->FindMap() || me->GetExactDist(HOSPlayer) > 30) continue; ++ if (HasAuraName(HOSPlayer, HOS_1)) continue; ++ AttackerSet h_attackers = HOSPlayer->getAttackers(); ++ if (h_attackers.empty()) continue; ++ for (AttackerSet::iterator iter = h_attackers.begin(); iter != h_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->isDead()) continue; ++ if (!(*iter)->CanHaveThreatList()) continue; ++ threat = (*iter)->getThreatManager().getThreat(HOSPlayer); ++ if (threat < 25.f) continue;//too small threat ++ if ((*iter)->getAttackers().size() < 2) continue;//would be useless ++ if (HOSPlayer->GetDistance((*iter)) > 10) continue; ++ if (HOSTarget(HOSPlayer, diff)) ++ return; ++ }//end for ++ }//end for ++ if (!bots) return; ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pl = itr->GetSource(); ++ if (!pl) continue; ++ if (!pl->HaveBot()) continue; ++ if (master->GetMap() != pl->FindMap()) continue; ++ if (!pl->IsInWorld() || pl->IsBeingTeleported()) continue; ++ BotMap const* map = pl->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre || cre->isDead()) continue; ++ if (IsTank(cre)) continue; ++ if (me->GetExactDist(cre) > 30) continue; ++ if (HasAuraName(cre, HOS_1)) continue; //Alredy has HOS ++ AttackerSet h_attackers = cre->getAttackers(); ++ if (h_attackers.empty()) continue; ++ for (AttackerSet::iterator iter = h_attackers.begin(); iter != h_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->isDead()) continue; ++ if (!(*iter)->CanHaveThreatList()) continue; ++ threat = (*iter)->getThreatManager().getThreat(cre); ++ if (threat < 25.f) continue;//too small threat ++ if ((*iter)->getAttackers().size() < 2) continue;//would be useless ++ if (cre->GetDistance((*iter)) > 10) continue; ++ if (HOSTarget(cre, diff)) ++ return; ++ }//end for ++ }//end for ++ }//end for ++ }//end if ++ } ++ ++ bool HOSTarget(Unit* target, uint32 diff) ++ { ++ if (!target || target->isDead()) return false; ++ if (!IsSpellReady(HOS_1, diff) || Rand() > 50) return false; ++ if (IsTank(target)) return false; //tanks do not need it ++ if (IsCasting()) return false; //I'm busy casting ++ if (me->GetExactDist(target) > 30) return false; //too far away ++ if (HasAuraName(target, HOS_1)) return false; //Alredy has HOS ++ ++ AttackerSet h_attackers = target->getAttackers(); ++ if (h_attackers.empty()) return false; //no aggro ++ float threat; ++ uint8 Tattackers = 0; ++ for (AttackerSet::iterator iter = h_attackers.begin(); iter != h_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->isDead()) continue; ++ if (!(*iter)->CanHaveThreatList()) continue; ++ threat = (*iter)->getThreatManager().getThreat(target); ++ if (threat < 25.f) continue; //too small threat ++ if ((*iter)->getAttackers().size() < 2) continue;//would be useless ++ if (target->GetDistance((*iter)) <= 10) ++ Tattackers++; ++ } ++ if (Tattackers > 0 && doCast(target, GetSpell(HOS_1))) ++ { ++ for (AttackerSet::iterator iter = h_attackers.begin(); iter != h_attackers.end(); ++iter) ++ if ((*iter)->getThreatManager().getThreat(target) > 0.f) ++ (*iter)->getThreatManager().modifyThreatPercent(target, -(30 + 50*(target->HasAura(586)))); //Fade ++ return true; ++ } ++ return false; ++ } ++ //Holy_Shock setup (Modify HERE) ++ bool HS(Unit* target, uint32 diff) ++ { ++ if (!target || target->isDead()) return false; ++ if (!IsSpellReady(HOLY_SHOCK_1, diff)) return false; ++ if (IsCasting()) return false; ++ if (target->GetTypeId() == TYPEID_PLAYER && (target->IsCharmed() || target->isPossessed())) ++ return false; //do not damage friends under control ++ if (me->GetExactDist(target) > 40) return false; ++ ++ if (doCast(target, GetSpell(HOLY_SHOCK_1))) ++ { ++ if (urand(1,100) <= 20) //Daybreak: 20% to not trigger HS CD, only GCD ++ ResetSpellCooldown(HOLY_SHOCK_1); ++ return true; ++ } ++ return false; ++ } ++ ++ bool HealTarget(Unit* target, uint8 hp, uint32 diff) ++ { ++ if (!HasRole(BOT_ROLE_HEAL)) return false; ++ if (!target || target->isDead()) return false; ++ if (hp > 97) return false; ++ //sLog->outBasic("HealTarget() by %s on %s", me->GetName().c_str(), target->GetName().c_str()); ++ if (Rand() > 40 + 20*target->IsInCombat() + 50*master->GetMap()->IsRaid()) return false; ++ if (me->GetExactDist(target) > 35) return false; ++ if (IsCasting()) return false; ++ if (IsSpellReady(HAND_OF_PROTECTION_1, diff, false) && target->GetTypeId() == TYPEID_PLAYER && ++ IsInBotParty(target) && ++ ((hp < 30 && !target->getAttackers().empty()) || (hp < 50 && target->getAttackers().size() > 3)) && ++ me->GetExactDist(target) < 30 && ++ !HasAuraName(target, HAND_OF_PROTECTION_1) && ++ !HasAuraName(target, FORBEARANCE_AURA)) ++ { ++ if (doCast(target, GetSpell(HAND_OF_PROTECTION_1))) ++ { ++ BotWhisper("BOP on you!", target->ToPlayer()); ++ ++ //debug ++ if (!HasAuraName(target, FORBEARANCE_AURA)) ++ me->AddAura(FORBEARANCE_AURA, target); ++ if (HasAuraName(target, FORBEARANCE_AURA) && !HasAuraName(target, HAND_OF_PROTECTION_1)) ++ me->AddAura(GetSpell(HAND_OF_PROTECTION_1), target); ++ } ++ return true; ++ } ++ else if (hp < 20 && !HasAuraName(target, HAND_OF_PROTECTION_1)) ++ { ++ // 20% to cast loh, else just do a Shock ++ switch (rand()%3) ++ { ++ case 1: ++ if (IsSpellReady(LAY_ON_HANDS_1, diff, false) && hp < 20 && ++ target->GetTypeId() == TYPEID_PLAYER && ++ (target->IsInCombat() || !target->getAttackers().empty()) && ++ !HasAuraName(target, FORBEARANCE_AURA)) ++ { ++ if (doCast(target, GetSpell(LAY_ON_HANDS_1))) ++ { ++ BotWhisper("Lay of Hands on you!", target->ToPlayer()); ++ return true; ++ } ++ } ++ case 2: ++ if (GC_Timer > diff) return false; ++ if (doCast(target, GetSpell(FLASH_OF_LIGHT_1), me->HasAura(THE_ART_OF_WAR_BUFF))) ++ return true; ++ case 3: ++ if (GC_Timer > diff) return false; ++ if (HS(target, diff)) ++ return true; ++ } ++ } ++ ++ Unit* u = target->GetVictim(); ++ if (IsSpellReady(SACRED_SHIELD_1, diff) && !IAmFree() && target->GetTypeId() == TYPEID_PLAYER && ++ (hp < 65 || target->getAttackers().size() > 1 || (u && u->GetMaxHealth() > target->GetMaxHealth()*10 && target->IsInCombat())) && ++ !HasAuraName(target, SACRED_SHIELD_1) && IsInBotParty(target)) ++ { ++ Unit* aff = FindAffectedTarget(GetSpell(SACRED_SHIELD_1), me->GetGUID(), 50, 1);//use players since we cast only on them ++ if ((!aff || (aff->getAttackers().empty() && !IsTank(aff))) && ++ doCast(target, GetSpell(SACRED_SHIELD_1))) ++ return true; ++ } ++ if ((hp < 85 || GetLostHP(target) > 6000)) ++ if (HS(target, diff)) ++ return true; ++ if ((hp > 35 && (hp < 75 || GetLostHP(target) > 8000)) || (!GetSpell(FLASH_OF_LIGHT_1) && hp < 85)) ++ if (doCast(target, GetSpell(HOLY_LIGHT_1))) ++ return true; ++ if ((hp < 90 || GetLostHP(target) > 1500)) ++ if (doCast(target, GetSpell(FLASH_OF_LIGHT_1), me->HasAura(THE_ART_OF_WAR_BUFF))) ++ return true; ++ return false; ++ }//end HealTarget ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ //HOFTarget(me, diff);//self stun cure goes FIRST ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && GetManaPCT(me) < 30) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (GetManaPCT(me) < 40 && IsSpellReady(DIVINE_PLEA_1, diff, false)) ++ if (doCast(me, GetSpell(DIVINE_PLEA_1))) ++ return; ++ ++ CureTarget(me, GetSpell(CLEANSE_1), diff); //maybe unnecessary but this goes FIRST ++ HOFTarget(master, diff); //maybe unnecessary ++ CureTarget(master, GetSpell(CLEANSE_1), diff); //maybe unnecessary ++ BuffAndHealGroup(master, diff); ++ HOSTarget(master, diff); ++ CureGroup(master, GetSpell(CLEANSE_1), diff); ++ HOFGroup(master, diff); ++ HOSGroup(master, diff); ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ //buff ++ if (IsSpellReady(SEAL_OF_COMMAND_1, diff, false) && Rand() < 20 && !HasAuraName(me, SEAL_OF_COMMAND_1) && ++ doCast(me, GetSpell(SEAL_OF_COMMAND_1))) ++ GC_Timer = 500; ++ ++ // Heal myself ++ if (GetHealthPCT(me) < 80) ++ HealTarget(me, GetHealthPCT(me), diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_PALADIN)) ++ return; ++ ++ Repentance(diff); ++ //Counter(diff); ++ DoNormalAttack(diff); ++ } ++ ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || me->IsMounted() || IsCasting()) ++ return; ++ ++ RezGroup(GetSpell(REDEMPTION_1), master); ++ ++ if (Feasting()) ++ return; ++ ++ //aura ++ if (master->IsAlive() && me->GetExactDist(master) < 20) ++ { ++ uint8 myAura; ++ uint32 DEVOTION_AURA = GetSpell(DEVOTION_AURA_1); ++ uint32 CONCENTRATION_AURA = GetSpell(CONCENTRATION_AURA_1); ++ ++ if (me->HasAura(DEVOTION_AURA, me->GetGUID())) ++ myAura = DEVOTIONAURA; ++ else if (me->HasAura(CONCENTRATION_AURA, me->GetGUID())) ++ myAura = CONCENTRATIONAURA; ++ else ++ myAura = NOAURA; ++ ++ if (myAura != NOAURA) ++ return; //do not bother ++ ++ Aura* concAura = master->GetAura(CONCENTRATION_AURA); ++ Aura* devAura = master->GetAura(DEVOTION_AURA); ++ if (devAura && concAura) return; ++ if (devAura && devAura->GetCasterGUID() == me->GetGUID()) return; ++ if (concAura && concAura->GetCasterGUID() == me->GetGUID()) return; ++ ++ if ((master->getClass() == BOT_CLASS_MAGE || ++ master->getClass() == BOT_CLASS_PRIEST || ++ master->getClass() == BOT_CLASS_WARLOCK || ++ master->getClass() == BOT_CLASS_DRUID || devAura) && ++ !concAura && ++ doCast(me, CONCENTRATION_AURA)) ++ { ++ /*GC_Timer = 800;*/ ++ return; ++ } ++ if (!devAura && doCast(me, DEVOTION_AURA)) ++ { ++ /*GC_Timer = 800;*/ ++ return; ++ } ++ } ++ } ++ ++ bool BuffTarget(Unit* target, uint32 diff) ++ { ++ if (!target || target->isDead() || GC_Timer > diff || Rand() > 30) return false; ++ if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; ++ if (me->GetExactDist(target) > 30) return false; ++ //if (HasAuraName(target, BLESSING_OF_WISDOM_1, me->GetGUID()) || ++ // HasAuraName(target, BLESSING_OF_KINGS_1, me->GetGUID()) || ++ // HasAuraName(target, BLESSING_OF_SANCTUARY_1, me->GetGUID()) || ++ // HasAuraName(target, BLESSING_OF_MIGHT_1, me->GetGUID())) ++ // return false; ++ //if (HasAuraName(target, "Greater Blessing of Wisdom", me->GetGUID()) || ++ // HasAuraName(target, "Greater Blessing of Might", me->GetGUID()) || ++ // HasAuraName(target, "Greater Blessing of Kings", me->GetGUID()) || ++ // HasAuraName(target, "Greater Blessing of Sanctuary", me->GetGUID())) ++ // return false; ++ ++ uint32 mask = GetBlessingsMask(target); ++ ++ //already has my blessing ++ if (mask & SPECIFIC_BLESSING_MY_BLESSING) ++ return false; ++ ++ uint32 BLESSING_OF_WISDOM = GetSpell(BLESSING_OF_WISDOM_1); ++ uint32 BLESSING_OF_KINGS = GetSpell(BLESSING_OF_KINGS_1); ++ uint32 BLESSING_OF_SANCTUARY = GetSpell(BLESSING_OF_SANCTUARY_1); ++ uint32 BLESSING_OF_MIGHT = GetSpell(BLESSING_OF_MIGHT_1); ++ ++ bool wisdom = (mask & SPECIFIC_BLESSING_WISDOM); ++ bool kings = (mask & SPECIFIC_BLESSING_KINGS); ++ bool sanctuary = (mask & SPECIFIC_BLESSING_SANCTUARY); ++ bool might = (mask & SPECIFIC_BLESSING_MIGHT); ++ ++ //bool wisdom = HasAuraName(target, BLESSING_OF_WISDOM_1) || HasAuraName(target, GREATER_BLESSING_OF_WISDOM_1); ++ //bool kings = HasAuraName(target, BLESSING_OF_KINGS_1) || HasAuraName(target, GREATER_BLESSING_OF_KINGS_1); ++ //bool sanctuary = HasAuraName(target, BLESSING_OF_SANCTUARY_1) || HasAuraName(target, GREATER_BLESSING_OF_SANCTUARY_1); ++ //bool might = (HasAuraName(target, BLESSING_OF_MIGHT_1) || HasAuraName(target, GREATER_BLESSING_OF_MIGHT_1) || HasAuraName(target, BATTLESHOUT_1)); ++ ++ uint8 Class = 0; ++ if (target->GetTypeId() == TYPEID_PLAYER) ++ Class = target->getClass(); ++ else if (Creature* cre = target->ToCreature()) ++ Class = cre->GetBotAI() ? cre->GetBotAI()->GetPlayerClass() : cre->getClass(); ++ ++ switch (Class) ++ { ++ case BOT_CLASS_PRIEST: ++ if (BLESSING_OF_WISDOM && !wisdom && doCast(target, BLESSING_OF_WISDOM)) ++ return true; ++ else if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) ++ return true; ++ else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) ++ return true; ++ break; ++ case BOT_CLASS_DEATH_KNIGHT: ++ case BOT_CLASS_WARRIOR: ++ case BOT_CLASS_PALADIN: ++ case BOT_CLASS_ROGUE: ++ case BOT_CLASS_HUNTER: ++ case BOT_CLASS_SHAMAN: ++ if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) ++ return true; ++ else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) ++ return true; ++ else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) ++ return true; ++ else if (BLESSING_OF_WISDOM && !wisdom && target->getPowerType() == POWER_MANA && doCast(target, BLESSING_OF_WISDOM)) ++ return true; ++ break; ++ default: ++ if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) ++ return true; ++ else if (BLESSING_OF_WISDOM && !wisdom && target->getPowerType() == POWER_MANA && doCast(target, BLESSING_OF_WISDOM)) ++ return true; ++ else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) ++ return true; ++ else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) ++ return true; ++ break; ++ } ++ return false; ++ } ++ ++ void Repentance(uint32 diff, Unit* target = NULL) ++ { ++ temptimer = GC_Timer; ++ if (target) ++ { ++ if (IsSpellReady(REPENTANCE_1, diff, false, 25000) && doCast(target, GetSpell(REPENTANCE_1))) ++ {} ++ } ++ else if (IsSpellReady(REPENTANCE_1, diff, false)) ++ { ++ Unit* u = FindStunTarget(); ++ if (u && u->GetVictim() != me && doCast(u, GetSpell(REPENTANCE_1))) ++ {} ++ } ++ GC_Timer = temptimer; ++ } ++ ++ void Counter(uint32 diff) ++ { ++ if (IsCasting()) ++ return; ++ if (Rand() > 60) ++ return; ++ ++ Unit* target = IsSpellReady(REPENTANCE_1, diff, false, 25000) ? FindCastingTarget(20, 0, false, REPENTANCE_1) : NULL; ++ if (target) ++ Repentance(diff, target); //first check repentance ++ else if (IsSpellReady(TURN_EVIL_1, diff, false, 1500)) ++ { ++ target = FindCastingTarget(20, 0, false, TURN_EVIL_1); ++ temptimer = GC_Timer; ++ if (target && doCast(target, GetSpell(TURN_EVIL_1), true)) ++ GC_Timer = temptimer; ++ } ++ else if (IsSpellReady(HOLY_WRATH_1, diff, false, 8000) && HasRole(BOT_ROLE_DPS)) ++ { ++ target = FindCastingTarget(8, 0, false, TURN_EVIL_1); //here we check target as with turn evil cuz of same requirements ++ temptimer = GC_Timer; ++ if (target && doCast(me, GetSpell(HOLY_WRATH_1))) ++ GC_Timer = temptimer; ++ } ++ else if (IsSpellReady(HAMMER_OF_JUSTICE_1, diff, /*true*/false, 7000)) ++ { ++ target = FindCastingTarget(10); ++ if (target && doCast(opponent, GetSpell(HAMMER_OF_JUSTICE_1))) ++ {} ++ } ++ } ++ ++ void TurnEvil(uint32 diff) ++ { ++ if (!IsSpellReady(TURN_EVIL_1, diff) || IsCasting() || Rand() > 50 || ++ FindAffectedTarget(GetSpell(TURN_EVIL_1), me->GetGUID(), 50)) ++ return; ++ Unit* target = FindUndeadCCTarget(20, TURN_EVIL_1); ++ if (target && ++ (target != me->GetVictim() || GetHealthPCT(me) < 70 || target->GetVictim() == master) && ++ doCast(target, GetSpell(TURN_EVIL_1), true)) ++ return; ++ else ++ if ((opponent->GetCreatureType() == CREATURE_TYPE_UNDEAD || opponent->GetCreatureType() == CREATURE_TYPE_DEMON) && ++ !CCed(opponent) && ++ opponent->GetVictim() && !IsTank(opponent->GetVictim()) && opponent->GetVictim() != me && ++ GetHealthPCT(me) < 90 && ++ doCast(opponent, GetSpell(TURN_EVIL_1), true)) ++ return; ++ } ++ ++ void Wrath(uint32 diff) ++ { ++ if (!IsSpellReady(HOLY_WRATH_1, diff) || !HasRole(BOT_ROLE_DPS) || Rand() > 50) ++ return; ++ if ((opponent->GetCreatureType() == CREATURE_TYPE_UNDEAD || opponent->GetCreatureType() == CREATURE_TYPE_DEMON) && ++ me->GetExactDist(opponent) <= 8 && doCast(me, GetSpell(HOLY_WRATH_1))) ++ {} ++ else ++ { ++ Unit* target = FindUndeadCCTarget(8, GetSpell(HOLY_WRATH_1)); ++ if (target && doCast(me, GetSpell(HOLY_WRATH_1))) ++ {} ++ } ++ } ++ ++ void DoNormalAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ Counter(diff); ++ TurnEvil(diff); ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //HAMMER OF WRATH //custom GCD check ++ if (IsSpellReady(HOW_1, diff, false) && GC_Timer <= 300 && HasRole(BOT_ROLE_DPS) && Rand() < 30 && GetHealthPCT(opponent) < 19 && ++ me->GetExactDist(opponent) < 30) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(HOW_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //HAND OF RECKONING //No GCD ++ Unit* u = opponent->GetVictim(); ++ if (IsSpellReady(HANDOFRECKONING_1, diff, false) && me->GetExactDist(opponent) < 30 && ++ HasRole(BOT_ROLE_DPS) && u && u != me && !IsTank(u) && Rand() < 50 && ++ (IsInBotParty(u) || IsTank())) ++ { ++ Creature* cre = opponent->ToCreature(); ++ temptimer = GC_Timer; ++ if (((cre && cre->isWorldBoss() && !IsMeleeClass(u->getClass())) || ++ GetHealthPCT(u) < GetHealthPCT(me) - 5 || IsTank()) && ++ doCast(opponent, GetSpell(HANDOFRECKONING_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ if (IsSpellReady(HAMMER_OF_JUSTICE_1, diff) && !CCed(opponent) && ++ me->GetExactDist(opponent) < 10 && Rand() < 20) ++ { ++ if (doCast(opponent, GetSpell(HAMMER_OF_JUSTICE_1))) ++ return; ++ } ++ ++ if (IsSpellReady(JUDGEMENT_1, diff) && HasRole(BOT_ROLE_DPS) && me->GetExactDist(opponent) < 10 && ++ Rand() < 50 && me->HasAura(GetSpell(SEAL_OF_COMMAND_1))) ++ { ++ if (doCast(opponent, GetSpell(JUDGEMENT_1))) ++ return; ++ } ++ ++ if (IsSpellReady(CONSECRATION_1, diff) && HasRole(BOT_ROLE_DPS) && me->GetDistance(opponent) < 7 && ++ !opponent->isMoving() && Rand() < 50) ++ { ++ if (doCast(me, GetSpell(CONSECRATION_1))) ++ return; ++ } ++ ++ if (IsSpellReady(AVENGING_WRATH_1, diff, false) && HasRole(BOT_ROLE_DPS) && ++ opponent->GetHealth() > (master->GetMaxHealth()*2)/3 && Rand() < 25) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(AVENGING_WRATH_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ if (IsSpellReady(CRUSADER_STRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && me->GetDistance(opponent) < 5) ++ { ++ if (doCast(opponent, GetSpell(CRUSADER_STRIKE_1))) ++ return; ++ } ++ ++ if (IsSpellReady(EXORCISM_1, diff) && HasRole(BOT_ROLE_DPS) && me->GetExactDist(opponent) < 30 && ++ (!IsTank() || opponent->GetVictim() == me || opponent->IsVehicle() || opponent->ToPlayer())) ++ { ++ if (doCast(opponent, GetSpell(EXORCISM_1), me->HasAura(THE_ART_OF_WAR_BUFF))) ++ return; ++ } ++ ++ Wrath(diff); ++ ++ if (IsSpellReady(DIVINE_STORM_1, diff) && HasRole(BOT_ROLE_DPS) && me->GetExactDist(opponent) < 7) ++ { ++ if (doCast(opponent, GetSpell(DIVINE_STORM_1))) ++ return; ++ } ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Fanaticism: 18% additional critical chance for all Judgements (not shure which check is right) ++ if (lvl >= 45 && (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_JUDGEMENT)) ++ aftercrit += 18.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ //if (crit) ++ //{ ++ //} ++ //Sanctity of Battle: 15% bonus damage for Exorcism and Crusader Strike ++ if (lvl >= 25 && spellId == GetSpell(EXORCISM_1)) ++ pctbonus += 0.15f; ++ //The Art of War (damage part): 10% bonus damage for Judgements, Crusader Strike and Divine Storm ++ if (lvl >= 40 && ++ (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || ++ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_JUDGEMENT || ++ spellId == GetSpell(CRUSADER_STRIKE_1) || ++ spellId == GetSpell(DIVINE_STORM_1))) ++ pctbonus += 0.1f; ++ //Judgements of the Pure (damage part): 25% bonus damage for Judgements and Seals ++ if (lvl >= 50 && ++ (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || ++ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_JUDGEMENT || ++ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_SEAL || ++ spellId == JUDGEMENT_OF_COMMAND_DAMAGE)) ++ pctbonus += 0.25f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Sanctified Wrath: 50% additional critical chance for Hammer of Wrath ++ if (lvl >= 45 && spellId == GetSpell(HOW_1)) ++ aftercrit += 50.f; ++ //Holy Power: 5% additional critical chance for Holy spells ++ if (lvl >= 35 && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_HOLY)) ++ aftercrit += 5.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ //if (crit) ++ //{ ++ //} ++ ++ //Judgements of the Pure (damage part): 25% bonus damage for Judgements and Seals ++ if (lvl >= 50 && ++ (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || ++ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_JUDGEMENT || ++ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_SEAL || ++ spellId == JUDGEMENT_OF_COMMAND_DAMAGE)) ++ pctbonus += 0.25f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType /*damagetype*/, uint32 /*stack*/) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ float flat_mod = 0.0f; ++ ++ //Healing Light: 12% bonus healing for Holy Light, Flash of Light and Holy Shock ++ if (lvl >= 15 && ++ (spellId == GetSpell(HOLY_LIGHT_1) || ++ spellId == GetSpell(FLASH_OF_LIGHT_1) || ++ spellId == GetSpell(HOLY_SHOCK_1))) ++ pctbonus += 0.12f; ++ ++ heal = heal * (1.0f + pctbonus) + flat_mod; ++ } ++ ++ void ApplyClassCritMultiplierHeal(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType /*attackType*/) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float aftercrit = 0.0f; ++ ++ //Sanctified Light: 6% additional critical chance for Holy Light and Holy Shock ++ if (lvl >= 30 && (spellId == GetSpell(HOLY_LIGHT_1) || spellId == GetSpell(HOLY_SHOCK_1))) ++ aftercrit += 6.f; ++ //Holy Power: 5% additional critical chance for Holy spells ++ if (lvl >= 35 && (schoolMask & SPELL_SCHOOL_MASK_HOLY)) ++ aftercrit += 5.f; ++ ++ crit_chance += aftercrit; ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if ((spellId == GetSpell(EXORCISM_1) || spellId == GetSpell(FLASH_OF_LIGHT_1)) && ++ me->HasAura(THE_ART_OF_WAR_BUFF)) ++ { ++ //Art of War: consume buff ++ me->RemoveAura(THE_ART_OF_WAR_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ ++ if (spellId == GetSpell(HOF_1)) ++ { ++ //Guardian's Favor part 2 (handled separately) ++ if (Aura* hof = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = hof->GetDuration() + 4000; ++ hof->SetDuration(dur); ++ hof->SetMaxDuration(dur); ++ } ++ } ++ ++ //if (!IAmFree()) ++ { ++ if (spellId == GetSpell(BLESSING_OF_KINGS_1) || spellId == GetSpell(BLESSING_OF_MIGHT_1) || ++ spellId == GetSpell(BLESSING_OF_WISDOM_1) || spellId == GetSpell(BLESSING_OF_SANCTUARY_1)) ++ { ++ //Blessings duration 1h ++ if (Aura* bless = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = HOUR * IN_MILLISECONDS; ++ bless->SetDuration(dur); ++ bless->SetMaxDuration(dur); ++ } ++ } ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ //Custom OnHit() handlers ++ ++ if (damageType == DIRECT_DAMAGE) ++ { ++ //The Art of War: 20% on autoattack ++ if (me->getLevel() >= 33) ++ if (urand(1,100) <= 20) ++ me->CastSpell(me, THE_ART_OF_WAR_BUFF, true); ++ } ++ ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void HealReceived(Unit* healer, uint32& heal) ++ { ++ //Spiritual Attunement ++ if (heal && me->getLevel() >= 40 && healer != me && HasRole(BOT_ROLE_TANK) && GetLostHP(me)) ++ { ++ if (int32 basepoints = int32(CalculatePct(std::min(heal, GetLostHP(me)), 10))) ++ me->CastCustomSpell(me, SPIRITUAL_ATTUNEMENT_ENERGIZE, &basepoints, NULL, NULL, true); ++ } ++ ++ //bot_ai::HealReceived(healer, heal); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 /*diff*/) ++ { ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(FLASH_OF_LIGHT_1); ++ InitSpellMap(HOLY_LIGHT_1); ++ InitSpellMap(LAY_ON_HANDS_1); ++ InitSpellMap(SACRED_SHIELD_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(HOLY_SHOCK_1) : RemoveSpell(HOLY_SHOCK_1); ++ InitSpellMap(CLEANSE_1); ++ InitSpellMap(REDEMPTION_1); ++ InitSpellMap(HAMMER_OF_JUSTICE_1); ++ /*Talent*/lvl >= 45 ? InitSpellMap(REPENTANCE_1) : RemoveSpell(REPENTANCE_1); ++ InitSpellMap(TURN_EVIL_1); ++ InitSpellMap(HOLY_WRATH_1); ++ InitSpellMap(EXORCISM_1); ++ /*Talent*/lvl >= 25 ? InitSpellMap(SEAL_OF_COMMAND_1) : RemoveSpell(SEAL_OF_COMMAND_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(CRUSADER_STRIKE_1) : RemoveSpell(CRUSADER_STRIKE_1); ++ InitSpellMap(JUDGEMENT_1); ++ InitSpellMap(CONSECRATION_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(DIVINE_STORM_1) : RemoveSpell(DIVINE_STORM_1); ++ InitSpellMap(HOW_1); ++ InitSpellMap(AVENGING_WRATH_1); ++ InitSpellMap(BLESSING_OF_MIGHT_1); ++ InitSpellMap(BLESSING_OF_WISDOM_1); ++ InitSpellMap(BLESSING_OF_KINGS_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(BLESSING_OF_SANCTUARY_1) : RemoveSpell(BLESSING_OF_SANCTUARY_1); ++ InitSpellMap(DEVOTION_AURA_1); ++ InitSpellMap(CONCENTRATION_AURA_1); ++ InitSpellMap(DIVINE_PLEA_1); ++ InitSpellMap(HAND_OF_PROTECTION_1); ++ InitSpellMap(HOF_1); ++ InitSpellMap(HOS_1); ++ InitSpellMap(HANDOFRECKONING_1); ++ ++ /*SPECIAL*/InitSpellMap(ARDENT_DEFENDER_HEAL, true); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ //RefreshAura(SPELLDMG, /*level >= 78 ? 5 : level >= 75 ? 4 */level >= 55 ? 3 : level >= 35 ? 2 : level >= 15 ? 1 : 0); ++ //RefreshAura(SPELLDMG2, level >= 55 ? 3 : level >= 35 ? 2 : level >= 15 ? 1 : 0); ++ RefreshAura(PURE1, level >= 55 ? 1 : 0); ++ RefreshAura(WISE, level >= 35 ? 1 : 0); ++ RefreshAura(RECKONING5, level >= 50 ? 1 : 0); ++ RefreshAura(RECKONING4, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(RECKONING3, level >= 40 && level < 45 ? 1 : 0); ++ RefreshAura(RECKONING2, level >= 35 && level < 40 ? 1 : 0); ++ RefreshAura(RECKONING1, level >= 30 && level < 35 ? 1 : 0); ++ RefreshAura(VENGEANCE3, level >= 30 ? 1 : 0); ++ RefreshAura(VENGEANCE2, level >= 27 && level < 30 ? 1 : 0); ++ RefreshAura(VENGEANCE1, level >= 25 && level < 27 ? 1 : 0); ++ RefreshAura(SHOFL3, level >= 60 ? 1 : 0); ++ RefreshAura(SHOFL2, level >= 55 && level < 60 ? 1 : 0); ++ RefreshAura(SHOFL1, level >= 50 && level < 55 ? 1 : 0); ++ RefreshAura(SACRED_CLEANSING, level >= 45 ? 1 : 0); ++ RefreshAura(DIVINE_PURPOSE, level >= 35 ? 1 : 0); ++ RefreshAura(VINDICATION2, level >= 25 ? 1 : 0); ++ RefreshAura(VINDICATION1, level >= 20 && level < 25 ? 1 : 0); ++ RefreshAura(LAYHANDS, level >= 30 ? 1 : 0); ++ RefreshAura(FANATICISM, level >= 20 ? 2 : 0); ++ RefreshAura(ARDENT_DEFENDER, level >= 40 ? 1 : 0); ++ RefreshAura(ILLUMINATION, level >= 20 ? 1 : 0); ++ RefreshAura(INFUSION_OF_LIGHT, level >= 55 ? 1 : 0); //NYI ++ RefreshAura(REDOUBT3, level >= 68 ? 2 : level >= 55 ? 1 : 0); ++ RefreshAura(REDOUBT2, level >= 50 && level < 55 ? 1 : 0); ++ RefreshAura(REDOUBT1, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(GLYPH_HOLY_LIGHT, level >= 15 ? 1 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case FLASH_OF_LIGHT_1: ++ case HOLY_LIGHT_1: ++ case LAY_ON_HANDS_1: ++ case HOF_1: ++ case SACRED_SHIELD_1: ++ case HOLY_SHOCK_1: ++ case CLEANSE_1: ++ case HAND_OF_PROTECTION_1: ++ case HOS_1: ++ case SEAL_OF_COMMAND_1: ++ case DIVINE_PLEA_1: ++ case AVENGING_WRATH_1: ++ case BLESSING_OF_MIGHT_1: ++ case BLESSING_OF_WISDOM_1: ++ case BLESSING_OF_KINGS_1: ++ case BLESSING_OF_SANCTUARY_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ ++ //uint32 GetBlessingsMask(Unit const*) const ++ //Scans target for auras which are related to paladin's blessings ++ //(even if aura is just incompatible with one) ++ //returns applied blessings mask ++ //used for finding out which blessings target lacks ++ uint32 GetBlessingsMask(Unit const* target) const ++ { ++ uint32 mask = 0; ++ ++ bool blessing; ++ Unit::AuraApplicationMap const& aurapps = target->GetAppliedAuras(); ++ for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) ++ { ++ blessing = true; ++ switch (itr->second->GetBase()->GetSpellInfo()->GetFirstRankSpell()->Id) ++ { ++ case BLESSING_OF_WISDOM_1: ++ case GREATER_BLESSING_OF_WISDOM_1: ++ mask |= SPECIFIC_BLESSING_WISDOM; ++ break; ++ case BLESSING_OF_KINGS_1: ++ case GREATER_BLESSING_OF_KINGS_1: ++ mask |= SPECIFIC_BLESSING_KINGS; ++ break; ++ case BLESSING_OF_SANCTUARY_1: ++ case GREATER_BLESSING_OF_SANCTUARY_1: ++ mask |= SPECIFIC_BLESSING_SANCTUARY; ++ break; ++ case BLESSING_OF_MIGHT_1: ++ case GREATER_BLESSING_OF_MIGHT_1: ++ case BATTLESHOUT_1: ++ mask |= SPECIFIC_BLESSING_MIGHT; ++ break; ++ default: ++ blessing = false; //next aura ++ break; ++ } ++ ++ if (blessing && itr->second->GetBase()->GetCasterGUID() == me->GetGUID()) ++ mask |= SPECIFIC_BLESSING_MY_BLESSING; ++ } ++ ++ return mask; ++ } ++ ++ enum PaladinBaseSpells// all orignals ++ { ++ FLASH_OF_LIGHT_1 = 19750, ++ HOLY_LIGHT_1 = 635, ++ LAY_ON_HANDS_1 = 633, ++ REDEMPTION_1 = 7328, ++ HOF_1 /*Hand of Freedom*/ = 1044, ++ SACRED_SHIELD_1 = 53601, ++ HOLY_SHOCK_1 = 20473, ++ CLEANSE_1 = 4987, ++ HAND_OF_PROTECTION_1 = 1022, ++ HOS_1 /*Hand of salvation*/ = 1038, ++ SEAL_OF_COMMAND_1 = 20375, ++ HANDOFRECKONING_1 = 62124, ++ DIVINE_PLEA_1 = 54428, ++ REPENTANCE_1 = 20066, ++ TURN_EVIL_1 = 10326, ++ CRUSADER_STRIKE_1 = 35395, ++ JUDGEMENT_1 = 20271, ++ CONSECRATION_1 = 26573, ++ HAMMER_OF_JUSTICE_1 = 853, ++ DIVINE_STORM_1 = 53385, ++ HOW_1 /*Hammer of Wrath*/ = 24275, ++ EXORCISM_1 = 879, ++ HOLY_WRATH_1 = 2812, ++ AVENGING_WRATH_1 = 31884, ++ BLESSING_OF_MIGHT_1 = 19740, ++ BLESSING_OF_WISDOM_1 = 19742, ++ BLESSING_OF_KINGS_1 = 20217, ++ BLESSING_OF_SANCTUARY_1 = 20911, ++ DEVOTION_AURA_1 = 465, ++ CONCENTRATION_AURA_1 = 19746 ++ }; ++ enum PaladinPassives ++ { ++ //Talents ++ DIVINE_PURPOSE = 31872, ++ PURE1/*Judgements of the Pure*/ = 54155, ++ WISE/*Judgements of the Wise*/ = 31878, ++ SACRED_CLEANSING = 53553,//rank 3 ++ RECKONING1 = 20177, ++ RECKONING2 = 20179, ++ RECKONING3 = 20181, ++ RECKONING4 = 20180, ++ RECKONING5 = 20182, ++ VINDICATION1 = 9452 ,//rank 1 ++ VINDICATION2 = 26016,//rank 2 ++ LAYHANDS /*Improved LOH rank 2*/ = 20235, ++ FANATICISM = 31881,//rank 3 ++ //RIGHTEOUS_VENGEANCE1 = 53380,//rank 1 ++ //RIGHTEOUS_VENGEANCE2 = 53381,//rank 2 ++ //RIGHTEOUS_VENGEANCE3 = 53382,//rank 3 ++ VENGEANCE1 = 20049,//rank 1 ++ VENGEANCE2 = 20056,//rank 2 ++ VENGEANCE3 = 20057,//rank 3 ++ SHOFL1 /*Sheath of Light*/ = 53501,//rank 1 ++ SHOFL2 = 53502,//rank 2 ++ SHOFL3 = 53503,//rank 3 ++ ARDENT_DEFENDER = 31852,//rank 3 ++ ILLUMINATION = 20215,//rank 5 ++ INFUSION_OF_LIGHT = 53576,//rank 2 ++ REDOUBT1 = 20127,//rank 3 ++ REDOUBT2 = 20130,//rank 3 ++ REDOUBT3 = 20135,//rank 3 ++ //Glyphs ++ GLYPH_HOLY_LIGHT = 54937 ++ //other ++ //SPELLDMG/*Arcane Instability-mage*/ = 15060,//rank3 3% dam/crit ++ //SPELLDMG2/*Earth and Moon - druid*/ = 48511 //rank3 6% dam ++ }; ++ ++ enum PaladinSpecial ++ { ++ NOAURA = 0, ++ DEVOTIONAURA = 1, ++ CONCENTRATIONAURA = 2, ++ ++ THE_ART_OF_WAR_BUFF = 59578, ++ FORBEARANCE_AURA = 25771, ++ ++ GREATER_BLESSING_OF_MIGHT_1 = 25782, ++ GREATER_BLESSING_OF_WISDOM_1 = 25894, ++ GREATER_BLESSING_OF_KINGS_1 = 25898, ++ GREATER_BLESSING_OF_SANCTUARY_1 = 25899, ++ BATTLESHOUT_1 = 6673, ++ ++ ARDENT_DEFENDER_HEAL = 66235, ++ JUDGEMENT_OF_COMMAND_DAMAGE = 20467, ++ SPIRITUAL_ATTUNEMENT_ENERGIZE = 31786, ++ ++ SPECIFIC_BLESSING_WISDOM = 0x01, ++ SPECIFIC_BLESSING_KINGS = 0x02, ++ SPECIFIC_BLESSING_SANCTUARY = 0x04, ++ SPECIFIC_BLESSING_MIGHT = 0x08, ++ SPECIFIC_BLESSING_MY_BLESSING = 0x10 ++ }; ++ }; ++}; ++ ++void AddSC_paladin_bot() ++{ ++ new paladin_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_priest_ai.cpp b/src/server/game/AI/NpcBots/bot_priest_ai.cpp +new file mode 100644 +index 0000000..47cadc7 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_priest_ai.cpp +@@ -0,0 +1,1066 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++//#include "WorldSession.h" ++/* ++Priest NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - Around 50% ++TODO: maybe remove Divine Spirit or so, too much buffs ++*/ ++class priest_bot : public CreatureScript ++{ ++public: ++ priest_bot() : CreatureScript("priest_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new priest_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct priest_botAI : public bot_minion_ai ++ { ++ priest_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_PRIEST; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_PRIEST) != SPELL_CAST_OK) ++ return false; ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ bool MassGroupHeal(Player* player, uint32 diff) ++ { ++ if (IAmFree() || !player->GetGroup()) return false; ++ if (IsCasting()) return false; ++ if (Rand() > 35) return false; ++ ++ if (IsSpellReady(DIVINE_HYMN_1, diff, false)) ++ { ++ Group* gr = player->GetGroup(); ++ uint8 LHPcount = 0; ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->IsInWorld() || me->GetMap() != tPlayer->FindMap() || ++ tPlayer->IsBeingTeleported() || tPlayer->isPossessed() || tPlayer->IsCharmed()) continue; ++ if (tPlayer->IsAlive()) ++ { ++ if (me->GetExactDist(tPlayer) > 35) continue; ++ uint8 pct = 50 + tPlayer->getAttackers().size()*10; ++ pct = pct < 80 ? pct : 80; ++ if (GetHealthPCT(tPlayer) < pct && GetLostHP(tPlayer) > 4000) ++ ++LHPcount; ++ } ++ if (LHPcount > 1) ++ break; ++ if (!tPlayer->HaveBot()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (bot && GetHealthPCT(bot) < 40 && me->GetExactDist(bot) < 30) ++ ++LHPcount; ++ if (LHPcount > 1) ++ break; ++ } ++ } ++ if (LHPcount > 1 && doCast(me, GetSpell(DIVINE_HYMN_1))) ++ return true; ++ } ++ if (GetSpell(PRAYER_OF_HEALING_1)) ++ { ++ Group* gr = player->GetGroup(); ++ Unit* castTarget = NULL; ++ uint8 LHPcount = 0; ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ uint8 lowestPCT = 100; ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->IsInWorld() || me->GetMap() != tPlayer->GetMap() || ++ tPlayer->IsBeingTeleported() || tPlayer->isPossessed() || tPlayer->IsCharmed()) continue; ++ if (tPlayer->IsAlive()) ++ { ++ if (me->GetExactDist(tPlayer) > 25) continue; ++ if (GetHealthPCT(tPlayer) < 85) ++ { ++ ++LHPcount; ++ if (GetHealthPCT(tPlayer) < lowestPCT) ++ lowestPCT = GetHealthPCT(tPlayer); ++ castTarget = tPlayer; ++ } ++ } ++ if (LHPcount > 2) ++ break; ++ if (!tPlayer->HaveBot()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (bot && GetHealthPCT(bot) < 70 && me->GetExactDist(bot) < 15) ++ { ++ ++LHPcount; ++ if (GetHealthPCT(bot) < lowestPCT) ++ lowestPCT = GetHealthPCT(bot); ++ castTarget = bot; ++ } ++ if (LHPcount > 2) ++ break; ++ } ++ } ++ ++ if (LHPcount > 2 && castTarget && doCast(castTarget, GetSpell(PRAYER_OF_HEALING_1))) ++ return true; ++ } ++ ++ return false; ++ } ++ ++ bool ShieldTarget(Unit* target, uint32 diff) ++ { ++ if (!IsSpellReady(PW_SHIELD_1, diff, false) || IsCasting() || Rand() > 50) ++ return false; ++ if (me->GetExactDist(target) > 40) ++ return false; ++ if (target->getAttackers().empty() && GetHealthPCT(target) > 33 && ++ !target->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ++ return false; ++ if (target->HasAura(WEAKENED_SOUL_DEBUFF) || HasAuraName(target, PW_SHIELD_1)) ++ return false; ++ ++ if (doCast(target, GetSpell(PW_SHIELD_1))) ++ { ++ GC_Timer = 800; ++ return true; ++ } ++ return false; ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ Disperse(diff); ++ BreakCC(diff); ++ if (CCed(me)) return; ++ DoDevCheck(diff); ++ ++ if (Potion_cd <= diff && GetManaPCT(me) < 33) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ //check possible fear ++ doDefend(diff); ++ //buff and heal master's group ++ MassGroupHeal(master, diff); ++ BuffAndHealGroup(master, diff); ++ CureGroup(master, GetSpell(DISPEL_MAGIC_1), diff); ++ CureGroup(master, GetSpell(CURE_DISEASE_1), diff); ++ //ShieldGroup(master); ++ if (master->IsInCombat() || me->IsInCombat()) ++ { ++ CheckDispel(diff); ++ CheckSilence(diff); ++ } ++ ++ if (me->IsInCombat()) ++ CheckShackles(diff); ++ else ++ DoNonCombatActions(diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_PRIEST)) ++ return; ++ ++ AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ if (GetHealthPCT(master) > 90 && GetManaPCT(me) > 35 && GetHealthPCT(me) > 90 && ++ (m_attackers.size() < 4 || b_attackers.size() + m_attackers.size() < 3) && ++ !IsCasting()) ++ //general rule ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent); ++ } ++ else ++ return; ++ float dist = me->GetExactDist(opponent); ++ if (HasRole(BOT_ROLE_DPS) && dist < 30) ++ { ++ if (IsSpellReady(SW_DEATH_1, diff, false) && Rand() < 50 && ++ (GetHealthPCT(opponent) < 15 || opponent->GetHealth() < me->GetMaxHealth()/6) && ++ doCast(opponent, GetSpell(SW_DEATH_1))) ++ return; ++ if (IsSpellReady(SW_PAIN_1, diff) && Rand() < 25 && ++ opponent->GetHealth() > me->GetMaxHealth()/4 && ++ !HasAuraName(opponent, SW_PAIN_1, me->GetGUID()) && ++ doCast(opponent, GetSpell(SW_PAIN_1))) ++ return; ++ if (IsSpellReady(VAMPIRIC_TOUCH_1, diff) && Rand() < 50 && ++ opponent->GetHealth() > me->GetMaxHealth()/4 && ++ !HasAuraName(opponent, VAMPIRIC_TOUCH_1, me->GetGUID()) && ++ doCast(opponent, GetSpell(VAMPIRIC_TOUCH_1))) ++ return; ++ if (IsSpellReady(DEVOURING_PLAGUE_1, diff) && !Devcheck && Rand() < 30 && ++ opponent->GetHealth() > me->GetMaxHealth()/3 && ++ !HasAuraName(opponent, DEVOURING_PLAGUE_1, me->GetGUID()) && ++ doCast(opponent, GetSpell(DEVOURING_PLAGUE_1))) ++ return; ++ if (IsSpellReady(MIND_BLAST_1, diff) && Rand() < 35 && ++ (!GetSpell(VAMPIRIC_TOUCH_1) || HasAuraName(opponent, VAMPIRIC_TOUCH_1, me->GetGUID())) && ++ doCast(opponent, GetSpell(MIND_BLAST_1))) ++ return; ++ if (IsSpellReady(MIND_FLAY_1, diff, false) && Rand() < 20 && ++ (opponent->isMoving() || opponent->GetHealth() < me->GetMaxHealth()/5 || ++ (HasAuraName(opponent, SW_PAIN_1, me->GetGUID()) && HasAuraName(opponent, DEVOURING_PLAGUE_1, me->GetGUID()))) && ++ doCast(opponent, GetSpell(MIND_FLAY_1))) ++ return; ++ if (IsSpellReady(MIND_SEAR_1, diff, false) && !opponent->isMoving() && dist < 35 && Rand() < 50 && ++ HasAuraName(opponent, SW_PAIN_1, me->GetGUID()) && ++ HasAuraName(opponent, DEVOURING_PLAGUE_1, me->GetGUID())) ++ { ++ if (Unit* u = FindSplashTarget(30, opponent)) ++ if (doCast(u, GetSpell(MIND_SEAR_1))) ++ return; ++ } ++ }//endif opponent ++ }//endif damage ++ //check horror after dots/damage ++ if (IsSpellReady(PSYCHIC_HORROR_1, diff, false) && ++ opponent->GetCreatureType() != CREATURE_TYPE_UNDEAD && ++ opponent->GetHealth() > me->GetMaxHealth()/5 && !CCed(opponent) && Rand() < 30 && ++ me->GetExactDist(opponent) < 30 && !HasAuraName(opponent, PSYCHIC_HORROR_1)) ++ { ++ if (doCast(opponent, GetSpell(PSYCHIC_HORROR_1))) ++ return; ++ } ++ }//end UpdateAI ++ ++ bool HealTarget(Unit* target, uint8 hp, uint32 diff) ++ { ++ if (!HasRole(BOT_ROLE_HEAL)) ++ return false; ++ if (hp > 98) ++ return false; ++ if (!target || !target->IsAlive() || me->GetExactDist(target) > 40) ++ return false; ++ if (Rand() > 50 + 20*target->IsInCombat() + 50*master->GetMap()->IsRaid()) ++ return false; ++ ++ //GUARDIAN SPIRIT ++ if (IsSpellReady(GUARDIAN_SPIRIT_1, diff, false) && !IAmFree() && target->IsInCombat() && ++ !target->getAttackers().empty() && hp < (5 + std::min(20, uint8(target->getAttackers().size())*5)) && ++ IsInBotParty(target) && ++ Rand() < 80 && !target->HasAura(GetSpell(GUARDIAN_SPIRIT_1))) ++ { ++ temptimer = GC_Timer; ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (doCast(target, GetSpell(GUARDIAN_SPIRIT_1))) ++ { ++ GC_Timer = temptimer; ++ if (target->GetTypeId() == TYPEID_PLAYER) ++ BotWhisper("Guardian Spirit on you!", target->ToPlayer()); ++ else if (!IAmFree()) ++ { ++ std::ostringstream msg; ++ msg << "Guardian Spirit on " << (target == me ? "myself" : target->GetName()) << '!'; ++ BotWhisper(msg.str().c_str(), master); ++ } ++ ++ return true; ++ } ++ } ++ ++ if (IsCasting()) return false; ++ ++ //PAIN SUPPRESSION ++ if (hp < 35 && IsSpellReady(PAIN_SUPPRESSION_1, diff, false) && ++ (target->IsInCombat() || !target->getAttackers().empty()) && Rand() < 50 && ++ !target->HasAura(GetSpell(PAIN_SUPPRESSION_1))) ++ { ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(PAIN_SUPPRESSION_1))) ++ { ++ GC_Timer = temptimer; ++ if (target->GetTypeId() == TYPEID_PLAYER) ++ BotWhisper("Pain Suppression on you!", target->ToPlayer()); ++ else if (!IAmFree()) ++ { ++ std::ostringstream msg; ++ msg << "Guardin Spirit on " << (target == me ? "myself" : target->GetName()) << '!'; ++ BotWhisper(msg.str().c_str(), master); ++ } ++ ++ return true; ++ } ++ } ++ ++ //Now Heals Requires GCD ++ if ((hp < 80 || !target->getAttackers().empty()) && ++ ShieldTarget(target, diff)) ++ return true; ++ ++ //PENANCE/Greater Heal ++ if (hp < 75 || GetLostHP(target) > 4000) ++ { ++ if (IsSpellReady(PENANCE_1, diff, false) && !me->isMoving() && Rand() < 80 && ++ (target->GetTypeId() != TYPEID_PLAYER || ++ !(target->ToPlayer()->IsCharmed() || target->ToPlayer()->isPossessed())) && ++ doCast(target, GetSpell(PENANCE_1))) ++ return true; ++ else if (HEAL && Heal_Timer <= diff && GC_Timer <= diff && hp > 50 && Rand() < 70 && ++ doCast(target, HEAL)) ++ { ++ Heal_Timer = 2500; ++ return true; ++ } ++ } ++ //Flash Heal ++ if (IsSpellReady(FLASH_HEAL_1, diff) && ++ ((hp > 75 && hp < 90) || hp < 50 || GetLostHP(target) > 1500) && ++ doCast(target, GetSpell(FLASH_HEAL_1))) ++ return true; ++ //maintain HoTs ++ Unit* u = target->GetVictim(); ++ Creature* boss = u && u->ToCreature() && u->ToCreature()->isWorldBoss() ? u->ToCreature() : NULL; ++ bool tanking = IsTank(target) && boss; ++ //Renew ++ if (IsSpellReady(RENEW_1, diff) && ++ ((hp < 98 && hp > 70) || GetLostHP(target) > 500 || tanking) && ++ !HasAuraName(target, RENEW_1, me->GetGUID()) && ++ doCast(target, GetSpell(RENEW_1))) ++ { ++ GC_Timer = 800; ++ return true; ++ } ++ ++ return false; ++ } ++ ++ bool BuffTarget(Unit* target, uint32 diff) ++ { ++ if (!target || !target->IsInWorld() || target->isDead() || ++ GC_Timer > diff || me->GetExactDist(target) > 30 || Rand() > 20) ++ return false; ++ ++ if (IsSpellReady(FEAR_WARD_1, diff, false) && ++ !target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, MECHANIC_FEAR) && ++ doCast(target, GetSpell(FEAR_WARD_1))) ++ { ++ GC_Timer = 800; ++ return true; ++ } ++ ++ if (target == me) ++ { ++ uint32 INNER_FIRE = GetSpell(INNER_FIRE_1); ++ if (INNER_FIRE && !me->HasAura(INNER_FIRE) && ++ doCast(me, INNER_FIRE)) ++ { ++ GC_Timer = 800; ++ return true; ++ } ++ uint32 VAMPIRIC_EMBRACE = GetSpell(VAMPIRIC_EMBRACE_1); ++ if (VAMPIRIC_EMBRACE && !me->HasAura(VAMPIRIC_EMBRACE) && ++ doCast(me, VAMPIRIC_EMBRACE)) ++ { ++ GC_Timer = 800; ++ return true; ++ } ++ } ++ ++ if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; ++ ++ if (uint32 PW_FORTITUDE = GetSpell(PW_FORTITUDE_1)) ++ { ++ if (!HasAuraName(target, PW_FORTITUDE) && ++ doCast(target, PW_FORTITUDE)) ++ { ++ /*GC_Timer = 800;*/ ++ return true; ++ } ++ } ++ if (uint32 SHADOW_PROTECTION = GetSpell(SHADOW_PROTECTION_1)) ++ { ++ if (!HasAuraName(target, SHADOW_PROTECTION) && ++ doCast(target, SHADOW_PROTECTION)) ++ { ++ /*GC_Timer = 800;*/ ++ return true; ++ } ++ } ++ if (uint32 DIVINE_SPIRIT = GetSpell(DIVINE_SPIRIT_1)) ++ { ++ if (!HasAuraName(target, DIVINE_SPIRIT) && ++ doCast(target, DIVINE_SPIRIT)) ++ { ++ /*GC_Timer = 800;*/ ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || me->IsMounted() || IsCasting()) ++ return; ++ ++ RezGroup(GetSpell(RESURRECTION_1), master); ++ ++ //if (Feasting()) ++ // return; ++ ++ //if (BuffTarget(master, diff)) ++ // return; ++ //if (BuffTarget(me, diff)) ++ // return; ++ } ++ ++ void CheckDispel(uint32 diff) ++ { ++ if (CheckDispelTimer > diff || Rand() > 25 || IsCasting()) ++ return; ++ ++ uint32 DM = GetSpell(DISPEL_MAGIC_1); ++ uint32 MD = GetSpell(MASS_DISPEL_1); ++ ++ if (!DM && !MD) ++ return; ++ ++ if (Unit* target = FindHostileDispelTarget()) ++ { ++ uint32 dm = DM && !target->IsImmunedToSpell(sSpellMgr->GetSpellInfo(DM)) ? DM : MD; ++ if (target && doCast(target, dm)) ++ { ++ CheckDispelTimer = 1000; ++ GC_Timer = 800; ++ return; ++ } ++ } ++ ++ CheckDispelTimer = 2000; //fail ++ } ++ ++ void CheckShackles(uint32 diff) ++ { ++ if (!IsSpellReady(SHACKLE_UNDEAD_1, diff) || IsCasting() || Rand() > 50) ++ return; ++ ++ uint32 SHACKLE_UNDEAD = GetSpell(SHACKLE_UNDEAD_1); ++ if (FindAffectedTarget(SHACKLE_UNDEAD, me->GetGUID())) ++ { ++ Shackle_Timer = 1500; ++ return; ++ } ++ Unit* target = FindUndeadCCTarget(30, SHACKLE_UNDEAD); ++ if (target && doCast(target, SHACKLE_UNDEAD)) ++ { ++ Shackle_Timer = 3000; ++ GC_Timer = 800; ++ } ++ } ++ ++ void CheckSilence(uint32 diff) ++ { ++ if (IsCasting()) return; ++ temptimer = GC_Timer; ++ if (IsSpellReady(SILENCE_1, diff, false)) ++ { ++ if (Unit* target = FindCastingTarget(30)) ++ if (doCast(target, GetSpell(SILENCE_1))) ++ {} ++ } ++ else if (IsSpellReady(PSYCHIC_HORROR_1, diff, false, 20000)) ++ { ++ if (Unit* target = FindCastingTarget(30)) ++ if (doCast(target, GetSpell(PSYCHIC_HORROR_1))) ++ {} ++ } ++ GC_Timer = temptimer; ++ } ++ ++ void doDefend(uint32 diff) ++ { ++ AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ ++ //fear master's attackers ++ if (IsSpellReady(PSYCHIC_SCREAM_1, diff, false)) ++ { ++ if (!m_attackers.empty() && (!IsTank(master) || GetHealthPCT(master) < 75)) ++ { ++ uint8 tCount = 0; ++ for (AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; ++ if (me->GetExactDist((*iter)) > 7) continue; ++ if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue; ++ if (me->IsValidAttackTarget(*iter)) ++ ++tCount; ++ } ++ if (tCount > 1 && doCast(me, GetSpell(PSYCHIC_SCREAM_1))) ++ return; ++ } ++ ++ // Defend myself (psychic horror) ++ if (!b_attackers.empty()) ++ { ++ uint8 tCount = 0; ++ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; ++ if (me->GetExactDist((*iter)) > 7) continue; ++ if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue; ++ if (me->IsValidAttackTarget(*iter)) ++ ++tCount; ++ } ++ if (tCount > 0 && doCast(me, GetSpell(PSYCHIC_SCREAM_1))) ++ return; ++ } ++ } ++ // Heal myself ++ if (GetHealthPCT(me) < 98 && !b_attackers.empty()) ++ { ++ if (ShieldTarget(me, diff)) return; ++ ++ if (IsSpellReady(FADE_1, diff, false) && me->IsInCombat()) ++ { ++ if (b_attackers.empty()) return; ++ uint8 Tattackers = 0; ++ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->isDead()) continue; ++ if (!(*iter)->ToCreature()) continue; ++ if (!(*iter)->CanHaveThreatList()) continue; ++ if (me->GetExactDist((*iter)) < 15) ++ Tattackers++; ++ } ++ if (Tattackers > 0) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(FADE_1))) ++ { ++ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ if ((*iter)->getThreatManager().getThreat(me) > 0.f) ++ (*iter)->getThreatManager().modifyThreatPercent(me, -50); ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ if (GetHealthPCT(me) < 90 && HealTarget(me, GetHealthPCT(me), diff)) ++ return; ++ } ++ } ++ ++ void DoDevCheck(uint32 diff) ++ { ++ if (DevcheckTimer <= diff) ++ { ++ Devcheck = FindAffectedTarget(GetSpell(DEVOURING_PLAGUE_1), me->GetGUID()); ++ DevcheckTimer = 5000; ++ } ++ } ++ ++ void Disperse(uint32 diff) ++ { ++ if (!IsSpellReady(DISPERSION_1, diff) || IsCasting() || Rand() > 60) ++ return; ++ if ((me->getAttackers().size() > 3 && !IsSpellReady(FADE_1, diff, false) && GetHealthPCT(me) < 90) || ++ (GetHealthPCT(me) < 20 && me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || ++ (GetManaPCT(me) < 30) || ++ (me->getAttackers().size() > 1 && me->HasAuraWithMechanic((1<Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //434 new ++ //Improved Fire Blast (part 1): 8% additional crit chance for Fire Blast ++ //if (lvl >= 11 && spellId == FLAMESTRIKE) ++ // aftercrit += 8.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ ////!!!spell damage is not yet critical and will be multiplied by 1.5 ++ ////so we should put here bonus damage mult /1.5 ++ ////Ice Shards: 50% additional crit damage bonus for Frost spells ++ //else if (lvl >= 15 && (SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask())) ++ // pctbonus += 0.333f; ++ } ++ //Focused Power: 4% bonus damage for all spells ++ if (lvl >= 35) ++ pctbonus += 0.04f; ++ //Darkness: 10% bonus damage for shadow spells ++ if (lvl >= 10 && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) ++ pctbonus += 0.1f; ++ //Twin Disciplines (damage part): 5% bonus damage for instant spells ++ if (lvl >= 13 && !spellInfo->CalcCastTime()) ++ pctbonus += 0.05f; ++ //Twisted Faith (part 1): 10% bonus damage for Mind Blast and Mind Flay if target is affected BY SW: Pain ++ if (lvl >= 55 && (spellId == GetSpell(SW_PAIN_1) || spellId == GetSpell(MIND_FLAY_1)) && ++ damageinfo.target && damageinfo.target->HasAura(GetSpell(SW_PAIN_1), me->GetGUID())) ++ pctbonus += 0.1f; ++ //Mind Melt (part 1): 30% bonus damage for Shadow Word: Death ++ if (lvl >= 41 && spellId == GetSpell(SW_DEATH_1)) ++ pctbonus += 0.3f; ++ ++ //Glyph of Mind Flay: 10% damage bonus for Mind Flay ++ if (lvl >= 25 && spellId == GetSpell(MIND_FLAY_1)) ++ pctbonus += 0.1f; ++ ++ //other ++ if (spellId == SW_DEATH_BACKLASH) ++ { ++ ////T13 Shadow 2P Bonus (Shadow Word: Death), part 2 ++ //if (lvl >= 60) //buffed ++ // pctbonus -= 0.95f; ++ //Pain and Suffering (part 2): 40% reduced backlash damage ++ if (lvl >= 50) ++ pctbonus -= 0.4f; ++ ++ pctbonus = std::min(pctbonus, 1.0f); ++ } ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ float flat_mod = 0.0f; ++ ++ //Improved Renew: 15% bonus healing for Renew ++ if (lvl >= 10 && spellId == GetSpell(RENEW_1)) ++ pctbonus += 0.15f; ++ //Spiritual Healing: 10% bonus healing for all spells ++ if (lvl >= 35) ++ pctbonus += 0.15f; ++ //Blessend Resilience: 3% bonus healing for all spells ++ if (lvl >= 40) ++ pctbonus += 0.03f; ++ //Empowered Healing: 40% bonus (from spellpower) for Greater Heal and 20% bonus (from spellpower) for Flash Heal ++ if (lvl >= 45) ++ { ++ if (spellId == HEAL) ++ flat_mod += spellpower * 0.4f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ else if (spellId == GetSpell(FLASH_HEAL_1)) ++ flat_mod += spellpower * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ } ++ //Impowered Renew (heal bonus part): 15% bonus healing for Renew ++ if (lvl >= 50 && spellId == GetSpell(RENEW_1)) ++ flat_mod += spellpower * 0.15f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * int32(stack) * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ //Test of Faith: 12% bonus healing on targets at or below 50% health ++ if (lvl >= 50 && GetHealthPCT(victim) <= 50) ++ pctbonus += 0.12f; ++ //Test of Faith: 10 bonus healing for Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending ++ if (lvl >= 55 && ++ (/*spellId == GetSpell(CIRCLE_OF_HEALING_1) || spellId == GetSpell(BINDING_HEAL_1) || ++ spellId == GetSpell(HOLY_NOVA_1) || */spellId == GetSpell(PRAYER_OF_HEALING_1) || ++ spellId == DIVINE_HYMN_HEAL/* || spellId == GetSpell(PRAYER_OF_MENDING_1)*/)) ++ pctbonus += 0.12f; ++ ++ heal = heal * (1.0f + pctbonus) + flat_mod; ++ } ++ ++ void ApplyClassCritMultiplierHeal(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType /*attackType*/) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float aftercrit = 0.0f; ++ ++ //Improved Flash Heal (part 2): 10% additional critical chance on targets at or below 50% hp for Flash Heal ++ if (lvl >= 40 && spellId == GetSpell(FLASH_HEAL_1) && GetHealthPCT(victim) <= 50) ++ aftercrit += 10.f; ++ //Holy Specialization: 5% additional critical chance for Holy spells ++ if (lvl >= 10 && (schoolMask & SPELL_SCHOOL_MASK_HOLY)) ++ aftercrit += 5.f; ++ ++ crit_chance += aftercrit; ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ ////Strength of Soul: direct heals reduce Weakened Soul duration on target by 4 sec ++ //if (spellId == HEAL || spellId == GetSpell(FLASH_HEAL_1)) ++ //{ ++ // if (me->getLevel() >= 51) ++ // { ++ // if (Aura* soul = target->GetAura(WEAKENED_SOUL_DEBUFF)) ++ // { ++ // if (soul->GetDuration() > 4000) ++ // soul->SetDuration(soul->GetDuration() - 4000); ++ // else ++ // target->RemoveAura(soul, AURA_REMOVE_BY_EXPIRE); ++ // } ++ // } ++ //} ++ ++ //Weakened Soul Reduction (id: 33333 lol): -2 sec to Weakened Soul duration ++ if (spellId == WEAKENED_SOUL_DEBUFF) ++ if (me->getLevel() >= 51) ++ if (Aura* soul = target->GetAura(WEAKENED_SOUL_DEBUFF)) ++ soul->SetDuration(soul->GetDuration() - 2000); ++ ++ //Pain and Suffering (part 1, 335 version): 100% to refresh Shadow Word: Pain on target hit by Mind Flay ++ if (spellId == GetSpell(MIND_FLAY_1)) ++ if (me->getLevel() >= 51) ++ if (Aura* pain = target->GetAura(GetSpell(SW_PAIN_1), me->GetGUID())) ++ pain->RefreshDuration(); ++ ++ if (spellId == GetSpell(FEAR_WARD_1)) ++ { ++ //2 minutes bonus duration for Fear Ward ++ if (Aura* ward = target->GetAura(GetSpell(FEAR_WARD_1), me->GetGUID())) ++ { ++ uint32 dur = ward->GetDuration() + 120000; ++ ward->SetDuration(dur); ++ ward->SetMaxDuration(dur); ++ } ++ } ++ ++ if (spellId == GetSpell(INNER_FIRE_1) || spellId == GetSpell(VAMPIRIC_EMBRACE_1) || spellId == GetSpell(PW_FORTITUDE_1) || ++ spellId == GetSpell(SHADOW_PROTECTION_1) || spellId == GetSpell(DIVINE_SPIRIT_1)) ++ { ++ //1 hour duration for all buffs ++ if (Aura* buff = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = HOUR * IN_MILLISECONDS; ++ buff->SetDuration(dur); ++ buff->SetMaxDuration(dur); ++ } ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ Heal_Timer = 0; ++ Shackle_Timer = 0; ++ ++ CheckDispelTimer = 0; ++ DevcheckTimer = 0; ++ ++ Devcheck = false; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (Heal_Timer > diff) Heal_Timer -= diff; ++ if (Shackle_Timer > diff) Shackle_Timer -= diff; ++ ++ if (CheckDispelTimer > diff) CheckDispelTimer -= diff; ++ if (DevcheckTimer > diff) DevcheckTimer -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(DISPEL_MAGIC_1); ++ InitSpellMap(MASS_DISPEL_1); ++ InitSpellMap(CURE_DISEASE_1); ++ InitSpellMap(FEAR_WARD_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(PAIN_SUPPRESSION_1) : RemoveSpell(PAIN_SUPPRESSION_1); ++ InitSpellMap(PSYCHIC_SCREAM_1); ++ InitSpellMap(FADE_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(PSYCHIC_HORROR_1) : RemoveSpell(PSYCHIC_HORROR_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(SILENCE_1) : RemoveSpell(SILENCE_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(PENANCE_1) : RemoveSpell(PENANCE_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(VAMPIRIC_EMBRACE_1) : RemoveSpell(VAMPIRIC_EMBRACE_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(DISPERSION_1) : RemoveSpell(DISPERSION_1); ++ InitSpellMap(MIND_SEAR_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(GUARDIAN_SPIRIT_1) : RemoveSpell(GUARDIAN_SPIRIT_1); ++ InitSpellMap(SHACKLE_UNDEAD_1); ++ InitSpellMap(GREATER_HEAL_1); ++ InitSpellMap(NORMAL_HEAL_1); ++ InitSpellMap(LESSER_HEAL_1); ++ InitSpellMap(RENEW_1); ++ InitSpellMap(FLASH_HEAL_1); ++ InitSpellMap(PRAYER_OF_HEALING_1); ++ InitSpellMap(DIVINE_HYMN_1); ++ InitSpellMap(RESURRECTION_1); ++ InitSpellMap(PW_SHIELD_1); ++ InitSpellMap(INNER_FIRE_1); ++ InitSpellMap(PW_FORTITUDE_1); ++ InitSpellMap(SHADOW_PROTECTION_1); ++ InitSpellMap(DIVINE_SPIRIT_1); ++ InitSpellMap(SW_PAIN_1); ++ InitSpellMap(MIND_BLAST_1); ++ InitSpellMap(SW_DEATH_1); ++ InitSpellMap(DEVOURING_PLAGUE_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(MIND_FLAY_1) : RemoveSpell(MIND_FLAY_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(VAMPIRIC_TOUCH_1) : RemoveSpell(VAMPIRIC_TOUCH_1); ++ ++ HEAL = GetSpell(GREATER_HEAL_1) ? GetSpell(GREATER_HEAL_1) : ++ GetSpell(NORMAL_HEAL_1) ? GetSpell(NORMAL_HEAL_1) : ++ GetSpell(LESSER_HEAL_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(BORROWED_TIME, level >= 65 ? 1 : 0); ++ RefreshAura(DIVINE_AEGIS, level >= 55 ? 1 : 0); ++ RefreshAura(EMPOWERED_RENEW3, level >= 55 ? 1 : 0); ++ RefreshAura(EMPOWERED_RENEW2, level >= 50 && level < 55 ? 1 : 0); ++ RefreshAura(EMPOWERED_RENEW1, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(BODY_AND_SOUL1, level >= 45 ? 1 : 0); ++ RefreshAura(RENEWED_HOPE, level >= 45 ? 1 : 0); ++ RefreshAura(PAINANDSUFFERING3, level >= 50 ? 1 : 0); ++ RefreshAura(PAINANDSUFFERING2, level >= 48 && level < 50 ? 1 : 0); ++ RefreshAura(PAINANDSUFFERING1, level >= 45 && level < 48 ? 1 : 0); ++ RefreshAura(MISERY3, level >= 50 ? 1 : 0); ++ RefreshAura(MISERY2, level >= 48 && level < 50 ? 1 : 0); ++ RefreshAura(MISERY1, level >= 45 && level < 48 ? 1 : 0); ++ RefreshAura(GRACE, level >= 25 ? 1 : 0); ++ RefreshAura(ENLIGHTENMENT, level >= 35 ? 1 : 0); ++ RefreshAura(RAPTURE, level >= 45 ? 1 : 0); ++ RefreshAura(IMPROVED_DEVOURING_PLAGUE, level >= 25 ? 1 : 0); ++ RefreshAura(INSPIRATION3, level >= 25 ? 1 : 0); ++ RefreshAura(INSPIRATION2, level >= 23 && level < 25 ? 1 : 0); ++ RefreshAura(INSPIRATION1, level >= 20 && level < 23 ? 1 : 0); ++ RefreshAura(SHADOW_WEAVING3, level >= 30 ? 1 : 0); ++ RefreshAura(SHADOW_WEAVING2, level >= 28 && level < 30 ? 1 : 0); ++ RefreshAura(SHADOW_WEAVING1, level >= 25 && level < 28 ? 1 : 0); ++ RefreshAura(GLYPH_SW_PAIN, level >= 15? 1 : 0); ++ RefreshAura(GLYPH_PW_SHIELD, level >= 15 ? 1 : 0); ++ RefreshAura(SHADOWFORM, level >= 40 ? 1 : 0); ++ RefreshAura(PRIEST_T10_2P_BONUS, level >= 70 ? 1 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case DISPEL_MAGIC_1: ++ case MASS_DISPEL_1: ++ case CURE_DISEASE_1: ++ case FEAR_WARD_1: ++ case PAIN_SUPPRESSION_1: ++ case FADE_1: ++ case PENANCE_1: ++ case VAMPIRIC_EMBRACE_1: ++ case DISPERSION_1: ++ case GUARDIAN_SPIRIT_1: ++ case LESSER_HEAL_1: ++ case NORMAL_HEAL_1: ++ case GREATER_HEAL_1: ++ case RENEW_1: ++ case FLASH_HEAL_1: ++ case PRAYER_OF_HEALING_1: ++ case DIVINE_HYMN_1: ++ case PW_SHIELD_1: ++ case INNER_FIRE_1: ++ case PW_FORTITUDE_1: ++ case SHADOW_PROTECTION_1: ++ case DIVINE_SPIRIT_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ uint32 HEAL; ++ uint32 Heal_Timer, Shackle_Timer; ++/*Misc*/uint16 CheckDispelTimer, DevcheckTimer; ++/*Misc*/bool Devcheck; ++ ++ enum PriestBaseSpells ++ { ++ DISPEL_MAGIC_1 = 527, ++ MASS_DISPEL_1 = 32375, ++ CURE_DISEASE_1 = 528, ++ FEAR_WARD_1 = 6346, ++ /*Talent*/PAIN_SUPPRESSION_1 = 33206, ++ PSYCHIC_SCREAM_1 = 8122, ++ FADE_1 = 586, ++ /*Talent*/PSYCHIC_HORROR_1 = 64044, ++ /*Talent*/SILENCE_1 = 15487, ++ /*Talent*/PENANCE_1 = 47540, ++ /*Talent*/VAMPIRIC_EMBRACE_1 = 15286, ++ /*Talent*/DISPERSION_1 = 47585, ++ MIND_SEAR_1 = 48045, ++ /*Talent*/GUARDIAN_SPIRIT_1 = 47788, ++ SHACKLE_UNDEAD_1 = 9484, ++ LESSER_HEAL_1 = 2050, ++ NORMAL_HEAL_1 = 2054, ++ GREATER_HEAL_1 = 2060, ++ RENEW_1 = 139, ++ FLASH_HEAL_1 = 2061, ++ PRAYER_OF_HEALING_1 = 596, ++ DIVINE_HYMN_1 = 64843, ++ RESURRECTION_1 = 2006, ++ PW_SHIELD_1 = 17, ++ INNER_FIRE_1 = 588, ++ PW_FORTITUDE_1 = 1243, ++ SHADOW_PROTECTION_1 = 976, ++ DIVINE_SPIRIT_1 = 14752, ++ SW_PAIN_1 = 589, ++ MIND_BLAST_1 = 8092, ++ SW_DEATH_1 = 32379, ++ DEVOURING_PLAGUE_1 = 2944, ++ /*Talent*/MIND_FLAY_1 = 15407, ++ /*Talent*/VAMPIRIC_TOUCH_1 = 34914 ++ }; ++ enum PriestPassives ++ { ++ SHADOWFORM /*For DOT crits*/ = 49868, ++ //Talents ++ IMPROVED_DEVOURING_PLAGUE = 63627,//rank 3 ++ MISERY1 = 33191, ++ MISERY2 = 33192, ++ MISERY3 = 33193, ++ PAINANDSUFFERING1 = 47580, ++ PAINANDSUFFERING2 = 47581, ++ PAINANDSUFFERING3 = 47582, ++ SHADOW_WEAVING1 = 15257, ++ SHADOW_WEAVING2 = 15331, ++ SHADOW_WEAVING3 = 15332, ++ DIVINE_AEGIS = 47515,//rank 3 ++ BORROWED_TIME = 52800,//rank 5 ++ GRACE = 47517,//rank 2 ++ EMPOWERED_RENEW1 = 63534, ++ EMPOWERED_RENEW2 = 63542, ++ EMPOWERED_RENEW3 = 63543, ++ INSPIRATION1 = 14892, ++ INSPIRATION2 = 15362, ++ INSPIRATION3 = 15363, ++ BODY_AND_SOUL1 = 64127, ++ RENEWED_HOPE = 57472,//rank 3 ++ ENLIGHTENMENT = 34910,//rank 3 ++ RAPTURE = 47537,//rank 3 ++ //Glyphs ++ GLYPH_SW_PAIN = 55681, ++ GLYPH_PW_SHIELD = 55672, ++ //other ++ PRIEST_T10_2P_BONUS = 70770 //33% renew ++ }; ++ enum PriestSpecial ++ { ++ IMPROVED_DEVOURING_PLAGUE_DAMAGE= 63675, ++ MIND_SEAR_DAMAGE = 49821, ++ SW_DEATH_BACKLASH = 32409, ++ WEAKENED_SOUL_DEBUFF = 6788, ++ DIVINE_HYMN_HEAL = 64844 ++ }; ++ }; ++}; ++ ++void AddSC_priest_bot() ++{ ++ new priest_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_rogue_ai.cpp b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp +new file mode 100644 +index 0000000..43d2551 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp +@@ -0,0 +1,828 @@ ++#include "bot_ai.h" ++//#include "botmgr.h" ++//#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++/* ++Rogue NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - 25% maybe... ++TODO: ++*/ ++#define DMGMIN 1 ++#define DMGMAX 2 ++#define MAX_COMBO_POINTS 5 ++#define EVISCERATE_MAX_RANK 12 ++const uint32 EVSCRDamage[EVISCERATE_MAX_RANK+1][MAX_COMBO_POINTS+1][DMGMAX+1] = ++{ ++ { { 0,0,0 }, { 0,0,0 }, { 0,0,0 }, { 0,0,0 }, { 0,0,0 }, { 0,0,0 } }, ++ { { 0,0,0 }, { 0,6,11 }, { 0,12,16 }, { 0,17,22 }, { 0,22,28 }, { 0,28,34 } }, ++ { { 0,0,0 }, { 0,14,23 }, { 0,26,34 }, { 0,37,46 }, { 0,48,58 }, { 0,60,70 } }, ++ { { 0,0,0 }, { 0,25,49 }, { 0,45,59 }, { 0,64,79 }, { 0,83,99 }, { 0,103,119 } }, ++ { { 0,0,0 }, { 0,41,62 }, { 0,73,93 }, { 0,104,125 }, { 0,135,157 }, { 0,167,189 } }, ++ { { 0,0,0 }, { 0,60,91 }, { 0,106,136 }, { 0,151,182 }, { 0,196,228 }, { 0,242,274 } }, ++ { { 0,0,0 }, { 0,93,138 }, { 0,165,209 }, { 0,236,281 }, { 0,307,353 }, { 0,379,425 } }, ++ { { 0,0,0 }, { 0,144,213 }, { 0,255,323 }, { 0,365,434 }, { 0,475,545 }, { 0,586,656 } }, ++ { { 0,0,0 }, { 0,199,296 }, { 0,351,447 }, { 0,502,599 }, { 0,653,751 }, { 0,805,903 } }, ++ { { 0,0,0 }, { 0,224,333 }, { 0,395,503 }, { 0,565,674 }, { 0,735,845 }, { 0,906,1016 } }, ++ { { 0,0,0 }, { 0,245,366 }, { 0,431,551 }, { 0,616,737 }, { 0,801,923 }, { 0,987,1109 } }, ++ { { 0,0,0 }, { 0,405,614 }, { 0,707,915 }, { 0,1008,1217 }, { 0,1309,1519 }, { 0,1611,1821 } }, ++ { { 0,0,0 }, { 0,497,752 }, { 0,868,1122 }, { 0,1238,1493 }, { 0,1608,1864 }, { 0,1979,2235 } } ++}; ++#define RUPTURE_MAX_RANK 9 ++const uint32 RuptureDamage[RUPTURE_MAX_RANK+1][MAX_COMBO_POINTS+1] = ++{ ++ { 0, 0, 0, 0, 0, 0 }, ++ { 0, 41, 61, 86, 114, 147 }, ++ { 0, 61, 91, 128, 170, 219 }, ++ { 0, 89, 131, 182, 240, 307 }, ++ { 0, 129, 186, 254, 331, 419 }, ++ { 0, 177, 256, 350, 457, 579 }, ++ { 0, 273, 381, 506, 646, 803 }, ++ { 0, 325, 461, 620, 800, 1003 }, ++ { 0, 489, 686, 914, 1171, 1459 }, ++ { 0, 581, 816, 1088, 1395, 1739 } ++}; ++ ++class rogue_bot : public CreatureScript ++{ ++public: ++ rogue_bot() : CreatureScript("rogue_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new rogue_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct rogue_botAI : public bot_minion_ai ++ { ++ rogue_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_ROGUE; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_ROGUE) != SPELL_CAST_OK) ++ return false; ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { comboPoints = 0; tempComboPoints = 0; bot_minion_ai::JustDied(u); } ++ void DoNonCombatActions(uint32 /*diff*/) ++ {} ++ ++ //This method should be used to emulate energy usage reduction ++ void modenergy(int32 mod, bool set = false) ++ { ++ //can't set enery to -x (2 cases) ++ if (set && mod < 0) ++ return; ++ if (mod < 0 && energy < uint32(abs(mod))) ++ { ++ mod = 0; ++ set = true; ++ } ++ ++ if (set) ++ energy = mod; ++ else ++ energy += mod; ++ ++ energy = std::min(energy, 100); ++ me->SetPower(POWER_ENERGY, energy); ++ } ++ ++ uint32 getenergy() ++ { ++ energy = me->GetPower(POWER_ENERGY); ++ return energy; ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_ROGUE)) ++ return; ++ ++ Attack(diff); ++ } ++ ++ void Attack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ comboPoints = std::min(comboPoints, 5); ++ //AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ ++ //Blade Flurry (434 deprecated) ++ if (IsSpellReady(BLADE_FLURRY_1, diff, false) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && ++ Rand() < 30 && getenergy() >= 25 && FindSplashTarget(7, opponent)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLADE_FLURRY_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //KICK ++ if (IsSpellReady(KICK_1, diff, false) && meleedist <= 5 && Rand() < 80 && getenergy() >= 25 && ++ opponent->IsNonMeleeSpellCast(false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(KICK_1))) ++ { ++ GC_Timer = temptimer; ++ //return; ++ } ++ } ++ //SHADOWSTEP ++ if (IsSpellReady(SHADOWSTEP_1, diff, false) && HasRole(BOT_ROLE_DPS) && dist < 25 && ++ (opponent->GetVictim() != me || opponent->GetTypeId() == TYPEID_PLAYER) && ++ Rand() < 30 && getenergy() >= 10) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(SHADOWSTEP_1))) ++ { ++ GC_Timer = temptimer; ++ //return; ++ } ++ } ++ //BACKSTAB ++ if (IsSpellReady(BACKSTAB_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && comboPoints < 4 && ++ /*Rand() < 90 && */getenergy() >= 60 && !opponent->HasInArc(M_PI, me)) ++ { ++ if (doCast(opponent, GetSpell(BACKSTAB_1))) ++ return; ++ } ++ //SINISTER STRIKE ++ if (IsSpellReady(SINISTER_STRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && comboPoints < 5 && ++ Rand() < 25 && getenergy() >= 45) ++ { ++ if (doCast(opponent, GetSpell(SINISTER_STRIKE_1))) ++ return; ++ } ++ //SLICE AND DICE ++ if (IsSpellReady(SLICE_DICE_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 20 && comboPoints > 1 && getenergy() >= 25 && ++ (b_attackers.size() <= 1 || !IsSpellReady(BLADE_FLURRY_1, diff)) && Rand() < 30) ++ { ++ if (doCast(opponent, GetSpell(SLICE_DICE_1))) ++ return; ++ } ++ //KIDNEY SHOT ++ if (IsSpellReady(KIDNEY_SHOT_1, diff) && meleedist <= 5 && comboPoints > 0 && ++ !CCed(opponent) && getenergy() >= 25 && ((Rand() < 15 + comboPoints*15 && opponent->GetVictim() == me && comboPoints > 2) || opponent->IsNonMeleeSpellCast(false))) ++ { ++ if (doCast(opponent, GetSpell(KIDNEY_SHOT_1))) ++ return; ++ } ++ //EVISCERATE ++ if (IsSpellReady(EVISCERATE_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && comboPoints > 2 && ++ getenergy() >= 35 && Rand() < comboPoints*15) ++ { ++ uint32 EVISCERATE = GetSpell(EVISCERATE_1); ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(EVISCERATE); ++ uint8 rank = spellInfo->GetRank(); ++ float ap = me->GetTotalAttackPowerValue(BASE_ATTACK); ++ float combo = float(comboPoints); ++ int32 damage = int32(urand(EVSCRDamage[rank][comboPoints][DMGMIN], EVSCRDamage[rank][comboPoints][DMGMAX]));//base damage ++ damage += irand(int32(ap*combo*0.03f), int32(ap*combo*0.07f));//ap bonus ++ ++ currentSpell = EVISCERATE; ++ me->CastCustomSpell(opponent, EVISCERATE, &damage, NULL, NULL, false); ++ return; ++ } ++ //MUTILATE ++ //if (isTimerReady(Mutilate_Timer) && energy>60) ++ //{ ++ // // TODO: calculate correct dmg for mutilate (dont forget poison bonus) ++ // // for now use same formula as evicerate ++ // uint32 base_attPower = me->GetUInt32Value(UNIT_FIELD_ATTACK_POWER); ++ // //float minDmg = me->GetFloatValue(UNIT_FIELD_MINDAMAGE); ++ // float minDmg = me->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE); ++ // int damage = irand(int32(base_attPower*7*0.03f),int32(base_attPower*7*0.08f))+minDmg+me->getLevel(); ++ ++ // // compensate for lack of attack power ++ // damage = damage*(rand()%4+1); ++ ++ // me->CastCustomSpell(opponent, MUTILATE, &damage, NULL, NULL, false, NULL, NULL); ++ ++ // //doCast (me, MUTILATE); ++ // Mutilate_Timer = 10; ++ // comboPoints+=3; ++ // energy -= 60; ++ //} ++ ++ //RUPTURE ++ if (IsSpellReady(RUPTURE_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && comboPoints > 3 && getenergy() >= 25 && ++ opponent->GetHealth() > me->GetMaxHealth()/3 && Rand() < (50 + 70 * opponent->isMoving())) ++ { ++ uint32 RUPTURE = GetSpell(RUPTURE_1); ++ //no damage range for rupture ++ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(RUPTURE); ++ uint8 rank = spellInfo->GetRank(); ++ float ap = me->GetTotalAttackPowerValue(BASE_ATTACK); ++ float AP_per_combo[6] = {0.0f, 0.015f, 0.024f, 0.03f, 0.03428571f, 0.0375f}; ++ float divider[6] = {0.0f, 4.f, 5.f, 6.f, 7.f, 8.f};//duration/2 = number of ticks ++ int32 damage = int32(RuptureDamage[rank][comboPoints]/divider[comboPoints]);//base damage ++ damage += int32(ap*AP_per_combo[comboPoints]);//ap bonus is strict - applied to every tick ++ ++ currentSpell = RUPTURE; ++ me->CastCustomSpell(opponent, RUPTURE, &damage, NULL, NULL, false); ++ return; ++ } ++ //DISMANTLE ++ if (IsSpellReady(DISMANTLE_1, diff, false) && meleedist <= 5 && opponent->GetTypeId() == TYPEID_PLAYER && ++ Rand() < 20 && getenergy() >= 25 && !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_DISARM) && ++ (opponent->ToPlayer()->GetWeaponForAttack(BASE_ATTACK) || opponent->ToPlayer()->GetWeaponForAttack(RANGED_ATTACK))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(DISMANTLE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Puncturing Wounds: 30% additional critical chance for Backstab ++ if (lvl >= 15 && spellId == GetSpell(BACKSTAB_1)) ++ aftercrit += 30.f; ++ //Puncturing Wounds: 15% additional critical chance for Mutilate ++ else if (spellId == GetSpell(MUTILATE_1)) ++ aftercrit += 15.f; ++ //Glyph of Eviscerate: 10% additional critical chance for Eviscerate ++ else if (spellId == GetSpell(EVISCERATE_1)) ++ aftercrit += 10.f; ++ //Improved Ambush: 60% additional critical chance for Ambush ++ //else if (spellId == AMBUSH) ++ // crit_chance += 60.f; ++ if (lvl >= 50 && spellInfo->HasEffect(SPELL_EFFECT_ADD_COMBO_POINTS) && me->HasAura(TURN_THE_TABLES_EFFECT)) ++ aftercrit += 6.f; ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Lethality: 30% crit damage bonus for non-stealth combo-generating abilities (on 25 lvl) ++ if (lvl >= 25 && !(spellInfo->Attributes & SPELL_ATTR0_ONLY_STEALTHED) && ++ spellInfo->HasEffect(SPELL_EFFECT_ADD_COMBO_POINTS)) ++ pctbonus += 0.15f; ++ } ++ //Shadowstep: 20% bonus damage to all abilities once ++ //if (shadowstep == true) ++ //{ ++ // shadowstep = false; ++ // me->RemoveAurasDueToSpell(SHADOWSTEP_EFFECT_DAMAGE); ++ // pctbonus += 0.2f; ++ //} ++ //Find Weakness: 6% bonus damage to all abilities ++ if (lvl >= 45) ++ pctbonus += 0.06f; ++ //DeathDealer set bonus: 15% damage bonus for Eviscerate ++ if (lvl >= 60 && spellId == GetSpell(EVISCERATE_1)) ++ pctbonus += 0.15f; ++ //Imoroved Eviscerate: 20% damage bonus for Eviscerate ++ if (spellId == GetSpell(EVISCERATE_1)) ++ pctbonus += 0.2f; ++ //Opportunity: 20% damage bonus for Backstab, Mutilate, Garrote and Ambush ++ if (spellId == GetSpell(BACKSTAB_1) || spellId == GetSpell(MUTILATE_1)/* || ++ spellId == GARROTE || spellId == AMBUSH*/) ++ pctbonus += 0.2f; ++ //Aggression: 15% damage bonus for Sinister Strike, Backstab and Eviscerate ++ if (lvl >= 30 && (spellId == GetSpell(SINISTER_STRIKE_1) || spellId == GetSpell(BACKSTAB_1) || spellId == GetSpell(EVISCERATE_1))) ++ pctbonus += 0.15f; ++ //Blood Spatter: 30% bonus damage for Rupture and Garrote ++ if (lvl >= 15 && (spellId == GetSpell(RUPTURE_1)/* || spellId == GARROTE*/)) ++ pctbonus += 0.3f; ++ //Serrated Blades: 30% bonus damage for Rupture ++ if (lvl >= 20 && spellId == GetSpell(RUPTURE_1)) ++ pctbonus += 0.3f; ++ //Surprise Attacks: 10% bonus damage for Sinister Strike, Backstab, Shiv, Hemmorhage and Gouge ++ if (lvl >= 50 && (spellId == GetSpell(SINISTER_STRIKE_1) || spellId == GetSpell(BACKSTAB_1)/* || ++ spellId == SHIV || spellId == HEMMORHAGE || spellId == GOUGE*/)) ++ pctbonus += 0.1f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ uint32 WOUND_POISON = GetSpell(WOUND_POISON_1); ++ uint32 MIND_NUMBING_POISON = GetSpell(MIND_NUMBING_POISON_1); ++ if (!WOUND_POISON && !MIND_NUMBING_POISON) ++ return; ++ ++ if (damageType == DIRECT_DAMAGE || damageType == SPELL_DIRECT_DAMAGE) ++ { ++ if (victim && me->GetExactDist(victim) <= 40) ++ { ++ switch (rand()%2) ++ { ++ case 0: ++ break; ++ case 1: ++ { ++ switch (rand()%2) ++ { ++ case 0: ++ if (WOUND_POISON) ++ { ++ currentSpell = WOUND_POISON; ++ DoCast(victim, WOUND_POISON, true); ++ } ++ break; ++ case 1: ++ if (MIND_NUMBING_POISON) ++ { ++ currentSpell = MIND_NUMBING_POISON; ++ DoCast(victim, MIND_NUMBING_POISON, true); ++ } ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ //combo points use up ++ if (spellId == GetSpell(SLICE_DICE_1)) ++ { ++ SetSpellCooldown(SLICE_DICE_1, 15000 + (tempComboPoints-1)*4500); //no initial cooldown ++ GC_Timer = 800; ++ ++ if (Aura* dice = me->GetAura(GetSpell(SLICE_DICE_1))) ++ { ++ uint32 dur = dice->GetDuration(); ++ dur += tempComboPoints * 3000; //use cp ++ dur += 6000; // Glyph of Slice and Dice ++ dur = (dur * 3) / 2; //Improved Slice and Dice ++ dice->SetDuration(dur); ++ dice->SetMaxDuration(dur); ++ } ++ } ++ else if (spellId == GetSpell(RUPTURE_1)) ++ { ++ SetSpellCooldown(RUPTURE_1, 8000 + (tempComboPoints-1)*2000 + 4000); //no initial cooldown ++ GC_Timer = 800; ++ ++ if (Aura* rupture = target->GetAura(GetSpell(RUPTURE_1), me->GetGUID())) ++ { ++ uint32 dur = rupture->GetDuration() + tempComboPoints*2000; //use cp ++ dur += 4000; //Glyph of Rupture ++ rupture->SetDuration(dur); ++ rupture->SetMaxDuration(dur); ++ } ++ } ++ else if (spellId == GetSpell(KIDNEY_SHOT_1)) ++ { ++ if (Aura* kidney = target->GetAura(GetSpell(KIDNEY_SHOT_1), me->GetGUID())) ++ { ++ uint32 dur = kidney->GetDuration() + tempComboPoints*1000; ++ kidney->SetDuration(dur); ++ kidney->SetMaxDuration(dur); ++ } ++ } ++ ++ if (currentSpell == 0) ++ return; ++ ++ //BONUS CP MANAGEMENT ++ ++ //if (spellId == EVISCERATE || spellId == KIDNEY_SHOT || spellId == SLICE_DICE || spellId == RUPTURE/* || spellId == EXPOSE_ARMOR || spellId == ENVENOM*/) ++ //Relentless Strikes ++ if (spell->NeedsComboPoints()) ++ { ++ //std::ostringstream msg; ++ //msg << "casting "; ++ //if (spellId == EVISCERATE) ++ // msg << "Eviscerate, "; ++ //else if (spellId == RUPTURE) ++ // msg << "Rupture, "; ++ //else if (spellId == SLICE_DICE) ++ // msg << "Slice and Dice, "; ++ //else if (spellId == KIDNEY_SHOT) ++ // msg << "Kidney Shot, "; ++ ////else if (spellId == EXPOSE_ARMOR) ++ //// msg << "Expose Armor, "; ++ ////else if (spellId == ENVENOM) ++ //// msg << "Envenom, "; ++ //msg << "combo points: " << uint32(std::min(comboPoints,5)); ++ //me->Whisper(msg.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ if (irand(0, 99) < 20 * (comboPoints > 5 ? 5 : comboPoints)) ++ { ++ currentSpell = RELENTLESS_STRIKES_EFFECT; ++ DoCast(me, RELENTLESS_STRIKES_EFFECT, true); ++ } ++ tempComboPoints = comboPoints; ++ //CP adding effects are handled before actual finisher so use temp value ++ //std::ostringstream msg2; ++ //msg2 << "cp set to 0"; ++ if (tempAddCP) ++ { ++ //msg2 << " + " << uint32(tempAddCP) << " (temp)"; ++ comboPoints = tempAddCP; ++ tempAddCP = 0; ++ } ++ else ++ comboPoints = 0; ++ //me->Whisper(msg2.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ } ++ else if (spellId == GetSpell(SINISTER_STRIKE_1) || ++ spellId == GetSpell(BACKSTAB_1)/* || ++ spellId == GOUGE || ++ spellId == HEMORRHAGE*/) ++ { ++ ++comboPoints; ++ //std::ostringstream msg; ++ //msg << "1 cp generated "; ++ //if (spellId == SINISTER_STRIKE) ++ // msg << "(Sinister Strike)"; ++ //else if (spellId == BACKSTAB) ++ // msg << "(Backstab)"; ++ //msg << " set to " << uint32(comboPoints); ++ //if (tempAddCP) ++ // msg << " + " << uint32(tempAddCP) << " (triggered)"; ++ //me->Whisper(msg.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ if (tempAddCP) ++ { ++ comboPoints += tempAddCP; ++ tempAddCP = 0; ++ } ++ } ++ else if (spellId == GetSpell(MUTILATE_1)/* || ++ spellId == AMBUSH*/) ++ { ++ comboPoints += 2; ++ //std::ostringstream msg; ++ //msg << "2 cp generated (Mutilate), set to " << uint32(comboPoints); ++ //if (tempAddCP) ++ // msg << " + " << uint32(tempAddCP) << " (triggered)"; ++ //me->Whisper(msg.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ if (tempAddCP) ++ { ++ comboPoints += tempAddCP; ++ tempAddCP = 0; ++ } ++ } ++ else if (spellId == SEAL_FATE_EFFECT || spellId == RUTHLESSNESS_EFFECT) ++ { ++ ++tempAddCP; ++ //std::ostringstream msg; ++ //msg << "1 temp cp generated "; ++ //if (spellId == SEAL_FATE_EFFECT) ++ // msg << "(Seal Fate)"; ++ //else if (spellId == RUTHLESSNESS_EFFECT) ++ // msg << "(Ruthleness)"; ++ //me->Whisper(msg.str().c_str(), LANG_UNIVERSAL, master->GetGUID()); ++ } ++ ++ //Glyph of Sinister Strike (20% to add cp on hit) ++ //Seal Fate means crit so this glyph is enabled from lvl 35) ++ //as addition always add cp on ss crit ++ if (currentSpell == GetSpell(SINISTER_STRIKE_1) && (spellId == SEAL_FATE_EFFECT || urand(0,100) >= 20)) ++ { ++ ++tempAddCP; ++ //me->Whisper("1 temp cp generated (glyph of SS)", LANG_UNIVERSAL, master->GetGUID()); ++ } ++ ++ //ENERGY COST REDUCTION ++ ++ if (spellId == GetSpell(SINISTER_STRIKE_1)) ++ { ++ //Improved Sinister Strike ++ //instead of restoring energy we should override current value ++ if (me->getLevel() >= 10) ++ modenergy(-40, true);//45 - 5 ++ } ++ //Slaughter from the Shadows energy restore ++ //instead of restoring energy we should override current value ++ if (me->getLevel() >= 55) ++ { ++ if (spellId == GetSpell(BACKSTAB_1)/* || spellId == AMBUSH*/) ++ modenergy(-40, true); ++ //else if (spellId == HEMORRHAGE) ++ // modenergy(-30, true); ++ } ++ ++ //OTHER ++ ++ if (spellId == GetSpell(EVISCERATE_1)) ++ { ++ //Eviscerate speedup ++ GC_Timer = 800; ++ ++ //Serrated Blades: Eviscerate has 20% chance per cp to refresh Rupture ++ //getting cheaty - remove roll ++ //getting cheaty - increase duration ++ //if (irand(0, 99) < 20 * (comboPoints > 5 ? 5 : comboPoints)) ++ { ++ if (Aura* rupture = target->GetAura(GetSpell(RUPTURE_1), me->GetGUID())) ++ { ++ int32 dur = rupture->GetMaxDuration() + 2000; ++ dur = std::min(dur, 30000); ++ rupture->SetDuration(dur); ++ rupture->SetMaxDuration(dur); ++ SetSpellCooldown(RUPTURE_1, dur - 2000); //no initial cooldown ++ } ++ } ++ } ++ ++ //Cut to the Chase: Eviscerate and Envenom will refresh Slice and Dice duration ++ //getting cheaty - increase duration ++ if (spellId == GetSpell(EVISCERATE_1)/* || spellId == ENVENOM*/) ++ { ++ if (Aura* dice = me->GetAura(GetSpell(SLICE_DICE_1))) ++ { ++ int32 dur = dice->GetMaxDuration() + 2000; ++ dur = std::min(dur, 59000); ++ dice->SetDuration(dur); ++ dice->SetMaxDuration(dur); ++ SetSpellCooldown(SLICE_DICE_1, dur - 2000); //no initial cooldown ++ } ++ } ++ ++ //Murderous Intent: When Backstabbing enemy that is below 35% hp, instantly restoring 30 energy ++ if (spellId == GetSpell(BACKSTAB_1)) ++ { ++ if (target->HealthBelowPct(35)) ++ { ++ //since talent doesn't work just restore energy manually ++ //DoCast(me, MURDEROUS_INTENT_EFFECT, true); ++ modenergy(30); ++ } ++ } ++ ++ //Waylay ++ if ((spellId == GetSpell(BACKSTAB_1)/* || spellId == AMBUSH*/) && ++ me->getLevel() >= 20) ++ { ++ DoCast(target, WAYLAY_EFFECT, true); ++ } ++ ++ //if (spellId == SHADOWSTEP) ++ //{ ++ // Shadowstep_eff_Timer = 10000; ++ // shadowstep = true; ++ //} ++ ++ //move behind on Kidney Shot and Gouge (optionally) ++ if (spellId == GetSpell(KIDNEY_SHOT_1)/* || spellId == GOUGE*/) ++ if (MoveBehind(*target)) ++ wait = 3; ++ ++ if (spellId == currentSpell) ++ currentSpell = 0; ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ comboPoints = 0; ++ tempComboPoints = 0; ++ tempAddCP = 0; ++ ++ tempDICE = false; ++ //shadowstep = false; ++ ++ me->setPowerType(POWER_ENERGY); ++ //10 energy gained per stack ++ DefaultInit(); ++ ++ RefreshAura(GLADIATOR_VIGOR, 10); ++ ++ me->SetPower(POWER_ENERGY, me->GetMaxPower(POWER_ENERGY)); ++ } ++ ++ void ReduceCD(uint32 /*diff*/) ++ { ++ //if (Shadowstep_eff_Timer > diff) Shadowstep_eff_Timer -= diff; ++ //else if (shadowstep) shadowstep = false; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(BACKSTAB_1); ++ InitSpellMap(SINISTER_STRIKE_1); ++ InitSpellMap(SLICE_DICE_1); ++ InitSpellMap(EVISCERATE_1); ++ InitSpellMap(KICK_1); ++ InitSpellMap(RUPTURE_1); ++ InitSpellMap(KIDNEY_SHOT_1); ++ lvl >= 50 ? InitSpellMap(MUTILATE_1) : RemoveSpell(MUTILATE_1); ++ lvl >= 50 ? InitSpellMap(SHADOWSTEP_1) : RemoveSpell(SHADOWSTEP_1); ++ InitSpellMap(DISMANTLE_1); ++ lvl >= 30 ? InitSpellMap(BLADE_FLURRY_1) : RemoveSpell(BLADE_FLURRY_1); ++ ++ InitSpellMap(WOUND_POISON_1); ++ InitSpellMap(MIND_NUMBING_POISON_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(COMBAT_POTENCY5, level >= 70 ? 2 : level >= 55 ? 1 : 0); ++ RefreshAura(COMBAT_POTENCY4, level >= 52 && level < 55 ? 1 : 0); ++ RefreshAura(COMBAT_POTENCY3, level >= 49 && level < 52 ? 1 : 0); ++ RefreshAura(COMBAT_POTENCY2, level >= 47 && level < 49 ? 1 : 0); ++ RefreshAura(COMBAT_POTENCY1, level >= 45 && level < 47 ? 1 : 0); ++ RefreshAura(SEAL_FATE5, level >= 35 ? 1 : 0); ++ RefreshAura(SEAL_FATE4, level >= 32 && level < 35 ? 1 : 0); ++ RefreshAura(SEAL_FATE3, level >= 29 && level < 32 ? 1 : 0); ++ RefreshAura(SEAL_FATE2, level >= 27 && level < 29 ? 1 : 0); ++ RefreshAura(SEAL_FATE1, level >= 25 && level < 27 ? 1 : 0); ++ RefreshAura(VITALITY, level >= 70 ? 3 : level >= 55 ? 2 : level >= 40 ? 1 : 0); ++ RefreshAura(TURN_THE_TABLES, level >= 55 ? 1 : 0); ++ RefreshAura(DEADLY_BREW, level >= 40 ? 1 : 0); ++ RefreshAura(BLADE_TWISTING1, level >= 35 ? 1 : 0); ++ RefreshAura(QUICK_RECOVERY2, level >= 35 ? 1 : 0); ++ RefreshAura(QUICK_RECOVERY1, level >= 30 && level < 35 ? 1 : 0); ++ RefreshAura(IMPROVED_KIDNEY_SHOT, level >= 30 ? 1 : 0); ++ RefreshAura(GLYPH_BACKSTAB, level >= 10 ? 1 : 0); ++ RefreshAura(SURPRISE_ATTACKS, level >= 10 ? 1 : 0); ++ RefreshAura(ROGUE_VIGOR, level >= 25 ? 2 : level >= 20 ? 1 : 0); ++ } ++ ++ private: ++ uint32 energy; ++ uint8 comboPoints, tempComboPoints, tempAddCP; ++ bool tempDICE/*, shadowstep*/; ++ ++ enum RogueBaseSpells ++ { ++ BACKSTAB_1 = 53, ++ SINISTER_STRIKE_1 = 1757, ++ SLICE_DICE_1 = 5171, ++ EVISCERATE_1 = 2098, ++ KICK_1 = 1766, ++ RUPTURE_1 = 1943, ++ KIDNEY_SHOT_1 = 408, ++ /*Talent*/MUTILATE_1 = 1329, ++ /*Talent*/SHADOWSTEP_1 = 36554, ++ DISMANTLE_1 = 51722, ++ BLADE_FLURRY_1 = 13877, ++ //Special ++ WOUND_POISON_1 = 13218, ++ MIND_NUMBING_POISON_1 = 5760 ++ }; ++ ++ enum RoguePassives ++ { ++ //Talents ++ SEAL_FATE1 = 14189, ++ SEAL_FATE2 = 14190, ++ SEAL_FATE3 = 14193, ++ SEAL_FATE4 = 14194, ++ SEAL_FATE5 = 14195, ++ COMBAT_POTENCY1 = 35541, ++ COMBAT_POTENCY2 = 35550, ++ COMBAT_POTENCY3 = 35551, ++ COMBAT_POTENCY4 = 35552, ++ COMBAT_POTENCY5 = 35553, ++ QUICK_RECOVERY1 = 31244, ++ QUICK_RECOVERY2 = 31245, ++ BLADE_TWISTING1 = 31124, ++ //BLADE_TWISTING2 = 31126, ++ VITALITY = 61329,//rank 3 ++ DEADLY_BREW = 51626,//rank 2 ++ IMPROVED_KIDNEY_SHOT = 14176,//rank 3 ++ TURN_THE_TABLES = 51629,//rank 3 ++ SURPRISE_ATTACKS = 32601, ++ ROGUE_VIGOR = 14983, ++ //Other ++ //ROGUE_ARMOR_ENERGIZE/*Deathmantle*/ = 27787, ++ GLADIATOR_VIGOR = 21975, ++ GLYPH_BACKSTAB = 56800 ++ }; ++ ++ enum RogueSpecial ++ { ++ RELENTLESS_STRIKES_EFFECT = 14181, ++ RUTHLESSNESS_EFFECT = 14157, ++ SEAL_FATE_EFFECT = 14189, ++ //SHADOWSTEP_EFFECT_DAMAGE = 36563, ++ TURN_THE_TABLES_EFFECT = 52910,//'rank 3' ++ WAYLAY_EFFECT = 51693, ++ //434 ++ MURDEROUS_INTENT_EFFECT = 79132 ++ }; ++ }; ++}; ++ ++void AddSC_rogue_bot() ++{ ++ new rogue_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_shaman_ai.cpp b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp +new file mode 100644 +index 0000000..6671b3c +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp +@@ -0,0 +1,1340 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++#include "Totem.h" ++/* ++Shaman NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - around 30% ++TODO: ++*/ ++enum TotemSlot ++{ ++ T_FIRE = 0,//m_SummonSlot[1] ++ T_EARTH = 1,//m_SummonSlot[2] ++ T_WATER = 2,//m_SummonSlot[3] ++ T_AIR = 3,//m_SummonSlot[4] ++ MAX_TOTEMS ++}; ++struct TotemParam ++{ ++ TotemParam() : effradius(0.f) {} ++ Position pos; ++ float effradius; ++}; ++class shaman_bot : public CreatureScript ++{ ++public: ++ shaman_bot() : CreatureScript("shaman_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new shaman_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct shaman_botAI : public bot_minion_ai ++ { ++ shaman_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_SHAMAN; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_SHAMAN) != SPELL_CAST_OK) ++ return false; ++ ++ bool maelstrom = false; ++ if (!triggered) ++ maelstrom = (MaelstromCount >= 5 && ++ (spellId == GetSpell(LIGHTNING_BOLT_1) || spellId == GetSpell(CHAIN_LIGHTNING_1) || ++ spellId == GetSpell(HEALING_WAVE_1) || spellId == GetSpell(LESSER_HEALING_WAVE_1) || ++ spellId == GetSpell(CHAIN_HEAL_1) || spellId == GetSpell(HEX_1))); ++ ++ triggered |= maelstrom; ++ ++ bool result = bot_ai::doCast(victim, spellId, triggered, me->GetGUID()); ++ ++ if (result && maelstrom) ++ { ++ MaelstromCount = 0; ++ me->RemoveAurasDueToSpell(MAELSTROM_WEAPON_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ ++ return result; ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ ++ bool Shielded(Unit* target) const ++ { ++ return ++ (HasAuraName(target, WATER_SHIELD_1) || ++ HasAuraName(target, EARTH_SHIELD_1) || ++ HasAuraName(target, LIGHTNING_SHIELD_1)); ++ } ++ ++ void CheckBloodlust(uint32 diff) ++ { ++ if (!IsSpellReady(BLOODLUST_1, diff, false) || me->GetDistance(master) > 18 || IsCasting() || Rand() > 15) ++ return; ++ if (!me->IsInCombat() || !master->IsInCombat()) ++ return; ++ ++ if (HasAuraName(master, BLOODLUST_1)) ++ { ++ SetSpellCooldown(BLOODLUST_1, 3000); //fail ++ return; ++ } ++ ++ if (Unit* u = me->GetVictim()) ++ { ++ Creature* cre = u->ToCreature(); ++ if (u->GetMaxHealth() > me->GetHealth() * 2 || ++ (cre && (cre->IsDungeonBoss() || cre->isWorldBoss())) || ++ me->getAttackers().size() + master->getAttackers().size() > 5) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLOODLUST_1))) ++ { ++ SetSpellCooldown(BLOODLUST_1, 300000); //5 minutes ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ SetSpellCooldown(BLOODLUST_1, 2000); //fail ++ } ++ ++ void CheckTotems(uint32 diff) ++ { ++ //update rate ++ if (Rand() > 25) ++ return; ++ //Unsummon ++ for (uint8 i = 0; i != MAX_TOTEMS; ++i) ++ { ++ if (_totems[i].first) ++ { ++ if (master->GetDistance2d(_totems[i].second.pos.m_positionX, _totems[i].second.pos.m_positionY) > _totems[i].second.effradius && ++ me->GetDistance2d(_totems[i].second.pos.m_positionX, _totems[i].second.pos.m_positionY) > _totems[i].second.effradius) ++ { ++ Unit* to = ObjectAccessor::FindConnectedPlayer(_totems[i].first); ++ if (!to) ++ { ++ _totems[i].first.Clear(); ++ //TC_LOG_ERROR("entities.player", "%s has lost totem in slot %u! Despawned normally?", me->GetName().c_str(), i); ++ continue; ++ } ++ ++ to->ToTotem()->UnSummon(); ++ } ++ } ++ } ++ if (GC_Timer > diff || IsCasting() || me->GetDistance(master) > 15 || Feasting()) ++ return; ++ //Summon ++ //TODO: role-based totems (attack/heal) ++ if (me->IsInCombat()) ++ { ++ if (GetSpell(WINDFURY_TOTEM_1) && !_totems[T_AIR].first && !master->m_SummonSlot[T_AIR+1]) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(WINDFURY_TOTEM_1))) ++ { ++ if (me->getLevel() >= 57) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ if (!_totems[T_EARTH].first && !master->m_SummonSlot[T_EARTH+1]) ++ { ++ if (GetSpell(STRENGTH_OF_EARTH_TOTEM_1)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(STRENGTH_OF_EARTH_TOTEM_1))) ++ { ++ if (me->getLevel() >= 57) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ else if (GetSpell(STONESKIN_TOTEM_1)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(STONESKIN_TOTEM_1))) ++ { ++ if (me->getLevel() >= 57) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ if (!_totems[T_FIRE].first && !master->m_SummonSlot[T_FIRE+1]) ++ { ++ if (IsSpellReady(TOTEM_OF_WRATH_1, diff, false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(TOTEM_OF_WRATH_1))) ++ { ++ //bot's poor AI cannot use totems wisely so just reduce CD on this ++ //SetSpellCooldown(TOTEM_OF_WRATH_1, 30000); //30 sec, old 5 min ++ if (me->getLevel() >= 57) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ else if (IsSpellReady(SEARING_TOTEM_1, diff, false)) ++ { ++ if (Unit* u = me->GetVictim()) ++ { ++ if (HasRole(BOT_ROLE_DPS) && me->GetExactDist(u) < (u->isMoving() ? 10 : 25)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(SEARING_TOTEM_1))) ++ { ++ if (me->getLevel() >= 57) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ } ++ } ++ if (!me->isMoving() && !master->isMoving()) ++ { ++ if (!_totems[T_WATER].first && !master->m_SummonSlot[T_WATER+1]) ++ { ++ uint8 manapct = GetManaPCT(master); ++ uint8 hppct = GetHealthPCT(master); ++ if (GetSpell(HEALINGSTREAM_TOTEM_1) && hppct < 98 && master->getPowerType() != POWER_MANA && ++ (hppct < 25 || manapct > hppct)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(HEALINGSTREAM_TOTEM_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ else if (GetSpell(MANASPRING_TOTEM_1) && (manapct < 97 || GetManaPCT(me) < 90)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(MANASPRING_TOTEM_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ void CheckThunderStorm(uint32 diff) ++ { ++ if (!IsSpellReady(THUNDERSTORM_1, diff, false) || !HasRole(BOT_ROLE_DPS) || IsCasting() || Rand() > 25) ++ return; ++ ++ //case 1: low mana ++ if (GetManaPCT(me) < 15) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(THUNDERSTORM_1))) ++ GC_Timer = temptimer; ++ return; ++ } ++ ++ //case 2: knock attackers ++ if (IsTank()) //pretty stupid idea I think ++ return; ++ ++ //AttackerSet m_attackers = master->getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ if (b_attackers.empty()) ++ return; ++ ++ uint8 tCount = 0; ++ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if (me->GetExactDist((*iter)) > 9) continue; ++ if (CCed(*iter)) continue; ++ if (me->IsValidAttackTarget(*iter)) ++ { ++ ++tCount; ++ break; ++ } ++ } ++ ++ if (tCount > 0) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(THUNDERSTORM_1))) ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ void CheckManaTide(uint32 diff) ++ { ++ if (!IsSpellReady(MANA_TIDE_TOTEM_1, diff, false) || IAmFree() || IsCasting() || Rand() > 20) ++ return; ++ ++ Group* group = master->GetGroup(); ++ if (!group) ++ return; ++ ++ uint8 LMPcount = 0; ++ uint8 members = group->GetMembersCount(); ++ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->IsInWorld() || tPlayer->GetMapId() != me->GetMapId() || ++ (!tPlayer->IsAlive() && !tPlayer->HaveBot())) continue; ++ if (me->GetExactDist(tPlayer) > 20) continue; ++ if (tPlayer->getPowerType() != POWER_MANA) continue; ++ if (GetManaPCT(tPlayer) < 35) ++ { ++ ++LMPcount; ++ if (LMPcount > 3 || LMPcount > members / 3) break; ++ } ++ if (tPlayer->HaveBot()) ++ { ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (bot && bot->IsInWorld() && bot->getPowerType() == POWER_MANA && ++ bot->GetExactDist(me) < 20 && GetManaPCT(bot) < 35) ++ { ++ ++LMPcount; ++ if (LMPcount > 3 || LMPcount > members / 3) break; ++ } ++ } ++ } ++ } ++ ++ if (LMPcount > 3 || LMPcount > members / 3) ++ { ++ if (_totems[T_WATER].first) ++ { ++ Unit* to = ObjectAccessor::FindConnectedPlayer(_totems[T_WATER].first); ++ if (!to) ++ _totems[T_WATER].first.Clear(); ++ else ++ to->ToTotem()->UnSummon(); ++ } ++ if (doCast(me, GetSpell(MANA_TIDE_TOTEM_1))) ++ return; ++ } ++ ++ SetSpellCooldown(MANA_TIDE_TOTEM_1, 3000); //fail ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ CheckThunderStorm(diff); ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ CheckHexy(diff); ++ CheckEarthy(diff); ++ ++ if (Potion_cd <= diff && GetManaPCT(me) < 30) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ ++ CheckBloodlust(diff); ++ BuffAndHealGroup(master, diff); ++ CureGroup(master, CURE_TOXINS, diff); ++ CheckManaTide(diff); ++ CheckTotems(diff); ++ ++ if (master->IsInCombat() || me->IsInCombat()) ++ CheckDispel(diff); ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ //buff myself ++ if (GetSpell(LIGHTNING_SHIELD_1) && !IsTank() && !Shielded(me)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(LIGHTNING_SHIELD_1))) ++ GC_Timer = temptimer; ++ } ++ //heal myself ++ if (GetHealthPCT(me) < 80) ++ HealTarget(me, GetHealthPCT(me), diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_SHAMAN)) ++ return; ++ ++ CheckHexy2(diff); ++ ++ //Counter(diff); ++ DoNormalAttack(diff); ++ } ++ ++ void Counter(uint32 diff) ++ { ++ if (!IsSpellReady(WIND_SHEAR_1, diff, false) || Rand() > 60) ++ return; ++ ++ Unit* u = me->GetVictim(); ++ if (u && u->IsNonMeleeSpellCast(false)) ++ { ++ temptimer = GC_Timer; ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (doCast(u, GetSpell(WIND_SHEAR_1))) ++ { ++ SetSpellCooldown(WIND_SHEAR_1, 5000); //improved ++ GC_Timer = temptimer; ++ } ++ } ++ else if (Unit* target = FindCastingTarget(25)) ++ { ++ temptimer = GC_Timer; ++ if (me->IsNonMeleeSpellCast(false)) ++ me->InterruptNonMeleeSpells(false); ++ if (doCast(target, GetSpell(WIND_SHEAR_1))) ++ GC_Timer = temptimer; ++ } ++ } ++ ++ void DoNormalAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ ++ Counter(diff); ++ ++ //AttackerSet m_attackers = master->getAttackers(); ++ //AttackerSet b_attackers = me->getAttackers(); ++ float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ if (IsCasting()) return; ++ ++ //STORMSTRIKE ++ if (IsSpellReady(STORMSTRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && meleedist <= 5 && IsMelee() && Rand() < 70) ++ { ++ if (doCast(opponent, GetSpell(STORMSTRIKE_1))) ++ return; ++ } ++ //SHOCKS ++ if ((GetSpell(FLAME_SHOCK_1) || GetSpell(EARTH_SHOCK_1) || GetSpell(FROST_SHOCK_1)) && ++ IsSpellReady(FLAME_SHOCK_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 25 && Rand() < 30) ++ { ++ temptimer = GC_Timer; ++ ++ bool canFlameShock = (GetSpell(FLAME_SHOCK_1) != 0); ++ if (canFlameShock) ++ { ++ if (Aura* fsh = opponent->GetAura(GetSpell(FLAME_SHOCK_1), me->GetGUID())) ++ if (fsh->GetDuration() > 3000) ++ canFlameShock = false; ++ } ++ ++ if (canFlameShock) ++ { ++ if (doCast(opponent, GetSpell(FLAME_SHOCK_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ else if (GetSpell(EARTH_SHOCK_1) || GetSpell(FROST_SHOCK_1)) ++ { ++ uint32 SHOCK = !GetSpell(FROST_SHOCK_1) ? GetSpell(EARTH_SHOCK_1) : RAND(GetSpell(EARTH_SHOCK_1), GetSpell(FROST_SHOCK_1)); ++ if (SHOCK && !opponent->HasAuraWithMechanic((1<HasAura(SHOCK)) ++ { ++ if (doCast(opponent, SHOCK)) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ //LAVA BURST ++ if (IsSpellReady(LAVA_BURST_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 50) ++ { ++ if (doCast(opponent, GetSpell(LAVA_BURST_1))) ++ return; ++ } ++ ++ if (GetManaPCT(me) < 15 || (MaelstromCount < 5 && IsMelee())) ++ return; ++ ++ //CHAIN LIGHTNING ++ if (IsSpellReady(CHAIN_LIGHTNING_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 80) ++ { ++ if (doCast(opponent, GetSpell(CHAIN_LIGHTNING_1))) ++ return; ++ } ++ //LIGHTNING BOLT ++ if (IsSpellReady(LIGHTNING_BOLT_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30) ++ { ++ uint32 LIGHTNING_BOLT = GetSpell(LIGHTNING_BOLT_1); ++ if (doCast(opponent, LIGHTNING_BOLT)) ++ return; ++ } ++ } ++ ++ void CheckHexy(uint32 diff) ++ { ++ if (HexyCheckTimer <= diff) ++ { ++ Hexy = FindAffectedTarget(GetSpell(HEX_1), me->GetGUID()); ++ HexyCheckTimer = 2000; ++ } ++ } ++ ++ void CheckHexy2(uint32 diff) ++ { ++ if (Hexy == false && me->GetVictim() && IsSpellReady(HEX_1, diff, false)) ++ { ++ if (Unit* target = FindPolyTarget(20, me->GetVictim())) ++ { ++ if (doCast(target, GetSpell(HEX_1))) ++ { ++ Hexy = true; ++ HexyCheckTimer += 2000; ++ } ++ } ++ } ++ } ++ ++ void CheckEarthy(uint32 diff) ++ { ++ if (EarthyCheckTimer <= diff) ++ { ++ Unit* u = FindAffectedTarget(GetSpell(EARTH_SHIELD_1), me->GetGUID(), 90.f, 3); ++ Earthy = (u && (IsTank(u) || u == master)); ++ EarthyCheckTimer = 1000; ++ } ++ } ++ ++ void DoNonCombatActions(uint32 diff) ++ { ++ if (GC_Timer > diff || me->IsMounted() || IsCasting()) ++ return; ++ ++ RezGroup(GetSpell(ANCESTRAL_SPIRIT_1), master); ++ ++ if (Feasting()) return; ++ ++ if (Shielded(me) && Rand() < 25) ++ { ++ Aura* shield = NULL; ++ uint32 SHIELD = HasRole(BOT_ROLE_DPS) ? GetSpell(LIGHTNING_SHIELD_1) : 0; ++ if (SHIELD) ++ shield = me->GetAura(SHIELD); ++ if (!shield && IsTank() && GetSpell(EARTH_SHIELD_1)) ++ { ++ SHIELD = GetSpell(EARTH_SHIELD_1); ++ shield = me->GetAura(SHIELD); ++ } ++ if (!shield && GetSpell(WATER_SHIELD_1)) ++ { ++ SHIELD = GetSpell(WATER_SHIELD_1); ++ shield = me->GetAura(SHIELD); ++ } ++ if (shield && shield->GetCharges() < 5) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, SHIELD)) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ ++ bool BuffTarget(Unit* target, uint32 diff) ++ { ++ if (!GetSpell(WATER_WALKING_1) && !GetSpell(WATER_BREATHING_1) && !GetSpell(EARTH_SHIELD_1)) ++ return false; ++ ++ if (GC_Timer > diff || !target || !target->IsAlive() || Rand() > 40) ++ return false; ++ ++ if (GetSpell(EARTH_SHIELD_1) && Earthy == false && (target == master || IsTank(target)) && ++ (target->IsInCombat() || !target->isMoving()) && ++ me->GetExactDist(target) < 40 && Rand() < 75) ++ { ++ bool cast = !Shielded(target); ++ if (!cast) ++ if (Aura* eShield = target->GetAura(GetSpell(EARTH_SHIELD_1))) ++ if (eShield->GetCharges() < 5) ++ cast = true; ++ if (cast && doCast(target, GetSpell(EARTH_SHIELD_1))) ++ { ++ Earthy = true; ++ //GC_Timer = 800; ++ return true; ++ } ++ } ++ ++ if (me->GetExactDist(target) > 30) return false; ++ if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; ++ ++ if (target->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) ++ { ++ //bots don't need water breathing ++ if (GetSpell(WATER_BREATHING_1) && target->GetTypeId() == TYPEID_PLAYER && ++ !target->HasAuraType(SPELL_AURA_WATER_BREATHING) && ++ doCast(target, GetSpell(WATER_BREATHING_1))) ++ { ++ //GC_Timer = 800; ++ return true; ++ } ++ //water walking breaks on any damage ++ if (GetSpell(WATER_WALKING_1) && target->getAttackers().empty() && ++ !target->HasAuraType(SPELL_AURA_WATER_WALK) && ++ doCast(target, GetSpell(WATER_WALKING_1))) ++ { ++ //GC_Timer = 800; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ void CheckDispel(uint32 diff) ++ { ++ if (!IsSpellReady(PURGE_1, diff, false) || IsCasting() || Rand() > 35) ++ return; ++ ++ Unit* target = FindHostileDispelTarget(); ++ if (target && doCast(target, GetSpell(PURGE_1))) ++ {} ++ ++ SetSpellCooldown(PURGE_1, 2000); //fail ++ } ++ ++ bool HealTarget(Unit* target, uint8 hp, uint32 diff) ++ { ++ if (!HasRole(BOT_ROLE_HEAL)) ++ return false; ++ if (hp > 97) ++ return false; ++ if (!target || !target->IsAlive() || me->GetExactDist(target) > 40) ++ return false; ++ if (Rand() > 50 + 20*target->IsInCombat() + 50*master->GetMap()->IsRaid()) ++ return false; ++ ++ //PLACEHOLDER: Instant spell req. interrupt current spell ++ ++ if (IsCasting()) return false; ++ ++ if (IsSpellReady(LESSER_HEALING_WAVE_1, diff) && ++ ((hp > 70 && hp < 85) || hp < 50 || GetLostHP(target) > 1800) && Rand() < 75) ++ { ++ if (doCast(target, GetSpell(LESSER_HEALING_WAVE_1))) ++ return true; ++ } ++ if (IsSpellReady(HEALING_WAVE_1, diff) && ++ hp > 40 && (hp < 75 || GetLostHP(target) > 4000) && Rand() < 65) ++ { ++ if (doCast(target, GetSpell(HEALING_WAVE_1))) ++ return true; ++ } ++ if (IsSpellReady(CHAIN_HEAL_1, diff) && ++ ((hp > 40 && hp < 90) || GetLostHP(target) > 1300) && Rand() < 120) ++ { ++ if (IsSpellReady(RIPTIDE_1, diff, false) && (hp < 85 || GetLostHP(target) > 2500) && ++ !target->HasAura(GetSpell(RIPTIDE_1))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(target, GetSpell(RIPTIDE_1), true)) ++ { ++ if (doCast(target, GetSpell(CHAIN_HEAL_1))) ++ { ++ GC_Timer = temptimer; ++ return true; ++ } ++ } ++ } ++ else if (doCast(target, GetSpell(CHAIN_HEAL_1))) ++ return true; ++ } ++ ++ return false; ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ ////Incite: 15% additional critical chance for Cleave, Heroic Strike and Thunder Clap ++ //if (lvl >= 15 && spellId == CLEAVE /*|| spellId == HEROICSTRIKE || spellId == THUNDERCLAP*/) ++ // aftercrit += 15.f; ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost (all) spells ++ if (lvl >= 21) ++ pctbonus += 0.25f; ++ } ++ ++ //SHAMAN_T8_ENCHANCEMENT_2P_BONUS: 20% bonus damage for Lava Lash and Stormstrike ++ if (lvl >= 60 && ++ (spellId == STORMSTRIKE_DAMAGE || spellId == STORMSTRIKE_DAMAGE_OFFHAND/* || spellId == LAVA_LASH*/)) ++ pctbonus += 0.2f; ++ ++ //custom bonus to make stormstrike useful ++ if (spellId == STORMSTRIKE_DAMAGE || spellId == STORMSTRIKE_DAMAGE_OFFHAND) ++ pctbonus += 1.0f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Call of Thunder: 5% additional critical chance for Lightning Bolt, Chain Lightning and Thunderstorm ++ if (lvl >= 30 && ++ (spellId == GetSpell(LIGHTNING_BOLT_1) || ++ spellId == GetSpell(CHAIN_LIGHTNING_1) || ++ spellId == GetSpell(THUNDERSTORM_1))) ++ aftercrit += 5.f; ++ //Tidal Mastery (part 2): 5% additional critical chance for lightning spells ++ if (lvl >= 25 && (SPELL_SCHOOL_MASK_NATURE & spellInfo->GetSchoolMask())) ++ aftercrit += 5.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!spell damage is not yet critical and will be multiplied by 1.5 ++ //so we should put here bonus damage mult /1.5 ++ //Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost (all) spells ++ if (lvl >= 21) ++ pctbonus += 0.333f; ++ //Lava Flows (part 1): 24% additional crit damage bonus for Lava Burst ++ if (lvl >= 50 && spellId == GetSpell(LAVA_BURST_1)) ++ pctbonus += 0.16f; ++ } ++ //Concussion: 5% bonus damage for Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shocks ++ if (lvl >= 10 && ++ (spellId == GetSpell(LIGHTNING_BOLT_1) || ++ spellId == GetSpell(CHAIN_LIGHTNING_1) || ++ spellId == GetSpell(THUNDERSTORM_1) || ++ spellId == GetSpell(LAVA_BURST_1) || ++ spellId == GetSpell(EARTH_SHOCK_1) || ++ spellId == GetSpell(FROST_SHOCK_1) || ++ spellId == GetSpell(FLAME_SHOCK_1))) ++ pctbonus += 0.05f; ++ //Call of Flame (part 2): 6% bonus damage for Lava burst ++ if (lvl >= 15 && spellId == GetSpell(LAVA_BURST_1)) ++ pctbonus += 0.06f; ++ //Storm, Earth and fire (part 3): 60% bonus damage for Flame Shock (periodic damage in fact but who cares?) ++ if (lvl >= 40 && spellId == GetSpell(FLAME_SHOCK_1)) ++ pctbonus += 0.6f; ++ //Booming Echoes (part 2): 20% bonus damage for Flame Shock and Frost Shock (direct damage) ++ if (lvl >= 45 && ++ (spellId == GetSpell(FLAME_SHOCK_1) || ++ spellId == GetSpell(FROST_SHOCK_1))) ++ pctbonus += 0.2f; ++ //Improved Shields (part 1): 15% bonus damage for Lightning Shield orbs ++ if (lvl >= 15 && spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(LIGHTNING_SHIELD_DAMAGE_1))) ++ pctbonus += 0.15f; ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ float flat_mod = 0.0f; ++ ++ //Healing Way: 25% bonus healing for Healing Wave ++ if (lvl >= 30 && spellId == GetSpell(HEALING_WAVE_1)) ++ pctbonus += 0.25f; ++ //Purification: 10% bonus healing for all spells ++ if (lvl >= 35) ++ pctbonus += 0.1f; ++ //Nature's Blessing: 15% of Intellect to healing ++ if (lvl >= 45) ++ flat_mod += me->GetTotalStatValue(STAT_INTELLECT) * 1.0f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ //Improved Chain Heal: 20% bonus healing for Chain Heal ++ if (lvl >= 45 && spellId == GetSpell(CHAIN_HEAL_1)) ++ pctbonus += 0.2f; ++ //Improved Earth Shield: 10% bonus healing for Earth Shield ++ //Glyph of Earth Shield: 20% bonus healing for Earth Shield ++ if (lvl >= 50 && spellId == EARTH_SHIELD_HEAL) ++ pctbonus += 0.1f + 0.2f; ++ //Improved Shields (part 3): 15% bonus healing for Earth Shield ++ if (lvl >= 15 && spellId == EARTH_SHIELD_HEAL) ++ pctbonus += 0.15f; ++ //Tidal Waves (part 2): 20% bonus (from spellpower) for Healing Wave and 10% bonus (from spellpower) for Lesser Healing Wave ++ if (lvl >= 55) ++ { ++ if (spellId == GetSpell(HEALING_WAVE_1)) ++ flat_mod += spellpower * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ else if (spellId == GetSpell(LESSER_HEALING_WAVE_1)) ++ flat_mod += spellpower * 0.1f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateLevelPenalty(spellInfo) * stack; ++ } ++ ++ heal = heal * (1.0f + pctbonus) + flat_mod; ++ } ++ ++ void ApplyClassCritMultiplierHeal(Unit const* /*victim*/, float& crit_chance, SpellInfo const* /*spellInfo*/, SpellSchoolMask schoolMask, WeaponAttackType /*attackType*/) const ++ { ++ //uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float aftercrit = 0.0f; ++ ++ //Tidal Mastery (part 1): 5% additional critical chance for healing spells ++ if (lvl >= 25 && (schoolMask & SPELL_SCHOOL_MASK_NATURE)) ++ aftercrit += 5.f; ++ //Blessing of the Eternals: 4% additional critical chance for all spells ++ if (lvl >= 45) ++ aftercrit += 4.f; ++ ++ crit_chance += aftercrit; ++ } ++ ++ void OnBotDespawn(Creature* summon) ++ { ++ if (!summon) ++ { ++ UnsummonAll(); ++ return; ++ } ++ ++ TempSummon* totem = summon->ToTempSummon(); ++ if (!totem || !totem->ToTotem()) ++ { ++ TC_LOG_ERROR("entities.player", "SummonedCreatureDespawn(): Shaman bot %s has despawned summon %s which is not a temp summon or not a totem...", me->GetName().c_str(), summon->GetName().c_str()); ++ return; ++ } ++ ++ int8 slot = -1; ++ switch (totem->m_Properties->Id) ++ { ++ case SUMMON_TYPE_TOTEM_FIRE: ++ slot = T_FIRE; ++ break; ++ case SUMMON_TYPE_TOTEM_EARTH: ++ slot = T_EARTH; ++ break; ++ case SUMMON_TYPE_TOTEM_WATER: ++ slot = T_WATER; ++ break; ++ case SUMMON_TYPE_TOTEM_AIR: ++ slot = T_AIR; ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "SummonedCreatureDespawn(): Shaman bot %s has despawned totem %s with unknown type %u", me->GetName().c_str(), summon->GetName().c_str(), totem->m_Properties->Id); ++ return; ++ } ++ ++ _totems[slot].first.Clear(); ++ } ++ ++ void OnBotSummon(Creature* summon) ++ { ++ TempSummon* totem = summon->ToTempSummon(); ++ if (!totem || !totem->ToTotem()) ++ { ++ //TC_LOG_ERROR("entities.player", "OnBotSummon(): Shaman bot %s has summoned creature %s which is not a temp summon or not a totem...", me->GetName().c_str(), summon->GetName().c_str()); ++ return; ++ } ++ ++ totem->SetCreatorGUID(me->GetGUID()); ++ ++ int8 slot = -1; ++ switch (totem->m_Properties->Id) ++ { ++ case SUMMON_TYPE_TOTEM_FIRE: ++ slot = T_FIRE; ++ break; ++ case SUMMON_TYPE_TOTEM_EARTH: ++ slot = T_EARTH; ++ break; ++ case SUMMON_TYPE_TOTEM_WATER: ++ slot = T_WATER; ++ break; ++ case SUMMON_TYPE_TOTEM_AIR: ++ slot = T_AIR; ++ break; ++ default: ++ TC_LOG_ERROR("entities.player", "OnBotSummon(): Shaman bot %s has summoned totem %s with unknown type %u", me->GetName().c_str(), summon->GetName().c_str(), totem->m_Properties->Id); ++ return; ++ } ++ ++ float radius = 0.f; ++ if (SpellInfo const* info = sSpellMgr->GetSpellInfo(summon->m_spells[0])) ++ if (SpellRadiusEntry const* entry = info->Effects[0].RadiusEntry) ++ radius = entry->RadiusMax; ++ ++ _totems[slot].first = summon->GetGUID(); ++ _totems[slot].second.pos.Relocate(*summon); ++ _totems[slot].second.effradius = std::max(radius, 20.f) + 5.f; ++ ++ //TC_LOG_ERROR("entities.player", "shaman bot: summoned %s (type %u) at x='%f', y='%f', z='%f'", ++ // summon->GetName().c_str(), slot, _totems[slot].second.GetPositionX(), _totems[slot].second.GetPositionY(), _totems[slot].second.GetPositionZ()); ++ ++ summon->SetDisplayId(me->GetModelForTotem(PlayerTotemType(totem->m_Properties->Id))); ++ master->m_SummonSlot[++slot].Clear(); ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ //Maelstrom Weapon improved: 10% to gain full stack and 30% to add an extra stack ++ if (spellId == MAELSTROM_WEAPON_BUFF) ++ { ++ if (Aura* mwb = me->GetAura(MAELSTROM_WEAPON_BUFF)) ++ { ++ uint32 stacks = mwb->GetStackAmount(); ++ if (stacks < 5) ++ { ++ if (urand(1,100) <= 10) ++ mwb->ModStackAmount(5); ++ if (urand(1,100) <= 30) ++ mwb->ModStackAmount(1); ++ } ++ ++ MaelstromCount = mwb->GetStackAmount(); ++ } ++ ++ MaelstromTimer = 30000; //30 sec duration then reset ++ } ++ ++ OnSpellHit(caster, spell); ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ //Shields improvement, replaces Static Shock (part 2) and Improved Earth Shield (part 1) ++ if (spellId == GetSpell(LIGHTNING_SHIELD_1) || ++ spellId == GetSpell(EARTH_SHIELD_1)/* || ++ spellId == GetSpell(WATER_SHIELD_1)*/) ++ { ++ if (Aura* shield = target->GetAura(spellId, me->GetGUID())) ++ { ++ shield->SetCharges(shield->GetCharges() + 12); ++ } ++ } ++ //Lightning Overload: 20% cast SAME spell with no mana! make sure this does not proc on itself! ++ if (me->getLevel() >= 40 && (spellId == GetSpell(LIGHTNING_SHIELD_1) || spellId == GetSpell(CHAIN_LIGHTNING_1))) ++ { ++ bool cast = (urand(1,100) <= 20); ++ if (spellId == GetSpell(LIGHTNING_BOLT_1)) ++ { ++ if (LOvBolt == false) ++ { ++ if (cast) ++ { ++ LOvBolt = true; ++ me->CastSpell(target, spellId, true); ++ } ++ } ++ else ++ LOvBolt = false; ++ } ++ if (spellId == GetSpell(CHAIN_LIGHTNING_1)) ++ { ++ if (LOvChain == false) ++ { ++ if (cast) ++ { ++ LOvChain = true; ++ me->CastSpell(target, spellId, true); ++ } ++ } ++ else ++ LOvChain = false; ++ } ++ } ++ if (spellId == GetSpell(STORMSTRIKE_1)) ++ { ++ //Windfury: 10% chance ++ if (WindfuryTimer == 0 && me->getLevel() >= 30) ++ { ++ if (urand(0,100) < 10) ++ WindfuryTimer = 1000; ++ ++ if (WindfuryTimer > 0) ++ me->CastSpell(target, WINDFURY_PROC, true); ++ } ++ } ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ if (victim == me) ++ return; ++ ++ if (damageType == DIRECT_DAMAGE) ++ { ++ //Windfury: 10% chance ++ if (WindfuryTimer == 0 && me->getLevel() >= 30) ++ { ++ if (urand(0,100) < 10) ++ WindfuryTimer = 1000; ++ ++ if (WindfuryTimer > 0) ++ me->CastSpell(victim, WINDFURY_PROC, true); ++ } ++ } ++ ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void UnsummonAll() ++ { ++ for (uint8 i = 0; i != MAX_TOTEMS; ++i) ++ { ++ if (_totems[i].first) ++ { ++ Unit* to = ObjectAccessor::FindConnectedPlayer(_totems[i].first); ++ if (!to) ++ { ++ //TC_LOG_ERROR("entities.player", "%s has no totem in slot %u during remove!", me->GetName().c_str(), i); ++ continue; ++ } ++ to->ToTotem()->UnSummon(); ++ } ++ } ++ } ++ ++ void Reset() ++ { ++ HexyCheckTimer = 3000; ++ EarthyCheckTimer = 2000; ++ MaelstromTimer = 0; ++ WindfuryTimer = 0; ++ ++ MaelstromCount = 0; ++ ++ Hexy = false; ++ Earthy = false; ++ LOvBolt = false; ++ LOvChain = false; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (HexyCheckTimer > diff) HexyCheckTimer -= diff; ++ if (EarthyCheckTimer > diff) EarthyCheckTimer -= diff; ++ ++ if (MaelstromTimer > diff) MaelstromTimer -= diff; ++ else if (MaelstromCount > 0) MaelstromCount = 0; ++ ++ if (WindfuryTimer > diff) WindfuryTimer -= diff; ++ else WindfuryTimer = 0; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(HEALING_WAVE_1); ++ InitSpellMap(CHAIN_HEAL_1); ++ InitSpellMap(LESSER_HEALING_WAVE_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(RIPTIDE_1) : RemoveSpell(RIPTIDE_1); ++ InitSpellMap(ANCESTRAL_SPIRIT_1); ++ CURE_TOXINS = lvl >= 39 ? InitSpell(me, CLEANSE_SPIRIT_1) : InitSpell(me, CURE_TOXINS_1); ++ InitSpellMap(CURE_TOXINS); ++ InitSpellMap(FLAME_SHOCK_1); ++ InitSpellMap(EARTH_SHOCK_1); ++ InitSpellMap(FROST_SHOCK_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(STORMSTRIKE_1) : RemoveSpell(STORMSTRIKE_1); ++ InitSpellMap(LIGHTNING_BOLT_1); ++ InitSpellMap(CHAIN_LIGHTNING_1); ++ InitSpellMap(LAVA_BURST_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(THUNDERSTORM_1) : RemoveSpell(THUNDERSTORM_1); ++ InitSpellMap(LIGHTNING_SHIELD_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(EARTH_SHIELD_1) : RemoveSpell(EARTH_SHIELD_1); ++ /*NYI*///InitSpellMap(WATER_SHIELD_1); ++ InitSpellMap(WATER_BREATHING_1); ++ InitSpellMap(WATER_WALKING_1); ++ /*CUSTOM*/lvl >= 60 ? InitSpellMap(BLOODLUST_1) : RemoveSpell(BLOODLUST_1); ++ InitSpellMap(PURGE_1); ++ InitSpellMap(WIND_SHEAR_1); ++ InitSpellMap(HEX_1); ++ /*Quest*/lvl >= 10 ? InitSpellMap(STONESKIN_TOTEM_1) : RemoveSpell(STONESKIN_TOTEM_1); ++ InitSpellMap(HEALINGSTREAM_TOTEM_1); ++ InitSpellMap(MANASPRING_TOTEM_1); ++ /*Quest*/lvl >= 10 ? InitSpellMap(SEARING_TOTEM_1) : RemoveSpell(SEARING_TOTEM_1); ++ InitSpellMap(WINDFURY_TOTEM_1); ++ InitSpellMap(STRENGTH_OF_EARTH_TOTEM_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(TOTEM_OF_WRATH_1) : RemoveSpell(TOTEM_OF_WRATH_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(MANA_TIDE_TOTEM_1) : RemoveSpell(MANA_TIDE_TOTEM_1); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ RefreshAura(ELEMENTAL_WARDING, level >= 58 ? 2 : level >= 15 ? 1 : 0); ++ RefreshAura(ELEMENTAL_DEVASTATION3, level >= 18 ? 1 : 0); ++ RefreshAura(ELEMENTAL_DEVASTATION2, level >= 15 && level < 18 ? 1 : 0); ++ RefreshAura(ELEMENTAL_DEVASTATION1, level >= 12 && level < 15 ? 1 : 0); ++ RefreshAura(ANCESTRAL_KNOWLEDGE, level >= 30 ? 3 : level >= 20 ? 2 : level >= 10 ? 1 : 0); ++ RefreshAura(TOUGHNESS, level >= 25 ? 1 : 0); ++ RefreshAura(FLURRY5, level >= 29 ? 1 : 0); ++ RefreshAura(FLURRY4, level >= 28 && level < 29 ? 1 : 0); ++ RefreshAura(FLURRY3, level >= 27 && level < 28 ? 1 : 0); ++ RefreshAura(FLURRY2, level >= 26 && level < 27 ? 1 : 0); ++ RefreshAura(FLURRY1, level >= 25 && level < 26 ? 1 : 0); ++ RefreshAura(WEAPON_MASTERY, level >= 50 ? 3 : level >= 40 ? 2 : level >= 30 ? 1 : 0); ++ RefreshAura(STATIC_SHOCK, level >= 45 ? 2 : level >= 41 ? 1 : 0); ++ RefreshAura(ANCESTRAL_HEALING, level >= 20 ? 1 : 0); ++ RefreshAura(ANCESTRAL_AWAKENING, level >= 50 ? 1 : 0); ++ RefreshAura(SHAMAN_T10_RESTO_4P, level >= 70 ? 1 : 0); ++ RefreshAura(MAELSTROM_WEAPON5, level >= 70 ? 2 : level >= 60 ? 1 : 0); ++ RefreshAura(MAELSTROM_WEAPON4, level >= 55 && level < 60 ? 1 : 0); ++ RefreshAura(MAELSTROM_WEAPON3, level >= 50 && level < 55 ? 1 : 0); ++ RefreshAura(MAELSTROM_WEAPON2, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(MAELSTROM_WEAPON1, level >= 40 && level < 45 ? 1 : 0); ++ RefreshAura(UNLEASHED_RAGE, level >= 40 ? 1 : 0); ++ RefreshAura(IMPROVED_STORMSTRIKE, level >= 40 ? 1 : 0); ++ RefreshAura(ELEMENTAL_OATH, level >= 40 ? 1 : 0); ++ RefreshAura(EARTHLIVING_WEAPON_PASSIVE_6, level >= 70 ? 3 : 0); ++ RefreshAura(EARTHLIVING_WEAPON_PASSIVE_5, level >= 50 && level < 70 ? 3 : 0); ++ RefreshAura(EARTHLIVING_WEAPON_PASSIVE_4, level >= 30 && level < 50 ? 3 : 0); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case HEALING_WAVE_1: ++ case CHAIN_HEAL_1: ++ case LESSER_HEALING_WAVE_1: ++ case RIPTIDE_1: ++ case CURE_TOXINS_1: ++ case CLEANSE_SPIRIT_1: ++ case BLOODLUST_1: ++ case WATER_SHIELD_1: ++ case MANA_TIDE_TOTEM_1: ++ return true; ++ default: ++ return false; ++ } ++ } ++ ++ private: ++ typedef std::pair BotTotem; ++ BotTotem _totems[MAX_TOTEMS]; ++ uint32 CURE_TOXINS; ++ //Timers ++ uint32 HexyCheckTimer, EarthyCheckTimer, MaelstromTimer, WindfuryTimer; ++ uint8 MaelstromCount; ++ bool Hexy, Earthy, LOvChain, LOvBolt; ++ ++ enum ShamanBaseSpells ++ { ++ HEALING_WAVE_1 = 331, ++ CHAIN_HEAL_1 = 1064, ++ LESSER_HEALING_WAVE_1 = 8004, ++ RIPTIDE_1 = 61295, ++ ANCESTRAL_SPIRIT_1 = 2008, ++ CURE_TOXINS_1 = 526, ++ CLEANSE_SPIRIT_1 = 51886, ++ FLAME_SHOCK_1 = 8050, ++ EARTH_SHOCK_1 = 8042, ++ FROST_SHOCK_1 = 8056, ++ STORMSTRIKE_1 = 17364, ++ LIGHTNING_BOLT_1 = 403, ++ CHAIN_LIGHTNING_1 = 421, ++ LAVA_BURST_1 = 51505, ++ THUNDERSTORM_1 = 51490, ++ LIGHTNING_SHIELD_1 = 324, ++ EARTH_SHIELD_1 = 974, ++ WATER_SHIELD_1 = 52127, ++ WATER_BREATHING_1 = 131, ++ WATER_WALKING_1 = 546, ++ //BLOODLUST_1 = 54516,//custom, moved to specials ++ PURGE_1 = 370, ++ WIND_SHEAR_1 = 57994, ++ HEX_1 = 51514, ++ STONESKIN_TOTEM_1 = 8071, ++ HEALINGSTREAM_TOTEM_1 = 5394, ++ MANASPRING_TOTEM_1 = 5675, ++ SEARING_TOTEM_1 = 3599, ++ WINDFURY_TOTEM_1 = 8512, ++ STRENGTH_OF_EARTH_TOTEM_1 = 8075, ++ TOTEM_OF_WRATH_1 = 30706, ++ MANA_TIDE_TOTEM_1 = 16190 ++ }; ++ ++ enum ShamanPassives ++ { ++ //Elemental ++ ELEMENTAL_DEVASTATION1 = 30160, ++ ELEMENTAL_DEVASTATION2 = 29179, ++ ELEMENTAL_DEVASTATION3 = 29180, ++ ELEMENTAL_WARDING = 28998,//rank 3 ++ ELEMENTAL_OATH = 51470,//rank 2 ++ //Enchancement ++ ANCESTRAL_KNOWLEDGE = 17489,//rank 5 ++ TOUGHNESS = 16309,//rank 5 ++ FLURRY1 = 16256, ++ FLURRY2 = 16281, ++ FLURRY3 = 16282, ++ FLURRY4 = 16283, ++ FLURRY5 = 16284, ++ WEAPON_MASTERY = 29086,//rank 3 ++ UNLEASHED_RAGE = 30809,//rank 3 ++ STATIC_SHOCK = 51527,//rank 3 ++ IMPROVED_STORMSTRIKE = 51522,//rank 2 ++ MAELSTROM_WEAPON1 = 51528, ++ MAELSTROM_WEAPON2 = 51529, ++ MAELSTROM_WEAPON3 = 51530, ++ MAELSTROM_WEAPON4 = 51531, ++ MAELSTROM_WEAPON5 = 51532, ++ //Restoration ++ ANCESTRAL_HEALING = 16240,//rank 3 ++ ANCESTRAL_AWAKENING = 51558,//rank 3 ++ //Special ++ SHAMAN_T10_RESTO_4P = 70808 //Chain Heal HoT ++ }; ++ ++ enum ShamanSpecial ++ { ++ //2 extra white attacks ++ //100 yd ++ //"Increases attack power for 1.50 sec" ++ //Warning! can proc even from itself! ++ WINDFURY_PROC = 32910, ++ //"Increases melee,ranged and spell casting speed by 35% ++ //for all party members. Lasts 20 sec." ++ //250 mana, 20 yd ++ //affects raid ++ //no penalty ++ BLOODLUST_1 = 54516, ++ //20% chance to put HoT on healed target over 12 sec ++ EARTHLIVING_WEAPON_PASSIVE_4 = 52005,//348 base hp ++ EARTHLIVING_WEAPON_PASSIVE_5 = 52007,//456 base hp ++ EARTHLIVING_WEAPON_PASSIVE_6 = 52008,//652 base hp ++ ++ MAELSTROM_WEAPON_BUFF = 53817, ++ STORMSTRIKE_DAMAGE = 32175, ++ STORMSTRIKE_DAMAGE_OFFHAND = 32176, ++ ++ LIGHTNING_SHIELD_DAMAGE_1 = 26364, ++ EARTH_SHIELD_HEAL = 379 ++ }; ++ }; ++}; ++ ++ ++void AddSC_shaman_bot() ++{ ++ new shaman_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_warlock_ai.cpp b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp +new file mode 100644 +index 0000000..d5eac66 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp +@@ -0,0 +1,519 @@ ++#include "bot_ai.h" ++//#include "botmgr.h" ++//#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellAuras.h" ++/* ++Warlock NpcBot (reworked by Graff onlysuffering@gmail.com) ++Voidwalker pet AI included ++Complete - 3% ++TODO: ++*/ ++class warlock_bot : public CreatureScript ++{ ++public: ++ warlock_bot() : CreatureScript("warlock_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new warlock_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct warlock_botAI : public bot_minion_ai ++ { ++ warlock_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_WARLOCK; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_WARRIOR) != SPELL_CAST_OK) ++ return false; ++ ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { me->SetBotsPetDied(); bot_minion_ai::JustDied(u); } ++ void DoNonCombatActions() { } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ feartimer = std::max(feartimer, 1000); ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ ////if pet is dead or unreachable ++ //Creature* m_botsPet = me->GetBotsPet(); ++ //if (!m_botsPet || m_botsPet->FindMap() != master->GetMap() || (me->GetDistance2d(m_botsPet) > sWorld->GetMaxVisibleDistanceOnContinents() - 20.f)) ++ // if (master->getLevel() >= 10 && !me->IsInCombat() && !IsCasting() && !me->IsMounted()) ++ // SummonBotsPet(PET_VOIDWALKER); ++ ++ //TODO: implement healthstone ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (Potion_cd <= diff && GetManaPCT(me) < 50) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, MANAPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ if (!me->IsInCombat()) ++ DoNonCombatActions(); ++ ++ if (!CheckAttackTarget(BOT_CLASS_WARLOCK)) ++ return; ++ ++ DoNormalAttack(diff); ++ } ++ ++ void DoNormalAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent); ++ } ++ else ++ return; ++ ++ //TODO: add more damage spells ++ ++ if (feartimer <= diff && GC_Timer <= diff) ++ { CheckFear(); feartimer = 2000; } ++ ++ if (IsSpellReady(RAIN_OF_FIRE_1, diff) && !me->isMoving() && HasRole(BOT_ROLE_DPS) && Rand() < 25) ++ { ++ Unit* blizztarget = FindAOETarget(30, true); ++ if (blizztarget && doCast(blizztarget, GetSpell(RAIN_OF_FIRE_1))) ++ return; ++ SetSpellCooldown(RAIN_OF_FIRE_1, 2000);//fail ++ } ++ ++ float dist = me->GetExactDist(opponent); ++ ++ if (IsSpellReady(CURSE_OF_THE_ELEMENTS_1, diff) && dist < 30 && Rand() < 15 && ++ !HasAuraName(opponent, CURSE_OF_THE_ELEMENTS_1) && ++ doCast(opponent, GetSpell(CURSE_OF_THE_ELEMENTS_1))) ++ { ++ GC_Timer = 800; ++ return; ++ } ++ ++ if (IsSpellReady(CORRUPTION_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 25 && ++ !opponent->HasAura(GetSpell(CORRUPTION_1), me->GetGUID()) && ++ doCast(opponent, GetSpell(CORRUPTION_1))) ++ return; ++ ++ if (IsSpellReady(HAUNT_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 25 && ++ !opponent->HasAura(GetSpell(HAUNT_1), me->GetGUID()) && ++ doCast(opponent, GetSpell(HAUNT_1))) ++ return; ++ ++ if (GC_Timer <= diff && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 15 && !Afflicted(opponent)) ++ { ++ if (GetSpellCooldown(CONFLAGRATE_1) <= 8000 && doCast(opponent, GetSpell(IMMOLATE_1))) ++ return; ++ else if (doCast(opponent, GetSpell(UNSTABLE_AFFLICTION_1))) ++ return; ++ } ++ ++ if (IsSpellReady(CONFLAGRATE_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 35 && ++ HasAuraName(opponent, IMMOLATE_1) && ++ doCast(opponent, GetSpell(CONFLAGRATE_1))) ++ return; ++ ++ if (IsSpellReady(CHAOS_BOLT_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 50 && ++ doCast(opponent, GetSpell(CHAOS_BOLT_1))) ++ return; ++ ++ if (IsSpellReady(SHADOW_BOLT_1, diff) && HasRole(BOT_ROLE_DPS) && dist < 30 && ++ doCast(opponent, GetSpell(SHADOW_BOLT_1))) ++ return; ++ } ++ ++ uint8 Afflicted(Unit* target) ++ { ++ if (!target || target->isDead()) return 0; ++ bool aff = HasAuraName(target, UNSTABLE_AFFLICTION_1, me->GetGUID()); ++ bool imm = HasAuraName(target, IMMOLATE_1, me->GetGUID()); ++ if (imm) return 1; ++ if (aff) return 2; ++ return 0; ++ } ++ ++ void CheckFear() ++ { ++ uint32 FEAR = GetSpell(FEAR_1); ++ if (Unit* u = FindAffectedTarget(FEAR, me->GetGUID())) ++ if (Aura* aura = u->GetAura(FEAR, me->GetGUID())) ++ if (aura->GetDuration() > 3000) ++ return; ++ Unit* feartarget = FindFearTarget(); ++ if (feartarget && doCast(feartarget, FEAR)) ++ return; ++ } ++ ++ //void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) ++ //{ ++ // if (summon == me->GetBotsPet()) ++ // me->SetBotsPetDied(); ++ //} ++ ++ //void SummonedCreatureDespawn(Creature* summon) ++ //{ ++ // if (summon == me->GetBotsPet()) ++ // me->SetBotsPet(NULL); ++ //} ++ ++ void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool& crit) const ++ { ++ //uint32 spellId = spellInfo->Id; ++ //uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ ////Shatter: frozen targets crit ++ //if (lvl >= 11 && damageinfo.target && damageinfo.target->isFrozen()) ++ // aftercrit *= 4.f; ++ ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ ////!!!spell damage is not yet critical and will be multiplied by 1.5 ++ ////so we should put here bonus damage mult /1.5 ++ ////Spell Power: 50% additional crit damage bonus for All spells ++ //if (lvl >= 55) ++ // pctbonus += 0.333f; ++ } ++ //if (lvl >= 11 && spellId == FROSTBOLT && damageinfo.target && damageinfo.target->isFrozen()) ++ // pctbonus *= 0.2f; ++ ++ ////Spellpower bonus damage (temp) ++ //if (m_spellpower > 0) ++ //{ ++ // if (spellId == SHADOW_BOLT) ++ // fdamage += m_spellpower * 1.38f; ++ // else if (spellId == IMMOLATE) ++ // fdamage += m_spellpower * 0.75f; //guessed ++ // else if (spellId == CONFLAGRATE) ++ // fdamage += m_spellpower * 2.75f; //guessed ++ // else if (spellId == CHAOS_BOLT) ++ // fdamage += m_spellpower * 2.25f * 1.24f; ++ // else if (spellId == RAIN_OF_FIRE || spellId == 42223) ++ // fdamage += m_spellpower * 0.25f * 4.f; ++ // else if (spellId == HAUNT) ++ // fdamage += m_spellpower * 1.75f; ++ //} ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassDamageMultiplierEffect(SpellInfo const* /*spellInfo*/, uint8 /*effect_index*/, float& /*value*/) const ++ { ++ //uint32 spellId = spellInfo->Id; ++ ++ //float pct_mod = 1.f; ++ ++ //Spellpower bonus damage (temp) ++ //if (spellInfo->Effects[effect_index].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE) ++ //{ ++ // if (spellId == CORRUPTION) ++ // value += m_spellpower * 1.35f / float(spellInfo->GetMaxDuration() / spellInfo->Effects[effect_index].Amplitude); ++ // else if (spellId == IMMOLATE) ++ // value += m_spellpower * 1.59f / float(spellInfo->GetMaxDuration() / spellInfo->Effects[effect_index].Amplitude); ++ // else if (spellId == UNSTABLE_AFFLICTION) ++ // value += m_spellpower * 1.68f / float(spellInfo->GetMaxDuration() / spellInfo->Effects[effect_index].Amplitude); ++ //} ++ ++ //value *= pct_mod; ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ feartimer = 0; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (feartimer > diff) feartimer -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(CURSE_OF_THE_ELEMENTS_1); ++ InitSpellMap(SHADOW_BOLT_1); ++ InitSpellMap(IMMOLATE_1); ++ lvl >= 40 ? InitSpellMap(CONFLAGRATE_1) : RemoveSpell(CONFLAGRATE_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(CHAOS_BOLT_1) : RemoveSpell(CHAOS_BOLT_1); ++ InitSpellMap(RAIN_OF_FIRE_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(HAUNT_1) : RemoveSpell(HAUNT_1); ++ InitSpellMap(CORRUPTION_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(UNSTABLE_AFFLICTION_1) : RemoveSpell(UNSTABLE_AFFLICTION_1); ++ InitSpellMap(FEAR_1); ++ } ++ ++ //TODO ++ void ApplyClassPassives() { } ++ ++ private: ++ //Timers ++ uint32 feartimer; ++ ++ enum WarlockBaseSpells ++ { ++ CURSE_OF_THE_ELEMENTS_1 = 1490, ++ SHADOW_BOLT_1 = 686, ++ IMMOLATE_1 = 348, ++ CONFLAGRATE_1 = 17962, ++ CHAOS_BOLT_1 = 50796, ++ RAIN_OF_FIRE_1 = 5740, ++ HAUNT_1 = 59164, ++ CORRUPTION_1 = 172, ++ UNSTABLE_AFFLICTION_1 = 30404, ++ FEAR_1 = 6215 ++ }; ++ enum WarlockPassives ++ { ++ }; ++ }; ++}; ++ ++class voidwalker_bot : public CreatureScript ++{ ++public: ++ voidwalker_bot() : CreatureScript("voidwalker_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new voidwalker_botAI(creature); ++ } ++ ++ struct voidwalker_botAI : public bot_pet_ai ++ { ++ voidwalker_botAI(Creature* creature) : bot_pet_ai(creature) ++ { ++ _botclass = BOT_CLASS_MAGE; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_NONE) != SPELL_CAST_OK) ++ return false; ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ void EnterCombat(Unit*) { } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit*) { } ++ void EnterEvadeMode() { } ++ void MoveInLineOfSight(Unit*) { } ++ void JustDied(Unit*) { m_creatureOwner->SetBotsPetDied(); } ++ void DoNonCombatActions() { } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (IAmDead()) return; ++ CheckAttackState(); ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ if (CCed(me)) return; ++ ++ //TODO: add checks to help owner ++ ++ if (!me->IsInCombat()) ++ DoNonCombatActions(); ++ ++ if (!CheckAttackTarget(PET_TYPE_VOIDWALKER)) ++ return; ++ ++ DoNormalAttack(diff); ++ } ++ ++ void DoNormalAttack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ if (MoveBehind(*opponent)) ++ wait = 5; ++ ++ //float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ ++ //TORMENT ++ if (IsSpellReady(TORMENT_1, diff, false) && meleedist < 5 && !IsTank(opponent->GetVictim())) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(TORMENT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ OnSpellHit(caster, spell); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (m_creatureOwner->IsAIEnabled) ++ if (bot_minion_ai* ai = m_creatureOwner->GetBotMinionAI()) ++ ai->OnOwnerDamagedBy(u); ++ } ++ ++ //debug ++ //void ListSpells(ChatHandler* ch) const ++ //{ ++ // ch->PSendSysMessage("Spells list:"); ++ // ch->PSendSysMessage("Torment: %u", TORMENT); ++ // ch->PSendSysMessage("End of spells list."); ++ //} ++ ++ void Reset() ++ { ++ if (master && m_creatureOwner) ++ { ++ DefaultInit(); ++ SetBaseArmor(162 * master->getLevel()); ++ } ++ } ++ ++ void ReduceCD(uint32 /*diff*/) ++ { ++ } ++ ++ void InitSpells() ++ { ++ InitSpellMap(TORMENT_1); ++ } ++ ++ void ApplyClassPassives() { } ++ ++ private: ++ //Timers ++ ++ enum VoidwalkerBaseSpells ++ { ++ TORMENT_1 = 3716 ++ }; ++ enum VoidwalkerPassives ++ { ++ }; ++ }; ++}; ++ ++void AddSC_warlock_bot() ++{ ++ new warlock_bot(); ++ new voidwalker_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/bot_warrior_ai.cpp b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp +new file mode 100644 +index 0000000..3cc8060 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp +@@ -0,0 +1,1915 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Group.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "Spell.h" ++#include "SpellAuras.h" ++#include "SpellAuraEffects.h" ++//#include "WorldSession.h" ++/* ++Warrior NpcBot (reworked by Graff onlysuffering@gmail.com) ++Complete - 92-97% ++*/ ++class warrior_bot : public CreatureScript ++{ ++public: ++ warrior_bot() : CreatureScript("warrior_bot") { } ++ ++ CreatureAI* GetAI(Creature* creature) const ++ { ++ return new warrior_botAI(creature); ++ } ++ ++ bool OnGossipHello(Player* player, Creature* creature) ++ { ++ return bot_minion_ai::OnGossipHello(player, creature, 0); ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelect(player, creature, sender, action); ++ return true; ++ } ++ ++ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) ++ { ++ if (bot_minion_ai* ai = creature->GetBotMinionAI()) ++ return ai->OnGossipSelectCode(player, creature, sender, action, code); ++ return true; ++ } ++ ++ struct warrior_botAI : public bot_minion_ai ++ { ++ warrior_botAI(Creature* creature) : bot_minion_ai(creature) ++ { ++ _botclass = BOT_CLASS_WARRIOR; ++ } ++ ++ bool doCast(Unit* victim, uint32 spellId, bool triggered = false) ++ { ++ if (CheckBotCast(victim, spellId, BOT_CLASS_WARRIOR) != SPELL_CAST_OK) ++ return false; ++ ++ if (spellId == BATTLESTANCE_1 || spellId == DEFENSIVESTANCE_1 || spellId == BERSERKERSTANCE_1) ++ temptimer = GC_Timer; ++ ++ return bot_ai::doCast(victim, spellId, triggered); ++ } ++ ++ uint8 GetBotStance() const ++ { ++ if (battleStance) ++ return WARRIOR_BATTLE_STANCE; ++ else if (defensiveStance) ++ return WARRIOR_DEFENSIVE_STANCE; ++ else if (berserkerStance) ++ return WARRIOR_BERSERKER_STANCE; ++ ++ return BOT_STANCE_NONE; ++ } ++ ++ void UpdateAI(uint32 diff) ++ { ++ ReduceCD(diff); ++ if (!GlobalUpdate(diff)) ++ return; ++ CheckAttackState(); ++ getrage(); ++ if (ragetimer2 <= diff) ++ { ++ if (me->IsInCombat() && me->getLevel() >= 20) ++ { ++ if (me->GetPower(POWER_RAGE) < 990) ++ me->SetPower(POWER_RAGE, me->GetPower(POWER_RAGE) + uint32(10.f * rageIncomeMult)); //1 rage per 2 sec ++ else ++ me->SetPower(POWER_RAGE, 1000); //max ++ } ++ ragetimer2 = 2000; ++ } ++ if (ragetimer <= diff) ++ { ++ if (!me->IsInCombat() && !HasAuraName(me, BLOODRAGE_1)) ++ { ++ if (me->GetPower(POWER_RAGE) > uint32(10.f * rageLossMult)) ++ me->SetPower(POWER_RAGE, me->GetPower(POWER_RAGE) - uint32(10.f * rageLossMult)); //-1 rage per 1.5 sec ++ else ++ me->SetPower(POWER_RAGE, 0); //min ++ } ++ ragetimer = 1500; ++ } ++ CheckAuras(); ++ if (wait == 0) ++ wait = GetWait(); ++ else ++ return; ++ BreakCC(diff); ++ if (CCed(me)) return; ++ ++ if (Potion_cd <= diff && GetHealthPCT(me) < 67) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, HEALINGPOTION)) ++ { ++ Potion_cd = POTION_CD; ++ GC_Timer = temptimer; ++ } ++ } ++ CheckShouts(diff); ++ CheckVigilance(diff); ++ CheckIntervene(diff); ++ CheckSpellReflect(diff); ++ if (!me->IsInCombat()) ++ DoNonCombatActions(diff); ++ ++ if (!CheckAttackTarget(BOT_CLASS_WARRIOR)) ++ { ++ if (!IsTank() && !me->IsInCombat() && battleStance != true && master->getAttackers().empty() && ++ stancetimer <= diff && Rand() < 25) ++ stanceChange(diff, 1); ++ return; ++ } ++ ++ if (IsSpellReady(BLOODRAGE_1, diff, false) && me->IsInCombat() && rage < 600 && ++ Rand() < 20 && !me->HasAura(ENRAGED_REGENERATION_1)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLOODRAGE_1))) ++ { ++ GC_Timer = temptimer; ++ getrage(); ++ } ++ } ++ ++ Attack(diff); ++ } ++ ++ void StartAttack(Unit* u, bool force = false) ++ { ++ if (GetBotCommandState() == COMMAND_ATTACK && !force) return; ++ Aggro(u); ++ SetBotCommandState(COMMAND_ATTACK); ++ OnStartAttack(u); ++ GetInPosition(force); ++ } ++ ++ void EnterCombat(Unit* u) { bot_minion_ai::EnterCombat(u); } ++ void Aggro(Unit*) { } ++ void AttackStart(Unit*) { } ++ void KilledUnit(Unit* u) ++ { ++ //Victorious State spell ++ //only on targets which give xp or honor ++ if (me->getLevel() >= 5 && u->getLevel() + 9 >= me->getLevel()) ++ me->CastSpell(me, VICTORIOUS_SPELL, true); ++ } ++ void EnterEvadeMode() { bot_minion_ai::EnterEvadeMode(); } ++ void MoveInLineOfSight(Unit* u) { bot_minion_ai::MoveInLineOfSight(u); } ++ void JustDied(Unit* u) { bot_minion_ai::JustDied(u); } ++ void DoNonCombatActions(uint32 /*diff*/) { } ++ ++ void modrage(int32 mod, bool set = false) ++ { ++ if (set && mod < 0) ++ return; ++ if (mod < 0 && rage < abs(mod)) ++ { ++ //debug set rage to 0 ++ mod = 0; ++ set = true; ++ return; ++ } ++ ++ if (set) ++ rage = mod ? mod*10 : 0; ++ else ++ rage += mod*10; ++ ++ me->SetPower(POWER_RAGE, rage); ++ } ++ ++ int32 getrage() ++ { ++ rage = me->GetPower(POWER_RAGE); ++ if (me->FindCurrentSpellBySpellId(GetSpell(CLEAVE_1))) ++ rage = std::max(rage - 200, 0); ++ else if (me->FindCurrentSpellBySpellId(GetSpell(HEROIC_STRIKE_1))) ++ rage = std::max(rage - 150, 0); ++ ++ return rage; ++ } ++ ++ int32 rcost(uint32 spellId) const ++ { ++ if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) ++ return spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); ++ return 0; ++ } ++ ++ void BreakCC(uint32 diff) ++ { ++ if (me->HasAuraWithMechanic((1<HasAura(ENRAGED_REGENERATION_1) && ++ doCast(me, GetSpell(BERSERKERRAGE_1))) ++ return; ++ } ++ bot_minion_ai::BreakCC(diff); ++ } ++ ++ void Attack(uint32 diff) ++ { ++ opponent = me->GetVictim(); ++ if (opponent) ++ { ++ if (!IsCasting()) ++ StartAttack(opponent, true); ++ } ++ else ++ return; ++ //Keep defensive stance if tank ++ if (IsTank() && defensiveStance != true && stancetimer <= diff) ++ stanceChange(diff, 2); ++ //SelfHeal ++ if (IsSpellReady(ENRAGED_REGENERATION_1, diff) && rage > rcost(ENRAGED_REGENERATION_1) && ++ GetHealthPCT(me) < 40 && Rand() < 40 && me->HasAuraWithMechanic(uint32(1<getAttackers(); ++ AttackerSet b_attackers = me->getAttackers(); ++ float dist = me->GetExactDist(opponent); ++ float meleedist = me->GetDistance(opponent); ++ //charge + warbringer ++ if (IsSpellReady(CHARGE_1, diff, false) && dist > 11 && dist < 25 && me->HasInArc(M_PI, opponent) && ++ (me->getLevel() >= 50 || ++ (!me->IsInCombat() && (battleStance || stanceChange(diff, 1))))) ++ { ++ temptimer = GC_Timer; ++ if (me->getLevel() >= 29) ++ me->RemoveMovementImpairingAuras(); ++ if (doCast(opponent, GetSpell(CHARGE_1), me->IsInCombat())) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //intercept ++ if (IsSpellReady(INTERCEPT_1, diff, false) && !IsTank() && ++ rage > rcost(INTERCEPT_1) && dist > 11 && dist < 25 && me->HasInArc(M_PI, opponent) && ++ !CCed(opponent) && (berserkerStance || stanceChange(diff, 3))) ++ { ++ if (doCast(opponent, GetSpell(INTERCEPT_1))) ++ return; ++ } ++ //SelfHeal 2 - LAST STAND ++ if (IsSpellReady(LAST_STAND_1, diff, false) && IsTank() && Rand() < 67 && ++ GetHealthPCT(me) < (30 + 20 * (b_attackers.size() > 1) + 10 * me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(LAST_STAND_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //FEAR ++ if (IsSpellReady(INTIMIDATING_SHOUT_1, diff) && Rand() < 70 && rage > rcost(INTIMIDATING_SHOUT_1)) ++ { ++ if (opponent->IsNonMeleeSpellCast(false, false, true) && dist <= 8 && ++ !(opponent->ToCreature() && opponent->ToCreature()->GetCreatureType() == CREATURE_TYPE_UNDEAD)) ++ { ++ if (doCast(opponent, GetSpell(INTIMIDATING_SHOUT_1))) ++ return; ++ } ++ Unit* fearTarget = NULL; ++ bool triggered = false; ++ uint8 tCount = 0; ++ //fear master's attackers ++ if (!m_attackers.empty() && ++ ((master->getClass() != BOT_CLASS_DEATH_KNIGHT && ++ master->getClass() != BOT_CLASS_WARRIOR && ++ master->getClass() != BOT_CLASS_PALADIN) || ++ GetHealthPCT(master) < 70)) ++ { ++ for(AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; ++ if (me->GetExactDist((*iter)) <= 8 && (*iter)->isTargetableForAttack()) ++ { ++ ++tCount; ++ fearTarget = (*iter); ++ if (tCount > 1) break; ++ } ++ } ++ if (tCount > 0 && !fearTarget) ++ { ++ fearTarget = opponent; ++ triggered = true; ++ } ++ if (tCount > 1 && doCast(fearTarget, GetSpell(INTIMIDATING_SHOUT_1), triggered)) ++ return; ++ } ++ //Defend myself ++ if (b_attackers.size() > 1) ++ { ++ tCount = 0; ++ fearTarget = NULL; ++ triggered = false; ++ for(AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) ++ { ++ if (!(*iter)) continue; ++ if ((*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; ++ if (me->GetExactDist((*iter)) <= 8 && (*iter)->isTargetableForAttack()) ++ { ++ ++tCount; ++ fearTarget = (*iter); ++ if (tCount > 1) break; ++ } ++ } ++ if (tCount > 0 && !fearTarget) ++ { ++ fearTarget = opponent; ++ triggered = true; ++ } ++ if (tCount > 1 && doCast(fearTarget, GetSpell(INTIMIDATING_SHOUT_1), triggered)) ++ return; ++ } ++ }//end FEAR ++ //TAUNT //No GCD ++ Unit* u = opponent->GetVictim(); ++ if (IsSpellReady(TAUNT_1, diff, false) && u && u != me && !IsTank(u) && dist <= 30 && ++ !CCed(opponent) && (!IsTankingClass(u->getClass()) || IsTank()) && IsInBotParty(u) && ++ (defensiveStance || (stancetimer <= diff && stanceChange(diff, 2)))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(opponent, GetSpell(TAUNT_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //CHALLENGING SHOUT ++ if (IsSpellReady(CHALLENGING_SHOUT_1, diff)) ++ { ++ u = opponent->GetVictim(); ++ if (u && u != me && !IsTank(u) && !CCed(opponent) && dist <= 10 && rage > rcost(CHALLENGING_SHOUT_1) && ++ Rand() < 30 && (!IsTankingClass(u->getClass()) || IsTank()) && IsInBotParty(u)) ++ { ++ if (doCast(me, GetSpell(CHALLENGING_SHOUT_1))) ++ return; ++ } ++ if (IsTank() && rage > rcost(CHALLENGING_SHOUT_1) && Rand() < 20) ++ { ++ std::list targets; ++ GetNearbyTargetsList(targets); ++ if (int8(targets.size()) - int8(b_attackers.size()) >= 1) ++ if (doCast(me, GetSpell(CHALLENGING_SHOUT_1))) ++ return; ++ } ++ } ++ u = opponent->GetVictim(); ++ //MOCKING BLOW ++ if (IsSpellReady(MOCKING_BLOW_1, diff) && HasRole(BOT_ROLE_DPS) && u && u != me && !IsTank(u) && ++ meleedist <= 5 && rage > rcost(MOCKING_BLOW_1) && ++ !CCed(opponent) && Rand() < 70 && (!IsTankingClass(u->getClass()) || IsTank()) && IsInBotParty(u) && ++ (battleStance || defensiveStance || (stancetimer <= diff && stanceChange(diff, 4)))) ++ { ++ if (doCast(opponent, GetSpell(MOCKING_BLOW_1))) ++ return; ++ } ++ //SHIELD SLAM ++ if (IsSpellReady(SHIELD_SLAM_1, diff) && HasRole(BOT_ROLE_DPS) && ++ (battleStance || defensiveStance || stancetimer <= diff) && ++ meleedist <= 5 && rage > rcost(SHIELD_SLAM_1) && CanBlock() && ++ Rand() < (55 + 200*me->HasAura(SWORD_AND_BOARD_BUFF))) ++ { ++ //check Shield Block ++ if (IsSpellReady(SHIELD_BLOCK_1, diff, false) && (defensiveStance || stanceChange(diff, 2))) ++ { ++ temptimer = GC_Timer; ++ ++ if (!doCast(me, GetSpell(SHIELD_BLOCK_1))) ++ return; ++ ++ GC_Timer = temptimer; ++ } ++ if (battleStance || defensiveStance || stanceChange(diff, 4)) ++ { ++ if (doCast(opponent, GetSpell(SHIELD_SLAM_1))) ++ return; ++ } ++ } ++ //SHIELD BLOCK ++ if (IsSpellReady(SHIELD_BLOCK_1, diff, false) && CanBlock() && IsTank() && ++ ((u == me && meleedist <= 8) || (!b_attackers.empty() && me->GetDistance2d(*(b_attackers.begin())) <= 8)) && ++ GetHealthPCT(me) < (65 + 8 * uint8(b_attackers.size())) && Rand() < 50 && ++ (defensiveStance || stancetimer <= diff)) ++ { ++ temptimer = GC_Timer; ++ if ((defensiveStance || stanceChange(diff, 2)) && ++ doCast(me, GetSpell(SHIELD_BLOCK_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //HEROIC THROW ++ if (IsSpellReady(HEROIC_THROW_1, diff) && HasRole(BOT_ROLE_DPS) && dist <= 30 && ++ Rand() < (20 + 70 * opponent->IsNonMeleeSpellCast(false))) ++ { ++ if (doCast(opponent, GetSpell(HEROIC_THROW_1))) ++ return; ++ } ++ //SHOCKWAVE - frontal cone ++ if (IsSpellReady(SHOCKWAVE_1, diff) && HasRole(BOT_ROLE_DPS) && CanBlock() && dist <= 10 && !CCed(opponent) && ++ rage > rcost(SHOCKWAVE_1) && Rand() < (30 + 50 * opponent->IsNonMeleeSpellCast(true)) && ++ me->HasInArc(M_PI / 2.f, opponent) && opponent->IsWithinLOSInMap(me)) ++ { ++ if (doCast(me, GetSpell(SHOCKWAVE_1))) ++ return; ++ } ++ //OVERPOWER ++ if (IsSpellReady(OVERPOWER_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ meleedist <= 5 && rage > rcost(OVERPOWER_1) && ++ (battleStance || stancetimer <= diff) &&/* Rand() < 80 &&*/ ++ (me->HasReactive(REACTIVE_OVERPOWER) || ++ (IsSpellReady(TASTE_FOR_BLOOD_BUFF, diff, false) && me->HasAura(TASTE_FOR_BLOOD_BUFF)))) ++ { ++ if (battleStance || stanceChange(diff, 1)) ++ { ++ //custom ++ me->CastSpell(opponent, GetSpell(OVERPOWER_1)); ++ return; ++ } ++ } ++ //THUNDER CLAP ++ if (IsSpellReady(THUNDER_CLAP_1, diff) && HasRole(BOT_ROLE_DPS) && ++ dist <= 8 && rage > rcost(THUNDER_CLAP_1) && Rand() < 20 && ++ (battleStance || defensiveStance || stancetimer <= diff) && ++ (IsTank() || !HasAuraName(opponent, THUNDER_CLAP_1))) ++ { ++ if (battleStance || defensiveStance || stanceChange(diff, 4)) ++ { ++ if (doCast(me, GetSpell(THUNDER_CLAP_1))) ++ return; ++ } ++ } ++ //DEVASTATE - only with shield ++ if (IsSpellReady(DEVASTATE_1, diff) && HasRole(BOT_ROLE_DPS)/* && IsTank()*/ && CanBlock() && ++ meleedist <= 5 && rage > rcost(DEVASTATE_1) && Rand() < 70) ++ { ++ if (doCast(opponent, GetSpell(DEVASTATE_1))) ++ return; ++ } ++ //REVENGE ++ if (IsSpellReady(REVENGE_1, diff) && me->HasAuraState(AURA_STATE_DEFENSE) && HasRole(BOT_ROLE_DPS) && IsTank() && ++ meleedist <= 5 && rage > rcost(REVENGE_1) && Rand() < 30 && ++ (defensiveStance || stancetimer <= diff)) ++ { ++ if (defensiveStance || stanceChange(diff, 2)) ++ { ++ if (doCast(opponent, GetSpell(REVENGE_1))) ++ { ++ //Improved Revenge (part 2): find second target ++ if (me->getLevel() >= 25) ++ if (Unit* u = FindSplashTarget(5, opponent)) ++ me->CastSpell(u, GetSpell(REVENGE_1), true); ++ return; ++ } ++ } ++ } ++ ++ if (MoveBehind(*opponent)) ++ wait = 15; ++ ++ //CONCUSSION_BLOW ++ if (IsSpellReady(CONCUSSION_BLOW_1, diff) && HasRole(BOT_ROLE_DPS) && IsTank() && ++ meleedist <= 5 && rage > rcost(CONCUSSION_BLOW_1) && !CCed(opponent) && ++ Rand() < (30 + 60 * opponent->IsNonMeleeSpellCast(false))) ++ { ++ if (doCast(opponent, GetSpell(CONCUSSION_BLOW_1))) ++ return; ++ } ++ //HAMSTRING ++ if (IsSpellReady(HAMSTRING_1, diff) && (!GetSpell(PIERCING_HOWL_1) || opponent->GetTypeId() == TYPEID_PLAYER) && ++ opponent->isMoving() && meleedist <= 5 && rage > rcost(HAMSTRING_1) && ++ Rand() < 50 && (battleStance || berserkerStance || stancetimer <= diff) && ++ !opponent->HasAuraWithMechanic((1<isMoving() && meleedist <= 9 && rage > rcost(PIERCING_HOWL_1) && ++ Rand() < 70 && !opponent->HasAuraWithMechanic((1< rcost(DISARM_1) && Rand() < 50 && ++ !opponent->HasAuraType(SPELL_AURA_MOD_DISARM) && ++ (defensiveStance || stancetimer <= diff)) ++ { ++ //check weapons ++ bool hasWeapon = true; ++ if (opponent->GetTypeId() == TYPEID_UNIT && !opponent->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID)) ++ hasWeapon = false; ++ else if (Player* pla = opponent->ToPlayer()) ++ if (!pla->GetWeaponForAttack(BASE_ATTACK) || !pla->IsUseEquipedWeapon(true)) ++ hasWeapon = false; ++ ++ if (hasWeapon && (defensiveStance || stanceChange(diff, 2)) && ++ doCast(opponent, GetSpell(DISARM_1))) ++ return; ++ } ++ //Victory Rush ++ if (IsSpellReady(VICTORY_RUSH_1, diff) && VICTORIOUS && HasRole(BOT_ROLE_DPS) && !IsTank() && meleedist <= 5/* && ++ (b_attackers.size() <= 1 || (GetHealthPCT(me) < std::max(100 - b_attackers.size() * 10, 75)))*/ && ++ (battleStance || berserkerStance || stancetimer <= diff)) ++ { ++ if (battleStance || berserkerStance || stanceChange(diff, 5)) ++ if (doCast(opponent, GetSpell(VICTORY_RUSH_1))) ++ return; ++ } ++ //UBERS ++ //Shield Wall ++ if (IsSpellReady(SHIELD_WALL_1, diff, false) && CanBlock() && ++ GetHealthPCT(me) < (30 + 4 * b_attackers.size() + 10 * (opponent->GetTypeId() == TYPEID_UNIT && opponent->ToCreature()->isWorldBoss())) && ++ Rand() < 70 && ++ (defensiveStance || stanceChange(diff, 2))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(SHIELD_WALL_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //Retaliation ++ if (IsSpellReady(RETALIATION_1, diff) && HasRole(BOT_ROLE_DPS) && b_attackers.size() > 4 && Rand() < 30 && ++ (battleStance || stanceChange(diff, 1))) ++ { ++ if (doCast(me, GetSpell(RETALIATION_1))) ++ return; ++ } ++ //Recklessness ++ if (IsSpellReady(RECKLESSNESS_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ (m_attackers.size() > 3 || opponent->GetHealth() > me->GetHealth() * 10) && Rand() < 20 && ++ (berserkerStance || stanceChange(diff, 3))) ++ { ++ if (doCast(me, GetSpell(RECKLESSNESS_1))) ++ return; ++ } ++ //DEATHWISH ++ if (IsSpellReady(DEATHWISH_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && dist <= 20 && rage > rcost(DEATHWISH_1) && ++ opponent->GetHealth() > me->GetHealth()/2 && Rand() < 20 && ++ !me->HasAura(ENRAGED_REGENERATION_1)) ++ { ++ if (doCast(me, GetSpell(DEATHWISH_1))) ++ return; ++ } ++ //EXECUTE ++ if (IsSpellReady(EXECUTE_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ meleedist <= 5 && rage > rcost(EXECUTE_1) && ++ opponent->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT) && Rand() < 70 && ++ (battleStance || berserkerStance || (stancetimer <= diff && stanceChange(diff, 5)))) ++ { ++ if (doCast(opponent, GetSpell(EXECUTE_1))) ++ { ++ //sudden death ++ if (me->getLevel() >= 50 && rage <= 400) ++ modrage(10, true); ++ else if (rage > 300) ++ modrage(-30); ++ else ++ modrage(0, true); ++ return; ++ } ++ } ++ //SUNDER ARMOR ++ if (IsSpellReady(SUNDER_1, diff) && !GetSpell(DEVASTATE_1) && IsTank() && ++ meleedist <= 5 && rage > rcost(SUNDER_1) && ++ opponent->GetHealth() > me->GetMaxHealth() && Rand() < 45) ++ { ++ Aura* sunder = opponent->GetAura(GetSpell(SUNDER_1), me->GetGUID()); ++ if ((!sunder || sunder->GetStackAmount() < 5 || sunder->GetDuration() < 15000) && ++ doCast(opponent, GetSpell(SUNDER_1))) ++ return; ++ } ++ //SS //no GCD //no rage (glyph) ++ if (IsSpellReady(SWEEPING_STRIKES_1, diff, false) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ dist <= 20 && rage > rcost(SWEEPING_STRIKES_1) && ++ (battleStance || berserkerStance || stancetimer <= diff) && Rand() < 25 && ++ (b_attackers.size() > 1 || FindSplashTarget(7, opponent))) ++ { ++ temptimer = GC_Timer; ++ if ((battleStance || berserkerStance || stanceChange(diff, 5)) && ++ doCast(me, GetSpell(SWEEPING_STRIKES_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //WHIRLWIND ++ if (IsSpellReady(WHIRLWIND_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ (berserkerStance || stancetimer <= diff) && ++ dist <= 10 && rage > rcost(WHIRLWIND_1) && Rand() < 50 && ++ ((rage > 800 && dist <= 7) || FindSplashTarget(7, opponent))) ++ { ++ if ((berserkerStance || stanceChange(diff, 3)) && ++ doCast(me, GetSpell(WHIRLWIND_1))) ++ return; ++ } ++ //BLADESTORM ++ if (IsSpellReady(BLADESTORM_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ dist <= 10 && rage > rcost(BLADESTORM_1) && ++ (b_attackers.size() > 1 || opponent->GetHealth() > me->GetMaxHealth()) && ++ (Rand() < 20 || me->HasAura(RECKLESSNESS_1))) ++ { ++ if (doCast(me, GetSpell(BLADESTORM_1))) ++ return; ++ } ++ //Mortal Strike ++ if (IsSpellReady(MORTALSTRIKE_1, diff) && HasRole(BOT_ROLE_DPS) && ++ meleedist <= 5 && rage > rcost(MORTALSTRIKE_1) && Rand() < 50) ++ { ++ if (doCast(opponent, GetSpell(MORTALSTRIKE_1))) ++ return; ++ } ++ //Slam ++ if (IsSpellReady(SLAM_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && !opponent->isMoving() && ++ meleedist <= 5 && rage > rcost(SLAM_1) && Rand() < (20 + 80 * me->HasAura(BLOODSURGE_BUFF))) ++ { ++ if (doCast(opponent, GetSpell(SLAM_1))) ++ return; ++ } ++ //SHIELD BASH - shared cd with pummel ++ if (IsSpellReady(SHIELD_BASH_1, diff, false) && CanBlock() && ++ meleedist <= 5 && rage > rcost(SHIELD_BASH_1) && Rand() < 80 && ++ opponent->IsNonMeleeSpellCast(false) && ++ (battleStance || defensiveStance || stancetimer <= diff)) ++ { ++ temptimer = GC_Timer; ++ if ((battleStance || defensiveStance || stanceChange(diff, 4)) && ++ doCast(opponent, GetSpell(SHIELD_BASH_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //PUMMEL - shared cd with shield bash ++ if (IsSpellReady(PUMMEL_1, diff, false) && !IsTank() && ++ meleedist <= 5 && rage > rcost(PUMMEL_1) && Rand() < 80 && ++ opponent->IsNonMeleeSpellCast(false) && ++ (berserkerStance || stancetimer <= diff)) ++ { ++ temptimer = GC_Timer; ++ if ((berserkerStance || stanceChange(diff, 3)) && ++ doCast(opponent, GetSpell(PUMMEL_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //REND ++ if (IsSpellReady(REND_1, diff) && HasRole(BOT_ROLE_DPS) && !IsTank() && ++ opponent->GetHealth() > me->GetMaxHealth()/2 && meleedist <= 5 && rage > rcost(REND_1) && ++ Rand() < 50 && !opponent->HasAura(GetSpell(REND_1), me->GetGUID()) && ++ (battleStance || defensiveStance || (stancetimer <= diff && stanceChange(diff, 4)))) ++ { ++ if (doCast(opponent, GetSpell(REND_1))) ++ return; ++ } ++ ++ //skip if already have cleave of heroic strike casted ++ if (me->GetCurrentSpell(CURRENT_MELEE_SPELL)) ++ return; ++ ++ //CLEAVE //no GCD ++ if (IsSpellReady(CLEAVE_1, diff, false) && HasRole(BOT_ROLE_DPS) && ++ meleedist <= 5 && (!IsTank() || rage > 500) && rage > rcost(CLEAVE_1) && Rand() < 25) ++ { ++ temptimer = GC_Timer; ++ u = FindSplashTarget(5); ++ if (u && doCast(opponent, GetSpell(CLEAVE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ //HEROIC STRIKE ++ if (IsSpellReady(HEROIC_STRIKE_1, diff, false) && HasRole(BOT_ROLE_DPS) && IsTank() && ++ meleedist <= 5 && rage > rcost(HEROIC_STRIKE_1) && Rand() < (15 + rage / 10)) ++ { ++ temptimer = GC_Timer; ++ if (u && doCast(opponent, GetSpell(HEROIC_STRIKE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ }//end Attack ++ ++ void CheckShouts(uint32 diff) ++ { ++ if (shoutCheckTimer > diff || GC_Timer > diff || me->IsMounted() || IsCasting() || ++ /*rage < rcost(BATTLESHOUT_1) || */Rand() > 35) ++ return; ++ ++ shoutCheckTimer = 3000; ++ ++ if (IAmFree()) ++ { ++ if (!HasAuraName(me, BATTLESHOUT_1, me->GetGUID())) ++ { ++ if (rage < rcost(BATTLESHOUT_1) && IsSpellReady(BLOODRAGE_1, diff, false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLOODRAGE_1))) ++ { ++ GC_Timer = temptimer; ++ if (doCast(me, GetSpell(BATTLESHOUT_1))) ++ return; ++ } ++ } ++ } ++ ++ return; ++ } ++ ++ bool hasBS = HasAuraName(me, BATTLESHOUT_1/*, me->GetGUID()*/); ++ bool hasCS = HasAuraName(me, COMMANDING_SHOUT_1/*, me->GetGUID()*/); ++ if (hasCS || hasBS) ++ return; ++ ++ if (me->GetDistance2d(master) < 30/* && master->IsWithinLOSInMap(me)*/) ++ { ++ bool battleshout = !hasBS && (!IsTank(me) || !GetSpell(COMMANDING_SHOUT_1)); ++ bool commandingshout = !hasCS && GetSpell(COMMANDING_SHOUT_1); ++ ++ if (battleshout || commandingshout) ++ { ++ if (rage < rcost(BATTLESHOUT_1) && IsSpellReady(BLOODRAGE_1, diff, false)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(me, GetSpell(BLOODRAGE_1))) ++ GC_Timer = temptimer; ++ } ++ ++ if ((battleshout && doCast(me, GetSpell(BATTLESHOUT_1))) || ++ (commandingshout && doCast(me, GetSpell(COMMANDING_SHOUT_1)))) ++ { ++ shoutCheckTimer = 10000; ++ return; ++ } ++ } ++ } ++ } ++ ++ void CheckVigilance(uint32 diff) ++ { ++ if (!IsSpellReady(VIGILANCE_1, diff) || me->IsInCombat() || me->IsMounted() || IsCasting() || Rand() > 50) ++ return; ++ ++ uint32 VIGILANCE = GetSpell(VIGILANCE_1); ++ ++ Unit* u = ObjectAccessor::FindConnectedPlayer(vigilanceTargetGuid); ++ ++ if (u) ++ { ++ if (!IsTank()) ++ { ++ u->RemoveAura(VIGILANCE, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE); ++ vigilanceTargetGuid.Clear(); ++ } ++ if (IAmFree()) ++ return; ++ } ++ else if (IsTank()) ++ { ++ Group* pGroup = master->GetGroup(); ++ if (!pGroup) ++ { ++ if (me->GetExactDist(master) < 30 && !master->HasAura(VIGILANCE)) ++ u = master; ++ } ++ else ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pPlayer = itr->GetSource(); ++ if (!pPlayer || !pPlayer->IsInWorld() || pPlayer->isDead()) continue; ++ if (me->GetMapId() != pPlayer->GetMapId()) continue; ++ if (!IsTankingClass(pPlayer->getClass()) && me->GetExactDist(pPlayer) < 30 && ++ !pPlayer->HasAura(VIGILANCE)) ++ { ++ u = pPlayer; ++ break; ++ } ++ } ++ if (!u) ++ { ++ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pPlayer = itr->GetSource(); ++ if (!pPlayer || !pPlayer->IsInWorld() || !pPlayer->HaveBot()) continue; ++ if (me->GetMapId() != pPlayer->GetMapId()) continue; ++ BotMap const* map = pPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* cre = it->second; ++ if (!cre || !cre->IsInWorld() || cre == me || cre->isDead()) continue; ++ if (!IsTankingClass(cre->GetBotClass()) && me->GetExactDist(cre) < 30 && ++ !cre->HasAura(VIGILANCE)) ++ { ++ u = cre; ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (u && doCast(u, VIGILANCE)) ++ { ++ vigilanceTargetGuid = u->GetGUID(); ++ return; ++ } ++ } ++ ++ SetSpellCooldown(VIGILANCE_1, 10000); //fail ++ } ++ ++ void CheckIntervene(uint32 diff) ++ { ++ if (IsSpellReady(INTERVENE_1, diff, false) && GetBotCommandState() != COMMAND_STAY && ++ !me->IsMounted() && rage > rcost(INTERVENE_1) && ++ !IAmFree() && !IsCasting() && Rand() < (IsTank() ? 80 : 30) && ++ (defensiveStance || stancetimer <= diff)) ++ { ++ if (!master->IsInCombat() && master->getAttackers().empty() && master->isMoving()) ++ { ++ float mydist = me->GetExactDist(master); ++ if (mydist < 24 && mydist > 19 && (defensiveStance || stanceChange(diff, 2))) ++ { ++ temptimer = GC_Timer; ++ if (doCast(master, GetSpell(INTERVENE_1))) ++ { ++ GC_Timer = temptimer; ++ Follow(true); ++ return; ++ } ++ } ++ } ++ Group* gr = master->GetGroup(); ++ if (!gr) ++ { ++ if (GetHealthPCT(master) < 95 && !master->getAttackers().empty() && ++ me->getAttackers().size() <= master->getAttackers().size()) ++ { ++ float dist = me->GetExactDist(master); ++ if (dist > 25 || dist < 10) return; ++ if (!(defensiveStance || stanceChange(diff, 2))) return; ++ temptimer = GC_Timer; ++ if (doCast(master, GetSpell(INTERVENE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ else ++ { ++ bool Bots = false; ++ float dist; ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer) continue; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (tPlayer->FindMap() != me->GetMap()) continue; ++ if (tPlayer->HaveBot()) ++ Bots = true; ++ if (tPlayer->isDead() || GetHealthPCT(tPlayer) > 90 || IsTank(tPlayer)) continue; ++ if (tPlayer->getAttackers().size() < me->getAttackers().size()) continue; ++ dist = me->GetExactDist(tPlayer); ++ if (dist > 24 || dist < 10) continue; ++ if (defensiveStance || stanceChange(diff, 2)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(tPlayer, GetSpell(INTERVENE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ if (!Bots) return; ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* tPlayer = itr->GetSource(); ++ if (!tPlayer || !tPlayer->HaveBot()) continue; ++ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; ++ if (tPlayer->FindMap() != me->GetMap()) continue; ++ BotMap const* map = tPlayer->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) ++ { ++ Creature* bot = it->second; ++ if (!bot || !bot->IsInWorld() || bot == me || bot->isDead()) continue; ++ if (GetHealthPCT(bot) > 90 || IsTank(bot)) continue; ++ dist = me->GetExactDist(bot); ++ if (dist > 25 || dist < 10) continue; ++ if (bot->getAttackers().size() <= me->getAttackers().size()) continue; ++ if (defensiveStance || stanceChange(diff, 2)) ++ { ++ temptimer = GC_Timer; ++ if (doCast(bot, GetSpell(INTERVENE_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ SetSpellCooldown(INTERVENE_1, 2000); //fail ++ } ++ } ++ ++ void CheckSpellReflect(uint32 diff) ++ { ++ if (!IsSpellReady(SPELL_REFLECTION_1, diff, false) || me->IsMounted() || IsCasting() || ++ !CanBlock() || !(battleStance || defensiveStance || stancetimer <= diff) || ++ rage < rcost(SPELL_REFLECTION_1) || Rand() > 35) ++ return; ++ ++ //use simpliest finder - first match (covers most cases) ++ if (Unit* target = FindCastingTarget(70)) ++ { ++ temptimer = GC_Timer; ++ for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) ++ if (Spell* spell = target->GetCurrentSpell(CurrentSpellTypes(i))) ++ if (!spell->GetSpellInfo()->IsChanneled()) ++ if (spell->GetSpellInfo()->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && ++ !(spell->GetSpellInfo()->Attributes & SPELL_ATTR0_ABILITY) && ++ !(spell->GetSpellInfo()->AttributesEx & SPELL_ATTR1_CANT_BE_REFLECTED) && ++ !(spell->GetSpellInfo()->Attributes & SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY) && ++ !spell->GetSpellInfo()->IsPassive() && !spell->GetSpellInfo()->IsPositive()) ++ if (Unit* u = spell->m_targets.GetUnitTarget()) ++ if (u == me || (me->GetDistance(u) < 20 && !IAmFree() && IsInBotParty(u))) ++ if (doCast(me, GetSpell(SPELL_REFLECTION_1))) ++ { ++ GC_Timer = temptimer; ++ return; ++ } ++ } ++ ++ SetSpellCooldown(SPELL_REFLECTION_1, 1000); //fail ++ } ++ ++ bool stanceChange(uint32 diff, uint8 stance) ++ { ++ if (stancetimer > diff || !stance) ++ return false; ++ ++ if (stance == 5) ++ stance = (me->getLevel() >= 30 && !IsTank() && urand(1,100) > 70) ? 3 : 1; ++ else if (stance == 4) ++ stance = (me->getLevel() >= 10 && (IsTank() || urand(1,100) > 50)) ? 2 : 1; ++ ++ if (stance == 2 && me->getLevel() < 10) ++ return false; ++ if (stance == 3 && me->getLevel() < 30) ++ return false; ++ ++ switch (stance) ++ { ++ case 1: ++ if (doCast(me, GetSpell(BATTLESTANCE_1))) ++ { ++ if (me->HasAura(BATTLESTANCE_1)) ++ { ++ GC_Timer = temptimer; ++ return true; ++ } ++ } ++ break; ++ case 2: ++ if (doCast(me, GetSpell(DEFENSIVESTANCE_1))) ++ { ++ if (me->HasAura(DEFENSIVESTANCE_1)) ++ { ++ GC_Timer = temptimer; ++ return true; ++ } ++ } ++ break; ++ case 3: ++ if (doCast(me, GetSpell(BERSERKERSTANCE_1))) ++ { ++ if (me->HasAura(BERSERKERSTANCE_1)) ++ { ++ GC_Timer = temptimer; ++ return true; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ ++ GC_Timer = temptimer; ++ return false; ++ } ++ ++ void ApplyClassDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const ++ { ++ uint8 lvl = me->getLevel(); ++ float pctbonus = 0.0f; ++ ++ if (damageinfo.hitOutCome == MELEE_HIT_CRIT) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Poleaxe Specialization: 5% additional critical damage for all attacks ++ if (lvl >= 30) ++ if (Item const* weap = GetEquips(damageinfo.attackType)) ++ if (ItemTemplate const* proto = weap->GetTemplate()) ++ if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) ++ pctbonus += 0.025f; ++ } ++ ++ damage = damage * (1.0f + pctbonus); ++ } ++ ++ void ApplyClassDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++ { ++ uint32 spellId = spellInfo->Id; ++ uint8 lvl = me->getLevel(); ++ float fdamage = float(damage); ++ //1) apply additional crit chance. This additional chance roll will replace original (balance safe) ++ if (!crit) ++ { ++ float aftercrit = 0.f; ++ //Poleaxe Specialization: 5% additional critical chance for all attacks ++ if (lvl >= 30) ++ if (Item const* weap = GetEquips(attackType)) ++ if (ItemTemplate const* proto = weap->GetTemplate()) ++ if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) ++ aftercrit += 5.f; ++ //Incite: 15% additional critical chance for Cleave, Heroic Strike and Thunder Clap ++ if (lvl >= 15 && ++ (spellId == GetSpell(CLEAVE_1) || ++ spellId == GetSpell(HEROIC_STRIKE_1) || ++ spellId == GetSpell(THUNDER_CLAP_1))) ++ aftercrit += 15.f; ++ //Improved Overpower: 50% additional critical chance for Overpower ++ if (lvl >= 20 && spellId == GetSpell(OVERPOWER_1)) ++ aftercrit += 50.f; ++ //Critical Block: 15% additional critical chance for Shield Slam ++ if (lvl >= 50 && spellId == GetSpell(SHIELD_SLAM_1)) ++ aftercrit += 15.f; ++ //Sword and Board: 15% additional critical chance for Devastate ++ if (lvl >= 55 && spellId == GetSpell(DEVASTATE_1)) ++ aftercrit += 15.f; ++ //Warrior T8 Protection Bonus (id: 64933): 10% additional critical chance for Devastate (tanks only) ++ if (lvl >= 78 && IsTank() && spellId == GetSpell(DEVASTATE_1)) ++ aftercrit += 10.f; ++ ++ //second roll (may be illogical) ++ if (aftercrit > 0.f) ++ crit = roll_chance_f(aftercrit); ++ } ++ ++ //2) apply bonus damage mods ++ float pctbonus = 0.0f; ++ if (crit) ++ { ++ //!!!Melee spell damage is not yet critical, all reduced by half ++ //Impale: 20% crit damage bonus for all abilities ++ if (lvl >= 20) ++ pctbonus += 0.10f; ++ //Poleaxe Specialization: 5% additional critical damage for all attacks ++ if (lvl >= 30) ++ if (Item const* weap = GetEquips(attackType)) ++ if (ItemTemplate const* proto = weap->GetTemplate()) ++ if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || ++ proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) ++ pctbonus += 0.025f; ++ } ++ ++ //Improved Rend: 20% bonus damage for Rend ++ if (spellId == GetSpell(REND_1)) ++ pctbonus += 0.2f; ++ //Improved Whirlwind: 20% bonus damage for Whirlwind ++ if (lvl >= 40 && spellId == GetSpell(WHIRLWIND_1)) ++ pctbonus += 0.2f; ++ //Glyph of Mortal Strike: 10% bonus damage for Mortal Strike ++ if (lvl >= 40 && spellId == GetSpell(MORTALSTRIKE_1)) ++ pctbonus += 0.1f; ++ //Unrelenting Assault (part 2): 20% bonus damage for Overpower and Revenge ++ if (lvl >= 45 && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) ++ pctbonus += 0.2f; ++ //Improved Mortal Strike (part 1): 10% bonus damage for Mortal Strike ++ if (lvl >= 45 && spellId == GetSpell(MORTALSTRIKE_1)) ++ pctbonus += 0.1f; ++ //Undending Fury: 10% bonus damage for Whirlwind, Slam and Bloodthirst ++ if (lvl >= 55 && (spellId == GetSpell(WHIRLWIND_1) || spellId == GetSpell(SLAM_1) /*|| spellId == BLOODTHIRST*/)) ++ pctbonus += 0.1f; ++ //Improved Thunder Clap (part 2): 30% bonus damage for Thunder Clap ++ if (lvl >= 15 && spellId == GetSpell(THUNDER_CLAP_1)) ++ pctbonus += 0.3f; ++ ////Improved Revenge (part 1): 60% bonus damage for Revenge ++ //if (lvl >= 20 && spellId == GetSpell(REVENGE_1)) ++ // pctbonus += 0.6f; ++ //Gag Order (part 2): 10% bonus damage for Shield Slam ++ if (lvl >= 30 && spellId == GetSpell(SHIELD_SLAM_1)) ++ pctbonus += 0.1f; ++ //Improved Shield Slam (id: 38407): 10% bonus damage for Shield Slam ++ if (lvl >= 50 && spellId == GetSpell(SHIELD_SLAM_1)) ++ pctbonus += 0.1f; ++ //Shield Slam Damage Up (id: 60173): 10% bonus damage for Shield Slam ++ if (lvl >= 70 && spellId == GetSpell(SHIELD_SLAM_1)) ++ pctbonus += 0.1f; ++ //Warrior T10 Protection 2P Bonus (id: 70843): 20% bonus damage for Shield Slam and Shockwave (tanks only) ++ if (lvl >= 78 && IsTank() && (spellId == GetSpell(SHIELD_SLAM_1) || spellId == GetSpell(SHOCKWAVE_1))) ++ pctbonus += 0.2f; ++ //One-Handed Weapon Specialization: 10% bonus damage with 1H weapons (for bot - Devastate only) ++ if (lvl >= 35 && spellId == GetSpell(DEVASTATE_1)) ++ pctbonus += 0.1f; ++ //Warrior T9 Protection 2P Bonus (id: 67269): 5% bonus damage for Devastate ++ if (lvl >= 77 && spellId == GetSpell(DEVASTATE_1)) ++ pctbonus += 0.05f; ++ //Glyph of Mocking Blow: 25% bonus damage for Mocking Blow ++ if (lvl >= 15 && spellId == GetSpell(MOCKING_BLOW_1)) ++ pctbonus += 0.25f; ++ ++ //Improved Cleave: 120% increased '!bonus damage!' done by Cleave (flat mod) ++ if (lvl >= 25 && spellId == GetSpell(CLEAVE_1)) ++ { ++ float bp = spellInfo->Effects[EFFECT_0].BasePoints; //SPELL_EFFECT_WEAPON_DAMAGE (values: 15 - 222) ++ fdamage += bp * 1.2; ++ } ++ ++ damage = int32(fdamage * (1.0f + pctbonus)); ++ } ++ ++ void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const ++ { ++ uint32 spellId = spellInfo->Id; ++ //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); ++ uint8 lvl = me->getLevel(); ++ float fcost = float(cost); ++ int32 flatbonus = 0; ++ float pctbonus = 0.0f; ++ ++ //percent mods ++ //Glyph of Bloodrage: -100% health cost for Bloodrage ++ if (lvl >= 10 && spellId == GetSpell(BLOODRAGE_1)) ++ pctbonus += 1.0f; ++ //Sword and Board: -100% rage cost for Shield Slam ++ if (lvl >= 55 && spellId == GetSpell(SHIELD_SLAM_1) && me->HasAura(SWORD_AND_BOARD_BUFF)) ++ pctbonus += 1.0f; ++ //Glyph of Sweeping Strikes: -100% rage cost for Sweeping Strikes ++ if (lvl >= 30 && spellId == GetSpell(SWEEPING_STRIKES_1)) ++ pctbonus += 1.0f; ++ //Glyph of Revenge: -100% rage cost for Heroic Strike ++ if (lvl >= 20 && spellId == GetSpell(HEROIC_STRIKE_1) && me->HasAura(GLYPH_OF_REVENGE_BUFF)) ++ pctbonus += 1.0f; ++ ++ //flat mods ++ //!1 rage = 10 pts! ++ //Improved Heroic Strike: -3 rage cost for Heroic Strike ++ if (lvl >= 10 && spellId == GetSpell(HEROIC_STRIKE_1)) ++ flatbonus += 30; ++ //Bloodthirst and Mortal Strike Discount (id: 37535): -5 rage cost for Bloodthirst and Mortal Strike ++ if (lvl >= 40 && (/*spellId == GetSpell(BLOODTHIRST_1) || */spellId == GetSpell(MORTALSTRIKE_1))) ++ flatbonus += 50; ++ //Improved Execute: -5 rage cost for Execute ++ if (lvl >= 25 && spellId == GetSpell(EXECUTE_1)) ++ flatbonus += 50; ++ //Improved Thunder Clap (part 1): -4 rage cost for Execute ++ if (lvl >= 15 && spellId == GetSpell(THUNDER_CLAP_1)) ++ flatbonus += 40; ++ //Puncture: -3 rage cost for Sunder Armor and Devastate ++ if (lvl >= 25 && (spellId == GetSpell(SUNDER_1) || spellId == GetSpell(DEVASTATE_1))) ++ flatbonus += 30; ++ //Glyph of Shockwave: -3 rage cost for Shockwave ++ if (lvl >= 65 && spellId == GetSpell(SHOCKWAVE_1)) ++ flatbonus += 30; ++ //Improved Hamstring (id: 24428): -2 rage cost for Hamstring (for bot Piercing Howl also) ++ if (lvl >= 25 && (spellId == GetSpell(HAMSTRING_1) || spellId == GetSpell(PIERCING_HOWL_1))) ++ flatbonus += 20; ++ //Focused Rage: -3 rage cost for all abilities (using rage) ++ if (lvl >= 40 && spellInfo->PowerType == POWER_RAGE) ++ flatbonus += 30; ++ //Glyph of Resonating Power: -5 rage cost for Thunder Clap ++ if (lvl >= 15 && spellId == GetSpell(THUNDER_CLAP_1)) ++ flatbonus += 50; ++ ++ //cost can be < 0 ++ cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; ++ } ++ ++ void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const ++ { ++ //casttime is in milliseconds ++ uint32 spellId = spellInfo->Id; ++ //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); ++ uint8 lvl = me->getLevel(); ++ int32 timebonus = 0; ++ //float pctbonus = 0.0f; ++ ++ //100% mods ++ //Improved Slam: -100% sec cast time for Slam ++ if (lvl >= 40 && spellId == GetSpell(SLAM_1) && me->HasAura(BLOODSURGE_BUFF)) ++ timebonus += casttime; ++ ++ //flat mods ++ //Improved Slam: -1.0 sec cast time for Slam ++ if (lvl >= 40 && spellId == GetSpell(SLAM_1)) ++ timebonus += 1000; ++ ++ casttime = std::max(casttime - timebonus, 0); ++ } ++ ++ void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const ++ { ++ //cooldown is in milliseconds ++ uint32 spellId = spellInfo->Id; ++ //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); ++ uint8 lvl = me->getLevel(); ++ uint32 timebonus = 0; ++ float pctbonus = 0.0f; ++ ++ //pct mods ++ //Intensify Rage: -33% cooldown for Bloodrage, Berserker Rage, Recklessness and Death Wish ++ if (lvl >= 40 && ++ (spellId == GetSpell(BLOODRAGE_1) || spellId == GetSpell(BERSERKERRAGE_1) || ++ spellId == GetSpell(RECKLESSNESS_1) || spellId == GetSpell(DEATHWISH_1))) ++ pctbonus += 0.33f; ++ //Glyph of Rapid Charge: -7% cooldown for Charge ++ if (lvl >= 40 && spellId == GetSpell(CHARGE_1)) ++ pctbonus += 0.07f; ++ ++ //flat mods ++ //Improved Disciplines: -60 sec cooldown for Shield Wall, Retaliation and Recklessness ++ if (lvl >= 35 && ++ (spellId == GetSpell(SHIELD_WALL_1) || ++ spellId == GetSpell(RETALIATION_1) || ++ spellId == GetSpell(RECKLESSNESS_1))) ++ timebonus += 60000; ++ //Unrelenting Assault (part 1): -4 sec cooldown for Overpower and Revenge (not for tanks) ++ if (lvl >= 50 && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) ++ timebonus += 4000; ++ //Improved Intercept: -10 sec cooldown for Intercept ++ if (lvl >= 30 && spellId == GetSpell(INTERCEPT_1)) ++ timebonus += 10000; ++ //Shield Mastery (part 2): -20 sec cooldown for Shield Block ++ if (lvl >= 20 && spellId == GetSpell(SHIELD_BLOCK_1)) ++ timebonus += 20000; ++ //Improved Disarm (part 1): -20 sec cooldown for Disarm ++ if (lvl >= 25 && spellId == GetSpell(DISARM_1)) ++ timebonus += 20000; ++ //Improved Mortal Strike (part 2): -1 sec cooldown for Mortal Strike ++ if (lvl >= 25 && spellId == GetSpell(MORTALSTRIKE_1)) ++ timebonus += 1000; ++ //Glyph of Bladestorm: -15 sec cooldown for Bladestorm ++ if (lvl >= 60 && spellId == GetSpell(BLADESTORM_1)) ++ timebonus += 15000; ++ //Glyph of Last Stand: -1 min cooldown for Last Stand ++ if (lvl >= 20 && spellId == GetSpell(LAST_STAND_1)) ++ timebonus += 60000; ++ //Glyph of Spell Reflection: -1 sec cooldown for Spell Reflection ++ if (lvl >= 65 && spellId == GetSpell(SPELL_REFLECTION_1)) ++ timebonus += 60000; ++ //Glyph of Whirlwind: -2 sec cooldown for Whirlwind ++ if (lvl >= 36 && spellId == GetSpell(WHIRLWIND_1)) ++ timebonus += 2000; ++ //Warrior T9 2P Bonus (id: 67269): -2 sec cooldown for Taunt (tanks only) ++ if (lvl >= 68 && IsTank() && spellId == GetSpell(TAUNT_1)) ++ timebonus += 2000; ++ //Improved Challenging Shout (id: 12327): -2 min cooldown for Challenging Shout (tanks only) ++ if (lvl >= 30 && IsTank() && spellId == GetSpell(CHALLENGING_SHOUT_1)) ++ timebonus += 120000; ++ ++ cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); ++ } ++ ++ void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const ++ { ++ //cooldown is in milliseconds ++ uint32 spellId = spellInfo->Id; ++ //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); ++ uint8 lvl = me->getLevel(); ++ int32 timebonus = 0; ++ float pctbonus = 0.0f; ++ ++ //Unrelenting Assault (part 1): -4 sec cooldown for Overpower and Revenge (not for tanks) ++ if (lvl >= 50 && !IsTank() && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) ++ timebonus += 4000; ++ //Improved Mortal Strike (part 2): -1 sec cooldown for Mortal Strike ++ if (lvl >= 25 && spellId == GetSpell(MORTALSTRIKE_1)) ++ timebonus += 1000; ++ ++ cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); ++ } ++ ++ void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const ++ { ++ //cooldown is in milliseconds ++ uint32 spellId = spellInfo->Id; ++ //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); ++ uint8 lvl = me->getLevel(); ++ float timebonus = 0.0f; ++ float pctbonus = 0.0f; ++ ++ //Unrelenting Assault (part 1, special): -0.5 sec global cooldown for Overpower and Revenge (not for tanks) ++ if (lvl >= 50 && !IsTank() && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) ++ timebonus += 500.f; ++ ++ cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; ++ } ++ ++ void OnClassSpellGo(SpellInfo const* spellInfo) ++ { ++ uint32 spellId = spellInfo->Id; ++ ++ if (spellId == GetSpell(LAST_STAND_1)) ++ { ++ BotWhisper("Last Stand used!"); ++ } ++ if (spellId == GetSpell(SHIELD_WALL_1)) ++ { ++ BotWhisper("Shield Wall used!"); ++ //uint32 cd = 300000; ++ //ApplyBotSpellCooldownMods(sSpellMgr->GetSpellInfo(GetSpell(SHIELD_WALL_1)), cd); ++ SetSpellCooldown(RETALIATION_1, 12000); ++ //SetSpellCooldown(SHIELD_WALL_1, cd); ++ SetSpellCooldown(RECKLESSNESS_1, 12000); ++ } ++ if (spellId == GetSpell(RETALIATION_1)) ++ { ++ //custom ability - no cooldown ++ uint32 cd = 300000; ++ ApplyBotSpellCooldownMods(sSpellMgr->GetSpellInfo(GetSpell(RETALIATION_1)), cd); ++ SetSpellCooldown(RETALIATION_1, cd); ++ SetSpellCooldown(SHIELD_WALL_1, 12000); ++ SetSpellCooldown(RECKLESSNESS_1, 12000); ++ return; ++ } ++ if (spellId == GetSpell(RECKLESSNESS_1)) ++ { ++ //custom ability - no cooldown ++ uint32 cd = 300000; ++ ApplyBotSpellCooldownMods(sSpellMgr->GetSpellInfo(GetSpell(RECKLESSNESS_1)), cd); ++ SetSpellCooldown(RETALIATION_1, 12000); ++ SetSpellCooldown(SHIELD_WALL_1, 12000); ++ SetSpellCooldown(RECKLESSNESS_1, cd); ++ return; ++ } ++ if (spellId == GetSpell(OVERPOWER_1)) ++ { ++ //suctom ++ float gcd = 1500.f; ++ ApplyBotSpellGlobalCooldownMods(spellInfo, gcd); ++ GC_Timer = uint32(gcd); ++ } ++ if (spellId == GetSpell(CLEAVE_1) || spellId == GetSpell(HEROIC_STRIKE_1) || spellId == GetSpell(SLAM_1)) ++ { ++ //once per swing ++ SetSpellCooldown(spellId, me->getAttackTimer(BASE_ATTACK)); ++ } ++ if (spellId == GetSpell(VIGILANCE_1)) ++ { ++ SetSpellCooldown(VIGILANCE_1, 30000); //no initial cooldown ++ } ++ if (spellId == GetSpell(SLAM_1) && me->HasAura(BLOODSURGE_BUFF)) ++ { ++ me->RemoveAura(BLOODSURGE_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ if (spellId == GetSpell(HEROIC_STRIKE_1) && me->HasAura(GLYPH_OF_REVENGE_BUFF)) ++ { ++ me->RemoveAura(GLYPH_OF_REVENGE_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ if (spellId == GetSpell(SHIELD_SLAM_1) && me->HasAura(SWORD_AND_BOARD_BUFF)) ++ { ++ me->RemoveAura(SWORD_AND_BOARD_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ if (spellId == GetSpell(OVERPOWER_1) && me->HasAura(TASTE_FOR_BLOOD_BUFF)) ++ { ++ me->RemoveAura(TASTE_FOR_BLOOD_BUFF, ObjectGuid::Empty, 0, AURA_REMOVE_BY_EXPIRE); ++ } ++ if (spellId == TASTE_FOR_BLOOD_BUFF) ++ { ++ SetSpellCooldown(TASTE_FOR_BLOOD_BUFF, 6000); ++ } ++ } ++ ++ void SpellHit(Unit* caster, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if (spellId == BATTLESTANCE_1 || spellId == DEFENSIVESTANCE_1 || spellId == BERSERKERSTANCE_1) ++ { ++ //stance mastery impl ++ uint32 temprage = 0; ++ uint32 myrage = rage; ++ if (me->getLevel() >= 20) ++ temprage = myrage > 250 ? 250 : myrage; ++ else if (me->getLevel() >= 15) ++ temprage = myrage > 100 ? 100 : myrage; ++ ++ battleStance = (spellId == BATTLESTANCE_1); ++ defensiveStance = (spellId == DEFENSIVESTANCE_1); ++ berserkerStance = (spellId == BERSERKERSTANCE_1); ++ me->SetPower(POWER_RAGE, temprage); ++ stancetimer = 2100 - me->getLevel() * 20; //500 on 80 ++ GC_Timer = temptimer; ++ } ++ if (spellId == GetSpell(BERSERKERRAGE_1)) ++ { ++ //Improved Berserker Rage: 20 rage bonus when used ++ if (me->getLevel() >= 35) ++ me->CastSpell(me, BERSERKER_RAGE_EFFECT, true); ++ } ++ if (spellId == SWORD_AND_BOARD_BUFF) ++ { ++ //Sword And Board: remove Shield Slam cooldown ++ ResetSpellCooldown(SHIELD_SLAM_1); ++ } ++ if (spellId == VIGILANCE_PROC) ++ { ++ //Vigilance: remove Taunt cooldown ++ ResetSpellCooldown(TAUNT_1); ++ } ++ if (spellId == GetSpell(SHIELD_WALL_1)) ++ { ++ //Shield Wall Duration (id: 60175): 3 sec increased Shield Wall duration ++ if (Aura* wall = me->GetAura(spellId)) ++ { ++ int32 dur = wall->GetDuration() + 3000; ++ wall->SetDuration(dur); ++ wall->SetMaxDuration(dur); ++ } ++ } ++ ++ switch (spellId) ++ { ++ case VICTORIOUS_SPELL: ++ VICTORIOUS = true; ++ break; ++ default: ++ break; ++ } ++ OnSpellHit(caster, spell); ++ } ++ ++ void SpellHitTarget(Unit* target, SpellInfo const* spell) ++ { ++ uint32 spellId = spell->Id; ++ ++ if (spellId == GetSpell(PIERCING_HOWL_1)) ++ { ++ //Piercing Howl: 4 sec duraion increase (exclude players) ++ if (target->GetTypeId() != TYPEID_PLAYER) ++ { ++ if (Aura* howl = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = howl->GetDuration() + 4000; ++ howl->SetDuration(dur); ++ howl->SetMaxDuration(dur); ++ } ++ } ++ } ++ if (spellId == GetSpell(BATTLESHOUT_1) || spellId == GetSpell(COMMANDING_SHOUT_1)) ++ { ++ if (me->getLevel() >= 15) ++ { ++ //Commanding Presence: +25% increased effect (melee AP / HP) ++ AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_0, me->GetGUID()); ++ if (eff) ++ { ++ int32 amount = eff->GetAmount(); ++ amount = amount * 5 / 4; ++ eff->ChangeAmount(amount); ++ } ++ } ++ } ++ if (spellId == GetSpell(REVENGE_1)) ++ { ++ //zzzOLD Revenge Stun (25% chance) ++ if (me->getLevel() >= 25 && urand(1,100) <= 25) ++ me->CastSpell(target, REVENGE_STUN_SPELL, true); ++ } ++ if (spellId == GetSpell(THUNDER_CLAP_1)) ++ { ++ //We make it tanking bonus only, to prevent imbalance ++ if (me->getLevel() >= 15 && IsTank()) ++ { ++ AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_1, me->GetGUID()); ++ if (eff) ++ { ++ int32 amount = eff->GetAmount(); ++ //Improved Thunder Clap (part 3): 10% extra slow ++ amount += (-10); ++ //Conqueror Thunder Clap Bonus: 50% increased effect ++ if (me->getLevel() >= 60) ++ amount = amount * 3 / 2; ++ ++ eff->ChangeAmount(amount); ++ } ++ } ++ } ++ if (spellId == GetSpell(OVERPOWER_1)) ++ { ++ me->ClearReactive(REACTIVE_OVERPOWER); ++ //Unrelenting Assault (part 3): reduce spells efficiency ++ if (UNRELENTING_ASSAULT && target->HasUnitState(UNIT_STATE_CASTING)) ++ target->CastSpell(target, UNRELENTING_ASSAULT_SPELL, true); ++ } ++ if (spellId == GetSpell(BATTLESHOUT_1) || spellId == GetSpell(COMMANDING_SHOUT_1)) ++ { ++ //Glyph of Battle/Command + 2 min duration (8 for bots) ++ if (Aura* shout = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = shout->GetDuration() + 480000; ++ shout->SetDuration(dur); ++ shout->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(REND_1)) ++ { ++ //Glyph of Rending + 6 sec duration ++ if (Aura* rend = target->GetAura(spellId, me->GetGUID())) ++ { ++ uint32 dur = rend->GetDuration() + 6000; ++ rend->SetDuration(dur); ++ rend->SetMaxDuration(dur); ++ } ++ } ++ if (spellId == GetSpell(INTERVENE_1)) ++ { ++ //Glyph of Intervene + 2 bonus charges ++ if (Aura* vene = target->GetAura(spellId, me->GetGUID())) ++ { ++ vene->SetCharges(vene->GetCharges() + 2); ++ } ++ } ++ if (spellId == GetSpell(VICTORY_RUSH_1)) ++ { ++ me->RemoveAura(VICTORIOUS_SPELL); ++ VICTORIOUS = false; ++ } ++ } ++ ++ void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) ++ { ++ bot_ai::DamageDealt(victim, damage, damageType); ++ } ++ ++ void DamageTaken(Unit* u, uint32& /*damage*/) ++ { ++ if (!u->IsInCombat() && !me->IsInCombat()) ++ return; ++ OnOwnerDamagedBy(u); ++ } ++ ++ void OwnerAttackedBy(Unit* u) ++ { ++ OnOwnerDamagedBy(u); ++ } ++ ++ void Reset() ++ { ++ stancetimer = 0; ++ ragetimer = 1500; ++ ragetimer2 = 3000; ++ shoutCheckTimer = 5000; ++ ++ vigilanceTargetGuid.Clear(); ++ ++ battleStance = true; ++ defensiveStance = false; ++ berserkerStance = false; ++ ++ rageIncomeMult = sWorld->getRate(RATE_POWER_RAGE_INCOME); ++ rageLossMult = sWorld->getRate(RATE_POWER_RAGE_LOSS); ++ me->setPowerType(POWER_RAGE); ++ rage = 0; ++ ++ DefaultInit(); ++ } ++ ++ void ReduceCD(uint32 diff) ++ { ++ if (stancetimer > diff) stancetimer -= diff; ++ if (ragetimer > diff) ragetimer -= diff; ++ if (ragetimer2 > diff) ragetimer2 -= diff; ++ if (shoutCheckTimer > diff) shoutCheckTimer -= diff; ++ } ++ ++ void InitSpells() ++ { ++ uint8 lvl = me->getLevel(); ++ InitSpellMap(INTIMIDATING_SHOUT_1); ++ InitSpellMap(ENRAGED_REGENERATION_1); ++ InitSpellMap(CHARGE_1); ++ InitSpellMap(OVERPOWER_1); ++ /*Quest*/lvl >= 10 ? InitSpellMap(TAUNT_1) : RemoveSpell(TAUNT_1); ++ InitSpellMap(BLOODRAGE_1); ++ InitSpellMap(BERSERKERRAGE_1); ++ InitSpellMap(INTERCEPT_1); ++ InitSpellMap(CLEAVE_1); ++ InitSpellMap(HAMSTRING_1); ++ InitSpellMap(INTERVENE_1); ++ InitSpellMap(WHIRLWIND_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(BLADESTORM_1) : RemoveSpell(BLADESTORM_1); ++ InitSpellMap(BATTLESHOUT_1); ++ InitSpellMap(REND_1); ++ InitSpellMap(EXECUTE_1); ++ InitSpellMap(PUMMEL_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(MORTALSTRIKE_1) : RemoveSpell(MORTALSTRIKE_1); ++ InitSpellMap(SLAM_1); ++ /*Quest*/lvl >= 10 ? InitSpellMap(SUNDER_1) : RemoveSpell(SUNDER_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(SWEEPING_STRIKES_1) : RemoveSpell(SWEEPING_STRIKES_1); ++ InitSpellMap(BATTLESTANCE_1); ++ /*Quest*/lvl >= 10 ? InitSpellMap(DEFENSIVESTANCE_1) : RemoveSpell(DEFENSIVESTANCE_1); ++ /*Quest*/lvl >= 30 ? InitSpellMap(BERSERKERSTANCE_1) : RemoveSpell(BERSERKERSTANCE_1); ++ /*Special*/lvl >= 50 ? InitSpellMap(RECKLESSNESS_1) : RemoveSpell(RECKLESSNESS_1); ++ /*Special*/lvl >= 20 ? InitSpellMap(RETALIATION_1) : RemoveSpell(RETALIATION_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(DEATHWISH_1) : RemoveSpell(DEATHWISH_1); ++ InitSpellMap(VICTORY_RUSH_1); ++ InitSpellMap(THUNDER_CLAP_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(LAST_STAND_1) : RemoveSpell(LAST_STAND_1); ++ InitSpellMap(REVENGE_1); ++ InitSpellMap(SHIELD_BLOCK_1); ++ InitSpellMap(SHIELD_SLAM_1); ++ InitSpellMap(SPELL_REFLECTION_1); ++ InitSpellMap(DISARM_1); ++ InitSpellMap(SHIELD_WALL_1); ++ InitSpellMap(SHIELD_BASH_1); ++ InitSpellMap(HEROIC_THROW_1); ++ /*Talent*/lvl >= 30 ? InitSpellMap(CONCUSSION_BLOW_1) : RemoveSpell(CONCUSSION_BLOW_1); ++ /*Talent*/lvl >= 40 ? InitSpellMap(VIGILANCE_1) : RemoveSpell(VIGILANCE_1); ++ /*Talent*/lvl >= 50 ? InitSpellMap(DEVASTATE_1) : RemoveSpell(DEVASTATE_1); ++ InitSpellMap(MOCKING_BLOW_1); ++ /*Talent*/lvl >= 60 ? InitSpellMap(SHOCKWAVE_1) : RemoveSpell(SHOCKWAVE_1); ++ /*Talent*/lvl >= 20 ? InitSpellMap(PIERCING_HOWL_1) : RemoveSpell(PIERCING_HOWL_1); ++ InitSpellMap(HEROIC_STRIKE_1); ++ InitSpellMap(CHALLENGING_SHOUT_1); ++ InitSpellMap(COMMANDING_SHOUT_1); ++ ++ /*SPECIAL*/InitSpellMap(TASTE_FOR_BLOOD_BUFF, true); ++ } ++ ++ void ApplyClassPassives() ++ { ++ uint8 level = master->getLevel(); ++ ++ UNRELENTING_ASSAULT = (level >= 35); ++ RefreshAura(WC5, level >= 70 ? 1 : 0); ++ RefreshAura(WC4, level >= 68 && level < 70 ? 1 : 0); ++ RefreshAura(WC3, level >= 66 && level < 68 ? 1 : 0); ++ RefreshAura(WC2, level >= 64 && level < 66 ? 1 : 0); ++ RefreshAura(WC1, level >= 62 && level < 64 ? 1 : 0); ++ RefreshAura(FLURRY5, level >= 39 ? 1 : 0); ++ RefreshAura(FLURRY4, level >= 38 && level < 39 ? 1 : 0); ++ RefreshAura(FLURRY3, level >= 37 && level < 38 ? 1 : 0); ++ RefreshAura(FLURRY2, level >= 36 && level < 37 ? 1 : 0); ++ RefreshAura(FLURRY1, level >= 35 && level < 36 ? 1 : 0); ++ RefreshAura(SWORD_SPEC5, level >= 60 ? 2 : level >= 50 ? 1 : 0); ++ RefreshAura(SWORD_SPEC4, level >= 45 && level < 50 ? 1 : 0); ++ RefreshAura(SWORD_SPEC3, level >= 40 && level < 45 ? 1 : 0); ++ RefreshAura(SWORD_SPEC2, level >= 35 && level < 40 ? 1 : 0); ++ RefreshAura(SWORD_SPEC1, level >= 30 && level < 35 ? 1 : 0); ++ RefreshAura(RAMPAGE, level >= 60 ? 1 : 0); ++ RefreshAura(TRAUMA2, level >= 55 ? 1 : 0); ++ RefreshAura(TRAUMA1, level >= 35 && level < 55 ? 1 : 0); ++ RefreshAura(BLOOD_FRENZY, level >= 45 ? 1 : 0); ++ RefreshAura(SECOND_WIND, level >= 40 ? 1 : 0); ++ RefreshAura(TOUGHNESS, level >= 40 ? 2 : level >= 15 ? 1 : 0); ++ RefreshAura(IMP_HAMSTRING, level >= 40 ? 2 : level >= 35 ? 1 : 0); ++ RefreshAura(SHIELD_SPECIALIZATION, level >= 15 ? 1 : 0); ++ RefreshAura(GAG_ORDER, level >= 30 ? 1 : 0); ++ RefreshAura(IMPROVED_SPELL_REFLECTION, level >= 25 ? 1 : 0); ++ RefreshAura(IMPROVED_DISARM, level >= 25 ? 1 : 0); ++ RefreshAura(VITALITY, level >= 45 ? 1 : 0); ++ RefreshAura(CRITICAL_BLOCK, level >= 50 ? 1 : 0); ++ RefreshAura(SWORD_AND_BOARD, level >= 55 ? 1 : 0); ++ RefreshAura(ARMORED_TO_THE_TEETH, level >= 20 ? 1 : 0); ++ RefreshAura(ENDLESS_RAGE, level >= 55 ? 1 : 0); ++ RefreshAura(BLOODSURGE, level >= 50 ? 1 : 0); ++ RefreshAura(TASTE_FOR_BLOOD3, level >= 30 ? 1 : 0); ++ RefreshAura(TASTE_FOR_BLOOD2, level >= 28 && level < 30 ? 1 : 0); ++ RefreshAura(TASTE_FOR_BLOOD1, level >= 25 && level < 28 ? 1 : 0); ++ RefreshAura(BLOOD_CRAZE3, level >= 30 ? 1 : 0); ++ RefreshAura(BLOOD_CRAZE2, level >= 25 && level < 30 ? 1 : 0); ++ RefreshAura(BLOOD_CRAZE1, level >= 20 && level < 25 ? 1 : 0); ++ RefreshAura(WARRIOR_T10_4P, level >= 60 ? 1 : 0); ++ RefreshAura(GLYPH_BLOCKING); ++ RefreshAura(GLYPH_DEVASTATE); ++ RefreshAura(GLYPH_EXECUTION); ++ RefreshAura(GLYPH_HEROIC_STRIKE); ++ RefreshAura(GLYPH_REVENGE); ++ } ++ ++ bool CanUseManually(uint32 basespell) const ++ { ++ switch (basespell) ++ { ++ case ENRAGED_REGENERATION_1: ++ case BLOODRAGE_1: ++ case BERSERKERRAGE_1: ++ case BATTLESHOUT_1: ++ case COMMANDING_SHOUT_1: ++ case DEATHWISH_1: ++ return true; ++ case BATTLESTANCE_1: ++ return !battleStance; ++ case DEFENSIVESTANCE_1: ++ return !defensiveStance; ++ case BERSERKERSTANCE_1: ++ return !berserkerStance; ++ case SWEEPING_STRIKES_1: ++ return battleStance || berserkerStance; ++ //case RETALIATION_1: ++ // return battleStance; ++ //case RECKLESSNESS_1: ++ // return berserkerStance; ++ case SHIELD_WALL_1: ++ return CanBlock() && defensiveStance; ++ case SHIELD_BLOCK_1: ++ return CanBlock(); ++ case LAST_STAND_1: ++ case VIGILANCE_1: ++ return IsTank(); ++ default: ++ return false; ++ } ++ } ++ ++ float GetBotArmorPenetrationCoef() const ++ { ++ float bonus = 0.0f; ++ ++ if (battleStance) ++ { ++ bonus += 0.1f; ++ //Warrior T10 4P Bonus (part 1): 6% additional armor penetration in Battle Stance ++ if (me->getLevel() >= 75) ++ bonus += 0.06f; ++ } ++ ++ //Mace Specialization: 15% armor penetration ++ if (me->getLevel() >= 30) ++ if (Item const* weap = GetEquips(0)) ++ if (ItemTemplate const* proto = weap->GetTemplate()) ++ if (proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2) ++ bonus += 0.15f; ++ ++ return bonus; ++ } ++ ++ private: ++/*tmrs*/uint32 stancetimer, ragetimer, ragetimer2, shoutCheckTimer; ++/*misc*/int32 rage; ++/*misc*/ObjectGuid vigilanceTargetGuid; ++/*misc*/float rageIncomeMult, rageLossMult; ++/*Chck*/bool battleStance, defensiveStance, berserkerStance, UNRELENTING_ASSAULT, VICTORIOUS; ++ ++ enum WarriorBaseSpells ++ { ++ INTIMIDATING_SHOUT_1 = 5246, ++ ENRAGED_REGENERATION_1 = 55694, ++ CHARGE_1 = 11578, ++ OVERPOWER_1 = 7384, ++ TAUNT_1 = 355, ++ BLOODRAGE_1 = 2687, ++ BERSERKERRAGE_1 = 18499, ++ INTERCEPT_1 = 20252, ++ CLEAVE_1 = 845, ++ HAMSTRING_1 = 1715, ++ INTERVENE_1 = 3411, ++ WHIRLWIND_1 = 1680, ++ BLADESTORM_1 = 46924, ++ BATTLESHOUT_1 = 6673, ++ REND_1 = 772, ++ EXECUTE_1 = 5308, ++ PUMMEL_1 = 6552, ++ /*Talent*/MORTALSTRIKE_1 = 12294, ++ SLAM_1 = 1464, ++ SUNDER_1 = 7386, ++ /*Talent*/SWEEPING_STRIKES_1 = 12328, ++ BATTLESTANCE_1 = 2457, ++ DEFENSIVESTANCE_1 = 71, ++ BERSERKERSTANCE_1 = 2458, ++ RECKLESSNESS_1 = 13847,//1719, original warrior spell ++ RETALIATION_1 = 22857,//20230, original warrior spell ++ /*Talent*/DEATHWISH_1 = 12292, ++ VICTORY_RUSH_1 = 34428, ++ THUNDER_CLAP_1 = 6343, ++ /*Talent*/LAST_STAND_1 = 12975, ++ REVENGE_1 = 6572, ++ SHIELD_BLOCK_1 = 2565, ++ SHIELD_SLAM_1 = 23922, ++ SPELL_REFLECTION_1 = 23920, ++ DISARM_1 = 676, ++ SHIELD_WALL_1 = 871, ++ SHIELD_BASH_1 = 72, ++ HEROIC_THROW_1 = 57755, ++ /*Talent*/CONCUSSION_BLOW_1 = 12809, ++ /*Talent*/VIGILANCE_1 = 50720, ++ /*Talent*/DEVASTATE_1 = 20243, ++ MOCKING_BLOW_1 = 694, ++ SHOCKWAVE_1 = 46968, ++ PIERCING_HOWL_1 = 12323, ++ HEROIC_STRIKE_1 = 78, ++ CHALLENGING_SHOUT_1 = 1161, ++ COMMANDING_SHOUT_1 = 469 ++ }; ++ enum WarriorPassives ++ { ++ //Talents ++ WC1 /*WRECKING CREW1*/ = 46867, ++ WC2 /*WRECKING CREW2*/ = 56611, ++ WC3 /*WRECKING CREW3*/ = 56612, ++ WC4 /*WRECKING CREW4*/ = 56613, ++ WC5 /*WRECKING CREW5*/ = 56614, ++ FLURRY1 = 16256, ++ FLURRY2 = 16281, ++ FLURRY3 = 16282, ++ FLURRY4 = 16283, ++ FLURRY5 = 16284, ++ SWORD_SPEC1 = 12281, ++ SWORD_SPEC2 = 12812, ++ SWORD_SPEC3 = 12813, ++ SWORD_SPEC4 = 12814, ++ SWORD_SPEC5 = 12815, ++ BLOOD_CRAZE1 = 16487, ++ BLOOD_CRAZE2 = 16489, ++ BLOOD_CRAZE3 = 16492, ++ TASTE_FOR_BLOOD1 = 56636, ++ TASTE_FOR_BLOOD2 = 56637, ++ TASTE_FOR_BLOOD3 = 56638, ++ UNRELENTING_ASSAULT1 = 46859, ++ UNRELENTING_ASSAULT2 = 46860, ++ TRAUMA1 = 46854, ++ TRAUMA2 = 46855, ++ BLOOD_FRENZY = 29859, ++ RAMPAGE = 29801, ++ SECOND_WIND = 29838,//rank 2 ++ TOUGHNESS = 12764,//rank 5 ++ IMP_HAMSTRING = 23695,//rank 3 ++ SHIELD_SPECIALIZATION = 12727,//rank 5 ++ GAG_ORDER = 12958,//rank 2 ++ IMPROVED_SPELL_REFLECTION = 59089,//rank 2 ++ IMPROVED_DISARM = 12804,//rank 2 ++ VITALITY = 29144,//rank 3 ++ CRITICAL_BLOCK = 47296,//rank 3 ++ SWORD_AND_BOARD = 46953,//rank 3 ++ ARMORED_TO_THE_TEETH = 61222,//rank 3 ++ ENDLESS_RAGE = 29623, ++ BLOODSURGE = 46915,//rank 3 ++ //other ++ WARRIOR_T10_4P = 70844, ++ GLYPH_BLOCKING = 58375, ++ GLYPH_DEVASTATE = 58388, ++ GLYPH_EXECUTION = 58367, ++ GLYPH_HEROIC_STRIKE = 58357, ++ GLYPH_REVENGE = 58364 ++ }; ++ enum WarriorSpecial ++ { ++ TASTE_FOR_BLOOD_BUFF = 60503, ++ //LAMBS_TO_THE_SLAUGHTER_BUFF = 84586, ++ SWORD_AND_BOARD_BUFF = 50227, ++ BLOODSURGE_BUFF = 46916,//"Slam!" ++ GLYPH_OF_REVENGE_BUFF = 58363, ++ UNRELENTING_ASSAULT_SPELL = 65925,//64849 ++ VICTORIOUS_SPELL = 32216, ++ REVENGE_STUN_SPELL = 12798, ++ //COLOSSUS_SMASH_EFFECT = 108126, ++ //SWORD_SPECIALIZATION_TRIGGERED = 16459, ++ VIGILANCE_PROC = 50725, ++ BERSERKER_RAGE_EFFECT = 23691,//rank 2 ++ ++ BLESSING_OF_MIGHT_1 = 19740, ++ GREATER_BLESSING_OF_MIGHT_1 = 25782 ++ }; ++ }; ++}; ++ ++void AddSC_warrior_bot() ++{ ++ new warrior_bot(); ++} +diff --git a/src/server/game/AI/NpcBots/botcommands.cpp b/src/server/game/AI/NpcBots/botcommands.cpp +new file mode 100644 +index 0000000..571be04 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/botcommands.cpp +@@ -0,0 +1,931 @@ ++#include "bot_ai.h" ++#include "botmgr.h" ++#include "Chat.h" ++#include "RBAC.h" ++#include "Group.h" ++#include "Language.h" ++#include "Player.h" ++#include "ScriptMgr.h" ++#include "SpellInfo.h" ++/* ++Name: script_bot_commands ++%Complete: ??? ++Comment: Npcbot related commands ++Category: commandscripts/custom/ ++*/ ++//RBAC_PERM_GM_COMMANDS = 197 ++#define GM_COMMANDS rbac::RBACPermissions(197) ++ ++class script_bot_commands : public CommandScript ++{ ++private: ++ typedef std::pair BotPair; ++ static bool sortbots(BotPair p1, BotPair p2) ++ { ++ return p1.first < p2.first; ++ } ++ ++public: ++ script_bot_commands() : CommandScript("script_bot_commands") { } ++ ++ std::vector GetCommands() const ++ { ++ static std::vector npcbotSetCommandTable = ++ { ++ { "faction", rbac::RBAC_PERM_COMMAND_NPCBOT_FACTION, false, &HandleNpcSetFactionCommand, ""}, ++ { "owner", rbac::RBAC_PERM_COMMAND_NPCBOT_OWNER, false, &HandleNpcSetOwnerCommand, ""}, ++ }; ++ ++ static std::vector npcbotCommandTable = ++ { ++ { "set", rbac::RBAC_PERM_COMMAND_NPCBOT_SET, false, NULL, "", npcbotSetCommandTable}, ++ { "add", rbac::RBAC_PERM_COMMAND_NPCBOT_ADD, false, &HandleNpcBotAddCommand, ""}, ++ { "remove", rbac::RBAC_PERM_COMMAND_NPCBOT_REMOVE, false, &HandleNpcBotRemoveCommand, ""}, ++ { "spawn", rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, false, &HandleNpcBotSpawnCommand, ""}, ++ { "delete", rbac::RBAC_PERM_COMMAND_NPCBOT_DELETE, false, &HandleNpcBotDeleteCommand, ""}, ++ { "lookup", rbac::RBAC_PERM_COMMAND_NPCBOT_LOOKUP, false, &HandleNpcBotLookupCommand, ""}, ++ { "revive", rbac::RBAC_PERM_COMMAND_NPCBOT_REVIVE, false, &HandleNpcBotReviveCommand, ""}, ++ { "cast", rbac::RBAC_PERM_COMMAND_NPCBOT_CAST, false, &HandleNpcBotCastCustomSpell, ""}, ++ }; ++ ++ static std::vector commandTable = ++ { ++ { "npcbot", rbac::RBAC_PERM_COMMAND_NPCBOT, false, NULL, "", npcbotCommandTable}, ++ }; ++ return commandTable; ++ } ++ ++ static bool HandleNpcBotCastCustomSpell(ChatHandler* handler, const char* /*args*/) ++ { ++ handler->SetSentErrorMessage(true); ++ handler->SendSysMessage("This is a dev command. Do not use it."); ++ ++ //uint32 trig = SPELL_TRANSPARENCY_50; //transpar ++ //SpellInfo* trigInfo = const_cast(sSpellMgr->GetSpellInfo(trig)); ++ ++ //trigInfo->Dispel = DISPEL_NONE; ++ //trigInfo->Mechanic = MECHANIC_NONE; ++ //trigInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //6 - 100 yds ++ ++ //trigInfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //1 - instant //3 - 0.5 sec ++ //trigInfo->DurationEntry = sSpellDurationStore.LookupEntry(1); //1 - 10 sec //32 - 6 seconds ++ //trigInfo->ManaCost = 0; ++ //trigInfo->ManaCostPercentage = 0; ++ //trigInfo->ManaCostPerlevel = 0; ++ //trigInfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_CASTABLE_WHILE_SITTING); ++ //trigInfo->AttributesEx &= ~SPELL_ATTR1_CANT_TARGET_SELF; ++ //trigInfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH); ++ //trigInfo->AttributesEx2 |= SPELL_ATTR2_UNK22; ++ //trigInfo->AttributesEx5 |= SPELL_ATTR5_HASTE_AFFECT_DURATION; ++ //trigInfo->Targets = TARGET_FLAG_DEST_LOCATION; ++ //trigInfo->AuraInterruptFlags = ++ // AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | ++ // AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish ++ //trigInfo->ChannelInterruptFlags = 0x00007C3C; //31788 ++ //trigInfo->CasterAuraStateNot = 0; ++ ++ //trigInfo->Effects[0].Effect = SPELL_EFFECT_DUMMY; ++ //trigInfo->Effects[0].BasePoints = 1; ++ ////trigInfo->Effects[0].ValueMultiplier = 0.0f; ++ //trigInfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CHANNEL_TARGET); ++ //trigInfo->Effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ENEMY); ++ //trigInfo->Effects[0].ApplyAuraName = SPELL_AURA_NONE; ++ //trigInfo->Effects[0].Amplitude = 0; ++ //trigInfo->Effects[0].TriggerSpell = 0; ++ //trigInfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS); //14 ++ ++ ++ uint32 spellId = SPELL_MIRROR_IMAGE_BM; //69936 ++ SpellInfo* sinfo = const_cast(sSpellMgr->GetSpellInfo(spellId)); ++ ++ //sinfo->SpellLevel = 0; ++ //sinfo->MaxLevel = 80; ++ //sinfo->Dispel = DISPEL_NONE; ++ //sinfo->Mechanic = MECHANIC_NONE; ++ sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //1 - self only //6 - 100 yds ++ //sinfo->Speed = 25.f; ++ //sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //1 - instant //3 - 0.5 sec ++ sinfo->DurationEntry = sSpellDurationStore.LookupEntry(566); //566 - 0 sec //3 - 60 sec //1 - 10 sec //32 - 6 seconds ++ sinfo->RecoveryTime = 3000; ++ sinfo->PowerType = POWER_MANA; ++ sinfo->ManaCost = 125; ++ sinfo->ManaCostPercentage = 0; ++ sinfo->ManaCostPerlevel = 0; ++ //sinfo->DmgClass = SPELL_DAMAGE_CLASS_MELEE; ++ //sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; ++ //sinfo->EquippedItemClass = ITEM_CLASS_WEAPON; ++ //sinfo->EquippedItemSubClassMask = 0x0002A5F3; ++ ++ //sinfo->Attributes &= ~(SPELL_ATTR0_UNK11); ++ sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT/* | SPELL_ATTR0_CASTABLE_WHILE_SITTING | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY*/); ++ //sinfo->AttributesEx &= ~SPELL_ATTR1_UNK11; ++ //sinfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH | SPELL_ATTR1_NO_THREAT); ++ sinfo->AttributesEx2 &= ~(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS); ++ sinfo->AttributesEx3 |= SPELL_ATTR3_DONT_DISPLAY_RANGE; ++ //sinfo->AttributesEx5 |= SPELL_ATTR5_HIDE_DURATION; ++ //sinfo->AttributesEx7 &= ~SPELL_ATTR7_HAS_CHARGE_EFFECT; ++ //sinfo->Targets |= TARGET_FLAG_DEST_LOCATION; ++ //sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT_ENEMY; ++ //sinfo->InterruptFlags = 0x0000000F; //15 ++ //sinfo->AuraInterruptFlags = ++ // AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | ++ // AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish ++ //sinfo->ChannelInterruptFlags = 0x00007C3C; //31788 ++ //sinfo->CasterAuraStateNot = 0; ++ ++ sinfo->Effects[0].Effect = SPELL_EFFECT_DUMMY; ++ //sinfo->Effects[0].BasePoints = 9999; ++ //sinfo->Effects[0].ValueMultiplier = 0.0f; ++ sinfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ //sinfo->Effects[0].TargetB = SpellImplicitTargetInfo(0); ++ sinfo->Effects[0].MiscValue = 0; ++ sinfo->Effects[0].MiscValueB = 0; ++ //sinfo->Effects[0].ApplyAuraName = SPELL_AURA_MOD_INVISIBILITY; ++ //sinfo->Effects[0].Amplitude = 0; ++ //sinfo->Effects[0].TriggerSpell = 0; ++ sinfo->Effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); ++ ++ //sinfo->Effects[1].Effect = SPELL_EFFECT_APPLY_AURA; ++ //sinfo->Effects[1].BasePoints = 10; ++ //sinfo->Effects[1].RealPointsPerLevel = 0.5f; ++ //sinfo->Effects[1].ValueMultiplier = 1.0f; ++ //sinfo->Effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ //sinfo->Effects[1].TargetB = SpellImplicitTargetInfo(0); ++ //sinfo->Effects[1].ApplyAuraName = SPELL_AURA_MOD_INCREASE_SPEED; ++ //sinfo->Effects[1].Amplitude = 0; ++ //sinfo->Effects[1].TriggerSpell = 0; ++ //sinfo->Effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 ++ ++ //sinfo->Effects[2].Effect = SPELL_EFFECT_TRIGGER_SPELL; ++ //sinfo->Effects[2].BasePoints = 0; ++ //sinfo->Effects[2].ValueMultiplier = 0.0f; ++ //sinfo->Effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); ++ //sinfo->Effects[2].TargetB = SpellImplicitTargetInfo(0); ++ //sinfo->Effects[2].ApplyAuraName = SPELL_AURA_NONE; ++ //sinfo->Effects[2].Amplitude = 0; ++ //sinfo->Effects[2].TriggerSpell = trig; ++ //sinfo->Effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 ++ ++ ++ return true; ++ } ++ ++ static bool HandleNpcSetFactionCommand(ChatHandler* handler, const char* args) ++ { ++ Player* chr = handler->GetSession()->GetPlayer(); ++ Unit* ubot = chr->GetSelectedUnit(); ++ if (!ubot || !*args) ++ { ++ handler->SendSysMessage(".npcbot set faction #faction"); ++ handler->SendSysMessage("Sets faction for selected npcbot (saved in DB). Use 'a', 'h' or 'm' as argument to set faction to alliance, horde or monsters (hostile to all)"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* bot = ubot->ToCreature(); ++ if (!bot || !bot->GetIAmABot() || !bot->IsFreeBot()) ++ { ++ handler->SendSysMessage("You must select uncontrolled npcbot."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ uint32 factionId = 0; ++ const std::string facStr = args; ++ char const* factionChar = facStr.c_str(); ++ ++ if (factionChar[0] == 'a') ++ factionId = 1802; //Alliance ++ else if (factionChar[0] == 'h') ++ factionId = 1801; //Horde ++ else if (factionChar[0] == 'm') ++ factionId = 14; //Monsters ++ ++ if (!factionId) ++ { ++ char* pfactionid = handler->extractKeyFromLink((char*)args, "Hfaction"); ++ factionId = atoi(pfactionid); ++ } ++ ++ if (!sFactionTemplateStore.LookupEntry(factionId)) ++ { ++ handler->PSendSysMessage(LANG_WRONG_FACTION, factionId); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_FACTION); ++ //"UPDATE characters_npcbot SET faction = ? WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, factionId); ++ stmt->setUInt32(1, bot->GetEntry()); ++ CharacterDatabase.DirectExecute(stmt); ++ ++ handler->PSendSysMessage("%s's faction set to %u", bot->GetName().c_str(), factionId); ++ bot->GetBotAI()->InitFaction(); ++ return true; ++ } ++ ++ static bool HandleNpcSetOwnerCommand(ChatHandler* handler, const char* args) ++ { ++ Player* chr = handler->GetSession()->GetPlayer(); ++ Unit* ubot = chr->GetSelectedUnit(); ++ if (!ubot || !*args) ++ { ++ handler->SendSysMessage(".npcbot set owner #guid | #name"); ++ handler->SendSysMessage("Binds selected npcbot to new player owner using guid or name and updates owner in DB"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* bot = ubot->ToCreature(); ++ if (!bot || !bot->GetIAmABot() || bot->GetBotAI()->GetBotOwnerGuid()) ++ { ++ handler->SendSysMessage("This npcbot already has owner"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ char* characterName_str = strtok((char*)args, " "); ++ if (!characterName_str) ++ return false; ++ ++ std::string characterName = characterName_str; ++ uint32 guidlow = (uint32)atoi(characterName_str); ++ ++ if (guidlow) ++ sObjectMgr->GetPlayerNameByGUID(ObjectGuid(HighGuid::Player, guidlow), characterName); ++ else ++ guidlow = sObjectMgr->GetPlayerGUIDByName(characterName); ++ ++ if (!guidlow || !normalizePlayerName(characterName)) ++ { ++ handler->PSendSysMessage("Player %s not found", characterName.c_str()); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ bot->GetBotAI()->SetBotOwnerGUID(guidlow); ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); ++ //"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, guidlow); ++ stmt->setUInt32(1, bot->GetEntry()); ++ CharacterDatabase.Execute(stmt); ++ ++ handler->PSendSysMessage("%s's new owner is %s (guidlow: %u)", bot->GetName().c_str(), characterName.c_str(), guidlow); ++ return true; ++ } ++ ++ static bool HandleNpcBotLookupCommand(ChatHandler* handler, const char* args) ++ { ++ //this is just a modified '.lookup creature' command ++ if (!*args) ++ { ++ handler->SendSysMessage(".npcbot lookup #class"); ++ handler->SendSysMessage("Looks up npcbots by #class, and returns all matches with their creature ID's."); ++ handler->PSendSysMessage("BOT_CLASS_WARRIOR = %u", uint32(BOT_CLASS_WARRIOR)); ++ handler->PSendSysMessage("BOT_CLASS_PALADIN = %u", uint32(BOT_CLASS_PALADIN)); ++ handler->PSendSysMessage("BOT_CLASS_HUNTER = %u", uint32(BOT_CLASS_HUNTER)); ++ handler->PSendSysMessage("BOT_CLASS_ROGUE = %u", uint32(BOT_CLASS_ROGUE)); ++ handler->PSendSysMessage("BOT_CLASS_PRIEST = %u", uint32(BOT_CLASS_PRIEST)); ++ handler->PSendSysMessage("BOT_CLASS_DEATH_KNIGHT = %u", uint32(BOT_CLASS_DEATH_KNIGHT)); ++ handler->PSendSysMessage("BOT_CLASS_SHAMAN = %u", uint32(BOT_CLASS_SHAMAN)); ++ handler->PSendSysMessage("BOT_CLASS_MAGE = %u", uint32(BOT_CLASS_MAGE)); ++ handler->PSendSysMessage("BOT_CLASS_WARLOCK = %u", uint32(BOT_CLASS_WARLOCK)); ++ handler->PSendSysMessage("BOT_CLASS_DRUID = %u", uint32(BOT_CLASS_DRUID)); ++ handler->PSendSysMessage("BOT_CLASS_BM = %u", uint32(BOT_CLASS_BM)); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ char* classstr = strtok((char*)args, " "); ++ uint8 botclass = BOT_CLASS_NONE; ++ ++ if (classstr) ++ botclass = (uint8)atoi(classstr); ++ ++ if (botclass == BOT_CLASS_NONE || botclass >= BOT_CLASS_END) ++ { ++ handler->PSendSysMessage("Unknown bot class %u", uint32(botclass)); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ handler->PSendSysMessage("Looking for bots of class %u...", uint32(botclass)); ++ ++ uint8 localeIndex = handler->GetSessionDbLocaleIndex(); ++ CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates(); ++ typedef std::list BotList; ++ BotList botlist; ++ for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr) ++ { ++ uint32 id = itr->second.Entry; ++ if (id < BOT_ENTRY_BEGIN || id > BOT_ENTRY_END) continue; ++ uint32 trainer_class = itr->second.trainer_class; ++ if (trainer_class != botclass) continue; ++ ++ if (CreatureLocale const* creatureLocale = sObjectMgr->GetCreatureLocale(id)) ++ { ++ if (creatureLocale->Name.size() > localeIndex && !creatureLocale->Name[localeIndex].empty()) ++ { ++ botlist.push_back(BotPair(id, creatureLocale->Name[localeIndex])); ++ continue; ++ } ++ } ++ ++ std::string name = itr->second.Name; ++ if (name.empty()) ++ continue; ++ ++ botlist.push_back(BotPair(id, name)); ++ } ++ ++ if (botlist.empty()) ++ { ++ handler->SendSysMessage(LANG_COMMAND_NOCREATUREFOUND); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ botlist.sort(&script_bot_commands::sortbots); ++ ++ for (BotList::const_iterator itr = botlist.begin(); itr != botlist.end(); ++itr) ++ { ++ uint32 id = itr->first; ++ char const* name = itr->second.c_str(); ++ handler->PSendSysMessage(LANG_CREATURE_ENTRY_LIST_CHAT, id, id, name); ++ } ++ ++ return true; ++ } ++ ++ static bool HandleNpcBotDeleteCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* chr = handler->GetSession()->GetPlayer(); ++ Unit* ubot = chr->GetSelectedUnit(); ++ if (!ubot) ++ { ++ handler->SendSysMessage(".npcbot delete"); ++ handler->SendSysMessage("Deletes selected npcbot spawn from world and DB"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* bot = ubot->ToCreature(); ++ if (!bot || !bot->IsNPCBot()) ++ { ++ handler->SendSysMessage("No npcbot selected"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (Player* botowner = bot->GetBotOwner()->ToPlayer()) ++ botowner->GetBotMgr()->RemoveBot(bot->GetGUID(), BOT_REMOVE_DISMISS); ++ ++ uint32 id = bot->GetEntry(); ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP); ++ //"SELECT equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet, equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck ++ //FROM characters_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ PreparedQueryResult res = CharacterDatabase.Query(stmt); ++ ASSERT(res); ++ ++ Field* fields = res->Fetch(); ++ for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) ++ { ++ if (fields[i].GetUInt32()) ++ { ++ handler->PSendSysMessage("%s still has eqipment assigned. Please remove equips before deleting bot!", bot->GetName().c_str()); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ } ++ ++ bot->CombatStop(); ++ bot->DeleteFromDB(); ++ bot->AddObjectToRemoveList(); ++ ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT); ++ //"DELETE FROM characters_npcbot WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, id); ++ CharacterDatabase.Execute(stmt); ++ ++ handler->SendSysMessage("Npcbot successfully deleted."); ++ return true; ++ } ++ ++ static bool HandleNpcBotSpawnCommand(ChatHandler* handler, const char* args) ++ { ++ if (!*args) ++ { ++ handler->SendSysMessage(".npcbot spawn"); ++ handler->SendSysMessage("Adds new npcbot spawn of given entry in world. You can shift-link the npc"); ++ handler->SendSysMessage("Syntax: .npcbot spawn #entry"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ char* charID = handler->extractKeyFromLink((char*)args, "Hcreature_entry"); ++ if (!charID) ++ return false; ++ ++ uint32 id = atoi(charID); ++ ++ CreatureTemplate const* creInfo = sObjectMgr->GetCreatureTemplate(id); ++ ++ if (!creInfo) ++ { ++ handler->PSendSysMessage("creature %u does not exist!", id); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (!(creInfo->flags_extra & CREATURE_FLAG_EXTRA_NPCBOT)) ++ { ++ handler->PSendSysMessage("creature %u is not a npcbot!", id); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_OWNER); ++ //"SELECT owner FROM character_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ PreparedQueryResult res1 = CharacterDatabase.Query(stmt); ++ if (res1) ++ { ++ handler->PSendSysMessage("Npcbot %u already exists in `characters_npcbot` table!", id); ++ handler->SendSysMessage("If you want to replace this bot to new location use '.npc move' command"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_BY_ID); ++ //"SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ PreparedQueryResult res2 = WorldDatabase.Query(stmt); ++ if (res2) ++ { ++ handler->PSendSysMessage("Npcbot %u already exists in `creature` table!", id); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Player* chr = handler->GetSession()->GetPlayer(); ++ ++ if (chr->GetTransport()) ++ { ++ handler->SendSysMessage("Cannot spawn bots on transport!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ float x = chr->GetPositionX(); ++ float y = chr->GetPositionY(); ++ float z = chr->GetPositionZ(); ++ float o = chr->GetOrientation(); ++ Map* map = chr->GetMap(); ++ ++ if (map->Instanceable()) ++ { ++ handler->SendSysMessage("Cannot spawn bots in instances!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* creature = new Creature(); ++ if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) ++ { ++ delete creature; ++ return false; ++ } ++ ++ uint8 roleMask = BOT_ROLE_DPS; ++ ++ uint8 m_class = creature->GetCreatureTemplate()->trainer_class; ++ if (!(m_class == CLASS_WARRIOR || m_class == CLASS_ROGUE || ++ m_class == CLASS_PALADIN || m_class == CLASS_DEATH_KNIGHT || ++ m_class == CLASS_SHAMAN || m_class == BOT_CLASS_BM)) ++ roleMask |= BOT_ROLE_RANGED; ++ if (m_class == CLASS_PRIEST || m_class == CLASS_DRUID || ++ m_class == CLASS_SHAMAN || m_class == CLASS_PALADIN) ++ roleMask |= BOT_ROLE_HEAL; ++ ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT); ++ //"INSERT INTO characters_npcbot (entry, roles) VALUES (?, ?)", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ stmt->setUInt8(1, roleMask); ++ CharacterDatabase.DirectExecute(stmt); ++ ++ creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); ++ ++ uint32 db_guid = creature->GetSpawnId(); ++ if (!creature->LoadBotCreatureFromDB(db_guid, map)) ++ { ++ handler->SendSysMessage("Cannot load npcbot from DB!"); ++ handler->SetSentErrorMessage(true); ++ //return false; ++ delete creature; ++ return false; ++ } ++ ++ sObjectMgr->AddCreatureToGrid(db_guid, sObjectMgr->GetCreatureData(db_guid)); ++ ++ handler->SendSysMessage("Npcbot successfully spawned."); ++ return true; ++ } ++ ++ static bool HandleNpcBotJumpCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* player = handler->GetSession()->GetPlayer(); ++ ObjectGuid sel = player->GetTarget(); ++ if (!sel) ++ return false; ++ ++ Creature* bot = ObjectAccessor::GetCreatureOrPetOrVehicle(*player, sel); ++ if (!bot/* || (!bot->GetIAmABot() && !bot->GetIAmABotsPet())*/) ++ return false; ++ ++ float speedZ = 10.0f; ++ float dist = bot->GetExactDist2d(player->GetPositionX(), player->GetPositionY()); ++ float speedXY = dist; ++ ++ bot->StopMoving(); ++ bot->GetMotionMaster()->Clear(); ++ bot->GetMotionMaster()->MoveJump(*player, speedXY, speedZ); ++ ++ return true; ++ } ++ ++ static bool HandleNpcBotSummonCommand(ChatHandler* handler, const char* args) ++ { ++ if (!*args) ++ return false; ++ ++ char* guidLowstr = strtok((char*)args, " "); ++ uint32 guidLow = 0; ++ ++ if (guidLowstr) ++ guidLow = (uint32)atoi(guidLowstr); ++ ++ if (!guidLow) ++ return false; ++ ++ QueryResult result = WorldDatabase.PQuery("SELECT id FROM creature WHERE guid = %u", guidLow); ++ if (!result) ++ return false; ++ ++ Field* field = result->Fetch(); ++ uint32 id = field[0].GetUInt32(); ++ ++ Player* player = handler->GetSession()->GetPlayer(); ++ ++ /*if (Creature* cre = ObjectAccessor::GetObjectInOrOutOfWorld(ObjectGuid(HIGHGUID_UNIT, id, guidLow), (Creature*)NULL)) ++ { ++ if (cre->GetIAmABot() && !cre->GetBotAI()->GetBotOwnerGuid()) ++ { ++ BotMgr::TeleportBot(cre, player->GetMap(), player); ++ return true; ++ } ++ }*/ ++ ++ return false; ++ } ++ ++ static bool HandleNpcBotInfoCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ if (!owner->GetTarget()) ++ { ++ handler->PSendSysMessage(".npcbot info"); ++ handler->PSendSysMessage("Lists NpcBots count of each class owned by selected player. You can use this on self and your party members"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ Player* master = owner->GetSelectedPlayer(); ++ if (!master || (owner->GetGroup() ? !owner->GetGroup()->IsMember(master->GetGUID()) : master->GetGUID() != owner->GetGUID())) ++ { ++ handler->PSendSysMessage("You should select self or one of your party members."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ if (!master->HaveBot()) ++ { ++ handler->PSendSysMessage("%s has no NpcBots!", master->GetName().c_str()); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ handler->PSendSysMessage("Listing NpcBots for %s", master->GetName().c_str()); ++ handler->PSendSysMessage("Owned NpcBots: %u", master->GetNpcBotsCount()); ++ for (uint8 i = BOT_CLASS_WARRIOR; i != BOT_CLASS_END; ++i) ++ { ++ uint8 count = 0; ++ uint8 alivecount = 0; ++ BotMap const* map = master->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ if (Creature* cre = itr->second) ++ { ++ if (cre->GetBotClass() == i) ++ { ++ ++count; ++ if (cre->IsAlive()) ++ ++alivecount; ++ } ++ } ++ } ++ if (count == 0) ++ continue; ++ ++ char const* bclass; ++ if (i >= BOT_CLASS_EX_START) ++ { ++ ASSERT(count == 1); ++ ++ switch (i) ++ { ++ //|cffe6cc80|hxxx|h|r ++ case BOT_CLASS_BM: bclass = "|cff9d9d9d|hHas Blademaster!|h|r"; break; ++ default: bclass = "wtf"; break; ++ } ++ handler->PSendSysMessage("%s (alive: %s)", bclass, (alivecount ? "yes" : "no")); ++ } ++ else ++ { ++ switch (i) ++ { ++ case BOT_CLASS_WARRIOR: bclass = "Warriors"; break; ++ case BOT_CLASS_PALADIN: bclass = "Paladins"; break; ++ case BOT_CLASS_MAGE: bclass = "Mages"; break; ++ case BOT_CLASS_PRIEST: bclass = "Priests"; break; ++ case BOT_CLASS_WARLOCK: bclass = "Warlocks"; break; ++ case BOT_CLASS_DRUID: bclass = "Druids"; break; ++ case BOT_CLASS_DEATH_KNIGHT: bclass = "Death Knights"; break; ++ case BOT_CLASS_ROGUE: bclass = "Rogues"; break; ++ case BOT_CLASS_SHAMAN: bclass = "Shamans"; break; ++ case BOT_CLASS_HUNTER: bclass = "Hunters"; break; ++ default: bclass = "Unknown Class"; break; ++ } ++ handler->PSendSysMessage("%s: %u (alive: %u)", bclass, count, alivecount); ++ } ++ } ++ return true; ++ } ++ ++ static bool HandleNpcBotDistanceCommand(ChatHandler* handler, const char* args) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ if (!*args) ++ { ++ if (owner->HaveBot()) ++ { ++ handler->PSendSysMessage("bot follow distance is %u", owner->GetBotFollowDist()); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ handler->PSendSysMessage(".npcbot distance"); ++ handler->PSendSysMessage("Sets 'distance to target' at which bots will follow you"); ++ handler->PSendSysMessage("if set to 0, bots will not attack anything unless you point them"); ++ handler->PSendSysMessage("min: 0, max: 75"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ char* distance = strtok((char*)args, " "); ++ int8 dist = -1; ++ ++ if (distance) ++ dist = (int8)atoi(distance); ++ ++ if (dist >= 0 && dist <= 75) ++ { ++ owner->SetBotFollowDist(dist); ++ if (!owner->IsInCombat() && owner->HaveBot()) ++ owner->GetBotMgr()->SendBotCommandState(COMMAND_FOLLOW); ++ ++ Group* gr = owner->GetGroup(); ++ if (gr && owner->GetMap()->Instanceable() && /*gr->isRaidGroup() &&*/ gr->IsLeader(owner->GetGUID())) ++ { ++ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next()) ++ { ++ Player* pl = itr->GetSource(); ++ if (pl && pl->IsInWorld() && pl->GetMap() == owner->GetMap()) ++ { ++ pl->SetBotFollowDist(dist); ++ if (!pl->IsInCombat() && pl->HaveBot()) ++ pl->GetBotMgr()->SendBotCommandState(COMMAND_FOLLOW); ++ } ++ } ++ } ++ handler->PSendSysMessage("bot follow distance set to %u", dist); ++ return true; ++ } ++ handler->SendSysMessage("follow distance should be between 0 and 75"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ static bool HandleNpcBotCommandCommand(ChatHandler* handler, const char* args) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ if (!*args) ++ { ++ handler->PSendSysMessage(".npcbot command "); ++ handler->PSendSysMessage("Forces npcbots to either follow you or hold position."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ char* command = strtok((char*)args, " "); ++ int8 state = -1; ++ if (!strncmp(command, "s", 2) || !strncmp(command, "st", 3) || !strncmp(command, "stay", 5) || !strncmp(command, "stand", 6)) ++ state = COMMAND_STAY; ++ else if (!strncmp(command, "f", 2) || !strncmp(command, "follow", 7) || !strncmp(command, "fol", 4) || !strncmp(command, "fo", 3)) ++ state = COMMAND_FOLLOW; ++ if (state >= 0 && owner->HaveBot()) ++ { ++ owner->GetBotMgr()->SendBotCommandState(CommandStates(state)); ++ return true; ++ } ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ static bool HandleNpcBotRemoveCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ Unit* u = owner->GetSelectedUnit(); ++ if (!u) ++ { ++ handler->PSendSysMessage(".npcbot remove"); ++ handler->PSendSysMessage("Frees selected npcbot from it's owner. Select player to remove all npcbots"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Player* master = u->ToPlayer(); ++ if (master) ++ { ++ if (master->HaveBot()) ++ { ++ master->RemoveAllBots(BOT_REMOVE_DISMISS); ++ ++ if (!master->HaveBot()) ++ { ++ handler->PSendSysMessage("Npcbots were successfully removed"); ++ handler->SetSentErrorMessage(true); ++ return true; ++ } ++ handler->PSendSysMessage("Some npcbots were not removed!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ handler->PSendSysMessage("Npcbots are not found!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* cre = u->ToCreature(); ++ if (cre && cre->GetIAmABot() && !cre->IsFreeBot()) ++ { ++ master = cre->GetBotOwner(); ++ master->GetBotMgr()->RemoveBot(cre->GetGUID(), BOT_REMOVE_DISMISS); ++ if (master->GetBotMgr()->GetBot(cre->GetGUID()) == NULL) ++ { ++ handler->PSendSysMessage("NpcBot successfully removed"); ++ handler->SetSentErrorMessage(true); ++ return true; ++ } ++ handler->PSendSysMessage("NpcBot was NOT removed for some stupid reason!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ handler->PSendSysMessage("You must select player or controlled npcbot"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ static bool HandleNpcBotResetCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ Player* master = NULL; ++ bool all = false; ++ ObjectGuid guid = owner->GetTarget(); ++ if (!guid) ++ { ++ handler->PSendSysMessage(".npcbot reset"); ++ handler->PSendSysMessage("Reset selected npcbot, or all npcbots if used on self"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ if (guid.IsPlayer()) ++ { ++ master = owner; ++ all = true; ++ } ++ else if (guid.IsCreature()) ++ { ++ if (Creature* cre = ObjectAccessor::GetCreature(*owner, guid)) ++ master = cre->GetBotOwner(); ++ } ++ if (master && master->GetGUID() == owner->GetGUID()) ++ { ++ if (!master->HaveBot()) ++ { ++ handler->PSendSysMessage("Npcbots are not found!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ if (all) ++ master->RemoveAllBots(BOT_REMOVE_DISMISS); ++ else ++ master->GetBotMgr()->RemoveBot(guid, BOT_REMOVE_DISMISS); ++ handler->SetSentErrorMessage(true); ++ return true; ++ } ++ handler->PSendSysMessage(".npcbot reset"); ++ handler->PSendSysMessage("Reset selected npcbot. Cannot be used in combat"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ static bool HandleNpcBotReviveCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ Unit* u = owner->GetSelectedUnit(); ++ if (!u) ++ { ++ handler->SendSysMessage(".npcbot revive"); ++ handler->SendSysMessage("Revives selected npcbot. If player is selected, revives all selected player's npcbots"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ if (Player* master = u->ToPlayer()) ++ { ++ if (!master->HaveBot()) ++ { ++ handler->PSendSysMessage("%s has no npcbots!", master->GetName().c_str()); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ master->GetBotMgr()->ReviveAllBots(); ++ handler->SendSysMessage("Npcbots revived."); ++ return true; ++ } ++ else if (Creature* bot = u->ToCreature()) ++ { ++ if (bot->GetBotAI()) ++ { ++ BotMgr::ReviveBot(bot); ++ handler->PSendSysMessage("%s revived.", bot->GetName().c_str()); ++ return true; ++ } ++ } ++ ++ handler->SendSysMessage("You must select player or npcbot."); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ static bool HandleNpcBotAddCommand(ChatHandler* handler, const char* /*args*/) ++ { ++ Player* owner = handler->GetSession()->GetPlayer(); ++ Unit* cre = owner->GetSelectedUnit(); ++ ++ if (!cre || cre->GetTypeId() != TYPEID_UNIT) ++ { ++ handler->SendSysMessage(".npcbot add"); ++ handler->SendSysMessage("Allows to hire selected uncontrolled bot, bypassing price condition"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* bot = cre->ToCreature(); ++ if (!bot || !bot->GetIAmABot() || bot->GetBotAI()->GetBotOwnerGuid()) ++ { ++ handler->SendSysMessage("You must select uncontrolled npcbot"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ BotMgr* mgr = owner->GetBotMgr(); ++ if (!mgr) ++ mgr = new BotMgr(owner); ++ ++ if (mgr->AddBot(bot) == BOT_ADD_SUCCESS) ++ { ++ handler->PSendSysMessage("%s is now your npcbot", bot->GetName().c_str()); ++ return true; ++ } ++ ++ handler->PSendSysMessage("NpcBot is NOT created for some reason!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++}; ++ ++void AddSC_script_bot_commands() ++{ ++ new script_bot_commands(); ++} +diff --git a/src/server/game/AI/NpcBots/botmgr.cpp b/src/server/game/AI/NpcBots/botmgr.cpp +new file mode 100644 +index 0000000..7cccca0 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/botmgr.cpp +@@ -0,0 +1,841 @@ ++#include "bot_ai.h" ++#include "bot_Events.h" ++#include "botmgr.h" ++#include "Chat.h" ++#include "Config.h" ++#include "GroupMgr.h" ++#include "GridNotifiers.h" ++#include "GridNotifiersImpl.h" ++#include "Group.h" ++#include "Language.h" ++#include "MapManager.h" ++#include "ObjectMgr.h" ++#include "Player.h" ++#include "SpellAuras.h" ++/* ++Npc Bot Manager by Graff (onlysuffering@gmail.com) ++Player NpcBots management ++%Complete: ??? ++*/ ++ ++//config ++int8 _basefollowdist; ++uint8 _maxNpcBots; ++uint8 _maxClassNpcBots; ++uint8 _xpReductionNpcBots; ++uint8 _healTargetIconFlags; ++uint32 _npcBotsCost; ++bool _enableNpcBots; ++bool _allowgm; ++bool _enableNpcBotsDungeons; ++bool _enableNpcBotsRaids; ++bool _enableNpcBotsBGs; ++bool _enableNpcBotsArenas; ++bool _enableDungeonFinder; ++bool _limitNpcBotsDungeons; ++bool _limitNpcBotsRaids; ++bool _botPvP; ++float _mult_dmg_melee; ++float _mult_dmg_spell; ++float _mult_healing; ++ ++bool __firstload = true; ++ ++BotMgr::BotMgr(Player* const master) : _owner(master) ++{ ++ //LoadConfig(); already loaded (MapManager.cpp) ++ _followdist = _basefollowdist; ++ ++ master->SetBotMgr(this); ++} ++BotMgr::~BotMgr() { } ++ ++void BotMgr::LoadConfig(bool force) ++{ ++ if (__firstload) ++ __firstload = false; ++ else if (!force) ++ return; ++ ++ _enableNpcBots = sConfigMgr->GetBoolDefault("NpcBot.Enable", true); ++ _allowgm = sConfigMgr->GetBoolDefault("NpcBot.AllowGM", true); ++ _maxNpcBots = sConfigMgr->GetIntDefault("NpcBot.MaxBots", 1); ++ _maxClassNpcBots = sConfigMgr->GetIntDefault("NpcBot.MaxBotsPerClass", 1); ++ _basefollowdist = sConfigMgr->GetIntDefault("NpcBot.BaseFollowDistance", 30); ++ _xpReductionNpcBots = sConfigMgr->GetIntDefault("NpcBot.XpReduction", 0); ++ _healTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.HealTargetIconsMask", 0); ++ _mult_dmg_melee = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Melee", 1.0); ++ _mult_dmg_spell = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Spell", 1.0); ++ _mult_healing = sConfigMgr->GetFloatDefault("NpcBot.Mult.Healing", 1.0); ++ _enableNpcBotsDungeons = sConfigMgr->GetBoolDefault("NpcBot.Enable.Dungeon", true); ++ _enableNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Enable.Raid", false); ++ _enableNpcBotsBGs = sConfigMgr->GetBoolDefault("NpcBot.Enable.BG", false); ++ _enableNpcBotsArenas = sConfigMgr->GetBoolDefault("NpcBot.Enable.Arena", false); ++ _enableDungeonFinder = sConfigMgr->GetBoolDefault("NpcBot.Enable.DungeonFinder", true); ++ _limitNpcBotsDungeons = sConfigMgr->GetBoolDefault("NpcBot.Limit.Dungeon", true); ++ _limitNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Limit.Raid", true); ++ _npcBotsCost = sConfigMgr->GetIntDefault("NpcBot.Cost", 1000000); ++ _botPvP = sConfigMgr->GetBoolDefault("NpcBot.PvP", true); ++ ++ //limits ++ _mult_dmg_melee = std::max(_mult_dmg_melee, 0.1f); ++ _mult_dmg_spell = std::max(_mult_dmg_spell, 0.1f); ++ _mult_healing = std::max(_mult_healing, 0.1f); ++ _mult_dmg_melee = std::min(_mult_dmg_melee, 10.f); ++ _mult_dmg_spell = std::min(_mult_dmg_spell, 10.f); ++ _mult_healing = std::min(_mult_healing, 10.f); ++} ++ ++uint8 BotMgr::GetNpcBotsCount(bool inWorldOnly) const ++{ ++ if (!inWorldOnly) ++ return _bots.size(); ++ ++ //CRITICAL SECTION ++ //inWorldOnly is only for one-shot cases (opcodes, etc.) ++ uint8 count = 0; ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ if (ObjectAccessor::GetCreatureOrPetOrVehicle(*_owner, itr->first)) ++ ++count; ++ return count; ++} ++ ++bool BotMgr::IsNpcBotModEnabled() ++{ ++ return _enableNpcBots; ++} ++ ++bool BotMgr::IsAllowGMModEnabled() ++{ ++ return _allowgm; ++} ++ ++bool BotMgr::IsNpcBotDungeonFinderEnabled() ++{ ++ return _enableDungeonFinder; ++} ++ ++uint8 BotMgr::GetNpcBotXpReduction() ++{ ++ return _xpReductionNpcBots; ++} ++ ++uint8 BotMgr::GetMaxNpcBots() ++{ ++ return _maxNpcBots <= 4 ? _maxNpcBots : 4; ++} ++ ++bool BotMgr::LimitBots(Map const* map) ++{ ++ if (_limitNpcBotsDungeons && map->IsNonRaidDungeon()) ++ return true; ++ if (_limitNpcBotsRaids && map->IsRaid()) ++ return true; ++ ++ return false; ++} ++ ++void BotMgr::Update(uint32 diff) ++{ ++ //remove temp bots from bot map before updating it ++ while (!_removeList.empty()) ++ { ++ GuidList::iterator itr = _removeList.begin(); ++ ++ BotMap::iterator bitr = _bots.find(*itr); ++ ASSERT(bitr != _bots.end()); ++ _bots.erase(bitr); ++ ++ _removeList.erase(itr); ++ continue; ++ } ++ ++ if (!HaveBot()) ++ return; ++ ++ //uint64 guid; ++ Creature* bot; ++ bot_minion_ai* ai; ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ //guid = itr->first; ++ bot = itr->second; ++ ai = bot->GetBotMinionAI(); ++ ++ if (ai->IAmFree()) ++ continue; ++ ++ if (!bot->IsInWorld()) ++ { ++ ai->CommonTimers(diff); ++ continue; ++ } ++ ++ if (!_isPartyInCombat()) ++ ai->UpdateReviveTimer(diff); ++ ++ bot->SetCanUpdate(true); ++ bot->IsAIEnabled = true; ++ ++ if (ai->GetReviveTimer() <= diff) ++ { ++ if (bot->isDead() && _owner->IsAlive() && !_owner->IsInCombat() && !_owner->IsBeingTeleported() && !_owner->InArena() && ++ !_owner->IsInFlight() && !_owner->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH) && ++ !_owner->HasInvisibilityAura() && !_owner->HasStealthAura()) ++ { ++ _reviveBot(bot); ++ continue; ++ } ++ ++ ai->SetReviveTimer(urand(1000, 5000)); ++ } ++ ++ if (_owner->IsAlive() && bot->IsAlive() && !ai->IsTempBot() && !ai->IsDuringTeleport() && ++ (RestrictBots(bot, false) || ++ bot->GetMap() != _owner->GetMap() || ++ (bot->GetBotCommandState() != COMMAND_STAY && _owner->GetDistance(bot) > 100.f))) // _owner->GetDistance(bot) > SIZE_OF_GRIDS ++ { ++ _owner->m_Controlled.erase(bot); ++ TeleportBot(bot, _owner->GetMap(), _owner); ++ continue; ++ } ++ ++ bot->Update(diff); ++ bot->SetCanUpdate(false); ++ ++ if (Creature* pet = bot->GetBotsPet()) ++ { ++ pet->SetCanUpdate(true); ++ pet->IsAIEnabled = true; ++ pet->Update(diff); ++ pet->SetCanUpdate(false); ++ } ++ } ++} ++ ++bool BotMgr::RestrictBots(Creature const* bot, bool add) const ++{ ++ if (!_owner->FindMap()) ++ return true; ++ ++ if (_owner->IsInFlight()) ++ return true; ++ ++ Map const* currMap = _owner->GetMap(); ++ ++ if ((!_enableNpcBotsBGs && currMap->IsBattleground()) || ++ (!_enableNpcBotsArenas && currMap->IsBattleArena()) || ++ (!_enableNpcBotsDungeons && currMap->IsNonRaidDungeon()) || ++ (!_enableNpcBotsRaids && currMap->IsRaid())) ++ return true; ++ ++ if (LimitBots(currMap)) ++ { ++ //if bot is not in instance group - deny (only if trying to teleport to instance) ++ if (add) ++ if (!_owner->GetGroup() || !_owner->GetGroup()->IsMember(bot->GetGUID())) ++ return true; ++ ++ InstanceMap const* map = currMap->ToInstanceMap(); ++ uint32 count = map->GetPlayersCountExceptGMs(); ++ if (count + uint8(add) > map->GetMaxPlayers()) ++ return true; ++ } ++ ++ return false; ++} ++ ++void BotMgr::_reviveBot(Creature* bot) ++{ ++ if (bot->IsAlive()) ++ return; ++ ++ if (!bot->GetBotAI()->IAmFree()) ++ bot->Relocate(bot->GetBotOwner()); ++ ++ bot->SetUInt32Value(UNIT_NPC_FLAGS, bot->GetCreatureTemplate()->npcflag); ++ bot->ClearUnitState(uint32(UNIT_STATE_ALL_STATE)); ++ bot->setDeathState(ALIVE); ++ //bot->GetBotAI()->Reset(); ++ bot->SetBotShouldUpdateStats(); ++ ++ bot->SetHealth(bot->GetMaxHealth() / 6); //~15% of max health ++ if (bot->getPowerType() == POWER_MANA) ++ bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA) / 5); //20% of max mana ++ ++ if (!bot->GetBotAI()->IAmFree()) ++ bot->SetBotCommandState(COMMAND_FOLLOW, true); ++} ++ ++Creature* BotMgr::GetBot(ObjectGuid guid) const ++{ ++ BotMap::const_iterator itr = _bots.find(guid); ++ return itr != _bots.end() ? itr->second : NULL; ++} ++ ++bool BotMgr::_isPartyInCombat() const ++{ ++ if (_owner->IsInCombat()) ++ return true; ++ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ if (itr->second->IsInCombat()) ++ return true; ++ if (Creature* pet = itr->second->GetBotsPet()) ++ if (pet->IsInCombat()) ++ return true; ++ } ++ ++ return false; ++} ++ ++void BotMgr::OnTeleportFar(uint32 mapId, float x, float y, float z, float ori) ++{ ++ Map* newMap = sMapMgr->CreateBaseMap(mapId); ++ Creature* bot; ++ Position pos; ++ pos.Relocate(x, y, z, ori); ++ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ bot = itr->second; ++ ASSERT(bot && "BotMgr::OnTeleportFar(): bot does not exist!!!"); ++ ++ if (bot->IsTempBot()) ++ continue; ++ ++ _owner->m_Controlled.erase(bot); ++ TeleportBot(bot, newMap, &pos); ++ } ++} ++ ++void BotMgr::_teleportBot(Creature* bot, Map* newMap, float x, float y, float z, float ori) ++{ ++ ASSERT(bot->GetBotAI()); ++ bot->GetBotAI()->AbortTeleport(); ++ ++ bot->SetBotsPetDied(); ++ bot->GetBotAI()->UnsummonAll(); ++ bot->KillEvents(true); ++ ++ if (bot->IsInWorld()) ++ { ++ //bot->Whisper("teleport...", LANG_UNIVERSAL, bot->GetBotAI()->GetBotOwnerGuid()); ++ bot->CastSpell(bot, COSMETIC_TELEPORT_EFFECT, true); ++ } ++ ++ bot->IsAIEnabled = false; ++ //UnitAI* oldAI = bot->GetAI(); ++ //bot->SetAI(NULL); ++ ++ //if (bot->IsFreeBot() || bot->GetBotOwner()->GetSession()->isLogingOut()) ++ //{ ++ // bot->FarTeleportTo(newMap, x, y, z, ori); ++ ++ // //bot->SetAI(oldAI); ++ // bot->IsAIEnabled = true; ++ // return; ++ //} ++ ++ ////start Unit::CleanupBeforeRemoveFromMap() ++ if (bot->IsInWorld()) ++ bot->RemoveFromWorld(); ++ ++ ASSERT(bot->GetGUID()); ++ ++ // A unit may be in removelist and not in world, but it is still in grid ++ // and may have some references during delete ++ //RemoveAllAuras(); ++ bot->RemoveAllGameObjects(); ++ ++ //if (finalCleanup) ++ // m_cleanupDone = true; ++ ++ bot->m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList ++ bot->CombatStop(); ++ bot->ClearComboPointHolders(); ++ //bot->DeleteThreatList(); ++ bot->getHostileRefManager().setOnlineOfflineState(false); ++ //bot->GetMotionMaster()->Clear(false); // remove different non-standard movement generators. ++ //end Unit::CleanupBeforeRemoveFromMap() ++ ++ //bot->CleanupBeforeRemoveFromMap(false); ++ ++ bot->BotStopMovement(); ++ ++ if (Map* mymap = bot->FindMap()) ++ mymap->RemoveFromMap(bot, false); ++ ++ if (bot->IsFreeBot()/* || bot->GetBotOwner()->GetSession()->isLogingOut()*/) ++ { ++ //bot->FarTeleportTo(newMap, x, y, z, ori); ++ ++ //Creature::FarTeleportTo() ++ //{ ++ //CleanupBeforeRemoveFromMap(false); //done above ++ //GetMap()->RemoveFromMap(this, false); //done above ++ //Relocate(X, Y, Z, O); ++ //SetMap(map); ++ //GetMap()->AddToMap(this); ++ //} ++ bot->Relocate(x, y, z, ori); ++ bot->SetMap(newMap); ++ bot->GetMap()->AddToMap(bot); ++ //end Creature::FarTeleportTo() ++ ++ //bot->SetAI(oldAI); ++ bot->IsAIEnabled = true; ++ return; ++ } ++ ++ //update group member online state ++ if (Group* gr = bot->GetBotOwner()->GetGroup()) ++ if (gr->IsMember(bot->GetGUID())) ++ gr->SendUpdate(); ++ ++ //bot->Relocate(x, y, z); ++ TeleportFinishEvent* finishEvent = new TeleportFinishEvent(bot->GetBotMinionAI()/*, newMap->GetId(), newMap->GetInstanceId(), x, y, z, ori*/); ++ bot->GetBotAI()->GetEvents()->AddEvent(finishEvent, bot->GetBotAI()->GetEvents()->CalculateTime(urand(5000, 8000))); ++ bot->GetBotMinionAI()->SetTeleportFinishEvent(finishEvent); ++} ++ ++void BotMgr::TeleportBot(Creature* bot, Map* newMap, Position* pos) ++{ ++ _teleportBot(bot, newMap, pos->GetPositionX(), pos->GetPositionY(), pos->GetPositionZ(), pos->GetOrientation()); ++} ++ ++void BotMgr::CleanupsBeforeBotDelete(ObjectGuid guid) ++{ ++ BotMap::const_iterator itr = _bots.find(guid); ++ ASSERT(itr != _bots.end() && "Trying to remove bot which does not belong to this botmgr(b)!!"); ++ ASSERT(_owner->IsInWorld() && "Trying to remove bot while not in world(b)!!"); ++ ++ Creature* bot = itr->second; ++ ++ //don't allow removing bots while they are teleporting ++ if (!bot->IsInWorld()) ++ { ++ bot->GetBotAI()->AbortTeleport(); ++ //if (!bot->IsInWorld()) ++ //{ ++ // TC_LOG_ERROR("entities.player", "BotMgr::CleanupsBeforeBotDelete(): Failed to abort %s's teleport! Still out of world!", bot->GetName().c_str()); ++ // ASSERT(false); ++ //} ++ } ++ ++ //if player is logging out group will be disbanded (and bots removed) normal way ++ //WorldSession.cpp:: if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket) ++ if (!_owner->GetSession()->PlayerLogout()) ++ RemoveBotFromGroup(bot); ++ ++ bot->SetBotsPetDied(); ++ bot->OnBotDespawn(NULL); ++ ++ //_owner->SetMinion((Minion*)bot->ToTempSummon(), false); ++ ASSERT(bot->GetOwnerGUID() == _owner->GetGUID()); ++ bot->SetOwnerGUID(ObjectGuid::Empty); ++ _owner->m_Controlled.erase(bot); ++ bot->m_ControlledByPlayer = false; ++ bot->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); ++ bot->SetByteValue(UNIT_FIELD_BYTES_2, 1, 0); ++ bot->SetGuidValue(UNIT_FIELD_CREATEDBY, ObjectGuid::Empty); ++ ++ bot->SetIAmABot(false); ++} ++ ++void BotMgr::_addBotToRemoveList(ObjectGuid guid) ++{ ++ _removeList.push_back(guid); ++} ++ ++void BotMgr::RemoveAllBots(uint8 removetype) ++{ ++ while (!_bots.empty()) ++ RemoveBot(_bots.begin()->first, removetype); ++} ++//Bot is being abandoned by player ++void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype) ++{ ++ BotMap::const_iterator itr = _bots.find(guid); ++ ASSERT(itr != _bots.end() && "Trying to remove bot which does not belong to this botmgr(a)!!"); ++ ASSERT(_owner->IsInWorld() && "Trying to remove bot while not in world(a)!!"); ++ ++ //trying to remove temp bot second time means removing all bots ++ //just erase from bots because already cleaned up ++ for (GuidList::iterator it = _removeList.begin(); it != _removeList.end(); ++it) ++ { ++ if (*it == guid) ++ { ++ _removeList.erase(it); ++ _bots.erase(itr); ++ return; ++ } ++ } ++ ++ Creature* bot = itr->second; ++ CleanupsBeforeBotDelete(guid); ++ ++ ////remove control bar ++ //if (GetNpcBotsCount() <= 1 && !_owner->GetPetGUID() && _owner->m_Controlled.empty()) ++ // _owner->SendRemoveControlBar(); ++ ++ if (bot->GetBotAI()->IsTempBot()) ++ { ++ bot->GetBotAI()->OnBotDespawn(bot); //send to self ++ _addBotToRemoveList(guid); ++ return; ++ } ++ ++ _bots.erase(itr); ++ ++ bot->GetBotAI()->ResetBotAI(removetype == BOT_REMOVE_DISMISS ? BOTAI_RESET_DISMISS : BOTAI_RESET_LOGOUT); ++ ++ bot->setFaction(bot->GetCreatureTemplate()->faction); ++ bot->SetLevel(bot->GetCreatureTemplate()->minlevel); ++ ++ if (removetype == BOT_REMOVE_DISMISS) ++ { ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); ++ //"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, uint32(0)); ++ stmt->setUInt32(1, bot->GetEntry()); ++ CharacterDatabase.Execute(stmt); ++ } ++ ++ bot->AI()->Reset(); ++} ++ ++BotAddResult BotMgr::AddBot(Creature* bot, bool takeMoney) ++{ ++ ASSERT(bot->IsNPCBot()); ++ ASSERT(bot->GetBotAI() != NULL); ++ ++ bool temporary = bot->GetBotAI()->IsTempBot(); ++ ++ if (!_enableNpcBots) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.SendSysMessage("NpcBot system is currently disabled. Please contact administration."); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_DISABLED; ++ } ++ // Is this the best place for this? ++ if (_owner->IsGameMaster()) ++ { ++ if (!_allowgm) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.SendSysMessage("NpcBot system is only available for players. GM's are not permitted to have bots."); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_DISABLED; ++ } ++ } ++ if (GetBot(bot->GetGUID())) ++ return BOT_ADD_ALREADY_HAVE; //Silent error, intended ++ if (!bot->GetBotAI()->IAmFree()) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.PSendSysMessage("%s will not join you, already has master: %s", ++ bot->GetName().c_str(), bot->GetBotOwner()->GetName().c_str()); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_NOT_AVAILABLE; ++ } ++ if (bot->GetBotAI()->IsDuringTeleport()) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.PSendSysMessage("%s cannot join you while about to teleport", bot->GetName().c_str()); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_BUSY; ++ } ++ if (!temporary && _owner->GetNpcBotsCount() >= GetMaxNpcBots()) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.PSendSysMessage("Youre exceed max npcbots (%u)", GetMaxNpcBots()); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_MAX_EXCEED; ++ } ++ if (!temporary && HaveBot() && _maxClassNpcBots) ++ { ++ uint8 count = 0; ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ if (itr->second->GetBotClass() == bot->GetBotClass()) ++ ++count; ++ ++ if (count >= _maxClassNpcBots) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ ch.PSendSysMessage("You cannot have more bots of that class! %u of %u", count, _maxClassNpcBots); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_MAX_CLASS_EXCEED; ++ } ++ } ++ //Map* curMap = _owner->GetMap(); ++ //if (!temporary && LimitBots(curMap)) ++ //{ ++ // InstanceMap* map = curMap->ToInstanceMap(); ++ // uint32 count = map->GetPlayersCountExceptGMs(); ++ // if (count >= map->GetMaxPlayers()) ++ // { ++ // ChatHandler ch(_owner->GetSession()); ++ // ch.PSendSysMessage("Instance players limit exceed (%u of %u)", count, map->GetMaxPlayers()); ++ // //ch.SetSentErrorMessage(true); ++ // return BOT_ADD_INSTANCE_LIMIT; ++ // } ++ //} ++ if (!temporary && takeMoney) ++ { ++ uint32 cost = GetNpcBotCost(_owner->getLevel(), bot); ++ if (!_owner->HasEnoughMoney(cost)) ++ { ++ ChatHandler ch(_owner->GetSession()); ++ std::string str = "You don't have enough money ("; ++ str += GetNpcBotCostStr(_owner->getLevel(), bot); ++ str += ")!"; ++ ch.SendSysMessage(str.c_str()); ++ //ch.SetSentErrorMessage(true); ++ return BOT_ADD_CANT_AFFORD; ++ } ++ ++ _owner->ModifyMoney(-(int32(cost))); ++ } ++ ++ bot->SetBotsPetDied(); ++ bot->GetBotAI()->UnsummonAll(); ++ ++ _bots[bot->GetGUID()] = bot; ++ ++ bot->SetIAmABot(true); ++ bot->SetBotOwner(_owner); ++ bot->SetGuidValue(UNIT_FIELD_CREATEDBY, _owner->GetGUID()); ++ ++ //_owner->SetMinion((Minion*)bot->ToTempSummon(), true); ++ ASSERT(!bot->GetOwnerGUID()); ++ bot->SetOwnerGUID(_owner->GetGUID()); ++ _owner->m_Controlled.insert(bot); ++ bot->m_ControlledByPlayer = true; ++ bot->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); ++ bot->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); ++ ++ //bot->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); ++ //bot->SetByteValue(UNIT_FIELD_BYTES_0, 0, botrace); ++ bot->setFaction(_owner->getFaction()); ++ //bot->SetLevel(_owner->getLevel()); ++ //bot->SetBotClass(botclass); ++ //bot->AIM_Initialize(); ++ //bot->InitBotAI(); ++ //AddBotToGroup(bot); ++ //InitBotEquips(m_bot); ++ ++ ++ //CharmInfo charmInfo(_owner); //owner to not set reaction and other stuff, just empty charm ++ //charmInfo.InitPetActionBar(); ++ ++ //uint8 addlist = 0; ++ //for (uint8 i = 0; i != MAX_SPELL_CHARM; ++i) ++ // if (charmInfo.GetCharmSpell(i)->GetAction()) ++ // ++addlist; ++ ++ //WorldPacket data(SMSG_PET_SPELLS, 8+2+4+4+4*MAX_UNIT_ACTION_BAR_INDEX+1+4*addlist+1); ++ //data << uint64(bot->GetGUID()); ++ //data << uint16(0); ++ //data << uint32(0); ++ ++ //data << uint8(bot->GetReactState()) << uint8(bot->GetBotCommandState()) << uint16(0); ++ ++ //charmInfo.BuildActionBar(&data); ++ ++ //data << uint8(addlist); ++ ++ //if (addlist) ++ //{ ++ // for (uint8 i = 0; i != MAX_SPELL_CHARM; ++i) ++ // { ++ // CharmSpellInfo* cspell = charmInfo.GetCharmSpell(i); ++ // if (cspell->GetAction()) ++ // data << uint32(cspell->packedData); ++ // } ++ //} ++ ++ //data << uint8(0); // cooldowns count ++ ++ //_owner->GetSession()->SendPacket(&data); ++ ++ ++ bot->GetBotAI()->Reset(); ++ ++ if (!bot->GetBotAI()->IsTempBot()) ++ { ++ bot->SetBotCommandState(COMMAND_FOLLOW, true); ++ if (bot->GetBotAI()->HasRole(BOT_ROLE_PARTY)) ++ AddBotToGroup(bot); ++ } ++ ++ if (!temporary) ++ { ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); ++ //"UPDATE characters_npcbot SET owner = ? WHERE entry = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, _owner->GetGUID().GetCounter()); ++ stmt->setUInt32(1, bot->GetEntry()); ++ CharacterDatabase.Execute(stmt); ++ } ++ ++ return BOT_ADD_SUCCESS; ++} ++ ++bool BotMgr::AddBotToGroup(Creature* bot) ++{ ++ ASSERT(GetBot(bot->GetGUID())); ++ ++ Group* gr = _owner->GetGroup(); ++ if (gr) ++ { ++ if (gr->IsMember(bot->GetGUID())) ++ return true; ++ ++ if (gr->IsFull()) ++ { ++ if (!gr->isRaidGroup()) //non-raid group is full ++ gr->ConvertToRaid(); ++ else ++ return false; ++ } ++ } ++ else ++ { ++ gr = new Group; ++ if (!gr->Create(_owner)) ++ { ++ delete gr; ++ return false; ++ } ++ sGroupMgr->AddGroup(gr); ++ } ++ ++ if (gr->AddMember((Player*)bot)) ++ { ++ if (!bot->GetBotAI()->HasRole(BOT_ROLE_PARTY)) ++ bot->GetBotAI()->ToggleRole(BOT_ROLE_PARTY, true); ++ ++ bot->GetBotAI()->CancelBoot(); ++ return true; ++ } ++ ++ return false; ++} ++ ++bool BotMgr::RemoveBotFromGroup(Creature* bot) ++{ ++ ASSERT(GetBot(bot->GetGUID())); ++ ++ Group* gr = _owner->GetGroup(); ++ if (!gr || !gr->IsMember(bot->GetGUID())) ++ return false; ++ ++ gr->RemoveMember(bot->GetGUID()); ++ ++ if (bot->GetBotAI()->HasRole(BOT_ROLE_PARTY)) ++ bot->GetBotAI()->ToggleRole(BOT_ROLE_PARTY, true); ++ ++ Map* map = _owner->FindMap(); ++ gr = _owner->GetGroup(); //check if group has been deleted ++ if (map && map->IsDungeon() && (!gr || !gr->IsMember(bot->GetGUID()))) //make sure bot is removed from group ++ { ++ ChatHandler(_owner->GetSession()).PSendSysMessage("Your bot %s has been removed from your group and will be teleported out of the instance in 60 seconds if not invited back", bot->GetName().c_str()); ++ ++ if (gr && _owner->GetGUID() != gr->GetLeaderGUID()) ++ if (Player* leader = ObjectAccessor::FindPlayer(gr->GetLeaderGUID())) ++ ChatHandler(leader->GetSession()).PSendSysMessage("Bot %s has been removed from your group and will be teleported out of the instance in 60 seconds if not invited back", bot->GetName().c_str()); ++ ++ bot->GetBotAI()->StartBoot(); ++ } ++ ++ return true; ++} ++ ++bool BotMgr::RemoveAllBotsFromGroup(bool newGroup) ++{ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ RemoveBotFromGroup(itr->second); ++ itr->second->GetBotAI()->SetNeedParty(newGroup); ++ } ++ ++ return true; ++} ++ ++uint32 BotMgr::GetNpcBotCost(uint8 level, Creature* creature) ++{ ++ if (!creature || !creature->GetBotAI()) ++ return (_npcBotsCost * level) / DEFAULT_MAX_LEVEL; ++ ++ uint32 base; ++ switch (creature->GetBotAI()->GetBotClass()) ++ { ++ case BOT_CLASS_BM: ++ //base = GetNpcBotCost(level, NULL) * 9; ++ //base = 100000; //10 gold ++ //break; ++ case BOT_CLASS_WARRIOR: ++ case BOT_CLASS_PALADIN: ++ case BOT_CLASS_HUNTER: ++ case BOT_CLASS_ROGUE: ++ case BOT_CLASS_PRIEST: ++ case BOT_CLASS_DEATH_KNIGHT: ++ case BOT_CLASS_SHAMAN: ++ case BOT_CLASS_MAGE: ++ case BOT_CLASS_WARLOCK: ++ case BOT_CLASS_DRUID: ++ default: ++ base = 0; ++ break; ++ } ++ ++ return base + GetNpcBotCost(level, NULL); ++} ++ ++std::string BotMgr::GetNpcBotCostStr(uint8 level, Creature* creature) ++{ ++ std::ostringstream money; ++ ++ if (uint32 cost = GetNpcBotCost(level, creature)) ++ { ++ uint32 gold = uint32(cost / 10000); ++ cost -= (gold * 10000); ++ uint32 silver = uint32(cost / 100); ++ cost -= (silver * 100); ++ ++ if (gold != 0) ++ money << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; ++ if (silver != 0) ++ money << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; ++ if (cost) ++ money << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t"; ++ } ++ ++ return money.str(); ++} ++ ++void BotMgr::ReviveAllBots() ++{ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ _reviveBot(itr->second); ++ } ++} ++ ++void BotMgr::SendBotCommandState(CommandStates state) ++{ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ itr->second->SetBotCommandState(state, true); ++ } ++} ++ ++void BotMgr::SetBotsShouldUpdateStats() ++{ ++ for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) ++ { ++ itr->second->SetBotShouldUpdateStats(); ++ } ++} +diff --git a/src/server/game/AI/NpcBots/botmgr.h b/src/server/game/AI/NpcBots/botmgr.h +new file mode 100644 +index 0000000..b922fc9 +--- /dev/null ++++ b/src/server/game/AI/NpcBots/botmgr.h +@@ -0,0 +1,110 @@ ++#ifndef _BOTMGR_H ++#define _BOTMGR_H ++ ++#include "Common.h" ++ ++class Creature; ++class Map; ++class Player; ++ ++struct Position; ++ ++enum BotAddResult ++{ ++ BOT_ADD_DISABLED = 0x001, ++ BOT_ADD_ALREADY_HAVE = 0x002, ++ BOT_ADD_MAX_EXCEED = 0x004, ++ BOT_ADD_MAX_CLASS_EXCEED = 0x008, ++ BOT_ADD_CANT_AFFORD = 0x010, ++ BOT_ADD_INSTANCE_LIMIT = 0x020, ++ BOT_ADD_BUSY = 0x040, ++ BOT_ADD_NOT_AVAILABLE = 0x080, ++ ++ BOT_ADD_SUCCESS = 0x100, ++ ++ BOT_ADD_ALL_MASK = 0xFFF, ++ ++ BOT_ADD_FATAL = (BOT_ADD_DISABLED | BOT_ADD_CANT_AFFORD | BOT_ADD_MAX_EXCEED | BOT_ADD_MAX_CLASS_EXCEED) ++}; ++ ++enum BotRemoveType ++{ ++ BOT_REMOVE_LOGOUT = 0, ++ BOT_REMOVE_DISMISS = 1, ++ BOT_REMOVE_HIDE = 2, //NYI ++ BOT_REMOVE_UNSUMMON = 3, ++ BOT_REMOVE_BY_DEFAULT = BOT_REMOVE_LOGOUT ++}; ++ ++typedef std::unordered_map BotMap; ++ ++class BotMgr ++{ ++ public: ++ BotMgr(Player* const master); ++ ~BotMgr(); ++ ++ Player* GetOwner() const { return _owner; } ++ ++ BotMap const* GetBotMap() const { return &_bots; } ++ BotMap* GetBotMap() { return &_bots; } ++ ++ static bool IsNpcBotModEnabled(); ++ static bool IsAllowGMModEnabled(); ++ static bool IsNpcBotDungeonFinderEnabled(); ++ ++ static void ReloadConfig() { LoadConfig(true); } ++ static void LoadConfig(bool force = false); ++ ++ void Update(uint32 diff); ++ ++ Creature* GetBot(ObjectGuid guid) const; ++ bool HaveBot() const { return !_bots.empty(); } ++ uint8 GetNpcBotsCount(bool inWorldOnly = false) const; ++ static uint8 GetMaxNpcBots(); ++ static uint8 GetNpcBotXpReduction(); ++ static bool LimitBots(Map const* map); ++ bool RestrictBots(Creature const* bot, bool add) const; ++ ++ static uint32 GetNpcBotCost(uint8 level, Creature* creature); ++ static std::string GetNpcBotCostStr(uint8 level, Creature* creature); ++ ++ void OnTeleportFar(uint32 mapId, float x, float y, float z, float ori = 0.f); ++ void ReviveAllBots(); ++ void SendBotCommandState(CommandStates state); ++ ++ void CleanupsBeforeBotDelete(ObjectGuid guid); ++ void RemoveAllBots(uint8 removetype = BOT_REMOVE_LOGOUT); ++ void RemoveBot(ObjectGuid guid, uint8 removetype = BOT_REMOVE_LOGOUT); ++ BotAddResult AddBot(Creature* bot, bool takeMoney = false); ++ bool AddBotToGroup(Creature* bot); ++ bool RemoveBotFromGroup(Creature* bot); ++ bool RemoveAllBotsFromGroup(bool newGroup); ++ ++ uint8 GetBotFollowDist() const { return _followdist; } ++ void SetBotFollowDist(int8 dist) { _followdist = dist; } ++ ++ void SetBotsShouldUpdateStats(); ++ ++ static void ReviveBot(Creature* bot) { _reviveBot(bot); } ++ ++ //TELEPORT BETWEEN MAPS ++ //CONFIRMEND UNSAFE (charmer,owner) ++ static void TeleportBot(Creature* bot, Map* newMap, Position* pos); ++ ++ private: ++ static void _teleportBot(Creature* bot, Map* newMap, float x, float y, float z, float ori = 0.f); ++ ++ bool _isPartyInCombat() const; ++ static void _reviveBot(Creature* bot); ++ ++ void _addBotToRemoveList(ObjectGuid guid); ++ ++ Player* const _owner; ++ BotMap _bots; ++ GuidList _removeList; ++ ++ int8 _followdist; ++}; ++ ++#endif +diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h +index 3c3e838..6c3375e 100644 +--- a/src/server/game/Accounts/RBAC.h ++++ b/src/server/game/Accounts/RBAC.h +@@ -709,7 +709,17 @@ enum RBACPermissions + // 07 + // 08 + // 09 +- // 10 ++ RBAC_PERM_COMMAND_NPCBOT = 1800, ++ RBAC_PERM_COMMAND_NPCBOT_FACTION = 1801, ++ RBAC_PERM_COMMAND_NPCBOT_OWNER = 1802, ++ RBAC_PERM_COMMAND_NPCBOT_SET = 1803, ++ RBAC_PERM_COMMAND_NPCBOT_ADD = 1804, ++ RBAC_PERM_COMMAND_NPCBOT_REMOVE = 1805, ++ RBAC_PERM_COMMAND_NPCBOT_SPAWN = 1806, ++ RBAC_PERM_COMMAND_NPCBOT_DELETE = 1807, ++ RBAC_PERM_COMMAND_NPCBOT_LOOKUP = 1808, ++ RBAC_PERM_COMMAND_NPCBOT_REVIVE = 1809, ++ RBAC_PERM_COMMAND_NPCBOT_CAST = 1810, + // 11 + // 12 + // 13 +diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt +index aae5b48..542fcc8 100644 +--- a/src/server/game/CMakeLists.txt ++++ b/src/server/game/CMakeLists.txt +@@ -108,6 +108,7 @@ include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/Addons + ${CMAKE_CURRENT_SOURCE_DIR}/AI + ${CMAKE_CURRENT_SOURCE_DIR}/AI/CoreAI ++ ${CMAKE_CURRENT_SOURCE_DIR}/AI/NpcBots + ${CMAKE_CURRENT_SOURCE_DIR}/AI/ScriptedAI + ${CMAKE_CURRENT_SOURCE_DIR}/AI/SmartScripts + ${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouse +diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp +index b0a1777..fd00519 100644 +--- a/src/server/game/DungeonFinding/LFGMgr.cpp ++++ b/src/server/game/DungeonFinding/LFGMgr.cpp +@@ -34,6 +34,12 @@ + #include "WorldSession.h" + #include "InstanceSaveMgr.h" + ++//npcbot ++//#include "bot_ai.h" ++#include "botmgr.h" ++#include "Chat.h" ++//end npcbot ++ + namespace lfg + { + +@@ -436,6 +442,50 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const + joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; + ++memberCount; + players.insert(plrg->GetGUID()); ++ ++ //npcbot ++ if (!plrg->HaveBot()) ++ continue; ++ //add npcbots ++ BotMap const* map = plrg->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ if (!grp->IsMember(itr->first)) ++ continue; ++ ++ //disabled in config ++ if (!BotMgr::IsNpcBotDungeonFinderEnabled()) ++ { ++ (ChatHandler(plrg->GetSession())).SendSysMessage("Using npcbots in Dungeon Finder is restricted. Contact your administration."); ++ ++ if (plrg->GetGUID() != grp->GetLeaderGUID()) ++ if (Player* leader = ObjectAccessor::FindPlayer(grp->GetLeaderGUID())) ++ (ChatHandler(leader->GetSession())).PSendSysMessage("There is a npcbot in your group (owner: %s). Using npcbots in Dungeon Finder is restricted. Contact your administration.", ++ plrg->GetName().c_str()); ++ ++ joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; ++ break; ++ } ++ ++ if (Creature* bot = ObjectAccessor::GetCreatureOrPetOrVehicle(*plrg, itr->first)) ++ { ++ if (!bot->IsTempBot()) ++ { ++ if (joinData.result == LFG_JOIN_OK && ++ !(bot->GetBotRoles() & ( 1 | 2 | 4 ))) //(BOT_ROLE_TANK | BOT_ROLE_DPS | BOT_ROLE_HEAL) ++ { ++ //no valid roles - reqs are not met ++ (ChatHandler(plrg->GetSession())).PSendSysMessage("Your bot %s does not have any viable roles assigned.", bot->GetName().c_str()); ++ joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; ++ continue; ++ } ++ ++ ++memberCount; ++ players.insert(itr->first); ++ } ++ } ++ } ++ //end npcbot + } + } + +@@ -533,6 +583,9 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const + SetState(gguid, LFG_STATE_ROLECHECK); + // Send update to player + LfgUpdateData updateData = LfgUpdateData(LFG_UPDATETYPE_JOIN_QUEUE, dungeons, comment); ++ //npcbot ++ std::map brolemap; ++ //end npcbot + for (GroupReference* itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* plrg = itr->GetSource()) +@@ -546,10 +599,51 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const + if (!debugNames.empty()) + debugNames.append(", "); + debugNames.append(plrg->GetName()); ++ ++ //npcbot ++ if (!plrg->HaveBot()) ++ continue; ++ //add npcbots ++ BotMap const* map = plrg->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) ++ { ++ if (players.find(itr->first) == players.end() || !grp->IsMember(itr->first)) ++ continue; ++ ++ if (Creature* bot = ObjectAccessor::GetCreatureOrPetOrVehicle(*plrg, itr->first)) ++ { ++ if (!bot->IsTempBot()) ++ { ++ ObjectGuid bguid = itr->first; ++ SetState(bguid, LFG_STATE_ROLECHECK); ++ if (!isContinue) ++ SetSelectedDungeons(bguid, dungeons); ++ roleCheck.roles[bguid] = 0; ++ if (!debugNames.empty()) ++ debugNames.append(", "); ++ debugNames.append(bot->GetName()); ++ ++ uint8 broles = 0; ++ if (bot->GetBotRoles() & 1) //BOT_ROLE_TANK ++ broles |= PLAYER_ROLE_TANK; ++ if (bot->GetBotRoles() & 4) //BOT_ROLE_HEAL ++ broles |= PLAYER_ROLE_HEALER; ++ if (bot->GetBotRoles() & 2) //BOT_ROLE_DPS ++ broles |= PLAYER_ROLE_DAMAGE; ++ brolemap[bguid] = broles; ++ //UpdateRoleCheck(gguid, bguid, broles); ++ } ++ } ++ } ++ //end npcbot + } + } + // Update leader role + UpdateRoleCheck(gguid, guid, roles); ++ //npcbot - update bots' roles ++ for (std::map::const_iterator it = brolemap.begin(); it != brolemap.end(); ++it) ++ UpdateRoleCheck(gguid, it->first, it->second); ++ //end npcbot + } + else // Add player to queue + { +@@ -899,6 +993,48 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal) + if (!player) + continue; + ++ //npcbot - handle player's bots ++ if (player->HaveBot()) ++ { ++ Group* group = player->GetGroup(); ++ if (group && group != grp) ++ Player::RemoveFromGroup(group, pguid); ++ ++ if (!grp) ++ { ++ grp = new Group(); ++ grp->ConvertToLFG(); ++ grp->Create(player); ++ ObjectGuid gguid = grp->GetGUID(); ++ SetState(gguid, LFG_STATE_PROPOSAL); ++ sGroupMgr->AddGroup(grp); ++ } ++ else if (group != grp) ++ grp->AddMember(player); ++ ++ grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); ++ ++ // Add the cooldown spell if queued for a random dungeon ++ if (dungeon->type == LFG_TYPE_RANDOM) ++ player->CastSpell(player, LFG_SPELL_DUNGEON_COOLDOWN, false); ++ ++ for (GuidList::const_iterator itr2 = players.begin(); itr2 != players.end(); ++itr2) ++ { ++ ObjectGuid bguid = (*itr2); ++ if (bguid.IsPlayer()) ++ continue; ++ Creature* bot = player->GetBotMgr()->GetBot(bguid); ++ if (!bot) ++ continue; ++ ++ player->GetBotMgr()->AddBotToGroup(bot); ++ grp->SetLfgRoles(bguid, proposal.players.find(bguid)->second.role); ++ } ++ ++ continue; ++ } ++ //end npcbot ++ + Group* group = player->GetGroup(); + if (group && group != grp) + group->RemoveMember(player->GetGUID()); +@@ -967,6 +1103,29 @@ void LFGMgr::UpdateProposal(uint32 proposalId, ObjectGuid guid, bool accept) + if (itProposalPlayer == proposal.players.end()) + return; + ++ //npcbot - player accepted proposal ++ //make its bots accept too ++ if (accept && guid.IsPlayer()) ++ { ++ //if (Player* player = ObjectAccessor::GetObjectInOrOutOfWorld(guid, (Player*)NULL)) ++ //{ ++ //if (player->HaveBot()) ++ //{ ++ for (LfgProposalPlayerContainer::const_iterator itPlayers = proposal.players.begin(); itPlayers != proposal.players.end(); ++itPlayers) ++ { ++ ObjectGuid bguid = itPlayers->first; ++ if (bguid.IsPlayer()) ++ continue; ++ //if (!player->GetBotMgr()->GetBot(bguid)) ++ //continue; ++ ++ UpdateProposal(proposalId, bguid, accept); ++ } ++ //} ++ //} ++ } ++ //end npcbot ++ + LfgProposalPlayer& player = itProposalPlayer->second; + player.accept = LfgAnswer(accept); + +diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp +index 1497eb4..b077f94 100644 +--- a/src/server/game/Entities/Creature/Creature.cpp ++++ b/src/server/game/Entities/Creature/Creature.cpp +@@ -48,6 +48,10 @@ + #include "WorldPacket.h" + #include "Transport.h" + ++//npcbot ++#include "bot_ai.h" ++//end npcbot ++ + TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const + { + TrainerSpellMap::const_iterator itr = spellList.find(spell_id); +@@ -201,6 +205,13 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo( + m_isTempWorldObject = false; + _focusSpell = NULL; + _focusDelay = 0; ++ ++ //bot ++ m_creature_owner = NULL; ++ m_bots_pet = NULL; ++ bot_AI = NULL; ++ m_canUpdate = true; ++ //end bot + } + + Creature::~Creature() +@@ -490,6 +501,15 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/) + + void Creature::Update(uint32 diff) + { ++ //npcbot: update helper ++ if (bot_AI) ++ { ++ if (!m_canUpdate) ++ return; ++ bot_AI->CommonTimers(diff); ++ } ++ //end npcbot ++ + if (IsAIEnabled && TriggerJustRespawned) + { + TriggerJustRespawned = false; +@@ -542,6 +562,11 @@ void Creature::Update(uint32 diff) + if (m_deathState != CORPSE) + break; + ++ //npcbot ++ if (bot_AI) ++ break; ++ //end npcbot ++ + if (m_groupLootTimer && lootingGroupLowGUID) + { + if (m_groupLootTimer <= diff) +@@ -567,6 +592,9 @@ void Creature::Update(uint32 diff) + + // creature can be dead after Unit::Update call + // CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly) ++ //npcbot - skip dead state for bots (handled by AI) ++ if (!bot_AI) ++ //end npcbot + if (!IsAlive()) + break; + +@@ -633,8 +661,15 @@ void Creature::Update(uint32 diff) + m_AI_locked = true; + + i_AI->UpdateAI(diff); ++ //bot ++ if (!bot_AI) ++ //end bot + m_AI_locked = false; + } ++ //npcbot - Update evade mode AI ++ else if (bot_AI) ++ bot_AI->UpdateAI(diff); ++ //end npcbot + + // creature can be dead after UpdateAI call + // CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly) +@@ -687,6 +722,11 @@ void Creature::Update(uint32 diff) + + void Creature::RegenerateMana() + { ++ //npcbot - manual regen enabled ++ if (GetBotAI()) ++ return; ++ //end npcbot ++ + uint32 curValue = GetPower(POWER_MANA); + uint32 maxValue = GetMaxPower(POWER_MANA); + +@@ -722,6 +762,11 @@ void Creature::RegenerateMana() + + void Creature::RegenerateHealth() + { ++ //npcbot - manual regen enabled ++ if (GetBotAI()) ++ return; ++ //end npcbot ++ + if (!isRegeneratingHealth()) + return; + +@@ -1388,6 +1433,23 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad + + m_creatureData = data; + ++ //npcbot ++ if (IsNPCBot()) ++ { ++ //prevent loading npcbot twice (grid unload/load case) ++ if (sWorld->GetMaxPlayerCount() > 0) ++ return false; ++ ++ TC_LOG_INFO("entities.unit", "Creature: loading npcbot %s (id: %u)", GetName().c_str(), GetEntry()); ++ ASSERT(!IsInWorld()); ++ SetByteValue(UNIT_FIELD_BYTES_0, 0, GetCreatureTemplate()->trainer_race); //set race ++ ++ //don't allow removing dead bot's corpse ++ m_corpseDelay = std::numeric_limits::max(); ++ setActive(true); ++ } ++ //end npcbot ++ + if (addToMap && !GetMap()->AddToMap(this)) + return false; + return true; +@@ -1401,6 +1463,11 @@ void Creature::SetCanDualWield(bool value) + + void Creature::LoadEquipment(int8 id, bool force /*= true*/) + { ++ //npcbot: prevent loading equipment for bots ++ if (IsNPCBot()) ++ return; ++ //end npcbot ++ + if (id == 0) + { + if (force) +@@ -1714,6 +1781,9 @@ void Creature::Respawn(bool force) + if (IsAIEnabled) + { + //reset the AI to be sure no dirty or uninitialized values will be used till next tick ++ //npcbot - not for bots ++ if (!bot_AI) ++ //end npcbot + AI()->Reset(); + TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing + } +@@ -2296,6 +2366,16 @@ void Creature::SetInCombatWithZone() + + uint32 Creature::GetShieldBlockValue() const //dunno mob block value + { ++ //npcbot - bot block value is fully calculated into botAI ++ if (bot_AI) ++ { ++ float blockValue = bot_AI->GetShieldBlockValue(); ++ blockValue += GetTotalAuraModifier(SPELL_AURA_MOD_SHIELD_BLOCKVALUE); ++ blockValue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT); ++ return uint32(blockValue); ++ } ++ //end npcbot ++ + return (getLevel()/2 + uint32(GetStat(STAT_STRENGTH)/20)); + } + +@@ -2861,6 +2941,391 @@ void Creature::StartPickPocketRefillTimer() + _pickpocketLootRestore = time(NULL) + sWorld->getIntConfig(CONFIG_CREATURE_PICKPOCKET_REFILL); + } + ++//NPCBOT ++bool Creature::LoadBotCreatureFromDB(uint32 guid, Map* map, bool addToMap) ++{ ++ CreatureData const* data = sObjectMgr->GetCreatureData(guid); ++ ++ if (!data) ++ { ++ TC_LOG_ERROR("sql.sql", "Bot creature (GUID: %u) not found in table `creature`, can't load. ", guid); ++ return false; ++ } ++ ++ m_spawnId = guid; ++ ASSERT(map->GetInstanceId() == 0); ++ if (map->GetCreature(ObjectGuid(HighGuid::Unit, data->id, guid))) ++ return false; ++ ++ if (!Create(guid, map, data->phaseMask, data->id, data->posX, data->posY, data->posZ, data->orientation, data)) ++ return false; ++ ++ //We should set first home position, because then AI calls home movement ++ SetHomePosition(data->posX, data->posY, data->posZ, data->orientation); ++ ++ m_respawnradius = data->spawndist; ++ ++ m_respawnDelay = data->spawntimesecs; ++ m_deathState = ALIVE; ++ ++ m_respawnTime = GetMap()->GetCreatureRespawnTime(m_spawnId); ++ if (m_respawnTime) // respawn on Update ++ { ++ m_deathState = DEAD; ++ if (CanFly()) ++ { ++ float tz = map->GetHeight(GetPhaseMask(), data->posX, data->posY, data->posZ, false); ++ if (data->posZ - tz > 0.1f) ++ Relocate(data->posX, data->posY, tz); ++ } ++ } ++ ++ uint32 curhealth; ++ ++ if (!m_regenHealth) ++ { ++ curhealth = data->curhealth; ++ if (curhealth) ++ { ++ curhealth = uint32(curhealth*_GetHealthMod(GetCreatureTemplate()->rank)); ++ if (curhealth < 1) ++ curhealth = 1; ++ } ++ SetPower(POWER_MANA, data->curmana); ++ } ++ else ++ { ++ curhealth = GetMaxHealth(); ++ SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); ++ } ++ ++ SetHealth(m_deathState == ALIVE ? curhealth : 0); ++ ++ // checked at creature_template loading ++ m_defaultMovementType = MovementGeneratorType(data->movementType); ++ ++ m_creatureData = data; ++ ++ TC_LOG_INFO("entities.unit", "Creature: loading npcbot %s (id: %u)", GetName().c_str(), GetEntry()); ++ ASSERT(!IsInWorld()); ++ SetByteValue(UNIT_FIELD_BYTES_0, 0, GetCreatureTemplate()->trainer_race); //set race ++ ++ //don't allow removing dead bot's corpse ++ m_corpseDelay = std::numeric_limits::max(); ++ setActive(true); ++ ++ if (addToMap && !GetMap()->AddToMap(this)) ++ return false; ++ return true; ++} ++ ++uint8 Creature::GetBotClass() const ++{ ++ return bot_AI ? bot_AI->GetBotClass() : getClass(); ++} ++ ++Player* Creature::GetBotOwner() const ++{ ++ return bot_AI ? bot_AI->GetBotOwner() : NULL; ++} ++ ++void Creature::SetBotOwner(Player* newowner) ++{ ++ if (bot_AI) ++ bot_AI->SetBotOwner(newowner); ++} ++ ++bool Creature::IsNPCBot() const ++{ ++ return GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NPCBOT; ++} ++ ++bool Creature::IsFreeBot() const ++{ ++ return bot_AI && bot_AI->IAmFree(); ++} ++ ++void Creature::SetIAmABot(bool bot) ++{ ++ CombatStop(!bot); ++ DeleteThreatList(); ++ ++ if (bot) ++ { ++ m_unitTypeMask |= (/*UNIT_MASK_SUMMON | */UNIT_MASK_MINION); ++ } ++ else ++ { ++ SetCharmerGUID(ObjectGuid::Empty); ++ bot_AI->UnsummonAll(); ++ m_unitTypeMask &= ~(/*UNIT_MASK_SUMMON | */UNIT_MASK_MINION); ++ SetGuidValue(UNIT_FIELD_CREATEDBY, ObjectGuid::Empty); ++ //if (bot_AI->IsMinionAI()) ++ // SetOwnerGUID(0); ++ } ++} ++ ++void Creature::SetBotsPetDied() ++{ ++ if (!m_bots_pet) ++ return; ++ ++ m_bots_pet->SetCharmerGUID(ObjectGuid::Empty); ++ m_bots_pet->SetCreatureOwner(NULL); ++ //m_bots_pet->GetBotPetAI()->SetCreatureOwner(NULL); ++ GetBotOwner()->SetMinion((Minion*)m_bots_pet, false); ++ m_bots_pet->SetIAmABot(false); ++ m_bots_pet->CleanupsBeforeDelete(); ++ m_bots_pet->AddObjectToRemoveList(); ++ m_bots_pet = NULL; ++} ++ ++uint8 Creature::GetBotRoles() const ++{ ++ return bot_AI ? bot_AI->GetBotRoles() : 0; ++} ++ ++void Creature::SetBotCommandState(CommandStates st, bool force) ++{ ++ if (bot_AI) ++ bot_AI->SetBotCommandState(st, force); ++} ++ ++CommandStates Creature::GetBotCommandState() const ++{ ++ return bot_AI ? bot_AI->GetBotCommandState() : COMMAND_ABANDON; ++} ++//Bot damage mods ++void Creature::ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotDamageMultiplierMelee(damage, damageinfo); ++} ++ ++void Creature::ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotDamageMultiplierMelee(damage, damageinfo, spellInfo, attackType, crit); ++} ++ ++void Creature::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit); ++} ++ ++void Creature::ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotDamageMultiplierHeal(victim, heal, spellInfo, damagetype, stack); ++} ++ ++void Creature::ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotCritMultiplierAll(victim, crit_chance, spellInfo, schoolMask, attackType); ++} ++ ++void Creature::ApplyCreatureSpellCostMods(SpellInfo const* spellInfo, int32& cost) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotSpellCostMods(spellInfo, cost); ++} ++ ++void Creature::ApplyCreatureSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const ++{ ++ if (bot_AI) ++ bot_AI->ApplyBotSpellCastTimeMods(spellInfo, casttime); ++} ++ ++bool Creature::GetIAmABot() const ++{ ++ return bot_AI && bot_AI->IsMinionAI(); ++} ++ ++bool Creature::GetIAmABotsPet() const ++{ ++ return bot_AI && bot_AI->IsPetAI(); ++} ++ ++bot_minion_ai* Creature::GetBotMinionAI() const ++{ ++ return bot_AI ? bot_AI->ToMinionAI() : NULL; ++} ++ ++bot_pet_ai* Creature::GetBotPetAI() const ++{ ++ return bot_AI ? bot_AI->ToPetAI() : NULL; ++} ++ ++void Creature::SetBotShouldUpdateStats() ++{ ++ if (bot_AI) ++ bot_AI->SetShouldUpdateStats(); ++} ++ ++void Creature::OnBotSummon(Creature* summon) ++{ ++ if (bot_AI) ++ bot_AI->OnBotSummon(summon); ++} ++ ++void Creature::OnBotDespawn(Creature* summon) ++{ ++ if (bot_AI) ++ bot_AI->OnBotDespawn(summon); ++} ++ ++void Creature::KillEvents(bool force) ++{ ++ if (bot_AI) ++ bot_AI->KillEvents(force); ++} ++ ++void Creature::BotStopMovement() ++{ ++ if (IsInWorld()) ++ { ++ GetMotionMaster()->Clear(); ++ GetMotionMaster()->MoveIdle(); ++ } ++ StopMoving(); ++ DisableSpline(); ++} ++ ++void Creature::ResetBotAI(uint8 resetType) ++{ ++ if (bot_AI) ++ bot_AI->ResetBotAI(resetType); ++} ++ ++bool Creature::CanParry() const ++{ ++ return bot_AI ? bot_AI->CanParry() : true; ++} ++ ++bool Creature::CanDodge() const ++{ ++ return bot_AI ? bot_AI->CanDodge() : true; ++} ++ ++bool Creature::CanBlock() const ++{ ++ return bot_AI ? bot_AI->CanBlock() : true; ++} ++ ++bool Creature::CanCrit() const ++{ ++ return bot_AI ? bot_AI->CanCrit() : true; ++} ++ ++bool Creature::CanMiss() const ++{ ++ return bot_AI ? bot_AI->CanMiss() : true; ++} ++ ++float Creature::GetCreatureParryChance() const ++{ ++ return bot_AI ? bot_AI->GetBotParryChance() : 5.0f; ++} ++ ++float Creature::GetCreatureDodgeChance() const ++{ ++ return bot_AI ? bot_AI->GetBotDodgeChance() : 5.0f; ++} ++ ++float Creature::GetCreatureBlockChance() const ++{ ++ return bot_AI ? bot_AI->GetBotBlockChance() : 5.0f; ++} ++ ++float Creature::GetCreatureCritChance() const ++{ ++ return bot_AI ? bot_AI->GetBotCritChance() : 0.0f; ++} ++ ++float Creature::GetCreatureMissChance() const ++{ ++ return bot_AI ? bot_AI->GetBotMissChance() : 5.0f; ++} ++ ++float Creature::GetCreatureEvasion() const ++{ ++ return bot_AI ? bot_AI->GetBotEvasion() : 0.0f; ++} ++ ++float Creature::GetCreatureArmorPenetrationCoef() const ++{ ++ return bot_AI ? bot_AI->GetBotArmorPenetrationCoef() : 0.0f; ++} ++ ++float Creature::GetCreatureDamageTakenMod() const ++{ ++ return bot_AI ? bot_AI->GetBotDamageTakenMod() : 1.0f; ++} ++ ++uint32 Creature::GetCreatureExpertise() const ++{ ++ return bot_AI ? bot_AI->GetBotExpertise() : 0; ++} ++ ++uint32 Creature::GetCreatureSpellPenetration() const ++{ ++ return bot_AI ? bot_AI->GetBotSpellPenetration() : 0; ++} ++ ++uint32 Creature::GetCreatureSpellPower() const ++{ ++ return bot_AI ? bot_AI->GetBotSpellPower() : 0; ++} ++ ++bool Creature::IsCreatureImmuneToSpell(SpellInfo const* spellInfo) const ++{ ++ return bot_AI && bot_AI->IsBotImmuneToSpell(spellInfo); ++} ++ ++bool Creature::IsTempBot() const ++{ ++ return bot_AI && bot_AI->IsTempBot(); ++} ++ ++MeleeHitOutcome Creature::BotRollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const ++{ ++ return bot_AI ? bot_AI->BotRollCustomMeleeOutcomeAgainst(victim, attType) : RollMeleeOutcomeAgainst(victim, attType); ++} ++ ++void Creature::CastCreatureItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Spell const* spell) ++{ ++ if (bot_AI) ++ bot_AI->CastBotItemCombatSpell(target, attType, procVictim, procEx, spell); ++} ++ ++void Creature::OnSpellGo(Spell const* spell) ++{ ++ if (bot_AI) ++ bot_AI->OnBotSpellGo(spell); ++} ++ ++void Creature::AddBotSpellCooldown(uint32 spellId, uint32 cooldown) ++{ ++ if (bot_AI) ++ bot_AI->SetSpellCooldown(sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id, cooldown); ++} ++ ++//static ++bool Creature::IsBotCustomSpell(uint32 spellId) ++{ ++ return bot_ai::IsBotCustomSpell(spellId); ++} ++ ++//advanced ++bool Creature::IsQuestBot() const ++{ ++ return ++ m_creatureInfo->Entry >= 71000 && m_creatureInfo->Entry < 72000 && ++ (m_creatureInfo->unit_flags2 & UNIT_FLAG2_ALLOW_ENEMY_INTERACT); ++} ++//END NPCBOT + void Creature::SetTextRepeatId(uint8 textGroup, uint8 id) + { + CreatureTextRepeatIds& repeats = m_textRepeat[textGroup]; +diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h +index 49bd854..e90c03c 100644 +--- a/src/server/game/Entities/Creature/Creature.h ++++ b/src/server/game/Entities/Creature/Creature.h +@@ -37,6 +37,11 @@ class Player; + class SpellInfo; + class WorldSession; + ++// npcbot ++class bot_ai; ++class bot_minion_ai; ++class bot_pet_ai; ++ + enum CreatureFlagsExtra + { + CREATURE_FLAG_EXTRA_INSTANCE_BIND = 0x00000001, // creature kill bind instance with killer and killer's group +@@ -55,6 +60,7 @@ enum CreatureFlagsExtra + CREATURE_FLAG_EXTRA_TAUNT_DIMINISH = 0x00080000, // Taunt is a subject to diminishing returns on this creautre + CREATURE_FLAG_EXTRA_ALL_DIMINISH = 0x00100000, // creature is subject to all diminishing returns as player are + CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ = 0x00200000, // creature does not need to take player damage for kill credit ++ CREATURE_FLAG_EXTRA_NPCBOT = 0x04000000, // custom flag for NPCBots (not confirmed safe) + CREATURE_FLAG_EXTRA_DUNGEON_BOSS = 0x10000000, // creature is a dungeon boss (SET DYNAMICALLY, DO NOT ADD IN DB) + CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING = 0x20000000, // creature ignore pathfinding + CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK = 0x40000000 // creature is immune to knockback effects +@@ -65,7 +71,8 @@ enum CreatureFlagsExtra + CREATURE_FLAG_EXTRA_NO_CRUSH | CREATURE_FLAG_EXTRA_NO_XP_AT_KILL | CREATURE_FLAG_EXTRA_TRIGGER | \ + CREATURE_FLAG_EXTRA_NO_TAUNT | CREATURE_FLAG_EXTRA_WORLDEVENT | CREATURE_FLAG_EXTRA_NO_CRIT | \ + CREATURE_FLAG_EXTRA_NO_SKILLGAIN | CREATURE_FLAG_EXTRA_TAUNT_DIMINISH | CREATURE_FLAG_EXTRA_ALL_DIMINISH | \ +- CREATURE_FLAG_EXTRA_GUARD | CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING | CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ | CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK) ++ CREATURE_FLAG_EXTRA_GUARD | CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING | CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ | \ ++ CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK | CREATURE_FLAG_EXTRA_NPCBOT) + + #define CREATURE_REGEN_INTERVAL 2 * IN_MILLISECONDS + +@@ -682,6 +689,76 @@ class Creature : public Unit, public GridObject, public MapObject + void SetTextRepeatId(uint8 textGroup, uint8 id); + void ClearTextRepeatGroup(uint8 textGroup); + ++ //Bot commands ++ bool LoadBotCreatureFromDB(uint32 guid, Map* map, bool addToMap = true); ++ Player* GetBotOwner() const; ++ void SetBotOwner(Player* newowner); ++ Creature* GetCreatureOwner() const { return m_creature_owner; } ++ void SetCreatureOwner(Creature* newCreOwner) { m_creature_owner = newCreOwner; } ++ Creature* GetBotsPet() const { return m_bots_pet; } ++ void SetBotsPetDied(); ++ void SetBotsPet(Creature* newpet) { /*ASSERT (!m_bots_pet);*/ m_bots_pet = newpet; } ++ bool IsNPCBot() const; ++ bool IsFreeBot() const; ++ void SetIAmABot(bool bot = true); ++ bool GetIAmABot() const; ++ bool GetIAmABotsPet() const; ++ uint8 GetBotClass() const; ++ uint8 GetBotRoles() const; ++ bot_ai* GetBotAI() const { return bot_AI; } ++ bot_minion_ai* GetBotMinionAI() const; ++ bot_pet_ai* GetBotPetAI() const; ++ void SetBotAI(bot_ai* ai) { bot_AI = ai; } ++ void SetBotCommandState(CommandStates st, bool force = false); ++ CommandStates GetBotCommandState() const; ++ void ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const; ++ void ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const; ++ void ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const; ++ void ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const; ++ void ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const; ++ void ApplyCreatureSpellCostMods(SpellInfo const* spellInfo, int32& cost) const; ++ void ApplyCreatureSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const; ++ void SetBotShouldUpdateStats(); ++ void OnBotSummon(Creature* summon); ++ void OnBotDespawn(Creature* summon); ++ void SetCanUpdate(bool can) { m_canUpdate = can; } ++ void KillEvents(bool force); ++ void BotStopMovement(); ++ void ResetBotAI(uint8 resetType = 0); ++ ++ bool CanParry() const; ++ bool CanDodge() const; ++ bool CanBlock() const; ++ bool CanCrit() const; ++ bool CanMiss() const; ++ ++ float GetCreatureParryChance() const; ++ float GetCreatureDodgeChance() const; ++ float GetCreatureBlockChance() const; ++ float GetCreatureCritChance() const; ++ float GetCreatureMissChance() const; ++ float GetCreatureEvasion() const; ++ float GetCreatureArmorPenetrationCoef() const; ++ float GetCreatureDamageTakenMod() const; ++ uint32 GetCreatureExpertise() const; ++ uint32 GetCreatureSpellPenetration() const; ++ uint32 GetCreatureSpellPower() const; ++ ++ bool IsCreatureImmuneToSpell(SpellInfo const* spellInfo) const; ++ bool IsTempBot() const; ++ ++ MeleeHitOutcome BotRollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const; ++ ++ void CastCreatureItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Spell const* spell = NULL); ++ ++ void OnSpellGo(Spell const* spell); ++ void AddBotSpellCooldown(uint32 spellId, uint32 cooldown); ++ ++ static bool IsBotCustomSpell(uint32 spellId); ++ //advanced ++ bool IsQuestBot() const; ++ //End Bot commands ++ + protected: + bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, CreatureData const* data = nullptr, uint32 vehId = 0); + bool InitEntry(uint32 entry, CreatureData const* data = nullptr); +@@ -737,6 +814,13 @@ class Creature : public Unit, public GridObject, public MapObject + bool CanAlwaysSee(WorldObject const* obj) const override; + + private: ++ //bot system ++ Creature* m_creature_owner; ++ Creature* m_bots_pet; ++ bot_ai* bot_AI; ++ bool m_canUpdate; ++ //end bot system ++ + void ForcedDespawn(uint32 timeMSToDespawn = 0); + bool CheckNoGrayAggroConfig(uint32 playerLevel, uint32 creatureLevel) const; // No aggro from gray creatures + +diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp +index 8bf3a1e..c4dcf16 100644 +--- a/src/server/game/Entities/Creature/TemporarySummon.cpp ++++ b/src/server/game/Entities/Creature/TemporarySummon.cpp +@@ -254,6 +254,16 @@ void TempSummon::UnSummon(uint32 msTime) + if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled) + owner->ToCreature()->AI()->SummonedCreatureDespawn(this); + ++ //npcbot ++ if (GetIAmABot() || GetIAmABotsPet()) ++ { ++ //TC_LOG_ERROR("entities.player", "TempSummon::UnSummon(): Trying to unsummon Bot %s (guidLow: %u owner: %s)", GetName().c_str(), GetGUID().GetCounter(), GetBotOwner()->GetName().c_str()); ++ if (IsTempBot()) ++ AI()->JustDied(NULL); ++ return; ++ } ++ //end npcbots ++ + AddObjectToRemoveList(); + } + +diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp +index 4877ff0..96d3510 100644 +--- a/src/server/game/Entities/Object/Object.cpp ++++ b/src/server/game/Entities/Object/Object.cpp +@@ -1032,6 +1032,11 @@ void WorldObject::setActive(bool on) + if (GetTypeId() == TYPEID_PLAYER) + return; + ++ //bot ++ if (on == false && GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBot()) ++ return; ++ //end bot ++ + m_isActive = on; + + if (!IsInWorld()) +@@ -1857,6 +1862,11 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert + summon = new Puppet(properties, summoner); + break; + case UNIT_MASK_TOTEM: ++ //npcbot: totem emul step 1 ++ if (summoner && summoner->GetTypeId() == TYPEID_UNIT && summoner->ToCreature()->GetIAmABot()) ++ summon = new Totem(properties, summoner->ToCreature()->GetBotOwner()); ++ else ++ //end npcbot + summon = new Totem(properties, summoner); + break; + case UNIT_MASK_MINION: +@@ -1878,6 +1888,12 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert + AddToMap(summon->ToCreature()); + summon->InitSummon(); + ++ //npcbot: totem emul step 2 ++ //if (mask == UNIT_MASK_TOTEM) ++ if (summoner && summoner->GetTypeId() == TYPEID_UNIT && summoner->ToCreature()->GetIAmABot()) ++ summoner->ToCreature()->OnBotSummon(summon); ++ //end npcbot ++ + //ObjectAccessor::UpdateObjectVisibility(summon); + + return summon; +diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp +index 7ddb8f4..1e7b2899 100644 +--- a/src/server/game/Entities/Player/KillRewarder.cpp ++++ b/src/server/game/Entities/Player/KillRewarder.cpp +@@ -25,6 +25,9 @@ + #include "InstanceScript.h" + #include "Pet.h" + #include "Player.h" ++//npcbot ++#include "botmgr.h" ++//end npcbot + + // == KillRewarder ==================================================== + // KillRewarder encapsulates logic of rewarding player upon kill with: +@@ -154,6 +157,17 @@ inline void KillRewarder::_RewardXP(Player* player, float rate) + for (auto const& aura : player->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT)) + AddPct(xp, aura->GetAmount()); + ++ //npcbot 4.2.2.1. Apply NpcBot XP reduction ++ if (player->GetNpcBotsCount() > 1) ++ { ++ if (uint8 xp_reduction = BotMgr::GetNpcBotXpReduction()) ++ { ++ uint32 ratePct = std::max(100 - ((player->GetNpcBotsCount() - 1) * xp_reduction), 10); ++ xp = xp * ratePct / 100; ++ } ++ } ++ //end npcbot ++ + // 4.2.3. Give XP to player. + player->GiveXP(xp, _victim, _groupRate); + if (Pet* pet = player->GetPet()) +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 66c9cff..074dddd 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -89,7 +89,9 @@ + // 64 + // 65 + // 66 +-// 67 ++//npcbot ++#include "botmgr.h" ++//end npcbot + // 68 + // 69 + // 70 +@@ -545,6 +547,10 @@ Player::Player(WorldSession* session): Unit(true) + m_timeSyncClient = 0; + m_timeSyncServer = 0; + ++ /////////////// Bot System ////////////////// ++ _botMgr = NULL; ++ ///////////// End Bot System //////////////// ++ + for (uint8 i = 0; i < MAX_POWERS; ++i) + m_powerFraction[i] = 0; + +@@ -601,6 +607,14 @@ Player::~Player() + delete m_achievementMgr; + delete m_reputationMgr; + ++ //npcbot ++ if (_botMgr) ++ { ++ delete _botMgr; ++ _botMgr = NULL; ++ } ++ //end npcbot ++ + sWorld->DecreasePlayerCount(); + } + +@@ -1573,7 +1587,10 @@ void Player::Update(uint32 p_time) + // 88 + // 89 + // 90 +- // 91 ++ //NpcBot mod: Update ++ if (_botMgr) ++ _botMgr->Update(p_time); ++ //end Npcbot + // 92 + // 93 + // 94 +@@ -2016,6 +2033,11 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati + if (pet) + UnsummonPetTemporaryIfAny(); + ++ //bot: teleport npcbots ++ if (HaveBot()) ++ _botMgr->OnTeleportFar(mapid, x, y, z, orientation); ++ //end bot ++ + // remove all dyn objects + RemoveAllDynObjects(); + +@@ -2211,6 +2233,38 @@ bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) co + return Unit::IsImmunedToSpellEffect(spellInfo, index); + } + ++//BOT ++bool Player::HaveBot() const ++{ ++ return _botMgr && _botMgr->HaveBot(); ++} ++ ++uint8 Player::GetNpcBotsCount(bool inWorldOnly) const ++{ ++ return HaveBot() ? _botMgr->GetNpcBotsCount(inWorldOnly) : 0; ++} ++ ++uint8 Player::GetBotFollowDist() const ++{ ++ return _botMgr ? _botMgr->GetBotFollowDist() : 30; ++} ++ ++void Player::SetBotFollowDist(int8 dist) ++{ ++ if (_botMgr) _botMgr->SetBotFollowDist(dist); ++} ++ ++void Player::SetBotsShouldUpdateStats() ++{ ++ if (HaveBot()) _botMgr->SetBotsShouldUpdateStats(); ++} ++ ++void Player::RemoveAllBots(uint8 removetype) ++{ ++ if (HaveBot()) _botMgr->RemoveAllBots(removetype); ++} ++//END BOT ++ + void Player::RegenerateAll() + { + //if (m_regenTimer <= 500) +@@ -2486,6 +2540,11 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflag + if (creature->GetCharmerGUID()) + return NULL; + ++ //npcbot ++ if ((creature->IsQuestBot() || creature->IsNPCBot()) && creature->IsWithinDistInMap(this, INTERACTION_DISTANCE)) ++ return creature; ++ //end npcbot ++ + // not unfriendly/hostile + if (creature->GetReactionTo(this) <= REP_UNFRIENDLY) + return NULL; +@@ -2711,6 +2770,42 @@ void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method + if (!group) + return; + ++ if (group) ++ { ++ //npcbot - player is being removed from group - remove bots from that group ++ if (Player* player = ObjectAccessor::FindPlayer(guid)) ++ { ++ if (player->HaveBot()) ++ { ++ uint8 players = 0; ++ Group::MemberSlotList const& members = group->GetMemberSlots(); ++ for (Group::member_citerator itr = members.begin(); itr != members.end(); ++itr) ++ { ++ if (ObjectAccessor::FindPlayer(itr->guid)) ++ ++players; ++ } ++ ++ //remove npcbots and set up new group if needed ++ player->GetBotMgr()->RemoveAllBotsFromGroup(players > 1); ++ group = player->GetGroup(); ++ if (!group) ++ return; //group has been disbanded ++ } ++ } ++ //npcbot - bot is being removed from group - find master and remove bot through botmap ++ /*else if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(guid, (Creature*)NULL)) ++ { ++ Player* master = bot->GetBotOwner(); ++ if (master && master->GetTypeId() == TYPEID_PLAYER) //check for free bot just in case ++ { ++ master->GetBotMgr()->RemoveBotFromGroup(bot); ++ group = NULL; ++ return; ++ } ++ }*/ ++ } ++ //end npcbot ++ + group->RemoveMember(guid, method, kicker, reason); + } + +@@ -4575,6 +4670,14 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe + + Corpse::DeleteFromDB(playerguid, trans); + ++ //npcbot - erase npcbots ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER_ALL); ++ //"UPDATE characters_npcbot SET owner = ? WHERE owner = ?", CONNECTION_ASYNC ++ stmt->setUInt32(0, uint32(0)); ++ stmt->setUInt32(1, guid); ++ trans->Append(stmt); ++ //end npcbot ++ + CharacterDatabase.CommitTransaction(trans); + break; + } +@@ -6869,6 +6972,10 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + } + else + { ++ //npcbot - honor for bots ++ if (!(victim->ToCreature()->GetIAmABot() && victim->ToCreature()->IsFreeBot())) //exclude pets ++ //TODO: honor rate ++ //end npcbot + if (!victim->ToCreature()->IsRacialLeader()) + return false; + +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index 53eae3a..121d90b 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -57,6 +57,10 @@ class UpdateMask; + + struct CharacterCustomizeInfo; + ++// NpcBot mod ++class BotMgr; ++// end NpcBot mod ++ + typedef std::deque PlayerMails; + + #define PLAYER_MAX_SKILLS 127 +@@ -2246,7 +2250,20 @@ class Player : public Unit, public GridObject + // 08 + // 09 + // 10 +- // 11 ++ /*********************************************************/ ++ /*** BOT SYSTEM ***/ ++ /*********************************************************/ ++ void SetBotMgr(BotMgr* mgr) { ASSERT(!_botMgr); _botMgr = mgr; } ++ BotMgr* GetBotMgr() const { return _botMgr; } ++ bool HaveBot() const; ++ uint8 GetNpcBotsCount(bool inWorldOnly = false) const; ++ uint8 GetBotFollowDist() const; ++ void SetBotFollowDist(int8 dist); ++ void SetBotsShouldUpdateStats(); ++ void RemoveAllBots(uint8 removetype = 0); ++ /*********************************************************/ ++ /*** END BOT SYSTEM ***/ ++ /*********************************************************/ + // 12 + // 13 + // 14 +@@ -2517,6 +2534,14 @@ class Player : public Unit, public GridObject + bool m_needsZoneUpdate; + + private: ++ /*********************************************************/ ++ /*** BOT SYSTEM ***/ ++ /*********************************************************/ ++ BotMgr* _botMgr; ++ /*********************************************************/ ++ /*** END BOT SYSTEM ***/ ++ /*********************************************************/ ++ + // internal common parts for CanStore/StoreItem functions + InventoryResult CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemTemplate const* pProto, uint32& count, bool swap, Item* pSrcItem) const; + InventoryResult CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemTemplate const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const; +diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp +index 5e85442..e4778ff 100644 +--- a/src/server/game/Entities/Totem/Totem.cpp ++++ b/src/server/game/Entities/Totem/Totem.cpp +@@ -146,6 +146,13 @@ void Totem::UnSummon(uint32 msTime) + if (IsAlive()) + setDeathState(DEAD); + ++ //npcbot: send SummonedCreatureDespawn() ++ if (GetCreatorGUID().IsCreature()) ++ if (Unit* bot = ObjectAccessor::FindConnectedPlayer(GetCreatorGUID())) ++ if (bot->ToCreature()->GetIAmABot()) ++ bot->ToCreature()->OnBotDespawn(this); ++ //end npcbot ++ + AddObjectToRemoveList(); + } + +diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp +index a8e13a9..14dd4ba 100644 +--- a/src/server/game/Entities/Unit/StatSystem.cpp ++++ b/src/server/game/Entities/Unit/StatSystem.cpp +@@ -216,6 +216,10 @@ bool Player::UpdateAllStats() + RecalculateRating(CR_ARMOR_PENETRATION); + UpdateAllResistances(); + ++ //npcbot - Player::UpdateAllStats() is called on level change - update bots ++ SetBotsShouldUpdateStats(); ++ //end npcbot ++ + return true; + } + +diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp +index 3aed5fd..79ca835 100644 +--- a/src/server/game/Entities/Unit/Unit.cpp ++++ b/src/server/game/Entities/Unit/Unit.cpp +@@ -278,6 +278,13 @@ Unit::Unit(bool isWorldObject) : + + _oldFactionId = 0; + _isWalkingBeforeCharm = false; ++ ++ //npcbot ++ _damageInfo.target = NULL; ++ _delayedTargetGuid.Clear(); ++ _swingDelayTimer = 0; ++ _swingLanded = true; ++ //end npcbot + } + + //////////////////////////////////////////////////////////// +@@ -368,6 +375,29 @@ void Unit::Update(uint32 p_time) + } + } + ++ //npcbot: update combat timer also for npcbots ++ if (IsInCombat() && GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()/* && (!GetVictim() || ToCreature()->IsFreeBot())*/) ++ { ++ if (m_HostileRefManager.isEmpty()) ++ { ++ if (m_CombatTimer <= p_time) ++ ClearInCombat(); ++ else ++ m_CombatTimer -= p_time; ++ } ++ } ++ //npcbot: ++ if (_delayedTargetGuid) ++ { ++ if (_swingLanded) ++ _delayedTargetGuid.Clear(); ++ else if (_swingDelayTimer >= p_time) ++ _swingDelayTimer -= p_time; ++ else ++ ExecuteDelayedSwingHit(); ++ } ++ //end npcbot ++ + // not implemented before 3.0.2 + if (uint32 base_att = getAttackTimer(BASE_ATTACK)) + setAttackTimer(BASE_ATTACK, (p_time >= base_att ? 0 : base_att - p_time)); +@@ -604,6 +634,13 @@ uint32 Unit::DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDam + if (pet && pet->IsAlive()) + pet->AI()->OwnerAttackedBy(this); + ++ // NpcBot mod: also signal owned npcbots ++ for (ControlList::const_iterator itr = victim->ToPlayer()->m_Controlled.begin(); itr != victim->ToPlayer()->m_Controlled.end(); ++itr) ++ if (Creature* cre = (*itr)->ToCreature()) ++ if (cre->IsAIEnabled) ++ cre->AI()->OwnerAttackedBy(this); ++ // End NpcBot ++ + if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) + return 0; + } +@@ -868,6 +905,14 @@ void Unit::CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo + return; + } + ++ //npcbot ++ if (Creature::IsBotCustomSpell(spellInfo->Id) && !(ToCreature() && ToCreature()->GetBotAI())) ++ { ++ TC_LOG_ERROR("entities.unit", "CastSpell: NpcBot system custom spell %u by caster: %s %u), aborted. Please report", spellInfo->Id, (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUID().GetCounter() : GetEntry())); ++ return; ++ } ++ //end npcbot ++ + /// @todo this is a workaround - not needed anymore, but required for some scripts :( + if (!originalCaster && triggeredByAura) + originalCaster = triggeredByAura->GetCasterGUID(); +@@ -1022,6 +1067,12 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama + case SPELL_DAMAGE_CLASS_RANGED: + case SPELL_DAMAGE_CLASS_MELEE: + { ++ //Npcbot mod: apply bot damage mods ++ if (Creature* bot = ToCreature()) ++ if (bot->GetIAmABot() || bot->GetIAmABotsPet()) ++ bot->ApplyBotDamageMultiplierMelee(damage, *damageInfo, spellInfo, attackType, crit); ++ // End NpcBot ++ + // Physical Damage + if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL) + { +@@ -1079,6 +1130,12 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama + case SPELL_DAMAGE_CLASS_NONE: + case SPELL_DAMAGE_CLASS_MAGIC: + { ++ //Npcbot mod: apply bot damage mods ++ if (Creature* bot = ToCreature()) ++ if (bot->GetIAmABot() || bot->GetIAmABotsPet()) ++ bot->ApplyBotDamageMultiplierSpell(damage, *damageInfo, spellInfo, attackType, crit); ++ // End NpcBot ++ + // If crit add critical bonus + if (crit) + { +@@ -1205,8 +1262,19 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam + else + damageInfo->damage = damage; + ++ //NpcBot mod: check custom melee outcome ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ damageInfo->hitOutCome = ToCreature()->BotRollMeleeOutcomeAgainst(damageInfo->target, damageInfo->attackType); ++ else ++ //End NpcBot + damageInfo->hitOutCome = RollMeleeOutcomeAgainst(damageInfo->target, damageInfo->attackType); + ++ //NpcBot mod: apply bot damage mods ++ if (Creature* bot = ToCreature()) ++ if (bot->GetIAmABot() || bot->GetIAmABotsPet()) ++ bot->ApplyBotDamageMultiplierMelee(damage, *damageInfo); ++ //End NpcBot ++ + switch (damageInfo->hitOutCome) + { + case MELEE_HIT_EVADE: +@@ -1355,6 +1423,9 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) + victim->HandleEmoteCommand(EMOTE_ONESHOT_PARRY_SHIELD); + + if (damageInfo->TargetState == VICTIMSTATE_PARRY) ++ //npcbot - implement CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN (TC sup) ++ if (!(GetTypeId() == TYPEID_UNIT && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN)) ++ //end npcbot + { + // Get attack timers + float offtime = float(victim->getAttackTimer(OFF_ATTACK)); +@@ -1420,6 +1491,10 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) + + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->CastItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, damageInfo->procEx); ++ //npcbot - CastItemCombatSpell for bots ++ else if (ToCreature()->GetBotAI()) ++ ToCreature()->CastCreatureItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, damageInfo->procEx); ++ //end npcbot + + // Do effect if any damage done to target + if (damageInfo->damage) +@@ -1566,6 +1641,11 @@ uint32 Unit::CalcArmorReducedDamage(Unit* victim, const uint32 damage, SpellInfo + if (armor < 0.0f) + armor = 0.0f; + ++ //npcbot custom armor penetration modifier ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ armor *= std::max(1.0f - ToCreature()->GetCreatureArmorPenetrationCoef(), 0.0f); ++ //end npcbot ++ + float levelModifier = getLevel(); + if (levelModifier > 59) + levelModifier = levelModifier + 4.5f * (levelModifier - 59); +@@ -1606,6 +1686,10 @@ uint32 Unit::CalcSpellResistance(Unit* victim, SpellSchoolMask schoolMask, Spell + + if (Player const* player = ToPlayer()) + baseVictimResistance -= player->GetSpellPenetrationItemMod(); ++ //npcbot - spell penetration for bots ++ else if (ToCreature()->GetBotAI()) ++ baseVictimResistance -= ToCreature()->GetCreatureSpellPenetration(); ++ //end npcbot + + // Resistance can't be lower then 0 + int32 victimResistance = std::max(baseVictimResistance, 0); +@@ -1962,6 +2046,38 @@ void Unit::CalcHealAbsorb(Unit* victim, SpellInfo const* healSpell, uint32 &heal + healAmount = RemainingHeal; + } + ++//NpcBot mod ++void Unit::ExecuteDelayedSwingHit(bool extra) ++{ ++ if (_swingLanded) ++ return; ++ ++ _swingLanded = true; ++ if (!extra) ++ _damageInfo.target = ObjectAccessor::FindConnectedPlayer(_delayedTargetGuid); ++ ++ if (!_damageInfo.target) ++ return; ++ ++ //TC_LOG_DEBUG(LOG_FILTER_UNITS, "Unit::ExecuteDelayedSwingHit() call for %s, victim = %s", GetName().c_str(), _damageInfo.target->GetName().c_str()); ++ ++ //TriggerAurasProcOnEvent(*_damageInfo); ++ DealMeleeDamage(&_damageInfo, true); ++ ++ // Recursion warning here ++ ProcDamageAndSpell(_damageInfo.target, _damageInfo.procAttacker, _damageInfo.procVictim, _damageInfo.procEx, _damageInfo.damage, _damageInfo.attackType); ++} ++//NpcBot mod ++void Unit::SuspendDelayedSwing() ++{ ++ if (_swingLanded) ++ return; ++ ++ _swingLanded = true; ++ //TC_LOG_DEBUG(LOG_FILTER_UNITS, "Unit::SuspendDelayedSwing() call for %s, victim = %s", GetName().c_str(), _damageInfo.target ? _damageInfo.target->GetName().c_str() : "_removed_"); ++} ++//End NpcBot mod ++ + void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool extra) + { + if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) +@@ -1973,6 +2089,48 @@ void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool ext + if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim)) + return; + ++ //npcbot ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ { ++ // if attack is executed before previous swing finished, finish it forcefully ++ ExecuteDelayedSwingHit(); ++ ++ CombatStart(victim); ++ RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK); ++ ++ if (attType != BASE_ATTACK && attType != OFF_ATTACK) ++ return; // ignore ranged case ++ ++ // melee attack spell casted at main hand attack only - no normal melee dmg dealt ++ if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra) ++ m_currentSpells[CURRENT_MELEE_SPELL]->cast(); ++ else ++ { ++ // attack can be redirected to another target ++ victim = GetMeleeHitRedirectTarget(victim); ++ CalculateMeleeDamage(victim, 0, &_damageInfo, attType); ++ DealDamageMods(victim, _damageInfo.damage, &_damageInfo.absorb); ++ ++ // Send log damage message to client ++ SendAttackStateUpdate(&_damageInfo); ++ ++ // If this swing is extra attack, execute it right now ++ // Else delay melee hit by melee swing animation time ++ _swingLanded = false; ++ _delayedTargetGuid = victim->GetGUID(); ++ if (m_extraAttacks) ++ ExecuteDelayedSwingHit(true); ++ else ++ _swingDelayTimer = 450; ++ ++ //TC_LOG_DEBUG("entities.unit", "AttackerStateUpdateBot: (NPCBot) %u attacked %u (TypeId: %u) for %u dmg, absorbed %u, blocked %u, resisted %u.", ++ // GetGUID().GetCounter(), victim->GetGUID().GetCounter(), victim->GetTypeId(), _damageInfo.damage, _damageInfo.absorb, _damageInfo.blocked_amount, _damageInfo.resist); ++ } ++ ++ return; ++ } ++ //end npcbot ++ + CombatStart(victim); + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK); + +@@ -2083,11 +2241,24 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst (const Unit* victim, WeaponAttackT + { + TC_LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: attack came from behind and victim was a player."); + } ++ //npcbot - bots cannot dodge if attacker is behind ++ else if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->GetBotAI() && !victim->HasInArc(M_PI, this) && !victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)) ++ { ++ //TC_LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: attack came from behind and victim was a bot."); ++ } ++ //end npcbot + else + { + // Reduce dodge chance by attacker expertise rating + if (GetTypeId() == TYPEID_PLAYER) + dodge_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100); ++ //npcbot - manual expertise instead of auras ++ else if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ { ++ dodge_chance -= ToCreature()->GetCreatureExpertise() * 25; ++ dodge_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; ++ } ++ //end npcbot + else + dodge_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; + +@@ -2115,6 +2286,13 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst (const Unit* victim, WeaponAttackT + // Reduce parry chance by attacker expertise rating + if (GetTypeId() == TYPEID_PLAYER) + parry_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100); ++ //npcbot - manual expertise instead of auras ++ else if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ { ++ parry_chance -= ToCreature()->GetCreatureExpertise() * 25; ++ parry_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; ++ } ++ //end npcbot + else + parry_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; + +@@ -2736,6 +2914,14 @@ float Unit::GetUnitDodgeChance() const + else + { + float dodge = 5.0f; ++ //npcbot - custom dodge chance instead of bunch of auras and remove base chance ++ if (ToCreature()->GetBotAI()) ++ { ++ if (!ToCreature()->CanDodge()) ++ return 0.0f; ++ dodge = ToCreature()->GetCreatureDodgeChance(); ++ } ++ //end npcbot + dodge += GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); + return dodge > 0.0f ? dodge : 0.0f; + } +@@ -2765,6 +2951,15 @@ float Unit::GetUnitParryChance() const + { + if (GetCreatureType() == CREATURE_TYPE_HUMANOID) + { ++ //npcbot - custom parry chance instead of bunch of auras ++ if (ToCreature()->GetBotAI()) ++ { ++ if (!ToCreature()->CanParry()) ++ return 0.0f; ++ chance = ToCreature()->GetCreatureParryChance(); ++ } ++ else ++ //end npcbot + chance = 5.0f; + chance += GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); + } +@@ -2811,6 +3006,14 @@ float Unit::GetUnitBlockChance() const + else + { + float block = 5.0f; ++ //npcbot - custom block chance instead of bunch of auras and remove base chance ++ if (ToCreature()->GetBotAI()) ++ { ++ if (!ToCreature()->CanBlock()) ++ return 0.0f; ++ block = ToCreature()->GetCreatureBlockChance(); ++ } ++ //end npcbot + block += GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); + return block > 0.0f ? block : 0.0f; + } +@@ -2842,6 +3045,15 @@ float Unit::GetUnitCriticalChance(WeaponAttackType attackType, const Unit* victi + } + else + { ++ //npcbot - custom crit chance instead of bunch of auras and remove base chance ++ if (ToCreature()->GetBotAI()) ++ { ++ if (!ToCreature()->CanCrit()) ++ return 0.0f; ++ crit = 5.0f + ToCreature()->GetCreatureCritChance(); ++ } ++ else ++ //end npcbot + crit = 5.0f; + crit += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); + crit += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); +@@ -6118,6 +6330,12 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere + // Check cooldown of heal spell cooldown + if (!GetSpellHistory()->HasCooldown(34299)) + CastCustomSpell(this, 68285, &basepoints1, 0, 0, true, 0, triggeredByAura); ++ ++ //npcbot - proc for bot ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI() && !ToCreature()->GetSpellHistory()->HasCooldown(34299)) ++ CastCustomSpell(this, 68285, &basepoints1, 0, 0, true, 0, triggeredByAura); ++ //end npcbot ++ + break; + } + // Healing Touch (Dreamwalker Raiment set) +@@ -6248,6 +6466,31 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere + triggered_spell_id = isWrathSpell ? 48518 : 48517; + break; + } ++ ++ //npcbot - Eclipse for bot ++ if (dummySpell->SpellIconID == 2856 && GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ { ++ if (!procSpell || effIndex != 0) ++ return false; ++ ++ bool isWrathSpell = (procSpell->SpellFamilyFlags[0] & 1); ++ ++ if (!roll_chance_f(dummySpell->ProcChance * (isWrathSpell ? 0.6f : 1.0f))) ++ return false; ++ ++ target = this; ++ if (target->HasAura(isWrathSpell ? 48517 : 48518)) ++ return false; ++ ++ triggered_spell_id = isWrathSpell ? 48518 : 48517; ++ ++ if (ToCreature()->GetSpellHistory()->HasCooldown(triggered_spell_id)) ++ return false; ++ ++ break; ++ } ++ //end npcbot ++ + break; + } + case SPELLFAMILY_ROGUE: +@@ -8983,6 +9226,9 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) + // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); + + if (GetTypeId() == TYPEID_UNIT && !IsPet()) ++ //npcbot - not for npcbots either ++ if (!ToCreature()->GetBotAI()) ++ //end npcbot + { + // should not let player enter combat by right clicking target - doesn't helps + SetInCombatWith(victim); +@@ -9054,6 +9300,9 @@ void Unit::CombatStop(bool includingCast) + InterruptNonMeleeSpells(false); + + AttackStop(); ++ //npcbot ++ SuspendDelayedSwing(); ++ //end npcbot + RemoveAllAttackers(); + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel +@@ -9091,6 +9340,9 @@ void Unit::RemoveAllAttackers() + while (!m_attackers.empty()) + { + AttackerSet::iterator iter = m_attackers.begin(); ++ //npcbot ++ (*iter)->SuspendDelayedSwing(); ++ //end npcbot + if (!(*iter)->AttackStop()) + { + TC_LOG_ERROR("entities.unit", "WORLD: Unit has an attacker that isn't attacking it!"); +@@ -9681,12 +9933,25 @@ void Unit::RemoveAllControlled() + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->StopCastingCharm(); + ++ //npcbot - store bots for recontroll; bots are to be removed manually ++ std::list nBots; ++ for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) ++ if ((*itr)->GetTypeId() == TYPEID_UNIT && (*itr)->ToCreature()->GetBotAI()) ++ nBots.push_back(*itr); ++ //end npcbot ++ + while (!m_Controlled.empty()) + { + Unit* target = *m_Controlled.begin(); + m_Controlled.erase(m_Controlled.begin()); + if (target->GetCharmerGUID() == GetGUID()) + target->RemoveCharmAuras(); ++ //npcbot - debug info ++ else if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->GetBotAI()) ++ { ++ //TC_LOG_ERROR("entities.unit", "RemoveAllControlled(): Unit %u removing bot %s (%u)", GetGUID().GetCounter(), target->GetName().c_str(), target->GetEntry()); ++ } ++ //end npcbot + else if (target->GetOwnerGUID() == GetGUID() && target->IsSummon()) + target->ToTempSummon()->UnSummon(); + else +@@ -9698,6 +9963,14 @@ void Unit::RemoveAllControlled() + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str()); + if (GetCharmGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmGUID().ToString().c_str()); ++ ++ //npcbot - restore controlled bots ++ if (nBots.empty()) ++ return; ++ ++ for (std::list::const_iterator itr = nBots.begin(); itr != nBots.end(); ++itr) ++ m_Controlled.insert(*itr); ++ //end npcbot + } + + bool Unit::isPossessedByPlayer() const +@@ -10320,6 +10593,11 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui + AddPct(TakenTotalMod, (*i)->GetAmount()); + } + ++ //npcbot - damage taken modifier ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ TakenTotalMod *= ToCreature()->GetCreatureDamageTakenMod(); ++ //end npcbot ++ + //.. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) +@@ -10410,6 +10688,11 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) const + { + int32 DoneAdvertisedBenefit = 0; + ++ //npcbot: apply bot spellpower ++ if (schoolMask && !(schoolMask & SPELL_SCHOOL_MASK_NORMAL) && GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ DoneAdvertisedBenefit += ToCreature()->GetCreatureSpellPower(); ++ //end npcbot ++ + AuraEffectList const& mDamageDone = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE); + for (AuraEffectList::const_iterator i = mDamageDone.begin(); i != mDamageDone.end(); ++i) + if (((*i)->GetMiscValue() & schoolMask) != 0 && +@@ -10467,6 +10750,9 @@ float Unit::GetUnitSpellCriticalChance(Unit* victim, SpellInfo const* spellProto + //! Mobs can't crit with spells. Player Totems can + //! Fire Elemental (from totem) can too - but this part is a hack and needs more research + if (GetGUID().IsCreatureOrVehicle() && !(IsTotem() && GetOwnerGUID().IsPlayer()) && GetEntry() != 15438) ++ //npcbot - allow bots to crit ++ if (!ToCreature()->GetBotAI()) ++ //end npcbot + return 0.0f; + + // not critting spell +@@ -10658,6 +10944,11 @@ float Unit::GetUnitSpellCriticalChance(Unit* victim, SpellInfo const* spellProto + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CRITICAL_CHANCE, crit_chance); + ++ //npcbot - apply bot spell crit mods ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ ToCreature()->ApplyBotCritMultiplierAll(victim, crit_chance, spellProto, schoolMask, attackType); ++ //end npcbot ++ + return crit_chance > 0.0f ? crit_chance : 0.0f; + } + +@@ -10836,6 +11127,11 @@ uint32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, ui + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto->Id, damagetype == DOT ? SPELLMOD_DOT : SPELLMOD_DAMAGE, heal); + ++ //npcbot - healing bonus done for bots ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ ToCreature()->ApplyBotDamageMultiplierHeal(victim, heal, spellProto, damagetype, stack); ++ //end npcbot ++ + return uint32(std::max(heal, 0.0f)); + } + +@@ -11024,6 +11320,11 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) const + if (!(*i)->GetMiscValue() || ((*i)->GetMiscValue() & schoolMask) != 0) + advertisedBenefit += (*i)->GetAmount(); + ++ //npcbot: apply bot spellpower to healing ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ advertisedBenefit += ToCreature()->GetCreatureSpellPower(); ++ //end npcbot ++ + // Healing bonus of spirit, intellect and strength + if (GetTypeId() == TYPEID_PLAYER) + { +@@ -11115,6 +11416,11 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo) const + if (spellInfo->HasAttribute(SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)) + return false; + ++ //npcbot - check 'magic immunity' state and other ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI() && ToCreature()->IsCreatureImmuneToSpell(spellInfo)) ++ return true; ++ //end npcbot ++ + if (spellInfo->Dispel) + { + SpellImmuneList const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; +@@ -11436,6 +11742,11 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT + // ..taken + TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, attacker->GetMeleeDamageSchoolMask()); + ++ //npcbot - damage taken modifier ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ TakenTotalMod *= ToCreature()->GetCreatureDamageTakenMod(); ++ //end npcbot ++ + // .. taken pct (special attacks) + if (spellProto) + { +@@ -11777,6 +12088,12 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy) + if (PvP) + m_CombatTimer = 5000; + ++ ////npcbot - if combat with npcbot or its pet set extended timer ++ //if (PvP &&((GetTypeId() == TYPEID_UNIT && ToCreature()->IsFreeBot()) || ++ // (enemy->GetTypeId() == TYPEID_UNIT && enemy->ToCreature()->IsFreeBot()))) ++ // m_CombatTimer += 5000; ++ ////end npcbot ++ + if (IsInCombat() || HasUnitState(UNIT_STATE_EVADE)) + return; + +@@ -12206,6 +12523,12 @@ bool Unit::IsAlwaysVisibleFor(WorldObject const* seer) const + if (ownerPlayer->IsGroupVisibleFor(seerPlayer)) + return true; + ++ //npcbot - bots are always visible for owner ++ if (Creature const* bot = ToCreature()) ++ if (bot->GetBotAI() && seer->GetGUID() == bot->GetBotOwner()->GetGUID()) ++ return true; ++ //end npcbot ++ + return false; + } + +@@ -12579,6 +12902,11 @@ bool Unit::CanHaveThreatList(bool skipAliveCheck) const + if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLABLE_GUARDIAN) && ((Pet*)this)->GetOwnerGUID().IsPlayer()) + return false; + ++ //npcbot - npcbots and their pets cannot have threatlist ++ if (ToCreature()->GetBotAI()) ++ return false; ++ //end npcbot ++ + return true; + } + +@@ -12973,6 +13301,10 @@ void Unit::ModSpellCastTime(SpellInfo const* spellInfo, int32 & castTime, Spell* + // called from caster + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CASTING_TIME, castTime, spell); ++ //npcbot - apply bot spell cast time mods ++ if (castTime > 0 && GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ ToCreature()->ApplyCreatureSpellCastTimeMods(spellInfo, castTime); ++ //end npcbot + + if (!(spellInfo->HasAttribute(SPELL_ATTR0_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR0_TRADESPELL) || spellInfo->HasAttribute(SPELL_ATTR3_NO_DONE_BONUS)) && + ((GetTypeId() == TYPEID_PLAYER && spellInfo->SpellFamilyName) || GetTypeId() == TYPEID_UNIT)) +@@ -14150,6 +14482,13 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u + ToPlayer()->AddComboPoints(target, 1); + StartReactiveTimer(REACTIVE_OVERPOWER); + } ++ ++ //npcbot - update reactives for bots ++ if ((procExtra & (PROC_EX_DODGE | PROC_EX_PARRY)) && GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI() && ToCreature()->GetBotClass() == CLASS_WARRIOR) ++ { ++ StartReactiveTimer(REACTIVE_OVERPOWER); ++ } ++ //end npcbot + } + } + } +@@ -14707,6 +15046,29 @@ void Unit::ClearComboPointHolders() + } + } + ++//npcbot ++void Unit::ClearReactive(ReactiveType reactive) ++{ ++ m_reactiveTimer[reactive] = 0; ++ ++ switch (reactive) ++ { ++ case REACTIVE_DEFENSE: ++ if (HasAuraState(AURA_STATE_DEFENSE)) ++ ModifyAuraState(AURA_STATE_DEFENSE, false); ++ break; ++ case REACTIVE_HUNTER_PARRY: ++ if (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) ++ ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); ++ break; ++ case REACTIVE_OVERPOWER: ++ if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) ++ ToPlayer()->ClearComboPoints(); ++ break; ++ } ++} ++//end npcbot ++ + void Unit::ClearAllReactives() + { + for (uint8 i = 0; i < MAX_REACTIVE; ++i) +@@ -14815,6 +15177,9 @@ uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectTyp + { + // Not apply this to creature cast spells with casttime == 0 + if (CastingTime == 0 && GetTypeId() == TYPEID_UNIT && !IsPet()) ++ //npcbot - skip bots ++ if (!ToCreature()->GetBotAI()) ++ //endnpcbot + return 3500; + + if (CastingTime > 7000) CastingTime = 7000; +@@ -15458,6 +15823,9 @@ void Unit::Kill(Unit* victim, bool durabilityLoss) + + // only if not player and not controlled by player pet. And not at BG + if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) ++ //npcbot - bots should not cause durability loss unless rampaging around ++ if (player || !ToCreature()->GetBotAI() || ToCreature()->GetBotOwner()->GetGUID().GetCounter() == GetGUID().GetCounter()) ++ //end npcbot + { + TC_LOG_DEBUG("entities.unit", "We are dead, losing %f percent durability", sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH)); + plrVictim->DurabilityLossAll(sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH), false); +@@ -16397,6 +16765,21 @@ float Unit::MeleeSpellMissChance(const Unit* victim, WeaponAttackType attType, i + //calculate miss chance + float missChance = victim->GetUnitMissChance(attType); + ++ //npcbot - custom miss chance instead of bunch of auras, extra miss chance against bots ++ //bot can have extra miss chance for attackers ++ //but if attacker is also a bot and cannot miss then return this extra miss chance ++ float evasion = 0.0f; ++ ++ if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->GetBotAI()) ++ evasion = victim->ToCreature()->GetCreatureEvasion(); ++ if (GetTypeId() == TYPEID_UNIT && !ToCreature()->CanMiss()) ++ return evasion; ++ if (GetTypeId() == TYPEID_UNIT && ToCreature()->GetBotAI()) ++ missChance += ToCreature()->GetCreatureMissChance(); ++ ++ missChance += evasion; ++ //end npcbot ++ + if (!spellId && haveOffhandWeapon()) + missChance += 19; + +@@ -16425,6 +16808,11 @@ float Unit::MeleeSpellMissChance(const Unit* victim, WeaponAttackType attType, i + else + missChance -= m_modMeleeHitChance; + ++ //npcbot - limit chance from 30% to 60% if evasion is here ++ if (evasion > 0.0f && missChance < evasion) ++ missChance = evasion; ++ //end npcbot ++ + // Limit miss chance from 0 to 60% + if (missChance < 0.0f) + return 0.0f; +@@ -16741,6 +17129,183 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const + } + } + ++ else if (ToCreature() && ToCreature()->GetBotOwner() && ToCreature()->GetBotOwner()->ToPlayer()) ++ { ++ Player const* player = ToCreature()->GetBotOwner(); ++ //let's make druids alike for each player ++ switch (form) ++ { ++ case FORM_CAT: ++ // Based on master's Hair color ++ if (player->getRace() == RACE_NIGHTELF) ++ { ++ uint8 hairColor = player->GetByteValue(PLAYER_BYTES, 3); ++ switch (hairColor) ++ { ++ case 7: // Violet ++ case 8: ++ return 29405; ++ case 3: // Light Blue ++ return 29406; ++ case 0: // Green ++ case 1: // Light Green ++ case 2: // Dark Green ++ return 29407; ++ case 4: // White ++ return 29408; ++ default: // original - Dark Blue ++ return 892; ++ } ++ } ++ // Based on master's Skin color ++ else if (player->getRace() == RACE_TAUREN) ++ { ++ uint8 skinColor = player->GetByteValue(PLAYER_BYTES, 0); ++ // Male master ++ if (player->getGender() == GENDER_MALE) ++ { ++ switch (skinColor) ++ { ++ case 12: // White ++ case 13: ++ case 14: ++ case 18: // Completly White ++ return 29409; ++ case 9: // Light Brown ++ case 10: ++ case 11: ++ return 29410; ++ case 6: // Brown ++ case 7: ++ case 8: ++ return 29411; ++ case 0: // Dark ++ case 1: ++ case 2: ++ case 3: // Dark Grey ++ case 4: ++ case 5: ++ return 29412; ++ default: // original - Grey ++ return 8571; ++ } ++ } ++ // Female master ++ else switch (skinColor) ++ { ++ case 10: // White ++ return 29409; ++ case 6: // Light Brown ++ case 7: ++ return 29410; ++ case 4: // Brown ++ case 5: ++ return 29411; ++ case 0: // Dark ++ case 1: ++ case 2: ++ case 3: ++ return 29412; ++ default: // original - Grey ++ return 8571; ++ } ++ } ++ else if (Player::TeamForRace(player->getRace()) == ALLIANCE) ++ return 892; ++ else ++ return 8571; ++ case FORM_DIREBEAR: ++ case FORM_BEAR: ++ // Based on Hair color ++ if (player->getRace() == RACE_NIGHTELF) ++ { ++ uint8 hairColor = player->GetByteValue(PLAYER_BYTES, 3); ++ switch (hairColor) ++ { ++ case 0: // Green ++ case 1: // Light Green ++ case 2: // Dark Green ++ return 29413; // 29415? ++ case 6: // Dark Blue ++ return 29414; ++ case 4: // White ++ return 29416; ++ case 3: // Light Blue ++ return 29417; ++ default: // original - Violet ++ return 2281; ++ } ++ } ++ // Based on Skin color ++ else if (player->getRace() == RACE_TAUREN) ++ { ++ uint8 skinColor = player->GetByteValue(PLAYER_BYTES, 0); ++ // Male ++ if (player->getGender() == GENDER_MALE) ++ { ++ switch (skinColor) ++ { ++ case 0: // Dark (Black) ++ case 1: ++ case 2: ++ return 29418; ++ case 3: // White ++ case 4: ++ case 5: ++ case 12: ++ case 13: ++ case 14: ++ return 29419; ++ case 9: // Light Brown/Grey ++ case 10: ++ case 11: ++ case 15: ++ case 16: ++ case 17: ++ return 29420; ++ case 18: // Completly White ++ return 29421; ++ default: // original - Brown ++ return 2289; ++ } ++ } ++ // Female ++ else switch (skinColor) ++ { ++ case 0: // Dark (Black) ++ case 1: ++ return 29418; ++ case 2: // White ++ case 3: ++ return 29419; ++ case 6: // Light Brown/Grey ++ case 7: ++ case 8: ++ case 9: ++ return 29420; ++ case 10: // Completly White ++ return 29421; ++ default: // original - Brown ++ return 2289; ++ } ++ } ++ else if (Player::TeamForRace(player->getRace()) == ALLIANCE) ++ return 2281; ++ else ++ return 2289; ++ case FORM_FLIGHT: ++ if (Player::TeamForRace(player->getRace()) == ALLIANCE) ++ return 20857; ++ return 20872; ++ case FORM_FLIGHT_EPIC: ++ if (Player::TeamForRace(player->getRace()) == ALLIANCE) ++ return 21243; ++ return 21244; ++ default: ++ break; ++ } ++ } ++ + uint32 modelid = 0; + SpellShapeshiftEntry const* formEntry = sSpellShapeshiftStore.LookupEntry(form); + if (formEntry && formEntry->modelID_A) +diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h +index 8572c79..0979c39 100644 +--- a/src/server/game/Entities/Unit/Unit.h ++++ b/src/server/game/Entities/Unit/Unit.h +@@ -2136,6 +2136,18 @@ class Unit : public WorldObject + void TextEmote(uint32 textId, WorldObject const* target = nullptr, bool isBossEmote = false); + void Whisper(uint32 textId, Player* target, bool isBossWhisper = false); + ++ //npcbot ++ bool HasReactive(ReactiveType reactive) const { return m_reactiveTimer[reactive] > 0; } ++ void ClearReactive(ReactiveType reactive); ++ ++ void SuspendDelayedSwing(); ++ void ExecuteDelayedSwingHit(bool extra = false); ++ CalcDamageInfo _damageInfo; ++ ObjectGuid _delayedTargetGuid; ++ uint32 _swingDelayTimer; ++ bool _swingLanded; ++ //end npcbot ++ + protected: + explicit Unit (bool isWorldObject); + +diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp +index d206409..0156e20 100644 +--- a/src/server/game/Globals/ObjectMgr.cpp ++++ b/src/server/game/Globals/ObjectMgr.cpp +@@ -7892,6 +7892,82 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry) + return SKILL_RANGE_LEVEL; + } + ++void ObjectMgr::LoadCreatureOutfits() ++{ ++ uint32 oldMSTime = getMSTime(); ++ ++ _creatureOutfitStore.clear(); // for reload case (test only) ++ ++ // 0 1 2 3 4 5 6 7 ++ QueryResult result = WorldDatabase.Query("SELECT entry, race, gender, skin, face, hair, haircolor, facialhair, " ++ //8 9 10 11 12 13 14 15 16 17 18 ++ "head, shoulders, body, chest, waist, legs, feet, wrists, hands, back, tabard FROM creature_template_outfits"); ++ ++ if (!result) ++ { ++ TC_LOG_ERROR("server.loading", ">> Loaded 0 creature outfits. DB table `creature_template_outfits` is empty!"); ++ return; ++ } ++ ++ uint32 count = 0; ++ ++ do ++ { ++ Field* fields = result->Fetch(); ++ ++ uint32 i = 0; ++ uint32 entry = fields[i++].GetUInt32(); ++ ++ if (!GetCreatureTemplate(entry)) ++ { ++ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits`, but not in `creature_template`!", entry); ++ continue; ++ } ++ ++ CreatureOutfit co; // const, shouldnt be changed after saving ++ co.race = fields[i++].GetUInt8(); ++ ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(co.race); ++ if (!rEntry) ++ { ++ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits` has incorrect race (%u).", entry, uint32(co.race)); ++ continue; ++ } ++ co.gender = fields[i++].GetUInt8(); ++ // Set correct displayId ++ switch (co.gender) ++ { ++ case GENDER_FEMALE: ++ _creatureTemplateStore[entry].Modelid1 = rEntry->model_f; ++ break; ++ case GENDER_MALE: ++ _creatureTemplateStore[entry].Modelid1 = rEntry->model_m; ++ break; ++ default: ++ TC_LOG_ERROR("server.loading", ">> Creature entry %u in `creature_template_outfits` has invalid gender %u", entry, uint32(co.gender)); ++ continue; ++ } ++ _creatureTemplateStore[entry].Modelid2 = 0; ++ _creatureTemplateStore[entry].Modelid3 = 0; ++ _creatureTemplateStore[entry].Modelid4 = 0; ++ _creatureTemplateStore[entry].unit_flags2 |= UNIT_FLAG2_MIRROR_IMAGE; // Needed so client requests mirror packet ++ ++ co.skin = fields[i++].GetUInt8(); ++ co.face = fields[i++].GetUInt8(); ++ co.hair = fields[i++].GetUInt8(); ++ co.haircolor = fields[i++].GetUInt8(); ++ co.facialhair = fields[i++].GetUInt8(); ++ for (uint32 j = 0; j != MAX_CREATURE_OUTFIT_DISPLAYS; ++j) ++ co.outfit[j] = fields[i+j].GetUInt32(); ++ ++ _creatureOutfitStore[entry] = co; ++ ++ ++count; ++ } ++ while (result->NextRow()); ++ ++ TC_LOG_INFO("server.loading", ">> Loaded %u creature outfits in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); ++} ++ + void ObjectMgr::LoadGameTele() + { + uint32 oldMSTime = getMSTime(); +diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h +index ae12587..7c7a7ad 100644 +--- a/src/server/game/Globals/ObjectMgr.h ++++ b/src/server/game/Globals/ObjectMgr.h +@@ -136,6 +136,21 @@ struct GameTele + + typedef std::unordered_map GameTeleContainer; + ++#define MAX_CREATURE_OUTFIT_DISPLAYS 11 ++struct CreatureOutfit ++{ ++ uint8 race; ++ uint8 gender; ++ uint8 face; ++ uint8 skin; ++ uint8 hair; ++ uint8 facialhair; ++ uint8 haircolor; ++ uint32 outfit[MAX_CREATURE_OUTFIT_DISPLAYS]; ++}; ++ ++typedef std::unordered_map CreatureOutfitContainer; ++ + enum ScriptsType + { + SCRIPTS_FIRST = 1, +@@ -1034,6 +1049,7 @@ class ObjectMgr + + void LoadNPCSpellClickSpells(); + ++ void LoadCreatureOutfits(); + void LoadGameTele(); + + void LoadGossipMenu(); +@@ -1248,6 +1264,8 @@ class ObjectMgr + bool AddGameTele(GameTele& data); + bool DeleteGameTele(std::string const& name); + ++ CreatureOutfitContainer const& GetCreatureOutfitMap() const { return _creatureOutfitStore; } ++ + TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const + { + CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry); +@@ -1399,6 +1417,8 @@ class ObjectMgr + PageTextContainer _pageTextStore; + InstanceTemplateContainer _instanceTemplateStore; + ++ CreatureOutfitContainer _creatureOutfitStore; ++ + private: + void LoadScripts(ScriptsType type); + void LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table, bool starter, bool go); +diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp +index 4315982..cefaa2b 100644 +--- a/src/server/game/Groups/Group.cpp ++++ b/src/server/game/Groups/Group.cpp +@@ -104,6 +104,11 @@ bool Group::Create(Player* leader) + if (m_groupType & GROUPTYPE_RAID) + _initRaidSubGroupsCounter(); + ++ //npcbot - set loot mode on create ++ if (leader->HaveBot()) //player + npcbot so set to free-for-all on create ++ m_lootMethod = FREE_FOR_ALL; ++ else ++ //end npcbot + if (!isLFGGroup()) + m_lootMethod = GROUP_LOOT; + +@@ -362,6 +367,10 @@ bool Group::AddMember(Player* player) + + SubGroupCounterIncrease(subGroup); + ++ //npcbot - check if trying to add bot ++ if (player->GetGUID().IsPlayer()) ++ { ++ //end npcbot + player->SetGroupInvite(NULL); + if (player->GetGroup()) + { +@@ -375,7 +384,9 @@ bool Group::AddMember(Player* player) + + // if the same group invites the player back, cancel the homebind timer + player->m_InstanceValid = player->CheckInstanceValidity(false); +- ++ //npcbot ++ } ++ //end npcbot + if (!isRaidGroup()) // reset targetIcons for non-raid-groups + { + for (uint8 i = 0; i < TARGETICONCOUNT; ++i) +@@ -399,6 +410,10 @@ bool Group::AddMember(Player* player) + SendUpdate(); + sScriptMgr->OnGroupAddMember(this, player->GetGUID()); + ++ //npcbot - check 2 ++ if (player->GetGUID().IsPlayer()) ++ { ++ //end npcbot + if (!IsLeader(player->GetGUID()) && !isBGGroup() && !isBFGroup()) + { + // reset the new member's instances, unless he is currently in one of them +@@ -474,6 +489,9 @@ bool Group::AddMember(Player* player) + + if (m_maxEnchantingLevel < player->GetSkillValue(SKILL_ENCHANTING)) + m_maxEnchantingLevel = player->GetSkillValue(SKILL_ENCHANTING); ++ //npcbot ++ } ++ //end npcbot + + return true; + } +@@ -597,6 +615,9 @@ bool Group::RemoveMember(ObjectGuid guid, const RemoveMethod& method /*= GROUP_R + } + + if (m_memberMgr.getSize() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u)) ++ //npcbot ++ if (GetMembersCount() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u)) ++ //end npcbot + Disband(); + + return true; +diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h +index 42fd9b5..850495c 100644 +--- a/src/server/game/Groups/Group.h ++++ b/src/server/game/Groups/Group.h +@@ -324,6 +324,9 @@ class Group + // FG: evil hacks + void BroadcastGroupUpdate(void); + ++ //Bot ++ ObjectGuid const *GetTargetIcons() const { return m_targetIcons; } ++ + protected: + bool _setMembersGroup(ObjectGuid guid, uint8 group); + void _homebindIfInstance(Player* player); +diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp +index 6be1fd3..b069433 100644 +--- a/src/server/game/Handlers/SpellHandler.cpp ++++ b/src/server/game/Handlers/SpellHandler.cpp +@@ -576,8 +576,38 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) + if (!unit) + return; + ++ //bot ++ if (unit->GetTypeId() == TYPEID_UNIT) ++ { ++ CreatureOutfitContainer const& outfits = sObjectMgr->GetCreatureOutfitMap(); ++ CreatureOutfitContainer::const_iterator it = outfits.find(unit->GetEntry()); ++ if (it != outfits.end()) ++ { ++ WorldPacket data(SMSG_MIRRORIMAGE_DATA, 68); ++ data << uint64(guid); ++ data << uint32(unit->GetNativeDisplayId()); // displayId ++ data << uint8(it->second.race); // race ++ data << uint8(it->second.gender); // gender ++ data << uint8(unit->getClass()); // class ++ data << uint8(it->second.skin); // skin ++ data << uint8(it->second.face); // face ++ data << uint8(it->second.hair); // hair ++ data << uint8(it->second.haircolor); // haircolor ++ data << uint8(it->second.facialhair); // facialhair ++ data << uint32(0); // guildId ++ ++ // item displays ++ for (uint8 i = 0; i != MAX_CREATURE_OUTFIT_DISPLAYS; ++i) ++ data << uint32(it->second.outfit[i]); ++ ++ SendPacket(&data); ++ return; ++ } ++ } ++ + if (!unit->HasAuraType(SPELL_AURA_CLONE_CASTER)) + return; ++ //end bot + + // Get creator of the unit (SPELL_AURA_CLONE_CASTER does not stack) + Unit* creator = unit->GetAuraEffectsByType(SPELL_AURA_CLONE_CASTER).front()->GetCaster(); +diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp +index a2cb843..66ab324 100644 +--- a/src/server/game/Maps/Map.cpp ++++ b/src/server/game/Maps/Map.cpp +@@ -36,6 +36,10 @@ + #include "Vehicle.h" + #include "VMapFactory.h" + ++//npcbot ++#include "botmgr.h" ++//end npcbot ++ + u_map_magic MapMagic = { {'M','A','P','S'} }; + u_map_magic MapVersionMagic = { {'v','1','.','3'} }; + u_map_magic MapAreaMagic = { {'A','R','E','A'} }; +@@ -2793,8 +2797,24 @@ uint32 Map::GetPlayersCountExceptGMs() const + { + uint32 count = 0; + for (MapRefManager::const_iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr) +- if (!itr->GetSource()->IsGameMaster()) ++ if (!itr->GetSource()->IsGameMaster()) { ++ //npcbot - count npcbots as group members (event if not in group) ++ if (itr->GetSource()->HaveBot() && BotMgr::LimitBots(this)) ++ { ++ ++count; ++ BotMap const* botmap = itr->GetSource()->GetBotMgr()->GetBotMap(); ++ for (BotMap::const_iterator itr = botmap->begin(); itr != botmap->end(); ++itr) ++ { ++ Creature* cre = itr->second; ++ if (!cre || !cre->IsInWorld() || cre->FindMap() != this) ++ continue; ++ ++count; ++ } ++ } ++ else ++ //end npcbot + ++count; ++ } + return count; + } + +@@ -2860,6 +2880,10 @@ void Map::AddToActive(Creature* c) + GridCoord p = Trinity::ComputeGridCoord(x, y); + if (getNGrid(p.x_coord, p.y_coord)) + getNGrid(p.x_coord, p.y_coord)->incUnloadActiveLock(); ++ //bot ++ else if (c->GetIAmABot()) ++ EnsureGridLoadedForActiveObject(Cell(Trinity::ComputeCellCoord(c->GetPositionX(), c->GetPositionY())), c); ++ //end bot + else + { + GridCoord p2 = Trinity::ComputeGridCoord(c->GetPositionX(), c->GetPositionY()); +@@ -2891,6 +2915,10 @@ void Map::RemoveFromActive(Creature* c) + GridCoord p = Trinity::ComputeGridCoord(x, y); + if (getNGrid(p.x_coord, p.y_coord)) + getNGrid(p.x_coord, p.y_coord)->decUnloadActiveLock(); ++ //bot ++ else if (c->GetIAmABot()) ++ EnsureGridLoaded(Cell(Trinity::ComputeCellCoord(c->GetPositionX(), c->GetPositionY()))); ++ //end bot + else + { + GridCoord p2 = Trinity::ComputeGridCoord(c->GetPositionX(), c->GetPositionY()); +diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp +index e5f364e..ae3b512 100644 +--- a/src/server/game/Maps/MapManager.cpp ++++ b/src/server/game/Maps/MapManager.cpp +@@ -37,6 +37,10 @@ + #include "Opcodes.h" + #include "AchievementMgr.h" + ++//npcbot ++#include "botmgr.h" ++//end npcbot ++ + MapManager::MapManager() + { + i_gridCleanUpDelay = sWorld->getIntConfig(CONFIG_INTERVAL_GRIDCLEAN); +@@ -54,6 +58,82 @@ void MapManager::Initialize() + // Start mtmaps if needed. + if (num_threads > 0) + m_updater.activate(num_threads); ++ ++ //npcbot - spawn bots ++ BotMgr::LoadConfig(); ++ ++ if (!BotMgr::IsNpcBotModEnabled()) ++ return; ++ ++ uint32 botoldMSTime = getMSTime(); ++ ++ TC_LOG_INFO("server.loading", "Starting NpcBot system..."); ++ PreparedStatement* botstmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOTS); ++ //"SELECT entry FROM characters_npcbot", CONNECTION_SYNCH ++ PreparedQueryResult res = CharacterDatabase.Query(botstmt); ++ if (!res) ++ { ++ TC_LOG_INFO("server.loading", ">> Spawned 0 npcbots. Table `characters_npcbot` is empty!"); ++ return; ++ } ++ ++ PreparedQueryResult infores; ++ uint32 botcounter = 0; ++ Field* field; ++ std::list botgrids; ++ do ++ { ++ field = res->Fetch(); ++ uint32 entry = field[0].GetUInt32(); ++ CreatureTemplate const* proto = sObjectMgr->GetCreatureTemplate(entry); ++ if (!proto) ++ { ++ TC_LOG_ERROR("server.loading", "Cannot find creature_template entry for npcbot (id: %u)!", entry); ++ continue; ++ } ++ ++ botstmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_NPCBOT_INFO); ++ //"SELECT guid, map, position_x, position_y, position_z, orientation FROM creature WHERE id = ?", CONNECTION_SYNCH ++ botstmt->setUInt32(0, entry); ++ infores = WorldDatabase.Query(botstmt); ++ if (!infores) ++ { ++ TC_LOG_ERROR("server.loading", "Cannot spawn npcbot %s (id: %u), not found in `creature` table!", proto->Name.c_str(), entry); ++ continue; ++ } ++ ++ field = infores->Fetch(); ++ uint32 tableGuid = field[0].GetUInt32(); ++ uint32 mapId = uint32(field[1].GetUInt16()); ++ float pos_x = field[2].GetFloat(); ++ float pos_y = field[3].GetFloat(); ++ //float pos_z = field[4].GetFloat(); ++ //float ori = field[5].GetFloat(); ++ ++ CellCoord c = Trinity::ComputeCellCoord(pos_x, pos_y); ++ GridCoord g = Trinity::ComputeGridCoord(pos_x, pos_y); ++ ASSERT(c.IsCoordValid() && "Invalid Cell coord!"); ++ ASSERT(g.IsCoordValid() && "Invalid Grid coord!"); ++ Map* npcbotmap = sMapMgr->CreateBaseMap(mapId); ++ npcbotmap->LoadGrid(pos_x, pos_y); ++ /*Creature* bot = npcbotmap->GetCreature(ObjectGuid(HighGuid::Unit, entry, tableGuid)); ++ ABORT(); ++ //debug ++ if (!bot->IsAlive()) ++ { ++ bot->Respawn(); ++ bot->ResetBotAI(1); ++ }*/ ++ ++ TC_LOG_DEBUG("server.loading", ">> Spawned npcbot %s (id: %u, map: %u, grid: %u, cell: %u)", proto->Name.c_str(), entry, mapId, g.GetId(), c.GetId()); ++ botgrids.push_back(g.GetId()); ++ ++botcounter; ++ ++ } while (res->NextRow()); ++ ++ botgrids.unique(); ++ TC_LOG_INFO("server.loading", ">> Spawned %u npcbot(s) within %lu grid(s) in %u ms", botcounter, botgrids.size(), GetMSTimeDiffToNow(botoldMSTime)); ++ //end npcbot + } + + void MapManager::InitializeVisibilityDistanceInfo() +diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp +index 3fcae13..3c9b193 100644 +--- a/src/server/game/Movement/MotionMaster.cpp ++++ b/src/server/game/Movement/MotionMaster.cpp +@@ -363,6 +363,20 @@ void MotionMaster::MoveJumpTo(float angle, float speedXY, float speedZ) + + float x, y, z; + ++ //npcbot ++ if (_owner->GetTypeId() == TYPEID_UNIT && _owner->ToCreature()->IsNPCBot()) ++ { ++ Movement::MoveSplineInit init(_owner); ++ init.MoveTo(x, y, z); ++ init.SetParabolic(speedZ/*max_height*/, 0); ++ init.SetOrientationFixed(true); ++ init.SetVelocity(speedXY); ++ init.Launch(); ++ Mutate(new EffectMovementGenerator(0), MOTION_SLOT_CONTROLLED); ++ return; ++ } ++ //end npcbot ++ + float moveTimeHalf = speedZ / Movement::gravity; + float dist = 2 * moveTimeHalf * speedXY; + _owner->GetClosePoint(x, y, z, _owner->GetObjectSize(), dist, angle); +diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp +index 868cba9..8e853db 100644 +--- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp ++++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp +@@ -345,6 +345,23 @@ bool OPvPCapturePoint::Update(uint32 diff) + if (!fact_diff) + return false; + ++ //npcbots - count bots as players but 2 times less affect and only if there is a players difference ++ uint32 botsCount[2]; ++ ++ for (uint8 team = 0; team != 2; ++team) ++ { ++ botsCount[team] = 0; ++ ++ for (GuidSet::iterator itr = m_activePlayers[team].begin(); itr != m_activePlayers[team].end(); ++itr) ++ { ++ if (Player* player = ObjectAccessor::FindPlayer(*itr)) ++ botsCount[team] += player->GetNpcBotsCount(); ++ } ++ } ++ ++ fact_diff += 0.5f * float(botsCount[0] - botsCount[1]) * diff / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL; ++ //end npcbot ++ + uint32 Challenger = 0; + float maxDiff = m_maxSpeed * diff; + +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index e983c9e..8afb2f6 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1608,7 +1608,22 @@ void AddBattlegroundScripts() + // start197 + // start198 + // start199 +-// start200 ++//Bots ++void AddSC_death_knight_bot(); ++void AddSC_druid_bot(); ++void AddSC_hunter_bot(); ++void AddSC_mage_bot(); ++void AddSC_paladin_bot(); ++void AddSC_priest_bot(); ++void AddSC_rogue_bot(); ++void AddSC_shaman_bot(); ++void AddSC_warlock_bot(); ++void AddSC_warrior_bot(); ++void AddSC_blademaster_bot(); ++void AddSC_script_bot_commands(); ++//advanced ++//void AddSC_BotQuests_chapter1(); ++//end Bots + #endif + + void AddCustomScripts() +@@ -1814,6 +1829,21 @@ void AddCustomScripts() + // end197 + // end198 + // end199 +-// end200 ++ //Bots ++ AddSC_death_knight_bot(); ++ AddSC_druid_bot(); ++ AddSC_hunter_bot(); ++ AddSC_mage_bot(); ++ AddSC_paladin_bot(); ++ AddSC_priest_bot(); ++ AddSC_rogue_bot(); ++ AddSC_shaman_bot(); ++ AddSC_warlock_bot(); ++ AddSC_warrior_bot(); ++ AddSC_blademaster_bot(); ++ AddSC_script_bot_commands(); ++ //advanced ++ //AddSC_BotQuests_chapter1(); ++ //end Bots + #endif + } +diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp +index c380c1a..b4cd77d 100644 +--- a/src/server/game/Server/WorldSession.cpp ++++ b/src/server/game/Server/WorldSession.cpp +@@ -444,6 +444,10 @@ void WorldSession::LogoutPlayer(bool save) + m_playerLogout = true; + m_playerSave = save; + ++ //npcbot - free all bots and remove from botmap ++ _player->RemoveAllBots(); ++ //end npcbots ++ + if (_player) + { + if (ObjectGuid lguid = _player->GetLootGUID()) +diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp +index 63fe148..7f9cf47 100644 +--- a/src/server/game/Spells/Spell.cpp ++++ b/src/server/game/Spells/Spell.cpp +@@ -2402,6 +2402,13 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) + if (caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) == 0 && + (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) + caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx); ++ ++ //npcbot - CastItemCombatSpell for bots ++ if (caster->GetTypeId() == TYPEID_UNIT && ++ caster->ToCreature()->GetBotAI() && !(m_spellInfo->Attributes & SPELL_ATTR0_STOP_ATTACK_TARGET) && ++ (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) ++ caster->ToCreature()->CastCreatureItemCombatSpell(unitTarget, m_attackType, procVictim, procEx, this); ++ //end npcbot + } + + m_damage = damageInfo.damage; +@@ -3193,6 +3200,11 @@ void Spell::cast(bool skipCheck) + return; + } + ++ //NpcBot: If we are applying crowd control aura execute caster's delayed attack immediately to prevent instant CC break ++ if (m_targets.GetUnitTarget() && (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE)) ++ m_caster->ExecuteDelayedSwingHit(); ++ //end NpcBot ++ + PrepareTriggersExecutedOnHit(); + + CallScriptOnCastHandlers(); +@@ -3643,6 +3655,9 @@ void Spell::finish(bool ok) + + // Stop Attack for some spells + if (m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET)) ++ //npcbot - disable for npcbots ++ if (!(m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->GetBotAI())) ++ //end npcbot + m_caster->AttackStop(); + } + +@@ -3852,6 +3867,11 @@ void Spell::SendSpellStart() + + void Spell::SendSpellGo() + { ++ //npcbot - hook for spellcast finish ++ if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->GetBotAI()) ++ m_caster->ToCreature()->OnSpellGo(this); ++ //end npcbot ++ + // not send invisible spell casting + if (!IsNeedSendToClient()) + return; +diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp +index 699e485..32d52d9 100644 +--- a/src/server/game/Spells/SpellInfo.cpp ++++ b/src/server/game/Spells/SpellInfo.cpp +@@ -2230,6 +2230,11 @@ int32 SpellInfo::CalcPowerCost(Unit const* caster, SpellSchoolMask schoolMask) c + } + } + ++ //npcbot - apply bot spell cost mods ++ if (powerCost > 0 && caster->GetTypeId() == TYPEID_UNIT && caster->ToCreature()->GetBotAI()) ++ caster->ToCreature()->ApplyCreatureSpellCostMods(this, powerCost); ++ //end npcbot ++ + // PCT mod from user auras by school + powerCost = int32(powerCost * (1.0f + caster->GetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + school))); + if (powerCost < 0) +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index b720078..b40fbe7 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1566,6 +1566,9 @@ void World::SetInitialWorldSettings() + TC_LOG_INFO("server.loading", "Loading Creature templates..."); + sObjectMgr->LoadCreatureTemplates(); + ++ TC_LOG_INFO("server.loading", "Loading Creature template outfits..."); // must be after LoadCreatureTemplates ++ sObjectMgr->LoadCreatureOutfits(); ++ + TC_LOG_INFO("server.loading", "Loading Equipment templates..."); // must be after LoadCreatureTemplates + sObjectMgr->LoadEquipmentTemplates(); + +diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt +index a15b6f8..3136976 100644 +--- a/src/server/scripts/CMakeLists.txt ++++ b/src/server/scripts/CMakeLists.txt +@@ -63,6 +63,7 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/game/Addons + ${CMAKE_SOURCE_DIR}/src/server/game/AI + ${CMAKE_SOURCE_DIR}/src/server/game/AI/CoreAI ++ ${CMAKE_SOURCE_DIR}/src/server/game/AI/NpcBots + ${CMAKE_SOURCE_DIR}/src/server/game/AI/ScriptedAI + ${CMAKE_SOURCE_DIR}/src/server/game/AI/SmartScripts + ${CMAKE_SOURCE_DIR}/src/server/game/AuctionHouse +diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp +index fbd199b..7eb9117 100644 +--- a/src/server/scripts/Commands/cs_npc.cpp ++++ b/src/server/scripts/Commands/cs_npc.cpp +@@ -32,6 +32,8 @@ EndScriptData */ + #include "CreatureAI.h" + #include "Player.h" + #include "Pet.h" ++#include "bot_ai.h" ++#include "botmgr.h" + + template + struct EnumName +@@ -242,6 +244,16 @@ public: + if (!charID) + return false; + ++ uint32 id = atoi(charID); ++ ++ CreatureTemplate const* creInfo = sObjectMgr->GetCreatureTemplate(id); ++ ++ if (!(creInfo->flags_extra & CREATURE_FLAG_EXTRA_NPCBOT)) ++ { ++ char* charID = handler->extractKeyFromLink((char*)args, "Hcreature_entry"); ++ if (!charID) ++ return false; ++ + uint32 id = atoi(charID); + if (!sObjectMgr->GetCreatureTemplate(id)) + return false; +@@ -296,6 +308,97 @@ public: + + sObjectMgr->AddCreatureToGrid(db_guid, sObjectMgr->GetCreatureData(db_guid)); + return true; ++ } else { ++ char* charID = handler->extractKeyFromLink((char*)args, "Hcreature_entry"); ++ if (!charID) ++ return false; ++ ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_OWNER); ++ //"SELECT owner FROM character_npcbot WHERE entry = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ PreparedQueryResult res1 = CharacterDatabase.Query(stmt); ++ if (res1) ++ { ++ handler->PSendSysMessage("Npcbot %u already exists in `characters_npcbot` table!", id); ++ handler->SendSysMessage("If you want to replace this bot to new location use '.npc move' command"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_BY_ID); ++ //"SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ PreparedQueryResult res2 = WorldDatabase.Query(stmt); ++ if (res2) ++ { ++ handler->PSendSysMessage("Npcbot %u already exists in `creature` table!", id); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Player* chr = handler->GetSession()->GetPlayer(); ++ ++ if (chr->GetTransport()) ++ { ++ handler->SendSysMessage("Cannot spawn bots on transport!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ float x = chr->GetPositionX(); ++ float y = chr->GetPositionY(); ++ float z = chr->GetPositionZ(); ++ float o = chr->GetOrientation(); ++ Map* map = chr->GetMap(); ++ ++ if (map->Instanceable()) ++ { ++ handler->SendSysMessage("Cannot spawn bots in instances!"); ++ handler->SetSentErrorMessage(true); ++ return false; ++ } ++ ++ Creature* creature = new Creature(); ++ if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) ++ { ++ delete creature; ++ return false; ++ } ++ ++ uint8 roleMask = BOT_ROLE_DPS; ++ ++ uint8 m_class = creature->GetCreatureTemplate()->trainer_class; ++ if (!(m_class == CLASS_WARRIOR || m_class == CLASS_ROGUE || ++ m_class == CLASS_PALADIN || m_class == CLASS_DEATH_KNIGHT || ++ m_class == CLASS_SHAMAN || m_class == BOT_CLASS_BM)) ++ roleMask |= BOT_ROLE_RANGED; ++ if (m_class == CLASS_PRIEST || m_class == CLASS_DRUID || ++ m_class == CLASS_SHAMAN || m_class == CLASS_PALADIN) ++ roleMask |= BOT_ROLE_HEAL; ++ ++ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT); ++ //"INSERT INTO characters_npcbot (entry, roles) VALUES (?, ?)", CONNECTION_SYNCH ++ stmt->setUInt32(0, id); ++ stmt->setUInt8(1, roleMask); ++ CharacterDatabase.DirectExecute(stmt); ++ ++ creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); ++ ++ uint32 db_guid = creature->GetSpawnId(); ++ if (!creature->LoadBotCreatureFromDB(db_guid, map)) ++ { ++ handler->SendSysMessage("Cannot load npcbot from DB!"); ++ handler->SetSentErrorMessage(true); ++ //return false; ++ delete creature; ++ return false; ++ } ++ ++ sObjectMgr->AddCreatureToGrid(db_guid, sObjectMgr->GetCreatureData(db_guid)); ++ ++ handler->SendSysMessage("Npcbot successfully spawned."); ++ return true; ++ } + } + + //add item in vendorlist +diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp +index 8bd4b3e..328c19a 100644 +--- a/src/server/scripts/Spells/spell_paladin.cpp ++++ b/src/server/scripts/Spells/spell_paladin.cpp +@@ -125,6 +125,12 @@ class spell_pal_ardent_defender : public SpellScriptLoader + { + healPct = GetSpellInfo()->Effects[EFFECT_1].CalcValue(); + absorbPct = GetSpellInfo()->Effects[EFFECT_0].CalcValue(); ++ ++ //npcbot - allow for npcbots ++ if (GetUnitOwner()->GetTypeId() == TYPEID_UNIT && GetUnitOwner()->ToCreature()->IsNPCBot()) ++ return true; ++ //end npcbot ++ + return GetUnitOwner()->GetTypeId() == TYPEID_PLAYER; + } + +@@ -139,6 +145,40 @@ class spell_pal_ardent_defender : public SpellScriptLoader + Unit* victim = GetTarget(); + int32 remainingHealth = victim->GetHealth() - dmgInfo.GetDamage(); + uint32 allowedHealth = victim->CountPctFromMaxHealth(35); ++ ++ //npcbot - calc for bots ++ if (victim->GetTypeId() == TYPEID_UNIT/* && victim->ToCreature()->IsNPCBot()*/) ++ { ++ if (remainingHealth <= 0 && !victim->ToCreature()->GetSpellHistory()->HasCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL)) ++ { ++ // Cast healing spell, completely avoid damage ++ absorbAmount = dmgInfo.GetDamage(); ++ ++ uint32 defenseSkillValue = victim->GetDefenseSkillValue(); ++ // Max heal when defense skill denies critical hits from raid bosses ++ // Formula: max defense at level + 140 (raiting from gear) ++ uint32 reqDefForMaxHeal = victim->getLevel() * 5 + 140; ++ float pctFromDefense = (defenseSkillValue >= reqDefForMaxHeal) ++ ? 1.0f ++ : float(defenseSkillValue) / float(reqDefForMaxHeal); ++ ++ int32 healAmount = int32(victim->CountPctFromMaxHealth(int32(healPct * pctFromDefense))); ++ victim->CastCustomSpell(victim, PAL_SPELL_ARDENT_DEFENDER_HEAL, &healAmount, NULL, NULL, true, NULL, aurEff); ++ victim->ToCreature()->AddBotSpellCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL, 120 * IN_MILLISECONDS); ++ } ++ else if (remainingHealth < int32(allowedHealth)) ++ { ++ // Reduce damage that brings us under 35% (or full damage if we are already under 35%) by x% ++ uint32 damageToReduce = (victim->GetHealth() < allowedHealth) ++ ? dmgInfo.GetDamage() ++ : allowedHealth - remainingHealth; ++ absorbAmount = CalculatePct(damageToReduce, absorbPct); ++ } ++ ++ return; ++ } ++ //end npcbot ++ + // If damage kills us + if (remainingHealth <= 0 && !victim->GetSpellHistory()->HasCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL)) + { +diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp +index ecf5b7b..87cbdb1 100644 +--- a/src/server/scripts/Spells/spell_priest.cpp ++++ b/src/server/scripts/Spells/spell_priest.cpp +@@ -574,6 +574,7 @@ class spell_pri_penance : public SpellScriptLoader + + bool Load() override + { ++ if (GetCaster() && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->IsNPCBot()) return true; + return GetCaster()->GetTypeId() == TYPEID_PLAYER; + } + +@@ -616,6 +617,8 @@ class spell_pri_penance : public SpellScriptLoader + SpellCastResult CheckCast() + { + Player* caster = GetCaster()->ToPlayer(); ++ if (!caster && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->IsNPCBot()) ++ caster = (Player*)GetCaster(); + if (Unit* target = GetExplTargetUnit()) + if (!caster->IsFriendlyTo(target)) + { +@@ -762,6 +765,7 @@ class spell_pri_renew : public SpellScriptLoader + + bool Load() override + { ++ if (GetCaster() && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->IsNPCBot()) return true; + return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER; + } + +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 17870e7..7a93cf9 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -3546,6 +3546,128 @@ PacketSpoof.BanDuration = 86400 + # + ################################################################################################### + ++################################################################################################### ++# NPCBOT CONFIGURATION ++# ++# NpcBot.Enable ++# Description: Enable NpcBot system ++# Default: 1 - enable ++# 0 - disable ++ ++NpcBot.Enable = 1 ++ ++# ++# NpcBot.AllowGM ++# Description: Allow GM's to have NpcBots ++# Default: 1 - Allow ++# 0 - disable ++ ++NpcBot.AllowGM = 1 ++ ++# NpcBot.MaxBots ++# Description: Maximum number of bots allowed for players. ++# Default: 1 ++# Recommend: 1-2 ++# Max: 4 ++ ++NpcBot.MaxBots = 1 ++ ++# NpcBot.MaxBotsPerClass ++# Description: Maximum bots of each class allowed for players. ++# Default: 1 ++# 0 - no limit ++ ++NpcBot.MaxBotsPerClass = 1 ++ ++# NpcBot.BaseFollowDistance ++# Description: Default bot follow distance. ++# Note: This parameter determines bots' formation size, distance at which bots will chase and attack enemies. ++# Note2: This parameter is set for each player at login. ++# Default: 30 ++ ++NpcBot.BaseFollowDistance = 20 ++ ++# NpcBot.XpReduction ++# Description: XP percent penalty for each bot used starting with second. ++# Example: 3 bots, xp reduction is 20: ((3-1)*20) = 40%, 60% exp gained only. ++# Note: Maximum overall xp reduction is 90%. ++# Default: 0 ++ ++NpcBot.XpReduction = 0 ++ ++# NpcBot.HealTargetIconsMask ++# Description: Icon number bitmask which bots use to search for additional targets to heal (out of party). ++# Note: Many creatures cannot accept heal. ++# Example: to check Star, Triangle and Square we need 1 + 8 + 32 = 41. ++# Default: 0 (Disable) ++# 1 - Star ++# 2 - Circle ++# 4 - Diamond ++# 8 - Triangle ++# 16 - Moon ++# 32 - Square ++# 64 - Cross ++# 128 - Skull ++ ++NpcBot.HealTargetIconsMask = 0 ++ ++# NpcBot.Mult.Damage.Melee ++# NpcBot.Mult.Damage.Spell ++# NpcBot.Mult.Healing ++# Description: Multipliers for bots' damage and healing. Allows to balance bots vs players. ++# Minimum: 0.1 ++# Maximum: 10.0 ++# Default: 1.0 ++ ++NpcBot.Mult.Damage.Melee = 1.0 ++NpcBot.Mult.Damage.Spell = 1.0 ++NpcBot.Mult.Healing = 1.0 ++ ++# NpcBot.Enable.Dungeon ++# NpcBot.Enable.Raid ++# NpcBot.Enable.BG ++# NpcBot.Enable.Arena ++# NpcBot.Enable.DungeonFinder ++# Description: Allow bots to enter PvE/PvP areas and Dungeon Finder query ++# Default: 1 - (NpcBot.Enable.Dungeon) ++# 0 - (NpcBot.Enable.Raid) ++# 0 - (NpcBot.Enable.BG) ++# 0 - (NpcBot.Enable.Arena) ++# 1 - (NpcBot.Enable.DungeonFinder) ++ ++NpcBot.Enable.Dungeon = 1 ++NpcBot.Enable.Raid = 0 ++NpcBot.Enable.BG = 0 ++NpcBot.Enable.Arena = 0 ++NpcBot.Enable.DungeonFinder = 1 ++ ++# NpcBot.Limit.Dungeon ++# NpcBot.Limit.Raid ++# Description: Enable/Disable instance players limitation rules for bots. ++# Default: 1 - (NpcBot.Limit.Dungeon) ++# 1 - (NpcBot.Limit.Raid) ++ ++NpcBot.Limit.Dungeon = 1 ++NpcBot.Limit.Raid = 1 ++ ++# NpcBot.Cost ++# Description: Bot recruitment cost (in copper). ++# Note: This value is for level 80 characters. ++# Cost is reduced for lower levels by simple formula: (cost * level / 80). ++# Default: 1000000 (100 gold, 1g25s at level 1) ++ ++NpcBot.Cost = 1000000 ++ ++# NpcBot.PvP ++# Description: Allow bots to attack player-controlled units (players, pets, bots, etc.) ++# Note: This rule only applies to player-controlled bots ++# Default: 1 ++ ++NpcBot.PvP = 1 ++ ++# ++################################################################################################### ++ + # + # Prepatch by LordPsyan. + # See http://www.realmsofwarcraft.com/bb for forums and information. +-- +2.1.4 diff --git a/pvp_ranks.diff b/pvp_ranks.diff new file mode 100644 index 0000000..4a37d14 --- /dev/null +++ b/pvp_ranks.diff @@ -0,0 +1,229 @@ + src/server/game/Entities/Player/Player.cpp | 81 ++++++++++++++++++++++++++---- + src/server/game/Entities/Player/Player.h | 22 ++++++++ + src/server/game/World/World.cpp | 12 +++++ + src/server/game/World/World.h | 23 +++++++++ + 4 files changed, 129 insertions(+), 9 deletions(-) + +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..c926db6 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -6790,6 +6790,7 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + + ObjectGuid victim_guid; + uint32 victim_rank = 0; ++ uint32 rank_diff = 0; + + // need call before fields update to have chance move yesterday data to appropriate fields before today data change. + UpdateHonorFields(); +@@ -6832,17 +6833,54 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + // title[1..14] -> rank[5..18] + // title[15..28] -> rank[5..18] + // title[other] -> 0 +- if (victim_title == 0) +- victim_guid.Clear(); // Don't show HK: message, only log. +- else if (victim_title < 15) +- victim_rank = victim_title + 4; +- else if (victim_title < 29) +- victim_rank = victim_title - 14 + 4; +- else +- victim_guid.Clear(); // Don't show HK: message, only log. ++ // PLAYER__FIELD_KNOWN_TITLES describe which titles player can use, ++ // so we must find biggest pvp title , even for killer to find extra honor value ++ uint32 vtitle = victim->GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES); ++ //uint32 victim_title = 0; ++ uint32 ktitle = GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES); ++ uint32 killer_title = 0; ++ if (PLAYER_TITLE_MASK_ALL_PVP & ktitle) ++ { ++ for (int i = ((GetTeam() == ALLIANCE) ? 1:HKRANKMAX);i!=((GetTeam() == ALLIANCE) ? HKRANKMAX : (2*HKRANKMAX-1));i++) ++ { ++ if (ktitle & (1<GetTeam() == ALLIANCE) ? 1:HKRANKMAX);i!=((plrVictim->GetTeam() == ALLIANCE) ? HKRANKMAX : (2*HKRANKMAX-1));i++) ++ { ++ if (vtitle & (1< rank[5..18] ++ // title[15..28] -> rank[5..18] ++ // title[other] -> 0 ++ if (victim_title == 0) ++ victim_guid.Clear(); // Don't show HK: message, only log. ++ else if (victim_title < HKRANKMAX) ++ victim_rank = victim_title + 4; ++ else if (victim_title < (2*HKRANKMAX-1)) ++ victim_rank = victim_title - (HKRANKMAX-1) + 4; ++ else ++ victim_guid.Clear(); // Don't show HK: message, only log. + +- honor_f = std::ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); ++ // now find rank difference ++ if (killer_title == 0 && victim_rank>4) ++ rank_diff = victim_rank - 4; ++ else if (killer_title < HKRANKMAX) ++ rank_diff = (victim_rank>(killer_title + 4))? (victim_rank - (killer_title + 4)) : 0; ++ else if (killer_title < (2*HKRANKMAX-1)) ++ rank_diff = (victim_rank>(killer_title - (HKRANKMAX-1) +4))? (victim_rank - (killer_title - (HKRANKMAX-1) + 4)) : 0; + ++ ++ honor_f = ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); ++ honor *= 1 + sWorld->getRate(RATE_PVP_RANK_EXTRA_HONOR)*(((float)rank_diff) / 10.0f); + // count the number of playerkills in one day + ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true); + // and those in a lifetime +@@ -6850,6 +6888,7 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, victim->getClass()); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, victim->getRace()); ++ UpdateKnownTitles(); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId()); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); + } +@@ -6967,6 +7006,30 @@ void Player::SetArenaPoints(uint32 value) + AddKnownCurrency(ITEM_ARENA_POINTS_ID); + } + ++void Player::UpdateKnownTitles() ++{ ++ uint32 new_title = 0; ++ uint32 honor_kills = GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS); ++ uint32 old_title = GetUInt32Value(PLAYER_CHOSEN_TITLE); ++ RemoveFlag64(PLAYER__FIELD_KNOWN_TITLES,PLAYER_TITLE_MASK_ALL_PVP); ++ // if (honor_kills < 0) ++ // return; ++ bool max_rank = ((honor_kills >= sWorld->pvp_ranks[HKRANKMAX-1]) ? true : false); ++ for (int i = HKRANK01; i != HKRANKMAX; ++i) ++ { ++ if (honor_kills < sWorld->pvp_ranks[i] || (max_rank)) ++ { ++ new_title = ((max_rank) ? (HKRANKMAX-1) : (i-1)); ++ if (new_title > 0) ++ new_title += ((GetTeam() == ALLIANCE) ? 0 : (HKRANKMAX-1)); ++ break; ++ } ++ } ++ SetFlag64(PLAYER__FIELD_KNOWN_TITLES,uint64(1) << new_title); ++ if (old_title > 0 && old_title < (2*HKRANKMAX-1) && new_title > old_title) ++ SetUInt32Value(PLAYER_CHOSEN_TITLE,new_title); ++} ++ + void Player::ModifyHonorPoints(int32 value, SQLTransaction trans) + { + int32 newValue = int32(GetHonorPoints()) + value; +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index b7d7d81..d80e242 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -414,6 +414,27 @@ enum PlayerFlags + PLAYER_FLAGS_UNK31 = 0x80000000 + }; + ++#define PLAYER_TITLE_MASK_ALLIANCE_PVP \ ++ (PLAYER_TITLE_PRIVATE | PLAYER_TITLE_CORPORAL | \ ++ PLAYER_TITLE_SERGEANT_A | PLAYER_TITLE_MASTER_SERGEANT | \ ++ PLAYER_TITLE_SERGEANT_MAJOR | PLAYER_TITLE_KNIGHT | \ ++ PLAYER_TITLE_KNIGHT_LIEUTENANT | PLAYER_TITLE_KNIGHT_CAPTAIN | \ ++ PLAYER_TITLE_KNIGHT_CHAMPION | PLAYER_TITLE_LIEUTENANT_COMMANDER | \ ++ PLAYER_TITLE_COMMANDER | PLAYER_TITLE_MARSHAL | \ ++ PLAYER_TITLE_FIELD_MARSHAL | PLAYER_TITLE_GRAND_MARSHAL) ++ ++#define PLAYER_TITLE_MASK_HORDE_PVP \ ++ (PLAYER_TITLE_SCOUT | PLAYER_TITLE_GRUNT | \ ++ PLAYER_TITLE_SERGEANT_H | PLAYER_TITLE_SENIOR_SERGEANT | \ ++ PLAYER_TITLE_FIRST_SERGEANT | PLAYER_TITLE_STONE_GUARD | \ ++ PLAYER_TITLE_BLOOD_GUARD | PLAYER_TITLE_LEGIONNAIRE | \ ++ PLAYER_TITLE_CENTURION | PLAYER_TITLE_CHAMPION | \ ++ PLAYER_TITLE_LIEUTENANT_GENERAL | PLAYER_TITLE_GENERAL | \ ++ PLAYER_TITLE_WARLORD | PLAYER_TITLE_HIGH_WARLORD) ++ ++#define PLAYER_TITLE_MASK_ALL_PVP \ ++ (PLAYER_TITLE_MASK_ALLIANCE_PVP | PLAYER_TITLE_MASK_HORDE_PVP) ++ + // used for PLAYER__FIELD_KNOWN_TITLES field (uint64), (1< + void ModifyHonorPoints(int32 value, SQLTransaction trans = SQLTransaction(nullptr)); //! If trans is specified, honor save query will be added to trans + void ModifyArenaPoints(int32 value, SQLTransaction trans = SQLTransaction(nullptr)); //! If trans is specified, arena point save query will be added to trans + uint32 GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) const; ++ void UpdateKnownTitles(); + void SetHonorPoints(uint32 value); + void SetArenaPoints(uint32 value); + +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..b053e2f 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -871,6 +871,18 @@ void World::LoadConfigSettings(bool reload) + TC_LOG_ERROR("server.loading", "MinPetitionSigns (%i) must be in range 0..9. Set to 9.", m_int_configs[CONFIG_MIN_PETITION_SIGNS]); + m_int_configs[CONFIG_MIN_PETITION_SIGNS] = 9; + } ++ rate_values[RATE_PVP_RANK_EXTRA_HONOR] = sConfigMgr->GetFloatDefault("PvPRank.Rate.ExtraHonor", 1); ++ std::string s_pvp_ranks = sConfigMgr->GetStringDefault("PvPRank.HKPerRank", "10,50,100,200,450,750,1300,2000,3500,6000,9500,15000,21000,30000"); ++ char *c_pvp_ranks = const_cast(s_pvp_ranks.c_str()); ++ for (int i = 0; i !=HKRANKMAX; i++) ++ { ++ if (i==0) ++ pvp_ranks[0] = 0; ++ else if (i==1) ++ pvp_ranks[1] = atoi(strtok (c_pvp_ranks, ",")); ++ else ++ pvp_ranks[i] = atoi(strtok (NULL, ",")); ++ } + + m_int_configs[CONFIG_GM_LOGIN_STATE] = sConfigMgr->GetIntDefault("GM.LoginState", 2); + m_int_configs[CONFIG_GM_VISIBLE_STATE] = sConfigMgr->GetIntDefault("GM.Visible", 2); +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 7a3e56c..2fe3cb4 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -511,6 +511,7 @@ enum Rates + RATE_CORPSE_DECAY_LOOTED, + RATE_INSTANCE_RESET_TIME, + RATE_TARGET_POS_RECALCULATION_RANGE, ++ RATE_PVP_RANK_EXTRA_HONOR, + RATE_DURABILITY_LOSS_ON_DEATH, + RATE_DURABILITY_LOSS_DAMAGE, + RATE_DURABILITY_LOSS_PARRY, +@@ -522,6 +523,26 @@ enum Rates + MAX_RATES + }; + ++enum HonorKillPvPRank ++{ ++ HKRANK00, ++ HKRANK01, ++ HKRANK02, ++ HKRANK03, ++ HKRANK04, ++ HKRANK05, ++ HKRANK06, ++ HKRANK07, ++ HKRANK08, ++ HKRANK09, ++ HKRANK10, ++ HKRANK11, ++ HKRANK12, ++ HKRANK13, ++ HKRANK14, ++ HKRANKMAX ++}; ++ + /// Can be used in SMSG_AUTH_RESPONSE packet + enum BillingPlanFlags + { +@@ -750,6 +771,8 @@ class World + bool SendZoneMessage(uint32 zone, WorldPacket* packet, WorldSession* self = nullptr, uint32 team = 0); + void SendZoneText(uint32 zone, const char *text, WorldSession* self = nullptr, uint32 team = 0); + ++ uint32 pvp_ranks[HKRANKMAX]; ++ \ No newline at end of file diff --git a/transmog_global.diff b/transmog_global.diff new file mode 100644 index 0000000..27eb6d5 --- /dev/null +++ b/transmog_global.diff @@ -0,0 +1,1595 @@ +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index a60bfb4a..f5db054 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -16,6 +16,7 @@ + * with this program. If not, see . + */ + ++#include "../../../scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h" + #include "Player.h" + #include "AccountMgr.h" + #include "AchievementMgr.h" +@@ -12077,7 +12078,10 @@ void Player::SetVisibleItemSlot(uint8 slot, Item* pItem) + { + if (pItem) + { +- SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry()); ++ if (uint32 entry = TransmogDisplayVendorMgr::GetFakeEntry(pItem)) ++ SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), entry); ++ else ++ SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry()); + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, pItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 1, pItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + } +@@ -12203,6 +12207,7 @@ void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update) + { + if (Item* it = GetItemByPos(bag, slot)) + { ++ TransmogDisplayVendorMgr::DeleteFakeEntry(this, it); + ItemRemovedQuestCheck(it->GetEntry(), it->GetCount()); + RemoveItem(bag, slot, update); + it->SetNotRefundable(this, false); +@@ -21274,23 +21279,29 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin + return false; + } + +- if (!(pProto->AllowableClass & getClassMask()) && pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster()) ++ Creature* creature = GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR); ++ if (!creature) + { +- SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, item, 0); ++ TC_LOG_DEBUG("network", "WORLD: BuyItemFromVendor - %s not found or you can't interact with him.", vendorguid.ToString().c_str()); ++ SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, NULL, item, 0); + return false; + } + +- if (!IsGameMaster() && ((pProto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY && GetTeam() == ALLIANCE) || (pProto->Flags2 == ITEM_FLAGS_EXTRA_ALLIANCE_ONLY && GetTeam() == HORDE))) ++ if (creature->GetScriptName() == "NPC_TransmogDisplayVendor") ++ { ++ TransmogDisplayVendorMgr::HandleTransmogrify(this, creature, vendorslot, item); + return false; ++ } + +- Creature* creature = GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR); +- if (!creature) ++ if (!(pProto->AllowableClass & getClassMask()) && pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster()) + { +- TC_LOG_DEBUG("network", "WORLD: BuyItemFromVendor - %s not found or you can't interact with him.", vendorguid.ToString().c_str()); +- SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, NULL, item, 0); ++ SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, item, 0); + return false; + } + ++ if (!IsGameMaster() && ((pProto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY && GetTeam() == ALLIANCE) || (pProto->Flags2 == ITEM_FLAGS_EXTRA_ALLIANCE_ONLY && GetTeam() == HORDE))) ++ return false; ++ + if (!sConditionMgr->IsObjectMeetingVendorItemConditions(creature->GetEntry(), item, this, creature)) + { + TC_LOG_DEBUG("condition", "BuyItemFromVendor: conditions not met for creature entry %u item %u", creature->GetEntry(), item); +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index 2388cf9..bfe096f 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -126,6 +126,18 @@ struct SpellModifier + Aura* const ownerAura; + }; + ++typedef std::unordered_map TransmogMapType; ++ ++#ifdef PRESETS ++typedef std::map PresetslotMapType; ++struct PresetData ++{ ++ std::string name; ++ PresetslotMapType slotMap; // slotMap[slotId] = entry ++}; ++typedef std::map PresetMapType; ++#endif ++ + typedef std::unordered_map PlayerTalentMap; + typedef std::unordered_map PlayerSpellMap; + typedef std::list SpellModList; +@@ -2249,6 +2261,11 @@ class Player : public Unit, public GridObject + std::string GetMapAreaAndZoneString(); + std::string GetCoordsMapAreaAndZoneString(); + ++ TransmogMapType transmogMap; // transmogMap[iGUID] = entry ++#ifdef PRESETS ++ PresetMapType presetMap; // presetMap[presetId] = presetData ++#endif ++ + protected: + // Gamemaster whisper whitelist + GuidList WhisperList; +diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp +index 6be1fd3..11c16f1 100644 +--- a/src/server/game/Handlers/SpellHandler.cpp ++++ b/src/server/game/Handlers/SpellHandler.cpp +@@ -16,6 +16,7 @@ + * with this program. If not, see . + */ + ++#include "../../scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h" + #include "Common.h" + #include "DBCStores.h" + #include "WorldPacket.h" +@@ -625,7 +626,12 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) + else if (*itr == EQUIPMENT_SLOT_BACK && player->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK)) + data << uint32(0); + else if (Item const* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, *itr)) +- data << uint32(item->GetTemplate()->DisplayInfoID); ++ { ++ if (uint32 entry = TransmogDisplayVendorMgr::GetFakeEntry(item)) ++ data << uint32(sObjectMgr->GetItemTemplate(entry)->DisplayInfoID); ++ else ++ data << uint32(item->GetTemplate()->DisplayInfoID); ++ } + else + data << uint32(0); + } +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index 2922e4e..cba162b 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -1410,6 +1410,7 @@ void AddBattlegroundScripts() + #ifdef SCRIPTS + /* This is where custom scripts' loading functions should be declared. */ + ++void AddSC_NPC_TransmogDisplayVendor(); + #endif + + void AddCustomScripts() +@@ -1417,5 +1418,6 @@ void AddCustomScripts() + #ifdef SCRIPTS + /* This is where custom scripts should be added. */ + ++ AddSC_NPC_TransmogDisplayVendor(); + #endif + } +diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt +index 595ff80..283136e 100644 +--- a/src/server/scripts/Custom/CMakeLists.txt ++++ b/src/server/scripts/Custom/CMakeLists.txt +@@ -13,6 +13,8 @@ + set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} + # ${sources_Custom} ++ Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h ++ Custom/TransmogDisplayVendor/TransmogDisplayVendor.cpp + ) + + message(" -> Prepared: Custom") +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/README.md b/src/server/scripts/Custom/TransmogDisplayVendor/README.md +new file mode 100644 +index 0000000..de921d6 +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/README.md +@@ -0,0 +1,49 @@ ++#Transmogrification Display Vendor [![Build Status](https://travis-ci.org/Rochet2/TrinityCore.svg?branch=transmogvendor)](https://travis-ci.org/Rochet2/TrinityCore) ++ ++####About ++Original idea by LilleCarl. ++Coding work and execution by Rochet2. ++Transmogrification Display Vendor allows you to change the display of an item to something else. ++You can use any item display in the game, as long as it fits the requirements. ++Requirements can be tweaked in the `TransmogDisplayVendor.cpp` file. ++Basically any item should work with transmogrification. Custom items as well. No item is hardcoded to the system. ++Has a feature to work with the regular [Transmogrification](http://rochet2.github.io/Transmogrification.html). This can be enabled before compiling in `TransmogDisplayVendor.h`. ++Made for 3.3.5a.
++Source: http://www.trinitycore.org/f/topic/7993-transmogrification-display-vendor/ ++ ++Video: https://youtu.be/PIheEziN_dY ++ ++####Installation ++ ++Available as: ++- Direct merge: https://github.com/Rochet2/TrinityCore/tree/transmogvendor ++- Diff: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...transmogvendor.diff ++- Diff in github view: https://github.com/Rochet2/TrinityCore/compare/TrinityCore:3.3.5...transmogvendor ++ ++Using direct merge: ++- open git bash to source location ++- do `git remote add rochet2 https://github.com/Rochet2/TrinityCore.git` ++- do `git pull rochet2 transmogvendor` ++- use cmake and compile ++ ++Using diff *(recommended)*: ++- DO NOT COPY THE DIFF DIRECTLY! It causes apply to fail. ++- download the diff by __right clicking__ the link and select __Save link as__ ++- place the downloaded `transmogvendor.diff` to the source root folder ++- open git bash to source location ++- do `git apply transmogvendor.diff` ++ - if using the regular transmogrification, simply use --reject with either and overwrite the parts of the other. Order doesnt matter, as long as duplicate code doesnt exist. ++- use cmake and compile ++ ++After compiling: ++- Navigate to `\src\server\scripts\Custom\TransmogDisplayVendor\sql\` ++- Run `characters.sql` to your characters database ++ - This is same file as with the regular transmog ++- Optionally you can also insert a transmogrifier NPC to your database by running `world_NPC.sql` to your world database. ++ ++####Usage ++Equip an item that is suitable for transmogrification. ++Talk to Transmogrifier and select the item slot. Then select the quality and then the item you want to transmogrify to. ++ ++####Bugs and Contact ++Report issues and similar to http://rochet2.github.io/ +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendor.cpp b/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendor.cpp +new file mode 100644 +index 0000000..81e037e +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendor.cpp +@@ -0,0 +1,1155 @@ ++/* ++Transmog display vendor ++Code by Rochet2 ++Ideas LilleCarl ++ ++ScriptName for NPC: ++NPC_TransmogDisplayVendor ++ ++Compatible with Transmogrification 6.1 by Rochet2 ++http://rochet2.github.io/Transmogrification ++*/ ++ ++#include "TransmogDisplayVendorConf.h" ++#include "Bag.h" ++#include "Common.h" ++#include "Config.h" ++#include "Creature.h" ++#include "DatabaseEnv.h" ++#include "DBCStructure.h" ++#include "Define.h" ++#include "Field.h" ++#include "GameEventMgr.h" ++#include "GossipDef.h" ++#include "Item.h" ++#include "ItemPrototype.h" ++#include "Language.h" ++#include "Log.h" ++#include "Player.h" ++#include "ObjectGuid.h" ++#include "ObjectMgr.h" ++#include "QueryResult.h" ++#include "ScriptedGossip.h" ++#include "ScriptMgr.h" ++#include "SharedDefines.h" ++#include "Transaction.h" ++#include "WorldSession.h" ++#include ++#include ++ ++// Config start ++ ++// Edit Transmogrification compatibility in TransmogDisplayVendorConf.h ++ ++// A multiplier for the default gold cost (change to 0.0f for no default cost) ++const float TransmogDisplayVendorMgr::ScaledCostModifier = 1.0f; ++// Cost added on top of other costs (can be negative) ++const int32 TransmogDisplayVendorMgr::CopperCost = 0; ++// For custom gold cost set ScaledCostModifier to 0.0f and CopperCost to what ever cost you want ++ ++const bool TransmogDisplayVendorMgr::RequireToken = false; ++const uint32 TransmogDisplayVendorMgr::TokenEntry = 49426; ++const uint32 TransmogDisplayVendorMgr::TokenAmount = 1; ++ ++const bool TransmogDisplayVendorMgr::AllowPoor = false; ++const bool TransmogDisplayVendorMgr::AllowCommon = false; ++const bool TransmogDisplayVendorMgr::AllowUncommon = true; ++const bool TransmogDisplayVendorMgr::AllowRare = true; ++const bool TransmogDisplayVendorMgr::AllowEpic = true; ++const bool TransmogDisplayVendorMgr::AllowLegendary = false; ++const bool TransmogDisplayVendorMgr::AllowArtifact = false; ++const bool TransmogDisplayVendorMgr::AllowHeirloom = true; ++ ++const bool TransmogDisplayVendorMgr::AllowMixedArmorTypes = false; ++const bool TransmogDisplayVendorMgr::AllowMixedWeaponTypes = false; ++const bool TransmogDisplayVendorMgr::AllowFishingPoles = false; ++ ++const bool TransmogDisplayVendorMgr::IgnoreReqRace = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqClass = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqSkill = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqSpell = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqLevel = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqEvent = false; ++const bool TransmogDisplayVendorMgr::IgnoreReqStats = false; ++ ++// Example AllowedItems[] = { 123, 234, 345 }; ++static const uint32 AllowedItems[] = { 0 }; ++static const uint32 NotAllowedItems[] = { 0 }; ++ ++// Config end ++ ++std::set TransmogDisplayVendorMgr::Allowed; ++std::set TransmogDisplayVendorMgr::NotAllowed; ++ ++#ifndef UNORDERED_MAP ++#define UNORDERED_MAP std::unordered_map ++#endif ++ ++#ifdef BOOST_VERSION ++#define USING_BOOST ++#endif ++#ifdef USING_BOOST ++#include ++#include ++#endif ++ ++namespace ++{ ++ class RWLockable ++ { ++ public: ++#ifdef USING_BOOST ++ typedef boost::shared_mutex LockType; ++ typedef boost::shared_lock ReadGuard; ++ typedef boost::unique_lock WriteGuard; ++#else ++ typedef ACE_RW_Thread_Mutex LockType; ++ typedef ACE_Read_Guard ReadGuard; ++ typedef ACE_Write_Guard WriteGuard; ++#endif ++ LockType& GetLock() { return _lock; } ++ private: ++ LockType _lock; ++ }; ++ ++ class SelectionStore : public RWLockable ++ { ++ public: ++ struct Selection { uint32 item; uint8 slot; uint32 offset; uint32 quality; }; ++ typedef UNORDERED_MAP PlayerLowToSelection; ++ ++ void SetSelection(uint32 playerLow, const Selection& selection) ++ { ++ WriteGuard guard(GetLock()); ++ hashmap[playerLow] = selection; ++ } ++ ++ bool GetSelection(uint32 playerLow, Selection& returnVal) ++ { ++ ReadGuard guard(GetLock()); ++ ++ PlayerLowToSelection::iterator it = hashmap.find(playerLow); ++ if (it == hashmap.end()) ++ return false; ++ ++ returnVal = it->second; ++ return true; ++ } ++ ++ void RemoveSelection(uint32 playerLow) ++ { ++ WriteGuard guard(GetLock()); ++ hashmap.erase(playerLow); ++ } ++ ++ private: ++ PlayerLowToSelection hashmap; ++ }; ++}; ++ ++// Selection store ++static SelectionStore selectionStore; // selectionStore[lowGUID] = Selection ++ ++// Vendor data store ++// optionMap[Class? + SubClass][invtype][Quality] = EntryVector ++typedef std::vector EntryVector; ++static EntryVector* optionMap[MAX_ITEM_SUBCLASS_WEAPON + MAX_ITEM_SUBCLASS_ARMOR][MAX_INVTYPE][MAX_ITEM_QUALITY]; ++ ++uint32 TransmogDisplayVendorMgr::GetFakeEntry(const Item* item) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::GetFakeEntry"); ++ ++ Player* owner = item->GetOwner(); ++ ++ if (!owner) ++ return 0; ++ if (owner->transmogMap.empty()) ++ return 0; ++ ++ TransmogMapType::const_iterator it = owner->transmogMap.find(item->GetGUID()); ++ if (it == owner->transmogMap.end()) ++ return 0; ++ return it->second; ++} ++void TransmogDisplayVendorMgr::DeleteFakeEntry(Player* player, Item* item) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::DeleteFakeEntry"); ++ ++ if (player->transmogMap.erase(item->GetGUID()) != 0) ++ UpdateItem(player, item); ++} ++void TransmogDisplayVendorMgr::SetFakeEntry(Player* player, Item* item, uint32 entry) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::SetFakeEntry"); ++ ++ player->transmogMap[item->GetGUID()] = entry; ++ UpdateItem(player, item); ++} ++void TransmogDisplayVendorMgr::UpdateItem(Player* player, Item* item) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::UpdateItem"); ++ ++ if (item->IsEquipped()) ++ { ++ player->SetVisibleItemSlot(item->GetSlot(), item); ++ if (player->IsInWorld()) ++ item->SendUpdateToPlayer(player); ++ } ++} ++const char* TransmogDisplayVendorMgr::getSlotName(uint8 slot, WorldSession* /*session*/) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::TransmogDisplayVendorMgr::getSlotName"); ++ ++ switch (slot) ++ { ++ case EQUIPMENT_SLOT_HEAD: return "Head";// session->GetTrinityString(LANG_SLOT_NAME_HEAD); ++ case EQUIPMENT_SLOT_SHOULDERS: return "Shoulders";// session->GetTrinityString(LANG_SLOT_NAME_SHOULDERS); ++ case EQUIPMENT_SLOT_BODY: return "Shirt";// session->GetTrinityString(LANG_SLOT_NAME_BODY); ++ case EQUIPMENT_SLOT_CHEST: return "Chest";// session->GetTrinityString(LANG_SLOT_NAME_CHEST); ++ case EQUIPMENT_SLOT_WAIST: return "Waist";// session->GetTrinityString(LANG_SLOT_NAME_WAIST); ++ case EQUIPMENT_SLOT_LEGS: return "Legs";// session->GetTrinityString(LANG_SLOT_NAME_LEGS); ++ case EQUIPMENT_SLOT_FEET: return "Feet";// session->GetTrinityString(LANG_SLOT_NAME_FEET); ++ case EQUIPMENT_SLOT_WRISTS: return "Wrists";// session->GetTrinityString(LANG_SLOT_NAME_WRISTS); ++ case EQUIPMENT_SLOT_HANDS: return "Hands";// session->GetTrinityString(LANG_SLOT_NAME_HANDS); ++ case EQUIPMENT_SLOT_BACK: return "Back";// session->GetTrinityString(LANG_SLOT_NAME_BACK); ++ case EQUIPMENT_SLOT_MAINHAND: return "Main hand";// session->GetTrinityString(LANG_SLOT_NAME_MAINHAND); ++ case EQUIPMENT_SLOT_OFFHAND: return "Off hand";// session->GetTrinityString(LANG_SLOT_NAME_OFFHAND); ++ case EQUIPMENT_SLOT_RANGED: return "Ranged";// session->GetTrinityString(LANG_SLOT_NAME_RANGED); ++ case EQUIPMENT_SLOT_TABARD: return "Tabard";// session->GetTrinityString(LANG_SLOT_NAME_TABARD); ++ default: return NULL; ++ } ++} ++uint32 TransmogDisplayVendorMgr::GetSpecialPrice(ItemTemplate const* proto) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::GetSpecialPrice"); ++ ++ uint32 cost = proto->SellPrice < 10000 ? 10000 : proto->SellPrice; ++ return cost; ++} ++bool TransmogDisplayVendorMgr::CanTransmogrifyItemWithItem(Player* player, ItemTemplate const* target, ItemTemplate const* source) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::CanTransmogrifyItemWithItem"); ++ ++ if (!target || !source) ++ return false; ++ ++ if (source->ItemId == target->ItemId) ++ return false; ++ ++ if (source->DisplayInfoID == target->DisplayInfoID) ++ return false; ++ ++ if (source->Class != target->Class) ++ return false; ++ ++ if (source->InventoryType == INVTYPE_BAG || ++ source->InventoryType == INVTYPE_RELIC || ++ // source->InventoryType == INVTYPE_BODY || ++ source->InventoryType == INVTYPE_FINGER || ++ source->InventoryType == INVTYPE_TRINKET || ++ source->InventoryType == INVTYPE_AMMO || ++ source->InventoryType == INVTYPE_QUIVER) ++ return false; ++ ++ if (target->InventoryType == INVTYPE_BAG || ++ target->InventoryType == INVTYPE_RELIC || ++ // target->InventoryType == INVTYPE_BODY || ++ target->InventoryType == INVTYPE_FINGER || ++ target->InventoryType == INVTYPE_TRINKET || ++ target->InventoryType == INVTYPE_AMMO || ++ target->InventoryType == INVTYPE_QUIVER) ++ return false; ++ ++ if (!SuitableForTransmogrification(player, target) || !SuitableForTransmogrification(player, source)) // if (!transmogrified->CanTransmogrify() || !transmogrifier->CanBeTransmogrified()) ++ return false; ++ ++ if (IsRangedWeapon(source->Class, source->SubClass) != IsRangedWeapon(target->Class, target->SubClass)) ++ return false; ++ ++ if (source->SubClass != target->SubClass && !IsRangedWeapon(target->Class, target->SubClass)) ++ { ++ if (source->Class == ITEM_CLASS_ARMOR && !AllowMixedArmorTypes) ++ return false; ++ if (source->Class == ITEM_CLASS_WEAPON && !AllowMixedWeaponTypes) ++ return false; ++ } ++ ++ if (source->InventoryType != target->InventoryType) ++ { ++ if (source->Class == ITEM_CLASS_WEAPON && !((IsRangedWeapon(target->Class, target->SubClass) || ++ ((target->InventoryType == INVTYPE_WEAPON || target->InventoryType == INVTYPE_2HWEAPON) && ++ (source->InventoryType == INVTYPE_WEAPON || source->InventoryType == INVTYPE_2HWEAPON)) || ++ ((target->InventoryType == INVTYPE_WEAPONMAINHAND || target->InventoryType == INVTYPE_WEAPONOFFHAND) && ++ (source->InventoryType == INVTYPE_WEAPON || source->InventoryType == INVTYPE_2HWEAPON))))) ++ return false; ++ if (source->Class == ITEM_CLASS_ARMOR && ++ !((source->InventoryType == INVTYPE_CHEST || source->InventoryType == INVTYPE_ROBE) && ++ (target->InventoryType == INVTYPE_CHEST || target->InventoryType == INVTYPE_ROBE))) ++ return false; ++ } ++ ++ return true; ++} ++bool TransmogDisplayVendorMgr::SuitableForTransmogrification(Player* player, ItemTemplate const* proto) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::SuitableForTransmogrification"); ++ ++ // ItemTemplate const* proto = item->GetTemplate(); ++ if (!proto) ++ return false; ++ ++ if (proto->Class != ITEM_CLASS_ARMOR && ++ proto->Class != ITEM_CLASS_WEAPON) ++ return false; ++ ++ // Skip all checks for allowed items ++ if (IsAllowed(proto->ItemId)) ++ return true; ++ ++ if (IsNotAllowed(proto->ItemId)) ++ return false; ++ ++ if (!AllowFishingPoles && proto->Class == ITEM_CLASS_WEAPON && proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE) ++ return false; ++ ++ if (!IsAllowedQuality(proto->Quality)) // (proto->Quality == ITEM_QUALITY_LEGENDARY) ++ return false; ++ ++ if (player) ++ { ++ if ((proto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY) && player->GetTeam() != HORDE) ++ return false; ++ ++ if ((proto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY) && player->GetTeam() != ALLIANCE) ++ return false; ++ ++ if (!IgnoreReqClass && (proto->AllowableClass & player->getClassMask()) == 0) ++ return false; ++ ++ if (!IgnoreReqRace && (proto->AllowableRace & player->getRaceMask()) == 0) ++ return false; ++ ++ if (!IgnoreReqSkill && proto->RequiredSkill != 0) ++ { ++ if (player->GetSkillValue(proto->RequiredSkill) == 0) ++ return false; ++ else if (player->GetSkillValue(proto->RequiredSkill) < proto->RequiredSkillRank) ++ return false; ++ } ++ ++ if (!IgnoreReqSpell && proto->RequiredSpell != 0 && !player->HasSpell(proto->RequiredSpell)) ++ return false; ++ ++ if (!IgnoreReqLevel && player->getLevel() < proto->RequiredLevel) ++ return false; ++ } ++ ++ // If World Event is not active, prevent using event dependant items ++ if (!IgnoreReqEvent && proto->HolidayId && !IsHolidayActive((HolidayIds)proto->HolidayId)) ++ return false; ++ ++ if (!IgnoreReqStats) ++ { ++ if (!proto->RandomProperty && !proto->RandomSuffix) ++ { ++ bool found = false; ++ for (uint8 i = 0; i < proto->StatsCount; ++i) ++ { ++ if (proto->ItemStat[i].ItemStatValue != 0) ++ { ++ found = true; ++ break; ++ } ++ } ++ if (!found) ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++bool TransmogDisplayVendorMgr::IsRangedWeapon(uint32 Class, uint32 SubClass) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::IsRangedWeapon"); ++ ++ return Class == ITEM_CLASS_WEAPON && ( ++ SubClass == ITEM_SUBCLASS_WEAPON_BOW || ++ SubClass == ITEM_SUBCLASS_WEAPON_GUN || ++ SubClass == ITEM_SUBCLASS_WEAPON_CROSSBOW); ++} ++bool TransmogDisplayVendorMgr::IsAllowed(uint32 entry) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::IsAllowed"); ++ ++ return Allowed.find(entry) != Allowed.end(); ++} ++bool TransmogDisplayVendorMgr::IsNotAllowed(uint32 entry) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::IsNotAllowed"); ++ ++ return NotAllowed.find(entry) != NotAllowed.end(); ++} ++bool TransmogDisplayVendorMgr::IsAllowedQuality(uint32 quality) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::IsAllowedQuality"); ++ ++ switch (quality) ++ { ++ case ITEM_QUALITY_POOR: return AllowPoor; ++ case ITEM_QUALITY_NORMAL: return AllowCommon; ++ case ITEM_QUALITY_UNCOMMON: return AllowUncommon; ++ case ITEM_QUALITY_RARE: return AllowRare; ++ case ITEM_QUALITY_EPIC: return AllowEpic; ++ case ITEM_QUALITY_LEGENDARY: return AllowLegendary; ++ case ITEM_QUALITY_ARTIFACT: return AllowArtifact; ++ case ITEM_QUALITY_HEIRLOOM: return AllowHeirloom; ++ default: return false; ++ } ++} ++ ++static const char* getQualityName(uint32 quality) ++{ ++ switch (quality) ++ { ++ case ITEM_QUALITY_POOR: return "|CFF9d9d9d[Poor]"; ++ case ITEM_QUALITY_NORMAL: return "|CFFffffff[Common]"; ++ case ITEM_QUALITY_UNCOMMON: return "|CFF1eff00[Uncommon]"; ++ case ITEM_QUALITY_RARE: return "|CFF0070dd[Rare]"; ++ case ITEM_QUALITY_EPIC: return "|CFFa335ee[Epic]"; ++ case ITEM_QUALITY_LEGENDARY: return "|CFFff8000[Legendary]"; ++ case ITEM_QUALITY_ARTIFACT: return "|CFFe6cc80[Artifact]"; ++ case ITEM_QUALITY_HEIRLOOM: return "|CFFe5cc80[Heirloom]"; ++ default: return "[Unknown]"; ++ } ++} ++ ++static std::string getItemName(const ItemTemplate* itemTemplate, WorldSession* session) ++{ ++ std::string name = itemTemplate->Name1; ++ int loc_idx = session->GetSessionDbLocaleIndex(); ++ if (loc_idx >= 0) ++ if (ItemLocale const* il = sObjectMgr->GetItemLocale(itemTemplate->ItemId)) ++ sObjectMgr->GetLocaleString(il->Name, loc_idx, name); ++ return name; ++} ++ ++static uint32 getCorrectInvType(uint32 inventorytype) ++{ ++ switch (inventorytype) ++ { ++ case INVTYPE_WEAPONMAINHAND: ++ case INVTYPE_WEAPONOFFHAND: ++ return INVTYPE_WEAPON; ++ case INVTYPE_RANGEDRIGHT: ++ return INVTYPE_RANGED; ++ case INVTYPE_ROBE: ++ return INVTYPE_CHEST; ++ default: ++ return inventorytype; ++ } ++} ++ ++void TransmogDisplayVendorMgr::HandleTransmogrify(Player* player, Creature* /*creature*/, uint32 vendorslot, uint32 itemEntry, bool no_cost) ++{ ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify"); ++ ++ SelectionStore::Selection selection; ++ if (!selectionStore.GetSelection(player->GetGUID().GetCounter(), selection)) ++ return; // cheat, no slot selected ++ ++ const char* slotname = TransmogDisplayVendorMgr::getSlotName(selection.slot, player->GetSession()); ++ if (!slotname) ++ return; ++ uint8 slot = selection.slot; ++ ++ // slot of the transmogrified item ++ if (slot >= EQUIPMENT_SLOT_END) ++ { ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify - %s (%s) tried to transmogrify item %u with a wrong slot (%u) when transmogrifying items.", player->GetName().c_str(), player->GetGUID().ToString().c_str(), itemEntry, slot); ++ return; // LANG_ERR_TRANSMOG_INVALID_SLOT ++ } ++ ++ const ItemTemplate* itemTransmogrifier = NULL; ++ // guid of the transmogrifier item, if it's not 0 ++ if (itemEntry) ++ { ++ itemTransmogrifier = sObjectMgr->GetItemTemplate(itemEntry); ++ if (!itemTransmogrifier) ++ { ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify - %s (%s) tried to transmogrify with an invalid item entry %u.", player->GetName().c_str(), player->GetGUID().ToString().c_str(), itemEntry); ++ return; // LANG_ERR_TRANSMOG_MISSING_SRC_ITEM ++ } ++ } ++ ++ // transmogrified item ++ Item* itemTransmogrified = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); ++ if (!itemTransmogrified) ++ { ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify - %s (%s) tried to transmogrify an invalid item in a valid slot (slot: %u).", player->GetName().c_str(), player->GetGUID().ToString().c_str(), slot); ++ player->GetSession()->SendNotification("No item in %s slot", slotname); ++ return; // LANG_ERR_TRANSMOG_MISSING_DEST_ITEM ++ } ++ ++ if (!itemTransmogrifier) // reset look newEntry ++ { ++ DeleteFakeEntry(player, itemTransmogrified); ++ } ++ else ++ { ++ if (!CanTransmogrifyItemWithItem(player, itemTransmogrified->GetTemplate(), itemTransmogrifier)) ++ { ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify - %s (%s) failed CanTransmogrifyItemWithItem (%u with %u).", player->GetName().c_str(), player->GetGUID().ToString().c_str(), itemTransmogrified->GetEntry(), itemTransmogrifier->ItemId); ++ player->GetSession()->SendNotification("Equipped item is not suitable for selected transmogrification"); ++ return; // LANG_ERR_TRANSMOG_INVALID_ITEMS ++ } ++ ++ if (uint32 fakeEntry = GetFakeEntry(itemTransmogrified)) ++ { ++ if (const ItemTemplate* fakeItemTemplate = sObjectMgr->GetItemTemplate(fakeEntry)) ++ { ++ if (fakeItemTemplate->DisplayInfoID == itemTransmogrifier->DisplayInfoID) ++ { ++ player->GetSession()->SendNotification("%s already transmogrified with %s", slotname, getItemName(itemTransmogrifier, player->GetSession()).c_str()); ++ return; ++ } ++ } ++ } ++ ++ // {{entry}, {entry}, ...} ++ std::list L; ++ uint32 counter = 0; ++ bool over = false; ++ if (itemTransmogrified->GetTemplate()->Class != ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedArmorTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ { ++ const EntryVector* oM = optionMap[MAX_ITEM_SUBCLASS_WEAPON + i][getCorrectInvType(itemTransmogrified->GetTemplate()->InventoryType)][selection.quality]; ++ if (!oM) ++ continue; ++ if (!over && counter + oM->size() < selection.offset) ++ { ++ counter += oM->size(); ++ } ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ else if (itemTransmogrified->GetTemplate()->Class == ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedWeaponTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON; ++i) ++ { ++ const EntryVector* oM = optionMap[i][getCorrectInvType(itemTransmogrified->GetTemplate()->InventoryType)][selection.quality]; ++ if (!oM) ++ continue; ++ if (!over && counter + oM->size() < selection.offset) ++ { ++ counter += oM->size(); ++ } ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ else ++ { ++ const EntryVector* oM = optionMap[(itemTransmogrified->GetTemplate()->Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itemTransmogrified->GetTemplate()->SubClass][getCorrectInvType(itemTransmogrified->GetTemplate()->InventoryType)][selection.quality]; ++ if (oM) ++ { ++ if (!over && counter + oM->size() < selection.offset) ++ { ++ counter += oM->size(); ++ } ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ std::list::const_iterator it = L.begin(); ++ std::advance(it, (selection.offset - counter) + vendorslot); ++ if (it == L.end() || (*it) != itemEntry) ++ { ++ player->GetSession()->SendNotification("Equipped item is not suitable for selected transmogrification"); ++ return; // either cheat or changed items (not found in correct place in transmog vendor view) ++ } ++ ++ if (!no_cost) ++ { ++ if (RequireToken) ++ { ++ if (player->HasItemCount(TokenEntry, TokenAmount)) ++ { ++ player->DestroyItemCount(TokenEntry, TokenAmount, true); ++ } ++ else ++ { ++ player->GetSession()->SendNotification("You do not have enough %ss", getItemName(sObjectMgr->GetItemTemplate(TransmogDisplayVendorMgr::TokenEntry), player->GetSession()).c_str()); ++ return; // LANG_ERR_TRANSMOG_NOT_ENOUGH_TOKENS ++ } ++ } ++ ++ int32 cost = 0; ++ cost = GetSpecialPrice(itemTransmogrified->GetTemplate()); ++ cost *= ScaledCostModifier; ++ cost += CopperCost; ++ ++ if (cost) // 0 cost if reverting look ++ { ++ if (cost < 0) ++ { ++ TC_LOG_DEBUG("custom.transmog", "TransmogDisplayVendorMgr::HandleTransmogrify - %s (%s) transmogrification invalid cost (non negative, amount %i). Transmogrified %u with %u", player->GetName().c_str(), player->GetGUID().ToString().c_str(), -cost, itemTransmogrified->GetEntry(), itemTransmogrifier->ItemId); ++ } ++ else ++ { ++ if (!player->HasEnoughMoney(cost)) ++ { ++ player->GetSession()->SendNotification("You do not have enough money"); ++ return; // LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY ++ } ++ player->ModifyMoney(-cost, false); ++ } ++ } ++ ++ SetFakeEntry(player, itemTransmogrified, itemTransmogrifier->ItemId); ++ ++ itemTransmogrified->UpdatePlayedTime(player); ++ ++ itemTransmogrified->SetOwnerGUID(player->GetGUID()); ++ itemTransmogrified->SetNotRefundable(player); ++ itemTransmogrified->ClearSoulboundTradeable(player); ++ ++ //if (itemTransmogrifier->GetTemplate()->Bonding == BIND_WHEN_EQUIPED || itemTransmogrifier->GetTemplate()->Bonding == BIND_WHEN_USE) ++ // itemTransmogrifier->SetBinding(true); ++ ++ //itemTransmogrifier->SetOwnerGUID(player->GetGUID()); ++ //itemTransmogrifier->SetNotRefundable(player); ++ //itemTransmogrifier->ClearSoulboundTradeable(player); ++ } ++ ++ player->PlayDirectSound(3337); ++ player->GetSession()->SendAreaTriggerMessage("%s transmogrified", slotname); ++ //return LANG_ERR_TRANSMOG_OK; ++ } ++} ++ ++class NPC_TransmogDisplayVendor : public CreatureScript ++{ ++public: ++ NPC_TransmogDisplayVendor() : CreatureScript("NPC_TransmogDisplayVendor") { } // If you change this, also change in Player.cpp: if (creature->GetScriptName() == "NPC_TransmogDisplayVendor") ++ ++ bool OnGossipHello(Player* player, Creature* creature) override ++ { ++ player->PlayerTalkClass->ClearMenus(); ++ selectionStore.RemoveSelection(player->GetGUID().GetCounter()); ++ WorldSession* session = player->GetSession(); ++ for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) ++ { ++ // if (player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) ++ if (const char* slotName = TransmogDisplayVendorMgr::getSlotName(slot, session)) ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TRAINER, slotName, SENDER_SELECT_VENDOR, slot); ++ } ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TRAINER, "Remove transmogrifications", SENDER_REMOVE_MENU, 0); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ return true; ++ } ++ ++ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override ++ { ++ WorldSession* session = player->GetSession(); ++ player->PlayerTalkClass->ClearMenus(); ++ switch (sender) ++ { ++ case SENDER_SELECT_VENDOR: // action = slot ++ { ++ Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, action); ++ if (!item) ++ { ++ if (const char* slotname = TransmogDisplayVendorMgr::getSlotName(action, player->GetSession())) ++ session->SendNotification("No item equipped in %s slot", slotname); ++ OnGossipHello(player, creature); ++ return true; ++ } ++ const ItemTemplate * itemTemplate = item->GetTemplate(); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, (std::string)"Update selected; " + getItemName(itemTemplate, session), sender, action); ++ ++ // [quality] = {size} ++ std::map L; ++ if (itemTemplate->Class != ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedArmorTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ { ++ EntryVector** oM = optionMap[MAX_ITEM_SUBCLASS_WEAPON + i][getCorrectInvType(itemTemplate->InventoryType)]; ++ for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i, ++oM) ++ if (TransmogDisplayVendorMgr::IsAllowedQuality(i)) // skip not allowed qualities ++ if (*oM) ++ L[i] += (*oM)->size(); ++ } ++ } ++ else if (itemTemplate->Class == ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedWeaponTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON; ++i) ++ { ++ EntryVector** oM = optionMap[i][getCorrectInvType(itemTemplate->InventoryType)]; ++ for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i, ++oM) ++ if (TransmogDisplayVendorMgr::IsAllowedQuality(i)) // skip not allowed qualities ++ if (*oM) ++ L[i] += (*oM)->size(); ++ } ++ } ++ else ++ { ++ EntryVector** oM = optionMap[(itemTemplate->Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itemTemplate->SubClass][getCorrectInvType(itemTemplate->InventoryType)]; ++ for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i, ++oM) ++ if (TransmogDisplayVendorMgr::IsAllowedQuality(i)) // skip not allowed qualities ++ if (*oM) ++ L[i] += (*oM)->size(); ++ } ++ ++ for (std::map::const_iterator it = L.begin(); it != L.end(); ++it) ++ { ++ for (uint32 count = 0; count*MAX_VENDOR_ITEMS < it->second; ++count) ++ { ++ std::ostringstream ss; ++ ss << getQualityName(it->first); ++ if (count) ++ ss << " [" << count << "]"; ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_VENDOR, ss.str().c_str(), it->first, count*MAX_VENDOR_ITEMS); ++ } ++ } ++ ++ if (player->PlayerTalkClass->GetGossipMenu().GetMenuItemCount() <= 1) ++ { ++ if (const char* slotname = TransmogDisplayVendorMgr::getSlotName(action, player->GetSession())) ++ session->SendNotification("No transmogrifications available for %s", slotname); ++ OnGossipHello(player, creature); ++ return true; ++ } ++ ++ SelectionStore::Selection temp = { item->GetEntry(), action, 0, 0 }; // entry, slot, offset, quality ++ selectionStore.SetSelection(player->GetGUID().GetCounter(), temp); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Back..", SENDER_BACK, 0); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ } break; ++ case SENDER_BACK: // Back ++ { ++ OnGossipHello(player, creature); ++ } break; ++ case SENDER_REMOVE_ALL: // Remove TransmogDisplayVendorMgrs ++ { ++ bool removed = false; ++ for (uint8 Slot = EQUIPMENT_SLOT_START; Slot < EQUIPMENT_SLOT_END; Slot++) ++ { ++ if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, Slot)) ++ { ++ if (!TransmogDisplayVendorMgr::GetFakeEntry(newItem)) ++ continue; ++ TransmogDisplayVendorMgr::DeleteFakeEntry(player, newItem); ++ removed = true; ++ } ++ } ++ if (removed) ++ { ++ session->SendAreaTriggerMessage("Transmogrifications removed from equipped items"); ++ player->PlayDirectSound(3337); ++ } ++ else ++ { ++ session->SendNotification("You have no transmogrified items equipped"); ++ } ++ OnGossipSelect(player, creature, SENDER_REMOVE_MENU, 0); ++ } break; ++ case SENDER_REMOVE_ONE: // Remove TransmogDisplayVendorMgr from single item ++ { ++ const char* slotname = TransmogDisplayVendorMgr::getSlotName(action, player->GetSession()); ++ if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, action)) ++ { ++ if (TransmogDisplayVendorMgr::GetFakeEntry(newItem)) ++ { ++ TransmogDisplayVendorMgr::DeleteFakeEntry(player, newItem); ++ if (slotname) ++ session->SendAreaTriggerMessage("%s transmogrification removed", slotname); ++ player->PlayDirectSound(3337); ++ } ++ else if (slotname) ++ { ++ session->SendNotification("No transmogrification on %s slot", slotname); ++ } ++ } ++ else if (slotname) ++ { ++ session->SendNotification("No item equipped in %s slot", slotname); ++ } ++ OnGossipSelect(player, creature, SENDER_REMOVE_MENU, 0); ++ } break; ++ case SENDER_REMOVE_MENU: ++ { ++ for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) ++ { ++ const char* slotname = TransmogDisplayVendorMgr::getSlotName(slot, player->GetSession()); ++ if (!slotname) ++ continue; ++ std::ostringstream ss; ++ ss << "Remove transmogrification from " << slotname << "?"; ++ player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_INTERACT_1, (std::string)"Remove from " + slotname, SENDER_REMOVE_ONE, slot, ss.str().c_str(), 0, false); ++ } ++ player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_INTERACT_1, "Remove all transmogrifications", SENDER_REMOVE_ALL, 0, "Are you sure you want to remove all transmogrifications?", 0, false); ++ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, "Back..", SENDER_BACK, 0); ++ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); ++ } break; ++ default: // Show items you can use ++ { ++ if (sender >= MAX_ITEM_QUALITY) // sender = quality, action = iterator ++ return false; // cheat ++ ++ SelectionStore::Selection selection; ++ if (!selectionStore.GetSelection(player->GetGUID().GetCounter(), selection)) ++ return false; // cheat ++ if (selection.offset != 0 || selection.quality != 0) ++ return false; // cheat (something is off) ++ ++ selection.offset = action; ++ selection.quality = sender; ++ uint32 slot = selection.slot; // slot ++ selectionStore.SetSelection(player->GetGUID().GetCounter(), selection); ++ ++ if (const ItemTemplate* itemTemplate = sObjectMgr->GetItemTemplate(selection.item)) ++ { ++ if (!TransmogDisplayVendorMgr::SuitableForTransmogrification(player, itemTemplate)) ++ { ++ player->GetSession()->SendNotification("Equipped item is not suitable for transmogrification"); ++ OnGossipSelect(player, creature, SENDER_SELECT_VENDOR, slot); ++ return true; ++ } ++ ++ // {{entry}, {entry}, ...} ++ std::list L; ++ uint32 counter = 0; ++ bool over = false; ++ if (itemTemplate->Class != ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedArmorTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ { ++ const EntryVector* oM = optionMap[MAX_ITEM_SUBCLASS_WEAPON + i][getCorrectInvType(itemTemplate->InventoryType)][selection.quality]; ++ if (!oM) ++ continue; ++ if (!over && counter + oM->size() < selection.offset) ++ { ++ counter += oM->size(); ++ } ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ else if (itemTemplate->Class == ITEM_CLASS_WEAPON && TransmogDisplayVendorMgr::AllowMixedWeaponTypes) ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON; ++i) ++ { ++ const EntryVector* oM = optionMap[i][getCorrectInvType(itemTemplate->InventoryType)][selection.quality]; ++ if (!oM) ++ continue; ++ if (!over && counter + oM->size() < selection.offset) ++ counter += oM->size(); ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ else ++ { ++ const EntryVector* oM = optionMap[(itemTemplate->Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itemTemplate->SubClass][getCorrectInvType(itemTemplate->InventoryType)][selection.quality]; ++ if (oM) ++ { ++ if (!over && counter + oM->size() < selection.offset) ++ { ++ counter += oM->size(); ++ } ++ else ++ { ++ over = true; ++ L.insert(L.end(), oM->begin(), oM->end()); ++ } ++ } ++ } ++ ++ // EntryVector oM = optionMap[(itemTemplate->Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itemTemplate->SubClass][getCorrectInvType(itemTemplate->InventoryType)][selection.quality]; ++ uint32 itemCount = L.size() - (selection.offset - counter); ++ if (itemCount > MAX_VENDOR_ITEMS) ++ itemCount = MAX_VENDOR_ITEMS; ++ ++ if (!itemCount) ++ { ++ session->SendAreaTriggerMessage("No items found"); ++ OnGossipSelect(player, creature, SENDER_SELECT_VENDOR, slot); ++ return true; ++ } ++ player->CLOSE_GOSSIP_MENU(); ++ ++ TC_LOG_DEBUG("network", "WORLD: Sent SMSG_LIST_INVENTORY"); ++ ++ Creature* vendor = player->GetNPCIfCanInteractWith(creature->GetGUID(), UNIT_NPC_FLAG_VENDOR); ++ if (!vendor) ++ { ++ TC_LOG_DEBUG("network", "WORLD: SendListInventory - Unit (GUID: %u) not found or you can not interact with him.", creature->GetGUID().GetCounter()); ++ player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, NULL, ObjectGuid::Empty, 0); ++ return true; ++ } ++ ++ if (player->HasUnitState(UNIT_STATE_DIED)) ++ player->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); ++ ++ if (vendor->HasUnitState(UNIT_STATE_MOVING)) ++ vendor->StopMoving(); ++ ++ uint8 count = 0; ++ ++ WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + itemCount * 8 * 4); ++ data << uint64(creature->GetGUID()); ++ ++ size_t countPos = data.wpos(); ++ data << uint8(count); ++ ++ uint32 item_amount = 0; ++ std::list::const_iterator it = L.begin(); ++ std::advance(it, (selection.offset - counter)); ++ for (; it != L.end() && count < itemCount; ++it, ++count) ++ { ++ if (ItemTemplate const* curtemp = sObjectMgr->GetItemTemplate(*it)) ++ { ++ if (!TransmogDisplayVendorMgr::CanTransmogrifyItemWithItem(player, itemTemplate, curtemp)) ++ continue; ++ ++ data << uint32(count + 1); ++ data << uint32(curtemp->ItemId); ++ data << uint32(curtemp->DisplayInfoID); ++ data << int32(0xFFFFFFFF); ++ data << uint32(0); ++ data << uint32(curtemp->MaxDurability); ++ data << uint32(curtemp->BuyCount); ++ data << uint32(0); ++ ++item_amount; ++ } ++ } ++ ++ if (!item_amount) ++ { ++ session->SendAreaTriggerMessage("No transmogrifications found for equipped item"); ++ OnGossipSelect(player, creature, SENDER_SELECT_VENDOR, slot); ++ return true; ++ } ++ else ++ { ++ data.put(countPos, item_amount); ++ session->SendPacket(&data); ++ } ++ } ++ else ++ { ++ session->SendNotification("Invalid item equipped"); ++ OnGossipSelect(player, creature, SENDER_SELECT_VENDOR, slot); ++ return true; ++ } ++ } break; ++ } ++ return true; ++ } ++}; ++ ++#if !TRANSMOGRIFICATION_ALREADY_INSTALLED ++class Player_Transmogrify : public PlayerScript ++{ ++public: ++ Player_Transmogrify() : PlayerScript("Player_Transmogrify") { } ++ ++ std::vector GetItemList(const Player* player) const ++ { ++ std::vector itemlist; ++ ++ // Copy paste from Player::GetItemByGuid(guid) ++ ++ for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) ++ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ itemlist.push_back(pItem->GetGUID()); ++ ++ for (uint8 i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i) ++ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ itemlist.push_back(pItem->GetGUID()); ++ ++ for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_BAG_END; ++i) ++ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) ++ itemlist.push_back(pItem->GetGUID()); ++ ++ for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) ++ if (Bag* pBag = player->GetBagByPos(i)) ++ for (uint32 j = 0; j < pBag->GetBagSize(); ++j) ++ if (Item* pItem = pBag->GetItemByPos(j)) ++ itemlist.push_back(pItem->GetGUID()); ++ ++ for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) ++ if (Bag* pBag = player->GetBagByPos(i)) ++ for (uint32 j = 0; j < pBag->GetBagSize(); ++j) ++ if (Item* pItem = pBag->GetItemByPos(j)) ++ itemlist.push_back(pItem->GetGUID()); ++ ++ return itemlist; ++ } ++ ++ void OnSave(Player* player) override ++ { ++ uint32 lowguid = player->GetGUID().GetCounter(); ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ trans->PAppend("DELETE FROM `custom_transmogrification` WHERE `Owner` = %u", lowguid); ++ ++ if (!player->transmogMap.empty()) ++ { ++ // Only save items that are in inventory / bank / etc ++ std::vector items = GetItemList(player); ++ for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) ++ { ++ TransmogMapType::const_iterator it2 = player->transmogMap.find(*it); ++ if (it2 == player->transmogMap.end()) ++ continue; ++ ++ trans->PAppend("REPLACE INTO custom_transmogrification (GUID, FakeEntry, Owner) VALUES (%u, %u, %u)", it2->first.GetCounter(), it2->second, lowguid); ++ } ++ } ++ ++ if (trans->GetSize()) // basically never false ++ CharacterDatabase.CommitTransaction(trans); ++ } ++ ++ void OnLogin(Player* player, bool /*firstLogin*/) override ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT GUID, FakeEntry FROM custom_transmogrification WHERE Owner = %u", player->GetGUID().GetCounter()); ++ ++ if (result) ++ { ++ do ++ { ++ Field* field = result->Fetch(); ++ ObjectGuid itemGUID(HighGuid::Item, 0, field[0].GetUInt32()); ++ uint32 fakeEntry = field[1].GetUInt32(); ++ // Only load items that are in inventory / bank / etc ++ if (sObjectMgr->GetItemTemplate(fakeEntry) && player->GetItemByGuid(itemGUID)) ++ { ++ player->transmogMap[itemGUID] = fakeEntry; ++ } ++ else ++ { ++ // Ignore, will be erased on next save. ++ // Additionally this can happen if an item was deleted from DB but still exists for the player ++ // TC_LOG_ERROR("custom.transmog", "Item entry (Entry: %u, itemGUID: %u, playerGUID: %u) does not exist, ignoring.", fakeEntry, GUID_LOPART(itemGUID), player->GetGUID().GetCounter()); ++ // CharacterDatabase.PExecute("DELETE FROM custom_transmogrification WHERE FakeEntry = %u", fakeEntry); ++ } ++ } while (result->NextRow()); ++ ++ if (!player->transmogMap.empty()) ++ { ++ for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) ++ { ++ if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) ++ { ++ player->SetVisibleItemSlot(slot, item); ++ if (player->IsInWorld()) ++ item->SendUpdateToPlayer(player); ++ } ++ } ++ } ++ } ++ } ++ ++ void OnLogout(Player* player) override ++ { ++ selectionStore.RemoveSelection(player->GetGUID().GetCounter()); ++ } ++}; ++#endif ++ ++class PREP_TransmogDisplayVendor : public WorldScript ++{ ++public: ++ PREP_TransmogDisplayVendor() : WorldScript("PREP_TransmogDisplayVendor") { } ++ ++ void OnStartup() override ++ { ++ for (size_t i = 0; i < sizeof(AllowedItems) / sizeof(*AllowedItems); ++i) ++ TransmogDisplayVendorMgr::Allowed.insert(AllowedItems[i]); ++ for (size_t i = 0; i < sizeof(NotAllowedItems) / sizeof(*NotAllowedItems); ++i) ++ TransmogDisplayVendorMgr::NotAllowed.insert(NotAllowedItems[i]); ++ ++ TC_LOG_INFO("server.loading", "Creating a list of usable transmogrification entries..."); ++ // initialize .. for reload in future? ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON + MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ for (uint32 j = 0; j < MAX_INVTYPE; ++j) ++ for (uint32 k = 0; k < MAX_ITEM_QUALITY; ++k) ++ delete optionMap[i][j][k], optionMap[i][j][k] = NULL; ++ ++ std::unordered_set displays; ++ ItemTemplateContainer const* its = sObjectMgr->GetItemTemplateStore(); ++ for (ItemTemplateContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr) ++ { ++ if (itr->second.Class != ITEM_CLASS_WEAPON && itr->second.Class != ITEM_CLASS_ARMOR) ++ continue; ++ if (!TransmogDisplayVendorMgr::SuitableForTransmogrification(NULL, &itr->second)) ++ continue; ++ if (displays.find(itr->second.DisplayInfoID) != displays.end()) // skip duplicate item displays ++ continue; ++ EntryVector* oM = optionMap[(itr->second.Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itr->second.SubClass][getCorrectInvType(itr->second.InventoryType)][itr->second.Quality]; ++ if (!oM) ++ { ++ oM = new EntryVector(); ++ optionMap[(itr->second.Class != ITEM_CLASS_WEAPON ? MAX_ITEM_SUBCLASS_WEAPON : 0) + itr->second.SubClass][getCorrectInvType(itr->second.InventoryType)][itr->second.Quality] = oM; ++ } ++ if (oM->size() < MAX_VENDOR_ITEMS * 3) ++ { ++ oM->push_back(itr->second.ItemId); ++ displays.insert(itr->second.DisplayInfoID); ++ } ++ else ++ { ++ TC_LOG_INFO("server.loading", "Too many items for transmogrification: Class: %u SubClass: %u InventoryType: %u Quality: %u", itr->second.Class, itr->second.SubClass, getCorrectInvType(itr->second.InventoryType), itr->second.Quality); ++ } ++ } ++ ++ // resize entry lists ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON + MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ for (uint32 j = 0; j < MAX_INVTYPE; ++j) ++ for (uint32 k = 0; k < MAX_ITEM_QUALITY; ++k) ++ if (optionMap[i][j][k]) ++ optionMap[i][j][k]->resize(optionMap[i][j][k]->size()); ++ ++#if !TRANSMOGRIFICATION_ALREADY_INSTALLED ++ TC_LOG_INFO("custom.transmog", "Deleting non-existing transmogrification entries..."); ++ CharacterDatabase.DirectExecute("DELETE FROM custom_transmogrification WHERE NOT EXISTS (SELECT 1 FROM item_instance WHERE item_instance.guid = custom_transmogrification.GUID)"); ++#endif ++ } ++ ++ void OnShutdown() override ++ { ++ for (uint32 i = 0; i < MAX_ITEM_SUBCLASS_WEAPON + MAX_ITEM_SUBCLASS_ARMOR; ++i) ++ for (uint32 j = 0; j < MAX_INVTYPE; ++j) ++ for (uint32 k = 0; k < MAX_ITEM_QUALITY; ++k) ++ delete optionMap[i][j][k], optionMap[i][j][k] = NULL; ++ } ++}; ++ ++void AddSC_NPC_TransmogDisplayVendor() ++{ ++ new NPC_TransmogDisplayVendor(); ++ new PREP_TransmogDisplayVendor(); ++ ++#if !TRANSMOGRIFICATION_ALREADY_INSTALLED ++ new Player_Transmogrify(); ++#endif ++} +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h b/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h +new file mode 100644 +index 0000000..a1e8a99 +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h +@@ -0,0 +1,93 @@ ++#ifndef DEF_TRANSMOGRIFICATION_DISPLAY_H ++#define DEF_TRANSMOGRIFICATION_DISPLAY_H ++ ++/* ++Transmogrification display vendor ++Code by Rochet2 ++Ideas LilleCarl ++ ++ScriptName for NPC: ++NPC_TransmogDisplayVendor ++ ++Compatible with Transmogrification 6.1 by Rochet2 ++http://rochet2.github.io/Transmogrification ++*/ ++ ++// use 0 or 1 ++#define TRANSMOGRIFICATION_ALREADY_INSTALLED 0 ++// Note! If you use both, set this to true (1) and in scriptloader make transmog load first ++ ++#include "Define.h" ++#include "ItemPrototype.h" ++#include "SharedDefines.h" ++#include ++ ++class Creature; ++class Item; ++class Player; ++class WorldSession; ++struct ItemTemplate; ++ ++enum TransmogDisplayVendorSenders ++{ ++ SENDER_START = MAX_ITEM_QUALITY, ++ SENDER_BACK, ++ SENDER_SELECT_VENDOR, ++ SENDER_REMOVE_ALL, ++ SENDER_REMOVE_ONE, ++ SENDER_REMOVE_MENU, ++ SENDER_END, ++}; ++ ++class TransmogDisplayVendorMgr ++{ ++public: ++ static const float ScaledCostModifier; ++ static const int32 CopperCost; ++ ++ static const bool RequireToken; ++ static const uint32 TokenEntry; ++ static const uint32 TokenAmount; ++ ++ static const bool AllowPoor; ++ static const bool AllowCommon; ++ static const bool AllowUncommon; ++ static const bool AllowRare; ++ static const bool AllowEpic; ++ static const bool AllowLegendary; ++ static const bool AllowArtifact; ++ static const bool AllowHeirloom; ++ ++ static const bool AllowMixedArmorTypes; ++ static const bool AllowMixedWeaponTypes; ++ static const bool AllowFishingPoles; ++ ++ static const bool IgnoreReqRace; ++ static const bool IgnoreReqClass; ++ static const bool IgnoreReqSkill; ++ static const bool IgnoreReqSpell; ++ static const bool IgnoreReqLevel; ++ static const bool IgnoreReqEvent; ++ static const bool IgnoreReqStats; ++ ++ static std::set Allowed; ++ static std::set NotAllowed; ++ ++ static void HandleTransmogrify(Player* player, Creature* creature, uint32 vendorslot, uint32 itemEntry, bool no_cost = false); ++ ++ // From Transmogrification ++ static uint32 GetFakeEntry(const Item* item); ++ static void DeleteFakeEntry(Player* player, Item* item); ++ static void SetFakeEntry(Player* player, Item* item, uint32 entry); ++ static const char* getSlotName(uint8 slot, WorldSession* session); ++ static void UpdateItem(Player* player, Item* item); ++ static uint32 GetSpecialPrice(ItemTemplate const* proto); ++ static bool CanTransmogrifyItemWithItem(Player* player, ItemTemplate const* target, ItemTemplate const* source); ++ static bool SuitableForTransmogrification(Player* player, ItemTemplate const* proto); ++ static bool IsRangedWeapon(uint32 Class, uint32 SubClass); ++ static bool IsAllowed(uint32 entry); ++ static bool IsNotAllowed(uint32 entry); ++ static bool IsAllowedQuality(uint32 quality); ++}; ++ ++#endif +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/sql/characters.sql b/src/server/scripts/Custom/TransmogDisplayVendor/sql/characters.sql +new file mode 100644 +index 0000000..4529815 +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/sql/characters.sql +@@ -0,0 +1,37 @@ ++-- -------------------------------------------------------- ++-- Host: localhost ++-- Server version: 5.5.39 - MySQL Community Server (GPL) ++-- Server OS: Win32 ++-- HeidiSQL Version: 9.1.0.4894 ++-- -------------------------------------------------------- ++ ++/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; ++/*!40101 SET NAMES utf8mb4 */; ++/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; ++/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; ++ ++-- Dumping structure for table tc_c.custom_transmogrification ++CREATE TABLE IF NOT EXISTS `custom_transmogrification` ( ++ `GUID` int(10) unsigned NOT NULL COMMENT 'Item guidLow', ++ `FakeEntry` int(10) unsigned NOT NULL COMMENT 'Item entry', ++ `Owner` int(10) unsigned NOT NULL COMMENT 'Player guidLow', ++ PRIMARY KEY (`GUID`), ++ KEY `Owner` (`Owner`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='6_2'; ++ ++-- Data exporting was unselected. ++ ++ ++-- Dumping structure for table tc_c.custom_transmogrification_sets ++CREATE TABLE IF NOT EXISTS `custom_transmogrification_sets` ( ++ `Owner` int(10) unsigned NOT NULL COMMENT 'Player guidlow', ++ `PresetID` tinyint(3) unsigned NOT NULL COMMENT 'Preset identifier', ++ `SetName` text COMMENT 'SetName', ++ `SetData` text COMMENT 'Slot1 Entry1 Slot2 Entry2', ++ PRIMARY KEY (`Owner`,`PresetID`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='6_1'; ++ ++-- Data exporting was unselected. ++/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; ++/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; ++/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/sql/updates/characters_update_6_1_to_6_2.sql b/src/server/scripts/Custom/TransmogDisplayVendor/sql/updates/characters_update_6_1_to_6_2.sql +new file mode 100644 +index 0000000..0e675b5 +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/sql/updates/characters_update_6_1_to_6_2.sql +@@ -0,0 +1,3 @@ ++ALTER TABLE `custom_transmogrification` ++ COMMENT='6_2', ++ COLLATE='utf8_general_ci'; +diff --git a/src/server/scripts/Custom/TransmogDisplayVendor/sql/world_NPC.sql b/src/server/scripts/Custom/TransmogDisplayVendor/sql/world_NPC.sql +new file mode 100644 +index 0000000..353e593 +--- /dev/null ++++ b/src/server/scripts/Custom/TransmogDisplayVendor/sql/world_NPC.sql +@@ -0,0 +1,6 @@ ++SET ++@Entry = 190011, ++@Name = "Narpweaver"; ++ ++INSERT INTO `creature_template` (`entry`, `modelid1`, `modelid2`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `faction`, `npcflag`, `scale`, `rank`, `dmgschool`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `type`, `type_flags`, `lootid`, `pickpocketloot`, `skinloot`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `RacialLeader`, `movementId`, `RegenHealth`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`) VALUES ++(@Entry, 19646, 0, @Name, "Transmogrifier", NULL, 0, 80, 80, 2, 35, 129, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 3, 1, 0, 0, 1, 0, 0, 'NPC_TransmogDisplayVendor'); +diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp +index abde43e..9735efb 100644 +--- a/src/server/scripts/Spells/spell_generic.cpp ++++ b/src/server/scripts/Spells/spell_generic.cpp +@@ -22,6 +22,7 @@ + * Scriptnames of files in this file should be prefixed with "spell_gen_" + */ + ++#include "../Custom/TransmogDisplayVendor/TransmogDisplayVendorConf.h" + #include "ScriptMgr.h" + #include "Battleground.h" + #include "Cell.h" +@@ -861,7 +862,12 @@ class spell_gen_clone_weapon_aura : public SpellScriptLoader + if (Player* player = caster->ToPlayer()) + { + if (Item* mainItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND)) +- target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, mainItem->GetEntry()); ++ { ++ if (uint32 entry = TransmogDisplayVendorMgr::GetFakeEntry(mainItem)) ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, entry); ++ else ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, mainItem->GetEntry()); ++ } + } + else + target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID)); +@@ -875,7 +881,12 @@ class spell_gen_clone_weapon_aura : public SpellScriptLoader + if (Player* player = caster->ToPlayer()) + { + if (Item* offItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) +- target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, offItem->GetEntry()); ++ { ++ if (uint32 entry = TransmogDisplayVendorMgr::GetFakeEntry(offItem)) ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, entry); ++ else ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, offItem->GetEntry()); ++ } + } + else + target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1)); +@@ -888,7 +899,12 @@ class spell_gen_clone_weapon_aura : public SpellScriptLoader + if (Player* player = caster->ToPlayer()) + { + if (Item* rangedItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) +- target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, rangedItem->GetEntry()); ++ { ++ if (uint32 entry = TransmogDisplayVendorMgr::GetFakeEntry(rangedItem)) ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, entry); ++ else ++ target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, rangedItem->GetEntry()); ++ } + } + else + target->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2)); \ No newline at end of file diff --git a/trinity_irc.diff b/trinity_irc.diff new file mode 100644 index 0000000..a5da3be --- /dev/null +++ b/trinity_irc.diff @@ -0,0 +1,6248 @@ + .../TriniIRC/World_TrinityChat.sql | 81 + + src/server/game/CMakeLists.txt | 3 + + src/server/game/Chat/Channels/Channel.cpp | 4 + + src/server/game/Chat/Chat.cpp | 40 + + src/server/game/Chat/Chat.h | 3 + + src/server/game/Entities/Player/Player.cpp | 22 + + src/server/game/Entities/Player/Player.h | 3 +- + src/server/game/Events/GameEventMgr.cpp | 7 + + src/server/game/Handlers/AuctionHouseHandler.cpp | 4 +- + src/server/game/Handlers/ChatHandler.cpp | 3 +- + src/server/game/TriniChat/IRCClient.cpp | 135 ++ + src/server/game/TriniChat/IRCClient.h | 291 +++ + src/server/game/TriniChat/IRCCmd.cpp | 857 +++++++ + src/server/game/TriniChat/IRCCmd.h | 144 ++ + src/server/game/TriniChat/IRCCmde.cpp | 2353 ++++++++++++++++++++ + src/server/game/TriniChat/IRCFunc.h | 292 +++ + src/server/game/TriniChat/IRCIO.cpp | 529 +++++ + src/server/game/TriniChat/IRCLog.cpp | 81 + + src/server/game/TriniChat/IRCLog.h | 40 + + src/server/game/TriniChat/IRCSock.cpp | 160 ++ + src/server/game/TriniChat/MCS_OnlinePlayers.cpp | 94 + + src/server/game/TriniChat/MCS_OnlinePlayers.h | 38 + + src/server/game/World/World.cpp | 190 +- + src/server/game/World/World.h | 4 + + src/server/scripts/CMakeLists.txt | 1 + + src/server/scripts/Commands/cs_message.cpp | 20 + + src/server/worldserver/CMakeLists.txt | 5 + + src/server/worldserver/CommandLine/CliRunnable.cpp | 9 + + src/server/worldserver/Main.cpp | 35 + + src/server/worldserver/worldserver.conf.dist | 278 ++- + 30 files changed, 5719 insertions(+), 7 deletions(-) + create mode 100644 sql/TrinityCore-Patches/TriniIRC/World_TrinityChat.sql + create mode 100644 src/server/game/TriniChat/IRCClient.cpp + create mode 100644 src/server/game/TriniChat/IRCClient.h + create mode 100644 src/server/game/TriniChat/IRCCmd.cpp + create mode 100644 src/server/game/TriniChat/IRCCmd.h + create mode 100644 src/server/game/TriniChat/IRCCmde.cpp + create mode 100644 src/server/game/TriniChat/IRCFunc.h + create mode 100644 src/server/game/TriniChat/IRCIO.cpp + create mode 100644 src/server/game/TriniChat/IRCLog.cpp + create mode 100644 src/server/game/TriniChat/IRCLog.h + create mode 100644 src/server/game/TriniChat/IRCSock.cpp + create mode 100644 src/server/game/TriniChat/MCS_OnlinePlayers.cpp + create mode 100644 src/server/game/TriniChat/MCS_OnlinePlayers.h + +diff --git a/sql/TrinityCore-Patches/TriniIRC/World_TrinityChat.sql b/sql/TrinityCore-Patches/TriniIRC/World_TrinityChat.sql +new file mode 100644 +index 0000000..48dd18e +--- /dev/null ++++ b/sql/TrinityCore-Patches/TriniIRC/World_TrinityChat.sql +@@ -0,0 +1,81 @@ ++/** ++* Table structure for irc_commands ++*/ ++DROP TABLE IF EXISTS `irc_commands`; ++CREATE TABLE `irc_commands` ( ++ `Command` varchar(10) NOT NULL default '', ++ `Description` varchar(350) default NULL, ++ `gmlevel` tinyint(3) unsigned NOT NULL default '0', ++ PRIMARY KEY (`Command`) ++) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='IRC Module System'; ++ ++/** ++* Records ++*/ ++INSERT INTO `irc_commands` VALUES ('acct', '[acct <(un)lock/email/pass/rename/gmlevel>] : Perform Action To Account.', '3'); ++INSERT INTO `irc_commands` VALUES ('ban', '[ban ] : Ban/Unban ', '3'); ++INSERT INTO `irc_commands` VALUES ('chan', '[chan <*IRC Nick*>] : Set Mode On Yourself, If Is Specified Then Set mode On Nick.', '3'); ++INSERT INTO `irc_commands` VALUES ('char', '[char ] : Perform Action To Character.', '3'); ++INSERT INTO `irc_commands` VALUES ('fun', '[fun ] : Do Selected Fun Action To .', '3'); ++INSERT INTO `irc_commands` VALUES ('help', '[help Command] : Use No Paramaters For List Of Available Commands.', '0'); ++INSERT INTO `irc_commands` VALUES ('inchan', '[inchan ] : Display Users In Selected In Game ', '0'); ++INSERT INTO `irc_commands` VALUES ('info', '[info] : Display Server Info. (Number Of Players Online/Max Since Last Restart/Uptime)', '0'); ++INSERT INTO `irc_commands` VALUES ('item', '[item ] : Additem To , Use Or <[Exact Item Name]>.', '3'); ++INSERT INTO `irc_commands` VALUES ('jail', '[jail ] : Jail Selected For . Using release As Releases Player.', '3'); ++INSERT INTO `irc_commands` VALUES ('kick', '[kick ] : Kick For .', '3'); ++INSERT INTO `irc_commands` VALUES ('kill', '[kill ] : Kill For .', '3'); ++INSERT INTO `irc_commands` VALUES ('level', '[level ] : Level To . *Can Be Done Offline*', '3'); ++INSERT INTO `irc_commands` VALUES ('login', '[login ] : Login To TriniChat Admin Mode. (Must Be Done In A PM)', '0'); ++INSERT INTO `irc_commands` VALUES ('logout', '[logout] : Logout Of TriniChat Admin Mode.', '0'); ++INSERT INTO `irc_commands` VALUES ('lookup', '[lookup ] : ', '3'); ++INSERT INTO `irc_commands` VALUES ('money', '[money <(-)Money>] : Give Money To , Use - To Take Money. *Can Be Done Offline*', '3'); ++INSERT INTO `irc_commands` VALUES ('mute', '[mute ] : Mute Player For Reason, For . Using release As Time Releases Player. *Can Be Done Offline*', '3'); ++INSERT INTO `irc_commands` VALUES ('online', '[online] : Display All Users Logged In Game.', '0'); ++INSERT INTO `irc_commands` VALUES ('pm', '[pm ] : Whisper In WoW .', '3'); ++INSERT INTO `irc_commands` VALUES ('reload', '[reload] : Reload TriniChat Config Options And Security Level From DataBase.', '3'); ++INSERT INTO `irc_commands` VALUES ('restart', '[restart] : Restart TriniChat, NOT Trinity Core World Server Itself. Forces Reconnection To IRC Server.', '3'); ++INSERT INTO `irc_commands` VALUES ('revive', '[revive ] : Revive .', '3'); ++INSERT INTO `irc_commands` VALUES ('saveall', '[saveall] : Forces Trinity Core To Save All Players.', '3'); ++INSERT INTO `irc_commands` VALUES ('server', '[server setmotd []/flusharenapoints]', '3'); ++INSERT INTO `irc_commands` VALUES ('shutdown', '[shutdown ] : Shuts The Server Down In , Use 0 For Immediate Shut Down', '3'); ++INSERT INTO `irc_commands` VALUES ('spell', '[spell ] : Make Or A Spell, Or A Spell On A .', '3'); ++INSERT INTO `irc_commands` VALUES ('sysmsg', '[sysmsg ] : Broadcasts A System Message. (a-Broadcast System Message)(n-Broadcast Notify Message)(e-Event Message)', '3'); ++INSERT INTO `irc_commands` VALUES ('tele', '[tele ] : Teleport Player To Location, Coords, Recall Location, Another Player, Creature or Gameobject. (l-Location)(c-Coords)', '3'); ++INSERT INTO `irc_commands` VALUES ('top', '[top ] : Display top stats for given option. Only GM Higher Than Config Option Can Use Limit.', '3'); ++INSERT INTO `irc_commands` VALUES ('who', '[who] : Displays Users Currently Logged In To TriniChat.', '1'); ++ ++/** ++* Table structure for irc_inchan ++*/ ++DROP TABLE IF EXISTS `irc_inchan`; ++CREATE TABLE `irc_inchan` ( ++ `guid` int(11) unsigned NOT NULL default '0' COMMENT 'Global Unique Identifier', ++ `name` varchar(12) NOT NULL default '', ++ `channel` varchar(15) NOT NULL default '', ++ PRIMARY KEY (`guid`,`channel`) ++) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='IRC Module System'; ++ ++/** ++* Records ++*/ ++ ++/** ++* Table structure for irc_autoannounce ++*/ ++DROP TABLE IF EXISTS `irc_autoannounce`; ++CREATE TABLE `irc_autoannounce` ( ++ `id` int(11) NOT NULL auto_increment, ++ `message` longtext NOT NULL, ++ `addedby` varchar(12) NOT NULL default '', ++ PRIMARY KEY (`id`) ++) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='IRC Module System'; ++ ++/** ++* Records ++*/ ++INSERT INTO `irc_autoannounce` VALUES ('1', 'Welcome to IRC Channel', ''); ++ ++DELETE FROM `trinity_string` WHERE entry IN (6610,6611,6612); ++INSERT INTO `trinity_string` VALUES ('6610', '|cffff0000[System Message]: %s|r', null, null, null, null, null, null, null, null); ++INSERT INTO `trinity_string` VALUES ('6611', '|cffff0000[Server Event]: %s|r', null, null, null, null, null, null, null, null); ++INSERT INTO `trinity_string` VALUES ('6612', '|cffff0000[Automatic]: %s|r', null, null, null, null, null, null, null, null); +diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt +index aae5b48..4e5f42c 100644 +--- a/src/server/game/CMakeLists.txt ++++ b/src/server/game/CMakeLists.txt +@@ -46,6 +46,7 @@ file(GLOB_RECURSE sources_Spells Spells/*.cpp Spells/*.h) + file(GLOB_RECURSE sources_Texts Texts/*.cpp Texts/*.h) + file(GLOB_RECURSE sources_Tools Tools/*.cpp Tools/*.h) + file(GLOB_RECURSE sources_Tickets Tickets/*.cpp Tickets/*.h) ++file(GLOB_RECURSE sources_TriniChat TriniChat/*.cpp TriniChat/*.h) + file(GLOB_RECURSE sources_Warden Warden/*.cpp Warden/*.h) + file(GLOB_RECURSE sources_Weather Weather/*.cpp Weather/*.h) + file(GLOB_RECURSE sources_World World/*.cpp World/*.h) +@@ -95,6 +96,7 @@ set(game_STAT_SRCS + ${sources_Skills} + ${sources_Spells} + ${sources_Texts} ++ ${sources_TriniChat} + ${sources_Tools} + ${sources_Tickets} + ${sources_Warden} +@@ -167,6 +169,7 @@ include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/Texts + ${CMAKE_CURRENT_SOURCE_DIR}/Tickets + ${CMAKE_CURRENT_SOURCE_DIR}/Tools ++ ${CMAKE_CURRENT_SOURCE_DIR}/TriniChat + ${CMAKE_CURRENT_SOURCE_DIR}/Warden + ${CMAKE_CURRENT_SOURCE_DIR}/Warden/Modules + ${CMAKE_CURRENT_SOURCE_DIR}/Weather +diff --git a/src/server/game/Chat/Channels/Channel.cpp b/src/server/game/Chat/Channels/Channel.cpp +index 0875cee..7d1a183 100644 +--- a/src/server/game/Chat/Channels/Channel.cpp ++++ b/src/server/game/Chat/Channels/Channel.cpp +@@ -24,6 +24,7 @@ + #include "DatabaseEnv.h" + #include "AccountMgr.h" + #include "Player.h" ++#include "IRCClient.h" + + Channel::Channel(std::string const& name, uint32 channelId, uint32 team): + _announce(true), +@@ -209,6 +210,8 @@ void Channel::JoinChannel(Player* player, std::string const& pass) + + JoinNotify(guid); + ++ sIRC->Handle_WoW_Channel(_name, ObjectAccessor::FindPlayer(guid), CHANNEL_JOIN); ++ + // Custom channel handling + if (!IsConstant()) + { +@@ -259,6 +262,7 @@ void Channel::LeaveChannel(Player* player, bool send) + SendToAll(&data); + } + ++ sIRC->Handle_WoW_Channel(_name, ObjectAccessor::FindPlayer(guid), CHANNEL_LEAVE); + LeaveNotify(guid); + + if (!IsConstant()) +diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp +index 011b68a..1a45504 100644 +--- a/src/server/game/Chat/Chat.cpp ++++ b/src/server/game/Chat/Chat.cpp +@@ -312,6 +312,14 @@ bool ChatHandler::ExecuteCommandInTable(std::vector const& table, c + areaId, areaName.c_str(), zoneName.c_str(), + (player->GetSelectedUnit()) ? player->GetSelectedUnit()->GetName().c_str() : "", + guid.ToString().c_str()); ++ if ((sIRC->logmask & 2) != 0) ++ { ++ std::string logchan = "#"; ++ logchan += sIRC->logchan; ++ std::stringstream ss; ++ ss << sIRC->iLog.GetLogDateTimeStr() << ": [ " << player->GetName() << "(" << GetSession()->GetSecurity() << ") ] Used Command: [ " << fullcmd << " ] Target Guid: [" << guid.ToString().c_str() << "]"; ++ sIRC->Send_IRC_Channel(logchan,ss.str().c_str(), true, "LOG"); ++ } + } + } + // some commands have custom error messages. Don't send the default one in these cases. +@@ -859,6 +867,38 @@ char* ChatHandler::extractKeyFromLink(char* text, char const* const* linkTypes, + return NULL; + } + ++char const *fmtstring(char const *format, ...) ++{ ++ va_list argptr; ++ #define MAX_FMT_STRING 32000 ++ static char temp_buffer[MAX_FMT_STRING]; ++ static char string[MAX_FMT_STRING]; ++ static int index = 0; ++ char *buf; ++ int len; ++ ++ va_start(argptr, format); ++ vsnprintf(temp_buffer,MAX_FMT_STRING, format, argptr); ++ va_end(argptr); ++ ++ len = strlen(temp_buffer); ++ ++ if (len >= MAX_FMT_STRING) ++ return "ERROR"; ++ ++ if (len + index >= MAX_FMT_STRING-1) ++ { ++ index = 0; ++ } ++ ++ buf = &string[index]; ++ memcpy(buf, temp_buffer, len+1); ++ ++ index += len + 1; ++ ++ return buf; ++} ++ + GameObject* ChatHandler::GetNearbyGameObject() + { + if (!m_session) +diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h +index 7ce0792..dcbf0a5 100644 +--- a/src/server/game/Chat/Chat.h ++++ b/src/server/game/Chat/Chat.h +@@ -23,6 +23,7 @@ + #include "StringFormat.h" + #include "WorldSession.h" + #include "RBAC.h" ++#include "../TriniChat/IRCClient.h" + + #include + +@@ -182,4 +183,6 @@ class CliHandler : public ChatHandler + Print* m_print; + }; + ++char const *fmtstring(char const *format, ...); ++ + #endif +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..613a0df 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -32,6 +32,7 @@ + #include "ChannelMgr.h" + #include "CharacterDatabaseCleaner.h" + #include "Chat.h" ++#include "IRCClient.h" + #include "Common.h" + #include "ConditionMgr.h" + #include "CreatureAI.h" +@@ -2690,6 +2691,15 @@ void Player::UninviteFromGroup() + delete group; + } + } ++ //TODO: FIXME ++ if (sIRC->ajoin == 1) ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT `name` FROM `irc_inchan` WHERE `name` = '%s'", GetName().c_str()); ++ if (!result) ++ { ++ sIRC->AutoJoinChannel(this); ++ } ++ } + } + + void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method /* = GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /* = ObjectGuid::Empty */, const char* reason /* = NULL */) +@@ -2827,6 +2837,17 @@ void Player::GiveLevel(uint8 level) + InitTaxiNodesForLevel(); + InitGlyphsForLevel(); + ++ if ((sIRC->BOTMASK & 64) != 0 && sIRC->Status.size() > 0) ++ { ++ char temp [5]; ++ sprintf(temp, "%u", level); ++ std::string plevel = temp; ++ std::string pname = GetName(); ++ std::string ircchan = "#"; ++ ircchan += sIRC->Status; ++ sIRC->Send_IRC_Channel(ircchan, "\00311["+pname+"] : Has Reached Level: "+plevel, true); ++ } ++ + UpdateAllStats(); + + if (sWorld->getBoolConfig(CONFIG_ALWAYS_MAXSKILL)) // Max weapon skill when leveling up +@@ -20466,6 +20487,7 @@ void Player::PetSpellInitialize() + //Cooldowns + pet->GetSpellHistory()->WritePacket(data); + ++ + GetSession()->SendPacket(&data); + } + +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index b7d7d81..723cfb2 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -2606,7 +2606,8 @@ class Player : public Unit, public GridObject + // 32 + // 33 + // 34 +- // 35 ++ public : ++ QuestStatusSaveMap m_RewardedQuestsSave2; + // 36 + // 37 + // 38 +diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp +index b287178..50047ba 100644 +--- a/src/server/game/Events/GameEventMgr.cpp ++++ b/src/server/game/Events/GameEventMgr.cpp +@@ -28,6 +28,7 @@ + #include "BattlegroundMgr.h" + #include "UnitAI.h" + #include "GameObjectAI.h" ++#include "IRCClient.h" + + bool GameEventMgr::CheckOneGameEvent(uint16 entry) const + { +@@ -1087,6 +1088,12 @@ void GameEventMgr::ApplyNewEvent(uint16 event_id) + uint8 announce = mGameEvent[event_id].announce; + if (announce == 1 || (announce == 2 && sWorld->getBoolConfig(CONFIG_EVENT_ANNOUNCE))) + sWorld->SendWorldText(LANG_EVENTMESSAGE, mGameEvent[event_id].description.c_str()); ++ if ((sIRC->BOTMASK & 256) != 0 && sIRC->anchn.size() > 0) ++ { ++ std::string ircchan = "#"; ++ ircchan += sIRC->anchn; ++ sIRC->Send_IRC_Channel(ircchan, sIRC->MakeMsg("\00304,08\037/!\\\037\017\00304 Game Event \00304,08\037/!\\\037\017 %s", "%s", mGameEvent[event_id].description.c_str()), true); ++ } + + TC_LOG_INFO("gameevent", "GameEvent %u \"%s\" started.", event_id, mGameEvent[event_id].description.c_str()); + +diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp +index f23888c..9f392b6 100644 +--- a/src/server/game/Handlers/AuctionHouseHandler.cpp ++++ b/src/server/game/Handlers/AuctionHouseHandler.cpp +@@ -28,6 +28,7 @@ + #include "UpdateMask.h" + #include "Util.h" + #include "AccountMgr.h" ++#include "IRCClient.h" + + //void called when player click on auctioneer npc + void WorldSession::HandleAuctionHelloOpcode(WorldPacket& recvData) +@@ -597,7 +598,8 @@ void WorldSession::HandleAuctionRemoveItem(WorldPacket& recvData) + SendAuctionCommandResult(auction->Id, AUCTION_CANCEL, ERR_AUCTION_OK); + + // Now remove the auction +- ++ uint32 item; ++ recvData >> item; + player->SaveInventoryAndGoldToDB(trans); + auction->DeleteFromDB(trans); + CharacterDatabase.CommitTransaction(trans); +diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp +index f7fd9c8..4bcbbe8 100644 +--- a/src/server/game/Handlers/ChatHandler.cpp ++++ b/src/server/game/Handlers/ChatHandler.cpp +@@ -41,7 +41,7 @@ + // Prepatch by LordPsyan + // 61 + // 62 +-// 63 ++#include "IRCClient.h" + // 64 + // 65 + // 66 +@@ -453,6 +453,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) + + if (ChannelMgr* cMgr = ChannelMgr::forTeam(sender->GetTeam())) + { ++ sIRC->Send_WoW_IRC(sender, channel, msg); + if (Channel* chn = cMgr->GetChannel(channel, sender)) + { + sScriptMgr->OnPlayerChat(sender, type, lang, msg, chn); +diff --git a/src/server/game/TriniChat/IRCClient.cpp b/src/server/game/TriniChat/IRCClient.cpp +new file mode 100644 +index 0000000..3c4b643 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCClient.cpp +@@ -0,0 +1,135 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCClient.h" ++#include "World.h" ++#include "ObjectMgr.h" ++#include "MapManager.h" ++#include "Player.h" ++ ++#ifdef WIN32 ++ #define Delay(x) Sleep(x) ++#else ++ #define Delay(x) sleep(x / 1000) ++#endif ++// IRCClient Constructor ++IRCClient::IRCClient() ++{ ++ for (int i = 0;i > 5;i++) ++ sIRC->Script_Lock[i] = false; ++} ++// IRCClient Destructor ++IRCClient::~IRCClient(){} ++ ++void TrinityChatThread() ++{ ++ //call irc bot ++ IRCClient* a = nullptr; ++ // run the bot within a thread ++ a->run(); ++} ++ ++// ZThread Entry This function is called when the thread is created in Master.cpp (trinitycore) ++void IRCClient::run() ++{ ++// iLog.WriteLog(" %s : ****** TrinityCore With TriniChat Has Been Started ******", iLog.GetLogDateTimeStr().c_str()); ++ ++ // before we begin we wait a few ++ // mangos is still starting up. ++ std::this_thread::sleep_for(std::chrono::milliseconds(500)); ++ std::stringstream ss(sIRC->_bot_names); ++ string temp = ""; ++ uint8 counter = 0; ++ for(uint8 i=0;i_bot_names.length();i++) ++ { ++ if(sIRC->_bot_names[i] == ',') ++ { ++ sIRC->_ignore_bots[counter] = temp; ++ temp = ""; ++ counter++; ++ } ++ else ++ { ++ temp += sIRC->_bot_names[i]; ++ } ++ } ++ // check for hanging name ++ sIRC->_ignore_bots[counter] = temp; ++ TC_LOG_INFO("server.loading", ">> TrinityChat Ignore Bots set."); ++ TC_LOG_ERROR("misc", "\n%s\n%s\n%s\n%s", ++ "***************************************", ++ "** TriniChat2 Threaded IRC Client **", ++ "** With Enhanced GM Control. **", ++ "***************************************"); ++ TC_LOG_ERROR("misc", "****** TriniChat: %s ********", sIRC->_Mver.c_str()); ++ int cCount = 1; ++ // Clean Up MySQL Tables ++ TC_LOG_ERROR("misc", "*** TriniChat: Cleaning Up Inchan Table*"); ++ WorldDatabase.PExecute("DELETE FROM `irc_inchan`"); ++ sIRC->_Max_Script_Inst = 0; ++ // Create a loop to keep the thread running untill active is set to false ++ while (sIRC->Active && !World::IsStopped()) ++ { ++ // Initialize socket library ++ if (this->InitSock()) ++ { ++ // Connect To The IRC Server ++ TC_LOG_ERROR("misc", "*** TriniChat: Connecting to %s Try # %d ******", sIRC->_Host.c_str(), cCount); ++ if (this->Connect(sIRC->_Host.c_str(), sIRC->_Port)) ++ { ++ // On connection success reset the connection counter ++ cCount = 0; ++ TC_LOG_ERROR("misc", "*** TriniChat: Connected And Logging In*"); ++ // Login to the IRC server ++ if (this->Login(sIRC->_Nick, sIRC->_User, sIRC->_Pass)) ++ { ++ TC_LOG_ERROR("misc", "*** TriniChat: Logged In And Running!! *"); ++ // While we are connected to the irc server keep listening for data on the socket ++ while (sIRC->Connected && !World::IsStopped()){ sIRC->SockRecv(); } ++ } ++ TC_LOG_ERROR("misc", "*** TriniChat: Connection To IRC Server Lost! ***"); ++ } ++ // When an error occures or connection lost cleanup ++ Disconnect(); ++ // Increase the connection counter ++ cCount++; ++ // if MAX_CONNECT_ATTEMPT is reached stop trying ++ if (sIRC->_MCA != 0 && cCount == sIRC->_MCA) ++ sIRC->Active = false; ++ // If we need to reattempt a connection wait WAIT_CONNECT_TIME milli seconds before we try again ++ if (sIRC->Active) ++ { ++ std::this_thread::sleep_for(std::chrono::milliseconds(_wct)); ++ } ++ } ++ else ++ { ++ // Socket could not initialize cancel ++ sIRC->Active = false; ++ TC_LOG_ERROR("misc", "** TriniChat: Could not initialize socket"); ++ } ++ } ++ while (!World::IsStopped()){}; ++} ++ ++std::string IRCClient::GetChatLine(int nItem) ++{ ++ return sIRC->ILINES[nItem]; ++} +diff --git a/src/server/game/TriniChat/IRCClient.h b/src/server/game/TriniChat/IRCClient.h +new file mode 100644 +index 0000000..a2e9327 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCClient.h +@@ -0,0 +1,291 @@ ++/* ++* Copyright (C) 2005-2008 MaNGOS ++* ++* Copyright (C) 2008 Trinity ++* ++* 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 _IRC_CLIENT_H ++#define _IRC_CLIENT_H ++ ++#include ++#include ++#include "boost/thread.hpp" ++//#include "Player.h" ++#include "IRCLog.h" ++#include "IRCCmd.h" ++ ++using namespace std; ++// The maximum ammount of channels used, must be >= config option ++#define MAX_CONF_CHANNELS 10 ++#define MAX_CHAT_LINES 10 ++#define MAX_CONF_BOTS 10 ++// time we need to wait before we try another connecton attempt ++// Default is 30 seconds ++#define MAX_SCRIPT_INST 10 ++// CLINES is used for the default chatlines ++// By using the GetChatLine function its easier and faster ++// to receieve the line you need. ++enum CLINES ++{ ++ IRC_WOW = 0, ++ WOW_IRC = 1, ++ JOIN_WOW = 2, ++ JOIN_IRC = 3, ++ LEAVE_WOW = 4, ++ LEAVE_IRC = 5, ++ CHANGE_NICK = 6 ++}; ++// CACTION is used by the Handle_WoW_Channel function ++// this function is called in channel.h when a player ++// joins or leave a channel inside the client. ++enum CACTION ++{ ++ CHANNEL_JOIN, ++ CHANNEL_LEAVE, ++}; ++ ++enum script_Names ++{ ++ MCS_Players_Online = 0, ++}; ++ ++void TrinityChatThread(); ++ ++// IRCClient main class ++class IRCClient ++{ ++ ++public: ++ // IRCClient Constructor ++ IRCClient(); ++ // IRCClient Destructor ++ ~IRCClient(); ++ // ZThread Entry ++ void run(); ++ static IRCClient* instance(boost::asio::io_service* ioService = nullptr) ++ { ++ static IRCClient instance; ++ ++ if (ioService != nullptr) ++ { ++ instance._ioService = ioService; ++ instance._strand = new boost::asio::strand(*ioService); ++ } ++ ++ return &instance; ++ } ++ // Send a message to the specified IRC channel ++ void Send_IRC_Channel(std::string sChannel, std::string sMsg, bool NoPrefix = false, std::string nType = "PRIVMSG"); ++public: ++ // AH Function ++ void AHCancel(uint64 itmid, std::string itmnme, std::string plname, uint32 faction); ++ //bool BeenToGMI(float posx, float posy, std::string player, std::string from); ++ // IRCClient active ++ bool Active; ++ // Connected to IRC ++ bool Connected; ++ // Socket indentifier ++ int SOCKET; ++ fd_set sfdset; ++ // Send data to IRC, in addition the endline is added \n ++ bool SendIRC(std::string data); ++ // This function is called in ChatHandler.cpp and processes the chat from game to IRC ++ void Send_WoW_IRC(Player *plr, std::string Channel, std::string Msg); ++ // Sends a message to all players on the specified channel ++ void Send_WoW_Channel(const char *channel, std::string chat); ++ // Send a system message to all players ++ void Send_WoW_System(std::string Message); ++ // Send a message to the specified IRC channel ++ //void Send_IRC_Channel(std::string sChannel, std::string sMsg, bool NoPrefix = false, std::string nType = "PRIVMSG"); ++ // Sends a message to all IRC Channels ++ void Send_IRC_Channels(std::string sMsg); ++ std::string MakeMsg(std::string msg, std::string var, std::string val) ++ { ++ std::size_t start = msg.find(var); ++ if (start != std::string::npos) ++ msg.replace(start, var.length(), val); ++ return msg; ++ } ++ void Send_WoW_Player(string sPlayer, string sMsg); ++ void Send_WoW_Player(Player *plr, string sMsg); ++ ++ // This function is called in Channel.cpp and processes Join/leave messages ++ void Handle_WoW_Channel(std::string Channel, Player *plr, int nAction); ++ void ResetIRC(); ++public: ++ void AutoJoinChannel(Player *plr); ++ ++public: ++ bool Script_Lock[5]; ++ bool _AmiOp; ++ ++public: ++ string _Mver; ++ // IRC Server host ++ string _Host; ++ // IRC Server Port ++ int _Port; ++ // IRC Username ++ string _User; ++ // IRC Password ++ string _Pass; ++ // IRC Nickname ++ string _Nick; ++ //Password for in-game channel ++ std::string _irc_pass[MAX_CONF_CHANNELS]; ++ // Authentication type ++ int _Auth; ++ string _Auth_Nick; ++ // IRC Connect code ++ string _ICC; ++ // IRC Default channel ++ string _defchan; ++ // IRC Leave Default channel ++ int _ldefc; ++ // Wait Connect Time ++ int _wct; ++ // Check if staff chat is enabled ++ int _staffLink; ++ // IRC Default Staff channel ++ string _staffChan; ++ // String that contains bot names ++ string _bot_names; ++ // Number of bots to ignore ++ string _ignore_bots[MAX_CONF_BOTS]; ++ // Ticket Channel ++ string ticann; ++ // Ticket Channel Password ++ string ticannpw; ++ // Status Channel ++ string Status; ++ // Status Channel Password ++ string Statuspw; ++ // Announce Channel ++ string anchn; ++ // Announce Channel Password ++ string anchnpw; ++ // Auto-announce timer ++ int autoanc; ++ // IRC Channel count ++ int _chan_count; ++ // IRC Channel list ++ // Array to store our IRC channels ++ // each element will corrospond ++ // with _wow_chan array below. ++ std::string _irc_chan[MAX_CONF_CHANNELS]; ++ // Game Channel list ++ std::string _wow_chan[MAX_CONF_CHANNELS]; ++ // AutoJoin Options ++ int ajoin; ++ string ajchan; ++ // Online Command Max Results ++ int onlrslt; ++ // Channel OnJoin/Restart/Kick Messages ++ string JoinMsg; ++ string RstMsg; ++ string kikmsg; ++ // Misc Options ++ string ojGM1; ++ string ojGM2; ++ string ojGM3; ++ string ojGM4; ++ string logfile; ++ string logchan; ++ string logchanpw; ++ int logmask; ++ int games; ++ int gmlog; ++ // IRC Commands Security Level ++ int CACCT; ++ int CBAN; ++ int CCHAN; ++ int CCHAR; ++ int CFUN; ++ int CHELP; ++ int CINCHAN; ++ int CINFO; ++ int CITEM; ++ int CJAIL; ++ int CKICK; ++ int _KILL; ++ int CLEVEL; ++ int CLOOKUP; ++ int CMONEY; ++ int CMUTE; ++ int CONLINE; ++ int CPM; ++ int CRECONNECT; ++ int CRELOAD; ++ int CREVIVE; ++ int CSAVEALL; ++ int CSERVERCMD; ++ int CSHUTDOWN; ++ int CSPELL; ++ int CSYSMSG; ++ int CTELE; ++ int CTOP; ++ int CPLAYER; ++ int CWHO; ++ // BotMask ++ int BOTMASK; ++ // TicketMask ++ int TICMASK; ++ // Max connect attempt ++ int _MCA; ++ // Auto rejoin when kicked from irc ++ int _autojoinkick; ++ // IRC Command prefix ++ string _cmd_prefx; ++ int _op_gm; ++ int _op_gm_lev; ++ // Array that contains our chatlines from the conf file ++ // To increase this value change the MAX_CHAT_LINE define above ++ // Make sure the number of elements must match your items ++ // (remeber this starts at 0 so 0..9 is 10 items) ++ // and that you load the line in the LoadConfig function. ++ string ILINES[MAX_CHAT_LINES]; ++ string GetChatLine(int nItem); ++ ++ int _Max_Script_Inst; ++ // MAX_SCRIPT_INST ++ ++ IRCLog iLog; ++ ++private: ++ // Returns default chatline based on enum CLINES ++ // Initialize socket library ++ bool InitSock(); ++ // Connect to IRC Server ++ bool Connect(const char *cHost, int nPort); ++ // Login to IRC Server ++ bool Login(std::string sNick, std::string sUser, std::string sPass); ++ // Send raw data to IRC ++ bool SendData(const char *data); ++ // Disconnect from IRC and cleanup socket ++ void Disconnect(); ++ // Processes the data receieved from IRC ++ void Handle_IRC(std::string sData); ++ // Receieves data from the socket. ++ void SockRecv(); ++ ++ // add boost ioservice ++ boost::asio::io_service* _ioService; ++ boost::asio::strand* _strand; ++}; ++ ++#define sIRC IRCClient::instance() ++#endif +diff --git a/src/server/game/TriniChat/IRCCmd.cpp b/src/server/game/TriniChat/IRCCmd.cpp +new file mode 100644 +index 0000000..260dee9 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCCmd.cpp +@@ -0,0 +1,857 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCCmd.h" ++#include "IRCClient.h" ++#include "Database/DatabaseEnv.h" ++#include "ObjectMgr.h" ++#include "AccountMgr.h" ++#include "MapManager.h" ++#include "World.h" ++// Constructor ++IRCCmd::IRCCmd(){} ++// Destructor ++IRCCmd::~IRCCmd(){} ++ ++std::string IRCCmd::MakeUpper(std::string Channel) ++{ ++ std::string tmpchan = Channel; ++ std::transform(tmpchan.begin(), tmpchan.end(), tmpchan.begin(), towupper); ++ return tmpchan; ++} ++bool IRCCmd::ParamsValid(_CDATA *CD, int pCnt) ++{ ++ CD->PCOUNT = pCnt; ++ if (CD->PARAMS.size() == 0) ++ return false; ++ return ValidParams(CD->PARAMS, pCnt); ++} ++ ++int IRCCmd::ParamsValid(_CDATA *CD, int pCnt, int rLev) ++{ ++ //CD->PCOUNT = pCnt; ++ if (!CanUse(CD->USER, rLev)) ++ return E_AUTH; ++ else if (pCnt == 0) ++ return E_OK; ++ else if (CD->PARAMS.size() == 0) ++ return E_SIZE; ++ else if (!ValidParams(CD->PARAMS, pCnt)) ++ return E_SIZE; ++ return E_OK; ++} ++ ++// This function checks if chat from irc is a command or not ++// return true on yes and false on no ++bool IRCCmd::IsValid(std::string USER, std::string FROM, std::string CHAT, std::string TYPE) ++{ ++ // If the first line of our chat is the command prefix we have a command ++ if (CHAT.substr(0, 1) == sIRC->_cmd_prefx && CHAT.size() > 1) ++ { ++ _CDATA CDATA; ++ bool cValid = false; ++ bool AuthValid = true; ++ bool dontlog = true; ++ std::string* _PARAMS = getArray(CHAT, 2); ++ CDATA.USER = USER; ++ CDATA.FROM = FROM; ++ CDATA.TYPE = TYPE; ++ CDATA.PCOUNT = 0; ++ CDATA.CMD = MakeUpper(_PARAMS[0].substr(1, _PARAMS[0].size() - 1)); ++ CDATA.PARAMS = _PARAMS[1]; ++ if (CDATA.CMD == "LOGIN") ++ { ++ if (FROM == sIRC->_Nick) ++ { ++ if (ParamsValid(&CDATA, 2)) ++ Handle_Login(&CDATA); ++ else ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"login )", true, "ERROR"); ++ } ++ else ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Please Send A PM To Login!", true, "ERROR"); ++ if (GetLevel(USER) >= sIRC->gmlog) ++ dontlog = false; ++ cValid = true; ++ } ++ else if (CDATA.CMD == "LOGOUT") ++ { ++ if (FROM == sIRC->_Nick) ++ { ++ Handle_Logout(&CDATA); ++ } ++ else ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Please Send A PM To Logout!", true, "ERROR"); ++ cValid = true; ++ } ++ else if (CDATA.CMD == "ACCT") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CACCT)) ++ { ++ case E_OK: ++ Account_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"acct <(un)lock/email/pass/rename/gmlevel>)", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "BAN") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CBAN)) ++ { ++ case E_OK: ++ Ban_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"ban )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "CHAN") ++ { ++ switch(ParamsValid(&CDATA, 1, sIRC->CCHAN)) ++ { ++ case E_OK: ++ Chan_Control(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"chan )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "CHAR") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CCHAR)) ++ { ++ case E_OK: ++ Char_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"char )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "FUN") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CFUN)) ++ { ++ case E_OK: ++ Fun_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"fun )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "HELP") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CHELP)) ++ { ++ case E_OK: ++ Help_IRC(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"help )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "INCHAN") ++ { ++ switch(ParamsValid(&CDATA, 1, sIRC->CINCHAN)) ++ { ++ case E_OK: ++ Inchan_Server(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"inchan )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "INFO") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CINFO)) ++ { ++ case E_OK: ++ Info_Server(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"info)", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "ITEM") ++ { ++ CDATA.PCOUNT = 3; ++ switch(ParamsValid(&CDATA, 2, sIRC->CITEM)) ++ { ++ case E_OK: ++ Item_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"item )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "JAIL") ++ { ++ CDATA.PCOUNT = 3; ++ switch(ParamsValid(&CDATA, 1, sIRC->CJAIL)) ++ { ++ case E_OK: ++ Jail_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"jail )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "KICK") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 1, sIRC->CKICK)) ++ { ++ case E_OK: ++ Kick_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"kick )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "KILL") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 1, sIRC->_KILL)) ++ { ++ case E_OK: ++ Kill_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"kill )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "LEVEL") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 2, sIRC->CLEVEL)) ++ { ++ case E_OK: ++ Level_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"level )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "LOOKUP") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 2, sIRC->CLOOKUP)) ++ { ++ case E_OK: ++ Lookup_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"lookup )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "MONEY") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 2, sIRC->CMONEY)) ++ { ++ case E_OK: ++ Money_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"money <(-)Money>)", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "MUTE") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CMUTE)) ++ { ++ case E_OK: ++ Mute_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"mute )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "ONLINE") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CONLINE)) ++ { ++ case E_OK: ++ Online_Players(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"online)", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "PM") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CPM)) ++ { ++ case E_OK: ++ PM_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"pm )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "RELOAD") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CRELOAD)) ++ { ++ case E_OK: ++ sIRC->Send_IRC_Channels("Reloading Configuration Options."); ++ sWorld->LoadConfigSettings(true); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "RECONNECT") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CRECONNECT)) ++ { ++ case E_OK: ++ sIRC->Send_IRC_Channels(sIRC->RstMsg); ++ sIRC->ResetIRC(); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "RESTART") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CSHUTDOWN)) ++ { ++ case E_OK: ++ Restart_Trinity(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"server )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "REVIVE") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 1, sIRC->CREVIVE)) ++ { ++ case E_OK: ++ Revive_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"revive )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "SAVEALL") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CSAVEALL)) ++ { ++ case E_OK: ++ Saveall_Player(&CDATA); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "SERVER") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 2, sIRC->CSERVERCMD)) ++ { ++ case E_OK: ++ Server(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"server )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "SHUTDOWN") ++ { ++ switch(ParamsValid(&CDATA, 1, sIRC->CSHUTDOWN)) ++ { ++ case E_OK: ++ Shutdown_Trinity(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"shutdown )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "SPELL") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CSPELL)) ++ { ++ case E_OK: ++ Spell_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"spell )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "SYSMSG") ++ { ++ CDATA.PCOUNT = 2; ++ switch(ParamsValid(&CDATA, 2, sIRC->CSYSMSG)) ++ { ++ case E_OK: ++ Sysmsg_Server(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"sysmsg )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "TELE") ++ { ++ switch(ParamsValid(&CDATA, 2, sIRC->CTELE)) ++ { ++ case E_OK: ++ Tele_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"tele )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "TOP") ++ { ++ CDATA.PCOUNT = 1; ++ switch(ParamsValid(&CDATA, 1, sIRC->CTOP)) ++ { ++ case E_OK: ++ Top_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"top )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "PLAYER") ++ { ++ CDATA.PCOUNT = 1; ++ switch(ParamsValid(&CDATA, 1, sIRC->CPLAYER)) ++ { ++ case E_OK: ++ Player_Player(&CDATA); ++ break; ++ case E_SIZE: ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Syntax Error! ("+sIRC->_cmd_prefx+"player )", true, "ERROR"); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ else if (CDATA.CMD == "WHO") ++ { ++ switch(ParamsValid(&CDATA, 0, sIRC->CWHO)) ++ { ++ case E_OK: ++ Who_Logged(&CDATA); ++ break; ++ case E_AUTH: ++ AuthValid = false; ++ break; ++ } ++ cValid = true; ++ } ++ if (!AuthValid && IsLoggedIn(USER)) ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Access Denied! Your Security Level Is Too Low To Use This Command!", true, "ERROR"); ++ if (cValid == false && (sIRC->BOTMASK & 4) != 0) ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : Unknown Command!", true, "ERROR"); ++ if (cValid && dontlog) ++ { ++ sIRC->iLog.WriteLog(" %s : [ %s(%d) ] Used Command: [ %s ] With Parameters: [ %s ]", sIRC->iLog.GetLogDateTimeStr().c_str(), CDATA.USER.c_str(), GetLevel(USER), CDATA.CMD.c_str(), CDATA.PARAMS.c_str()); ++ if ((sIRC->logmask & 1) != 0) ++ { ++ std::string logchan = "#"; ++ logchan += sIRC->logchan; ++ std::stringstream ss; ++ ss << sIRC->iLog.GetLogDateTimeStr() << ": [ " << CDATA.USER << "(" << GetLevel(USER) << ") ] Used Command: [ " << CDATA.CMD << " ] With Parameters: [" << CDATA.PARAMS << " ]"; ++ sIRC->Send_IRC_Channel(logchan, ss.str().c_str(), true); ++ } ++ } ++ return cValid; ++ } ++ return false; ++} ++ ++bool IRCCmd::CanUse(std::string USER, int nLevel) ++{ ++ if (IsLoggedIn(USER)) ++ { ++ if (GetLevel(USER) >= nLevel) ++ return true; ++ else ++ return false; ++ } ++ else if (nLevel == 0) ++ { ++ return true; ++ } ++ else ++ sIRC->Send_IRC_Channel(USER, "\0034[ERROR] : You Are Not Logged In!", true, "ERROR"); ++ return false; ++} ++ ++std::string IRCCmd::ChanOrPM(_CDATA *CD) ++{ ++ if (CD->FROM == sIRC->_Nick) ++ return CD->USER; ++ else ++ return CD->FROM; ++} ++ ++Player *IRCCmd::GetPlayer(std::string WHO) ++{ ++ normalizePlayerName(WHO); ++ return ObjectAccessor::FindPlayerByName(WHO.c_str()); ++} ++ ++_client *IRCCmd::GetClient(std::string cname) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == cname) ++ return (*i); ++ } ++ return (NULL); ++} ++ ++bool IRCCmd::IsLoggedIn(std::string USER) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == USER) ++ return true; ++ } ++ return false; ++} ++ ++bool IRCCmd::AcctIsLoggedIn(std::string USER) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if (MakeUpper((*i)->UName) == MakeUpper(USER)) ++ return true; ++ } ++ return false; ++} ++ ++std::string IRCCmd::AcctIsBanned(std::string ACCT) ++{ ++ uint32 acctid = AccountMgr::GetId(ACCT); ++ std::string banned = "NOTBANNED"; ++ QueryResult result = LoginDatabase.PQuery("SELECT banreason FROM ip_banned WHERE ip=(SELECT last_ip FROM account WHERE id = '%i')", acctid); ++ if (result) ++ { ++ banned = (*result)[0].GetCString(); ++ ++ return "IP Banned. Reason:" + banned; ++ } ++ QueryResult result2 = LoginDatabase.PQuery("SELECT banreason FROM account_banned WHERE id='%i' AND active =1", acctid); ++ if (result2) ++ { ++ banned = (*result2)[0].GetCString(); ++ ++ return "Account Banned. Reason:" + banned; ++ } ++ return banned; ++} ++ ++int IRCCmd::GetLevel(std::string sName) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == sName) ++ return (*i)->GMLevel; ++ } ++ return 0; ++} ++ ++int IRCCmd::AcctLevel(std::string plnme) ++{ ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(plnme); ++ uint32 account_id = 0; ++ uint32 security = 0; ++ account_id = sObjectMgr->GetPlayerAccountIdByGUID(guid); ++ security = AccountMgr::GetSecurity(account_id); ++ return security; ++} ++ ++std::string IRCCmd::GetAccName(std::string sName) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == sName) ++ return (*i)->UName; ++ } ++ return ""; ++} ++ ++std::string IRCCmd::GetNameFromAcct(std::string sName) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->UName == sName) ++ return (*i)->Name; ++ } ++ return ""; ++} ++ ++int IRCCmd::GetAcctIDFromName(std::string sName) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == sName) ++ { ++ uint32 acct_id = 0; ++ acct_id = AccountMgr::GetId((*i)->UName.c_str()); ++ return acct_id; ++ } ++ } ++ return 0; ++} ++ ++std::string IRCCmd::GetAcctNameFromID(uint32 acctid) ++{ ++ QueryResult result = LoginDatabase.PQuery("SELECT username FROM account WHERE id = '%d'", acctid); ++ if (result) ++ { ++ std::string name = (*result)[0].GetCString(); ++ ++ return name; ++ } ++ ++ return ""; ++} ++ ++std::string IRCCmd::GetIPFromPlayer(std::string player) ++{ ++ QueryResult result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE name = '%s'", player.c_str()); ++ if (result) ++ { ++ std::string acctid = (*result)[0].GetCString(); ++ ++ QueryResult result2 = LoginDatabase.PQuery("SELECT last_ip FROM account WHERE id = '%s'", acctid.c_str()); ++ if (result2) ++ { ++ std::string ip = (*result2)[0].GetCString(); ++ ++ return ip; ++ } ++ } ++ ++ return ""; ++} ++ ++std::string IRCCmd::SecToDay(std::string secons) ++{ ++ unsigned int seconds = atoi(secons.c_str()); ++ unsigned int days = seconds / 86400; ++ unsigned int hours = seconds / 3600 % 24; ++ unsigned int mins = seconds / 60 % 60; ++ char tottime[1000]; ++ sprintf(tottime, "%iDays:%iHours:%iMinutes", days, hours, mins); ++ ++ return tottime; ++} ++ ++bool IRCCmd::ValidParams(std::string PARAMS, int nCount) ++{ ++ if (nCount == 1 && PARAMS.size() == 0) ++ return false; ++ int pcount = 0; ++ int p = -1; ++ for (int i = 0;i < nCount;i++) ++ { ++ p = PARAMS.find(" ", p + 1); ++ if (p == -1) ++ break; ++ else ++ pcount++; ++ } ++ nCount--; ++ if (pcount >= nCount) ++ return true; ++ else ++ return false; ++} ++ ++std::string* IRCCmd::getArray(std::string PARAMS, int nCount) ++{ ++ std::string *array = new std::string[nCount]; ++ if (PARAMS.size() > 0) ++ { ++ size_t ps = 0; ++ size_t pc = -1; ++ for (int i = 0;i < nCount;i++) ++ { ++ pc = PARAMS.find(" ", pc + 1); ++ if (i + 1 == nCount && nCount != 1) ++ { ++ if (ps > 0 && pc > 0) ++ array[i] = PARAMS.substr(ps, PARAMS.size() - ps); ++ } ++ else ++ array[i] = PARAMS.substr(ps, pc - ps); ++ ps = pc + 1; ++ } ++ } ++ return array; ++} ++ ++std::string IRCCmd::MakeMsg(const char *sLine, ...) ++{ ++ va_list ap; ++ char tmpoutp[1024]; ++ va_start(ap, sLine); ++ vsnprintf(tmpoutp, 1024, sLine, ap); ++ va_end(ap); ++ std::string outp = tmpoutp; ++ return outp; ++} +diff --git a/src/server/game/TriniChat/IRCCmd.h b/src/server/game/TriniChat/IRCCmd.h +new file mode 100644 +index 0000000..6921f8c +--- /dev/null ++++ b/src/server/game/TriniChat/IRCCmd.h +@@ -0,0 +1,144 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * 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 _IRC_CMD_H ++#define _IRC_CMD_H ++ ++#define MAX_CLIENTS 50 ++//include boost ++#include "boost/thread.hpp" ++#include "boost/date_time.hpp" ++#include "Common.h" ++#include "Player.h" ++#include "ObjectAccessor.h" ++#include "BattlegroundMgr.h" ++ ++struct ChannelUser ++{ ++ int UserType; ++ std::string Name; ++ std::string UName; ++ int UserLevel; ++}; ++ ++struct _client ++{ ++ bool LoggedIn; ++ std::string Name; ++ std::string UName; ++ int GMLevel; ++}; ++struct _CDATA ++{ ++ std::string CMD; ++ std::string USER; ++ std::string FROM; ++ std::string PARAMS; ++ std::string TYPE; ++ int PCOUNT; ++}; ++enum APVERR ++{ ++ E_OK, ++ E_SIZE, ++ E_AUTH, ++ E_IVALID, ++}; ++enum ESOUNDS ++{ ++ S_ENTERWORLD = 602, ++ S_QUESTFAILED = 847, ++ S_INVITE = 880, ++ S_LEVELUP = 888, ++ S_COINSOUND = 895, ++ S_WHISPER = 3081, ++ S_STEALTH = 3325, ++}; ++class IRCCmd ++{ ++ public: ++ IRCCmd(); ++ ~IRCCmd(); ++ ++ void Handle_Logout(_CDATA *CD); ++ bool IsLoggedIn(std::string USER); ++ bool IsValid(std::string USER, std::string FROM, std::string CHAT, std::string TYPE); ++ bool AcctIsLoggedIn(std::string USER); ++ _client *GetClient(std::string cname); ++ ++ public: ++ static std::string MakeMsg(const char *sLine, ...); ++ static std::string ChanOrPM(_CDATA *CD); ++ int AcctLevel(std::string plnme); ++ int GetLevel(std::string sName); ++ std::string MakeUpper(std::string Channel); ++ std::string AcctIsBanned(std::string ACCT); ++ std::list<_client*> _CLIENTS; ++ Player* GetPlayer(std::string WHO); ++ ++ private: ++ // TriniChat Commands ++ void Handle_Login(_CDATA *CD); ++ void Account_Player(_CDATA *CD); ++ void Ban_Player(_CDATA *CD); ++ void Chan_Control(_CDATA *CD); ++ void Char_Player(_CDATA *CD); ++ void Fun_Player(_CDATA *CD); ++ void Help_IRC(_CDATA *CD); ++ void Inchan_Server(_CDATA *CD); ++ void Info_Server(_CDATA *CD); ++ void Item_Player(_CDATA *CD); ++ void Jail_Player(_CDATA *CD); ++ void Kick_Player(_CDATA *CD); ++ void Kill_Player(_CDATA *CD); ++ void Player_Player(_CDATA *CD); ++ void Lookup_Player(_CDATA *CD); ++ void Level_Player(_CDATA *CD); ++ void Money_Player(_CDATA *CD); ++ void Mute_Player(_CDATA *CD); ++ void Online_Players(_CDATA *CD); ++ void PM_Player(_CDATA *CD); ++ void Restart_Trinity(_CDATA *CD); ++ void Revive_Player(_CDATA *CD); ++ void Saveall_Player(_CDATA *CD); ++ void Server(_CDATA *CD); ++ void Shutdown_Trinity(_CDATA *CD); ++ void Spell_Player(_CDATA *CD); ++ void Sysmsg_Server(_CDATA *CD); ++ void Tele_Player(_CDATA *CD); ++ void Top_Player(_CDATA *CD); ++ void Who_Logged(_CDATA *CD); ++ bool CanUse(std::string USER, int nLevel); ++ bool ValidParams(std::string PARAMS, int nCount = 1); ++ bool ParamsValid(_CDATA *CD, int pCnt); ++ int ParamsValid(_CDATA *CD, int pCnt, int rLev); ++ std::string GetAccName(std::string sName); ++ std::string GetNameFromAcct(std::string sName); ++ std::string GetAcctNameFromID(uint32 acctid); ++ std::string GetIPFromPlayer(std::string player); ++ std::string SecToDay(std::string secons); ++ int GetAcctIDFromName(std::string sName); ++ std::string* getArray(std::string PARAMS, int nCount = 1); ++}; ++inline void MakeLower(std::string& str) ++{ ++ std::transform(str.begin(), str.end(), str.begin(), ::tolower); ++} ++#endif +diff --git a/src/server/game/TriniChat/IRCCmde.cpp b/src/server/game/TriniChat/IRCCmde.cpp +new file mode 100644 +index 0000000..15e9c6e +--- /dev/null ++++ b/src/server/game/TriniChat/IRCCmde.cpp +@@ -0,0 +1,2353 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCCmd.h" ++#include ++#include "IRCClient.h" ++#include "MCS_OnlinePlayers.h" ++#include "WorldPacket.h" ++#include "Database/DatabaseEnv.h" ++#include "Chat.h" ++#include "MapManager.h" ++#include "World.h" ++#include "Guild.h" ++#include "ObjectMgr.h" ++#include "AccountMgr.h" ++#include "Language.h" ++#include "SpellAuras.h" ++#include "Config.h" ++#include "ReputationMgr.h" ++#include "ArenaTeamMgr.h" ++#include "GitRevision.h" ++ ++#define Send_Player(p, m) sIRC->Send_WoW_Player(p, m) ++#define Send_IRCA(c, m, b, t) sIRC->Send_IRC_Channel(c, m, b, t) ++ ++#ifdef WIN32 ++#define Delay(x) Sleep(x) ++#else ++#define Delay(x) sleep(x / 1000) ++#endif ++ ++void IRCCmd::Handle_Login(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ std::string isbanned = AcctIsBanned(_PARAMS[0]); ++ LoginDatabase.EscapeString(_PARAMS[0]); ++ LoginDatabase.EscapeString(_PARAMS[1]); ++ if (isbanned == "NOTBANNED") ++ { ++ if (!IsLoggedIn(CD->USER)) ++ { ++ if (!AcctIsLoggedIn(_PARAMS[0].c_str())) ++ { ++ QueryResult result = LoginDatabase.PQuery("SELECT `gmlevel` FROM `account`, `account_access` WHERE `username`='%s' AND `account_access`.`id`=`account`.`id` AND `sha_pass_hash`=SHA1(CONCAT(UPPER(`username`),':',UPPER('%s')));", _PARAMS[0].c_str(), _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ int GMLevel = fields[0].GetUInt8(); ++ if (GMLevel >= 0) ++ { ++ _client *NewClient = new _client(); ++ NewClient->Name = CD->USER; ++ NewClient->UName = MakeUpper(_PARAMS[0]); ++ NewClient->GMLevel = fields[0].GetUInt8(); ++ _CLIENTS.push_back(NewClient); ++ Send_IRCA(CD->USER, MakeMsg("You Are Now Logged In As %s.", _PARAMS[0].c_str()), true, CD->TYPE); ++ ++ if (sIRC->_op_gm == 1 && GMLevel >= sIRC->_op_gm_lev) ++ { ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ sIRC->SendIRC("MODE #"+sIRC->_irc_chan[i]+" +o "+CD->USER); ++ } ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Sorry, Your Username Or Password Is Incorrect. Please Try Again. ", true, "ERROR"); ++ } ++ else ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->UName == _PARAMS[0]) ++ Send_IRCA(CD->USER, MakeMsg("%s Is Already Logged In With This Username. ", (*i)->Name.c_str()), true, "ERROR"); ++ } ++ } ++ else ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == CD->USER) ++ Send_IRCA(CD->USER, MakeMsg("You are already logged in as %s.", (*i)->UName.c_str()), true, "ERROR"); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Sorry, you are "+isbanned+" and can not log in.", true, "ERROR"); ++} ++ ++void IRCCmd::Handle_Logout(_CDATA *CD) ++{ ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ if ((*i)->Name == CD->USER) ++ { ++ delete (*i); ++ i = _CLIENTS.erase(i); ++ Send_IRCA(CD->USER, "Successfully logged out", true, CD->TYPE); ++ return; ++ } ++ } ++ Send_IRCA(CD->USER, "Not logged in", true, "ERROR"); ++} ++ ++void IRCCmd::Account_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 3); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ normalizePlayerName(_PARAMS[0]); ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[0]); ++ int account_id = 0; ++ account_id = sObjectMgr->GetPlayerAccountIdByGUID(guid); ++ if (account_id) ++ { ++ if (account_id == GetAcctIDFromName(CD->USER) || GetLevel(CD->USER) >= sIRC->_op_gm_lev) ++ { ++ Player* plr = ObjectAccessor::FindPlayer(guid); ++ if (_PARAMS[1] == "lock") ++ { ++ LoginDatabase.PExecute("UPDATE `account` SET `locked` = '1' WHERE `id` = '%d'",account_id); ++ if (plr) Send_Player(plr, MakeMsg("Your Account Has Been Locked To Your Current IP By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : Account Has Been Locked To Their Current IP Address.", true, CD->TYPE); ++ } ++ else if (_PARAMS[1] == "unlock") ++ { ++ LoginDatabase.PExecute("UPDATE `account` SET `locked` = '0' WHERE `id` = '%d'",account_id); ++ if (plr) Send_Player(plr, MakeMsg("Your Account Has Been UnLocked From The Associated IP By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : Account Has Been UnLocked From The Associated IP Address.", true, CD->TYPE); ++ } ++ else if (_PARAMS[1] == "email") ++ { ++ LoginDatabase.PExecute("UPDATE `account` SET `email` = '%s' WHERE `id` = '%d'",_PARAMS[2].c_str() ,account_id); ++ if (plr) Send_Player(plr, MakeMsg("%s Has Changed Your EMail Adress To: %s", CD->USER.c_str(), _PARAMS[2].c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : EMail Address Successfully Changed To: "+_PARAMS[2], true, CD->TYPE); ++ } ++ else if (_PARAMS[1] == "pass") ++ { ++ LoginDatabase.PExecute("UPDATE `account` SET `sha_pass_hash` = SHA1(CONCAT(UPPER(`username`),':',UPPER('%s'))) WHERE `id` = '%d'",_PARAMS[2].c_str() ,account_id); ++ if (plr) Send_Player(plr, MakeMsg("%s Has Changed Your Password To: %s", CD->USER.c_str(), _PARAMS[2].c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : Password Successfully Changed To: "+_PARAMS[2], true, CD->TYPE); ++ } ++ else if (_PARAMS[1] == "rename") ++ { ++ if (plr) ++ { ++ plr->SetAtLoginFlag(AT_LOGIN_RENAME); ++ Send_Player(plr, MakeMsg("%s Has Requested You Change This Characters Name, Rename Will Be Forced On Next Login!", CD->USER.c_str())); ++ } ++ CharacterDatabase.PExecute("UPDATE `characters` SET `at_login` = `at_login` | '1' WHERE `guid` = '%u'", guid); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : Has Been Forced To Change Their Characters Name, Rename Will Be Forced On Next Login!", true, CD->TYPE); ++ } ++ else if (_PARAMS[1] == "gmlevel") ++ { ++ const char *cgmlevel = _PARAMS[2].c_str(); ++ if (GetLevel(CD->USER) >= atoi(cgmlevel)) ++ { ++ LoginDatabase.PExecute("UPDATE `account_access` SET `gmlevel` = '%s' WHERE `id` = '%d'", _PARAMS[2].c_str(), account_id); ++ Send_IRCA(ChanOrPM(CD), "\00313["+GetAcctNameFromID(account_id)+"] : Has GM Level Successfully Changed To: "+_PARAMS[2], true, CD->TYPE); ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "The Specified GM Level Is Higher Than Your GM Level.", true, "ERROR"); ++ } ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "You Are Not A GM, You May Only Change Settings In Your Own Account.", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "No such player - account lookup failed", true, "ERROR"); ++} ++ ++void IRCCmd::Ban_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 4); ++ std::string duration = SecToDay (_PARAMS[3].c_str()); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (_PARAMS[1] == "ip") ++ { ++ std::string ip = GetIPFromPlayer(_PARAMS[0]); ++ if (_PARAMS[2] == "") ++ _PARAMS[2] = "No Reason"; ++ if (ip != "") ++ { ++ sWorld->BanAccount(BAN_IP, ip.c_str(), _PARAMS[3].c_str(), _PARAMS[2], CD->USER); ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ plr->GetSession()->KickPlayer(); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("[%s] Has Had Their IP Address Banned. [%s] Reason: %s Duration: %s",_PARAMS[0].c_str() ,ip.c_str() , _PARAMS[2].c_str(), _PARAMS[3].c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Can not locate IP-address for that charactername", true, "ERROR"); ++ } ++ if (_PARAMS[1] == "acct") ++ { ++ ++ if (_PARAMS[2] == "") ++ _PARAMS[2] = "No reason"; ++ if (_PARAMS[3] == "")//set standard bantime to 1 day ++ _PARAMS[3] = "1d"; ++ QueryResult result = LoginDatabase.PQuery("SELECT id FROM `account` WHERE username = '%s'", _PARAMS[0].c_str()); ++ if (result) ++ { ++ sWorld->BanAccount(BAN_ACCOUNT, _PARAMS[0].c_str(), _PARAMS[3].c_str(), _PARAMS[2], CD->USER); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("[%s] has been account-banned. Reason: %s Duration: %s",_PARAMS[0].c_str(), _PARAMS[2].c_str(), _PARAMS[3].c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Can not find any accounts for that accountname", true, "ERROR"); ++ ++ } ++ if (_PARAMS[1] == "unban") ++ { ++ std::string unbani = _PARAMS[0]; ++ if (atoi(unbani.c_str()) > 0) ++ { ++ LoginDatabase.PExecute("DELETE FROM ip_banned WHERE ip = '%s'", _PARAMS[0].c_str()); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Has Been Removed From The IP Ban List.", _PARAMS[0].c_str()), true, CD->TYPE); ++ } ++ else ++ { ++ QueryResult result = LoginDatabase.PQuery("SELECT id FROM `account` WHERE username = '%s'", _PARAMS[0].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string id = fields[0].GetString(); ++ ++ LoginDatabase.PExecute("DELETE FROM account_banned WHERE id = %s", id.c_str()); ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Has Been Removed From The Account Ban List.", _PARAMS[0].c_str()), true, CD->TYPE); ++ ++ } ++ else ++ Send_IRCA(CD->USER, "I Cannot Locate An Account Or IP Address For The Paramaters Given.", true, "ERROR"); ++ } ++ } ++} ++ ++void IRCCmd::Char_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 5); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ normalizePlayerName(_PARAMS[0]); ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[0]); ++ Player* plr = ObjectAccessor::FindPlayer(guid); ++ if (plr) ++ { ++ if (_PARAMS[1] == "mapcheat") ++ { ++ bool explore = false; ++ if (_PARAMS[2] != "0") ++ explore = true; ++ for (uint8 i=0; i<64; i++) ++ { ++ if (_PARAMS[2] != "0") ++ plr->SetFlag(PLAYER_EXPLORED_ZONES_1+i,0xFFFFFFFF); ++ else ++ plr->SetFlag(PLAYER_EXPLORED_ZONES_1+i,0); ++ } ++ if (explore) ++ { ++ Send_Player(plr, MakeMsg("All Your Zones Have Been Set To Explored By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Now Explored All Zones.", true, CD->TYPE); ++ } ++ else ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Now Had All Zones Set To Un-Explored.", true, CD->TYPE); ++ Send_Player(plr, MakeMsg("All Your Zones Have Been Set To Un-Explored By: %s", CD->USER.c_str())); ++ } ++ } ++ if (_PARAMS[1] == "taxicheat") ++ { ++ if (_PARAMS[2] != "0") ++ { ++ plr->SetTaxiCheater(true); ++ Send_Player(plr, MakeMsg("Taxi Node Cheat Has Been Enabled By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Taxi Node Cheat Has Been Enabled.", true, CD->TYPE); ++ } ++ else ++ { ++ plr->SetTaxiCheater(false); ++ Send_Player(plr, MakeMsg("Taxi Node Cheat Has Been Disabled By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Taxi Node Cheat Has Been Disabled.", true, CD->TYPE); ++ } ++ } ++ if (_PARAMS[1] == "maxskill") ++ { ++ plr->UpdateSkillsToMaxSkillsForLevel(); ++ Send_Player(plr, MakeMsg("Your Skills Have Been Maxed Out By: %s", CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Skills Have Been Maxed Out.", true, CD->TYPE); ++ } ++ if (_PARAMS[1] == "setskill") ++ { ++ uint32 skill = atoi(_PARAMS[2].c_str()); ++ uint32 step = atoi(_PARAMS[3].c_str()); ++ uint32 level = atol(_PARAMS[4].c_str()); ++ int32 max = _PARAMS[5].c_str() ? atol (_PARAMS[5].c_str()) : plr->GetPureMaxSkillValue(skill); ++ SkillLineEntry const* skilllookup = sSkillLineStore.LookupEntry(skill); ++ //if skillid entered is not a number and greater then 0 then the command is being used wrong ++ if (skill > 0) ++ { ++ //does the skill even exist ++ if (skilllookup) ++ { ++ //does player have the skill yet ++ if (plr->GetSkillValue(skill)) ++ { ++ plr->SetSkill(skill,step,level,max); ++ Send_Player(plr, MakeMsg("Skill: %s Has Been Set To Level: %i Max: %i By: %s",skilllookup->name[0], level, max, CD->USER.c_str())); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Has Had Skill: %s Set To Level: %d Max: %d",_PARAMS[0].c_str() , skilllookup->name[0], level, max), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, MakeMsg("Player Does Not Have The %s Skill Yet.", skilllookup->name[0]), true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "That Skill ID Does Not Exist.", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "The Skill ID Entered Is Invalid.", true, "ERROR"); ++ } ++ if (_PARAMS[1] == "combatstop") ++ { ++ if (!plr->IsInCombat()) ++ { ++ plr->CombatStop(); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Was Dropped From Combat",_PARAMS[0].c_str()), true, CD->TYPE); ++ }else ++ { ++ Send_IRCA(CD->USER, "Specified Player Is Not In Combat.", true, "ERROR"); ++ } ++ } ++ if (_PARAMS[1] == "quest") ++ { ++ std::string s_param = _PARAMS[3]; ++ std::string QName = ""; ++ char *args = (char*)s_param.c_str(); ++ uint32 qId = 0; ++ if (args[0]=='[') ++ { ++ char* cQName = strtok((char*)args, "]"); ++ if (cQName && cQName[0]) ++ { ++ QName = cQName+1; ++ WorldDatabase.EscapeString(QName); ++ QueryResult result = WorldDatabase.PQuery("SELECT id FROM quest_template WHERE name = '%s'", QName.c_str()); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "Quest Not Found!", true, "ERROR"); ++ return; ++ } ++ qId = result->Fetch()->GetUInt16(); ++ ++ } ++ } ++ else ++ { ++ qId = atoi(args); ++ QueryResult result = WorldDatabase.PQuery("SELECT title FROM quest_template WHERE id = '%d'", qId); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "Quest Not Found!", true, "ERROR"); ++ return; ++ } ++ QName = result->Fetch()->GetString(); ++ ++ } ++ if (_PARAMS[2] == "add") ++ { ++ QueryResult item_max = WorldDatabase.PQuery("SELECT MAX(entry) FROM item_template"); ++ Quest const* pQuest = sObjectMgr->GetQuestTemplate(qId); ++ for (uint32 id = 0; id < item_max->Fetch()->GetUInt32(); id++) ++ { ++ ItemTemplate const *pProto = sObjectMgr->GetItemTemplate(id); ++ if (!pProto) ++ continue; ++ ++ if (pProto->StartQuest == qId) ++ { ++ Send_IRCA(CD->USER, MakeMsg("This Quest Requires Activation By Item %d, Add It To The Player And Start Quest Manually.", pProto->ItemId),true, "ERROR"); ++ } ++ } ++ ++ if (plr->CanAddQuest(pQuest, true)) ++ { ++ plr->AddQuest(pQuest, NULL); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Had Quest [%s] Added To Quest Log.", _PARAMS[0].c_str(), QName.c_str()), true, "ERROR"); ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Cannot Add Quest To Player, He Either Has No Space Or He Already Has The Quest In His Quest Log.", true, "ERROR"); ++ } ++ } ++ if (_PARAMS[2] == "complete") ++ { ++ Quest const* pQuest = sObjectMgr->GetQuestTemplate(qId); ++ if (plr->GetQuestStatus(qId) == QUEST_STATUS_NONE) ++ { ++ Send_IRCA(CD->USER, "Player Does Not Have This Quest In Quest Log, Cannot Complete It.", true, "ERROR"); ++ } ++ else ++ { ++ for (uint8 x = 0; x < QUEST_OBJECTIVES_COUNT; ++x) ++ { ++ uint32 id = pQuest->RequiredItemId[x]; ++ uint32 count = pQuest->RequiredItemCount[x]; ++ if (!id || !count) ++ continue; ++ uint32 curItemCount = plr->GetItemCount(id,true); ++ ItemPosCountVec dest; ++ uint8 msg = plr->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, id, count-curItemCount); ++ if (msg == EQUIP_ERR_OK) ++ { ++ Item* item = plr->StoreNewItem(dest, id, true); ++ plr->SendNewItem(item,count-curItemCount,true,false); ++ } ++ } ++ ++ for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++) ++ { ++ uint32 creature = pQuest->RequiredNpcOrGo[i]; ++ uint32 creaturecount = pQuest->RequiredNpcOrGoCount[i]; ++ /*if (uint32 spell_id = pQuest->RequiredSpellCast[i]) ++ { ++ for (uint16 z = 0; z < creaturecount; ++z) ++ plr->CastedCreatureOrGO(creature,0,spell_id); ++ } ++ else */ ++ if (creature > 0) ++ { ++ if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(creature)) ++ for (uint16 z = 0; z < creaturecount; ++z) ++ plr->KilledMonster(cInfo,ObjectGuid::Empty); ++ } ++ /*else if (creature < 0) ++ { ++ for (uint16 z = 0; z < creaturecount; ++z) ++ plr->CastedCreatureOrGO(creature,0,0); ++ }*/ ++ } ++ ++ if (uint32 repFaction = pQuest->GetRepObjectiveFaction()) ++ { ++ uint32 repValue = pQuest->GetRepObjectiveValue(); ++ uint32 curRep = plr->GetReputationMgr().GetReputation(repFaction); ++ if (curRep < repValue) ++ { ++ FactionEntry const *factionEntry = sFactionStore.LookupEntry(repFaction); ++ plr->GetReputationMgr().SetReputation(factionEntry,repValue); ++ } ++ } ++ ++ int32 ReqOrRewMoney = pQuest->GetRewOrReqMoney(); ++ if (ReqOrRewMoney < 0) ++ plr->ModifyMoney(-ReqOrRewMoney); ++ ++ plr->CompleteQuest(qId); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Quest [%s] Status Set To Complete.", _PARAMS[0].c_str(), QName.c_str()), true, CD->TYPE); ++ } ++ } ++ } ++ if (_PARAMS[1] == "mod") ++ { ++ /*if (_PARAMS[2] == "rep") TODO ++ { ++ uint32 factionId = atoi((char*)_PARAMS[3].c_str()); ++ FactionEntry const *factionEntry = sFactionStore.LookupEntry(factionId); ++ if (!factionEntry) ++ { ++ Send_IRCA(CD->USER, "No Faction With That Name Exists.", true, "ERROR"); ++ } ++ else ++ { ++ int32 amount = atol((char*)_PARAMS[4].c_str()); ++ if (amount > -39000 && amount < 43000) ++ { ++ plr->SetFactionReputation(factionId,amount); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Reputation With Faction %s Set To %s.", _PARAMS[0].c_str(), _PARAMS[3].c_str(), _PARAMS[4].c_str()), true, CD->TYPE); ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Reputation Value Incorrect. Must Be Between -39000 and 43000.", true, "ERROR"); ++ } ++ } ++ }*/ ++ if (_PARAMS[2] == "morph") ++ { ++ uint16 display_id = (uint16)atoi((char*)_PARAMS[3].c_str()); ++ plr->SetDisplayId(display_id); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Has Been Morphed Into DisplayID: %s.", _PARAMS[0].c_str(), _PARAMS[3].c_str()), true, CD->TYPE); ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Valid Parameters Are: morph , reputation .", true, "ERROR"); ++ } ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "No Character With That Name Exists.", true, "ERROR"); ++} ++ ++void IRCCmd::Fun_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 3); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ if (_PARAMS[1] == "say") ++ { ++ plr->Say(_PARAMS[2], LANG_UNIVERSAL); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Was Forced To Say: "+_PARAMS[2]+".", true, CD->TYPE); ++ } ++ if (_PARAMS[1] == "sound") ++ { ++ uint32 sndid = atoi(_PARAMS[2].c_str()); ++ plr->PlayDirectSound(sndid , plr->ToPlayer()); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Just Heard Sound ID: "+_PARAMS[2]+".", true, CD->TYPE); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Player Not Online!", true, "ERROR"); ++} ++ ++void IRCCmd::Help_IRC(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 1); ++ QueryResult result = WorldDatabase.PQuery("SELECT `Command`, `Description`, `gmlevel` FROM `irc_commands`"); ++ if (result) ++ { ++ if (IsLoggedIn(CD->USER)) ++ { ++ if (_PARAMS[0] == "") ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT * FROM `irc_commands` WHERE `gmlevel` <= %u ORDER BY `Command`", GetLevel(CD->USER)); ++ if (result) ++ { ++ std::string output = "\002TriniChat IRC Commands:\017 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ Field *fields = result->Fetch(); ++ output += fields[0].GetString() + ", "; ++ result->NextRow(); ++ } ++ ++ Send_IRCA(CD->USER, output, true, CD->TYPE.c_str()); ++ } ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT `Description`, `gmlevel` FROM `irc_commands` WHERE `Command` = '%s'", _PARAMS[0].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ if (fields[1].GetInt32() > GetLevel(CD->USER)) ++ { ++ Send_IRCA(CD->USER, "You Do Not Have Access To That Command, So No Help Is Available.", true, CD->TYPE.c_str()); ++ return; ++ } ++ if (result) ++ { ++ std::string cmdhlp = fields[0].GetString(); ++ ++ Send_IRCA(CD->USER, cmdhlp, true, CD->TYPE.c_str()); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "No Such Command Exists, Please Check The Spelling And Try Again.", true, "ERROR"); ++ } ++ } ++ else if (!IsLoggedIn(CD->USER)) ++ { ++ if (_PARAMS[0] == "") ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT * FROM `irc_commands` WHERE `gmlevel` = 0 ORDER BY `Command`"); ++ if (result) ++ { ++ std::string output = "\002TriniChat IRC Commands:\017 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ Field *fields = result->Fetch(); ++ output += fields[0].GetString() + ", "; ++ result->NextRow(); ++ } ++ ++ Send_IRCA(CD->USER, output, true, CD->TYPE.c_str()); ++ Send_IRCA(CD->USER, "You Are Currently Not Logged In, Please Login To See A Complete List Of Commands Available To You.", true, CD->TYPE.c_str()); ++ } ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT `Description`, `gmlevel` FROM `irc_commands` WHERE `Command` = '%s'", _PARAMS[0].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ if (fields[1].GetUInt32() > 0) ++ { ++ Send_IRCA(CD->USER, "You Do Not Have Access To That Command, So No Help Is Available.", true, CD->TYPE.c_str()); ++ return; ++ } ++ std::string cmdhlp = fields[0].GetString(); ++ ++ Send_IRCA(CD->USER, cmdhlp, true, CD->TYPE.c_str()); ++ } ++ else ++ Send_IRCA(CD->USER, "No Such Command Exists, Please Check The Spelling And Try Again.", true, "ERROR"); ++ } ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Database Error! Please Make Sure You Used irc_commands.sql, You Must Have A Table In Your World Database (irc_commands)!", true, "ERROR"); ++} ++ ++void IRCCmd::Inchan_Server(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 1); ++ if (_PARAMS[0] == "") ++ { ++ Send_IRCA(CD->USER, "Syntax Error! ("+sIRC->_cmd_prefx+"inchan )", true, "ERROR"); ++ return; ++ } ++ QueryResult result = WorldDatabase.PQuery("SELECT * FROM `irc_inchan` WHERE `channel` = '%s' ORDER BY `name`", _PARAMS[0].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string output = "\0031Players In The \xF["+fields[2].GetString()+"] \0031Channel:\017 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ output += fields[1].GetString() + ", "; ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), output, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(ChanOrPM(CD), "\0031No Players Are Currently In \xF["+_PARAMS[0]+"] \0031Channel!", true, CD->TYPE.c_str()); ++} ++ ++void IRCCmd::Info_Server(_CDATA *CD) ++{ ++ char clientsNum [50]; ++ sprintf(clientsNum, "%u", sWorld->GetActiveSessionCount()); ++ char maxClientsNum [50]; ++ sprintf(maxClientsNum, "%u", sWorld->GetMaxActiveSessionCount()); ++ char ircupdt [50]; ++ sprintf(ircupdt, "%u", sWorld->GetUpdateTime()); ++ std::string str = secsToTimeString(sWorld->GetUptime()); ++ std::string svnrev = GitRevision::GetFullVersion(); ++ ++ float rdm = (sConfigMgr->GetFloatDefault("Rate.Drop.Money", 1.0f)); ++ float rxk = (sConfigMgr->GetFloatDefault("Rate.XP.Kill", 1.0f)); ++ float rxq = (sConfigMgr->GetFloatDefault("Rate.XP.Quest", 1.0f)); ++ Send_IRCA(ChanOrPM(CD), "\00310Number Of Players Online: \xF"+(std::string)clientsNum+" | \00310Max Since Last Restart: \xF"+(std::string)maxClientsNum+" |\00310 UpTime: \xF"+str, true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), "\00310Server: \xF"+svnrev+" |\00310 Update Time: \xF"+(std::string)ircupdt, true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00310Server Rates - \xF[Monster XP: %u][Quest XP: %u][Money Drop Rate: %u]", int(rxk), int(rxq), int(rdm)), true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), "\00310MotD: \xF"+(std::string)sWorld->GetMotd(), true, CD->TYPE); ++ ++} ++ ++void IRCCmd::Item_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 3); ++ ++ normalizePlayerName(_PARAMS[0]); ++ Player *chr = GetPlayer(_PARAMS[0].c_str()); ++ if (_PARAMS[1] == "add") ++ { ++ std::string s_param = _PARAMS[2]; ++ ++ char *args = (char*)s_param.c_str(); ++ uint32 itemId = 0; ++ if (args[0]=='[') ++ { ++ char* citemName = strtok((char*)args, "]"); ++ if (citemName && citemName[0]) ++ { ++ std::string itemName = citemName+1; ++ WorldDatabase.EscapeString(itemName); ++ QueryResult result = WorldDatabase.PQuery("SELECT entry FROM item_template WHERE name = '%s'", itemName.c_str()); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "Item Not Found!", true, "ERROR"); ++ return; ++ } ++ itemId = result->Fetch()->GetUInt16(); ++ ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Syntax Error! ("+sIRC->_cmd_prefx+"item [Exact Item Name] )", true, "ERROR"); ++ return; ++ } ++ } ++ else ++ { ++ std::string itemName = s_param; ++ WorldDatabase.EscapeString(itemName); ++ QueryResult result = WorldDatabase.PQuery("SELECT entry FROM item_template WHERE name = '%s'", itemName.c_str()); ++ if (result) ++ { ++ itemId = result->Fetch()->GetUInt16(); ++ } ++ ++ ++ char* cId = strtok(args, " "); ++ if (!cId) ++ { ++ Send_IRCA(CD->USER, "Syntax Error! ("+sIRC->_cmd_prefx+"item )", true, "ERROR"); ++ return; ++ } ++ itemId = atol(cId); ++ } ++ char* ccount = strtok(NULL, " "); ++ int32 count = 1; ++ if (ccount) { count = atol(ccount); } ++ Player* plTarget = chr; ++ if (!plTarget) ++ { ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Online!", true, "ERROR"); ++ return; ++ } ++ //Subtract ++ if (count < 0) ++ { ++ plTarget->DestroyItemCount(itemId, -count, true, false); ++ char itemid2[255]; ++ sprintf(itemid2,"%d",itemId); ++ std::string itake = " \00313["+ _PARAMS[0] +"] :\0031Has Had Item \xF" +itemid2+ " \0031Taken From Them!"; ++ Send_IRCA(ChanOrPM(CD), itake, true, CD->TYPE); ++ return; ++ } ++ //Adding items ++ uint32 noSpaceForCount = 0; ++ ++ // check space and find places ++ ItemPosCountVec dest; ++ uint8 msg = plTarget->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, count, &noSpaceForCount); ++ if (msg == EQUIP_ERR_INVENTORY_FULL) // convert to possibel store amount ++ count -= noSpaceForCount; ++ else if (msg != EQUIP_ERR_OK) // other error, can't add ++ { ++ char s_countForStore[255]; ++ sprintf(s_countForStore,"%d",count); ++ std::string ierror = " \00313["+ _PARAMS[0] +"] : Could Not Create All Items! " +s_countForStore+ " Item(s) Were Not Created!"; ++ Send_IRCA(ChanOrPM(CD), ierror, true, CD->TYPE); ++ return; ++ } ++ Item* item = plTarget->StoreNewItem(dest, itemId, true, Item::GenerateItemRandomPropertyId(itemId)); ++ if (count > 0 && item) ++ { ++ plTarget->SendNewItem(item,count,true,false); ++ QueryResult result = WorldDatabase.PQuery("SELECT name FROM item_template WHERE entry = %d", itemId); ++ char* dbitemname = NULL; ++ if (result) ++ { ++ dbitemname = (char*)result->Fetch()->GetCString(); ++ } ++ std::string iinfo = " \00313[" + _PARAMS[0] + "] : Has Been Given Item "+dbitemname+". From: "+CD->USER.c_str()+"."; ++ Send_IRCA(ChanOrPM(CD), iinfo, true, CD->TYPE); ++ ++ } ++ if (noSpaceForCount > 0) ++ { ++ char s_countForStore[255]; ++ sprintf(s_countForStore,"%d",noSpaceForCount); ++ std::string ierror = " \00313["+ _PARAMS[0] +"] : Could Not Create All Items! " +s_countForStore+ " Item(s) Were Not Created!"; ++ Send_IRCA(ChanOrPM(CD), ierror, true, CD->TYPE); ++ return; ++ } ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Syntax Error! ("+sIRC->_cmd_prefx+"item )", true, "ERROR"); ++ return; ++ } ++} ++ ++void IRCCmd::Jail_Player(_CDATA *CD) ++{ ++ if (ValidParams(CD->PARAMS, 1)) ++ { ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (Player *plr = GetPlayer(_PARAMS[0])) ++ { ++ std::string sReason = ""; ++ if (_PARAMS[1] == "release") ++ { ++ float rposx, rposy, rposz, rposo = 0; ++ uint32 rmapid = 0; ++ CharacterDatabase.EscapeString(_PARAMS[0]); ++ QueryResult result = CharacterDatabase.PQuery("SELECT `mapId`, `posX`, `posY`, `posZ` FROM `character_homebind` WHERE `guid` = '" UI64FMTD "'", plr->GetGUID()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ rmapid = fields[0].GetUInt16(); ++ rposx = fields[1].GetFloat(); ++ rposy = fields[2].GetFloat(); ++ rposz = fields[3].GetFloat(); ++ ++ plr->SetMovement(MOVE_UNROOT); ++ plr->TeleportTo(rmapid, rposx, rposy, rposz, rposo); ++ plr->RemoveAurasDueToSpell(42201); ++ plr->RemoveAurasDueToSpell(23775); ++ plr->RemoveAurasDueToSpell(9454); ++ Send_Player(plr, MakeMsg("You Have Been Released By: %s.", CD->USER.c_str())); ++ sReason = " \00313["+_PARAMS[0]+"] : Has Been Released By: "+CD->USER+"."; ++ Send_IRCA(ChanOrPM(CD), sReason, true, CD->TYPE); ++ } ++ } ++ else ++ { ++ if (_PARAMS[1] == "") ++ _PARAMS[1] = "No Reason Given."; ++ plr->TeleportTo(13, 0, 0, 0, 0); ++ plr->SetMovement(MOVE_ROOT); ++ plr->CastSpell(plr, 42201, true); ++ plr->CastSpell(plr, 23775, true); ++ plr->CastSpell(plr, 9454, true); ++ Send_Player(plr, MakeMsg("You Have Been Jailed By: %s. Reason: %s.", CD->USER.c_str(), _PARAMS[1].c_str())); ++ sReason = " \00313["+_PARAMS[0]+"] : Has Been Jailed By: "+CD->USER+". Reason: "+_PARAMS[1]+"."; ++ Send_IRCA(ChanOrPM(CD), sReason, true, CD->TYPE); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Online!", true, "ERROR"); ++ } ++} ++ ++void IRCCmd::Kick_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (_PARAMS[1] == "") ++ _PARAMS[1] = "No Reason Given."; ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ plr->GetSession()->KickPlayer(); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Been Kicked By: "+CD->USER+". Reason: "+_PARAMS[1]+".", true, CD->TYPE); ++ if (sWorld->getBoolConfig(CONFIG_SHOW_KICK_IN_WORLD)) ++ sIRC->Send_WoW_System("Player|cffff0000 "+_PARAMS[0]+"|r kicked by|cffff0000 "+CD->USER+"|r. Reason:|cffff0000"+_PARAMS[1]+"|r."); ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Online!", true, "ERROR"); ++} ++ ++void IRCCmd::Kill_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ if (plr->IsAlive()) ++ { ++ plr->DealDamage(plr, plr->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); ++ plr->SaveToDB(); ++ if (_PARAMS[1] == "") ++ _PARAMS[1] = "No Reason Given."; ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00313[%s] : Has Been Killed By: %s.", _PARAMS[0].c_str(), CD->USER.c_str()) + + + " Reason: "+_PARAMS[1]+".", true, CD->TYPE); ++ Send_Player(plr, MakeMsg("You Have Been Killed By: %s. Reason: %s.", CD->USER.c_str(), _PARAMS[1].c_str())); ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Already Dead!", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Online!", true, "ERROR"); ++} ++ ++void IRCCmd::Player_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ uint32 plguid = atoi(_PARAMS[0].c_str()); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ if (sObjectMgr->GetPlayerGUIDByName(_PARAMS[0].c_str())) ++ plguid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[0].c_str()); ++ if (plguid > 0) ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT guid, account, name, race, class, online, level, xp, money, totalHonorPoints, totaltime FROM characters WHERE guid =%i", plguid); ++ uint32 latency = 0; ++ ObjectGuid plguidnew = ObjectGuid(HighGuid::Player, plguid); ++ Player *chr = ObjectAccessor::FindPlayer(plguidnew); ++ if (chr) ++ { ++ latency = chr->GetSession()->GetLatency(); ++ } ++ char templatency [100]; ++ sprintf(templatency, "%ums", latency); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string pguid = fields[0].GetString(); ++ std::string pacct = fields[1].GetString(); ++ std::string pname = fields[2].GetString(); ++ uint32 praceid = fields[3].GetUInt32(); ++ uint32 pclassid = fields[4].GetUInt32(); ++ std::string ponline = (fields[5].GetInt32() == 1 ? "\x3\x30\x33Online" : "\x3\x30\x34Offline\xF"); ++ std::string plevel = fields[6].GetString(); ++ std::string pxp = fields[7].GetString(); ++ unsigned int money = fields[8].GetInt32(); ++ std::string honor = fields[9].GetString(); ++ std::string totaltim = SecToDay(fields[10].GetString()); ++ ++ std::string sqlquery = "SELECT `gmlevel` FROM `account_access` WHERE `id` = '" + pacct + "';"; ++ QueryResult gmresult = LoginDatabase.Query(sqlquery.c_str()); ++ std::string pgmlvl = "0"; ++ if (gmresult) ++ { ++ Field *fields2 = gmresult->Fetch(); ++ pgmlvl = fields2[0].GetString(); ++ } ++ ++ if (uint32(atoi(plevel.c_str())) < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) ++ plevel += " (" + pxp + ")"; ++ unsigned int gold = money / 10000; ++ unsigned int silv = (money % 10000) / 100; ++ unsigned int cop = (money % 10000) % 100; ++ char tempgold [100]; ++ sprintf(tempgold, "\x2\x3\x30\x37%ug \x3\x31\x34%us \x3\x30\x35%uc\xF", gold, silv, cop); ++ if (ponline == "\x3\x30\x33Online") ++ { ++ Player * plr = ObjectAccessor::FindPlayerByName(pname.c_str()); ++ if (plr) ++ { ++ AreaTableEntry const* area = GetAreaEntryByAreaID(plr->GetAreaId()); ++ ponline += " in " + (std::string) area->area_name[sWorld->GetDefaultDbcLocale()]; ++ if (area->zone != 0) ++ { ++ AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone); ++ ponline += " (" + (std::string)zone->area_name[sWorld->GetDefaultDbcLocale()] + ")"; ++ } ++ } ++ } ++ std::string pinfo = "\00310About Player: \xF"+pname+" |\00310 GM Level: \xF"+pgmlvl+" |\00310 AcctID: \xF"+pacct+" |\00310 CharID: \xF"+pguid+" |\00310 Played Time: \xF"+totaltim.c_str()+" |\00310 Latency: \xF"+templatency; ++ std::string pinfo2 = "\00310Race: \xF"+(std::string)GetRaceName(praceid, 0)+" |\00310 Class: \xF" + (std::string)GetClassName(pclassid, 0)+" |\00310 Level: \xF"+plevel+" |\00310 Money: \xF"+tempgold+"|\00310 Status: \xF"+ponline+" |\00310 Honor: \xF"+honor; ++ // pinfo3 = " :" + " \x2Honor Kills:\x2\x3\x31\x30 " + hk; ++ Send_IRCA(ChanOrPM(CD),pinfo , true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD),pinfo2 , true, CD->TYPE); ++ // Send_IRCA(ChanOrPM(CD),pinfo3 , true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Character ID. (GUID)" ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT guid, account, name FROM characters WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[0].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string items = "\x2 Character Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string guid = fields[0].GetString(); ++ std::string account = fields[1].GetString(); ++ std::string name = fields[2].GetString(); ++ MakeUpper(name); ++ items.append(name+"(Account:"+account+" - GUID:"+guid+")\0031 | \xF"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), items, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Character. I Cant Find Any Characters With Those Search Terms." ,true, "ERROR"); ++ } ++ ++} ++ ++void IRCCmd::Lookup_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (_PARAMS[0] == "acct") ++ { ++ uint32 acctid = atoi(_PARAMS[1].c_str()); ++ if (AccountMgr::GetId(_PARAMS[1])) ++ acctid = AccountMgr::GetId(_PARAMS[1]); ++ if (acctid > 0) ++ { ++ std::string DateTime = "%a, %b %d, %Y - %h:%i%p"; ++ QueryResult result = LoginDatabase.PQuery("SELECT `account`.`id`, username, last_ip, (SELECT banreason FROM account_banned WHERE `account`.`id` = id LIMIT 1) as banned, (SELECT banreason FROM ip_banned WHERE ip = last_ip) as bannedip,(SELECT active FROM account_banned WHERE `account`.`id` = id) as banactive, (SELECT( unbandate - unix_timestamp( now() ) ) FROM account_banned WHERE `account`.`id` = id) as remainingtime, DATE_FORMAT(last_login, '%s') FROM `account` WHERE `account`.`id` =%d" ,DateTime.c_str(), acctid); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ ++ uint32 id = fields[0].GetUInt32(); ++ std::string usrname = fields[1].GetString(); ++ std::string lastip = fields[2].GetString(); ++ std::string banreason = fields[3].GetString(); ++ std::string banreasonip = fields[4].GetString(); ++ uint32 banactive = (fields[5].GetInt32() == 1 ? 1 : 0); ++ std::string TimeLeft = SecToDay(fields[6].GetString()); ++ std::string lastlogin = fields[7].GetString(); ++ ++ QueryResult chars = CharacterDatabase.PQuery("SELECT guid, name, (SELECT SUM(totaltime) FROM characters WHERE account = %d) AS tottime FROM characters WHERE account = %u", id, id); ++ std::string characters = "None"; ++ std::string totaccttime = "Never Logged In"; ++ if (chars) ++ { ++ characters = ""; ++ Field *fields = chars->Fetch(); ++ totaccttime = SecToDay(fields[2].GetString()); ++ for (uint64 i=0; i < chars->GetRowCount(); i++) ++ { ++ std::string guid = fields[0].GetString(); ++ std::string charname = fields[1].GetString(); ++ characters.append(charname+"("+guid+"), "); ++ chars->NextRow(); ++ } ++ ++ } ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00310Username: \xF %s | \00310AccountID: \xF %d | \00310Last IP: \xF %s | \00310Last Login: \xF %s", usrname.c_str(), id, lastip.c_str(), lastlogin.c_str()), true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\00310Total play time: \xF %s | \00310Characters: \xF %s ", totaccttime.c_str(), characters.c_str()), true, CD->TYPE); ++ if (banreason.length() > 1) ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\0035Account banned : \xF %s | \0035Ban Active: \xF %u | \0035Ban Time: \xF %s", banreason.c_str(), banactive, TimeLeft.c_str()), true, CD->TYPE); ++ if (banreasonip.length() > 1) ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\0034This User Has An IP Ban. Ban Reason: %s", banreasonip.c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Account ID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = LoginDatabase.PQuery("SELECT id, username FROM `account` WHERE username LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string accts = "\002Account Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string acctid = fields[0].GetString(); ++ std::string acctname = fields[1].GetString(); ++ accts.append(acctname+"("+acctid+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), accts, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Username. I Cant Find Any Users With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "char") ++ { ++ uint32 plguid = atoi(_PARAMS[1].c_str()); ++ if (sObjectMgr->GetPlayerGUIDByName(_PARAMS[1].c_str())) ++ plguid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[1].c_str()); ++ if (plguid > 0) ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT guid, account, name, race, class, online, SUBSTRING_INDEX(SUBSTRING_INDEX(`level`, ' ' , 35), ' ' , -1) AS level, SUBSTRING_INDEX(SUBSTRING_INDEX(`xp`, ' ' , 927), ' ' , -1) AS xp, SUBSTRING_INDEX(SUBSTRING_INDEX(money, ' ' , 1462), ' ' , -1) AS gold, SUBSTRING_INDEX(SUBSTRING_INDEX(`totalHonorPoints`, ' ' , 1454), ' ' , -1) AS Honor, totaltime FROM characters WHERE guid =%i", plguid); ++ uint32 latency = 0; ++ ObjectGuid plguidnew = ObjectGuid(HighGuid::Player, plguid); ++ Player *chr = ObjectAccessor::FindPlayer(plguidnew); ++ if (chr) ++ { ++ latency = chr->GetSession()->GetLatency(); ++ } ++ char templatency [100]; ++ sprintf(templatency, "%ums", latency); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string pguid = fields[0].GetString(); ++ std::string pacct = fields[1].GetString(); ++ std::string pname = fields[2].GetString(); ++ uint32 praceid = fields[3].GetUInt32(); ++ uint32 pclassid = fields[4].GetUInt32(); ++ std::string ponline = (fields[5].GetInt32() == 1 ? "\x3\x30\x33Online" : "\x3\x30\x34Offline\xF"); ++ std::string plevel = fields[6].GetString(); ++ std::string pxp = fields[7].GetString(); ++ unsigned int money = fields[8].GetInt32(); ++ std::string honor = fields[9].GetString(); ++ std::string totaltim = SecToDay(fields[10].GetString()); ++ ++ std::string sqlquery = "SELECT `gmlevel` FROM `account_access` WHERE `id` = '" + pacct + "';"; ++ QueryResult gmresult = LoginDatabase.Query(sqlquery.c_str()); ++ std::string pgmlvl = "0"; ++ if (gmresult) ++ { ++ Field *fields = result->Fetch(); ++ pgmlvl = fields[0].GetString(); ++ } ++ ++ ChrRacesEntry const* prace = sChrRacesStore.LookupEntry(praceid); ++ ChrClassesEntry const* pclass = sChrClassesStore.LookupEntry(pclassid); ++ ++ if (uint32(atoi(plevel.c_str())) < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) ++ plevel += " (" + pxp + ")"; ++ unsigned int gold = money / 10000; ++ unsigned int silv = (money % 10000) / 100; ++ unsigned int cop = (money % 10000) % 100; ++ char tempgold [100]; ++ sprintf(tempgold, "\x2\x3\x30\x37%ug \x3\x31\x34%us \x3\x30\x35%uc\xF", gold, silv, cop); ++ if (ponline == "\x3\x30\x33Online") ++ { ++ Player * plr = ObjectAccessor::FindPlayerByName(pname.c_str()); ++ if (plr) ++ { ++ AreaTableEntry const* area = GetAreaEntryByAreaID(plr->GetAreaId()); ++ ponline += " in " + (std::string) area->area_name[sWorld->GetDefaultDbcLocale()]; ++ if (area->zone != 0) ++ { ++ AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone); ++ ponline += " (" + (std::string)zone->area_name[sWorld->GetDefaultDbcLocale()] + ")"; ++ } ++ } ++ } ++ std::string pinfo = "\x2 About Player:\x3\x31\x30 " +pname+ "\xF |\x2 GM Level:\x3\x31\x30 " +pgmlvl+ "\xF |\x2 AcctID:\x3\x31\x30 " +pacct+ "\xF |\x2 CharID:\x3\x31\x30 " +pguid+ " \xF |\x2 Played Time:\x2\x3\x31\x30 " +totaltim.c_str()+" \xF |\x2 Latency:\x2\x3\x31\x30 "+templatency; ++ std::string pinfo2 = "\x2 Race:\x2\x3\x31\x30 " + (std::string)prace->name[sWorld->GetDefaultDbcLocale()] + "\xF |\x2 Class:\x2\x3\x31\x30 " + (std::string)pclass->name[sWorld->GetDefaultDbcLocale()] + "\xF |\x2 Level:\x2\x3\x31\x30 " + plevel + "\xF |\x2 Money:\x2 " + tempgold + "\xF |\x2 Status:\x2 " + ponline; ++ // pinfo3 = " :" + " \x2Honor Kills:\x2\x3\x31\x30 " + hk; ++ Send_IRCA(ChanOrPM(CD),pinfo , true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD),pinfo2 , true, CD->TYPE); ++ // Send_IRCA(ChanOrPM(CD),pinfo3 , true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Character ID. (GUID)" ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT guid, account, name FROM characters WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string items = "\x2 Character Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string guid = fields[0].GetString(); ++ std::string account = fields[1].GetString(); ++ std::string name = fields[2].GetString(); ++ MakeUpper(name); ++ items.append(name+"(Account:"+account+" - GUID:"+guid+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), items, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Character. I Cant Find Any Characters With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "creature") ++ { ++ std::string creature = _PARAMS[1]; ++ if (atoi(creature.c_str()) > 0) ++ { ++ WorldDatabase.EscapeString(_PARAMS[1]); ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, name, minlevel,maxlevel, faction_A, (SELECT count(guid) FROM creature WHERE id = '%s') as spawns FROM creature_template WHERE entry = '%s';", _PARAMS[1].c_str(), _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ ++ uint32 entry = fields[0].GetUInt32(); ++ std::string name = fields[1].GetString(); ++ uint32 minlevel = fields[2].GetUInt32(); ++ uint32 maxlevel = fields[3].GetUInt32(); ++ uint32 faction = fields[4].GetUInt32(); ++ uint32 spawns = fields[5].GetUInt32(); ++ ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Name:\x3\x31\x30 %s \xF|\x2 CreatureID:\x3\x31\x30 %d", name.c_str(), entry), true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2minlevel:\x3\x31\x30 %d \xF|\x2 maxlevel:\x3\x31\x30 %d \xF|\x2 Faction:\x3\x31\x30 %d \xF|\x2 Spawns:\x3\x31\x30 %d", minlevel, maxlevel, faction, spawns), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Creature ID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, name FROM creature_template WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string items = "\002Creature Search Results:\x3\x31\x30 "; ++ //Send_IRCA(ChanOrPM(CD), "", true, CD->TYPE); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string CreatureID = fields[0].GetString(); ++ std::string Name = fields[1].GetString(); ++ items.append(Name+"("+CreatureID+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), items, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Creature. I Cant Find Any Creatures With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "faction") ++ { ++ std::string faction = _PARAMS[1]; ++ if (atoi(faction.c_str()) > 0) ++ { ++ FactionEntry const *factionEntry = sFactionStore.LookupEntry(atoi(faction.c_str())); ++ if (factionEntry) ++ { ++ std::string name = factionEntry->name[sWorld->GetDefaultDbcLocale()]; ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2 Faction:\x3\x31\x30 %s \xF|\x2 FactionID:\x3\x31\x30 %s",name.c_str(), faction.c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown FactionID." ,true, "ERROR"); ++ ++ } ++ else ++ { ++ uint32 counter = 0; ++ std::string factions = "\002Faction Search Results:\x3\x31\x30 "; ++ for (uint32 id = 0; id < sFactionStore.GetNumRows(); id++) ++ { ++ FactionEntry const *factionEntry = sFactionStore.LookupEntry(id); ++ if (factionEntry) ++ { ++ MakeLower(_PARAMS[1]); ++ std::string name = factionEntry->name[sWorld->GetDefaultDbcLocale()]; ++ MakeLower(name); ++ if (name.find(_PARAMS[1]) != std::string::npos && counter < 10) ++ { ++ char factionid[100]; ++ sprintf(factionid, "%d", id); ++ factions.append(name+"("+factionid+")\xF | \x3\x31\x30\x2"); ++ ++counter; ++ } ++ } ++ } ++ if (counter == 0) ++ factions.append("No Factions Found."); ++ Send_IRCA(ChanOrPM(CD), factions, true, CD->TYPE); ++ } ++ } ++ if (_PARAMS[0] == "go") ++ { ++ std::string gobject = _PARAMS[1]; ++ if (atoi(gobject.c_str()) > 0) ++ { ++ WorldDatabase.EscapeString(_PARAMS[1]); ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, type, displayId, name, faction, (SELECT count(*) FROM gameobject WHERE id = '%s') as spawns FROM gameobject_template WHERE entry = '%s';", _PARAMS[1].c_str(), _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ ++ uint32 entry = fields[0].GetUInt32(); ++ uint32 type = fields[1].GetUInt32(); ++ uint32 modelid = fields[2].GetUInt32(); ++ std::string name = fields[3].GetString(); ++ uint32 faction = fields[4].GetUInt32(); ++ uint32 spawns = fields[5].GetUInt32(); ++ ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2GO Name:\x3\x31\x30 %s \xF|\x2 GameobjectID:\x3\x31\x30 %d \xF|\x2 DisplayID:\x3\x31\x30 %d \xF|\x2 Spawns:\x3\x31\x30 %d", name.c_str(), entry, modelid, spawns), true, CD->TYPE); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Type:\x3\x31\x30 %d \xF|\x2 Faction:\x3\x31\x30 %d", type, faction), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown GameObject ID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, name FROM gameobject_template WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string gos = "\002Gameobject Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string GOID = fields[0].GetString(); ++ std::string GoName = fields[1].GetString(); ++ gos.append(GoName+"("+GOID+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), gos, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Game Object. I Cant Find Any Game Object's With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "item") ++ { ++ std::string item = _PARAMS[1]; ++ if (atoi(item.c_str()) > 0) ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, name, displayid, (SELECT count(*) FROM creature_loot_template WHERE item = '%s') as loot FROM `item_template` WHERE entry = %s", _PARAMS[1].c_str(), _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ QueryResult result2 = CharacterDatabase.PQuery("SELECT count(*) FROM `character_inventory` WHERE item_template = %s", _PARAMS[1].c_str()); ++ Field *fields2 = result2->Fetch(); ++ uint32 charcnt = fields2[0].GetUInt32(); ++ ++ ++ uint32 ItemID = fields[0].GetUInt32(); ++ std::string ItmName = fields[1].GetString(); ++ uint32 DisplayID = fields[2].GetUInt32(); ++ uint32 loots = 0; ++ loots = fields[3].GetUInt32(); ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Item:\x3\x31\x30 %s \xF|\x2 ItemID:\x3\x31\x30 %d \xF|\x2 DisplayID:\x3\x31\x30 %d \xF|\x2 Owned By:\x3\x31\x30 %d players \xF|\x2 Dropped By:\x3\x31\x30 %d creatures", ItmName.c_str(), ItemID, DisplayID, charcnt, loots), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown ItemID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT entry, name FROM `item_template` WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string items = "\002Item Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string ItemID = fields[0].GetString(); ++ std::string ItemName = fields[1].GetString(); ++ items.append(ItemName+"("+ItemID+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), items, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Item. I Cant Find Any Items With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "quest") ++ { ++ std::string quest = _PARAMS[1]; ++ if (atoi(quest.c_str()) > 0) ++ { ++ WorldDatabase.EscapeString(_PARAMS[1]); ++ QueryResult result = WorldDatabase.PQuery("SELECT id, Title FROM quest_template WHERE id = '%s';", _PARAMS[1].c_str(), _PARAMS[1].c_str()); ++ if (result) ++ { ++ QueryResult result2 = CharacterDatabase.PQuery("SELECT count(*) FROM character_queststatus WHERE quest = '%s' AND status = '1';", _PARAMS[1].c_str()); ++ Field *fields2 = result2->Fetch(); ++ uint32 status = fields2[0].GetUInt32(); ++ ++ ++ Field *fields = result->Fetch(); ++ uint32 entry = fields[0].GetUInt32(); ++ std::string name = fields[1].GetString(); ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Quest Name:\x3\x31\x30 %s \xF|\x2 QuestID:\x3\x31\x30 %d \xF|\x2 Completed:\x3\x31\x30 %d times", name.c_str(), entry, status), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Quest ID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT id, Title FROM quest_template WHERE Title LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string quests = "\002Quest Search Results:\x3\x31\x30 "; ++ //Send_IRCA(ChanOrPM(CD), "", true, CD->TYPE); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string QuestID = fields[0].GetString(); ++ std::string QuestName = fields[1].GetString(); ++ quests.append(QuestName+"("+QuestID+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), quests, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Quest. I Cant Find Any Quest's With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++ if (_PARAMS[0] == "skill") ++ { ++ std::string skill = _PARAMS[1]; ++ if (atoi(skill.c_str()) > 0) ++ { ++ SkillLineEntry const *skillInfo = sSkillLineStore.LookupEntry(atoi(skill.c_str())); ++ if (skillInfo) ++ { ++ std::string name = skillInfo->name[sWorld->GetDefaultDbcLocale()]; ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Skill:\x3\x31\x30 %s \xF|\x2 SkillID:\x3\x31\x30 %s",name.c_str(), skill.c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown SkillID." ,true, "ERROR"); ++ ++ } ++ else ++ { ++ uint32 counter = 0; ++ std::string skills = "\002Skill Search Results:\x3\x31\x30 "; ++ for (uint32 id = 0; id < sSkillLineStore.GetNumRows(); id++) ++ { ++ SkillLineEntry const *skillInfo = sSkillLineStore.LookupEntry(id); ++ if (skillInfo) ++ { ++ MakeLower(_PARAMS[1]); ++ std::string name = skillInfo->name[sWorld->GetDefaultDbcLocale()]; ++ MakeLower(name); ++ if (name.find(_PARAMS[1]) != std::string::npos && counter < 10) ++ { ++ char skillid[100]; ++ sprintf(skillid, "%d", id); ++ skills.append(name+"("+skillid+")\xF | \x3\x31\x30\x2"); ++ ++counter; ++ } ++ } ++ } ++ if (counter == 0) ++ skills.append("No Skills Found."); ++ Send_IRCA(ChanOrPM(CD), skills, true, CD->TYPE); ++ } ++ } ++ if (_PARAMS[0] == "spell") ++ { ++ std::string spell = _PARAMS[1]; ++ if (atoi(spell.c_str()) > 0) ++ { ++ SpellEntry const *spellInfo = sSpellStore.LookupEntry(atoi(spell.c_str())); ++ if (spellInfo) ++ { ++ std::string name = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Spell:\x3\x31\x30 %s \xF|\x2 SpellID:\x3\x31\x30 %s",name.c_str(), spell.c_str()), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown SpellID." ,true, "ERROR"); ++ ++ } ++ else ++ { ++ uint32 counter = 0; ++ std::string spells = "\002Spell Search Results:\x3\x31\x30 "; ++ for (uint32 id = 0; id < sSpellStore.GetNumRows(); id++) ++ { ++ SpellEntry const *spellInfo = sSpellStore.LookupEntry(id); ++ if (spellInfo) ++ { ++ MakeLower(_PARAMS[1]); ++ std::string name = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; ++ MakeLower(name); ++ if (name.find(_PARAMS[1]) != std::string::npos && counter < 10) ++ { ++ char itemid[100]; ++ sprintf(itemid, "%d", id); ++ spells.append(name+"("+itemid+")\xF | \x3\x31\x30\x2"); ++ ++counter; ++ } ++ } ++ } ++ if (counter == 0) ++ spells.append("No Spells Found."); ++ Send_IRCA(ChanOrPM(CD), spells, true, CD->TYPE); ++ } ++ } ++ if (_PARAMS[0] == "tele") ++ { ++ std::string tele = _PARAMS[1]; ++ if (atoi(tele.c_str()) > 0) ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT * FROM `game_tele` WHERE id = %s", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ ++ uint32 teleid = fields[0].GetUInt32(); ++ uint32 pos_x = fields[1].GetUInt32(); ++ uint32 pos_y = fields[2].GetUInt32(); ++ uint32 pos_z = fields[3].GetUInt32(); ++ uint32 oriet = fields[4].GetUInt32(); ++ uint32 map = fields[5].GetUInt32(); ++ std::string telname = fields[6].GetString(); ++ ++ ++ Send_IRCA(ChanOrPM(CD), MakeMsg("\x2Tele Name:\x3\x31\x30 %s \xF|\x2 TeleID:\x3\x31\x30 %d \xF|\x2 Coordinates:\x3\x31\x30 [X: %d, Y: %d, Z: %d, MAP: %d, Orientation: %d]", telname.c_str(), teleid, pos_x, pos_y, pos_z, map, oriet), true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Teleport Location ID." ,true, "ERROR"); ++ } ++ else ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT id, name FROM `game_tele` WHERE name LIKE '%%%s%%' LIMIT 10", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string teles = "\002Tele Location Search Results:\x3\x31\x30 "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string TeleID = fields[0].GetString(); ++ std::string TeleName = fields[1].GetString(); ++ teles.append(TeleName+"("+TeleID+")\xF | \x3\x31\x30\x2"); ++ result->NextRow(); ++ } ++ Send_IRCA(ChanOrPM(CD), teles, true, CD->TYPE); ++ ++ } ++ else ++ Send_IRCA(CD->USER, "Unknown Item. I Cant Find Any Items With Those Search Terms." ,true, "ERROR"); ++ } ++ } ++} ++ ++void IRCCmd::Level_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ std::string player = _PARAMS[0]; ++ normalizePlayerName(player); ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(player.c_str()); ++ std::string s_newlevel = _PARAMS[1]; ++ uint8 i_newlvl = atoi(s_newlevel.c_str()); ++ if (!guid) ++ { ++ Send_IRCA(CD->USER, "Player Not Found!", true, "ERROR"); ++ return; ++ } else if (i_newlvl < 1 || i_newlvl > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) ++ { ++ Send_IRCA(CD->USER, MakeMsg("Level Must Be Between 1 And %i!",sConfigMgr->GetIntDefault("MaxPlayerLevel", 80)), true, "ERROR"); ++ return; ++ } else ++ { ++ Player *chr = ObjectAccessor::FindPlayer(guid); ++ ObjectGuid level = ObjectGuid::Empty; ++ int32 i_oldlvl = chr ? chr->getLevel() : Player::GetLevelFromDB(level); ++ ++ if (chr) ++ { ++ chr->GiveLevel(i_newlvl); ++ chr->InitTalentForLevel(); ++ chr->SetUInt32Value(PLAYER_XP,0); ++ WorldPacket data; ++ std::stringstream ss; ++ ChatHandler CH(chr->GetSession()); ++ if (i_oldlvl == i_newlvl) ++ //CH.FillSystemMessageData(&data, "Your level progress has been reset."); ++ CH.BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, NULL, NULL, "Your level progress has been reset."); ++ else ++ if (i_oldlvl < i_newlvl) ++ { ++ ss << "You have been leveled up" << i_newlvl-i_oldlvl; ++ //CH.FillSystemMessageData(&data, ss.str().c_str()); ++ CH.BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, NULL, NULL, ss.str().c_str()); ++ } ++ else if (i_oldlvl > i_newlvl) ++ { ++ ss << "You have been leveled down" << i_newlvl-i_oldlvl; ++ //CH.FillSystemMessageData(&data, ss.str().c_str()); ++ CH.BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, NULL, NULL, ss.str().c_str()); ++ chr->GetSession()->SendPacket(&data); ++ } ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "" + _PARAMS[0] + " Is Not Online! Setting new level in DB.", true, "ERROR"); ++ CharacterDatabase.PExecute("UPDATE characters SET level = '%u', xp = 0 WHERE guid = '%u'", i_newlvl, guid); ++ } ++ } ++ Send_IRCA(ChanOrPM(CD), "\00313[" + _PARAMS[0]+ "] : Has Been Leveled To " + _PARAMS[1] + ". By: "+CD->USER+".", true, CD->TYPE); ++} ++ ++void IRCCmd::Money_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ std::string player = _PARAMS[0]; ++ normalizePlayerName(player); ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(player.c_str()); ++ ++ std::string s_money = _PARAMS[1]; ++ int32 money = atoi(s_money.c_str()); ++ unsigned int gold = money / 10000; ++ unsigned int silv = (money % 10000) / 100; ++ unsigned int cop = (money % 10000) % 100; ++ char tempgold [100]; ++ sprintf(tempgold, "\x2\x3\x30\x37%ug \x3\x31\x34%us \x3\x30\x35%uc\xF", gold, silv, cop); ++ if (!guid) ++ { ++ Send_IRCA(CD->USER, "Player Not Found!", true, "ERROR"); ++ return; ++ } ++ else ++ { ++ Player *chr = ObjectAccessor::FindPlayer(guid); ++ uint32 moneyuser = 0; ++ if (chr) ++ moneyuser = chr->GetMoney(); ++ else { ++ CharacterDatabase.EscapeString(player); ++ std::string sqlquery = "SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(money, ' ' , 1462), ' ' , -1) AS `gold` FROM `characters` WHERE `name` = '"+player+"';"; ++ QueryResult result = CharacterDatabase.Query(sqlquery.c_str()); ++ Field *fields = result->Fetch(); ++ moneyuser = fields[0].GetInt32(); ++ ++ } ++ int32 addmoney = money; ++ int32 newmoney = moneyuser + addmoney; ++ char s_newmoney[255]; ++ sprintf(s_newmoney,"%d",newmoney); ++ if (addmoney < 0) ++ { ++ TC_LOG_ERROR("misc", "USER1: %i, ADD: %i, DIF: %i\\n", moneyuser, addmoney, newmoney); ++ if (newmoney <= 0) ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+player+"] : Has Had All Money Taken By: "+CD->USER.c_str()+".", true, CD->TYPE); ++ if (chr) ++ { ++ chr->SetMoney(0); ++ Send_Player(chr, MakeMsg("You Have Been Liquidated By: %s. Total Money Is Now 0.", CD->USER.c_str())); ++ } ++ else ++ CharacterDatabase.PExecute("UPDATE `characters` SET money=concat(substring_index(name,' ',1462-1),' ','%u',' ', right(name,length(name)-length(substring_index(name,' ',1462))-1)) where guid='%u'",newmoney, guid); ++ } ++ else ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+player+"] : Has Had ("+s_money+"\00313) Taken From Them By: "+CD->USER.c_str()+".", true, CD->TYPE); ++ if (chr) ++ { ++ chr->SetMoney(newmoney); ++ Send_Player(chr, MakeMsg("You Have Had %s Copper Taken From You By: %s.", _PARAMS[1].c_str(), CD->USER.c_str())); ++ } ++ else ++ CharacterDatabase.PExecute("UPDATE `characters` SET money=concat(substring_index(name,' ',1462-1),' ','%u',' ', right(name,length(name)-length(substring_index(name,' ',1462))-1)) where guid='%u'",newmoney, guid); ++ } ++ } ++ else ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+player+"] : Has Been Given ("+tempgold+"\00313) From: "+CD->USER.c_str()+".", true, CD->TYPE); ++ if (chr) ++ { ++ chr->ModifyMoney(addmoney); ++ Send_Player(chr, MakeMsg("You Have Been Given %s Copper. From: %s.", _PARAMS[1].c_str(), CD->USER.c_str())); ++ } ++ else ++ CharacterDatabase.PExecute("UPDATE `characters` SET money=concat(substring_index(name,' ',1462-1),' ','%u',' ', right(name,length(name)-length(substring_index(name,' ',1462))-1)) where guid='%u'",newmoney, guid); ++ } ++ } ++} ++ ++void IRCCmd::Mute_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 3); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ normalizePlayerName(_PARAMS[0]); ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[0]); ++ if (guid) ++ { ++ if (_PARAMS[1] == "release") ++ { ++ Player* plr = ObjectAccessor::FindPlayer(guid); ++ uint32 account_id = 0; ++ account_id = sObjectMgr->GetPlayerAccountIdByGUID(guid); ++ LoginDatabase.PExecute("UPDATE `account` SET `mutetime` = '0', `mutereason` = '', `muteby` = '' WHERE `id` = '%d'",account_id); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Been UnMuted By: "+CD->USER+"." , true, CD->TYPE); ++ if (plr) ++ { ++ plr->GetSession()->m_muteTime = 0; ++ Send_Player(plr, MakeMsg("You Have Been UnMuted By: %s.", CD->USER.c_str())); ++ } ++ } ++ else ++ { ++ if (_PARAMS[2] == "") ++ _PARAMS[2] = "No Reason Given"; ++ Player* plr = ObjectAccessor::FindPlayer(guid); ++ time_t mutetime = time(NULL) + atoi(_PARAMS[1].c_str())*60; ++ std::string By = "IRC:"; ++ By += CD->USER.c_str(); ++ uint32 account_id = 0; ++ account_id = sObjectMgr->GetPlayerAccountIdByGUID(guid); ++ if (plr) ++ plr->GetSession()->m_muteTime = mutetime; ++ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME); ++ stmt->setInt64(0, mutetime); ++ stmt->setString(1, _PARAMS[2].c_str()); ++ stmt->setString(2, By.c_str()); ++ stmt->setUInt32(3, account_id); ++ LoginDatabase.Execute(stmt); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Been Muted By: "+CD->USER+". For: "+_PARAMS[1]+" Minutes. Reason: "+_PARAMS[2] , true, CD->TYPE); ++ if (plr) ++ Send_Player(plr, MakeMsg("You Have Been Muted By: %s. For: %s Minutes. Reason: %s", CD->USER.c_str(), _PARAMS[1].c_str(), _PARAMS[2].c_str())); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Player Does Not Exist!", true, "ERROR"); ++} ++ ++void IRCCmd::Online_Players(_CDATA *CD) ++{ ++ sIRC->Script_Lock[MCS_Players_Online] = true; ++ boost::thread script([CD](){ ++ mcs_OnlinePlayers mcs(CD); ++ mcs.run(); ++ }); ++} ++ ++void IRCCmd::PM_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ if (plr->isAcceptWhispers()) ++ { ++ std::string sMsg = MakeMsg("|cffFE87FD[%s] Whispers: %s|r", CD->USER.c_str(), _PARAMS[1].c_str()); ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ data << (uint8)CHAT_MSG_SYSTEM; ++ data << (uint32)LANG_UNIVERSAL; ++ data << (uint64)plr->GetGUID(); ++ data << (uint32)0; ++ data << (uint64)plr->GetGUID(); ++ data << (uint32)(sMsg.length()+1); ++ data << sMsg; ++ data << (uint8)0; ++ plr->GetSession()->SendPacket(&data); ++ plr->PlayDirectSound(3081, plr->ToPlayer()); ++ Send_IRCA(ChanOrPM(CD), "\00313To ["+_PARAMS[0]+"] : "+_PARAMS[1]+".", true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "Is Not Accepting Private Messages!", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "Player not online!", true, "ERROR"); ++} ++ ++void IRCCmd::Restart_Trinity(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 1); ++ if (_PARAMS[0] == "cancel") ++ { ++ sWorld->ShutdownCancel(); ++ Send_IRCA(ChanOrPM(CD), "\0034Server Restart Has Been Cancelled.", true, CD->TYPE); ++ } ++ ++ int32 i_time = atoi(_PARAMS[0].c_str()); ++ if (i_time < 0) ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Please Enter A Number! And No Negative Numbers! "+_PARAMS[0]+" Seconds!?", true, CD->TYPE); ++ return; ++ } ++ if (i_time >= 1) ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Has Requested Server To Restart In "+_PARAMS[0]+" Seconds!", true, CD->TYPE); ++ sWorld->ShutdownServ(i_time,SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE); ++ Delay(i_time*1000); ++ Send_IRCA(ChanOrPM(CD), "\0034Server Will Now Restart.. Be Back In A Flash!", true, CD->TYPE); ++ } ++} ++ ++void IRCCmd::Revive_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ if (plr->isDead()) ++ { ++ plr->ResurrectPlayer(0.5f); ++ plr->SpawnCorpseBones(); ++ plr->SaveToDB(); ++ sIRC->Send_IRC_Channel(ChanOrPM(CD), " \00313["+_PARAMS[0]+"] : Has Been Revived By: " + CD->USER, true, CD->TYPE); ++ Send_Player(plr, MakeMsg("You Have Been Revived By: %s.", CD->USER.c_str())); ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Dead!", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, ""+_PARAMS[0]+" Is Not Online!", true, "ERROR"); ++} ++ ++void IRCCmd::Saveall_Player(_CDATA *CD) ++{ ++ ObjectAccessor::SaveAllPlayers(); ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Has Saved All Players!", true, CD->TYPE); ++} ++ ++void IRCCmd::Server(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ if (_PARAMS[0] == "setmotd") ++ { ++ sWorld->SetMotd(_PARAMS[1]); ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Has Set New Message Of The Day To: "+_PARAMS[1], true, CD->TYPE); ++ } ++ if (_PARAMS[0] == "flusharenapoints") ++ { ++ sArenaTeamMgr->DistributeArenaPoints(); ++ } ++} ++ ++void IRCCmd::Shutdown_Trinity(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 1); ++ if (_PARAMS[0] == "cancel") ++ { ++ sWorld->ShutdownCancel(); ++ Send_IRCA(ChanOrPM(CD), "\0034Server Shutdown Has Been Cancelled.", true, CD->TYPE); ++ } ++ ++ int32 i_time = atoi(_PARAMS[0].c_str()); ++ if (i_time < 0) ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Please Enter A Number! And No Negative Numbers! "+_PARAMS[0]+" Seconds!?", true, CD->TYPE); ++ return; ++ } ++ if (i_time >= 1) ++ { ++ Send_IRCA(ChanOrPM(CD), "\00313["+CD->USER+"] : Has Requested Server To Be Shut Down In "+_PARAMS[0]+" Seconds!", true, CD->TYPE); ++ sWorld->ShutdownServ(i_time, SHUTDOWN_MASK_IDLE , SHUTDOWN_EXIT_CODE); ++ Delay(i_time*1000); ++ Send_IRCA(ChanOrPM(CD), "\0034Server Will Now Shut Down.. Good Bye!", true, CD->TYPE); ++ } ++} ++ ++void IRCCmd::Spell_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 3); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ uint32 spell = atoi(_PARAMS[2].c_str()); ++ SpellEntry const *spellInfo = sSpellStore.LookupEntry(spell); ++ if (Player* plr = GetPlayer(_PARAMS[0])) ++ { ++ if (spellInfo) ++ { ++ std::string name = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; ++ if (_PARAMS[1] == "cast") ++ { ++ plr->CastSpell(plr, spell, true); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Had Spell "+name+" Casted On Them.", true, CD->TYPE); ++ } ++ if (_PARAMS[1] == "learn") ++ { ++ plr->LearnSpell(spell, true); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Learned Spell "+name+".", true, CD->TYPE); ++ } ++ if (_PARAMS[1] == "unlearn") ++ { ++ plr->RemoveSpell(spell); ++ Send_IRCA(ChanOrPM(CD), "\00313["+_PARAMS[0]+"] : Has Unlearned Spell "+name+".", true, CD->TYPE); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Incorrect Spell ID!", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "Player Not Online!", true, "ERROR"); ++} ++ ++void IRCCmd::Sysmsg_Server(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, CD->PCOUNT); ++ std::string ircchan = "#"; ++ ircchan += sIRC->anchn; ++ if (_PARAMS[0] == "a") ++ { ++ std::string str = _PARAMS[1]; ++ std::string ancmsg = MakeMsg("\00304,08\037/!\\\037\017\00304 System Message \00304,08\037/!\\\037\017 %s",_PARAMS[1].c_str()); ++ sWorld->SendWorldText(6620,str.c_str()); ++ sIRC->Send_IRC_Channel(ircchan, ancmsg, true); ++ } ++ else if (_PARAMS[0] == "e") ++ { ++ std::string str = _PARAMS[1]; ++ std::string notstr = "[Server Event]: " + _PARAMS[1]; ++ std::string notmsg = MakeMsg("\00304,08\037/!\\\037\017\00304 Server Event \00304,08\037/!\\\037\017 %s",_PARAMS[1].c_str()); ++ WorldPacket data(SMSG_NOTIFICATION, (notstr.size()+1)); ++ data << notstr; ++ WorldPacket data2(SMSG_PLAY_SOUND,32); ++ data2 << (uint32)1400; ++ sWorld->SendGlobalMessage(&data2); ++ sWorld->SendGlobalMessage(&data); ++ sWorld->SendWorldText(6621,str.c_str()); ++ sIRC->Send_IRC_Channel(ircchan, notmsg, true); ++ } ++ else if (_PARAMS[0] == "n") ++ { ++ std::string str = "Global notify: " + _PARAMS[1]; ++ std::string notmsg = MakeMsg("\00304,08\037/!\\\037\017\00304 Global Notify \00304,08\037/!\\\037\017 %s",_PARAMS[1].c_str()); ++ WorldPacket data(SMSG_NOTIFICATION, (str.size()+1)); ++ data << str; ++ sWorld->SendGlobalMessage(&data); ++ sIRC->Send_IRC_Channel(ircchan, notmsg, true); ++ } ++ else if (_PARAMS[0] == "gm") ++ { ++ std::string str = "GM Announcement: " + _PARAMS[1]; ++ WorldPacket data(SMSG_NOTIFICATION, (str.size()+1)); ++ data << str; ++ sWorld->SendGlobalGMMessage(&data); ++ ++ } ++ else if (_PARAMS[0] == "add") ++ { ++ WorldDatabase.PExecute("INSERT INTO irc_autoannounce (message, addedby) VALUES ('%s', '%s')", _PARAMS[1].c_str(), CD->USER.c_str()); ++ std::string str = _PARAMS[1]; ++ std::string ancmsg = MakeMsg("\00304,08\037/!\\\037\017\00304 Automatic System Message \00304,08\037/!\\\037\017 %s",_PARAMS[1].c_str()); ++ sWorld->SendWorldText(6622,str.c_str()); ++ sIRC->Send_IRC_Channel(ircchan, ancmsg, true); ++ } ++ else if (_PARAMS[0] == "del") ++ { ++ WorldDatabase.PExecute("DELETE FROM irc_autoannounce WHERE id = %s", _PARAMS[1].c_str()); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("Deleted Automatic Announcement Message ID: %s", _PARAMS[1].c_str()), true, CD->TYPE); ++ } ++ else if (_PARAMS[0] == "list") ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT * FROM irc_autoannounce LIMIT 5;", _PARAMS[1].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string id = fields[0].GetString(); ++ std::string message = fields[1].GetString(); ++ std::string addedby = fields[2].GetString(); ++ Send_IRCA(ChanOrPM(CD), MakeMsg("ID: %s - Added By: %s - Message: %s", id.c_str(), addedby.c_str(), message.c_str()), true, CD->TYPE); ++ result->NextRow(); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "No Auto Announce Messages Are In The Database.", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "Please Use (a-Announce)(n-Notify)(e-Event) As Second Parameter!", true, "ERROR"); ++} ++ ++void IRCCmd::Tele_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 4); ++ if (AcctLevel(_PARAMS[0]) > GetLevel(CD->USER) && (sIRC->BOTMASK & 512)!= 0) ++ { ++ Send_IRCA(CD->USER, MakeMsg("You do not have access to do this to a higher ranked GM [%i]", AcctLevel(_PARAMS[0])), true, "ERROR"); ++ return; ++ } ++ bool DoTeleport = false; ++ float pX, pY, pZ, pO = 0; ++ uint32 mapid = 0; ++ std::string rMsg = " Teleport Failed!"; ++ std::string wMsg = "Invalid Tele Location"; ++ Player* plr = GetPlayer(_PARAMS[0]); ++ if (plr) ++ { ++ if (plr->IsInFlight() || plr->IsInCombat()) ++ { ++ Send_IRCA(CD->USER, MakeMsg("%s Is Busy And Cannot Be Teleported! They Could Be In Combat, Or Flying.",_PARAMS[0].c_str()), true, "ERROR"); ++ return; ++ } ++ } ++ if (_PARAMS[1] == "l" || _PARAMS[1].size() > 2) ++ { ++ if (_PARAMS[1].size() > 1) ++ _PARAMS[2] = _PARAMS[1]; ++ WorldDatabase.EscapeString(_PARAMS[2]); ++ QueryResult result = WorldDatabase.PQuery("SELECT position_x, position_y, position_z, orientation, map FROM game_tele WHERE name='%s';", _PARAMS[2].c_str()); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ pX = fields[0].GetFloat(); ++ pY = fields[1].GetFloat(); ++ pZ = fields[2].GetFloat(); ++ pO = fields[3].GetFloat(); ++ mapid = fields[4].GetUInt16(); ++ ++ rMsg = MakeMsg(" \00313[%s] : Teleported To %s! By: %s.", ++ _PARAMS[0].c_str(), ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Have Been Teleported To %s By: %s.", ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ DoTeleport = true; ++ } ++ else ++ { ++ WorldDatabase.EscapeString(_PARAMS[2]); ++ QueryResult result = WorldDatabase.PQuery("SELECT name FROM game_tele WHERE name LIKE '%%%s%%' LIMIT 7;", _PARAMS[2].c_str()); ++ if (result) ++ { ++ std::string telename = "<> "; ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ Field *fields = result->Fetch(); ++ telename.append(fields[0].GetString()); ++ result->NextRow(); ++ telename.append(" <> "); ++ } ++ ++ Send_IRCA(CD->USER, "I Cannot Find Location: "+_PARAMS[2]+" . Perhaps One Of These Will Work For You.", true, "ERROR"); ++ Send_IRCA(CD->USER, telename, true, "ERROR"); ++ return; ++ } ++ else ++ Send_IRCA(CD->USER, "Location Not Found! Nothing Even Close Found!", true, "ERROR"); ++ return; ++ } ++ } ++ else if (_PARAMS[1] == "c") ++ { ++ std::string* _PARAMSA = getArray(_PARAMS[2], 4); ++ pX = atof(_PARAMSA[1].c_str()); ++ pY = atof(_PARAMSA[2].c_str()); ++ pZ = atof(_PARAMSA[3].c_str()); ++ mapid = atoi(_PARAMSA[0].c_str()); ++ rMsg = MakeMsg(" \00313[%s] : Teleported To Map: %s. Position: X(%s) Y(%s) Z(%s)! By: %s.", ++ _PARAMS[0].c_str(), ++ _PARAMSA[0].c_str(), ++ _PARAMSA[1].c_str(), ++ _PARAMSA[2].c_str(), ++ _PARAMSA[3].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Have Been Teleported To Map: %s. Position: X(%s) Y(%s) Z(%s)! By: %s.", ++ _PARAMSA[0].c_str(), ++ _PARAMSA[1].c_str(), ++ _PARAMSA[2].c_str(), ++ _PARAMSA[3].c_str(), ++ CD->USER.c_str()); ++ DoTeleport = true; ++ } ++ else if (_PARAMS[1] == "r") ++ { ++ if (plr) ++ { ++ pX = plr->m_recallX; ++ pY = plr->m_recallY; ++ pZ = plr->m_recallZ; ++ pO = plr->m_recallO; ++ mapid = plr->m_recallMap; ++ rMsg = MakeMsg(" \00313[%s] : Has Been Recalled To Their Previous Location.", ++ _PARAMS[0].c_str()); ++ wMsg = MakeMsg("You Have Been Recalled To Your Previous Location. By: %s", ++ CD->USER.c_str()); ++ DoTeleport = true; ++ } ++ else ++ { ++ Send_IRCA(CD->USER, MakeMsg("\00313[%s] : Cannot Be Recalled, They Are Not Online.", _PARAMS[0].c_str()), true, "ERROR"); ++ return; ++ } ++ ++ } ++ else if (_PARAMS[1] == "to") ++ { ++ Player* plr2 = GetPlayer(_PARAMS[2]); ++ if (plr2) ++ { ++ plr2->GetContactPoint(plr, pX, pY, pZ); ++ mapid = plr2->GetMapId(); ++ } ++ else ++ { ++ if (ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[2].c_str())) ++ { ++ bool in_flight; ++ Player::LoadPositionFromDB(mapid, pX, pY, pZ, pO, in_flight, guid); ++ } ++ else ++ { ++ Send_IRCA(CD->USER, "Second Player Not Found!", true, "ERROR"); ++ return; ++ } ++ } ++ rMsg = MakeMsg(" \00313[%s] : Teleported To Player: [%s] By: %s.", ++ _PARAMS[0].c_str(), ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Are Being Teleported To: %s. By: %s.", ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ DoTeleport = true; ++ } ++ else if (_PARAMS[1] == "cr") ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map FROM creature WHERE guid ='%s' LIMIT 1", _PARAMS[2].c_str()); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "Creature GUID not found", true, "ERROR"); ++ } ++ else ++ { ++ Field *fields = result->Fetch(); ++ pX = fields[0].GetFloat(); ++ pY = fields[1].GetFloat(); ++ pZ = fields[2].GetFloat(); ++ pO = fields[3].GetFloat(); ++ mapid = fields[4].GetUInt16(); ++ ++ rMsg = MakeMsg(" \00313[%s] : Teleported To Creature: [%s] By: %s.", ++ _PARAMS[0].c_str(), ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Are Being Teleported To: %s. By: %s.", ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ DoTeleport = true; ++ ++ } ++ } ++ else if (_PARAMS[1] == "go") ++ { ++ QueryResult result = WorldDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map FROM gameobject WHERE guid ='%s' LIMIT 1", _PARAMS[2].c_str()); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "GO GUID not found", true, "ERROR"); ++ } ++ else ++ { ++ Field *fields = result->Fetch(); ++ pX = fields[0].GetFloat(); ++ pY = fields[1].GetFloat(); ++ pZ = fields[2].GetFloat(); ++ pO = fields[3].GetFloat(); ++ mapid = fields[4].GetUInt16(); ++ ++ rMsg = MakeMsg(" \00313[%s] : Teleported To Gameobject: [%s] By: %s.", ++ _PARAMS[0].c_str(), ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Are Being Teleported To: %s. By: %s.", ++ _PARAMS[2].c_str(), ++ CD->USER.c_str()); ++ DoTeleport = true; ++ ++ } ++ } ++ else if (_PARAMS[1] == "homebind") ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT posX,posY,posZ,mapId FROM `character_homebind` WHERE guid = '%d'", plr->GetGUID()); ++ if (!result) ++ { ++ Send_IRCA(CD->USER, "Unexpected Error Loading Homebind Location", true, "ERROR"); ++ } ++ else ++ { ++ Field *fields = result->Fetch(); ++ pX = fields[0].GetFloat(); ++ pY = fields[1].GetFloat(); ++ pZ = fields[2].GetFloat(); ++ pO = 1; ++ mapid = fields[3].GetUInt16(); ++ ++ rMsg = MakeMsg(" \00313[%s] : Teleported To Homebind Location By: %s.", ++ _PARAMS[0].c_str(), ++ CD->USER.c_str()); ++ wMsg = MakeMsg("You Are Being Teleported To Your Homebind Location By: %s.", ++ CD->USER.c_str()); ++ DoTeleport = true; ++ ++ } ++ ++ } ++ if (DoTeleport) ++ { ++ if (MapManager::IsValidMapCoord(mapid, pX ,pY ,pZ)) ++ { ++ //if player is online teleport them in real time, if not set the DB to our coordinates. ++ if (plr) ++ { ++ plr->SaveRecallPosition(); ++ plr->TeleportTo(mapid, pX, pY, pZ, pO); ++ sIRC->Send_IRC_Channel(ChanOrPM(CD), rMsg, true, CD->TYPE); ++ Send_Player(plr, wMsg); ++ } ++ else ++ { ++ ObjectGuid guid = sObjectMgr->GetPlayerGUIDByName(_PARAMS[0]); ++ WorldLocation loc; ++ loc.WorldRelocate(mapid,pX,pY,pZ,0.0f); ++ sIRC->Send_IRC_Channel(ChanOrPM(CD), rMsg + " \0034*Offline Tele.* ", true, CD->TYPE); ++ } ++ } ++ else ++ Send_IRCA(CD->USER, "Invalid Location!", true, "ERROR"); ++ } ++ else ++ Send_IRCA(CD->USER, "Invalid Paramaters, Please Try Again [ "+sIRC->_cmd_prefx+"help tele ] For More Information. ", true, "ERROR"); ++} ++ ++void IRCCmd::Top_Player(_CDATA *CD) ++{ ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ uint32 limitr = 10; ++ if (atoi(_PARAMS[1].c_str()) > 0 && GetLevel(CD->USER) >= sIRC->_op_gm_lev) ++ limitr = atoi(_PARAMS[1].c_str()); ++ if (_PARAMS[0] == "accttime") ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT account, name, (SUM(totaltime)) AS combinetime FROM characters GROUP BY account ORDER BY combinetime DESC LIMIT 0, %d ", limitr); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string tptime = MakeMsg("\x2 Top%d Accounts By Played Time:\x3\x31\x30 ", limitr); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ uint32 account = fields[0].GetUInt32(); ++ std::string PlName = GetAcctNameFromID(account); ++ std::string Time = SecToDay(fields[2].GetString()); ++ uint32 rank = i+1; ++ tptime.append(MakeMsg("[%u]%s %s \xF| \x3\x31\x30\x2", rank, PlName.c_str(), Time.c_str())); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), tptime, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "No Accounts Returned." ,true, "ERROR"); ++ } ++ if (_PARAMS[0] == "chartime") ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT name, totaltime FROM characters ORDER BY totaltime DESC LIMIT 0, %d ", limitr); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string tptime = MakeMsg("\x2 Top%d Characters By Played Time:\x3\x31\x30 ", limitr); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string Name = fields[0].GetString(); ++ std::string Time = SecToDay(fields[1].GetString()); ++ uint32 rank = i+1; ++ tptime.append(MakeMsg("[%u]%s %s \xF| \x3\x31\x30\x2", rank, Name.c_str(), Time.c_str())); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), tptime, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "No Characters Returned." ,true, "ERROR"); ++ } ++ if (_PARAMS[0] == "money") ++ { ++ QueryResult result = CharacterDatabase.PQuery("SELECT name, money FROM characters ORDER BY money DESC LIMIT 0, %d ", limitr); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ std::string tptime = MakeMsg("\x2 Top%d Characters By Money:\x3\x31\x30 ", limitr); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ std::string Name = fields[0].GetString(); ++ unsigned int money = fields[1].GetInt32(); ++ ++ uint32 rank = i+1; ++ ++ unsigned int gold = money / 10000; ++ unsigned int silv = (money % 10000) / 100; ++ unsigned int cop = (money % 10000) % 100; ++ char tempgold [100]; ++ sprintf(tempgold, "\x2\x3\x30\x37%ug \x3\x31\x34%us \x3\x30\x35%uc\xF", gold, silv, cop); ++ ++ tptime.append(MakeMsg("[%u]%s %s \xF| \x3\x31\x30\x2", rank, Name.c_str(), tempgold)); ++ result->NextRow(); ++ } ++ ++ Send_IRCA(ChanOrPM(CD), tptime, true, CD->TYPE); ++ } ++ else ++ Send_IRCA(CD->USER, "No Characters Returned." ,true, "ERROR"); ++ } ++ ++} ++ ++void IRCCmd::Chan_Control(_CDATA *CD) ++{ ++ ++ std::string* _PARAMS = getArray(CD->PARAMS, 2); ++ ++ if (CD->FROM == sIRC->_Nick) ++ { ++ Send_IRCA(CD->USER, "\0034[ERROR] : You Cannot Use This Command Through A PM Yet.", true, "ERROR"); ++ return; ++ } ++ ++ if (_PARAMS[0] == "op") ++ { ++ if (_PARAMS[1].length() > 1) ++ sIRC->SendIRC("MODE "+CD->FROM+" +o "+_PARAMS[1]); ++ else ++ sIRC->SendIRC("MODE "+CD->FROM+" +o "+CD->USER); ++ } ++ ++ if (_PARAMS[0] == "deop") ++ { ++ if (_PARAMS[1].length() > 1) ++ sIRC->SendIRC("MODE "+CD->FROM+" -o "+_PARAMS[1]); ++ else ++ sIRC->SendIRC("MODE "+CD->FROM+" -o "+CD->USER); ++ } ++ ++ if (_PARAMS[0] == "voice") ++ { ++ if (_PARAMS[1].length() > 1) ++ sIRC->SendIRC("MODE "+CD->FROM+" +v "+_PARAMS[1]); ++ else ++ sIRC->SendIRC("MODE "+CD->FROM+" +v "+CD->USER); ++ } ++ if (_PARAMS[0] == "devoice") ++ { ++ if (_PARAMS[1].length() > 1) ++ sIRC->SendIRC("MODE "+CD->FROM+" -v "+_PARAMS[1]); ++ else ++ sIRC->SendIRC("MODE "+CD->FROM+" -v "+CD->USER); ++ } ++}; ++void IRCCmd::Who_Logged(_CDATA *CD) ++{ ++ std::string OPS = ""; ++ for (std::list<_client*>::iterator i=_CLIENTS.begin(); i!=_CLIENTS.end();i++) ++ { ++ OPS.append(MakeMsg(" \002[GM:%d IRC: %s - WoW: %s]\002 ", (*i)->GMLevel, (*i)->Name.c_str(), (*i)->UName.c_str())); ++ } ++ Send_IRCA(ChanOrPM(CD), OPS, true, CD->TYPE); ++} +diff --git a/src/server/game/TriniChat/IRCFunc.h b/src/server/game/TriniChat/IRCFunc.h +new file mode 100644 +index 0000000..f780dd7 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCFunc.h +@@ -0,0 +1,292 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * 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 _IRC_CLIENT_FUNC ++#define _IRC_CLIENT_FUNC ++ ++std::string GetUser(std::string szU) ++{ ++ int pos = szU.find("!"); ++ return szU.substr(0, pos); ++} ++// Delink will remove anything considered "non chat" from a string ++std::string Delink(std::string msg) ++{ ++ std::size_t pos; ++ ++ while((pos = msg.find("|Htrade")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Hglyph")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Hitem")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Henchant")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ //msg.replace(find2, 2, "\x2"); ++ } ++ while((pos = msg.find("|Hquest")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Hspell")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Htalent")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Hachievement")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ while((pos = msg.find("|Htrade")) != std::string::npos) ++ { ++ std::size_t find1 = msg.find("|h", pos); ++ msg.replace(pos, find1 - pos + 2, "\x2"); ++ msg.replace(msg.find("|h", pos), 2, "\x2"); ++ } ++ return msg; ++} ++ ++// This function converts the characters used by the client to identify colour to IRC format. ++std::string WoWcol2IRC(std::string msg) ++{ ++ std::size_t pos; ++ char IRCCol[17][6] = { "\xF", "\xF", "\x3\x31\x34", "\x3\x30\x33", "\x3\x31\x32", "\x3\x30\x36", "\x3\x30\x37", "\x3\x30\x34", "\x3\x30\x34", "\x3\x31\x34", "\x3\x31\x32", "\x3\x30\x37", "\x3\x30\x34", "\x3\x30\x33", "\x3\x31\x32", "\x3\x31\x32", "\x3\x30\x37"}; ++ char WoWCol[17][12] = { "|r", "|cffffffff", "|cff9d9d9d", "|cff1eff00", "|cff0070dd", "|cffa335ee", "|cffff8000", "|cffe6cc80", "|cffffd000", "|cff808080", "|cff71d5ff", "|cffffff00", "|cffff2020", "|cff40c040", "|cff4e96f7", "|cff71d5ff", "|cffff8040"}; ++ for (int i=0; i<=15; i++) ++ { ++ while ((pos = msg.find(WoWCol[i])) != std::string::npos) ++ { ++ if (i == 0) ++ msg.replace(pos, 2, IRCCol[i]); ++ else ++ msg.replace(pos, 11, IRCCol[i]); ++ } ++ } ++ return msg; ++} ++ ++// This function converts the characters used by IRC to identify colour to a format the client can understand. ++std::string IRCcol2WoW(std::string msg) ++{ ++ std::size_t pos; ++ char IRCCol[18][4] = { "\x3\x30", "\x3\x31", "\x3\x32", "\x3\x33", "\x3\x34", "\x3\x35", "\x3\x36", "\x3\x37", "\x3\x38", "\x3\x39", "\x3\x31\x30", "\x3\x31\x31", "\x3\x31\x32", "\x3\x31\x33", "\x3\x31\x34", "\x3\x31\x35", "\x3\x30\x37", "\x3\x30\x37"}; ++ char IRCCol2[10][4] = { "\x3\x30\x30", "\x3\x30\x31", "\x3\x30\x32", "\x3\x30\x33", "\x3\x30\x34", "\x3\x30\x35", "\x3\x30\x36", "\x3\x30\x37", "\x3\x30\x38", "\x3\x30\x39"}; ++ char WoWcol[18][12] = { "|cffffffff", "|cff000000", "|cff00007f", "|cff009300", "|cffff0000", "|cff7f0000", "|cff9c009c", "|cfffc9300", "|cffffff00", "|cff00fc00", "|cff009393", "|cff00ffff", "|cff0000fc", "|cffff00ff", "|cff7f7f7f", "|cffd2d2d2", "|cff808080", "|cff71d5ff"}; ++ for (int i=15; i>=0; i--) ++ { ++ if (i<10) ++ { ++ while ((pos = msg.find(IRCCol2[i])) != std::string::npos) ++ { ++ msg.replace(pos, 3, WoWcol[i]); ++ } ++ while ((pos = msg.find(IRCCol[i])) != std::string::npos) ++ { ++ msg.replace(pos, 2, WoWcol[i]); ++ } ++ ++ } ++ else ++ { ++ while ((pos = msg.find(IRCCol[i])) != std::string::npos) ++ { ++ msg.replace(pos, 3, WoWcol[i]); ++ } ++ } ++ ++ // Remove Bold, Reverse, Underline from IRC ++ char Checker[3][3] = {"\x2","\x16","\x1F"}; // This is the Hex part not Dec. In Decimal its (2,22,31) ++ for (int I=0; I < 3; I++) ++ { ++ while ((pos = msg.find(Checker[I])) != std::string::npos) ++ { ++ msg.replace(pos, 1, ""); ++ } ++ } ++ // Finished Removing ! ++ ++ } ++ ++ while ((pos = msg.find("\x3")) != std::string::npos) ++ { ++ msg.replace(pos, 1, "|r"); ++ } ++ while ((pos = msg.find("\xF")) != std::string::npos) ++ { ++ msg.replace(pos, 1, "|r"); ++ } ++ ++ return msg; ++} ++ ++// This function compares 2 strings ++int nocase_cmp(const string & s1, const string& s2) ++{ ++ string::const_iterator it1=s1.begin(); ++ string::const_iterator it2=s2.begin(); ++ ++ //stop when either string's end has been reached ++ while ((it1!=s1.end()) && (it2!=s2.end())) ++ { ++ if (::toupper(*it1) != ::toupper(*it2)) //letters differ? ++ // return -1 to indicate smaller than, 1 otherwise ++ return (::toupper(*it1) < ::toupper(*it2)) ? -1 : 1; ++ //proceed to the next character in each string ++ ++it1; ++ ++it2; ++ } ++ size_t size1=s1.size(), size2=s2.size(); // cache lengths ++ //return -1,0 or 1 according to strings' lengths ++ if (size1==size2) ++ return 0; ++ return (size1MakeMsg(sIRC->GetChatLine(CLINE), "$Msg", Msg); ++ if (plr->GetTeam() == 67) ++ sMsg = sIRC->MakeMsg(sMsg, "$Name", MakeMsgA("\0034%s\003", plr->GetName().c_str())); ++ else if (plr->GetTeam() == 469) ++ sMsg = sIRC->MakeMsg(sMsg, "$Name", MakeMsgA("\00312%s\003", plr->GetName().c_str())); ++ if (plr->isAFK()) ++ sMsg = sIRC->MakeMsg(sMsg, "$Tag", ""); ++ else if (plr->isDND()) ++ sMsg = sIRC->MakeMsg(sMsg, "$Tag", ""); ++ else ++ sMsg = sIRC->MakeMsg(sMsg, "$Tag", ""); ++ sMsg = sIRC->MakeMsg(sMsg, "$Level", MakeMsgA("%d", plr->getLevel())); ++ if (plr->getClass() == 1) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\0035WR\003")); ++ else if (plr->getClass() == 2) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\00313PA\003")); ++ else if (plr->getClass() == 3) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\0033HU\003")); ++ else if (plr->getClass() == 4) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\00310RO\003")); ++ else if (plr->getClass() == 5) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\00314PR\003")); ++ else if (plr->getClass() == 6) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\0034DK\003")); ++ else if (plr->getClass() == 7) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\00312SH\003")); ++ else if (plr->getClass() == 8) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\00311MA\003")); ++ else if (plr->getClass() == 9) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\0036WL\003")); ++ else if (plr->getClass() == 11) ++ sMsg = sIRC->MakeMsg(sMsg, "$Class", MakeMsgA("\0037DR\003")); ++ sMsg = Delink(sMsg); ++ sMsg = WoWcol2IRC(sMsg); ++ return sMsg; ++} ++ ++// This function checks if a channel exists in out configuration ++// TriniChat supports as many channels as you like ++bool Channel_Valid(std::string Channel) ++{ ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ { ++ if (nocase_cmp(sIRC->_wow_chan[i], Channel)==0) ++ return true; ++ } ++ return false; ++} ++ ++std::string GetWoWChannel(std::string Channel) ++{ ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ { ++ if ("#" + sIRC->_irc_chan[i] == Channel) ++ return sIRC->_wow_chan[i]; ++ } ++ return ""; ++} ++ ++std::string GetIRCChannel(std::string Channel) ++{ ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ { ++ if (sIRC->_wow_chan[i] == Channel) ++ return sIRC->_irc_chan[i]; ++ } ++ return ""; ++} ++ ++std::string* getArray(std::string PARAMS, int nCount, std::string) ++{ ++ std::string *array = new std::string[nCount]; ++ if (PARAMS.size() > 0) ++ { ++ size_t ps = 0; ++ size_t pc = -1; ++ for (int i = 0;i < nCount;i++) ++ { ++ pc = PARAMS.find(" ", pc + 1); ++ if (i + 1 == nCount && nCount != 1) ++ { ++ if (ps > 0 && pc > 0) ++ array[i] = PARAMS.substr(ps, PARAMS.size() - ps); ++ } ++ else ++ array[i] = PARAMS.substr(ps, pc - ps); ++ ps = pc + 1; ++ } ++ } ++ return array; ++} ++#endif +diff --git a/src/server/game/TriniChat/IRCIO.cpp b/src/server/game/TriniChat/IRCIO.cpp +new file mode 100644 +index 0000000..efa1b0c +--- /dev/null ++++ b/src/server/game/TriniChat/IRCIO.cpp +@@ -0,0 +1,529 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCClient.h" ++#include "IRCCmd.h" ++#include "IRCFunc.h" ++#include "Language.h" ++#include "ObjectAccessor.h" ++#include "ObjectMgr.h" ++#include "WorldPacket.h" ++#include "ChannelMgr.h" ++#include "Config.h" ++#include "Channel.h" ++#include "World.h" ++ ++IRCCmd Command; ++void IRCClient::Handle_IRC(std::string sData) ++{ ++ // If first 5 chars are ERROR then something is wrong ++ // either link is being closed, nickserv ghost command, etc... ++ if (sData.substr(0, 5) == "ERROR") ++ { ++ Disconnect(); ++ return; ++ } ++ if (sData.substr(0, 4) == "PING") ++ { ++ // if the first 4 characters contain PING ++ // the server is checking if we are still alive ++ // sen back PONG back plus whatever the server send with it ++ SendIRC("PONG " + sData.substr(4, sData.size() - 4)); ++ } ++ else ++ { ++ // if the first line contains : its an irc message ++ // such as private messages channel join etc. ++ if (sData.substr(0, 1) == ":") ++ { ++ // find the spaces in the receieved line ++ size_t p1 = sData.find(" "); ++ size_t p2 = sData.find(" ", p1 + 1); ++ // because the irc protocol uses simple spaces ++ // to seperate data we can easy pick them out ++ // since we know the position of the spaces ++ std::string USR = sData.substr(1, p1 - 1); ++ std::string CMD = sData.substr(p1 + 1, p2 - p1 - 1); ++ // trasform the commands to lowercase to make sure they always match ++ std::transform(CMD.begin(), CMD.end(), CMD.begin(), towlower); ++ // Extract the username from the first part ++ std::string szUser = GetUser(USR); ++ // if we receieved the internet connect code ++ // we know for sure that were in and we can ++ // authenticate ourself. ++ if (CMD == sIRC->_ICC) ++ { ++ // _Auth is defined in trinitycore.conf (irc.auth) ++ // 0 do not authenticate ++ // 1 use nickserv ++ // 2 use quakenet ++ // aditionally you can provide you own authentication method here ++ switch(sIRC->_Auth) ++ { ++ case 1: ++ SendIRC("PRIVMSG nickserv :IDENTIFY " + sIRC->_Pass); ++ break; ++ case 2: ++ SendIRC("PRIVMSG nickserv :IDENTIFY " + sIRC->_Auth_Nick + " " + sIRC->_Pass); ++ break; ++ case 3: ++ SendIRC("PRIVMSG Q@CServe.quakenet.org :AUTH " + sIRC->_Nick + " " + sIRC->_Pass); ++ break; ++ case 4: ++ SendIRC("PRIVMSG Q@CServe.quakenet.org :AUTH " + sIRC->_Auth_Nick + " " + sIRC->_Pass); ++ break; ++ } ++ // if we join a default channel leave this now. ++ if (sIRC->_ldefc==1) ++ SendIRC("PART #" + sIRC->_defchan); ++ // Loop thru the channel array and send a command to join them on IRC. ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ { ++ if (sIRC->_irc_pass[i].size() > 0) ++ SendIRC("JOIN #" + sIRC->_irc_chan[i] + " " + sIRC->_irc_pass[i]); ++ else ++ SendIRC("JOIN #" + sIRC->_irc_chan[i]); ++ } ++ // See if there's a log channel available, if so: join it. ++ if (sIRC->logchan.size() > 0) ++ { ++ if (sIRC->logchanpw.size() > 0) ++ SendIRC("JOIN #" + sIRC->logchan + " " + sIRC->logchanpw); ++ else ++ SendIRC("JOIN #" + sIRC->logchan); ++ } ++ // See if there's a Status channel available, if so: join it. ++ if (sIRC->Status.size() > 0) ++ { ++ if (sIRC->Statuspw.size() > 0) ++ SendIRC("JOIN #" + sIRC->Status + " " + sIRC->Statuspw); ++ else ++ SendIRC("JOIN #" + sIRC->Status); ++ } ++ // See if there's a Ticket channel available, if so: join it. ++ if (sIRC->ticann.size() > 0) ++ { ++ if (sIRC->ticannpw.size() > 0) ++ SendIRC("JOIN #" + sIRC->ticann + " " + sIRC->ticannpw); ++ else ++ SendIRC("JOIN #" + sIRC->ticann); ++ } ++ // See if there's an Announce channel available, if so: join it. ++ if (sIRC->anchn.size() > 0) ++ { ++ if (sIRC->anchnpw.size() > 0) ++ SendIRC("JOIN #" + sIRC->anchn + " " + sIRC->anchnpw); ++ else ++ SendIRC("JOIN #" + sIRC->anchn); ++ } ++ } ++ // someone joined the channel this could be the bot or another user ++ if (CMD == "join") ++ { ++ size_t p = sData.find(":", p1); ++ std::string CHAN = sData.substr(p + 1, sData.size() - p - 2); ++ // if the user is us it means we join the channel ++ if ((szUser == sIRC->_Nick)) ++ { ++ // its us that joined the channel ++ Send_IRC_Channel(CHAN, MakeMsg(MakeMsg(sIRC->JoinMsg, "$Ver", sIRC->_Mver.c_str()), "$Trigger", sIRC->_cmd_prefx.c_str()), true); ++ } ++ else ++ { ++ // if the user is not us its someone else that joins ++ // so we construct a message and send this to the clients. ++ // TriniChat now uses Send_WoW_Channel to send to the client ++ // this makes TriniChat handle the packets instead of previously the world. ++ if ((sIRC->BOTMASK & 2) != 0) ++ Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(JOIN_IRC), "$Name", szUser), "$Channel", GetWoWChannel(CHAN)))); ++ } ++ } ++ // someone on irc left or quit the channel ++ if (CMD == "part" || CMD == "quit") ++ { ++ size_t p3 = sData.find(" ", p2 + 1); ++ std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); ++ // Logout IRC Nick From TriniChat If User Leaves Or Quits IRC. ++ if (Command.IsLoggedIn(szUser)) ++ { ++ _CDATA CDATA; ++ CDATA.USER = szUser; ++ Command.Handle_Logout(&CDATA); ++ } ++ // Construct a message and inform the clients on the same channel. ++ if ((sIRC->BOTMASK & 2) != 0) ++ Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(LEAVE_IRC), "$Name", szUser), "$Channel", GetWoWChannel(CHAN)))); ++ } ++ // someone changed their nick ++ if (CMD == "nick" && (sIRC->BOTMASK & 128) != 0) ++ { ++ MakeMsg(MakeMsg(GetChatLine(CHANGE_NICK), "$Name", szUser), "$NewName", sData.substr(sData.find(":", p2) + 1)); ++ // If the user is logged in and changes their nick ++ // then we want to either log them out or update ++ // their nick in the bot. I chose to update the bots user list. ++ if (Command.IsLoggedIn(szUser)) ++ { ++ std::string NewNick = sData.substr(sData.find(":", p2) + 1); ++ // On freenode I noticed the server sends an extra character ++ // at the end of the string, so we need to erase the last ++ // character of the string. if you have a problem with getting ++ // the last letter of your nick erased, then remove the - 1. ++ NewNick.erase(NewNick.length() - 1, 1); ++ ++ for (std::list<_client*>::iterator i=Command._CLIENTS.begin(); i!=Command._CLIENTS.end();i++) ++ { ++ if ((*i)->Name == szUser) ++ { ++ (*i)->Name = NewNick; ++ sIRC->Send_IRC_Channel(NewNick.c_str(), "I Noticed You Changed Your Nick, I Have Updated My Internal Database Accordingly.", true, "NOTICE"); ++ ++ // Figure why not output to the logfile, makes tracing problems easier. ++ sIRC->iLog.WriteLog(" %s : %s Changed Nick To: %s", sIRC->iLog.GetLogDateTimeStr().c_str(), szUser.c_str(), NewNick.c_str()); ++ } ++ } ++ } ++ ++ } ++ // someone was kicked from irc ++ if (CMD == "kick") ++ { ++ // extract the details ++ size_t p3 = sData.find(" ", p2 + 1); ++ size_t p4 = sData.find(" ", p3 + 1); ++ std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); ++ std::string WHO = sData.substr(p3 + 1, p4 - p3 - 1); ++ std::string BY = sData.substr(p4 + 1, sData.size() - p4 - 1); ++ // if the one kicked was us ++ if (WHO == sIRC->_Nick) ++ { ++ // and autojoin is enabled ++ // return to the channel ++ if (sIRC->_autojoinkick == 1) ++ { ++ SendIRC("JOIN " + CHAN); ++ Send_IRC_Channel(CHAN, sIRC->kikmsg, true); ++ } ++ } ++ else ++ { ++ // if it is not us who was kicked we need to inform the clients someone ++ // was removed from the channel ++ // construct a message and send it to the players. ++ Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), "[" + WHO + "]: Was Kicked From " + CHAN + " By: " + szUser); ++ } ++ } ++ // a private chat message was receieved. ++ if (CMD == "privmsg" || CMD == "notice") ++ { ++ // extract the values ++ size_t p = sData.find(" ", p2 + 1); ++ std::string FROM = sData.substr(p2 + 1, p - p2 - 1); ++ std::string CHAT = sData.substr(p + 2, sData.size() - p - 3); ++ // if this is our username it means we recieved a PM ++ if (FROM == sIRC->_Nick) ++ { ++ if (CHAT.find("\001VERSION\001") < CHAT.size()) ++ { ++ Send_IRC_Channel(szUser, MakeMsg("\001VERSION TriniChat %s ?2008-2009 |Death|, Cybrax, Machiavelli\001", "%s" , sIRC->_Mver.c_str()), true, "PRIVMSG"); ++ } ++ // a pm is required for certain commands ++ // such as login. to validate the command ++ // we send it to the command class wich handles ++ // evrything else. ++ Command.IsValid(szUser, FROM, CHAT, CMD); ++ } ++ else ++ { ++ // if our name is not in it, it means we receieved chat on one of the channels ++ // magchat is in. the first thing we do is check if it is a command or not ++ if (!Command.IsValid(szUser, FROM, CHAT, CMD)) ++ { ++ std::string fixStaffChan = "#"+sIRC->_staffChan; ++ bool ignored = false; ++ switch(sIRC->_staffLink) ++ { ++ case 1: ++ if(fixStaffChan == FROM) ++ { ++ for(uint8 i = 0;i_ignore_bots[i] != "") ++ { ++ if(Command.MakeUpper(sIRC->_ignore_bots[i]) == Command.MakeUpper(szUser)) ++ ignored = true; ++ } ++ } ++ if(!ignored) ++ { ++ szUser = ""+szUser; ++ //setup gm chat ++ sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, szUser.c_str(), CHAT.c_str()); ++ } ++ } ++ else ++ { ++ for(uint8 i = 0;i_ignore_bots[i] != "") ++ { ++ if(Command.MakeUpper(sIRC->_ignore_bots[i]) == Command.MakeUpper(szUser)) ++ ignored = true; ++ } ++ } ++ if(!ignored) ++ { ++ Send_WoW_Channel(GetWoWChannel(FROM).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(IRC_WOW), "$Name", szUser), "$Msg", CHAT))); ++ } ++ } ++ break; ++ case 0: ++ Send_WoW_Channel(GetWoWChannel(FROM).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(IRC_WOW), "$Name", szUser), "$Msg", CHAT))); ++ break; ++ } ++ } ++ // if we indeed receieved a command we do not want to display this to the players ++ // so only incanse the isvalid command returns false it will be sent to all player. ++ // the isvalid function will automaitcly process the command on true. ++ } ++ } ++ if (CMD == "mode") ++ { ++ // extract the mode details ++ size_t p3 = sData.find(" ", p2 + 1); ++ size_t p4 = sData.find(" ", p3 + 1); ++ size_t p5 = sData.find(" ", p4 + 1); ++ std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); ++ std::string MODE = sData.substr(p3 + 1, p4 - p3 - 1); ++ std::string NICK = sData.substr(p4 + 1, p5 - p4 - 1); ++ _AmiOp = false; ++ //A mode was changed on us ++ if (NICK.c_str() == sIRC->_Nick) ++ _AmiOp = true; ++ ++ } ++ } ++ } ++} ++ ++// This function is called in Channel.h ++// based on nAction it will inform the people on ++// irc when someone leaves one of the game channels. ++// nAction is based on the struct CACTION ++void IRCClient::Handle_WoW_Channel(std::string Channel, Player *plr, int nAction) ++{ ++ // make sure that we are connected ++ if (sIRC->Connected && (sIRC->BOTMASK & 1)!= 0) ++ { ++ if (Channel_Valid(Channel)) ++ { ++ std::string GMRank = ""; ++ std::string pname = plr->GetName().c_str(); ++ bool DoGMAnnounce = false; ++ if (plr->GetSession()->GetSecurity() > 0 && (sIRC->BOTMASK & 8)!= 0) ++ DoGMAnnounce = true; ++ if (plr->IsGameMaster() && (sIRC->BOTMASK & 16)!= 0) ++ DoGMAnnounce = true; ++ if (DoGMAnnounce) ++ { ++ switch(plr->GetSession()->GetSecurity()) //switch case to determine what rank the "gm" is ++ { ++ case SEC_PLAYER: GMRank = ""; break; ++ case SEC_MODERATOR: GMRank = "\0037" + sIRC->ojGM1; break; ++ case SEC_GAMEMASTER: GMRank = "\0037" + sIRC->ojGM2; break; ++ case SEC_ADMINISTRATOR: GMRank = "\0037" + sIRC->ojGM3; break; ++ case SEC_CONSOLE: GMRank = "\0037" + sIRC->ojGM4; break; ++ } ++ } ++ std::string ChatTag = ""; ++ switch (plr->GetTeam()) ++ { ++ case 67:ChatTag.append("\0034");break; //horde ++ case 469:ChatTag.append("\00312");break; //alliance ++ } ++ std::string query = "INSERT INTO `irc_inchan` VALUES (%d,'"+pname+"','"+Channel+"')"; ++ std::string lchan = "DELETE FROM `irc_inchan` WHERE `guid` = %d AND `channel` = '"+Channel+"'"; ++ switch(nAction) ++ { ++ case CHANNEL_JOIN: ++ Send_IRC_Channel(GetIRCChannel(Channel), MakeMsg(MakeMsg(MakeMsg(GetChatLine(JOIN_WOW), "$Name", ChatTag + plr->GetName().c_str()), "$Channel", Channel), "$GM", GMRank)); ++ WorldDatabase.PExecute(lchan.c_str(), plr->GetGUID()); ++ WorldDatabase.PExecute(query.c_str(), plr->GetGUID()); ++ break; ++ case CHANNEL_LEAVE: ++ Send_IRC_Channel(GetIRCChannel(Channel), MakeMsg(MakeMsg(MakeMsg(GetChatLine(LEAVE_WOW), "$Name", ChatTag + plr->GetName().c_str()), "$Channel", Channel), "$GM", GMRank)); ++ WorldDatabase.PExecute(lchan.c_str(), plr->GetGUID()); ++ break; ++ } ++ } ++ } ++} ++ ++// This function sends chat to a irc channel or user ++// to prevent the # beeing appended to send a msg to a user ++// set the NoPrefix to true ++void IRCClient::Send_IRC_Channel(std::string sChannel, std::string sMsg, bool NoPrefix, std::string nType) ++{ ++ std::string mType = "PRIVMSG"; ++ if (Command.MakeUpper(nType.c_str()) == "NOTICE") ++ mType = "NOTICE"; ++ if (Command.MakeUpper(nType.c_str()) == "ERROR" && (sIRC->BOTMASK & 32)!= 0) ++ mType = "NOTICE"; ++ if (sIRC->Connected) ++ { ++ if (NoPrefix) ++ SendIRC(mType + " " + sChannel + " :" + sMsg); ++ else ++ SendIRC(mType + " #" + sChannel + " :" + sMsg); ++ } ++} ++ ++// This function sends a message to all irc channels ++// that TriniChat has in its configuration ++void IRCClient::Send_IRC_Channels(std::string sMsg) ++{ ++ for (int i=1;i < sIRC->_chan_count + 1;i++) ++ Send_IRC_Channel(sIRC->_irc_chan[i], sMsg); ++} ++ ++// This function is called in ChatHandler.cpp, any channel chat from wow will come ++// to this function, validates the channel and constructs a message that is send to IRC ++void IRCClient::Send_WoW_IRC(Player *plr, std::string Channel, std::string Msg) ++{ ++ // Check if the channel exist in our configuration ++ if (Channel_Valid(Channel) && Msg.substr(0, 1) != ".") ++ Send_IRC_Channel(GetIRCChannel(Channel), MakeMsgP(WOW_IRC, Msg, plr)); ++} ++ ++void IRCClient::Send_WoW_Player(std::string sPlayer, std::string sMsg) ++{ ++ normalizePlayerName(sPlayer); ++ if (Player* plr = ObjectAccessor::FindPlayerByName(sPlayer.c_str())) ++ Send_WoW_Player(plr, sMsg); ++} ++ ++void IRCClient::Send_WoW_Player(Player *plr, string sMsg) ++{ ++ WorldPacket data(SMSG_MESSAGECHAT, 200); ++ data << (uint8)CHAT_MSG_SYSTEM; ++ data << (uint32)LANG_UNIVERSAL; ++ data << (uint64)plr->GetGUID(); ++ data << (uint32)0; ++ data << (uint64)plr->GetGUID(); ++ data << (uint32)(sMsg.length()+1); ++ data << sMsg; ++ data << (uint8)0; ++ plr->GetSession()->SendPacket(&data); ++} ++ ++// This function will construct and send a packet to all players ++// on the given channel ingame. (previuosly found in world.cpp) ++// it loops thru all sessions and checks if they are on the channel ++// if so construct a packet and send it. ++void IRCClient::Send_WoW_Channel(const char *channel, std::string chat) ++{ ++ if (!(strlen(channel) > 0)) ++ return; ++ ++ #ifdef USE_UTF8 ++ std::string chat2 = chat; ++ if (ConvertUTF8(chat2.c_str(), chat2)) ++ chat = chat2; ++ #endif ++ ++ HashMapHolder::MapType const& m = ObjectAccessor::GetPlayers(); ++ for (HashMapHolder::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) ++ { ++ if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) ++ { ++ if (ChannelMgr* cMgr = ChannelMgr::forTeam(itr->second->GetSession()->GetPlayer()->GetTeam())) ++ { ++ if (cMgr->GetChannel(channel, itr->second->GetSession()->GetPlayer())) ++ { ++ WorldPacket data; ++ data.Initialize(SMSG_MESSAGECHAT); ++ data << (uint8)CHAT_MSG_CHANNEL; ++ data << (uint32)LANG_UNIVERSAL; ++ data << (uint64)0; ++ data << (uint32)0; ++ data << channel; ++ data << (uint64)0; ++ data << (uint32) (strlen(chat.c_str()) + 1); ++ data << IRCcol2WoW(chat.c_str()); ++ data << (uint8)0; ++ itr->second->GetSession()->SendPacket(&data); ++ } ++ } ++ } ++ } ++} ++ ++void IRCClient::Send_WoW_System(std::string Message) ++{ ++ HashMapHolder::MapType const& m = ObjectAccessor::GetPlayers(); ++ for (HashMapHolder::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) ++ { ++ if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) ++ { ++ WorldPacket data; ++ data.Initialize(CHAT_MSG_SYSTEM); ++ data << (uint8)CHAT_MSG_SYSTEM; ++ data << (uint32)LANG_UNIVERSAL; ++ data << (uint64)0; ++ data << (uint32)0; ++ data << (uint64)0; ++ data << (uint32) (strlen(Message.c_str()) + 1); ++ data << Message.c_str(); ++ data << (uint8)0; ++ itr->second->GetSession()->SendPacket(&data); ++ } ++ } ++} ++void IRCClient::ResetIRC() ++{ ++ SendData("QUIT"); ++ Disconnect(); ++} ++ ++#define CHAT_INVITE_NOTICE 0x18 ++ ++// this function should be called on player login Player::AddToWorld ++void IRCClient::AutoJoinChannel(Player *plr) ++{ ++ // this will work if at least 1 player is logged in regrdless if he is on the channel or not ++ // the first person that login empty server is the one with bad luck and wont be invited, ++ // if at least 1 player is online the player will be inited to the chanel ++ ++ std::string m_name = sIRC->ajchan; ++ WorldPacket data; ++ data.Initialize(SMSG_CHANNEL_NOTIFY, 1+m_name.size()+1); ++ data << uint8(CHAT_INVITE_NOTICE); ++ data << m_name.c_str(); ++ ++ HashMapHolder::MapType const& m = ObjectAccessor::GetPlayers(); ++ for (HashMapHolder::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) ++ { ++ if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) ++ { ++ data << uint64(itr->second->GetGUID()); ++ break; ++ } ++ } ++ plr->GetSession()->SendPacket(&data); ++} +diff --git a/src/server/game/TriniChat/IRCLog.cpp b/src/server/game/TriniChat/IRCLog.cpp +new file mode 100644 +index 0000000..5fa5f32 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCLog.cpp +@@ -0,0 +1,81 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCLog.h" ++#include "Config.h" ++#include "IRCClient.h" ++#include ++#include "World.h" ++ ++IRCLog::IRCLog() ++{ ++ std::string logsDir = sConfigMgr->GetStringDefault("LogsDir",""); ++ std::string ircLogName = logsDir + "/IRC_"; ++ std::string ircLogTimestamp = GetLogDateStr(); ++ ircLogName += ircLogTimestamp +".log"; ++ ircLogfile.open (ircLogName.c_str(), std::ios::app); ++} ++ ++IRCLog::~IRCLog() ++{ ++ ircLogfile.close(); ++} ++// Was added because using the time for logs is very annoying... one log per day.. much cleaner looking.. ++std::string IRCLog::GetLogDateStr() const ++{ ++ time_t t = time(NULL); ++ tm* aTm = localtime(&t); ++ // YYYY year ++ // MM month (2 digits 01-12) ++ // DD day (2 digits 01-31) ++ // HH hour (2 digits 00-23) ++ // MM minutes (2 digits 00-59) ++ // SS seconds (2 digits 00-59) ++ char buf[20]; ++ snprintf(buf,20,"%04d-%02d-%02d",aTm->tm_year+1900,aTm->tm_mon+1,aTm->tm_mday); ++ return std::string(buf); ++} ++ ++std::string IRCLog::GetLogDateTimeStr() const ++{ ++ time_t t = time(NULL); ++ tm* aTm = localtime(&t); ++ // YYYY year ++ // MM month (2 digits 01-12) ++ // DD day (2 digits 01-31) ++ // HH hour (2 digits 00-23) ++ // MM minutes (2 digits 00-59) ++ // SS seconds (2 digits 00-59) ++ char buf[30]; ++ snprintf(buf,30,"[ %04d-%02d-%02d ] [ %02d:%02d:%02d ]",aTm->tm_year+1900,aTm->tm_mon+1,aTm->tm_mday,aTm->tm_hour,aTm->tm_min,aTm->tm_sec); ++ return std::string(buf); ++} ++ ++void IRCLog::WriteLog(const char *what, ...) ++{ ++ va_list ap; ++ char tmpoutp[1024]; ++ va_start(ap, what); ++ vsnprintf(tmpoutp, 1024, what, ap); ++ va_end(ap); ++ ircLogfile << tmpoutp; ++ ircLogfile << "\n"; ++ ircLogfile.flush(); ++} +diff --git a/src/server/game/TriniChat/IRCLog.h b/src/server/game/TriniChat/IRCLog.h +new file mode 100644 +index 0000000..ac6dff0 +--- /dev/null ++++ b/src/server/game/TriniChat/IRCLog.h +@@ -0,0 +1,40 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * 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 _IRC_LOG_H ++#define _IRC_LOG_H ++ ++#include "Common.h" ++#include ++ ++class IRCLog ++{ ++ public: ++ IRCLog(); ++ ~IRCLog(); ++ ++ public: ++ void WriteLog(const char *what, ...); ++ std::string GetLogDateStr() const; ++ std::string GetLogDateTimeStr() const; ++ private: ++ std::ofstream ircLogfile; ++}; ++#endif +diff --git a/src/server/game/TriniChat/IRCSock.cpp b/src/server/game/TriniChat/IRCSock.cpp +new file mode 100644 +index 0000000..e3bde2b +--- /dev/null ++++ b/src/server/game/TriniChat/IRCSock.cpp +@@ -0,0 +1,160 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "IRCClient.h" ++#define MAXDATASIZE 512 ++#include ++ ++#include ++#include ++ ++ ++#define _UNICODE ++ ++#ifdef _MBCS ++#undef _MBCS ++#endif ++ ++bool IRCClient::InitSock() ++{ ++ #ifdef _WIN32 ++ WSADATA wsaData; //WSAData ++ if (WSAStartup(MAKEWORD(2,0),&wsaData) != 0) ++ { ++ TC_LOG_ERROR("misc", "IRC Error: Winsock Initialization Error"); ++ return false; ++ } ++ #endif ++ if ((sIRC->SOCKET = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) ++ { ++ TC_LOG_ERROR("misc", "IRC Error: Socket Error"); ++ return false; ++ } ++ int on = 1; ++ if (setsockopt (sIRC->SOCKET, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof (on)) == -1) ++ { ++ TC_LOG_ERROR("misc", "IRC Error: Invalid Socket"); ++ return false; ++ } ++ #ifdef _WIN32 ++ u_long iMode = 0; ++ ioctlsocket(sIRC->SOCKET, FIONBIO, &iMode); ++ #else ++ fcntl(sIRC->SOCKET, F_SETFL, O_NONBLOCK); // set to non-blocking ++ fcntl(sIRC->SOCKET, F_SETFL, O_ASYNC); // set to asynchronous I/O ++ #endif ++ return true; ++} ++ ++bool IRCClient::Connect(const char *cHost, int nPort) ++{ ++ sIRC->Connected = false; ++ struct hostent *he; ++ if ((he=gethostbyname(cHost)) == NULL) ++ { ++ TC_LOG_ERROR("misc", "IRCLIENT: Could not resolve host: %s", cHost); ++ return false; ++ } ++ struct sockaddr_in their_addr; ++ their_addr.sin_family = AF_INET; ++ their_addr.sin_port = htons(nPort); ++ their_addr.sin_addr = *((struct in_addr *)he->h_addr); ++ memset(&(their_addr.sin_zero), '\0', 8); ++ if (::connect(sIRC->SOCKET, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) ++ { ++ TC_LOG_ERROR("misc", "IRCLIENT: Cannot connect to %s", cHost); ++ return false; ++ } ++ sIRC->Connected = true; ++ return true; ++} ++ ++bool IRCClient::Login(std::string sNick, std::string sUser, std::string sPass) ++{ ++ char hostname[128]; ++ gethostname(hostname, sizeof(hostname)); ++ if (SendIRC("HELLO")) ++ if (SendIRC("PASS " + sPass)) ++ if (SendIRC("NICK " + sNick)) ++ if (SendIRC("USER " + sUser + " " + (std::string)hostname + " TriniChat :TriniChat "+sIRC->_Mver.c_str())) ++ return true; ++ return false; ++} ++ ++bool IRCClient::SendData(const char *data) ++{ ++ if (sIRC->Connected) ++ { ++ if (send(sIRC->SOCKET, data, strlen(data), 0) == -1) ++ { ++ TC_LOG_ERROR("misc", "IRC Error: Socket Receieve ** \n"); ++ return false; ++ } ++ } ++ return true; ++} ++ ++bool IRCClient::SendIRC(std::string data) ++{ ++ std::string RealData = data + "\n"; ++ return SendData(RealData.c_str()); ++} ++ ++void IRCClient::Disconnect() ++{ ++ if (sIRC->SOCKET) ++ { ++ #ifdef _WIN32 ++ closesocket(sIRC->SOCKET); ++ #else ++ close(sIRC->SOCKET); ++ #endif ++ } ++} ++ ++void IRCClient::SockRecv() ++{ ++ char szBuffer[MAXDATASIZE]; ++ ++ memset(szBuffer, 0, MAXDATASIZE); ++ ++ int nBytesRecv = ::recv(sIRC->SOCKET, szBuffer, MAXDATASIZE - 1, 0); ++ if (nBytesRecv == -1) ++ { ++ TC_LOG_ERROR("misc", "Connection lost."); ++ sIRC->Connected = false; ++ } ++ else ++ { ++ if (-1 == nBytesRecv) ++ { ++ TC_LOG_ERROR("misc", "Error occurred while receiving from socket."); ++ } ++ else ++ { ++ std::string reply; ++ std::istringstream iss(szBuffer); ++ while (getline(iss, reply)) ++ { ++ Handle_IRC(reply); ++ } ++ } ++ } ++} +diff --git a/src/server/game/TriniChat/MCS_OnlinePlayers.cpp b/src/server/game/TriniChat/MCS_OnlinePlayers.cpp +new file mode 100644 +index 0000000..5bc8074 +--- /dev/null ++++ b/src/server/game/TriniChat/MCS_OnlinePlayers.cpp +@@ -0,0 +1,94 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include "MCS_OnlinePlayers.h" ++ ++#include "MapManager.h" ++#include "ObjectMgr.h" ++#include "Config.h" ++#include "WorldSession.h" ++ ++mcs_OnlinePlayers::mcs_OnlinePlayers() { CD = NULL; } ++ ++mcs_OnlinePlayers::mcs_OnlinePlayers(_CDATA *_CD) ++{ ++ //create a new instance of data struct and copy its data ++ CD = new _CDATA(); ++ CD->CMD = _CD->CMD; ++ CD->FROM = _CD->FROM; ++ CD->PARAMS = _CD->PARAMS; ++ CD->PCOUNT = _CD->PCOUNT; ++ CD->USER = _CD->USER; ++ CD->TYPE = _CD->TYPE; ++} ++ ++mcs_OnlinePlayers::~mcs_OnlinePlayers() ++{ ++ if (CD) ++ delete CD; ++} ++ ++void mcs_OnlinePlayers::run() ++{ ++ int OnlineCount = 0; ++ std::string IRCOut = ""; ++ HashMapHolder::MapType const& m = ObjectAccessor::GetPlayers(); ++ for (HashMapHolder::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) ++ { ++ if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) ++ { ++ OnlineCount++; ++ Player *plr = itr->second->GetSession()->GetPlayer(); ++ std::string ChatTag = " "; ++ switch(plr->GetSession()->GetSecurity()) ++ { ++ case SEC_PLAYER: ChatTag.append(""); break; ++ case SEC_MODERATOR: ChatTag.append("\0037" + sIRC->ojGM1); break; ++ case SEC_GAMEMASTER: ChatTag.append("\0037" + sIRC->ojGM2); break; ++ case SEC_ADMINISTRATOR: ChatTag.append("\0037" + sIRC->ojGM3); break; ++ case SEC_CONSOLE: ChatTag.append("\0037" + sIRC->ojGM4); break; ++ } ++ if (plr->isAFK()) ++ ChatTag.append("\002\0037\003\002"); ++ else if (plr->isDND()) ++ ChatTag.append("\002\0037\003\002"); ++ switch (plr->GetTeam()) ++ { ++ case 67:ChatTag.append("\0034");break; //horde ++ case 469:ChatTag.append("\00312");break; //alliance ++ } ++ ++ IRCOut.append(IRCCmd::MakeMsg("%s\002%s\003\017\002(%d)\002\017", ChatTag.c_str(), plr->GetName().c_str(), plr->getLevel())); ++ ++ // after XX players have been added to the string ++ // output to irc and reset for the next XX ++ if (OnlineCount % sIRC->onlrslt == 0) ++ { ++ sIRC->Send_IRC_Channel(IRCCmd::ChanOrPM(CD), IRCCmd::MakeMsg("\002 %s", IRCOut.c_str()), true, CD->TYPE.c_str()); ++ IRCOut = ""; ++ std::this_thread::sleep_for(std::chrono::milliseconds(1000)); ++ } ++ } ++ } ++ // Remainder in IRCOUT && Total plyersonline ++ sIRC->Send_IRC_Channel(IRCCmd::ChanOrPM(CD), IRCCmd::MakeMsg("\002Players Online(%d):\017 %s", OnlineCount, IRCOut.c_str()), true, CD->TYPE); ++ ++ sIRC->Script_Lock[MCS_Players_Online] = false; ++} +diff --git a/src/server/game/TriniChat/MCS_OnlinePlayers.h b/src/server/game/TriniChat/MCS_OnlinePlayers.h +new file mode 100644 +index 0000000..700d730 +--- /dev/null ++++ b/src/server/game/TriniChat/MCS_OnlinePlayers.h +@@ -0,0 +1,38 @@ ++/* ++ * Copyright (C) 2005-2008 MaNGOS ++ * ++ * Copyright (C) 2008 Trinity ++ * ++ * 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 _IRC_CLIENT_ONLINE ++#define _IRC_CLIENT_ONLINE ++ ++#include "IRCClient.h" ++#include "IRCCmd.h" ++ ++class mcs_OnlinePlayers ++{ ++ public: ++ mcs_OnlinePlayers(); ++ mcs_OnlinePlayers(_CDATA *_CD); ++ ~mcs_OnlinePlayers(); ++ void run(); ++ public: ++ _CDATA *CD; ++}; ++ ++#endif +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..bde64fe 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -67,7 +67,7 @@ + // Prepatch by LordPsyan + // 01 + // 02 +-// 03 ++#include "IRCClient.h" + // 04 + // 05 + // 06 +@@ -1365,6 +1365,168 @@ void World::LoadConfigSettings(bool reload) + // call ScriptMgr if we're reloading the configuration + if (reload) + sScriptMgr->OnConfigLoad(reload); ++ sScriptMgr->OnConfigLoad(reload); ++ ++ // IRC Configurations. ++ int ConfCnt = 0; ++ sIRC->_chan_count = 0; ++ if (sConfigMgr->GetIntDefault("irc.active", 1) == 1) ++ sIRC->Active = true; ++ else ++ sIRC->Active = false; ++ ++ sIRC->_Host = sConfigMgr->GetStringDefault("irc.host", "irc.freenode.net"); ++ if (sIRC->_Host.size() > 0) ++ ConfCnt++; ++ sIRC->_Mver = "Version 4.1"; ++ sIRC->_Port = sConfigMgr->GetIntDefault("irc.port", 6667); ++ sIRC->_User = sConfigMgr->GetStringDefault("irc.user", "TriniChat"); ++ sIRC->_Pass = sConfigMgr->GetStringDefault("irc.pass", "Services Password"); ++ sIRC->_Nick = sConfigMgr->GetStringDefault("irc.nick", "TriniChat"); ++ sIRC->_Auth = sConfigMgr->GetIntDefault("irc.auth", 0); ++ sIRC->_Auth_Nick = sConfigMgr->GetStringDefault("irc.auth.nick", "AuthNick"); ++ sIRC->_ICC = sConfigMgr->GetStringDefault("irc.icc", "001"); ++ sIRC->_defchan = sConfigMgr->GetStringDefault("irc.defchan", "lobby"); ++ sIRC->_ldefc = sConfigMgr->GetIntDefault("irc.ldef", 0); ++ sIRC->_wct = sConfigMgr->GetIntDefault("irc.wct", 30000); ++ sIRC->ajoin = sConfigMgr->GetIntDefault("irc.ajoin", 0); ++ sIRC->_staffLink = sConfigMgr->GetIntDefault("irc.staff_link", 1); ++ sIRC->_staffChan = sConfigMgr->GetStringDefault("irc.staff_chan", "staff"); ++ sIRC->_bot_names = sConfigMgr->GetStringDefault("irc.ignore_bots", ""); ++ sIRC->ajchan = sConfigMgr->GetStringDefault("irc.ajchan", "world"); ++ sIRC->onlrslt = sConfigMgr->GetIntDefault("irc.online.result", 10); ++ sIRC->BOTMASK = sConfigMgr->GetIntDefault("Botmask", 0); ++ sIRC->TICMASK = sConfigMgr->GetIntDefault("Ticketmask", 0); ++ sIRC->logfile = sConfigMgr->GetStringDefault("irc.logfile.prefix", "IRC_"); ++ sIRC->logmask = sConfigMgr->GetIntDefault("irc.logmask", 0); ++ sIRC->logchan = sConfigMgr->GetStringDefault("irc.logchannel",""); ++ sIRC->logchanpw = sConfigMgr->GetStringDefault("irc.logchannelpw",""); ++ for (int i = 1; i < MAX_CONF_CHANNELS;i++) ++ { ++ std::ostringstream ss; ++ ss << i; ++ std::string ci = "irc.chan_" + ss.str(); ++ std::string pw = "irc.pass_" + ss.str(); ++ std::string t_chan = sConfigMgr->GetStringDefault(ci.c_str(), ""); ++ if (t_chan.size() > 0) ++ { ++ sIRC->_chan_count++; ++ sIRC->_irc_chan[sIRC->_chan_count] = t_chan; ++ sIRC->_irc_pass[sIRC->_chan_count] = sConfigMgr->GetStringDefault(pw.c_str(), t_chan.c_str()); ++ ci = "wow.chan_" + ss.str(); ++ sIRC->_wow_chan[sIRC->_chan_count] = sConfigMgr->GetStringDefault(ci.c_str(), t_chan.c_str()); ++ } ++ } ++ sIRC->JoinMsg = sConfigMgr->GetStringDefault("irc.joinmsg", "TriniChat $Ver for Trinitycore 3.3.x"); ++ sIRC->RstMsg = sConfigMgr->GetStringDefault("irc.rstmsg", "TriniChat Is Restarting, I Will Be Right Back!"); ++ sIRC->kikmsg = sConfigMgr->GetStringDefault("irc.kickmsg", "Do Not Kick Me Again, Severe Actions Will Be Taken!"); ++ ++ // IRC LINES ++ sIRC->ILINES[WOW_IRC] = sConfigMgr->GetStringDefault("chat.wow_irc", "\003[\002$Name($Level)\002\003] $Msg"); ++ sIRC->ILINES[IRC_WOW] = sConfigMgr->GetStringDefault("chat.irc_wow", "\003[$Name]: $Msg"); ++ sIRC->ILINES[JOIN_WOW] = sConfigMgr->GetStringDefault("chat.join_wow", "\00312>>\00304 $Name \003Joined The Channel!"); ++ sIRC->ILINES[JOIN_IRC] = sConfigMgr->GetStringDefault("chat.join_irc", "\003[$Name]: Has Joined IRC!"); ++ sIRC->ILINES[LEAVE_WOW] = sConfigMgr->GetStringDefault("chat.leave_wow", "\00312<<\00304 $Name \003Left The Channel!"); ++ sIRC->ILINES[LEAVE_IRC] = sConfigMgr->GetStringDefault("chat.leave_irc", "\003[$Name]: Has Left IRC!"); ++ sIRC->ILINES[CHANGE_NICK] = sConfigMgr->GetStringDefault("chat.change_nick", "\003<> $Name Is Now Known As $NewName!"); ++ ++ // TriniChat Options ++ sIRC->_MCA = sConfigMgr->GetIntDefault("irc.maxattempt", 10); ++ sIRC->_autojoinkick = sConfigMgr->GetIntDefault("irc.autojoin_kick", 1); ++ sIRC->_cmd_prefx = sConfigMgr->GetStringDefault("irc.command_prefix", "."); ++ ++ sIRC->_op_gm = sConfigMgr->GetIntDefault("irc.op_gm_login", 0); ++ sIRC->_op_gm_lev = sConfigMgr->GetIntDefault("irc.op_gm_level", 3); ++ ++ // Misc Options ++ sIRC->games = sConfigMgr->GetIntDefault("irc.fun.games", 0); ++ sIRC->gmlog = sConfigMgr->GetIntDefault("irc.gmlog", 1); ++ sIRC->Status = sConfigMgr->GetStringDefault("irc.StatusChannel", ""); ++ sIRC->Statuspw = sConfigMgr->GetStringDefault("irc.StatusChannelPW",""); ++ sIRC->anchn = sConfigMgr->GetStringDefault("irc.AnnounceChannel", ""); ++ sIRC->anchnpw = sConfigMgr->GetStringDefault("irc.AnnounceChannelPW",""); ++ sIRC->ticann = sConfigMgr->GetStringDefault("irc.Tickets", ""); ++ sIRC->ticannpw = sConfigMgr->GetStringDefault("irc.TicketsPW",""); ++ sIRC->autoanc = sConfigMgr->GetIntDefault("irc.auto.announce", 30); ++ sIRC->ojGM1 = sConfigMgr->GetStringDefault("irc.gm1", "[Moderator]"); ++ sIRC->ojGM2 = sConfigMgr->GetStringDefault("irc.gm2", "[GameMaster]"); ++ sIRC->ojGM3 = sConfigMgr->GetStringDefault("irc.gm3", "[Developer]"); ++ sIRC->ojGM4 = sConfigMgr->GetStringDefault("irc.gm4", "[Owner]"); ++ // REQUIRED GM LEVEL ++ QueryResult result = WorldDatabase.PQuery("SELECT `Command`, `gmlevel` FROM `irc_commands` ORDER BY `Command`"); ++ if (result) ++ { ++ Field *fields = result->Fetch(); ++ for (uint64 i=0; i < result->GetRowCount(); i++) ++ { ++ //TODO: ELSEIF? STRCMP? ++ std::string command = fields[0].GetCString(); ++ uint32 gmlvl = fields[1].GetUInt32(); ++ if (command == "acct") sIRC->CACCT = gmlvl; ++ if (command == "ban") sIRC->CBAN = gmlvl; ++ if (command == "char") sIRC->CCHAN = gmlvl; ++ if (command == "char") sIRC->CCHAR = gmlvl; ++ if (command == "fun") sIRC->CFUN = gmlvl; ++ if (command == "help") sIRC->CHELP = gmlvl; ++ if (command == "inchan") sIRC->CINCHAN = gmlvl; ++ if (command == "info") sIRC->CINFO = gmlvl; ++ if (command == "item") sIRC->CITEM = gmlvl; ++ if (command == "jail") sIRC->CJAIL = gmlvl; ++ if (command == "kick") sIRC->CKICK = gmlvl; ++ if (command == "kill") sIRC->_KILL = gmlvl; ++ if (command == "level") sIRC->CLEVEL = gmlvl; ++ if (command == "lookup") sIRC->CLOOKUP = gmlvl; ++ if (command == "money") sIRC->CMONEY = gmlvl; ++ if (command == "mute") sIRC->CMUTE = gmlvl; ++ if (command == "online") sIRC->CONLINE = gmlvl; ++ if (command == "pm") sIRC->CPM = gmlvl; ++ if (command == "reconnect") sIRC->CRECONNECT = gmlvl; ++ if (command == "reload") sIRC->CRELOAD = gmlvl; ++ if (command == "restart") sIRC->CSHUTDOWN = gmlvl; ++ if (command == "revive") sIRC->CREVIVE = gmlvl; ++ if (command == "saveall") sIRC->CSAVEALL = gmlvl; ++ if (command == "server") sIRC->CSERVERCMD = gmlvl; ++ if (command == "shutdown") sIRC->CSHUTDOWN = gmlvl; ++ if (command == "spell") sIRC->CSPELL = gmlvl; ++ if (command == "sysmsg") sIRC->CSYSMSG = gmlvl; ++ if (command == "tele") sIRC->CTELE = gmlvl; ++ if (command == "top") sIRC->CTOP = gmlvl; ++ if (command == "who") sIRC->CWHO = gmlvl; ++ result->NextRow(); ++ } ++ } ++ else ++ { ++ sIRC->CACCT = 3; ++ sIRC->CBAN = 3; ++ sIRC->CCHAN = 3; ++ sIRC->CCHAR = 3; ++ sIRC->CFUN = 3; ++ sIRC->CHELP = 3; ++ sIRC->CINCHAN = 3; ++ sIRC->CINFO = 3; ++ sIRC->CITEM = 3; ++ sIRC->CJAIL = 3; ++ sIRC->CKICK = 3; ++ sIRC->_KILL = 3; ++ sIRC->CLEVEL = 3; ++ sIRC->CLOOKUP = 3; ++ sIRC->CMONEY = 3; ++ sIRC->CMUTE = 3; ++ sIRC->CONLINE = 3; ++ sIRC->CPM = 3; ++ sIRC->CRECONNECT= 3; ++ sIRC->CRELOAD = 3; ++ sIRC->CREVIVE = 3; ++ sIRC->CSAVEALL = 3; ++ sIRC->CSERVERCMD= 3; ++ sIRC->CSHUTDOWN = 3; ++ sIRC->CSPELL = 3; ++ sIRC->CSYSMSG = 3; ++ sIRC->CTELE = 3; ++ sIRC->CTOP = 3; ++ sIRC->CWHO = 3; ++ } + } + + extern void LoadGameObjectModelList(std::string const& dataPath); +@@ -1390,6 +1552,7 @@ void World::SetInitialWorldSettings() + + ///- Initialize config settings + LoadConfigSettings(); ++ TC_LOG_ERROR("misc" "Loading TrinityCore configuration settings...",""); + + ///- Initialize Allowed Security Level + LoadDBAllowedSecurityLevel(); +@@ -1854,6 +2017,9 @@ void World::SetInitialWorldSettings() + LoginDatabase.PExecute("INSERT INTO uptime (realmid, starttime, uptime, revision) VALUES(%u, %u, 0, '%s')", + realmID, uint32(m_startTime), GitRevision::GetFullVersion()); // One-time query + ++ static uint32 autoanc = 1; ++ autoanc = sIRC->autoanc; ++ + m_timers[WUPDATE_WEATHERS].SetInterval(1*IN_MILLISECONDS); + m_timers[WUPDATE_AUCTIONS].SetInterval(MINUTE*IN_MILLISECONDS); + m_timers[WUPDATE_AUCTIONS_PENDING].SetInterval(250); +@@ -1871,6 +2037,8 @@ void World::SetInitialWorldSettings() + + m_timers[WUPDATE_PINGDB].SetInterval(getIntConfig(CONFIG_DB_PING_INTERVAL)*MINUTE*IN_MILLISECONDS); // Mysql ping time in minutes + ++ m_timers[WUPDATE_AUTOANC].SetInterval(autoanc*MINUTE*1000); ++ + //to set mailtimer to return mails every day between 4 and 5 am + //mailtimer is increased when updating auctions + //one second is 1000 -(tested on win system) +@@ -2254,6 +2422,12 @@ void World::Update(uint32 diff) + WorldDatabase.KeepAlive(); + } + ++ if (m_timers[WUPDATE_AUTOANC].Passed()) ++ { ++ m_timers[WUPDATE_AUTOANC].Reset(); ++ SendRNDBroadcastIRC(); ++ } ++ + // update the instance reset times + sInstanceSaveMgr->Update(); + +@@ -2871,6 +3045,20 @@ void World::SendAutoBroadcast() + TC_LOG_DEBUG("misc", "AutoBroadcast: '%s'", msg.c_str()); + } + ++void World::SendRNDBroadcastIRC() ++{ ++ std::string msg; ++ QueryResult result = WorldDatabase.PQuery("SELECT `message` FROM `irc_autoannounce` ORDER BY RAND() LIMIT 1"); ++ if (!result) ++ return; ++ msg = result->Fetch()[0].GetString(); ++ ++ sWorld->SendWorldText(6612,msg.c_str()); ++ std::string ircchan = "#"; ++ ircchan += sIRC->anchn; ++ sIRC->Send_IRC_Channel(ircchan, sIRC->MakeMsg("\00304,08\037/!\\\037\017\00304 Automatic System Message \00304,08\037/!\\\037\017 %s", "%s", msg.c_str()), true); ++} ++ + void World::UpdateRealmCharCount(uint32 accountId) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_COUNT); +diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h +index 7a3e56c..90e20dc 100644 +--- a/src/server/game/World/World.h ++++ b/src/server/game/World/World.h +@@ -76,6 +76,7 @@ enum WorldTimers + WUPDATE_EVENTS, + WUPDATE_CLEANDB, + WUPDATE_AUTOBROADCAST, ++ WUPDATE_AUTOANC, + WUPDATE_MAILBOXQUEUE, + WUPDATE_DELETECHARS, + WUPDATE_AHBOT, +@@ -651,6 +652,9 @@ class World + void AddSession(WorldSession* s); + void SendAutoBroadcast(); + bool RemoveSession(uint32 id); ++ ++ void SendRNDBroadcastIRC(); ++ + /// Get the number of current active sessions + void UpdateMaxSessionCounters(); + const SessionMap& GetAllSessions() const { return m_sessions; } +diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt +index a15b6f8..337e3d9 100644 +--- a/src/server/scripts/CMakeLists.txt ++++ b/src/server/scripts/CMakeLists.txt +@@ -118,6 +118,7 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/game/Spells + ${CMAKE_SOURCE_DIR}/src/server/game/Spells/Auras + ${CMAKE_SOURCE_DIR}/src/server/game/Texts ++ ${CMAKE_SOURCE_DIR}/src/server/game/TriniChat + ${CMAKE_SOURCE_DIR}/src/server/game/Tickets + ${CMAKE_SOURCE_DIR}/src/server/game/Tools + ${CMAKE_SOURCE_DIR}/src/server/game/Warden +diff --git a/src/server/scripts/Commands/cs_message.cpp b/src/server/scripts/Commands/cs_message.cpp +index 69ff04f..9fdfad5 100644 +--- a/src/server/scripts/Commands/cs_message.cpp ++++ b/src/server/scripts/Commands/cs_message.cpp +@@ -28,6 +28,7 @@ EndScriptData */ + #include "Language.h" + #include "Player.h" + #include "ObjectMgr.h" ++#include "IRCClient.h" + + class message_commandscript : public CommandScript + { +@@ -123,6 +124,12 @@ public: + name = session->GetPlayer()->GetName(); + + sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), args); ++ //send to irc ++ if(sIRC->_staffLink == 1) ++ { ++ std::string sMsg = "["+name+"]: "+args; ++ sIRC->Send_IRC_Channel(sIRC->_staffChan, sMsg, false, "PRIVMSG"); ++ } + return true; + } + // global announce +@@ -131,6 +138,13 @@ public: + if (!*args) + return false; + ++ if ((sIRC->BOTMASK & 256) != 0 && sIRC->anchn.size() > 0) ++ { ++ std::string ircchan = "#"; ++ ircchan += sIRC->anchn; ++ sIRC->Send_IRC_Channel(ircchan, sIRC->MakeMsg("\00304,08\037/!\\\037\017\00304 System Message \00304,08\037/!\\\037\017 %s", "%s", args), true); ++ } ++ + char buff[2048]; + sprintf(buff, handler->GetTrinityString(LANG_SYSTEMMESSAGE), args); + sWorld->SendServerMessage(SERVER_MSG_STRING, buff); +@@ -150,6 +164,12 @@ public: + { + if (!*args) + return false; ++ if ((sIRC->BOTMASK & 256) != 0 && sIRC->anchn.size() > 0) ++ { ++ std::string ircchan = "#"; ++ ircchan += sIRC->anchn; ++ sIRC->Send_IRC_Channel(ircchan, sIRC->MakeMsg("\00304,08\037/!\\\037\017\00304 Global Notify \00304,08\037/!\\\037\017 %s", "%s", args), true); ++ } + + std::string str = handler->GetTrinityString(LANG_GLOBAL_NOTIFY); + str += args; +diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt +index 535383a..8a2c945 100644 +--- a/src/server/worldserver/CMakeLists.txt ++++ b/src/server/worldserver/CMakeLists.txt +@@ -78,11 +78,14 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Item/Container + ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Object + ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Object/Updates ++ ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Pet ++ ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Player + ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Unit + ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Vehicle + ${CMAKE_SOURCE_DIR}/src/server/game/Globals + ${CMAKE_SOURCE_DIR}/src/server/game/Grids + ${CMAKE_SOURCE_DIR}/src/server/game/Grids/Cells ++ ${CMAKE_SOURCE_DIR}/src/server/game/Groups + ${CMAKE_SOURCE_DIR}/src/server/game/Handlers + ${CMAKE_SOURCE_DIR}/src/server/game/Instances + ${CMAKE_SOURCE_DIR}/src/server/game/Loot +@@ -95,7 +98,9 @@ include_directories( + ${CMAKE_SOURCE_DIR}/src/server/game/Scripting + ${CMAKE_SOURCE_DIR}/src/server/game/Server + ${CMAKE_SOURCE_DIR}/src/server/game/Server/Protocol ++ ${CMAKE_SOURCE_DIR}/src/server/game/Spells + ${CMAKE_SOURCE_DIR}/src/server/game/Spells/Auras ++ ${CMAKE_SOURCE_DIR}/src/server/game/TriniChat + ${CMAKE_SOURCE_DIR}/src/server/game/Weather + ${CMAKE_SOURCE_DIR}/src/server/game/World + ${CMAKE_SOURCE_DIR}/src/server/shared +diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp +index 9936143..c3bbbd4 100644 +--- a/src/server/worldserver/CommandLine/CliRunnable.cpp ++++ b/src/server/worldserver/CommandLine/CliRunnable.cpp +@@ -28,6 +28,7 @@ + #include "CliRunnable.h" + #include "Log.h" + #include "Util.h" ++#include "IRCClient.h" + + #if PLATFORM != PLATFORM_WINDOWS + #include +@@ -108,6 +109,14 @@ void commandFinished(void*, bool /*success*/) + printf("TC> "); + fflush(stdout); + } ++//Reconnect TriniChat to IRC server via CLI command ++bool HandleIRCRelogCommand(bool, const char *args) ++{ ++ TC_LOG_ERROR("misc" "TriniChat is dropping from IRC Server",""); ++ sIRC->ResetIRC(); ++ TC_LOG_ERROR("misc" "TriniChat is reconnecting to IRC Server",""); ++ return true; ++} + + #ifdef linux + // Non-blocking keypress detector, when return pressed, return 1, else always return 0 +diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp +index 53c5f25..192e465 100644 +--- a/src/server/worldserver/Main.cpp ++++ b/src/server/worldserver/Main.cpp +@@ -49,8 +49,10 @@ + #include "WorldSocketMgr.h" + #include "DatabaseLoader.h" + #include "AppenderDB.h" ++#include "../../game/TriniChat/IRCClient.h" + + using namespace boost::program_options; ++#include "IRCClient.h" + + #ifndef _TRINITY_CORE_CONFIG + #define _TRINITY_CORE_CONFIG "worldserver.conf" +@@ -216,6 +218,15 @@ extern int main(int argc, char** argv) + soapThread = new std::thread(TCSoapThread, sConfigMgr->GetStringDefault("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetIntDefault("SOAP.Port", 7878))); + } + ++ // Start up TriniChat ++ boost::thread* triniChatThread = nullptr; ++ if (sIRC->Active == 1) ++ { ++ triniChatThread = new boost::thread(TrinityChatThread); ++ } ++ else ++ TC_LOG_ERROR("misc", "*** TriniChat Is Disabled. *"); ++ + // Launch the worldserver listener socket + uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD)); + std::string worldListener = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0"); +@@ -270,6 +281,18 @@ extern int main(int argc, char** argv) + + delete raAcceptor; + ++ // Clean TrinityChat ++ if (triniChatThread != nullptr) ++ { ++ // for some reason on win32 "sIRC->Active && !World::IsStopped()" fail to go false in time and the thread is stalled ++ // so we make sure the condition to live will fail from here, since we are shutting down... ++ sIRC->Active = 0; ++ triniChatThread->join(); ++ delete triniChatThread; ++ } ++ ++ if (raAcceptor != nullptr) ++ delete raAcceptor; + ///- Clean database before leaving + ClearOnlineAccounts(); + +@@ -277,6 +300,7 @@ extern int main(int argc, char** argv) + + TC_LOG_INFO("server.worldserver", "Halting process..."); + ++ + ShutdownCLIThread(cliThread); + + OpenSSLCrypto::threadsCleanup(); +@@ -456,6 +480,17 @@ bool StartDB() + if (!loader.Load()) + return false; + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + ///- Get the realm Id from the configuration file + realmID = sConfigMgr->GetIntDefault("RealmID", 0); + if (!realmID) +diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist +index 03d527b..f8e61d9 100644 +--- a/src/server/worldserver/worldserver.conf.dist ++++ b/src/server/worldserver/worldserver.conf.dist +@@ -2639,9 +2639,6 @@ CharDelete.Heroic.MinLevel = 0 + + CharDelete.KeepDays = 30 + +-# +-################################################################################################### +- + ################################################################################################### + # CUSTOM SERVER OPTIONS + # +@@ -3332,6 +3329,8 @@ AuctionHouseBot.Buyer.Recheck.Interval = 20 + # + ################################################################################################### + ++ ++ + ################################################################################################### + # LOGGING SYSTEM SETTINGS + # +@@ -3528,6 +3527,279 @@ PacketSpoof.BanDuration = 86400 + # + ################################################################################################### + ++######################################################### ++# TriniChat IRC BOT For Trinity Core Configuration File # ++######################################################### ++# irc.active ++# Enable TriniChat Addon ++# Default: 1 - Enable ++# 0 - Disable ++# ++irc.active = 1 ++ ++################################################## ++# irc.icc ++# IRC connect code ++# Default: 001 - Welcome To Network msg ++# 375 - Beginning Of MOTD ++# 376 - End Of MOTD ++# ++irc.icc = 001 ++ ++################################################## ++# irc.host ++# IRC server to have TriniChat connect to ++# ++irc.host = "irc.xxxx.net" ++ ++################################################## ++# irc.port ++# IRC server port to use ++# ++irc.port = "1337" ++ ++################################################## ++# irc.user ++# The username to have TriniChat use to connect to the IRC server ++# irc.nick ++# IRC nickname to be used by the bot ++# irc.pass ++# The password to be used to identify to NickServ (IRC NickName Enforcement Services) ++# ++irc.user = "Trini_Chat" ++irc.nick = "Trini_Chat" ++irc.pass = "ServicesPass" ++ ++################################################## ++# irc.auth ++# IRC Authentication Method ++# Default: 0 - Disable ++# 1 - NickServ - Normal Method - PRIVMSG NickServ :IDENTIFY Password ++# 2 - NickServ - Alternate Method To Identify To A Different Nick - PRIVMSG NickServ :IDENTIFY irc.auth.nick Password ++# 3 - QuakeNet - Normal Method - PRIVMSG Q@CServe.quakenet.org :AUTH irc.nick Password ++# 4 - QuakeNet - Alternate Method To Identify To A Different Nick - PRIVMSG Q@CServe.quakenet.org :AUTH irc.auth.nick Password ++# irc.auth.nick ++# IRC Nickname to use if Auth method 2 or 4 is used ++# ++irc.auth = 0 ++irc.auth.nick = "AuthNick" ++ ++################################################## ++# irc.ldef ++# Leave a defined IRC channel on server connect ++# Default: 0 - Disable ++# 1 - Enable ++# irc.defchan ++# IRC channel to leave on server connect if irc.ldef is on ++# ++irc.ldef = 0 ++irc.defchan = "lobby" ++ ++################################################## ++# irc.wct ++# Time to wait before (re)attemptimg connection to IRC server ++# Default: 30000 - (30 Seconds) ++# irc.maxattempt ++# Maximum attempts to try IRC server ++# Default: 20 ++# ++irc.wct = 30000 ++irc.maxattempt = 20 ++ ++################################################## ++# irc.auto.announce ++# Time to wait in Minutes to announce random messages from database. ++# Default: 30 - (30 Minutes) ++# ++irc.auto.announce = 30 ++ ++################################################## ++# irc.autojoin_kick ++# Autojoin IRC channel if kicked ++# Default: 1 - Enable ++# 0 - Disable ++# ++irc.autojoin_kick = 1 ++ ++################################################## ++# irc.command_prefix ++# IRC command prefix ++# Example: (.)online all ++# ++irc.command_prefix = "\" ++ ++################################################## ++# irc.joinmsg ++# irc.rstmsg ++# irc.kickmsg ++# TriniChat bot join/restart/kick messages ++# ++irc.joinmsg = "Trinity Core With TriniChat $Ver Is Up And Running! Command Trigger Is: $Trigger" ++irc.rstmsg = "TriniChat Is Restarting, I Will Be Right Back." ++irc.kickmsg = "Do Not Kick Me Again, Severe Actions Will Be Taken!" ++ ++################################################## ++# irc.chan_# ++# wow.chan_# ++# IRC and WOW channels to link. Leave # out of IRC channel. Both channels _ARE_ case sensitive ++# NOTE: you can have more than 1 IRC channel linked to wow channels, as in example: ++# Example: irc.chan_1 = "Trinity" ++# irc.chan_2 = "trinity2" ++# wow.chan_1 = "world" ++# wow.chan_2 = "LookingForGroup" ++# irc.pass_# ++# Passwords for said IRC channels (IRC server side) ++# Example: ++# irc.pass_1 = "password" password for irc channel #1 ++# irc.pass_2 = "password2" password for irc channel #2 ++irc.chan_1 = "ircchan" ++wow.chan_1 = "world" ++irc.pass_1 = "pass" ++ ++################################################## ++# irc.staff_link ++# if set to 1 will link gm name announce to irc channel ++# Default: 0 ++# irc.staff_chan ++# Print IRC messages to GM Name Announce and Gm Name Announce to IRC Channel ++# Default: "staff" ++# irc.ignore_bots ++# enter nicks of bots to ignore seprated by commas ++# Default: "" ++# ++irc.staff_link = 0 ++irc.staff_chan = "staff" ++irc.ignore_bots = "" ++ ++################################################## ++# irc.StatusChannel ++# Channel Name To Display Status Messages In (AuctionHouse, Levels, Deaths, Etc) ++# Default: "" ++# irc.AnnounceChannel ++# Channel Name To Display Announcements In (Announces, Notifies, Event) ++# Default: "" ++# irc.Tickets ++# Channel Name To Display Ticket Announcment in (Create, Edit, Closed, lag Reports) ++# Default: "" ++# Note: Botmask : Enable Ticket Announment must be activ ++# ++irc.StatusChannel = "" ++irc.StatusChannelPW = "" ++irc.AnnounceChannel = "" ++irc.AnnounceChannelPW = "" ++irc.Tickets = "" ++irc.TicketsPW = "" ++ ++################################################## ++# irc.op_gm_login ++# Op The GM In All Channels The Bot Is On When They Log In To TriniChat ++# Default: 0 - Disable ++# 1 - Enable ++# irc.op_gm_level ++# The Minimum GM Level Required To Have The Bot Op The User ++# Default: 3 - GM Level 3 ++# ++irc.op_gm_login = 0 ++irc.op_gm_level = 3 ++ ++################################################## ++# irc.ajoin (Experimental/Under Development) ++# Force players to autojoin a WOW in game channel ++# Atleast one player must be in the channel on server start, and atleast one person online for invite to work ++# Default: 0 - Disable ++# 1 - Enable ++# irc.ajchan ++# Channel to join if above is Enabled. ++# ++irc.ajoin = 1 ++irc.ajchan = "world" ++ ++################################################## ++# irc.online.result ++# Maximum number of results per line for the online command ++# ++irc.online.result = 30 ++ ++################################################## ++# chat.*** (Defineable Strings) (maybe more in future) ++# wow_* - String is displayed in IRC channel ++# irc_* - String is displayed in WOW channel ++# Options: $Name, $Level, $Class, $Msg, $GM (not all options work in every string) ++# ++chat.wow_irc = "[$Name($Level)[$Class]] $Msg" ++chat.irc_wow = "[$Name]: $Msg" ++chat.join_wow = "12>>04 $GM$Name Joined The $Channel Channel!" ++chat.join_irc = "[$Name]: Has Joined IRC!" ++chat.leave_wow = "12<<04 $GM$Name Left The $Channel Channel!" ++chat.leave_irc = "[$Name]: Has Left IRC!" ++chat.change_nick = "<> $Name Is Now Known As $NewName!" ++ ++################################################## ++# Botmask ++# This defines what the bot announces, if its 0 everything is disabled ++# simply add the values of the elements you want to create this mask. ++# Example: WoW join/leaves are 1 and IRC join/leaves are 2, if you want both of these active then the BotMask is 3. ++# (1)Display WoW Chan Join/Leaves In IRC ++# (2)Display IRC Chan Join/Leaves/NickChanges In WoW ++# (4)Display Unknown Command Message When Trigger Is Used And No Command Exists ++# (8)Announce Security Level > 0 As GM At Login ++# (16)Announce GM In GM ON State AS GM At Login ++# (32)Return Errors To Notice. (If disabled then default is Private Message) ++# (64)Display WoW Status Messages (Levels/Deaths) ++# (128)Display Nick Changes From IRC In WOW ++# (256)Display WoW Announces/Notifies In IRC ++# (512)Do Not Let Players Use Commands On Higher GM Level Players ++# (1024)Enable AuctionHouse Announcements !!DISSABLED!! ++# ++Botmask = 1023 ++ ++################################################## ++# irc.gmlog ++# Minimum GM level to not show login/pass info in IRC logs ++# irc.logfile.prefix ++# The prefix of the IRC logfile. Directories can be added here. ++# Example: "IRC/IRC_" outputs IRC_YYYY-MM-DD.log to the IRC subdirectory in your logs dir ++# irc.logchannel ++# Specefies the channel where logged output gets broadcasted. ++# Default: "" (none) ++# irc.logchannelpw ++# The password for the log channel. ++# irc.logmask ++# Bitmask, see Botmask for explanation. Determines what gets logged to the logchannel. ++# (0) Do not broadcast into logchannel. ++# (1) Broadcast IRC Commands into logchannel. ++# (2) Broadcast Ingame GM Commands into logchannel. ++# (3) Broadcast both IRC Commands and GM Commands into logchannel. ++irc.gmlog = 1 ++irc.logfile.prefix = "IRC_" ++irc.logchannel = "testlog" ++irc.logchannelpw = "" ++irc.logmask = 3 ++ ++################################################## ++# irc.fun.games (Experimental/Under Development) ++# Enable TriniChat Games ++# Default: 0 - Disable ++# 1 - Enable ++# ++irc.fun.games = 0 ++ ++################################################## ++# irc.gm# ++# GM tag to append to (GM onjoin / online command) IRC color codes are acceptable ++# ++irc.gm1 = "[VIP]" ++irc.gm2 = "[Donator]" ++irc.gm3 = "[Bug Tracker]" ++irc.gm4 = "[Moderator]" ++irc.gm5 = "[Game Master]" ++irc.gm6 = "[Admin]" ++irc.gm7 = "[Developer]" ++irc.gm8 = "[Owner]" ++ ++# ++################################################################################################### \ No newline at end of file diff --git a/trinity_jail.diff b/trinity_jail.diff new file mode 100644 index 0000000..214536c --- /dev/null +++ b/trinity_jail.diff @@ -0,0 +1,1154 @@ + sql/TrinityCore-Patches/TrinityJail/auth.jail.sql | 13 + + .../TrinityJail/characters_jail.sql | 66 ++++ + .../TrinityJail/world_command_Deutsch.sql | 7 + + .../TrinityJail/world_command_English.sql | 7 + + .../TrinityJail/world_command_Francais.sql | 7 + + .../TrinityJail/world_trinity_string.sql | 39 +++ + .../Database/Implementation/CharacterDatabase.cpp | 1 + + .../Database/Implementation/CharacterDatabase.h | 1 + + src/server/game/Accounts/RBAC.h | 5 +- + src/server/game/Entities/Player/Player.cpp | 187 +++++++++- + src/server/game/Entities/Player/Player.h | 19 + + src/server/game/Globals/ObjectMgr.cpp | 115 ++++++ + src/server/game/Globals/ObjectMgr.h | 23 ++ + src/server/game/Miscellaneous/Language.h | 36 ++ + src/server/game/Scripting/ScriptLoader.cpp | 2 + + src/server/game/World/World.cpp | 4 + + src/server/scripts/Commands/cs_jail.cpp | 388 +++++++++++++++++++++ + 17 files changed, 917 insertions(+), 3 deletions(-) + create mode 100644 sql/TrinityCore-Patches/TrinityJail/auth.jail.sql + create mode 100644 sql/TrinityCore-Patches/TrinityJail/characters_jail.sql + create mode 100644 sql/TrinityCore-Patches/TrinityJail/world_command_Deutsch.sql + create mode 100644 sql/TrinityCore-Patches/TrinityJail/world_command_English.sql + create mode 100644 sql/TrinityCore-Patches/TrinityJail/world_command_Francais.sql + create mode 100644 sql/TrinityCore-Patches/TrinityJail/world_trinity_string.sql + create mode 100644 src/server/scripts/Commands/cs_jail.cpp + +diff --git a/sql/TrinityCore-Patches/TrinityJail/auth.jail.sql b/sql/TrinityCore-Patches/TrinityJail/auth.jail.sql +new file mode 100644 +index 0000000..2634cfc +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/auth.jail.sql +@@ -0,0 +1,13 @@ ++DELETE FROM `rbac_permissions` WHERE `id` IN (901,902,903,904); ++INSERT INTO `rbac_permissions` (`id`, `name`) VALUES ++(901, 'Command: jail player'), ++(902, 'Command: jail info'), ++(903, 'Command: jail release'), ++(904, 'Command: jail reload'); ++ ++DELETE FROM `rbac_linked_permissions` WHERE `linkedid` IN (901,902,903,904); ++INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES ++(193, 901), ++(195, 902), ++(193, 903), ++(193, 904); +\ No newline at end of file +diff --git a/sql/TrinityCore-Patches/TrinityJail/characters_jail.sql b/sql/TrinityCore-Patches/TrinityJail/characters_jail.sql +new file mode 100644 +index 0000000..c8c080a +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/characters_jail.sql +@@ -0,0 +1,66 @@ ++/* ++MySQL Data Transfer ++Source Host: localhost ++Source Database: characters ++Target Host: localhost ++Target Database: characters ++Date: 25.04.2010 00:00:00 ++*/ ++DROP TABLE IF EXISTS `jail`; ++SET FOREIGN_KEY_CHECKS=0; ++-- ---------------------------- ++-- Table structure for jail ++-- ---------------------------- ++CREATE TABLE `jail` ( ++ `guid` int(11) unsigned NOT NULL COMMENT 'GUID of the jail brother', ++ `char` varchar(13) NOT NULL COMMENT 'Jailed charname', ++ `release` int(11) unsigned NOT NULL COMMENT 'Release time for the char', ++ `amnestietime` int(11) NOT NULL, ++ `reason` varchar(255) NOT NULL COMMENT 'Reason for the jail', ++ `times` int(11) unsigned NOT NULL COMMENT 'How many times this char already was jailed', ++ `gmacc` int(11) unsigned NOT NULL COMMENT 'Used GM acc to jail this char', ++ `gmchar` varchar(13) NOT NULL COMMENT 'Used GM char to jail this char', ++ `lasttime` timestamp NOT NULL default '0000-00-00 00:00:00' on update CURRENT_TIMESTAMP COMMENT 'Last time jailed', ++ `duration` int(11) unsigned NOT NULL default '0' COMMENT 'Duration of the jail', ++ PRIMARY KEY (`guid`) ++) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Jail table for Trinitycore by WarHead Edited by SPGM'; ++ ++-- ---------------------------- ++-- Records ++-- ---------------------------- ++DROP TABLE IF EXISTS `jail_conf`; ++SET FOREIGN_KEY_CHECKS=0; ++-- ---------------------------- ++-- Table structure for jail_conf ++-- ---------------------------- ++ ++CREATE TABLE `jail_conf` ( ++ `id` int(11) NOT NULL auto_increment, ++ `obt` varchar(50) default NULL, ++ `jail_conf` int(11) default NULL, ++ `jail_tele` float default NULL, ++ `help_ger` varchar(255) character set latin1 default '', ++ `help_enq` varchar(255) default '', ++ PRIMARY KEY (`id`) ++) ENGINE=MyISAM AUTO_INCREMENT=18 DEFAULT CHARSET=utf8; ++ ++-- ---------------------------- ++-- Records ++-- ---------------------------- ++INSERT INTO `jail_conf` VALUES ('1', 'm_jailconf_max_jails', '3', null, 'Hier legst ihre fest nach wie fielen Jails der Char gel�scht werden \r\nStandart = 3\r\n ', 'How many time the characters can be Jailed before being deleted\r\nDefault = 3'); ++INSERT INTO `jail_conf` VALUES ('2', 'm_jailconf_max_duration', '672', null, 'Hier legst ihre fest wie hoch der maximale Jail Time in Stunden \r\nStandart = 672\r\n', 'Here put how high the maximum Jail Time in hours\r\nDefault = 672'); ++INSERT INTO `jail_conf` VALUES ('3', 'm_jailconf_min_reason', '25', null, 'Hier legst ihre die minimalen Zeichen fest die als Grund angeben m�sst \r\nStandart = 25\r\n\r\n', 'Here list how many characters are needed as the minimum reason\r\nDefault = 25'); ++INSERT INTO `jail_conf` VALUES ('4', 'm_jailconf_warn_player', '1', null, 'Hier legst ihre fest wann der Char gewarnt wirt bevor er gel�scht wird \r\nStandart = 1\r\n', 'How many time to warn the player before jailing them\r\nDefault = 1'); ++INSERT INTO `jail_conf` VALUES ('5', 'm_jailconf_amnestie', '180', null, 'Hier legst ihre in Tagen fest wann der Jail Status auf 0 zur�ckgesetzt wird \r\nStandart = 180 Tage (das entspricht ca. � Jahr) \r\n 0 Tage (Aus)\r\n', 'This is how many in days when the Jail is status is set to 0\r\nStandard = 180 days (approximately � year)\r\n 0 days (off)'); ++INSERT INTO `jail_conf` VALUES ('6', 'm_jailconf_ally_x', null, '31.7282', 'Teleport Alliance X Achse \r\nStandart = 31,7282\r\n', 'Teleport Alliance X Axis \r\nStandart = 31,7282\r\n'); ++INSERT INTO `jail_conf` VALUES ('7', 'm_jailconf_ally_y', null, '135.794', 'Teleport Alliance Y Achse \r\nStandart = 135,794\r\n', 'Teleport Alliance Y Axis \r\nStandart = 135,794\r\n'); ++INSERT INTO `jail_conf` VALUES ('8', 'm_jailconf_ally_z', null, '-40.0508', 'Teleport Alliance Z Achse \r\nStandart = -40,0508', 'Teleport Alliance Z Axis \r\nStandart = -40,0508'); ++INSERT INTO `jail_conf` VALUES ('9', 'm_jailconf_ally_o', null, '4.73516', 'Teleport Alliance blickrichtung\r\nStandart = 4,73516', 'Teleport Alliance Orientation\r\nStandart = 4,73516'); ++INSERT INTO `jail_conf` VALUES ('10', 'm_jailconf_ally_m', '35', null, 'Teleport Alliance Mape\r\nStandart = 35', 'Teleport Alliance Map\r\nStandart = 35'); ++INSERT INTO `jail_conf` VALUES ('11', 'm_jailconf_horde_x', null, '2179.85', 'Teleport Horde X Achse \r\nStandart = \r\n', 'Teleport Horde X Axis \r\nStandart = \r\n'); ++INSERT INTO `jail_conf` VALUES ('12', 'm_jailconf_horde_y', null, '-4763.96', 'Teleport Horde Y Achse \r\nStandart = -4763,96', 'Teleport Horde Y Axis \r\nStandart = -4763,96'); ++INSERT INTO `jail_conf` VALUES ('13', 'm_jailconf_horde_z', null, '54.911', 'Teleport Horde Z Achse \r\nStandart = 54,911', 'Teleport Horde Z Axis \r\nStandart = 54,911'); ++INSERT INTO `jail_conf` VALUES ('14', 'm_jailconf_horde_o', null, '4.44216', 'Teleport Horde blickrichtung\r\nStandart = 4,44216', 'Teleport Horde Orientation\r\nStandart = 4,44216'); ++INSERT INTO `jail_conf` VALUES ('15', 'm_jailconf_horde_m', '1', null, 'Teleport Horde Mape\r\nStandart = 1', 'eleport Horde Map\r\nStandart = 1'); ++INSERT INTO `jail_conf` VALUES ('16', 'm_jailconf_ban', '0', null, 'Nach wie vielen Jail soll der Account Gebant werden\r\nStandart = 0 (aus)\r\n', 'After how many jails the account will be Banned\r\nDefault = 0 (off)'); ++INSERT INTO `jail_conf` VALUES ('17', 'm_jailconf_radius', '10', null, 'Legt den Bewegung Radius in Metern waren des Jails fest\r\nStandart = 10\r\n', 'Sets the range of motion in meters of the jail\r\nDefault = 10'); +diff --git a/sql/TrinityCore-Patches/TrinityJail/world_command_Deutsch.sql b/sql/TrinityCore-Patches/TrinityJail/world_command_Deutsch.sql +new file mode 100644 +index 0000000..5d2589c +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/world_command_Deutsch.sql +@@ -0,0 +1,7 @@ ++/* World Command Deutsch */ ++DELETE FROM `command` WHERE name IN ('jail','jailinfo','unjail','jailreload'); ++INSERT INTO `command` (name, permission, help) VALUES ++('jail', 1, 'Syntax: .jail Charakter Stunden Grund\nBuchtet den \'Charakter\' f�r \'Stunden\' aus dem \'Grund\' ein.'), ++('jailinfo', 0, 'Syntax: .jailinfo\nZeigt dir deinen Knast-Status an.'), ++('unjail', 1, 'Syntax: .unjail Charakter\nEntl��t den \'Charakter\' aus dem Knast.'), ++('jailreload', 3, 'Syntax: .jailreload\nL�dt die Jail-Konfiguration neu.'); +diff --git a/sql/TrinityCore-Patches/TrinityJail/world_command_English.sql b/sql/TrinityCore-Patches/TrinityJail/world_command_English.sql +new file mode 100644 +index 0000000..03333b5 +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/world_command_English.sql +@@ -0,0 +1,7 @@ ++/* World Command English */ ++DELETE FROM `command` WHERE name IN ('jail player','jail info','jail release','jail reload'); ++INSERT INTO `command` (name, permission, help) VALUES ++('jail player', 1, 'Syntax: .jail player character hours reason\nJailed the \'character\' for \'hours\' with the \'reason\'.'), ++('jail info', 0, 'Syntax: .jailinfo\nShows your jail status.'), ++('jail release', 1, 'Syntax: .release character\nRealeases the \'character\' out of the jail.'), ++('jail reload', 3, 'Syntax: .jailreload\nLoads the jail config new.'); +diff --git a/sql/TrinityCore-Patches/TrinityJail/world_command_Francais.sql b/sql/TrinityCore-Patches/TrinityJail/world_command_Francais.sql +new file mode 100644 +index 0000000..9c9bdfc +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/world_command_Francais.sql +@@ -0,0 +1,7 @@ ++/* World Command Francais */ ++DELETE FROM `command` WHERE name IN ('jail','jailinfo','unjail','jailreload'); ++INSERT INTO `command` (name, permission, help) VALUES ++('jail', 1, 'Syntaxe: .jail #personage #heures #raison\nEmprisonner un \'personnage\' pendant \'heures\' pour la \'raison\'.'), ++('jailinfo', 0, 'Syntaxe: .jailinfo\nVoir le statut de vos emprisonnements.'), ++('unjail', 1, 'Syntaxe: .unjail #personnages\nLib�rer le \'personnage\' de la prison.'), ++('jailreload', 3, 'Syntaxe: .jailreload\nRecharger la configuration du Jail.'); +diff --git a/sql/TrinityCore-Patches/TrinityJail/world_trinity_string.sql b/sql/TrinityCore-Patches/TrinityJail/world_trinity_string.sql +new file mode 100644 +index 0000000..5875afc +--- /dev/null ++++ b/sql/TrinityCore-Patches/TrinityJail/world_trinity_string.sql +@@ -0,0 +1,39 @@ ++/* World trinity_string */ ++SET NAMES 'utf8'; ++DELETE FROM trinity_string WHERE `entry` IN (950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983); ++ ++INSERT INTO `trinity_string` VALUES ++('950', 'You are jailed by \'%s\' for %u hour(s)!', null, 'Vous tes emprisonn par \'%s\' pour %u heures!', 'Du wurdest von \'%s\' f�r %u Stunde(n) eingebuchtet!', null, null, null, null, null), ++('951', '\'%s\' wrote this as reason: \'%s\'', null, '%s a crit ceci comme tant la raison de votre emprisonnement: %s.', '\'%s\' gab dies als Grund an: \'%s\'', null, null, null, null, null), ++('952', '\'%s\' was jailed by you for %u hour(s).', null, 'Vous avez emprisonn %s pour %u heures!', '\'%s\' wurde von dir f�r %u Stunde(n) eingebuchtet.', null, null, null, null, null), ++('953', 'You was released out of the jail by %s.', null, 'Vous avez t liber de la prison par %s.', '\'%s\' hat dich aus dem Knast entlassen.', null, null, null, null, null), ++('954', 'You have released %s out of the jail.', null, 'Vous avez liber \'%s\' de la prison.', 'Du hast \'%s\' aus dem Knast geholt.', null, null, null, null, null), ++('955', 'No reason given or reason is < %u chars!', null, 'Aucune raison d\\\'emprisonnement donne ou la raison est < %u personnages.', 'Du hast keinen Grund angegeben, oder der Grund ist < %u Zeichen!', null, null, null, null, null), ++('956', 'No name given!', null, 'Aucun nom donn!', 'Du hast keinen Namen angegeben!', null, null, null, null, null), ++('957', 'No time given!', null, 'Aucun temps donn!', 'Du hast keine Zeit angegeben!', null, null, null, null, null), ++('958', 'The jailtime must be between 1 and %u hours!', null, 'Le temps d\\\'emprisonnement est situ entre 1 et %u heures!', 'Die Jail-Zeit muss zwischen 1 und %u Std. liegen!', null, null, null, null, null), ++('959', 'The character \'%s\' is not jailed!', null, '\'%s\' n\\\'est pas emprisonn!', 'Der Charakter \'%s\' ist �berhaupt nicht im Knast!', null, null, null, null, null), ++('960', 'Command forbidden for jailed characters!', null, 'Commandes interdites pour les personnages emprisons!', 'Sorry, aber das d?rfen Gefangene nicht!', null, null, null, null, null), ++('961', 'You have %u hour(s) left in the jail.', null, 'Vous avez %u heures attendre avant de quitter la prison.', 'Du musst noch %u Stunde(n) absitzen.', null, null, null, null, null), ++('962', 'You have %u minute(s) left in the jail.', null, 'Vous avez %u minutes attendre avant de quitter la prison.', 'Du musst noch %u Minute(n) absitzen.', null, null, null, null, null), ++('963', 'You\'re a free like a bird! ;-)', null, 'Vous tes libre.', 'Du bist frei wie ein Vogel! ;-)', null, null, null, null, null), ++('964', '%s was %u times jailed and has %u minute(s) left. Last time jailed by %s. Last reason was: \'%s\'', null, '%s a t librde prison, il avait t emprisonn pour %u et a t libr au aprs %u minutes. Il avait t emprisonn par %s, pour la raison suivante: %s', '\'%s\' war bis jetzt %u mal im Knast, und hat noch %u Minute(n) abzusitzen.\n Zuletzt eingebuchtet von: \'%s\'\nLetzter Grund: %s', null, null, null, null, null), ++('965', '\'%s\' was never jailed.', null, '\'%s\' n\\\'a jamais t emprisonn.', '\'%s\' hat eine wei�e Weste.', null, null, null, null, null), ++('966', 'You can\'t jail yourself!', null, 'Vous ne pouvez pas vous emprisonner vous-m me!', 'Du kannst dich nicht selber einbuchten!', null, null, null, null, null), ++('967', 'You can\'t unjail yourself!', null, 'Vous ne pouvez pas vous librer vous m me!', 'So weit kommt es noch, da� Knastbruder sich selber befreien! :-(', null, null, null, null, null), ++('968', '|cffff0000[!!! ATTENTION - IMPORTANT - WARNING !!!\r\n You were already %u times in prison. If you are in Jail for a total of %u times, your character will be deleted\r\n|cffff0000!!! ATTENTION - IMPORTANT - WARNING !!!]', null, '|cffff0000[!!!ATTENTION - ATTENTION - ATTENTION!!!\r\n Vous �tiez d�j� %u fois en prison en %u fois, votre personnage supprim�\r\n|cffff0000!!! ATTENTION - ATTENTION - ATTENTION !!!]', '|cffff0000[!!! ACHTUNG - WICHTIG - WARNUNG !!!\r\n Du warst schon %u mal in Knast beim %u mal wird dein Charakter gel�scht\r\n|cffff0000!!! ACHTUNG - WICHTIG - WARNUNG !!!]', null, null, null, null, null), ++('969', 'The character \'', null, 'Le personnage ', 'Der Charakter \'', null, null, null, null, null), ++('970', '\' was jailed for ', null, ' a t emprisonn pour ', '\' wurde f�r ', null, null, null, null, null), ++('971', ' hour(s) by the GM character \'', null, ' heure(s) par le MJ ', ' Stunde(n) von dem GM-Charakter \'', null, null, null, null, null), ++('972', '\'. The reason is: ', null, '. La raison est: ', '\' eingebuchtet. Der Grund ist: ', null, null, null, null, null), ++('973', 'The jail configuration was reloaded.', null, 'La configuration de jail a t recharge.', 'Die Gef�ngnis-Konfiguration wurde neu geladen.', null, null, null, null, null), ++('974', '>> Trinity Jail config loaded.', null, '>> Configuration du jail charge.', '>> Gef�ngnis-Konfiguration geladen.', null, null, null, null, null), ++('975', 'Can\'t load jail config! Table empty or missed! Use characters_jail.sql!', null, 'Impossible de charger la configuration du jail! Table vide ou innexistante! Appliquez characters_jail.sql!', 'Fehler beim laden der Gef?ngnis-Konfiguration! Der Table \'jail_conf\' ist leer oder nicht vorhanden! Nutze die \'characters_jail.sql\'!', null, null, null, null, null), ++('976', 'Set all jail config settings to default...', null, 'Placez tous les param tres de configuration de prison par d faut.', 'Setze die Konfiguration des Gef?ngnisses auf Standardwerte...', null, null, null, null, null), ++('977', 'The Character \'%s\' is jailed and teleportet into the jail.', null, 'Le personnage \'%s\' est emprisonn et t leport dans la prison.', 'Der Charakter \'%s\' ist ein Knastbruder und wird in den Knast teleportiert.', null, null, null, null, null), ++('978', 'The Character \'%s\' was released out of the jail.', null, 'Le personnage %s est liber de prison.', 'Der Charakter \'%s\' wurde aus dem Knast entlassen.', null, null, null, null, null), ++('979', 'A character with this name doesn\'t exists!', null, 'Il n\'y a aucun personnage portant ce nom.', 'Ein Charakter mit diesem Namen gibt es nicht!', null, null, null, null, null), ++('980', '|cffff0000[!!! ATTENTION - IMPORTANT - WARNING !!!\r\n You were already %u times in prison. If you are in Jail for a total of %u times, your account will be banned!\r\n|cffff0000!!! ATTENTION - IMPORTANT - WARNING !!!]', null, '|cffff0000[!!!ATTENTION - ATTENTION - ATTENTION!!!\r\n Vous avez %u fois en prison en %u fois votre compte sera banni\r\n|cffff0000!!! ATTENTION - ATTENTION - ATTENTION !!!]', '|cffff0000[!!! ACHTUNG - WICHTIG - WARNUNG !!!\r\n Du hast %u mal in Knast beim %u mal wird dein Account gebannt\r\n|cffff0000!!! ACHTUNG - WICHTIG - WARNUNG !!!]', null, null, null, null, null), ++('981', 'Max. jailtimes reached!', null, 'Nombre maximum d\'Jails atteint!', 'Maximale Anzahl an Jails erreicht!', null, null, null, null, null), ++('982', 'Robotron', null, 'Robotron', 'Robotron', null, null, null, null, null), ++('983', 'Your Jail status was reset to 0 ', null, 'Votre statut a �t� Jail � 0 ', 'Dein Jail status wurde auf 0 zur�ck gesatzt', null, null, null, null, null); +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp +index 65dcad6..38c46a7 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp ++++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp +@@ -409,6 +409,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() + PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_GENDER_PLAYERBYTES, "UPDATE characters SET gender = ?, playerBytes = ?, playerBytes2 = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC); ++ PrepareStatement(CHAR_DEL_JAIL, "DELETE FROM jail WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags & ~ ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHARACTER_SOCIAL, "INSERT INTO character_social (guid, friend, flags) VALUES (?, ?, ?)", CONNECTION_ASYNC); +diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h +index d801556..17b0a55 100644 +--- a/src/server/database/Database/Implementation/CharacterDatabase.h ++++ b/src/server/database/Database/Implementation/CharacterDatabase.h +@@ -336,6 +336,7 @@ enum CharacterDatabaseStatements + CHAR_INS_CHAR_INSTANCE, + CHAR_UPD_GENDER_PLAYERBYTES, + CHAR_DEL_CHARACTER_SKILL, ++ CHAR_DEL_JAIL, + CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, + CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS, + CHAR_INS_CHARACTER_SOCIAL, +diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h +index 3c3e838..2da6f26 100644 +--- a/src/server/game/Accounts/RBAC.h ++++ b/src/server/game/Accounts/RBAC.h +@@ -700,7 +700,10 @@ enum RBACPermissions + + // custom permissions 1000+ + // Prepatch by LordPsyan +- // 01 ++ RBAC_PERM_COMMAND_JAIL = 901, ++ RBAC_PERM_COMMAND_JAIL_INFO = 902, ++ RBAC_PERM_COMMAND_JAIL_UN = 903, ++ RBAC_PERM_COMMAND_JAIL_RELOAD = 904, + // 02 + // 03 + // 04 +diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp +index 15c2c47..4722709 100644 +--- a/src/server/game/Entities/Player/Player.cpp ++++ b/src/server/game/Entities/Player/Player.cpp +@@ -314,8 +314,24 @@ std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi) + return ss; + } + ++ + Player::Player(WorldSession* session): Unit(true) + { ++ m_jail_guid = 0; ++ m_jail_char = ""; ++ m_jail_amnestie = false; ++ m_jail_warning = false; ++ m_jail_isjailed = false; ++ m_jail_amnestietime =0; ++ m_jail_release = 0; ++ m_jail_times = 0; ++ m_jail_reason = ""; ++ m_jail_gmacc = 0; ++ m_jail_gmchar = ""; ++ m_jail_lasttime = ""; ++ m_jail_duration = 0; ++ // Jail end ++ + m_speakTime = 0; + m_speakCount = 0; + +@@ -1248,6 +1264,78 @@ void Player::Update(uint32 p_time) + SetCanDelayTeleport(true); + Unit::Update(p_time); + SetCanDelayTeleport(false); ++ if (m_jail_isjailed) ++ { ++ time_t localtime; ++ localtime = time(NULL); ++ ++ if (m_jail_release <= localtime) ++ { ++ m_jail_isjailed = false; ++ m_jail_release = 0; ++ ++ _SaveJail(); ++ ++ sWorld->SendWorldText(LANG_JAIL_CHAR_FREE, GetName().c_str()); ++ ++ CastSpell(this,8690,false); ++ ++ return; ++ } ++ ++ if (m_team == ALLIANCE) ++ { ++ if (GetDistance(sObjectMgr->m_jailconf_ally_x, sObjectMgr->m_jailconf_ally_y, sObjectMgr->m_jailconf_ally_z) > sObjectMgr->m_jailconf_radius) ++ { ++ TeleportTo(sObjectMgr->m_jailconf_ally_m, sObjectMgr->m_jailconf_ally_x, ++ sObjectMgr->m_jailconf_ally_y, sObjectMgr->m_jailconf_ally_z, sObjectMgr->m_jailconf_ally_o); ++ return; ++ } ++ } ++ else ++ { ++ if (GetDistance(sObjectMgr->m_jailconf_horde_x, sObjectMgr->m_jailconf_horde_y, sObjectMgr->m_jailconf_horde_z) > sObjectMgr->m_jailconf_radius) ++ { ++ TeleportTo(sObjectMgr->m_jailconf_horde_m, sObjectMgr->m_jailconf_horde_x, ++ sObjectMgr->m_jailconf_horde_y, sObjectMgr->m_jailconf_horde_z, sObjectMgr->m_jailconf_horde_o); ++ return; ++ } ++ ++ } ++ } ++ ++ if (m_jail_warning == true) ++ { ++ m_jail_warning = false; ++ ++ if (sObjectMgr->m_jailconf_warn_player == m_jail_times || sObjectMgr->m_jailconf_warn_player <= m_jail_times) ++ { ++ if ((sObjectMgr->m_jailconf_max_jails-1 == m_jail_times-1) && sObjectMgr->m_jailconf_ban-1) ++ { ++ ChatHandler(GetSession()).PSendSysMessage(LANG_JAIL_WARNING_BAN, m_jail_times , sObjectMgr->m_jailconf_max_jails-1); ++ } ++ else ++ { ++ ChatHandler(GetSession()).PSendSysMessage(LANG_JAIL_WARNING, m_jail_times , sObjectMgr->m_jailconf_max_jails); ++ } ++ ++ } ++ return; ++ } ++if (m_jail_amnestie == true && sObjectMgr->m_jailconf_amnestie > 0) ++{ ++ m_jail_amnestie =false; ++ time_t localtime; ++ localtime = time(NULL); ++ ++ if (localtime > m_jail_amnestietime) ++ { ++ CharacterDatabase.PExecute("DELETE FROM `jail` WHERE `guid` = '%u'",GetGUID().GetCounter()); ++ ChatHandler(GetSession()).PSendSysMessage(LANG_JAIL_AMNESTII); ++ } ++ return; ++} ++ + + time_t now = time(NULL); + +@@ -1457,9 +1545,19 @@ void Player::Update(uint32 p_time) + } + + if (m_deathState == JUST_DIED) +- KillPlayer(); ++// KillPlayer(); ++ { + +- if (m_nextSave > 0) ++ // Prevent death of jailed players ++ if (!m_jail_isjailed) KillPlayer(); ++ else ++ { ++ m_deathState = ALIVE; ++ RegenerateAll(); ++ } ++ } ++ ++ if (m_nextSave > 0 && !m_jail_isjailed) + { + if (p_time >= m_nextSave) + { +@@ -17579,9 +17677,78 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) + + _LoadEquipmentSets(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS)); + ++ // Loads the jail datas and if jailed it corrects the position to the corresponding jail ++ _LoadJail(); ++ + return true; + } + ++void Player::_LoadJail(void) ++{ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT * FROM `jail` WHERE `guid`='%u' LIMIT 1", GetGUID().GetCounter()); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!result) ++ { ++ m_jail_isjailed = false; ++ return; ++ } ++ ++ Field *fields = result->Fetch(); ++ m_jail_warning = true; ++ m_jail_isjailed = true; ++ m_jail_guid = fields[0].GetUInt32(); ++ m_jail_char = fields[1].GetString(); ++ m_jail_release = fields[2].GetUInt32(); ++ m_jail_amnestietime = fields[3].GetUInt32(); ++ m_jail_reason = fields[4].GetString(); ++ m_jail_times = fields[5].GetUInt32(); ++ m_jail_gmacc = fields[6].GetUInt32(); ++ m_jail_gmchar = fields[7].GetString(); ++ m_jail_lasttime = fields[8].GetString(); ++ m_jail_duration = fields[9].GetUInt32(); ++ ++ if (m_jail_release == 0) ++ { ++ m_jail_isjailed = false; ++ return; ++ } ++ ++ time_t localtime; ++ localtime = time(NULL); ++ ++ if (m_jail_release <= localtime) ++ { ++ m_jail_isjailed = false; ++ m_jail_release = 0; ++ ++ _SaveJail(); ++ ++ sWorld->SendWorldText(LANG_JAIL_CHAR_FREE, GetName().c_str()); ++ ++ CastSpell(this,8690,false); ++ return; ++ } ++ ++ if (m_jail_isjailed) ++ { ++ if (m_team == ALLIANCE) ++ { ++ TeleportTo(sObjectMgr->m_jailconf_ally_m, sObjectMgr->m_jailconf_ally_x, ++ sObjectMgr->m_jailconf_ally_y, sObjectMgr->m_jailconf_ally_z, sObjectMgr->m_jailconf_ally_o); ++ } ++ else ++ { ++ TeleportTo(sObjectMgr->m_jailconf_horde_m, sObjectMgr->m_jailconf_horde_x, ++ sObjectMgr->m_jailconf_horde_y, sObjectMgr->m_jailconf_horde_z, sObjectMgr->m_jailconf_horde_o); ++ } ++ ++ sWorld->SendWorldText(LANG_JAIL_CHAR_TELE, GetName().c_str()); ++ } ++} ++ ++ + bool Player::isAllowedToLoot(const Creature* creature) + { + if (!creature->isDead() || !creature->IsDamageEnoughForLootingAndReward()) +@@ -18947,8 +19114,21 @@ bool Player::_LoadHomeBind(PreparedQueryResult result) + /*** SAVE SYSTEM ***/ + /*********************************************************/ + ++// Saves the jail datas (added by WarHead) edited by LordPsyan. ++void Player::_SaveJail(void) ++{ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT `guid` FROM `jail` WHERE `guid`='%u' LIMIT 1", m_jail_guid); ++ if (!result) CharacterDatabase.PExecute("INSERT INTO `jail` VALUES ('%u','%s','%u', '%u','%s','%u','%u','%s',CURRENT_TIMESTAMP,'%u')", m_jail_guid, m_jail_char.c_str(), m_jail_release, m_jail_amnestietime, m_jail_reason.c_str(), m_jail_times, m_jail_gmacc, m_jail_gmchar.c_str(), m_jail_duration); ++ else CharacterDatabase.PExecute("UPDATE `jail` SET `release`='%u', `amnestietime`='%u',`reason`='%s',`times`='%u',`gmacc`='%u',`gmchar`='%s',`duration`='%u' WHERE `guid`='%u' LIMIT 1", m_jail_release, m_jail_amnestietime, m_jail_reason.c_str(), m_jail_times, m_jail_gmacc, m_jail_gmchar.c_str(), m_jail_duration, m_jail_guid); ++ CharacterDatabase.CommitTransaction(trans); ++} ++ + void Player::SaveToDB(bool create /*=false*/) + { ++ // Jail: Prevent saving of jailed players ++ if (m_jail_isjailed) return; ++ + // delay auto save at any saves (manual, in code, or autosave) + m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE); + +@@ -24578,11 +24758,14 @@ void Player::_LoadSkills(PreparedQueryResult result) + TC_LOG_ERROR("entities.player", "Character %u has skill %u with value 0. Will be deleted.", GetGUID().GetCounter(), skill); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL); ++ PreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_DEL_JAIL); + + stmt->setUInt32(0, GetGUID().GetCounter()); + stmt->setUInt16(1, skill); + + CharacterDatabase.Execute(stmt); ++ CharacterDatabase.Execute(stmt2); ++ + + continue; + } +diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h +index b7d7d81..69cde74 100644 +--- a/src/server/game/Entities/Player/Player.h ++++ b/src/server/game/Entities/Player/Player.h +@@ -2010,6 +2010,25 @@ class Player : public Unit, public GridObject + + bool IsImmuneToEnvironmentalDamage(); + uint32 EnvironmentalDamage(EnviromentalDamage type, uint32 damage); ++ // Jail by WarHead edited by spgm ++ // --------------- ++ // Char datas... ++ bool m_jail_warning; ++ bool m_jail_amnestie; ++ bool m_jail_isjailed; // Is this player jailed? ++ std::string m_jail_char; // Name of jailed char ++ uint32 m_jail_guid; // guid of the jailed char ++ uint32 m_jail_release; // When is the player a free man/woman? ++ std::string m_jail_reason; // Why was the char jailed? ++ uint32 m_jail_times; // How often was the player jailed? ++ uint32 m_jail_amnestietime; ++ uint32 m_jail_gmacc; // Used GM acc ++ std::string m_jail_gmchar; // Used GM char ++ std::string m_jail_lasttime; // Last jail time ++ uint32 m_jail_duration; // Duration of the jail ++ // Load / save functions... ++ void _LoadJail(void); // Loads the jail datas ++ void _SaveJail(void); // Saves the jail datas + + /*********************************************************/ + /*** FLOOD FILTER SYSTEM ***/ +diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp +index d206409..95a169f 100644 +--- a/src/server/game/Globals/ObjectMgr.cpp ++++ b/src/server/game/Globals/ObjectMgr.cpp +@@ -6505,6 +6505,121 @@ uint32 ObjectMgr::GeneratePetNumber() + return ++_hiPetNumber; + } + ++// Loads the jail conf out of the database ++void ObjectMgr::LoadJailConf(void) ++{ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT * FROM `jail_conf`"); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!result) ++ { ++ TC_LOG_ERROR("misc", GetTrinityStringForDBCLocale(LANG_JAIL_CONF_ERR1)); ++ TC_LOG_ERROR("misc", GetTrinityStringForDBCLocale(LANG_JAIL_CONF_ERR2)); ++ ++ m_jailconf_max_jails = 3; ++ m_jailconf_max_duration = 672; ++ m_jailconf_min_reason = 25; ++ m_jailconf_warn_player = 1; ++ m_jailconf_amnestie = 180; ++ ++ m_jailconf_ally_x = -8673.43; ++ m_jailconf_ally_y = 631.795; ++ m_jailconf_ally_z = 96.9406; ++ m_jailconf_ally_o = 2.1785; ++ m_jailconf_ally_m = 0; ++ ++ m_jailconf_horde_x = 2179.85; ++ m_jailconf_horde_y = -4763.96; ++ m_jailconf_horde_z = 54.911; ++ m_jailconf_horde_o = 4.44216; ++ m_jailconf_horde_m = 1; ++ ++ m_jailconf_ban = 0; ++ m_jailconf_radius = 10; ++ ++ return; ++ } ++do ++{ ++ Field *fields = result->Fetch(); ++ m_jail_obt = fields[1].GetString(); ++ if (m_jail_obt == "m_jailconf_max_jails") ++ { ++ m_jailconf_max_jails = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_max_duration") ++ { ++ m_jailconf_max_duration = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_min_reason") ++ { ++ m_jailconf_min_reason = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_warn_player") ++ { ++ m_jailconf_warn_player = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_amnestie") ++ { ++ m_jailconf_amnestie = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_ally_x") ++ { ++ m_jailconf_ally_x = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_ally_y") ++ { ++ m_jailconf_ally_y = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_ally_z") ++ { ++ m_jailconf_ally_z = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_ally_o") ++ { ++ m_jailconf_ally_o = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_ally_m") ++ { ++ m_jailconf_ally_m = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_horde_x") ++ { ++ m_jailconf_horde_x = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_horde_y") ++ { ++ m_jailconf_horde_y = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_horde_z") ++ { ++ m_jailconf_horde_z = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_horde_o") ++ { ++ m_jailconf_horde_o = fields[3].GetFloat(); ++ } ++ if (m_jail_obt == "m_jailconf_horde_m") ++ { ++ m_jailconf_horde_m = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_ban") ++ { ++ m_jailconf_ban = fields[2].GetUInt32(); ++ } ++ if (m_jail_obt == "m_jailconf_radius") ++ { ++ m_jailconf_radius = fields[2].GetUInt32(); ++ } ++} ++while (result->NextRow()); ++ ++ TC_LOG_INFO("server.loading", ""); ++ TC_LOG_INFO("server.loading", GetTrinityStringForDBCLocale(LANG_JAIL_CONF_LOADED)); ++ TC_LOG_INFO("server.loading", ""); ++} ++ + uint32 ObjectMgr::GenerateCreatureSpawnId() + { + if (_creatureSpawnId >= uint32(0xFFFFFF)) +diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h +index ae12587..a79dc44 100644 +--- a/src/server/game/Globals/ObjectMgr.h ++++ b/src/server/game/Globals/ObjectMgr.h +@@ -1043,6 +1043,29 @@ class ObjectMgr + void LoadTrainerSpell(); + void AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel); + ++ // Loads the jail conf out of the database ++ void LoadJailConf(void); ++ ++ // Jail Config... ++ std::string m_jail_obt; ++ uint32 m_jailconf_max_jails; // Jail times when the char will be deleted ++ uint32 m_jailconf_max_duration; // Max. jail duration in hours ++ uint32 m_jailconf_min_reason; // Min. char length of the reason ++ uint32 m_jailconf_warn_player; // Warn player every login if max_jails is nearly reached? ++ uint32 m_jailconf_amnestie; // player amnestie ++ float m_jailconf_ally_x; // Coords of the jail for the allies ++ float m_jailconf_ally_y; ++ float m_jailconf_ally_z; ++ float m_jailconf_ally_o; ++ uint32 m_jailconf_ally_m; ++ float m_jailconf_horde_x; // Coords of the jail for the horde ++ float m_jailconf_horde_y; ++ float m_jailconf_horde_z; ++ float m_jailconf_horde_o; ++ uint32 m_jailconf_horde_m; ++ uint32 m_jailconf_ban; // Ban acc if max. jailtimes is reached? ++ uint32 m_jailconf_radius; // Radius in which a jailed char can walk ++ + std::string GeneratePetName(uint32 entry); + uint32 GetBaseXP(uint8 level); + uint32 GetXPForLevel(uint8 level) const; +diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h +index 73a9905..8d4fe22 100644 +--- a/src/server/game/Miscellaneous/Language.h ++++ b/src/server/game/Miscellaneous/Language.h +@@ -855,6 +855,42 @@ enum TrinityStrings + LANG_RBAC_EMAIL_REQUIRED = 881, + // Room for in-game strings 882-999 not used + ++ // Added by WarHead for the Jail edited by spgm ++ LANG_JAIL_YOURE_JAILED = 950, // "You are jailed by %s for %u hour(s)!" ++ LANG_JAIL_REASON = 951, // "%s wrote this as reason: %s" ++ LANG_JAIL_WAS_JAILED = 952, // "%s was jailed by you for %u hour(s)!" ++ LANG_JAIL_YOURE_UNJAILED = 953, // "You was released out of the jail by %s." ++ LANG_JAIL_WAS_UNJAILED = 954, // "You have released %s out of the jail." ++ LANG_JAIL_NOREASON = 955, // "No reason given or reason is < %u chars!" ++ LANG_JAIL_NONAME = 956, // "No name given!" ++ LANG_JAIL_NOTIME = 957, // "No time given!" ++ LANG_JAIL_VALUE = 958, // "The jailtime must be between 1 and %u hours!" ++ LANG_JAIL_CHAR_NOTJAILED = 959, // "The character (%s) is not jailed!" ++ LANG_JAIL_DENIED = 960, // "Command forbidden for jailed characters!" ++ LANG_JAIL_JAILED_H_INFO = 961, // "You have %u hour(s) left in the jail." ++ LANG_JAIL_JAILED_M_INFO = 962, // "You have %u minute(s) left in the jail." ++ LANG_JAIL_NOTJAILED_INFO = 963, // "You're a free woman / man. ;-)" ++ LANG_JAIL_GM_INFO = 964, // "%s was %u times jailed and has %u minute(s) left. Last time jailed by %s. Last reason was: '%s'" ++ LANG_JAIL_GM_NOINFO = 965, // "%s was never jailed." ++ LANG_JAIL_NO_JAIL = 966, // "You can't jail yourself!" ++ LANG_JAIL_NO_UNJAIL = 967, // "You can't unjail yourself!" ++ LANG_JAIL_WARNING = 968, // "Be carefull! Only one more jailtime and your current character will be deleted!" ++ LANG_JAIL_ANNOUNCE1 = 969, // "The character '" ++ LANG_JAIL_ANNOUNCE2 = 970, // "' was jailed for " ++ LANG_JAIL_ANNOUNCE3 = 971, // " hour(s) by the GM character '" ++ LANG_JAIL_ANNOUNCE4 = 972, // "'. The reason is: " ++ LANG_JAIL_RELOAD = 973, // "The jail configuration was reloaded." ++ LANG_JAIL_CONF_LOADED = 974, // ">> Jail config loaded." ++ LANG_JAIL_CONF_ERR1 = 975, // "Can't load jail config! Table empty or missed! Use jail_conf.sql!" ++ LANG_JAIL_CONF_ERR2 = 976, // "Set all jail config settings to default..." ++ LANG_JAIL_CHAR_TELE = 977, // "The Character '%s' (GUID %u) is jailed and teleportet into the jail." ++ LANG_JAIL_CHAR_FREE = 978, // "The Character '%s' (GUID %u) was released out of the jail." ++ LANG_JAIL_WRONG_NAME = 979, // "A character with this name doesn't exists!" ++ LANG_JAIL_WARNING_BAN = 980, // "Be carefull! Only one more jailtime and your account will be banned!" ++ LANG_JAIL_BAN_REASON = 981, // "Max. jailtimes reached!" ++ LANG_JAIL_BAN_BY = 982, // "Robotron" ++ LANG_JAIL_AMNESTII = 983, // "AMNESTII" ++ + // Level 4 (CLI only commands) + LANG_COMMAND_EXIT = 1000, + LANG_ACCOUNT_DELETED = 1001, +diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp +index e983c9e..ac6b51a 100644 +--- a/src/server/game/Scripting/ScriptLoader.cpp ++++ b/src/server/game/Scripting/ScriptLoader.cpp +@@ -57,6 +57,7 @@ void AddSC_group_commandscript(); + void AddSC_guild_commandscript(); + void AddSC_honor_commandscript(); + void AddSC_instance_commandscript(); ++void AddSC_jail_commandscript(); + void AddSC_learn_commandscript(); + void AddSC_lfg_commandscript(); + void AddSC_list_commandscript(); +@@ -746,6 +747,7 @@ void AddCommandScripts() + AddSC_guild_commandscript(); + AddSC_honor_commandscript(); + AddSC_instance_commandscript(); ++ AddSC_jail_commandscript(); + AddSC_learn_commandscript(); + AddSC_lookup_commandscript(); + AddSC_lfg_commandscript(); +diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp +index e1fc737..6439a93 100644 +--- a/src/server/game/World/World.cpp ++++ b/src/server/game/World/World.cpp +@@ -1816,6 +1816,10 @@ void World::SetInitialWorldSettings() + TC_LOG_INFO("server.loading", "Returning old mails..."); + sObjectMgr->ReturnOrDeleteOldMails(false); + ++ // Loads the jail conf out of the database ++ TC_LOG_INFO("server.loading", "Loading JailConfing..."); ++ sObjectMgr->LoadJailConf(); ++ + TC_LOG_INFO("server.loading", "Loading Autobroadcasts..."); + LoadAutobroadcasts(); + +diff --git a/src/server/scripts/Commands/cs_jail.cpp b/src/server/scripts/Commands/cs_jail.cpp +new file mode 100644 +index 0000000..f131546 +--- /dev/null ++++ b/src/server/scripts/Commands/cs_jail.cpp +@@ -0,0 +1,388 @@ ++/* ++ * Copyright (C) 2008-2015 TrinityCore ++ * ++ * 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, see . ++ */ ++ ++ /* Jail by LordPsyan (Original script by Warhead) */ ++ ++#include "Common.h" ++#include "Chat.h" ++#include "Language.h" ++#include "Pet.h" ++#include "Player.h" ++#include "ObjectMgr.h" ++#include "ScriptMgr.h" ++#include "AccountMgr.h" ++#include "World.h" ++#include "Player.h" ++#include "WorldSession.h" ++#include "DatabaseEnv.h" ++#include "AccountMgr.h" ++#include "CellImpl.h" ++#include "GridNotifiersImpl.h" ++#include "Log.h" ++#include "ChatLink.h" ++ ++class jail_commandscript : public CommandScript ++{ ++public: ++ jail_commandscript() : CommandScript("jail_commandscript") { } ++ ++ std::vector GetCommands() const override ++ { ++ static std::vector jailCommandTable = ++ { ++ { "player", rbac::RBAC_PERM_COMMAND_JAIL_INFO, false, &HandleJailPlayerCommand, "" }, // 901 ++ { "info", rbac::RBAC_PERM_COMMAND_JAIL_INFO, false, &HandleJailInfoCommand, "" }, // 902 ++ { "release", rbac::RBAC_PERM_COMMAND_JAIL_UN, false, &HandleUnJailCommand, "" }, // 903 ++ { "reload", rbac::RBAC_PERM_COMMAND_JAIL_RELOAD, false, &HandleJailReloadCommand, "" }, // 904 ++ }; ++ static std::vector commandTable = ++ { ++ { "jail", rbac::RBAC_PERM_COMMAND_JAIL, false, NULL, "", jailCommandTable }, ++ }; ++ return commandTable; ++ } ++ static bool HandleJailPlayerCommand(ChatHandler* handler, char const* args) ++ { ++ std::string cname, announce, ban_reason, ban_by; ++ time_t localtime; ++ localtime = time(NULL); ++ ++ char *charname = strtok((char*)args, " "); ++ if (charname == NULL) ++ { ++ handler->PSendSysMessage(LANG_JAIL_NONAME); ++ return true; ++ } else cname = charname; ++ ++ char *timetojail = strtok(NULL, " "); ++ if (timetojail == NULL) ++ { ++ handler->PSendSysMessage(LANG_JAIL_NOTIME); ++ return true; ++ } ++ ++ uint32 jailtime = (uint32) atoi((char*)timetojail); ++ if (jailtime < 1 || jailtime > sObjectMgr->m_jailconf_max_duration) ++ { ++ handler->PSendSysMessage(LANG_JAIL_VALUE, sObjectMgr->m_jailconf_max_duration); ++ return true; ++ } ++ ++ char *reason = strtok(NULL, "\0"); ++ std::string jailreason; ++ if (reason == NULL || strlen((const char*)reason) < sObjectMgr->m_jailconf_min_reason) ++ { ++ handler->PSendSysMessage(LANG_JAIL_NOREASON, sObjectMgr->m_jailconf_min_reason); ++ return true; ++ } else jailreason = reason; ++ ++ ObjectGuid GUID = sObjectMgr->GetPlayerGUIDByName(cname.c_str()); ++ if (GUID == 0) ++ { ++ handler->PSendSysMessage(LANG_JAIL_WRONG_NAME); ++ return true; ++ } ++ ++ Player *chr = ObjectAccessor::FindPlayer(GUID); ++ if (!chr) ++ { ++ ObjectGuid::LowType jail_guid = GUID.GetCounter(); ++ std::string jail_char = cname; ++ bool jail_isjailed = true; ++ uint32 jail_release = localtime + (jailtime * 60 * 60); ++ uint32 jail_amnestietime = localtime +(60* 60 * 24 * sObjectMgr->m_jailconf_amnestie); ++ std::string jail_reason = jailreason; ++ uint32 jail_times = 0; ++ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT * FROM `jail` WHERE `guid`='%u' LIMIT 1", jail_guid); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!result) ++ { ++ jail_times = 1; ++ } ++ else ++ { ++ Field *fields = result->Fetch(); ++ jail_times = fields[5].GetUInt32()+1; ++ } ++ ++ uint32 jail_gmacc = handler->GetSession()->GetAccountId(); ++ std::string jail_gmchar = handler->GetSession()->GetPlayerName(); ++ ++ SQLTransaction trans2 = CharacterDatabase.BeginTransaction(); ++ if (!result) CharacterDatabase.PExecute("INSERT INTO `jail` VALUES ('%u','%s','%u','%u','%s','%u','%u','%s',CURRENT_TIMESTAMP,'%u')", jail_guid, jail_char.c_str(), jail_release, jail_amnestietime, jail_reason.c_str(), jail_times, jail_gmacc, jail_gmchar.c_str(), jailtime); ++ else CharacterDatabase.PExecute("UPDATE `jail` SET `release`='%u', `amnestietime`='%u',`reason`='%s',`times`='%u',`gmacc`='%u',`gmchar`='%s',`duration`='%u' WHERE `guid`='%u' LIMIT 1", jail_release, jail_amnestietime, jail_reason.c_str(), jail_times, jail_gmacc, jail_gmchar.c_str(), jailtime, jail_guid); ++ CharacterDatabase.CommitTransaction(trans2); ++ ++ handler->PSendSysMessage(LANG_JAIL_WAS_JAILED, cname.c_str(), jailtime); ++ ++ announce = handler->GetTrinityString(LANG_JAIL_ANNOUNCE1); ++ announce += cname; ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE2); ++ announce += timetojail; ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE3); ++ announce += handler->GetSession()->GetPlayerName(); ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE4); ++ announce += jail_reason; ++ ++ sWorld->SendServerMessage(SERVER_MSG_STRING, announce.c_str()); ++ ++ if ((sObjectMgr->m_jailconf_max_jails == jail_times) && !sObjectMgr->m_jailconf_ban) ++ { ++ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_GUID); ++ stmt->setUInt32(0, GUID); ++ PreparedQueryResult result = CharacterDatabase.Query(stmt); ++ ++ if (!result) ++ { ++ handler->PSendSysMessage(LANG_NO_PLAYER, cname.c_str()); ++ return true; ++ } ++ ++ Field *fields = result->Fetch(); ++ ++ Player::DeleteFromDB(GUID, fields[1].GetUInt32()); ++ } ++ else if ((sObjectMgr->m_jailconf_max_jails == jail_times) && sObjectMgr->m_jailconf_ban) ++ { ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT * FROM `characters` WHERE `guid`='%u' LIMIT 1", ObjectGuid::LowType(GUID.GetCounter())); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!result) ++ { ++ handler->PSendSysMessage(LANG_NO_PLAYER, cname.c_str()); ++ return true; ++ } ++ Field *fields = result->Fetch(); ++ uint32 acc_id = fields[1].GetUInt32(); ++ ++ SQLTransaction trans2 = LoginDatabase.BeginTransaction(); ++ result = LoginDatabase.PQuery("SELECT * FROM `account` WHERE `id`='%u' LIMIT 1", acc_id); ++ LoginDatabase.CommitTransaction(trans2); ++ ++ if (!result) ++ { ++ handler->PSendSysMessage(LANG_NO_PLAYER, cname.c_str()); ++ return true; ++ } ++ ban_reason = handler->GetTrinityString(LANG_JAIL_BAN_REASON); ++ ban_by = handler->GetTrinityString(LANG_JAIL_BAN_BY); ++ ++ SQLTransaction trans3 = LoginDatabase.BeginTransaction(); ++ LoginDatabase.PExecute("INSERT IGNORE INTO `account_banned` (`id`,`bandate`,`bannedby`,`banreason`) VALUES ('%u',UNIX_TIMESTAMP,'%s','%s')", acc_id, ban_by.c_str(), ban_reason.c_str()); ++ LoginDatabase.CommitTransaction(trans3); ++ ++ } ++ return true; ++ } ++ ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult result = CharacterDatabase.PQuery("SELECT * FROM `characters` WHERE `guid`='%u' LIMIT 1", chr->GetGUID().GetCounter()); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!result) ++ { ++ handler->PSendSysMessage(LANG_NO_PLAYER, cname.c_str()); ++ return true; ++ } ++ ++ Field *fields = result->Fetch(); ++ ++ if (chr->GetName() == handler->GetSession()->GetPlayerName()) ++ { ++ handler->PSendSysMessage(LANG_JAIL_NO_JAIL); ++ return true; ++ } ++ ++ chr->SaveToDB(); ++ ++ chr->m_jail_guid = fields[0].GetUInt32(); ++ chr->m_jail_char = fields[3].GetString(); ++ chr->m_jail_isjailed = true; ++ chr->m_jail_release = localtime + (jailtime * 60 * 60); ++ chr->m_jail_amnestietime = localtime +(60* 60 * 24 * sObjectMgr->m_jailconf_amnestie); ++ chr->m_jail_reason = jailreason; ++ chr->m_jail_times = chr->m_jail_times+1; ++ chr->m_jail_gmacc = handler->GetSession()->GetAccountId(); ++ chr->m_jail_gmchar = handler->GetSession()->GetPlayerName(); ++ chr->m_jail_duration = jailtime; ++ ++ chr->_SaveJail(); ++ ++ handler->PSendSysMessage(LANG_JAIL_WAS_JAILED, fields[3].GetString().c_str(), jailtime); ++ handler->PSendSysMessage(LANG_JAIL_YOURE_JAILED, handler->GetSession()->GetPlayerName(), jailtime); ++ handler->PSendSysMessage(LANG_JAIL_REASON, handler->GetSession()->GetPlayerName(), jailreason.c_str()); ++ ++ announce = handler->GetTrinityString(LANG_JAIL_ANNOUNCE1); ++ announce += fields[3].GetString(); ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE2); ++ announce += timetojail; ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE3); ++ announce += handler->GetSession()->GetPlayerName(); ++ announce += handler->GetTrinityString(LANG_JAIL_ANNOUNCE4); ++ announce += chr->m_jail_reason; ++ ++ sWorld->SendServerMessage(SERVER_MSG_STRING, announce.c_str()); ++ ++ if (sObjectMgr->m_jailconf_max_jails == chr->m_jail_times) ++ { ++ chr->GetSession()->KickPlayer(); ++ Player::DeleteFromDB(ObjectGuid(HighGuid::Player, fields[0].GetUInt32()), fields[1].GetUInt32(), true, true); ++ } ++ else if ((sObjectMgr->m_jailconf_max_jails == chr->m_jail_times) && sObjectMgr->m_jailconf_ban) ++ { ++ uint32 acc_id = chr->GetSession()->GetAccountId(); ++ ban_reason = handler->GetTrinityString(LANG_JAIL_BAN_REASON); ++ ban_by = handler->GetTrinityString(LANG_JAIL_BAN_BY); ++ ++ SQLTransaction trans = LoginDatabase.BeginTransaction(); ++ LoginDatabase.PExecute("INSERT IGNORE INTO `account_banned` (`id`,`bandate`,`bannedby`,`banreason`) VALUES ('%u',UNIX_TIMESTAMP,'%s','%s')", acc_id, ban_by.c_str(), ban_reason.c_str()); ++ LoginDatabase.CommitTransaction(trans); ++ ++ chr->GetSession()->LogoutPlayer(false); ++ } ++ else chr->GetSession()->LogoutPlayer(false); ++ return true; ++ } ++ ++ static bool HandleJailInfoCommand(ChatHandler* handler, char const* args) ++ { ++ time_t localtime; ++ localtime = time(NULL); ++ Player *chr = handler->GetSession()->GetPlayer(); ++ ++ if (chr->m_jail_release > 0) ++ { ++ uint32 min_left = (uint32)floor(float(chr->m_jail_release - localtime) / 60); ++ ++ if (min_left <= 0) ++ { ++ chr->m_jail_release = 0; ++ chr->_SaveJail(); ++ handler->PSendSysMessage(LANG_JAIL_NOTJAILED_INFO); ++ return true; ++ } ++ else ++ { ++ if (min_left >= 60) handler->PSendSysMessage(LANG_JAIL_JAILED_H_INFO, (uint32)floor(float(chr->m_jail_release - localtime) / 60 / 60)); ++ else handler->PSendSysMessage(LANG_JAIL_JAILED_M_INFO, min_left); ++ handler->PSendSysMessage(LANG_JAIL_REASON, chr->m_jail_gmchar.c_str(), chr->m_jail_reason.c_str()); ++ ++ return true; ++ } ++ } ++ else ++ { ++ handler->PSendSysMessage(LANG_JAIL_NOTJAILED_INFO); ++ return true; ++ } ++ return false; ++ } ++ ++ static bool HandleUnJailCommand(ChatHandler* handler, char const* args) ++ { ++ char *charname = strtok((char*)args, " "); ++ std::string cname; ++ ++ if (charname == NULL) return false; ++ else cname = charname; ++ ++ ObjectGuid GUID = sObjectMgr->GetPlayerGUIDByName(cname.c_str()); ++ Player *chr = ObjectAccessor::FindPlayer(GUID); ++ ++ if (chr) ++ { ++ if (chr->GetName() == handler->GetSession()->GetPlayerName()) ++ { ++ handler->PSendSysMessage(LANG_JAIL_NO_UNJAIL); ++ return true; ++ } ++ ++ if (chr->m_jail_isjailed) ++ { ++ chr->m_jail_isjailed = false; ++ chr->m_jail_release = 0; ++ chr->m_jail_times = chr->m_jail_times-1; ++ ++ chr->_SaveJail(); ++ ++ if (chr->m_jail_times == 0) ++ { ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ CharacterDatabase.PQuery("DELETE FROM `jail` WHERE `guid`='%u' LIMIT 1", chr->GetGUID().GetCounter()); ++ CharacterDatabase.CommitTransaction(trans); ++ } ++ ++ handler->PSendSysMessage(LANG_JAIL_WAS_UNJAILED, cname.c_str()); ++ handler->PSendSysMessage(LANG_JAIL_YOURE_UNJAILED); ++ chr->CastSpell(chr,8690,false); ++ //chr->GetSession()->LogoutPlayer(false); ++ } else handler->PSendSysMessage(LANG_JAIL_CHAR_NOTJAILED, cname.c_str()); ++ return true; ++ } ++ else ++ { ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ QueryResult jresult = CharacterDatabase.PQuery("SELECT * FROM `jail` WHERE `guid`='%u' LIMIT 1", ObjectGuid::LowType(GUID.GetCounter())); ++ CharacterDatabase.CommitTransaction(trans); ++ ++ if (!jresult) ++ { ++ handler->PSendSysMessage(LANG_JAIL_CHAR_NOTJAILED, cname.c_str()); ++ return true; ++ } ++ else ++ { ++ Field *fields = jresult->Fetch(); ++ uint32 jail_times = fields[4].GetUInt32()-1; ++ ++ if (jail_times == 0) ++ { ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ CharacterDatabase.PQuery("DELETE FROM `jail` WHERE `guid`='%u' LIMIT 1", fields[0].GetUInt32()); ++ CharacterDatabase.CommitTransaction(trans); ++ } ++ else ++ { ++ SQLTransaction trans = CharacterDatabase.BeginTransaction(); ++ CharacterDatabase.PQuery("UPDATE `jail` SET `release`='0',`times`='%u' WHERE `guid`='%u' LIMIT 1", jail_times, fields[0].GetUInt32()); ++ CharacterDatabase.CommitTransaction(trans); ++ } ++ ++ handler->PSendSysMessage(LANG_JAIL_WAS_UNJAILED, cname.c_str()); ++ return true; ++ } ++ ++ } ++ return true; ++ } ++ ++ static bool HandleJailReloadCommand(ChatHandler* handler, char const* args) ++ { ++ sObjectMgr->LoadJailConf(); ++ handler->PSendSysMessage(LANG_JAIL_RELOAD); ++ return true; ++ } ++}; ++ ++void AddSC_jail_commandscript() ++{ ++ new jail_commandscript(); ++} \ No newline at end of file