From ce93f3d7456f57147b1abce63bd7a5de5bb7eb22 Mon Sep 17 00:00:00 2001
Date: Thu, 4 Apr 2013 17:59:34 +0700
Subject: [PATCH] NPCBots
---
sql/Bots/character_NPC_bots.sql | 12 +
.../world_bot_giver_locales_gossip_menu_option.sql | 92 +
sql/Bots/world_bots.sql | 524 ++++
sql/Bots/world_script_bot_giver.sql | 13 +
src/server/game/AI/NpcBots/bot_GridNotifiers.h | 462 +++
src/server/game/AI/NpcBots/bot_ai.cpp | 3185 ++++++++++++++++++++
src/server/game/AI/NpcBots/bot_ai.h | 355 +++
src/server/game/AI/NpcBots/bot_druid_ai.cpp | 1092 +++++++
src/server/game/AI/NpcBots/bot_hunter_ai.cpp | 340 +++
src/server/game/AI/NpcBots/bot_mage_ai.cpp | 935 ++++++
src/server/game/AI/NpcBots/bot_paladin_ai.cpp | 1014 +++++++
src/server/game/AI/NpcBots/bot_priest_ai.cpp | 859 ++++++
src/server/game/AI/NpcBots/bot_rogue_ai.cpp | 894 ++++++
src/server/game/AI/NpcBots/bot_shaman_ai.cpp | 390 +++
src/server/game/AI/NpcBots/bot_warlock_ai.cpp | 458 +++
src/server/game/AI/NpcBots/bot_warrior_ai.cpp | 1192 ++++++++
src/server/game/AI/NpcBots/botcommands.cpp | 851 ++++++
src/server/game/AI/NpcBots/botgiver.cpp | 643 ++++
src/server/game/AI/PlayerBots/bp_ai.cpp | 1888 ++++++++++++
src/server/game/AI/PlayerBots/bp_ai.h | 611 ++++
src/server/game/AI/PlayerBots/bp_cai.cpp | 14 +
src/server/game/AI/PlayerBots/bp_cai.h | 118 +
src/server/game/AI/PlayerBots/bp_dk_ai.cpp | 549 ++++
src/server/game/AI/PlayerBots/bp_dk_ai.h | 159 +
src/server/game/AI/PlayerBots/bp_dru_ai.cpp | 787 +++++
src/server/game/AI/PlayerBots/bp_dru_ai.h | 223 ++
src/server/game/AI/PlayerBots/bp_hun_ai.cpp | 407 +++
src/server/game/AI/PlayerBots/bp_hun_ai.h | 122 +
src/server/game/AI/PlayerBots/bp_mag_ai.cpp | 356 +++
src/server/game/AI/PlayerBots/bp_mag_ai.h | 165 +
src/server/game/AI/PlayerBots/bp_mgr.cpp | 1136 +++++++
src/server/game/AI/PlayerBots/bp_mgr.h | 64 +
src/server/game/AI/PlayerBots/bp_pal_ai.cpp | 673 +++++
src/server/game/AI/PlayerBots/bp_pal_ai.h | 208 ++
src/server/game/AI/PlayerBots/bp_pri_ai.cpp | 514 ++++
src/server/game/AI/PlayerBots/bp_pri_ai.h | 166 +
src/server/game/AI/PlayerBots/bp_rog_ai.cpp | 408 +++
src/server/game/AI/PlayerBots/bp_rog_ai.h | 104 +
src/server/game/AI/PlayerBots/bp_sha_ai.cpp | 1660 ++++++++++
src/server/game/AI/PlayerBots/bp_sha_ai.h | 246 ++
src/server/game/AI/PlayerBots/bp_warl_ai.cpp | 550 ++++
src/server/game/AI/PlayerBots/bp_warl_ai.h | 254 ++
src/server/game/AI/PlayerBots/bp_warr_ai.cpp | 551 ++++
src/server/game/AI/PlayerBots/bp_warr_ai.h | 182 ++
src/server/game/CMakeLists.txt | 2 +
src/server/game/Entities/Creature/Creature.cpp | 128 +
src/server/game/Entities/Creature/Creature.h | 38 +
.../game/Entities/Creature/TemporarySummon.cpp | 8 +
src/server/game/Entities/Player/Player.cpp | 937 ++++++
src/server/game/Entities/Player/Player.h | 86 +
src/server/game/Entities/Unit/Unit.cpp | 214 ++
src/server/game/Groups/Group.cpp | 10 +
src/server/game/Groups/Group.h | 3 +
src/server/game/Handlers/CharacterHandler.cpp | 98 +
src/server/game/Handlers/ChatHandler.cpp | 30 +
src/server/game/Handlers/QuestHandler.cpp | 8 +
src/server/game/Maps/Map.cpp | 3 +
src/server/game/Scripting/ScriptLoader.cpp | 24 +
src/server/game/Server/WorldSession.cpp | 100 +
src/server/game/Server/WorldSession.h | 24 +
src/server/scripts/Spells/spell_priest.cpp | 4 +
.../Database/Implementation/CharacterDatabase.cpp | 8 +
.../Database/Implementation/CharacterDatabase.h | 8 +
.../Database/Implementation/WorldDatabase.cpp | 4 +
.../shared/Database/Implementation/WorldDatabase.h | 4 +
src/server/worldserver/worldserver.conf.dist | 193 ++
66 files changed, 27360 insertions(+), 0 deletions(-)
create mode 100644 sql/Bots/character_NPC_bots.sql
create mode 100644 sql/Bots/world_bot_giver_locales_gossip_menu_option.sql
create mode 100644 sql/Bots/world_bots.sql
create mode 100644 sql/Bots/world_script_bot_giver.sql
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_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/botgiver.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_cai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_cai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_dk_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_dk_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_dru_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_dru_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_hun_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_hun_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_mag_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_mag_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_mgr.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_mgr.h
create mode 100644 src/server/game/AI/PlayerBots/bp_pal_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_pal_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_pri_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_pri_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_rog_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_rog_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_sha_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_sha_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_warl_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_warl_ai.h
create mode 100644 src/server/game/AI/PlayerBots/bp_warr_ai.cpp
create mode 100644 src/server/game/AI/PlayerBots/bp_warr_ai.h
diff --git a/sql/Bots/character_NPC_bots.sql b/sql/Bots/character_NPC_bots.sql
new file mode 100644
index 0000000..723043e
--- /dev/null
+++ b/sql/Bots/character_NPC_bots.sql
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS `character_npcbot`;
+CREATE TABLE `character_npcbot` (
+ `owner` int(10) default NULL,
+ `entry` int(10) default NULL,
+ `race` tinyint(3) default NULL,
+ `class` tinyint(3) default NULL,
+ `istank` tinyint(3) default NULL,
+ PRIMARY KEY (`owner`,`entry`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+
diff --git a/sql/Bots/world_bot_giver_locales_gossip_menu_option.sql b/sql/Bots/world_bot_giver_locales_gossip_menu_option.sql
new file mode 100644
index 0000000..a9f5c6b
--- /dev/null
+++ b/sql/Bots/world_bot_giver_locales_gossip_menu_option.sql
@@ -0,0 +1,92 @@
+delete from `locales_gossip_menu_option` where `menu_id` = '60000';
+
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','1','Abandon my Player','Abandon my Player','Abandon my Player','Abandon my Player','Abandon my Player','Abandon my Player','Abandon my Player','Abandon my Player',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','2','Recruit a Player','Recruit a Player','Recruit a Player','Recruit a Player','Recruit a Player','Recruit a Player','Recruit a Player','Recruit a Player',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','3','Abandon my Minion','Abandon my Minion','Abandon my Minion','Abandon my Minion','Abandon my Minion','Abandon my Minion','Abandon my Minion','Abandon my Minion',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','4','Recruit a Minion','Recruit a Minion','Recruit a Minion','Recruit a Minion','Recruit a Minion','Recruit a Minion','Recruit a Minion','Recruit a Minion',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','5','Tell me about these bots','Tell me about these bots','Tell me about these bots','Tell me about these bots','Tell me about these bots','Tell me about these bots','Tell me about these bots','Tell me about these bots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','6','ADD ALL','ADD ALL','ADD ALL','ADD ALL','ADD ALL','ADD ALL','ADD ALL','ADD ALL',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','7','REMOVE ALL','REMOVE ALL','REMOVE ALL','REMOVE ALL','REMOVE ALL','REMOVE ALL','REMOVE ALL','REMOVE ALL',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','8','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ','Recruit a Warrior ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','9','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ','Recruit a Hunter ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','10','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ','Recruit a Paladin ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','11','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ','Recruit a Shaman ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','12','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ','Recruit a Rogue ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','13','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ','Recruit a Druid ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','14','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ','Recruit a Mage ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','15','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ','Recruit a Priest ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','16','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ','Recruit a Warlock ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','17','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ','Recruit a Death Knight ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','18','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots','To see list of Playerbot commands whisper \'help\' to one of your playerbots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','19','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb','To see list of available npcbot commands type .npcbot or .npcb',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','20','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan','You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','21','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them','If you want your npcbots to heal someone out of your party set any raid target icon on them',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','22','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ','If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','23','star','star','star','star','star','star','star','star',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','24','circle','circle','circle','circle','circle','circle','circle','circle',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','25','diamond','diamond','diamond','diamond','diamond','diamond','diamond','diamond',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','26','triangle','triangle','triangle','triangle','triangle','triangle','triangle','triangle',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','27','moon','moon','moon','moon','moon','moon','moon','moon',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','28','square','square','square','square','square','square','square','square',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','29','cross','cross','cross','cross','cross','cross','cross','cross',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','30','skull','skull','skull','skull','skull','skull','skull','skull',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','31','unknown icon','unknown icon','unknown icon','unknown icon','unknown icon','unknown icon','unknown icon','unknown icon',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','32','no more bots available','no more bots available','no more bots available','no more bots available','no more bots available','no more bots available','no more bots available','no more bots available',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','33','more bot available','more bot available','more bot available','more bot available','more bot available','more bot available','more bot available','more bot available',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','34','more bots available','more bots available','more bots available','more bots available','more bots available','more bots available','more bots available','more bots available',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','35','bot available','bot available','bot available','bot available','bot available','bot available','bot available','bot available',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+insert into `locales_gossip_menu_option` (`menu_id`, `id`, `option_text_loc1`, `option_text_loc2`, `option_text_loc3`, `option_text_loc4`, `option_text_loc5`, `option_text_loc6`, `option_text_loc7`, `option_text_loc8`, `box_text_loc1`, `box_text_loc2`, `box_text_loc3`, `box_text_loc4`, `box_text_loc5`, `box_text_loc6`, `box_text_loc7`, `box_text_loc8`) values('60000','36','bots available','bots available','bots available','bots available','bots available','bots available','bots available','bots available',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+
+
+-- Custom section
+-- to change text displayed in botgiver's dialog you shoul
+-- 1) translate text to your language
+-- 2) place translated text in empty quotes below, (id = original id in `locales_gossip_menu_option`)
+-- 3) replace `option_text_loc1` with your locale index
+-- LOCALE_koKR `option_text_loc1`
+-- LOCALE_frFR `option_text_loc2`
+-- LOCALE_deDE `option_text_loc3`
+-- LOCALE_zhCN `option_text_loc4`
+-- LOCALE_zhTW `option_text_loc5`
+-- LOCALE_esES `option_text_loc6`
+-- LOCALE_esMX `option_text_loc7`
+-- LOCALE_ruRU `option_text_loc8`
+-- 4) run the queue below lol
+-- 5) you most likely need to save your translation for later
+
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 1;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 2;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 3;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 4;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 5;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 6;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 7;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 8;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 9;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 10;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 11;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 12;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 13;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 14;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 15;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 16;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 17;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 18;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 19;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 20;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 21;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 22;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 23;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 24;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 25;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 26;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 27;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 28;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 29;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 30;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 31;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 32;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 33;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 34;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 35;
+-- UPDATE `locales_gossip_menu_option` SET `option_text_loc1` = ' ' WHERE `menu_id` = 60000 AND `id` = 36;
diff --git a/sql/Bots/world_bots.sql b/sql/Bots/world_bots.sql
new file mode 100644
index 0000000..abd7bdd
--- /dev/null
+++ b/sql/Bots/world_bots.sql
@@ -0,0 +1,524 @@
+
+-- GENERAL --
+
+delete from `creature_template` where entry between 60001 and 60238;
+
+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_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
+('60001','0','0','0','0','0','5001','0','5001','0','Khelden','Mage Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60002','0','0','0','0','0','1294','0','1294','0','Zaldimar','Mage Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60003','0','0','0','0','0','1484','0','1484','0','Maginor','Mage Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60004','0','0','0','0','0','3344','0','3344','0','Anetta','Priest Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60005','0','0','0','0','0','1495','0','1495','0','Laurena','Priest Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60006','0','0','0','0','0','1295','0','1295','0','Josetta','Priest Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60007','0','0','0','0','0','3345','0','3345','0','Drusilla','Warlock Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60008','0','0','0','0','0','1930','0','1930','0','Alamar','Warlock Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60009','0','0','0','0','0','1469','0','1469','0','Demisette','Warlock Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60010','0','0','0','0','0','12749','0','12749','0','Nalesette','Hunter Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60011','0','0','0','0','0','3401','0','3401','0','Branstock','Priest Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60012','0','0','0','0','0','3395','0','3395','0','Thorgas','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60013','0','0','0','0','0','3343','0','3343','0','Llane','Warrior Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60014','0','0','0','0','0','3399','0','3399','0','Thran','Warrior Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60015','0','0','0','0','0','1300','0','1300','0','Lyria','Warrior Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60016','0','0','0','0','0','3351','0','3351','0','Jorik','Rogue Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60017','0','0','0','0','0','3407','0','3407','0','Solm','Rogue Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60018','0','0','0','0','0','1297','0','1297','0','Keryn','Rogue Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60019','0','0','0','0','0','1507','0','1507','0','Osborne','Rogue Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60020','0','0','0','0','0','3346','0','3346','0','Sammuel','Paladin Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60021','0','0','0','0','0','3393','0','3393','0','Bob','Paladin Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60022','0','0','0','0','0','1299','0','1299','0','Wilhelm','Paladin Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60023','0','0','0','0','0','1499','0','1499','0','Brisombre','Paladin Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60024','0','0','0','0','0','10216','0','10216','0','Marry','Mage Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60025','0','0','0','0','0','4552','0','4552','0','Haromm','Shaman Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60026','0','0','0','0','0','4567','0','4567','0','Kartosh','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60027','0','0','0','0','0','3429','0','3429','0','MaxanAnvol','Priest Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60028','0','0','0','0','0','10215','0','10215','0','Magis','Mage Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60029','0','0','0','0','0','3431','0','3431','0','GranVivehache','Warrior Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60030','0','0','0','0','0','1622','0','1622','0','Azar','Paladin Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60031','0','0','0','0','0','3436','0','3436','0','Hogral','Rogue Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60032','0','0','0','0','0','3053','0','3053','0','Kelstrum','Warrior Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60033','0','0','0','0','0','1578','0','1578','0','Dannal','Warrior Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60034','0','0','0','0','0','1579','0','1579','0','SombreDuesten','Priest Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60035','0','0','0','0','0','1592','0','1592','0','Isabella','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60036','0','0','0','0','0','1581','0','1581','0','Maximillion','Warlock Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60037','0','0','0','0','0','1604','0','1604','0','Rupert','Warlock Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60038','0','0','0','0','0','1600','0','1600','0','Cain','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60039','0','0','0','0','0','1602','0','1602','0','SombreBeryl','Priest Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60041','0','0','0','0','0','10548','0','10548','0','Milituus','Mage Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60042','0','0','0','0','0','2810','0','2810','0','Lexington','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60043','0','0','0','0','0','2123','0','2123','0','Siln','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60044','0','0','0','0','0','19598','0','19598','0','Umbrua','Shaman Bot','','0','80','80','2','1640','1640','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60045','0','0','0','0','0','2102','0','2102','0','Tigor','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60046','0','0','0','0','0','2082','0','2082','0','Beram','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60047','0','0','0','0','0','2106','0','2106','0','Turak','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60048','0','0','0','0','0','2121','0','2121','0','Sheal','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60049','0','0','0','0','0','2115','0','2115','0','Kym','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60050','0','0','0','0','0','2112','0','2112','0','Kary','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60051','0','0','0','0','0','2087','0','2087','0','Holt','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60052','0','0','0','0','0','2105','0','2105','0','Urek','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60053','0','0','0','0','0','2103','0','2103','0','Torm','Warrior Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60054','0','0','0','0','0','2096','0','2096','0','Sark','Warrior Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60055','0','0','0','0','0','17211','0','17211','0','Kerra','Warrior Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60056','0','0','0','0','0','2139','0','2139','0','Miles Welsh','Priest Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60057','0','0','0','0','0','2138','0','2138','0','Malakai','Priest Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60058','0','0','0','0','0','2137','0','2137','0','Cobb','Priest Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60059','0','0','0','0','0','2134','0','2134','0','Shymm','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','0','0','0','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','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60060','0','0','0','0','0','6058','0','6058','0','Ursyn','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60061','0','0','0','0','0','2135','0','2135','0','Thurston','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60062','0','0','0','0','0','3793','0','3793','0','Harutt','Warrior Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60063','0','0','0','0','0','3819','0','3819','0','Gart','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','0','0','0','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','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60064','0','0','0','0','0','3810','0','3810','0','Lanka','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60065','0','0','0','0','0','10180','0','10180','0','Meela','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60066','0','0','0','0','0','3794','0','3794','0','Krang','Warrior Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60067','0','0','0','0','0','10734','0','10734','0','Gennia','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60068','0','0','0','0','0','3811','0','3811','0','Yaw','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60069','0','0','0','0','0','3816','0','3816','0','Narm','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60070','0','0','0','0','0','1880','0','1880','0','Frang','Warrior Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60071','0','0','0','0','0','1882','0','1882','0','Jenshan','Hunter Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60072','0','0','0','0','0','1884','0','1884','0','Nartok','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60073','0','0','0','0','0','1878','0','1878','0','Shikrik','Shaman Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60074','0','0','0','0','0','3743','0','3743','0','Tarshaw','Warrior Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60075','0','0','0','0','0','3744','0','3744','0','Thotar','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60076','0','0','0','0','0','3745','0','3745','0','Dhugru','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60077','0','0','0','0','0','3746','0','3746','0','Swart','Shaman Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60078','0','0','0','0','0','1324','0','1324','0','Groldar','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60079','0','0','0','0','0','1325','0','1325','0','Mirket','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60080','0','0','0','0','0','1326','0','1326','0','Zevrost','Warlock Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60081','0','0','0','0','0','1360','0','1360','0','Kardris','Shaman Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60082','0','0','0','0','0','1373','0','1373','0','Ormak','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60083','0','0','0','0','0','1374','0','1374','0','Grezz','Warrior Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60084','0','0','0','0','0','1375','0','1375','0','Sorek','Warrior Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60085','0','0','0','0','0','4231','0','4231','0','Siantsu','Shaman Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60086','0','0','0','0','0','4239','0','4239','0','Xorjuul','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60087','0','0','0','0','0','4241','0','4241','0','Siandur','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60088','0','0','0','0','0','4242','0','4242','0','Zelmak','Warrior Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60089','0','0','0','0','0','7915','0','7915','0','ClaudeErksine','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60090','0','0','0','0','0','1721','0','1721','0','Alyissia','Warrior Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60091','0','0','0','0','0','1725','0','1725','0','FrahunMurmombre','Rogue Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60092','0','0','0','0','0','1733','0','1733','0','Shanda','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60093','0','0','0','0','0','1732','0','1732','0','Mardant','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60094','0','0','0','0','0','1707','0','1707','0','Kyra','Warrior Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60095','0','0','0','0','0','1704','0','1704','0','Jannok','Rogue Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60096','0','0','0','0','0','1708','0','1708','0','Laurna','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60097','0','0','0','0','0','1706','0','1706','0','Kal','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60098','0','0','0','0','0','4296','0','4296','0','Harruk','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60099','0','0','0','0','0','4299','0','4299','0','Reban','Hunter bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60100','0','0','0','0','0','4304','0','4304','0','Bolyun','Hunter Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60101','0','0','0','0','0','1897','0','1897','0','Taijin','Priest Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60102','0','0','0','0','0','4068','0','4068','0','Kenjai','Priest Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60103','0','0','0','0','0','2066','0','2066','0','Danlaar','Hunter Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60104','0','0','0','0','0','2196','0','2196','0','Ariasta','Warrior Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60105','0','0','0','0','0','2198','0','2198','0','Sildanair','Warrior Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60106','0','0','0','0','0','2200','0','2200','0','Astarii','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60107','0','0','0','0','0','2201','0','2201','0','Jandria','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60108','0','0','0','0','0','2202','0','2202','0','Lariia','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60109','0','0','0','0','0','2231','0','2231','0','Syurna','Rogue Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60110','0','0','0','0','0','7669','0','7669','0','Elissa','Mage Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60111','0','0','0','0','0','2252','0','2252','0','Erion','Rogue Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60112','0','0','0','0','0','2243','0','2243','0','Anishar','Rogue Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60113','0','0','0','0','0','2250','0','2250','0','Denatharion','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60114','0','0','0','0','0','2255','0','2255','0','Fylerian','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60115','0','0','0','0','0','2416','0','2416','0','Caelyb','Hunter Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60116','0','0','0','0','0','2675','0','2675','0','Kaal','Warlock Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60117','0','0','0','0','0','16800','0','16800','0','Lana','Warlock Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60118','0','0','0','0','0','2646','0','2646','0','Richard','Warlock Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60119','0','0','0','0','0','10214','0','10214','0','Kaelystia','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','0','0','0','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','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60120','0','0','0','0','0','2644','0','2644','0','Pierce','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60121','0','0','0','0','0','2657','0','2657','0','Anastasia','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60122','0','0','0','0','0','2620','0','2620','0','Chris','Warrior Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60123','0','0','0','0','0','2658','0','2658','0','Angela','Warrior Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60124','0','0','0','0','0','2614','0','2614','0','Baltus','Warrior Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60125','0','0','0','0','0','3054','0','3054','0','Kelv','Warrior Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60126','0','0','0','0','0','3055','0','3055','0','Bilban','Warrior Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60127','0','0','0','0','0','3056','0','3056','0','Daera','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60128','0','0','0','0','0','3072','0','3072','0','Olmin','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60129','0','0','0','0','0','3073','0','3073','0','Regnus','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60130','0','0','0','0','0','3086','0','3086','0','Theodrus','Priest Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60131','0','0','0','0','0','3066','0','3066','0','Braenna','Priest Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60132','0','0','0','0','0','3085','0','3085','0','Toldren','Priest Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60134','0','0','0','0','0','3108','0','3108','0','Bink','Mage Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60135','0','0','0','0','0','10214','0','10214','0','Juli','Mage Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60136','0','0','0','0','0','3109','0','3109','0','Nittegousse','Mage Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60137','0','0','0','0','0','3089','0','3089','0','Valgar','Paladin Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60138','0','0','0','0','0','3088','0','3088','0','Beldruk','Paladin Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60139','0','0','0','0','0','3087','0','3087','0','Brandur','Paladin Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60140','0','0','0','0','0','3101','0','3101','0','Hulfdan','Rogue Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60141','0','0','0','0','0','3100','0','3100','0','Ormyr','Rogue Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60142','0','0','0','0','0','3113','0','3113','0','Phenwick','Rogue Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60143','0','0','0','0','0','3115','0','3115','0','Coeurdechardon','Warlock Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60144','0','0','0','0','0','3116','0','3116','0','Eglantin','Warlock Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60145','0','0','0','0','0','3122','0','3122','0','Alexander','Warlock Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60146','0','0','0','0','0','3280','0','3280','0','Wu','Warrior Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60147','0','0','0','0','0','3287','0','3287','0','Ilsa','Warrior Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60148','0','0','0','0','0','3283','0','3283','0','Joshua','Priest Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60149','0','0','0','0','0','3284','0','3284','0','Arthur','Paladin Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60150','0','0','0','0','0','3289','0','3289','0','Katherine','Paladin Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60151','0','0','0','0','0','3291','0','3291','0','Deline','Warlock Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60152','0','0','0','0','0','3286','0','3286','0','Sandahl','Warlock Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60153','0','0','0','0','0','3292','0','3292','0','Jennea','Mage Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60154','0','0','0','0','0','19803','0','19803','0','Elsharin','Mage Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60155','0','0','0','0','0','3299','0','3299','0','Kaerbrus','Hunter Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60156','0','0','0','0','0','3300','0','3300','0','Sheldras','Druid Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60157','0','0','0','0','0','3301','0','3301','0','Theridran','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60158','0','0','0','0','0','3312','0','3312','0','Einris','Hunter Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60159','0','0','0','0','0','3309','0','3309','0','Ulfir','Hunter Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60160','0','0','0','0','0','3310','0','3310','0','Thorfin','Hunter Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60161','0','0','0','0','0','10171','0','10171','0','UnThuwa','Mage Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60162','0','0','0','0','0','4524','0','4524','0','Pephredo','Mage Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60163','0','0','0','0','0','4522','0','4522','0','Enyo','Mage Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60164','0','0','0','0','0','4526','0','4526','0','Mai','Mage Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60165','0','0','0','0','0','4523','0','4523','0','Deino','Mage Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60166','0','0','0','0','0','4665','0','4665','0','Birgitte','Mage Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60167','0','0','0','0','0','12849','0','12849','0','Thuul','Mage Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60168','0','0','0','0','0','4690','0','4690','0','Zayus','Priest Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60169','0','0','0','0','0','10473','0','10473','0','Xyera','Priest Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60170','0','0','0','0','0','4711','0','4711','0','Urkyo','Priest Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60171','0','0','0','0','0','6060','0','6060','0','Uthelnay','Mage Bot','','0','80','80','2','126','126','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','8','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60172','0','0','0','0','0','6072','0','6072','0','Dink','Mage Bot','','0','80','80','2','875','875','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','7','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60173','0','0','0','0','0','6071','0','6071','0','Darnath','Warrior Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60174','0','0','0','0','0','7356','0','7356','0','Karman','Paladin Bot','','0','80','80','2','894','894','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60175','0','0','0','0','0','11037','0','11037','0','Evencane','Warrior Bot','','0','80','80','2','894','894','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60176','0','0','0','0','0','7357','0','7357','0','Jannos','Druid Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60177','0','0','0','0','0','7538','0','7538','0','Alenndaar','Hunter Bot','','0','80','80','2','1076','1076','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60178','0','0','0','0','0','10738','0','10738','0','Golhine','Druid Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60179','0','0','0','0','0','9337','0','9337','0','Hesuwa','Hunter Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60180','0','0','0','0','0','9336','0','9336','0','Xao\'tsu','Hunter Bot','','0','80','80','2','29','29','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','2','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60181','0','0','0','0','0','9338','0','9338','0','Belia','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','3','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60182','0','0','0','0','0','10245','0','10245','0','Dargh','Hunter Bot','','0','80','80','2','55','55','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60183','0','0','0','0','0','11044','0','11044','0','Meideros','Priest Bot','','0','80','80','2','80','80','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60184','0','0','0','0','0','11048','0','11048','0','Presse','Priest Bot','','0','80','80','2','1076','1076','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60185','0','0','0','0','0','11053','0','11053','0','Rohan','Priest Bot','','0','80','80','2','122','122','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','3','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60186','0','0','0','0','0','12053','0','12053','0','Loganaar','Druid Bot','','0','80','80','2','994','994','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','4','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60187','0','0','0','0','0','13171','0','13171','0','Romano','Rogue Bot','','0','80','80','2','12','12','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','1','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60188','0','0','0','0','0','13341','0','13341','0','Sagorne','Shaman Bot','','0','80','80','2','104','104','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60189','0','0','0','0','0','15522','0','15522','0','Julia','Mage Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60190','0','0','0','0','0','15511','0','15511','0','Jesthenis','Paladin Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60191','0','0','0','0','0','15524','0','15524','0','Invocateur','Warlock Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60192','0','0','0','0','0','15518','0','15518','0','Matrone','Priest Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60193','0','0','0','0','0','2659','0','2659','0','Eclaireur','Rogue Bot','','0','80','80','2','68','68','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','5','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60194','0','0','0','0','0','15520','0','15520','0','Sallina','Hunter Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60195','0','0','0','0','0','16685','0','16685','0','Noellene','Paladin Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60196','0','0','0','0','0','16707','0','16707','0','Ponaris','Priest Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60197','0','0','0','0','0','16222','0','16222','0','Keilnei','Hunter Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60198','0','0','0','0','0','16223','0','16223','0','Valaatu','Mage Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60199','0','0','0','0','0','16224','0','16224','0','Aurelon','Paladin Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60200','0','0','0','0','0','16225','0','16225','0','Zalduun','Priest Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60201','0','0','0','0','0','16226','0','16226','0','Kore','Warrior Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60202','0','0','0','0','0','16787','0','16787','0','Alamma','Warlock Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60203','0','0','0','0','0','16800','0','16800','0','Talionia','Warlock Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3500','2000','8','0','0','0','0','0','0','9','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warlock_bot','0'),
+('60204','0','0','0','0','0','16831','0','16831','0','Zanien','Hunter Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','9','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60205','0','0','0','0','0','16781','0','16781','0','Zaedana','Mage Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60206','0','0','0','0','0','16824','0','16824','0','Quithas','Mage Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60207','0','0','0','0','0','16739','0','16739','0','Harene','Druid Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60208','0','0','0','0','0','16778','0','16778','0','Tana','Hunter Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60209','0','0','0','0','0','16816','0','16816','0','Oninath','Hunter Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60210','0','0','0','0','0','16829','0','16829','0','Bachi','Paladin Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60211','0','0','0','0','0','16767','0','16767','0','Zelanis','Rogue Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60212','0','0','0','0','0','16798','0','16798','0','Elara','Rogue Bot','','0','80','80','2','1604','1604','1','1.2','1.3','1','0','1','2','0','0','1','1600','2000','4','0','0','0','0','0','0','4','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','rogue_bot','0'),
+('60213','0','0','0','0','0','16858','0','16858','0','Shalannius','Druid Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','4','0','0','1','2200','2000','2','0','0','0','0','0','0','11','6','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','druid_bot','0'),
+('60214','0','0','0','0','0','17434','0','17434','0','Deremiis','Hunter Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60215','0','0','0','0','0','17247','0','17247','0','Caedmos','Priest Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60216','0','0','0','0','0','17225','0','17225','0','Baatun','Paladin Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60217','0','0','0','0','0','17212','0','17212','0','Ahonan','Warrior Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60218','0','0','0','0','0','17598','0','17598','0','Firmanvaar','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60219','0','0','0','0','0','16860','0','16860','0','Actron','Hunter Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60220','0','0','0','0','0','17213','0','17213','0','Behomat','Warrior Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60221','0','0','0','0','0','17600','0','17600','0','Nobundo','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60222','0','0','0','0','0','17599','0','17599','0','Tuluun','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60223','0','0','0','0','0','16914','0','16914','0','Sulaa','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60224','0','0','0','0','0','17215','0','17215','0','Ruada','Warrior Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','3','5','0','0','1','3400','2000','1','0','0','0','0','0','0','1','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','warrior_bot','0'),
+('60225','0','0','0','0','0','17233','0','17233','0','Semid','Mage Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60226','0','0','0','0','0','17232','0','17232','0','Guvan','Priest Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60227','0','0','0','0','0','17234','0','17234','0','Tullas','Paladin Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60228','0','0','0','0','0','17488','0','17488','0','Killac','Hunter bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','3','0','0','1','2800','2000','2','0','0','0','0','0','0','3','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','hunter_bot','0'),
+('60229','0','0','0','0','0','17226','0','17226','0','Jol','Paladin Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60230','0','0','0','0','0','17248','0','17248','0','Fallat','Priest Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','priest_bot','0'),
+('60231','0','0','0','0','0','17243','0','17243','0','Harnan','Mage Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60232','0','0','0','0','0','17241','0','17241','0','Bati','Mage Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','1','0','0','1','3800','2000','8','0','0','0','0','0','0','8','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','mage_bot','0'),
+('60233','0','0','0','0','0','17792','0','17792','0','Hobahken','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60234','0','0','0','0','0','6820','0','6820','0','Gurrag','Shaman Bot','','0','80','80','2','1638','1638','1','1.2','1.3','1','0','1','2','0','0','1','2600','2000','2','0','0','0','0','0','0','7','11','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','shaman_bot','0'),
+('60235','0','0','0','0','0','19596','0','19596','0','Auberose','Paladin Bot','','0','80','80','2','1602','1602','1','1.2','1.3','1','0','2','4','0','0','1','2300','2000','2','0','0','0','0','0','0','2','10','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','','0','3','1','1','1','1','0','0','0','0','0','0','0','0','0','1','1048688','paladin_bot','0'),
+('60236','0','0','0','0','0','10335','10335','10335','10335','Afina','Priest Bot','','0','80','80','2','35','35','1','1.2','1.3','1','0','1','1','0','0','1','3600','2000','8','0','0','0','0','0','0','5','2','0','0','0','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','0','0','0','0','0','0','0','0','1','1','1048688','priest_bot','0'),
+('60237','0','0','0','0','0','1132','0','1132','0','Voidwalker','Warlock\'s Pet Bot',NULL,'0','80','80','2','14','14','0','1.2','1.3','1','0','2','3','0','0','1','2000','2000','1','0','0','0','16','0','0','1','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','0','0','0','0','0','0','0','0','1','1','1048688','voidwalker_bot','0'),
+('60238','0','0','0','0','0','1105','0','0','0','Hunter\'s Pet',NULL,NULL,'0','80','80','0','14','14','0','1.1','1.14286','1','0','87','117','0','214','1','2000','0','1','0','0','0','7','0','0','1','0','61','90','21','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','0','0','0','0','0','0','0','149','1','0','0','','0');
+
+-- EQUIPS --
+
+delete from `creature_equip_template` where entry between 60001 and 60238;
+
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60001','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60002','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60003','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60004','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60005','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60006','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60007','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60008','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60009','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60010','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60011','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60012','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60013','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60014','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60015','1','7723','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60016','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60017','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60018','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60019','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60020','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60021','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60022','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60023','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60024','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60025','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60026','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60027','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60028','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60029','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60030','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60031','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60032','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60033','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60034','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60035','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60036','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60037','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60038','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60039','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60041','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60042','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60043','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60044','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60045','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60046','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60047','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60048','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60049','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60050','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60051','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60052','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60053','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60054','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60055','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60056','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60057','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60058','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60059','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60060','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60061','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60062','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60063','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60064','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60065','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60066','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60067','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60068','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60069','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60070','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60071','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60072','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60073','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60074','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60075','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60076','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60077','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60078','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60079','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60080','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60081','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60082','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60083','1','7723','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60084','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60085','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60086','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60087','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60088','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60089','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60090','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60091','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60092','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60093','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60094','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60095','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60096','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60097','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60098','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60099','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60100','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60101','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60102','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60103','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60104','1','7723','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60105','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60106','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60107','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60108','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60109','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60110','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60111','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60112','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60113','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60114','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60115','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60116','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60117','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60118','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60119','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60120','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60121','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60122','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60123','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60124','1','7723','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60125','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60126','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60127','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60128','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60129','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60130','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60131','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60132','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60134','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60135','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60136','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60137','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60138','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60139','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60140','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60141','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60142','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60143','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60144','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60145','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60146','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60147','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60148','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60149','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60150','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60151','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60152','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60153','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60154','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60155','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60156','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60157','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60158','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60159','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60160','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60161','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60162','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60163','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60164','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60165','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60166','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60167','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60168','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60169','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60170','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60171','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60172','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60173','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60174','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60175','1','7723','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60176','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60177','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60178','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60179','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60180','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60181','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60182','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60183','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60184','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60185','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60186','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60187','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60188','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60189','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60191','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60192','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60193','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60194','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60195','1','12584','18826','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60196','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60197','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60198','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60199','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60200','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60201','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60202','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60203','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60204','1','31186','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60205','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60206','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60207','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60208','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60209','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60210','1','12584','18826','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60211','1','6633','820','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60212','1','13984','6448','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60213','1','25622','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60214','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60215','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60216','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60217','1','28367','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60218','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60219','1','2291','0','2825');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60220','1','18002','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60221','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60222','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60223','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60224','1','27903','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60225','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60226','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60227','1','12584','18825','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60229','1','18876','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60230','1','31289','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60231','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60232','1','18842','0','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60233','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60234','1','18203','18202','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60235','1','29175','18826','0');
+insert into `creature_equip_template` (`entry`, `id`, `itemEntry1`, `itemEntry2`, `itemEntry3`) values('60236','1','31289','0','0');
+
+
+
+-- Customize section
+-- You can create your own values to be in line with your own server if these are not acceptable.
+
+-- Add flags_extra
+SET @EX_NO_BLOCK = 16;
+SET @EX_NO_CRUSH = 32;
+SET @EX_NO_XP = 64;
+SET @EX_DIMINISH = 1048576;
+SET @FLAGS_EX = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH;
+
+-- minions
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=2, maxdmg:=4, minlevel:=80, maxlevel:=80, baseattacktime:=2200, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Druid Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=2, maxdmg:=3, minlevel:=80, maxlevel:=80, baseattacktime:=2800, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Hunter Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=1, maxdmg:=1, minlevel:=80, maxlevel:=80, baseattacktime:=3800, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Mage Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=2, maxdmg:=4, minlevel:=80, maxlevel:=80, baseattacktime:=2300, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Paladin Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=1, maxdmg:=1, minlevel:=80, maxlevel:=80, baseattacktime:=3600, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Priest Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=1, maxdmg:=2, minlevel:=80, maxlevel:=80, baseattacktime:=1600, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Rogue Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=1, maxdmg:=2, minlevel:=80, maxlevel:=80, baseattacktime:=2600, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Shaman Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=1, maxdmg:=1, minlevel:=80, maxlevel:=80, baseattacktime:=3500, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Warlock Bot';
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:=3, maxdmg:=5, minlevel:=80, maxlevel:=80, baseattacktime:=3400, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry >= 60000 && entry < 60239 and subname='Warrior Bot';
+
+-- 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
+-- 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
+-- 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
+-- 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
+-- 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
+
+-- pets
+UPDATE `creature_template` SET exp:=2, dmg_multiplier:=1.0, attackpower:=0, mindmg:= 2, maxdmg:= 3, minlevel:=80, maxlevel:=80, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, InhabitType:=3, health_mod:=1, mana_mod:=1, armor_mod:=1, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EX, AIName='' where entry between 60001 and 60239 and name='Voidwalker';
+
+
diff --git a/sql/Bots/world_script_bot_giver.sql b/sql/Bots/world_script_bot_giver.sql
new file mode 100644
index 0000000..20d0ecd
--- /dev/null
+++ b/sql/Bots/world_script_bot_giver.sql
@@ -0,0 +1,13 @@
+delete from `creature_template` where entry = 60000;
+
+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_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
+('60000','0','0','0','0','0','27541','0','27541','0','Lagretta','Bot Officer','','0','83','83','2','35','35','1','1.4','1.14286','0.4','4','228','298','0','500','1','1500','0','1','0','2048','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','script_bot_giver','0');
+
+
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..c2dda46
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_GridNotifiers.h
@@ -0,0 +1,462 @@
+/*
+Name: bot_GridNotifiers
+%Complete: 91+
+Category: creature_cripts/custom/bots/grids
+*/
+
+#ifndef _BOT_GRIDNOTIFIERS_H
+#define _BOT_GRIDNOTIFIERS_H
+
+#include "Group.h"
+#include "Player.h"
+#include "SpellAuras.h"
+#include "bot_ai.h"
+#include "bp_ai.h"
+
+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) { }
+ bool operator()(Unit* u)
+ {
+ if (!me->IsWithinDistInMap(u, m_range))
+ return false;
+ if (!u->isInCombat())
+ return false;
+ if (ai ? !ai->CanBotAttack(u, byspell) : !PlayerbotAI::CanPlayerbotAttack(const_cast<Player*>(me->ToPlayer()), u, byspell))
+ return false;
+ if (ai ? ai->InDuel(u) : PlayerbotAI::IsUnitInDuel(u, const_cast<Player*>(me->ToPlayer())->GetPlayerbotAI()->GetMaster()))
+ 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 ? ai->IsInBotParty(u->getVictim()) : PlayerbotAI::IsUnitInPlayersParty(u->getVictim(), const_cast<Player*>(me->ToPlayer())->GetPlayerbotAI()->GetMaster())) || (u->getThreatManager().getThreat(const_cast<Unit*>(me)) > 0.f && u->HasUnitState(UNIT_STATE_FLEEING))))
+ 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;
+ 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* u)
+ {
+ if (u->IsWithinDistInMap(me, m_range) &&
+ u->isAlive() &&
+ u->InSamePhase(me) &&
+ u->isInCombat() &&
+ u->isTargetableForAttack() &&
+ u->IsVisible() &&
+ u->GetReactionTo(me) < REP_NEUTRAL &&
+ ((ai ? ai->IsInBotParty(u->getVictim()) : PlayerbotAI::IsUnitInPlayersParty(u->getVictim(), const_cast<Player*>(me->ToPlayer())->GetPlayerbotAI()->GetMaster())) || u->getThreatManager().getThreat(const_cast<Unit*>(me)) > 0.f))
+ {
+ 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();
+ for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr)
+ {
+ Aura* aura = itr->second;
+ SpellInfo const* Info = aura->GetSpellInfo();
+ if (Info->Dispel != DISPEL_MAGIC) continue;
+ if (Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue;
+ if (checksteal && (Info->AttributesEx4 & SPELL_ATTR4_NOT_STEALABLE)) continue;
+ AuraApplication* aurApp = aura->GetApplicationOfTarget(u->GetGUID());
+ if (aurApp && aurApp->IsPositive())
+ {
+ const std::string name = Info->SpellName[0];
+ if (name == "Vengeance" || name == "Bloody Vengeance")
+ continue;
+ 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(uint64 casterguid, float dist, uint32 spellId, Player const* groupCheck = 0, uint8 hostileCheckType = 0) :
+ caster(casterguid), m_range(dist), spell(spellId), checker(groupCheck), needhostile(hostileCheckType)
+ { if (checker->GetTypeId() != TYPEID_PLAYER) return; gr = checker->GetGroup(); }
+ bool operator()(Unit* u)
+ {
+ 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:
+ uint64 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* u)
+ {
+ 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 || u->GetHealthPct() < 70)
+ return false;
+ if (!u->InSamePhase(me))
+ return false;
+ if (!u->isTargetableForAttack())
+ return false;
+ if (!u->IsVisible())
+ return false;
+ if (!u->getAttackers().empty())
+ 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 (!u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(12826)))//Polymorph rank 4
+ 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* u)
+ {
+ 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->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 (!u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(5782)))//fear 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* u)
+ {
+ 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 (!(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->GetDistance(u) < 10)//do not allow close cast to prevent break due to consecration
+ return false;
+ if (u->IsPolymorphed() ||
+ u->HasAura(51514)/*hex*/ ||
+ u->HasAura(20066)/*repentance*/ ||
+ u->HasAuraWithMechanic(1<<MECHANIC_SHACKLE)/*shackleundead*/)
+ return false;
+ if (!u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(20066)))//repentance
+ 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* u)
+ {
+ 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* u)
+ {
+ 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<<MECHANIC_SHACKLE)/*shackleundead*/)
+ return false;
+ if (!u->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 dist = 30, bool friendly = false, uint32 spell = 0) : me(unit), m_range(dist), m_friend(friendly), m_spell(spell) { if (!m_spell) return; }
+ bool operator()(Unit* u)
+ {
+ if (!me->IsWithinDistInMap(u, m_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->IsNonMeleeSpellCasted(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 m_range;
+ bool m_friend;
+ uint32 m_spell;
+ CastingUnitCheck(CastingUnitCheck const&);
+};
+
+class SecondEnemyCheck
+{
+ public:
+ explicit SecondEnemyCheck(Unit const* unit, float dist, Unit const* currtarget, bot_ai const* m_ai) : me(unit), m_range(dist), mytar(currtarget), ai(m_ai) {}
+ bool operator()(Unit* u)
+ {
+ 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) > 4)//not close enough to each other
+ return false;
+
+ if (ai ? ai->CanBotAttack(u) : PlayerbotAI::CanPlayerbotAttack(const_cast<Player*>(me->ToPlayer()), u))
+ return true;
+
+ return false;
+ }
+ private:
+ Unit const* me;
+ float m_range;
+ Unit const* mytar;
+ bot_ai const* ai;
+ SecondEnemyCheck(SecondEnemyCheck const&);
+};
+
+#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..72c4c73
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_ai.cpp
@@ -0,0 +1,3185 @@
+/*
+Original patch (npcbot part only) from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch
+TODO:
+Convert doCast events (CD etc.) into SpellHit()- and SpellHitTarget()-based
+Implement heal/tank/DD modes
+Implement Racial Abilities
+Implement Equipment Change (maybe)
+I NEED MORE
+*/
+
+#include "bot_ai.h"
+#include "bot_GridNotifiers.h"
+#include "CellImpl.h"
+#include "Chat.h"
+#include "Config.h"
+#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "ScriptedGossip.h"
+#include "SpellAuraEffects.h"
+
+const uint8 GroupIconsFlags[TARGETICONCOUNT] =
+{
+ /*STAR = */0x001,
+ /*CIRCLE = */0x002,
+ /*DIAMOND = */0x004,
+ /*TRIANGLE = */0x008,
+ /*MOON = */0x010,
+ /*SQUARE = */0x020,
+ /*CROSS = */0x040,
+ /*SKULL = */0x080
+};
+
+bot_minion_ai::bot_minion_ai(Creature* creature): bot_ai(creature)
+{
+ Potion_cd = 0;
+ pvpTrinket_cd = 30000;
+ rezz_cd = 0;
+ evade_cd = 0;
+ myangle = 0.f;
+ classinfo = new PlayerClassLevelInfo;
+}
+bot_minion_ai::~bot_minion_ai()
+{
+ 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)
+{
+ master = me->GetBotOwner();
+ m_spellpower = 0;
+ haste = 0;
+ hit = 0.f;
+ regen_mp5 = 0.f;
+ m_TankGuid = 0;
+ tank = NULL;
+ extank = NULL;
+ info = NULL;
+ clear_cd = 2;
+ temptimer = 0;
+ wait = 15;
+ GC_Timer = 0;
+ checkAurasTimer = 20;
+ cost = 0;
+ doHealth = false;
+ doMana = false;
+ //shouldUpdateStats = true;
+ pos.m_positionX = 0.f;
+ pos.m_positionY = 0.f;
+ pos.m_positionZ = 0.f;
+ aftercastTargetGuid = 0;
+ currentSpell = 0;
+ dmgmult_melee = ConfigMgr::GetFloatDefault("Bot.DamageMult.Melee", 1.0);
+ dmgmult_spell = ConfigMgr::GetFloatDefault("Bot.DamageMult.Spell", 1.0);
+ dmgmult_melee = std::max(dmgmult_melee, 0.01f);
+ dmgmult_spell = std::max(dmgmult_spell, 0.01f);
+ dmgmult_melee = std::min(dmgmult_melee, 10.f);
+ dmgmult_spell = std::min(dmgmult_spell, 10.f);
+ dmgmod_melee = Creature::_GetDamageMod(me->GetCreatureTemplate()->rank);
+ dmgmod_spell = me->GetSpellDamageMod(me->GetCreatureTemplate()->rank);
+ healTargetIconFlags = ConfigMgr::GetIntDefault("Bot.HealTargetIconsMask", 8);
+}
+bot_ai::~bot_ai(){}
+
+SpellCastResult bot_ai::checkBotCast(Unit* victim, uint32 spellId, uint8 botclass) const
+{
+ if (spellId == 0) return SPELL_FAILED_DONT_REPORT;
+ if (!CheckImmunities(spellId, victim)) return SPELL_FAILED_DONT_REPORT;
+ if (InDuel(victim)) return SPELL_FAILED_DONT_REPORT;
+
+ switch (botclass)
+ {
+ case CLASS_MAGE:
+ case CLASS_PRIEST:
+ case CLASS_DRUID:
+ case CLASS_WARLOCK:
+ case CLASS_SHAMAN:
+ if (Feasting() && !master->isInCombat() && !master->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))
+ return SPELL_FAILED_DONT_REPORT;
+ return SPELL_CAST_OK;
+ case CLASS_PALADIN:
+ //Crusader Strike
+ if (spellId != 35395 && spellId != MANAPOTION && spellId != HEALINGPOTION && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
+ return SPELL_FAILED_DONT_REPORT;
+ return SPELL_CAST_OK;
+ case CLASS_WARRIOR:
+ //BladeStorm
+ if (me->HasAura(46924/*67541*/))
+ return SPELL_FAILED_DONT_REPORT;
+ return SPELL_CAST_OK;
+ case CLASS_ROGUE:
+ case CLASS_HUNTER:
+ case CLASS_DEATH_KNIGHT:
+ default:
+ return SPELL_CAST_OK;
+ }
+}
+
+bool bot_ai::doCast(Unit* victim, uint32 spellId, bool triggered, uint64 originalCaster)
+{
+ if (spellId == 0) return false;
+ if (me->IsMounted()) return false;
+ if (IsCasting()) return false;
+ if (!victim || !victim->IsInWorld() || me->GetMap() != victim->FindMap()) return false;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ return false;
+ info = spellInfo;
+
+ 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;
+ }
+
+ if (me->GetShapeshiftForm() != FORM_NONE && info->CheckShapeshift(me->GetShapeshiftForm()) != SPELL_CAST_OK)
+ removeFeralForm(true, true);
+
+ if (spellId != HEALINGPOTION && spellId != MANAPOTION)
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+
+ if (!victim->IsWithinLOSInMap(me) && IsInBotParty(victim))
+ {
+ //std::ostringstream msg;
+ //msg << "casting " << spellInfo->SpellName[0] << " on " << victim->GetName();
+ //me->MonsterWhisper(msg.str().c_str(), master->GetGUID());
+ me->Relocate(victim);
+ }
+
+ TriggerCastFlags flags = triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE;
+ SpellCastTargets targets;
+ targets.SetUnitTarget(victim);
+ Spell* spell = new Spell(me, info, 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 (Spell* curspell = me->GetCurrentSpell(i))
+ if (curspell == spell)
+ casted = true;
+
+ if (!casted)
+ {
+ //failed to cast
+ //delete spell;//crash due to invalid event added to master's eventmap
+ return false;
+ }
+
+ currentSpell = spellId;
+
+ switch (me->GetBotClass())
+ {
+ case CLASS_ROGUE:
+ case CAT:
+ value = int32(1000.f - 1000.f*(float(haste) / 100.f));
+ break;
+ default:
+ value = int32(1500.f - 1500.f*(float(haste) / 100.f));
+ break;
+ }
+ GC_Timer = std::max<uint32>(value, 500);
+
+ return true;
+}
+//Follow point calculation
+void bot_minion_ai::CalculatePos(Position & pos)
+{
+ uint8 followdist = master->GetBotFollowDist();
+ Unit* followTarget = master;
+ float mydist, angle;
+ if (master->GetBotTankGuid() == me->GetGUID())
+ {
+ mydist = frand(3.5f, 6.5f);
+ angle = (M_PI/2.f) / 16.f * frand(-3.f, 3.f);
+ }
+ else
+ {
+ switch (me->GetBotClass())
+ {
+ case CLASS_WARRIOR: case CLASS_DEATH_KNIGHT: case CLASS_PALADIN: case BEAR:
+ mydist = frand(0.2f, 1.f);//(1.f, 3.f);//RAND(1.f,1.5f,2.f,2.5f,3.f,3.5f);
+ angle = (M_PI/2.f) / 8.f * RAND(frand(5.f, 10.f), frand(-10.f, -5.f));//RAND(6.5f,7.f,7.5f,8.f,8.5f,9.f,9.5f,-6.5f,-7.f,-7.5f,-8.f,-8.5f,-9.f,-9.5f);
+ break;
+ case CLASS_WARLOCK: case CLASS_PRIEST: case CLASS_MAGE: case CAT:
+ mydist = frand(0.15f, 0.8f);//(0.5f, 2.f);//RAND(0.5f,1.f,1.5f,2.f);
+ angle = (M_PI/2.f) / 6.f * frand(10.5f, 13.5f);//RAND(10.5f,11.f,11.5f,12.f,12.5f,13.f,13.5f);
+ break;
+ default:
+ mydist = frand(0.3f, 1.2f);//(2.5f, 4.f);//RAND(2.5f,3.f,3.5f,4.f);
+ angle = (M_PI/2.f) / 6.f * frand(9.f, 15.f);//RAND(9.f,10.f,11.f,12.f,13.f,14.f,15.f);
+ break;
+ }
+ }
+ if (abs(abs(myangle) - abs(angle)) > M_PI/3.f)
+ myangle = angle;
+ else
+ angle = myangle;
+ mydist += followdist > 10 ? float(followdist - 10)/4.f : 0.f;
+ mydist = std::min<float>(mydist, 35.f);
+ angle += followTarget->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;
+ }
+ followTarget->GetNearPoint(me, x, y, z, size, mydist, angle);
+ if (!master->IsWithinLOS(x,y,z))
+ {
+ mydist *= 0.4f - float(i*0.07f);
+ size *= 0.1f;
+ if (size < 0.1)
+ size = 0.f;
+ if (size == 0.f && me->GetPositionZ() < followTarget->GetPositionZ())
+ z += 0.25f;
+ }
+ else
+ over = true;
+ }
+ pos.m_positionX = x;
+ pos.m_positionY = y;
+ pos.m_positionZ = z;
+
+ // T
+ // TTT
+ // mmmmmmmm mmmmmmmm
+ // mmmmmmm MMM mmmmmmm
+ // mmmmm rrrrrrr mmmmm
+ // ddd rrrrrrrrr ddd
+ // ddddddddddddddd
+ // ddddddddddd
+ //
+ //MMM - player
+ //TTT - bot tank
+ //m - melee (warrior, paladin, deathknight)
+ //d - default (druid, shaman, rogue, hunter)
+ //r - ranged/support (priest, warlock, mage)
+}
+// Movement set
+void bot_minion_ai::SetBotCommandState(CommandStates st, bool force, Position* newpos)
+{
+ if (me->isDead() || IAmDead())
+ return;
+ if (st == COMMAND_FOLLOW && ((!me->isMoving() && !IsCasting() && master->isAlive()) || force))
+ {
+ if (CCed(me, true)/* || master->HasUnitState(UNIT_STATE_FLEEING)*/) 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->StopMoving();
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ }
+ 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 (st == COMMAND_FOLLOW && ((!me->isMoving() && !IsCasting() && master->isAlive()) || force))
+ {
+ if (CCed(me, true)) 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->StopMoving();
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MoveIdle();
+ }
+ else if (st == COMMAND_ATTACK)
+ { }
+ m_botCommandState = st;
+}
+// Get Maintank
+void bot_ai::FindTank()
+{
+ if (tank == me)
+ extank = me;
+ //check group flags in DB
+ tank = _GetBotGroupMainTank(master->GetGroup());
+ //check if master has set tank
+ if (!tank)
+ tank = master->GetBotTankGuid() != 0 ? sObjectAccessor->GetObjectInWorld(master->GetBotTankGuid(), (Unit*)NULL) : NULL;
+ //check if we have tank flag in master's motmap
+ if (!tank)
+ tank = master->GetBotTank(me->GetEntry());
+ //at last try to find tank by class if master is too lazy to set it
+ if (!tank)
+ {
+ Player* owner = master;
+ uint8 Class = owner->getClass();
+ if (owner->isAlive() &&
+ (Class == CLASS_WARRIOR || Class == CLASS_PALADIN || Class == CLASS_DEATH_KNIGHT))
+ tank = owner;
+ else if (owner != master && master->isAlive())
+ {
+ Class = master->getClass();
+ if (Class == CLASS_WARRIOR || Class == CLASS_PALADIN || Class == CLASS_DEATH_KNIGHT)
+ tank = master;
+ }
+ }
+ //it happens to every bot so they all will know who the tank is
+ if (tank != extank)
+ me->SetBotTank(tank);
+ if (tank == me)
+ {
+ //if tank set by entry let master get right guid and set tank in botmap
+ if (master->GetBotTankGuid() != me->GetGUID())
+ master->SetBotTank(me->GetGUID());
+ }
+}
+//Get Group maintank
+Unit* bot_ai::_GetBotGroupMainTank(Group* group)
+{
+ if (!group)
+ return NULL;
+
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAINTANK);
+ stmt->setUInt32(0, group->GetGUID());
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+ //QueryResult result = CharacterDatabase.PQuery("SELECT memberGuid, memberFlags FROM `group_member` WHERE `guid`='%u'", group->GetGUID());
+ if (!result)
+ return NULL;
+ Unit* unit = NULL;
+ do
+ {
+ Field* field = result->Fetch();
+ uint32 lowGuid = field[0].GetInt32();
+ uint8 flags = field[1].GetInt8();
+ if (flags & MEMBER_FLAG_MAINTANK)
+ {
+ Group::MemberSlotList const &members = group->GetMemberSlots();
+ for (Group::MemberSlotList::const_iterator itr = members.begin(); itr != members.end(); ++itr)
+ if (GUID_LOPART(itr->guid) == lowGuid)
+ unit = ObjectAccessor::FindUnit(itr->guid);
+ }
+ } while (result->NextRow() && !unit);
+ return unit;
+}
+// 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 I'm already casting
+
+ Group* pGroup = gPlayer->GetGroup();
+ if (!pGroup)
+ {
+ if (!master->IsInWorld() || master->IsBeingTeleported())
+ return;
+ if (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->isDead()) continue;
+ if (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->isDead()) continue;
+ if (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->isDead()) continue;
+ if (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
+ for (uint8 i = 0; i != TARGETICONCOUNT; ++i)
+ {
+ if (healTargetIconFlags & GroupIconsFlags[i])
+ {
+ if (uint64 guid = pGroup->GetTargetIcons()[i])//check this one
+ {
+ if (Unit* unit = sObjectAccessor->FindUnit(guid))
+ {
+ if (unit->isAlive() && me->GetMap() == unit->FindMap() &&
+ master->getVictim() != unit && unit->getVictim() != master &&
+ unit->GetReactionTo(master) >= REP_NEUTRAL)
+ {
+ HealTarget(unit, GetHealthPCT(unit), diff);
+ //CureTarget(unit, getCureSpell(), diff);
+ }
+ }
+ }
+ }
+ }
+}
+// Attempt to resurrect dead players using class spells
+// Targets either player or its corpse
+void bot_minion_ai::RezGroup(uint32 REZZ, Player* gPlayer)
+{
+ if (!REZZ || !gPlayer || me->IsMounted()) return;
+ if (IsCasting()) return; // if I'm already casting
+ if (rezz_cd > 0) return;
+
+ //sLog->outBasic("RezGroup by %s", me->GetName().c_str());
+ Group* pGroup = gPlayer->GetGroup();
+ if (!pGroup)
+ {
+ Unit* target = master;
+ if (master->isAlive()) return;
+ if (master->isRessurectRequested()) return; //ressurected
+ if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ target = (Unit*)master->GetCorpse();
+ 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
+ {
+ me->MonsterWhisper("Rezzing You", master->GetGUID());
+ 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->isRessurectRequested()) continue; //ressurected
+ if (Rand() > 5) continue;
+ if (tPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ target = (Unit*)tPlayer->GetCorpse();
+ 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
+ {
+ me->MonsterWhisper("Rezzing You", tPlayer->GetGUID());
+ if (tPlayer != master)
+ {
+ std::string rezstr = "Rezzing ";
+ rezstr += tPlayer->GetName();
+ me->MonsterWhisper(rezstr.c_str(), master->GetGUID());
+ }
+ 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 (!master->GetMap()->IsRaid() && Rand() > 75) return;
+ //sLog->outBasic("%s: CureGroup() on %s", me->GetName().c_str(), pTarget->GetName().c_str());
+ Group* pGroup = pTarget->GetGroup();
+ if (!pGroup)
+ {
+ if (CureTarget(master, cureSpell, diff))
+ return;
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = master->GetBotMap(i)->_Cre();
+ 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;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = tPlayer->GetBotMap(i)->_Cre();
+ 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->isDead()) 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;
+ target->GetDispellableAuraList(me, dispelMask, dispel_list);
+ if (dispel_list.empty())
+ return false;
+ return true;
+}
+
+bool bot_ai::HasAuraName(Unit* unit, uint32 spellId, uint64 casterGuid, bool exclude) const
+{
+ if (!spellId) return false;
+ SpellInfo const* pSpellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!pSpellInfo) return false;
+
+ uint8 loc = master->GetSession()->GetSessionDbcLocale();
+ const std::string name = pSpellInfo->SpellName[loc];
+ if (name.length() == 0) return false;
+
+ return HasAuraName(unit, name, casterGuid, exclude);
+}
+
+bool bot_ai::HasAuraName(Unit* unit, const std::string spell, uint64 casterGuid, bool exclude) const
+{
+ if (spell.length() == 0) return false;
+
+ uint8 loc = master->GetSession()->GetSessionDbcLocale();
+ if (!unit || unit->isDead()) return false;
+
+ Unit::AuraMap const& vAuras = unit->GetOwnedAuras();
+ for (Unit::AuraMap::const_iterator itr = vAuras.begin(); itr != vAuras.end(); ++itr)
+ {
+ SpellInfo const* spellInfo = itr->second->GetSpellInfo();
+ const std::string name = spellInfo->SpellName[loc];
+ if (spell == name)
+ if (casterGuid == 0 || (casterGuid != 0 && 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 (!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: ";
+ std::string const& ownername = unit->ToCreature()->GetBotOwner()->GetName();
+ botstring << ownername;
+ }
+ else if (unit->ToCreature()->GetIAmABotsPet())
+ {
+ Player* owner = unit->ToCreature()->GetBotOwner();
+ Creature* creowner = unit->ToCreature()->GetBotPetAI()->GetCreatureOwner();
+ std::string const& ownername = owner ? 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->GetGUIDLow() << ')';
+ }
+ }
+ ch.PSendSysMessage("ListAuras for %s, %s", unit->GetName().c_str(), 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();
+ if (rank > 0)
+ spellmsg << " Rank " << rank;
+ if (talentcost > 0)
+ spellmsg << " [talent]";
+ if (spellInfo->IsPassive())
+ spellmsg << " [passive]";
+ if (unit->GetTypeId() == TYPEID_PLAYER && unit->ToPlayer()->HasSpell(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;
+ }
+ ch.PSendSysMessage("%s: %f", mystat.c_str(), unit->GetTotalStatValue(Stats(i)));
+ }
+ ch.PSendSysMessage("Melee AP: %f", unit->GetTotalAttackPowerValue(BASE_ATTACK));
+ ch.PSendSysMessage("Ranged AP: %f", unit->GetTotalAttackPowerValue(RANGED_ATTACK));
+ ch.PSendSysMessage("armor: %u", unit->GetArmor());
+ ch.PSendSysMessage("crit: %f pct", unit->GetUnitCriticalChance(BASE_ATTACK, me));
+ ch.PSendSysMessage("dodge: %f pct", unit->GetUnitDodgeChance());
+ ch.PSendSysMessage("parry: %f pct", unit->GetUnitParryChance());
+ ch.PSendSysMessage("Damage taken melee: %f", unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_NORMAL));
+ ch.PSendSysMessage("Damage taken spell: %f", unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_MAGIC));
+ ch.PSendSysMessage("Damage range mainhand: min: %f, max: %f", unit->GetFloatValue(UNIT_FIELD_MINDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXDAMAGE));
+ ch.PSendSysMessage("Damage range offhand: min: %f, max: %f", unit->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE));
+ ch.PSendSysMessage("Damage range ranged: min: %f, max: %f", unit->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), unit->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE));
+ ch.PSendSysMessage("Damage mult mainhand: %f", unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT));
+ ch.PSendSysMessage("Damage mult offhand: %f", unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT));
+ ch.PSendSysMessage("Damage mult ranged: %f", unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT)*unit->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT));
+ ch.PSendSysMessage("Attack time mainhand: %f", float(unit->GetAttackTime(BASE_ATTACK))/1000.f);
+ ch.PSendSysMessage("Attack time offhand: %f", float(unit->GetAttackTime(OFF_ATTACK))/1000.f);
+ ch.PSendSysMessage("Attack time ranged: %f", float(unit->GetAttackTime(RANGED_ATTACK))/1000.f);
+ if (unit == me)
+ ch.PSendSysMessage("melee damage mult: %f", dmgmult_melee);
+ 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));
+ //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("spellpower: %u", m_spellpower - m_spellpower % 50);
+ ch.PSendSysMessage("spell damage mult: %f", dmgmult_spell);
+ ch.PSendSysMessage("mana regen: %f", regen_mp5 - (int32(regen_mp5) % 45));
+ ch.PSendSysMessage("haste: %u *10 pct", haste);
+ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++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, me->GetResistance(SpellSchools(i)));
+ }
+ 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");
+ ch.PSendSysMessage("Follow distance: %u", master->GetBotFollowDist());
+ //ch.PSendSysMessage("healTargetIconFlags: %u", healTargetIconFlags);
+ if (tank != NULL && tank->IsInWorld())
+ {
+ if (tank == me)
+ ch.PSendSysMessage("Is a MainTank!");
+ else
+ ch.PSendSysMessage("Maintank is %s", tank->GetName().c_str());
+ }
+ //debug
+ //if (IsPetAI()) GetPetAI()->ListSpells(&ch);
+ }
+}
+//SETSTATS
+// Health, Armor, Powers, Combat Ratings, and global update setup
+void bot_minion_ai::setStats(uint8 myclass, uint8 myrace, uint8 mylevel, bool force)
+{
+ if (myrace == 0 || myclass == 0) return;
+ if (myclass != BEAR && myclass != CAT && (master->isDead() || (!shouldUpdateStats && !force))) return;
+ /*sLog->outBasic("setStats(): Updating bot %s, class: %u, race: %u, level %u, master: %s",
+ me->GetName().c_str(), myclass, myrace, mylevel, master->GetName().c_str());*/
+
+ mylevel = std::min<uint8>(mylevel, 80);
+
+ //LEVEL
+ if (me->getLevel() != mylevel)
+ {
+ me->SetLevel(mylevel);
+ force = true; //restore powers on lvl update
+ }
+ if (force)
+ InitSpells();
+
+ //PHASE
+ if (!me->InSamePhase(master))
+ 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 == BEAR || myclass == CAT ? CLASS_DRUID : myclass;
+ sObjectMgr->GetPlayerClassLevelInfo(tempclass, mylevel, classinfo);
+ const CreatureBaseStats* const classstats = sObjectMgr->GetCreatureBaseStats(mylevel, me->getClass());//use creature class
+ float value;
+ if (force)
+ for (uint8 i = STAT_STAMINA; i < MAX_STATS; i++)
+ me->SetCreateStat(Stats(i), master->GetCreateStat(Stats(i)));
+
+ //MAXSTAT
+ for (uint8 i = 0; i < MAX_STATS; ++i)
+ {
+ value = master->GetTotalStatValue(Stats(i));
+ if (i == 0 || value > stat)
+ stat = value;//Get Hightest stat (on first cycle just set base value)
+ }
+ stat = std::max(stat - 18.f, 0.f);
+
+ //INIT CLASS MODIFIERS
+ switch (myclass)
+ {
+ case CLASS_WARRIOR: ap_mod = 1.3f; spp_mod = 0.0f; armor_mod = 1.4f; crit_mod = 1.0f; haste_mod = 0.75f; dodge_mod = 0.75f; parry_mod = 1.75f; break;
+ case CLASS_DEATH_KNIGHT: ap_mod = 1.2f; spp_mod = 1.0f; armor_mod = 1.15f; crit_mod = 0.9f; haste_mod = 0.65f; dodge_mod = 0.8f; parry_mod = 2.0f; break;//NYI
+ case CLASS_PALADIN: ap_mod = 1.0f; spp_mod = 0.8f; armor_mod = 1.2f; crit_mod = 0.8f; haste_mod = 0.85f; dodge_mod = 0.7f; parry_mod = 1.5f; break;
+ case CLASS_ROGUE: ap_mod = 1.5f; spp_mod = 0.0f; armor_mod = 0.7f; crit_mod = 1.5f; haste_mod = 1.35f; dodge_mod = 1.5f; parry_mod = 0.8f; break;//NYI
+ case CLASS_HUNTER: ap_mod = 1.15f; spp_mod = 0.0f; armor_mod = 0.85f; crit_mod = 1.2f; haste_mod = 1.25f; dodge_mod = 1.2f; parry_mod = 1.2f; break;//NYI
+ case CLASS_SHAMAN: ap_mod = 0.9f; spp_mod = 1.0f; armor_mod = 0.9f; crit_mod = 1.2f; haste_mod = 1.65f; dodge_mod = 0.8f; parry_mod = 0.5f; break;//NYI
+ case CLASS_DRUID: ap_mod = 0.0f; spp_mod = 1.3f; armor_mod = 0.7f; crit_mod = 0.7f; haste_mod = 1.95f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ case CLASS_MAGE: ap_mod = 0.0f; spp_mod = 0.8f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ case CLASS_PRIEST: ap_mod = 0.0f; spp_mod = 1.2f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ case CLASS_WARLOCK: ap_mod = 0.0f; spp_mod = 1.0f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ case BEAR: ap_mod = 2.0f; spp_mod = 1.3f; armor_mod = 2.25f; crit_mod = 1.0f; haste_mod = 0.75f; dodge_mod = 2.5f; parry_mod = 0.0f; break;
+ case CAT: ap_mod = 1.5f; spp_mod = 1.3f; armor_mod = 1.1f; crit_mod = 1.5f; haste_mod = 2.25f; dodge_mod = 1.35f; parry_mod = 0.0f; break;
+ default: ap_mod = 0.0f; spp_mod = 0.0f; armor_mod = 0.0f; crit_mod = 0.0f; haste_mod = 0.00f; dodge_mod = 0.0f; parry_mod = 0.0f; break;
+ }
+ if (spp_mod != 0.f && mylevel > 39)
+ spp_mod *= (float(mylevel - 39))/41.f;// gain spell power slowly
+
+ //DAMAGE
+ _OnMeleeDamageUpdate(myclass);
+
+ //ARMOR
+ //sLog->outBasic("Unpdating %s's ARMOR: ", me->GetName().c_str());
+ //sLog->outBasic("armor mod: %f", armor_mod);
+ armor_mod *= (master->GetModifierValue(UNIT_MOD_ARMOR, BASE_PCT) + master->GetModifierValue(UNIT_MOD_ARMOR, TOTAL_PCT))/2.f;
+ //sLog->outBasic("armor mod * master's modifier: %f", armor_mod);
+ value = float(classstats->BaseArmor);
+ //sLog->outBasic("base armor: %f", value);
+ value += float(master->GetArmor())/5.f;
+ //sLog->outBasic("base armor + 1/5 of master's armor: %f", value);
+ value *= armor_mod;
+ //sLog->outBasic("multiplied by armor mod (total base armor): %f", value);
+ me->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, value);
+ me->UpdateArmor();//buffs will be took in consideration here
+
+ //RESISTANCES
+ //sLog->outBasic("Unpdating %s's RESISTANCES: ", me->GetName().c_str());
+ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i)
+ {
+ value = float(master->GetResistance(SpellSchools(i)));
+ //sLog->outBasic("master's resistance %u: %f, setting %f (triple) to bot", uint32(UNIT_MOD_RESISTANCE_START + i), value, value*3);
+ me->SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, value*2.5f + float(mylevel*2));
+ //me->SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_PCT, 1.f);
+ me->UpdateResistances(i);
+ }
+ //DAMAGE TAKEN
+ float directReduction = master->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_NORMAL);
+ float magicReduction = master->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_MAGIC);
+ value = (directReduction + magicReduction)/2.f;// average
+ if (value > 1.f)
+ value -= 1.f;
+ else
+ value = 1.f - value;//get reduction even if master's is 1.0+
+ value = std::min(0.42f, value);
+ value/= 0.01f; //here we get percent like 0.42/0.01 = 42% (value * 100.f)
+ if (mylevel > 77)
+ value += float(mylevel - 77)*6.f;// + 3 stacks for high level
+ RefreshAura(DMG_TAKEN, int8(value/6.f));//so max aura count = 10
+
+ //HEALTH
+ _OnHealthUpdate(myclass, mylevel);
+
+ //HASTE
+ value = 0.f;
+ for (uint8 i = CR_HASTE_MELEE; i != CR_HASTE_SPELL + 1; ++i)
+ if (float rating = master->GetRatingBonusValue(CombatRating(i)))
+ if (rating > value)//master got some haste
+ value = rating;//get hightest pct
+ for (uint8 i = EQUIPMENT_SLOT_BACK; i < EQUIPMENT_SLOT_END; ++i)
+ if (Item* item = master->GetItemByPos(0, i))//inventory weapons
+ if (item->GetTemplate()->ItemLevel >= 277)//bears ICC 25H LK items or Wrathful items
+ value += 10.f;//only weapons so we can add 1 to 3 stacks (rogue, warr, sham...)
+ value *= haste_mod;
+ if (isMeleeClass(myclass))
+ value *= 0.67f;//nerf melee haste by 1/3
+ value = value/10.f + float(mylevel/39);//get bonus at 78
+ if (myclass == CAT)//give cat lots of haste
+ value += float(mylevel/16);//or 20 (+ 4-5 stacks);
+ RefreshAura(HASTE, uint8(value));//spell haste
+ RefreshAura(HASTE2, uint8(value + 1*(myclass == CLASS_ROGUE)));//melee haste
+ haste = uint8(value);//for show only
+
+ //HIT
+ int32 melee_hit = master->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE) + master->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE);
+ int32 spell_hit = master->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE) + master->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT, SPELL_SCHOOL_MASK_SPELL);
+ value = float(melee_hit > spell_hit ? melee_hit : spell_hit)*1.5f;//hightest, buff hit chance for bots
+ hit = value/3.f;
+ RefreshAura(PRECISION, int8(hit) + mylevel/39);//melee
+ RefreshAura(PRECISION2, int8(hit) + mylevel/39);//spell
+
+ //CRIT
+ //chose melee or ranged cuz crit rating increases melee/spell, and hunter benefits from agility
+ value = master->GetUnitCriticalChance((master->getClass() == CLASS_HUNTER ? RANGED_ATTACK : BASE_ATTACK), me);
+ value = value > 5.f ? value - 5.f : 0.f;//remove base chance if can
+ value *= crit_mod;
+ RefreshAura(CRITS, int8(value/5.f) + mylevel/39);
+ if (myclass == CLASS_PRIEST)
+ RefreshAura(HOLYCRIT, int8(value/7.f));//add holy crit to healers
+
+ //PARRY
+ value = master->GetFloatValue(PLAYER_PARRY_PERCENTAGE);
+ value = value > 5.f ? value - 5.f : 0.f;//remove base chance if possible
+ value *= parry_mod;
+ if (master->GetBotTankGuid() == me->GetGUID() && myclass != CAT && myclass != BEAR)//feral cannot parry so let it be base 5%
+ value += 10.f;
+ if (value > 55.f)
+ value = 55.f;
+ float parryAndDodge = value;//set temp value, this is needed to keep total avoidance within 65%
+ RefreshAura(PARRY, int8(value/5.f));
+
+ //DODGE
+ value = master->GetUnitDodgeChance();
+ value = value > 5.f ? value - 5.f : 0.f;//remove base chance if possible
+ value *= dodge_mod;
+ if (master->GetBotTankGuid() == me->GetGUID())
+ value += 10.f;
+ if (value > 55.f)
+ value = 55.f;
+ if (parryAndDodge + value > 55.f)
+ value = 55.f - parryAndDodge;//do not allow avoidance to be more than 65% (base 5+5)
+ if (myclass == CLASS_ROGUE)
+ value += 6.f;
+ RefreshAura(DODGE, int8(value/5.f));
+
+ //MANA
+ _OnManaUpdate(myclass, mylevel);
+
+ //MANA REGEN
+ if (mylevel >= 40 && me->getPowerType() == POWER_MANA)
+ {
+ regen_mp5 = master->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER);
+ //regen_mp5 = (master->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) + sqrt(master->GetStat(STAT_INTELLECT)) * master->OCTRegenMPPerSpirit()) / 5.f;
+ //Unit::AuraEffectList const& regenAura = master->GetAuraEffectsByType(SPELL_AURA_MOD_MANA_REGEN_FROM_STAT);
+ //for (Unit::AuraEffectList::const_iterator i = regenAura.begin(); i != regenAura.end(); ++i)
+ // regen_mp5 += master->GetStat(Stats((*i)->GetMiscValue())) * (*i)->GetAmount() / 500.f;
+ //regen_mp5 *= 0.8f;//custom modifier
+ float regen_mp5_a = stat * 0.2f;
+ //regen_mp5 += master->GetTotalStatValue(STAT_SPIRIT) * 0.1f;
+ regen_mp5 = regen_mp5 > regen_mp5_a ? regen_mp5 : regen_mp5_a;
+ if (regen_mp5 >= 45.f)
+ {
+ me->RemoveAurasDueToSpell(MANAREGEN100);
+ me->RemoveAurasDueToSpell(MANAREGEN45);
+ if (regen_mp5 > 200.f) RefreshAura(MANAREGEN100,int8(regen_mp5/100.f) + mylevel/20);
+ else/*if (regen_mp5 > 150.f)*/RefreshAura(MANAREGEN45, int8(regen_mp5/45.f) + mylevel/20);
+ }
+ }
+
+ //SPELL POWER
+ if (mylevel >= 40 && spp_mod != 0.f)
+ {
+ //sLog->outBasic("Updating spellpower for %s:", me->GetName().c_str());
+ //sLog->outBasic("spp_mod: %f", spp_mod);
+ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i)
+ {
+ int32 power = master->SpellBaseDamageBonusDone(SpellSchoolMask(1 << i));
+ if (power > sppower || i == SPELL_SCHOOL_HOLY)
+ sppower = power;
+ }
+ //sppower = master->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC);//"Spell Power" stat
+ //sLog->outBasic("Master's spell power: %i", sppower);
+ atpower = master->GetTotalAttackPowerValue(master->getClass() == CLASS_HUNTER ? RANGED_ATTACK : BASE_ATTACK);
+ atpower *= 0.67f;
+ //sLog->outBasic("Master's 2/3 of attack power: %f", atpower);
+ m_spellpower = sppower > atpower ? sppower : atpower;
+ //sLog->outBasic("Chosen stat value: %i", m_spellpower);
+ m_spellpower = int32(float(m_spellpower)*spp_mod);
+ //sLog->outBasic("spellpower * mod: %i", m_spellpower);
+ if (myclass == CLASS_MAGE)
+ RefreshAura(FIREDAM_86, m_spellpower/4/86 + (mylevel >= 78)*2); //(86,172,258,344,430,516,602,688...) // fire spp, do not touch this
+ me->RemoveAurasDueToSpell(SPELL_BONUS_250);
+ me->RemoveAurasDueToSpell(SPELL_BONUS_150);
+ me->RemoveAurasDueToSpell(SPELL_BONUS_50);
+ if (mylevel < 60) RefreshAura(SPELL_BONUS_50, m_spellpower/50);
+ else if (mylevel < 80) RefreshAura(SPELL_BONUS_150, m_spellpower/150 + 1);
+ else RefreshAura(SPELL_BONUS_250, m_spellpower/250 + 2);
+ }
+
+ 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(mylevel, bot_pet_ai::GetPetType(pet), force);
+
+ shouldUpdateStats = false;
+}
+void bot_pet_ai::setStats(uint8 mylevel, uint8 petType, bool force)
+{
+ if (petType == PET_TYPE_NONE || petType >= MAX_PET_TYPES) return;
+ if (!shouldUpdateStats && !force) return;
+ //sLog->outError(LOG_FILTER_PLAYER, "setStats(): 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)
+ InitSpells();
+
+ //PHASE
+ if (!me->InSamePhase(master))
+ me->SetPhaseMask(master->GetPhaseMask(), true);
+
+ ////INIT STATS
+ uint8 botclass = m_creatureOwner->GetBotClass();
+ if (botclass == BEAR || botclass == CAT)
+ botclass = CLASS_DRUID;
+ //sObjectMgr->GetPlayerClassLevelInfo(botclass, m_creatureOwner->getLevel(), &classinfo);
+ //const CreatureBaseStats* const classstats = sObjectMgr->GetCreatureBaseStats(mylevel, me->GetBotClass());//use creature class
+ //if (force)
+ // for (uint8 i = STAT_STRENGTH; i < MAX_STATS; i++)
+ // me->SetCreateStat(Stats(i), master->GetCreateStat(Stats(i))*0.5f);
+
+ //MAXSTAT
+ float value;
+ for (uint8 i = 0; i < MAX_STATS; ++i)
+ {
+ value = master->GetTotalStatValue(Stats(i));
+ if (i == 0 || value > stat)
+ stat = value;//Get Hightest stat (on first cycle just set base value)
+ }
+ stat = std::max(stat - 18.f, 0.f);//remove base
+
+ //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
+ //Other x1.0 -- use as default
+ 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: ap_mod = 0.0f; spp_mod = 0.0f; crit_mod = 0.0f; break;
+ }
+ //case CLASS_WARRIOR: ap_mod = 1.3f; spp_mod = 0.0f; armor_mod = 1.4f; crit_mod = 1.0f; haste_mod = 0.75f; dodge_mod = 0.75f; parry_mod = 1.75f; break;
+ //case CLASS_DEATH_KNIGHT: ap_mod = 1.2f; spp_mod = 1.0f; armor_mod = 1.15f; crit_mod = 0.9f; haste_mod = 0.65f; dodge_mod = 0.8f; parry_mod = 2.0f; break;//NYI
+ //case CLASS_PALADIN: ap_mod = 1.0f; spp_mod = 0.8f; armor_mod = 1.2f; crit_mod = 0.8f; haste_mod = 0.85f; dodge_mod = 0.7f; parry_mod = 1.5f; break;
+ //case CLASS_ROGUE: ap_mod = 1.5f; spp_mod = 0.0f; armor_mod = 0.7f; crit_mod = 1.5f; haste_mod = 1.35f; dodge_mod = 1.5f; parry_mod = 0.8f; break;//NYI
+ //case CLASS_HUNTER: ap_mod = 1.15f; spp_mod = 0.0f; armor_mod = 0.85f; crit_mod = 1.2f; haste_mod = 1.25f; dodge_mod = 1.2f; parry_mod = 1.2f; break;//NYI
+ //case CLASS_SHAMAN: ap_mod = 0.9f; spp_mod = 1.0f; armor_mod = 0.9f; crit_mod = 1.2f; haste_mod = 1.65f; dodge_mod = 0.8f; parry_mod = 0.5f; break;//NYI
+ //case CLASS_DRUID: ap_mod = 0.0f; spp_mod = 1.3f; armor_mod = 0.7f; crit_mod = 0.7f; haste_mod = 1.95f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ //case CLASS_MAGE: ap_mod = 0.0f; spp_mod = 0.8f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ //case CLASS_PRIEST: ap_mod = 0.0f; spp_mod = 1.2f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ //case CLASS_WARLOCK: ap_mod = 0.0f; spp_mod = 1.0f; armor_mod = 0.5f; crit_mod = 0.7f; haste_mod = 1.75f; dodge_mod = 0.5f; parry_mod = 0.0f; break;
+ //case BEAR: ap_mod = 2.0f; spp_mod = 1.3f; armor_mod = 2.25f; crit_mod = 1.0f; haste_mod = 0.75f; dodge_mod = 2.5f; parry_mod = 0.0f; break;
+ //case CAT: ap_mod = 1.5f; spp_mod = 1.3f; armor_mod = 1.1f; crit_mod = 1.5f; haste_mod = 2.25f; dodge_mod = 1.35f; parry_mod = 0.0f; break;
+
+ if (spp_mod != 0.f && mylevel > 39)
+ spp_mod *= (float(mylevel - 39))/41.f;// gain spell power slowly
+
+ //DAMAGE
+ if (ap_mod > 0.f)//do not bother casters
+ {
+ switch (m_creatureOwner->GetBotClass())
+ {
+ case CLASS_WARLOCK:
+ value = float(m_creatureOwner->GetBotAI()->GetSpellPower());
+ break;
+ default://some weird class or NYI
+ value = 0.f;
+ break;
+ }
+ //Calculate ap
+ //set base strength
+ me->SetModifierValue(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, me->GetCreateStat(STAT_STRENGTH) - 9.f);
+ //calc attack power (strength and minion's spd)
+ atpower = me->GetTotalAuraModValue(UNIT_MOD_STAT_STRENGTH)*2.f + value*ap_mod;
+ //set value
+ 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();//buffs will be took in consideration here
+
+ //RESISTANCES
+ //based on minion's resistances gain x0.4
+ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i)
+ {
+ value = float(master->GetResistance(SpellSchools(i)));
+ me->SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, 0.4f*(value*2.5f + float(mylevel*2)));
+ me->UpdateResistances(i);
+ }
+
+ //DAMAGE TAKEN
+ //just get minion's reduction and apply to pet
+ value = m_creatureOwner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_NORMAL);
+ if (value > 1.f)
+ value -= 1.f;
+ else
+ value = 1.f - value;//get reduction even if owner's is 1.0+
+ value = std::min(0.42f, value);
+ value/= 0.01f; //here we get percent like 0.42/0.01 = 42% (value * 100.f)
+ RefreshAura(DMG_TAKEN, int8(value/6.f));//so max aura count = 10
+
+ //HEALTH
+ _OnHealthUpdate(petType, mylevel);
+
+//////RATINGS//////
+ //ok now, pet receives 100% of its master's ratings
+
+ //HASTE
+ haste = m_creatureOwner->GetBotAI()->GetHaste();
+ RefreshAura(HASTE, haste);//spell haste
+ RefreshAura(HASTE2, haste);//melee haste
+
+ //HIT
+ hit = m_creatureOwner->GetBotAI()->GetHitRating();
+ RefreshAura(PRECISION, int8(hit) + mylevel/39);//melee
+ RefreshAura(PRECISION2, int8(hit) + mylevel/39);//spell
+
+ //CRIT
+ //chose melee or ranged cuz crit rating increases melee/spell, and hunter benefits from agility
+ value = master->GetUnitCriticalChance((master->getClass() == CLASS_HUNTER ? RANGED_ATTACK : BASE_ATTACK), me);
+ if (crit_mod != 1.0f)
+ value *= crit_mod;
+ RefreshAura(CRITS, int8(value/5.f) + mylevel/39);
+
+ //PARRY
+ value = master->GetFloatValue(PLAYER_PARRY_PERCENTAGE);
+ if (master->GetBotTankGuid() == me->GetGUID())//feral cannot parry so let it be base 5%
+ value += 10.f;
+ if (value > 65.f)
+ value = 65.f;
+ float parryAndDodge = value;//set temp value, this is needed to keep total avoidance within 75%
+ RefreshAura(PARRY, int8(value/5.f));
+
+ //DODGE
+ value = master->GetUnitDodgeChance();
+ value = value > 5.f ? value - 5.f : 0.f;//remove base chance if possible
+ if (master->GetBotTankGuid() == me->GetGUID())
+ value += 10.f;
+ if (value > 65.f)
+ value = 65.f;
+ if (parryAndDodge + value > 65.f)
+ value = 65.f - parryAndDodge;//do not allow avoidance to be more than 75% (base 5+5)
+ RefreshAura(DODGE, int8(value/5.f));
+
+ //MANA
+ _OnManaUpdate(petType, mylevel);
+
+ //MANA REGEN
+ if (mylevel >= 40 && me->getPowerType() == POWER_MANA)
+ {
+ //let regen rate be same as stats rate x0.3
+ regen_mp5 = m_creatureOwner->GetBotAI()->GetManaRegen()*0.3f;
+ if (regen_mp5 >= 45.f)
+ {
+ me->RemoveAurasDueToSpell(MANAREGEN100);
+ me->RemoveAurasDueToSpell(MANAREGEN45);
+ if (regen_mp5 > 200.f) RefreshAura(MANAREGEN100,int8(regen_mp5/100.f) + mylevel/20);
+ else/*if (regen_mp5 > 150.f)*/RefreshAura(MANAREGEN45, int8(regen_mp5/45.f) + mylevel/20);
+ }
+ }
+
+ //SPELL POWER
+ if (mylevel >= 40 && spp_mod != 0.f)
+ {
+ switch (m_creatureOwner->GetBotClass())
+ {
+ case CLASS_WARLOCK:
+ value = float(m_creatureOwner->GetBotAI()->GetSpellPower());
+ break;
+ default://some weird class or NYI
+ value = 0.f;
+ break;
+ }
+ m_spellpower = int32(value*spp_mod);
+ me->RemoveAurasDueToSpell(SPELL_BONUS_250);
+ me->RemoveAurasDueToSpell(SPELL_BONUS_150);
+ me->RemoveAurasDueToSpell(SPELL_BONUS_50);
+ if (mylevel < 60) RefreshAura(SPELL_BONUS_50, m_spellpower/50);
+ else if (mylevel < 80) RefreshAura(SPELL_BONUS_150, m_spellpower/150 + 1);
+ else RefreshAura(SPELL_BONUS_250, m_spellpower/250 + 2);
+ }
+
+ 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);
+ me->MonsterWhisper("Standing Still.", player->GetGUID());
+ break;
+ case TEXT_EMOTE_WAVE:
+ if (!IsMinionAI())
+ return;
+ if (master != player)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE);
+ return;
+ }
+ SetBotCommandState(COMMAND_FOLLOW, true);
+ me->MonsterWhisper("Following!", player->GetGUID());
+ 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* unit) const
+{
+ if (!unit) return false;
+ if (unit == me || unit == master) return true;
+
+ //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 (uint64 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* cre = unit->ToCreature())
+ {
+ //npcbot/npcbot's pet case
+ if (Player* owner = cre->GetBotOwner())
+ {
+ if (owner == master)
+ return true;
+ }
+ //pets, minions, guardians etc.
+ else
+ {
+ uint64 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 spell, int8 count, Unit* target) const
+{
+ if (!spell)
+ return false;
+ if (!target)
+ target = me;
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell);
+ if (!spellInfo)
+ return false;
+ //if (!spellInfo->IsPassive())
+ //{
+ // sLog->outError(LOG_FILTER_PLAYER, "bot_ai::RefreshAura(): %s received spell %u (%s) which is not a passive spell!", target->GetName().c_str(), spell, spellInfo->SpellName[0]);
+ // //return false;
+ //}
+ if (target->HasAura(spell))
+ target->RemoveAurasDueToSpell(spell);
+ if (count > 0)
+ for (uint8 i = 0; i < count; ++i)
+ target->AddAura(spellInfo, MAX_EFFECT_MASK, target);
+ return true;
+}
+//CHECKAURAS
+//Updates bot's condition once a while
+void bot_minion_ai::CheckAuras(bool force)
+{
+ if (checkAurasTimer > 0 && !force) return;
+ if (checkAurasTimer == 0)
+ {
+ checkAurasTimer = 10 + master->GetNpcBotsCount()/2;
+ if (m_botCommandState != COMMAND_FOLLOW && m_botCommandState != COMMAND_STAY)
+ {
+ opponent = me->getVictim();
+ if (opponent)
+ {
+ switch (me->GetBotClass())
+ {
+ case CLASS_MAGE: case CLASS_DRUID: case CLASS_WARLOCK: case CLASS_PRIEST:/* case CLASS_SHAMAN:*/
+ CalculateAttackPos(opponent, attackpos);
+ if (me->GetDistance(attackpos) > 8)
+ GetInPosition(true, true, opponent, &attackpos);
+ break;
+ default:
+ if (me->GetDistance(opponent) > 1.5f)
+ GetInPosition(true, false);
+ break;
+ }
+ }
+ }
+ if (shouldUpdateStats)
+ setStats(me->GetBotClass(), me->getRace(), master->getLevel());
+ else
+ {
+ UpdateHealth();
+ UpdateMana();
+ }
+ if (rezz_cd > 0)
+ --rezz_cd;
+ if (clear_cd > 0)
+ --clear_cd;
+ else
+ {
+ FindTank();
+ clear_cd = 15;
+ }
+ return;
+ }
+ else if (force)
+ {
+ if (!opponent)
+ {
+ 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())
+ {
+ 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);
+ }
+ UpdateMountedState();
+ UpdateStandState();
+ UpdateRations();
+ }
+}
+void bot_pet_ai::CheckAuras(bool /*force*/)
+{
+ if (checkAurasTimer > 0) return;
+ checkAurasTimer = 10 + 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, true, opponent, &attackpos);
+ break;
+ default:
+ if (me->GetDistance(opponent) > 1.5f)
+ GetInPosition(true, false);
+ break;
+ }
+ }
+ }
+ if (clear_cd > 0)
+ --clear_cd;
+ else
+ {
+ FindTank();
+ clear_cd = 15;
+ }
+ return;
+}
+
+bool bot_ai::CanBotAttack(Unit* target, int8 byspell) const
+{
+ if (!target) return false;
+ uint8 followdist = master->GetBotFollowDist();
+ float foldist = _getAttackDistance(float(followdist));
+ return
+ (target->isAlive() &&
+ target->IsVisible() &&
+ (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'
+//uses follow distance if range isn't set
+Unit* bot_ai::getTarget(bool byspell, bool ranged, bool &reset) const
+{
+ //check if no need to change target
+ Unit* u = master->getVictim();
+ Unit* mytar = me->getVictim();
+ if (!mytar && IsMinionAI())
+ if (Creature* pet = me->GetBotsPet())
+ mytar = pet->getVictim();
+
+ if (u && u == mytar)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "bot %s continues attack common target %s", me->GetName().c_str(), u->GetName().c_str());
+ return u;//forced
+ }
+ //Follow if...
+ uint8 followdist = master->GetBotFollowDist();
+ float foldist = _getAttackDistance(float(followdist));
+ if (!u && master->isAlive() && (me->GetDistance(master) > foldist || (mytar && master->GetDistance(mytar) > foldist && me->GetDistance(master) > foldist)))
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "bot %s cannot attack target %s, too far away", me->GetName().c_str(), mytar ? mytar->GetName().c_str() : "");
+ return NULL;
+ }
+
+ if (u && (master->isInCombat() || u->isInCombat()) && !InDuel(u) && !IsInBotParty(u))
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "bot %s starts attack master's target %s", me->GetName().c_str(), u->GetName().c_str());
+ return u;
+ }
+
+ if (CanBotAttack(mytar, byspell) && !InDuel(mytar))
+ {
+ //sLog->outError(LOG_FILTER_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
+ Group* gr = master->GetGroup();
+ if (!gr)
+ {
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = master->GetBotMap(i)->_Cre();
+ 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))
+ {
+ //sLog->outError(LOG_FILTER_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))
+ {
+ //sLog->outError(LOG_FILTER_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))
+ {
+ //sLog->outError(LOG_FILTER_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;
+ for (uint8 i = 0; i != pl->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = pl->GetBotMap(i)->_Cre();
+ 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))
+ {
+ //sLog->outError(LOG_FILTER_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))
+ {
+ //sLog->outError(LOG_FILTER_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 <NearestHostileUnitCheck> searcher(master, t, check);
+ me->VisitNearbyWorldObject(maxdist, searcher);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <NearestHostileUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <NearestHostileUnitCheck>, GridTypeMapContainer > grid_unit_searcher(searcher);
+ cell.Visit(p, world_unit_searcher, *master->GetMap(), *master, maxdist);
+ cell.Visit(p, grid_unit_searcher, *master->GetMap(), *master, maxdist);
+ }
+ }
+
+ if (t && opponent && t != opponent)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "bot %s has Found new target %s", me->GetName().c_str(), t->GetName().c_str());
+ reset = true;
+ }
+ return t;
+}
+//'CanAttack' function
+bool bot_ai::CheckAttackTarget(uint8 botOrPetType)
+{
+ bool byspell = false, ranged = false, reset = false;
+ if (IsMinionAI())
+ {
+ switch (botOrPetType)
+ {
+ case CLASS_DRUID:
+ byspell = me->GetShapeshiftForm() == FORM_NONE ||
+ me->GetShapeshiftForm() == FORM_TREE ||
+ me->GetShapeshiftForm() == FORM_MOONKIN;
+ ranged = byspell;
+ break;
+ case CLASS_PRIEST:
+ case CLASS_MAGE:
+ case CLASS_WARLOCK:
+ case CLASS_SHAMAN:
+ byspell = true;
+ ranged = true;
+ break;
+ case CLASS_HUNTER:
+ ranged = true;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch (botOrPetType)
+ {
+ case PET_TYPE_IMP:
+ byspell = true;
+ ranged = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ opponent = getTarget(byspell, ranged, reset);
+ if (!opponent)
+ {
+ me->AttackStop();
+ 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 = 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 > 30 ? 30.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, bool ranged, Unit* newtarget, Position* mypos)
+{
+ if (me->HasUnitState(UNIT_STATE_ROOT)) return;
+ if (!newtarget)
+ newtarget = me->getVictim();
+ if (!newtarget)
+ return;
+ if ((!newtarget->isInCombat() || m_botCommandState == COMMAND_STAY) && !force)
+ return;
+ if (IsCasting())
+ return;
+ uint8 followdist = master->GetBotFollowDist();
+ if (ranged)
+ {
+ if (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) > 8)
+ me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos);
+ }
+ else
+ me->GetMotionMaster()->MoveChase(newtarget);
+ if (newtarget != me->getVictim())
+ me->Attack(newtarget, !ranged);
+}
+
+bool bot_ai::MoveBehind(Unit& target) const
+{
+ if (me->HasUnitState(UNIT_STATE_ROOT)) return false;
+ if (target.IsWithinCombatRange(me, ATTACK_DISTANCE) &&
+ target.HasInArc(M_PI, me) &&
+ tank != me &&
+ (me->GetBotClass() == 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()
+{
+ //DEBUG
+ if (master->IsMounted() && me->IsMounted())
+ {
+ if ((master->HasAuraType(SPELL_AURA_FLY) || master->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING)))
+ {
+ //creature don't benefit from mount flight speed, so force it
+ if (me->GetSpeed(MOVE_FLIGHT) != master->GetSpeed(MOVE_FLIGHT)/2)
+ me->SetSpeed(MOVE_FLIGHT, master->GetSpeed(MOVE_FLIGHT)/2);
+ }
+ return;
+ }
+ bool aura = me->HasAuraType(SPELL_AURA_MOUNTED);
+ bool mounted = me->IsMounted();
+ if ((!master->IsMounted() || aura != mounted || (me->isInCombat() && opponent)) && (aura || mounted))
+ {
+ me->RemoveAurasByType(SPELL_AURA_MOUNTED);
+ me->Dismount();
+ return;
+ }
+ //END DEBUG
+ if (me->isInCombat() || IsCasting() || me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) //IsInWater() is too much calculations
+ return;
+ //fly
+ //if ((master->IsMounted() && master->HasAuraType(SPELL_AURA_FLY))/* || master->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING)*/)
+ //{
+ // if (!me->IsMounted() || !me->HasAuraType(SPELL_AURA_FLY))
+ // {
+ // //if (me->GetBotClass() == CLASS_DRUID && InitSpell(FLY_FORM))//TODO
+ // //{
+ // //}
+ // //else
+ // {
+ // uint32 mount = 0;
+ // Unit::AuraEffectList const &mounts = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
+ // if (!mounts.empty())
+ // mount = mounts.front()->GetId();
+ // if (mount)
+ // {
+ // if (me->HasAuraType(SPELL_AURA_MOUNTED))
+ // me->RemoveAurasByType(SPELL_AURA_MOUNTED);
+ // if (doCast(me, mount))
+ // {
+ // if (Feasting())
+ // {
+ // me->RemoveAurasDueToSpell(DRINK);
+ // me->RemoveAurasDueToSpell(EAT);
+ // }
+ // }
+ // }
+ // }
+ // }
+ //}
+ ////ground
+ /*else */
+ 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())
+ 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 (doCast(me, mount))
+ {
+ return;
+ }
+ }
+ }
+}
+//STANDSTATE
+void bot_minion_ai::UpdateStandState() const
+{
+ 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 ((master->getStandState() == UNIT_STAND_STATE_SIT || Feasting()) && !me->isInCombat() && !me->isMoving() &&
+ me->getStandState() == UNIT_STAND_STATE_STAND)
+ me->SetStandState(UNIT_STAND_STATE_SIT);
+
+}
+//RATIONS
+void bot_minion_ai::UpdateRations() const
+{
+ if (me->isInCombat() || CCed(me))
+ {
+ if (me->HasAura(EAT)) me->RemoveAurasDueToSpell(EAT);
+ if (me->HasAura(DRINK)) me->RemoveAurasDueToSpell(DRINK);
+ }
+
+ //drink
+ if (me->getPowerType() == POWER_MANA && !me->IsMounted() && !me->isMoving() && !CCed(me) &&
+ !me->isInCombat() && !IsCasting() && urand(0, 100) < 20 && GetManaPCT(me) < 80 &&
+ !me->HasAura(DRINK))
+ {
+ me->CastSpell(me, DRINK);
+ me->SetStandState(UNIT_STAND_STATE_SIT);
+ }
+ if (me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA) && me->HasAura(DRINK))
+ me->ModifyPower(POWER_MANA, me->GetCreateMana()/20);
+
+ //eat
+ if (!me->IsMounted() && !me->isMoving() && !CCed(me) &&
+ !me->isInCombat() && !IsCasting() && urand(0, 100) < 20 && GetHealthPCT(me) < 80 &&
+ !me->HasAura(EAT))
+ {
+ me->CastSpell(me, EAT);
+ me->SetStandState(UNIT_STAND_STATE_SIT);
+ }
+ if (me->GetHealth() < me->GetMaxHealth() && me->HasAura(EAT))
+ me->SetHealth(me->GetHealth() + me->GetCreateHealth()/20);
+
+ //check
+ if (me->GetHealth() >= me->GetMaxHealth() && me->HasAura(EAT))
+ me->RemoveAurasDueToSpell(EAT);
+
+ if (me->getPowerType() == POWER_MANA &&
+ me->GetPower(POWER_MANA) >= me->GetMaxPower(POWER_MANA) &&
+ me->HasAura(DRINK))
+ me->RemoveAurasDueToSpell(DRINK);
+}
+//PASSIVES
+// Used to apply common passives (run once)
+void bot_ai::ApplyPassives(uint8 botOrPetType) const
+{
+ //me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true);
+ //me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
+
+ //apply +healing taken
+ if (master->getLevel() >= 60) RefreshAura(BOR);//+40%
+ if (IsMinionAI())
+ {
+ //apply -threat mod
+ switch (botOrPetType)
+ {
+ case CLASS_WARRIOR:
+ RefreshAura(RCP,1);//-27%
+ break;
+ case CLASS_PRIEST:
+ case CLASS_MAGE:
+ case CLASS_ROGUE:
+ RefreshAura(RCP,3);//-87%
+ break;
+ default:
+ RefreshAura(RCP,2);//-54%
+ break;
+ }
+ }
+ else
+ {
+ switch (botOrPetType)
+ {
+ case PET_TYPE_VOIDWALKER:
+ RefreshAura(DEFENSIVE_STANCE_PASSIVE,2);
+ break;
+ default:
+ break;
+ }
+ }
+}
+//check if our party players are in duel. if so - ignore them, their opponents and any bots they have
+bool bot_ai::InDuel(Unit* target) const
+{
+ if (!target) return false;
+ bool isbot = target->GetTypeId() == TYPEID_UNIT && (target->ToCreature()->GetIAmABot() || target->ToCreature()->GetIAmABotsPet());
+ Player* player = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer() : isbot ? target->ToCreature()->GetBotOwner() : NULL;
+ if (!player)
+ {
+ if (!target->IsControlledByPlayer())
+ return false;
+ player = target->GetCharmerOrOwnerPlayerOrPlayerItself();
+ }
+
+ return (player && player->duel && (IsInBotParty(player) || IsInBotParty(player->duel->opponent)));
+}
+//Used to find target for priest's dispels and mage's spellsteal (also shaman's purge in future)
+//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 <HostileDispelTargetCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <HostileDispelTargetCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <HostileDispelTargetCheck>, 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) <default>
+// 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, uint64 caster, float dist, uint8 hostile) const
+{
+ 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 <AffectedTargetCheck> searcher(master, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <AffectedTargetCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <AffectedTargetCheck>, 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 (maybe for Hex in future)
+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 <PolyUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <PolyUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <PolyUnitCheck>, 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 <FearUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <FearUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <FearUnitCheck>, 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::FindRepentanceTarget(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 <StunUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <StunUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <StunUnitCheck>, 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 <UndeadCCUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <UndeadCCUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <UndeadCCUnitCheck>, 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 <RootUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <RootUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <RootUnitCheck>, 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 dist, bool isFriend, uint32 spellId) const
+{
+ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(p);
+ cell.SetNoCreate();
+
+ Unit* unit = NULL;
+
+ CastingUnitCheck check(me, dist, isFriend, spellId);
+ Trinity::UnitLastSearcher <CastingUnitCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <CastingUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <CastingUnitCheck>, 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;
+}
+// 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 (me->isMoving() || IsCasting()) return NULL;//prevent aoe casts while running away
+ 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)->isDead()) 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;
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = master->GetBotMap(i)->_Cre();
+ if (!bot || bot->isDead() || !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)->isDead()) 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->isDead() || 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)->isDead()) 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;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ if (!bot || bot->isDead() || 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)->isDead()) 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) 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, To, this);
+ Trinity::UnitLastSearcher <SecondEnemyCheck> searcher(me, unit, check);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <SecondEnemyCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <SecondEnemyCheck>, 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;
+}
+
+//////////
+//Internal
+//////////
+
+//Using rist-rank spell as source, returns spell of max rank allowed for given caster
+uint32 bot_ai::InitSpell(Unit* caster, uint32 spell)
+{
+ SpellInfo const* info = sSpellMgr->GetSpellInfo(spell);
+ if (!info)
+ 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
+}
+//Health magement for minions
+//Including health calcs, set and regeneration
+void bot_minion_ai::_OnHealthUpdate(uint8 myclass, uint8 mylevel) const
+{
+ //sLog->outError(LOG_FILTER_PLAYER, "_OnHealthUpdate(): updating bot %s", me->GetName().c_str());
+ float pct = me->GetHealthPct();// needs for regeneration
+ uint32 m_basehp = classinfo->basehealth;
+ //sLog->outError(LOG_FILTER_PLAYER, "class base health: %u", m_basehp);
+ me->SetCreateHealth(m_basehp);
+ float stammod;
+ switch (myclass)
+ {
+ case CLASS_WARRIOR: case CLASS_DEATH_KNIGHT: case BEAR:
+ switch (master->getClass())
+ {
+ case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK:
+ stammod = 16.f;
+ break;
+ case CLASS_DRUID: case CLASS_SHAMAN: case CLASS_HUNTER: case CLASS_ROGUE:
+ stammod = 13.f;
+ break;
+ default: stammod = 9.8f; break;
+ }
+ break;
+ case CLASS_PALADIN:
+ switch (master->getClass())
+ {
+ case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK:
+ stammod = 15.5f;
+ break;
+ case CLASS_DRUID: case CLASS_SHAMAN: case CLASS_HUNTER: case CLASS_ROGUE:
+ stammod = 12.5f;
+ break;
+ case CLASS_PALADIN:
+ stammod = 9.8f;
+ break;
+ default: stammod = 9.f; break;
+ }
+ break;
+ case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK:
+ switch (master->getClass())
+ {
+ case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK:
+ stammod = 9.8f;
+ break;
+ case CLASS_DRUID: case CLASS_SHAMAN: case CLASS_HUNTER: case CLASS_ROGUE:
+ stammod = 8.f;
+ break;
+ default: stammod = 5.f; break;
+ }
+ break;
+ case CLASS_DRUID: case CAT: case CLASS_SHAMAN: case CLASS_HUNTER: case CLASS_ROGUE:
+ switch (master->getClass())
+ {
+ case CLASS_PRIEST: case CLASS_MAGE: case CLASS_WARLOCK:
+ stammod = 12.f;
+ break;
+ case CLASS_DRUID: case CLASS_SHAMAN: case CLASS_HUNTER: case CLASS_ROGUE:
+ stammod = 9.8f;
+ break;
+ default: stammod = 8.f; break;
+ }
+ break;
+ default: stammod = 10.f;
+ break;
+ }
+ stammod -= 0.3f;
+ //sLog->outError(LOG_FILTER_PLAYER, "stammod: %f", stammod);
+
+ //manually pick up stamina from bot's buffs
+ float stamValue = me->GetTotalStatValue(STAT_STAMINA);
+ stamValue = std::max(stamValue - 18.f, 1.f); //remove base stamina (not calculated into health)
+ //sLog->outError(LOG_FILTER_PLAYER, "bot's stats to health add: Stamina (%f), value: %f", stamValue, stamValue * 10.f);
+ int32 hp_add = int32(stamValue * 10.f);
+ //pick up master's stamina from items
+ float total_pct = std::max((master->GetModifierValue(UNIT_MOD_STAT_STAMINA, TOTAL_PCT) - 0.1f), 1.f);
+ float base_stam = master->GetModifierValue(UNIT_MOD_STAT_STAMINA, BASE_VALUE);
+ base_stam = std::max(base_stam - 18.f, 0.f); //remove base stamina (not calculated into health)
+ stamValue = base_stam * master->GetModifierValue(UNIT_MOD_STAT_STAMINA, BASE_PCT) * total_pct;
+ //sLog->outError(LOG_FILTER_PLAYER, "stat to health add: Stamina (%f), value: %f", stamValue, stamValue*stammod);
+ hp_add += int32(stamValue*stammod);
+ //float stamstat = stat * 0.5f;
+ //if (stamValue > stamstat)
+ //{
+ // //sLog->outBasic("selected stat to health add: Stamina (%f), value: %f", stamValue, stamValue*stammod);
+ // hp_add += int32(stamValue * stammod);
+ //}
+ //else
+ //{
+ // //sLog->outBasic("selected stat to health add: stamStat (%f), value: %f", stamstat, stamstat*stammod);
+ // hp_add += int32(stamstat * stammod);
+ //}
+ //sLog->outBasic("health to add after master's stat mod: %i", hp_add);
+ int32 miscVal = me->getGender()*mylevel;
+ //sLog->outError(LOG_FILTER_PLAYER, "health to remove from gender mod: %i", -miscVal);
+ hp_add -= miscVal;//less hp for females lol
+ //sLog->outError(LOG_FILTER_PLAYER, "health to add after gender mod: %i", hp_add);
+ //miscVal = myrace*(mylevel/5);
+ //sLog->outError(LOG_FILTER_PLAYER, "health to add from race mod: %i", miscVal);
+ //hp_add += miscVal;//draenei tanks lol
+ //sLog->outError(LOG_FILTER_PLAYER, "health to add after race mod: %i", hp_add);
+ miscVal = master->GetNpcBotSlot(me->GetGUID()) * (mylevel/5);
+ //sLog->outError(LOG_FILTER_PLAYER, "health to remove from slot mod: %i", -miscVal);
+ hp_add -= miscVal;
+ //sLog->outError(LOG_FILTER_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);
+ //sLog->outError(LOG_FILTER_PLAYER, "total base health: %u", m_totalhp);
+ if (master->GetBotTankGuid() == me->GetGUID())
+ {
+ m_totalhp = (m_totalhp * 135) / 100;//35% hp bonus for tanks
+ //sLog->outBasic("total base health (isTank): %u", m_totalhp);
+ }
+ me->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, float(m_totalhp));//replaces base 18900 hp at 80 lvl
+ me->UpdateMaxHealth();//will use our values we just set (update base health and buffs)
+ //sLog->outError(LOG_FILTER_PLAYER, "overall hp: %u", me->GetMaxHealth());
+ me->SetHealth(uint32(0.5f + float(me->GetMaxHealth()) * pct / 100.f));//restore pct
+ if (!me->isInCombat())
+ me->SetHealth(me->GetHealth() + m_basehp / 100);//regenerate
+}
+//Mana management for minions
+//Including calcs and set
+void bot_minion_ai::_OnManaUpdate(uint8 myclass, uint8 mylevel) const
+{
+ if (me->getPowerType() != POWER_MANA)
+ return;
+ //sLog->outError(LOG_FILTER_PLAYER, "_OnManaUpdate(): updating bot %s", me->GetName().c_str());
+ float pct = (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA));
+ float m_basemana = classinfo->basemana > 0 ? classinfo->basemana : me->GetCreateMana();
+ //sLog->outError(LOG_FILTER_PLAYER, "classinfo base mana = %f", m_basemana);
+ me->SetCreateMana(m_basemana);//set base mana, critical
+ float manamod = 15.f;//here we set mana multiplier from intellect as we gain mana from MASTER's stats mostly
+ switch (myclass)
+ {
+ case CLASS_PALADIN: case CLASS_HUNTER: manamod = 4.5f; break;
+ case CLASS_SHAMAN: manamod = 11.5f; break;
+ case CLASS_DRUID: manamod = 12.5f; break;
+ case CLASS_PRIEST: manamod = 16.5f; break;
+ case CLASS_MAGE: case CLASS_WARLOCK: manamod = 10.5f; break;
+ default: break;
+ }
+ //manamod += 1.f;//custom
+ //manamod *= 0.70f;//custom
+ //sLog->outError(LOG_FILTER_PLAYER, "Manamod: %f", manamod);
+ float intValue = me->GetTotalStatValue(STAT_INTELLECT);
+ intValue = std::max(intValue - 18.f, 1.f); //remove base int (not calculated into mana)
+ //sLog->outError(LOG_FILTER_PLAYER, "bot's stats to mana add: Int (%f), value: %f", intValue, intValue * manamod);
+ m_basemana += intValue * 15.f;
+ //pick up master's intellect from items if master has mana
+ if (master->getPowerType() == POWER_MANA)
+ {
+ float total_pct = std::max((master->GetModifierValue(UNIT_MOD_STAT_INTELLECT, TOTAL_PCT) - 0.1f), 1.f);
+ intValue = std::max(master->GetModifierValue(UNIT_MOD_STAT_INTELLECT, BASE_VALUE) - 18.f, 1.f); //remove base int (not calculated into mana)
+ intValue = intValue * master->GetModifierValue(UNIT_MOD_STAT_INTELLECT, BASE_PCT) * total_pct;
+ }
+ else// pick up maxstat
+ intValue = stat * 0.5f;
+ //sLog->outError(LOG_FILTER_PLAYER, "mana add from master's stat: %f", intValue * manamod);
+ m_basemana += intValue * manamod;
+ //sLog->outError(LOG_FILTER_PLAYER, "base mana + mana from master's intellect or stat: %f", m_basemana);
+ //intValue = me->GetTotalAuraModValue(UNIT_MOD_STAT_INTELLECT);
+ //sLog->outBasic("Intellect from buffs: %f", intValue);
+ //m_basemana += uint32(intValue) * manamod;
+ //sLog->outBasic("base mana + mana from intellect + mana from buffs: %u", m_basemana);
+ uint8 otherVal = me->getGender()*3*mylevel;
+ //sLog->outError(LOG_FILTER_PLAYER, "mana to add from gender mod: %u", otherVal);
+ m_basemana += float(otherVal);//more mana for females lol
+ //sLog->outError(LOG_FILTER_PLAYER, "base mana after gender mod: %f", m_basemana);
+ otherVal = master->GetNpcBotSlot(me->GetGUID()) * (mylevel/5);// only to make mana unique
+ //sLog->outError(LOG_FILTER_PLAYER, "mana to remove from slot mod: %i", -int8(otherVal));
+ m_basemana -= otherVal;
+ //sLog->outError(LOG_FILTER_PLAYER, "base mana after slot mod: %f", m_basemana);
+ float m_totalmana = m_basemana;
+ //sLog->outError(LOG_FILTER_PLAYER, "total mana to set: %f", m_totalmana);
+ me->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, m_totalmana);
+ me->UpdateMaxPower(POWER_MANA);
+ //sLog->outError(LOG_FILTER_PLAYER, "Overall mana to set: %u", me->GetMaxPower(POWER_MANA));
+ me->SetPower(POWER_MANA, uint32(0.5f + float(me->GetMaxPower(POWER_MANA)) * pct / 100.f));//restore pct
+ //No Regen
+}
+//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 (ap_mod == 0.f) return; //do not bother casters
+ //sLog->outBasic("_OnMeleeDamageUpdate: Updating bot %s", me->GetName().c_str());
+ float my_ap_mod = ap_mod;
+ float mod = master->getClass() == CLASS_HUNTER ? (master->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT) + master->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT))/2.f :
+ (master->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT) + master->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT))/2.f;
+ mod = std::max(mod, 1.f); // x1 is Minimum
+ mod = std::min(mod, 2.5f); // x2.5 is Maximum
+ //sLog->outBasic("got base damage modifier: %f", mod);
+ mod -= (mod - 1.f)*0.33f;//reduce bonus by 33%
+ //sLog->outBasic("damage modifier truencated to %f, applying", mod);
+ me->SetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT, mod);
+ me->SetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT, mod);
+ me->SetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT, mod);
+ me->SetCanDualWield(myclass == CLASS_ROGUE || myclass == CLASS_SHAMAN);
+ //Rogue has mainhand attack speed 1900, other dual-wielders - 2800 or 2600 or 2400
+ if (myclass == CLASS_ROGUE)
+ me->SetAttackTime(BASE_ATTACK, 1900);
+ if (me->CanDualWield())
+ me->SetAttackTime(OFF_ATTACK, myclass == CLASS_ROGUE ? 1400 : 1800);
+ //me->SetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT, mod);//NUY
+ mod = (mod - 1.f)*0.5f;
+ //sLog->outBasic("reduced damage modifier to gain bonus: %f", mod);
+ //sLog->outBasic("base ap modifier is %f", my_ap_mod);
+ my_ap_mod *= 0.5f;
+ //sLog->outBasic("ap modifier multiplied to %f", my_ap_mod);
+ my_ap_mod += my_ap_mod > 0.f ? mod : 0.f; //add reduced master's multiplier if can have damage
+ //sLog->outBasic("ap modifier + mod = %f", my_ap_mod);
+ me->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_PCT, my_ap_mod);
+ me->SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_PCT, my_ap_mod);
+
+ int32 sppower = 0;
+ for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i)
+ {
+ int32 power = master->SpellBaseDamageBonusDone(SpellSchoolMask(1 << i));
+ if (power > sppower)
+ sppower = power;
+ }
+ //sLog->outBasic("master's spellpower is %i, multiplying...", sppower);
+ sppower *= 1.5f;
+ //sLog->outBasic("got spellpower of %i", sppower);
+ //atpower = float(master->GetInt32Value(master->getClass() == CLASS_HUNTER ? UNIT_FIELD_RANGED_ATTACK_POWER : UNIT_FIELD_ATTACK_POWER));
+ float atpower = master->GetTotalAttackPowerValue(master->getClass() == CLASS_HUNTER ? RANGED_ATTACK : BASE_ATTACK);
+ //sLog->outBasic("master's base attack power is %f", atpower);
+ atpower = sppower > atpower ? sppower : atpower;//highest stat is used (either 1.5x spellpower or attack power)
+ //sLog->outBasic("chosen attack power stat value: %f", atpower);
+ //sLog->outBasic("expected attack power: %f", atpower*ap_mod);
+
+ me->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, atpower);
+ if (myclass == CLASS_HUNTER || myclass == CLASS_ROGUE)
+ {
+ me->SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, atpower);
+ me->UpdateAttackPowerAndDamage(true);
+ }
+ me->UpdateAttackPowerAndDamage();
+ //sLog->outBasic("listing stats: ");
+ //sLog->outBasic("attack power main hand: %f", me->GetTotalAttackPowerValue(BASE_ATTACK));
+ //sLog->outBasic("attack power off hand: %f", me->GetTotalAttackPowerValue(OFF_ATTACK));
+ //sLog->outBasic("attack power ranged: %f", me->GetTotalAttackPowerValue(RANGED_ATTACK));
+ //sLog->outBasic("damage multiplier main hand: %f", me->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT) * me->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT));
+ //sLog->outBasic("damage multiplier off hand: %f", me->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT) * me->GetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT));
+ //sLog->outBasic("damage multiplier ranged: %f", me->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT) * me->GetModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT));
+ //sLog->outBasic("Damage range main hand: min: %f, max: %f", me->GetFloatValue(UNIT_FIELD_MINDAMAGE), me->GetFloatValue(UNIT_FIELD_MAXDAMAGE));
+ //sLog->outBasic("Damage range off hand: min: %f, max: %f", me->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), me->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE));
+ //sLog->outBasic("Damage range ranged: min: %f, max: %f", me->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), me->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE));
+}
+//Health for pets
+//Same as for minions just simplified (modified to match real pets' values)
+void bot_pet_ai::_OnHealthUpdate(uint8 /*petType*/, uint8 mylevel) const
+{
+ float hp_mult = 10.f;
+ switch (GetPetType(me))
+ {
+ case PET_TYPE_VOIDWALKER:
+ hp_mult = 11.f;
+ break;
+ default:
+ break;
+ }
+ float pct = me->GetHealthPct();// needs for regeneration
+ //Use simple checks and calcs
+ //0.3 hp for bots (inaccurate but cheap)
+ uint32 m_basehp = me->GetCreateHealth()/2;
+ //pick up stamina from buffs
+ float stamValue = me->GetTotalStatValue(STAT_STAMINA);
+ stamValue = std::max(stamValue - 18.f, 1.f); //remove base stamina (not calculated into health)
+ uint32 hp_add = uint32(stamValue*hp_mult);
+ hp_add += (m_creatureOwner->GetMaxHealth() - m_creatureOwner->GetCreateHealth())*0.3f;
+ uint8 miscVal = GetPetType(me)*mylevel;
+ hp_add -= miscVal;
+ uint32 m_totalhp = m_basehp + hp_add;
+ if (master->GetBotTankGuid() == me->GetGUID())
+ 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
+ if (!me->isInCombat())
+ me->SetHealth(me->GetHealth() + m_basehp / 100);//regenerate
+}
+//Mana for pets
+//Same as for minions just simplified (modified to match real pets' values)
+void bot_pet_ai::_OnManaUpdate(uint8 /*petType*/, uint8 mylevel) const
+{
+ if (me->getPowerType() != POWER_MANA)
+ return;
+
+ float mana_mult = 15.f;
+ switch (GetPetType(me))
+ {
+ case PET_TYPE_VOIDWALKER:
+ mana_mult = 11.5f;
+ break;
+ default:
+ break;
+ }
+ float pct = (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA));
+ //Use simple checks and calcs
+ //0.3 mana for bots (inaccurate but cheap)
+ float m_basemana = float(me->GetCreateMana());
+ m_basemana += (std::max<float>(me->GetTotalStatValue(STAT_INTELLECT) - 18.f, 1.f))*mana_mult; //remove base stamina (not calculated into mana)
+ m_basemana += float(m_creatureOwner->GetMaxPower(POWER_MANA) - m_creatureOwner->GetCreateMana())*0.3f;
+ m_basemana -= float(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
+{
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ if (Creature* bot = master->GetBotMap(i)->_Cre())
+ 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::_OnEvade()
+{
+ 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->isDead() && !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->isDead() && !IsInBotParty(u))
+ m_botsPet->InterruptSpell(CurrentSpellTypes(i), false, false);
+
+ if (Rand() > 10) return;
+ if (!master->isInCombat() && !me->isInCombat() && (!m_botsPet || !m_botsPet->isInCombat())) return;
+ if (CheckAttackTarget(GetBotClassForCreature(me)))
+ return;
+ //ChatHandler ch(master);
+ //ch.PSendSysMessage("_OnEvade() by bot %s", me->GetName().c_str());
+ if (master->isInCombat())
+ {
+ HostileRefManager& mgr = master->getHostileRefManager();
+ if (!mgr.isEmpty())
+ {
+ std::set<Unit*> Set;
+ HostileReference* ref = mgr.getFirst();
+ while (ref)
+ {
+ if (ref->getSource() && ref->getSource()->getOwner())
+ Set.insert(ref->getSource()->getOwner());
+ ref = ref->next();
+ }
+ for (std::set<Unit*>::const_iterator i = Set.begin(); i != Set.end(); ++i)
+ {
+ Unit* unit = (*i);
+ if (/*unit->IsFriendlyTo(master)*/IsInBotParty(unit) || !unit->isInCombat())
+ {
+ //ch.PSendSysMessage("_OnEvade(): %s's hostile reference is removed from %s!", unit->GetName().c_str(), master->GetName().c_str());
+ mgr.deleteReference(unit);
+ }
+ }
+ }
+ }
+ else
+ {
+ SendPartyEvadeAbort();
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = master->GetBotMap(i)->_Cre();
+ if (!cre) continue;
+ if (cre->isInCombat())
+ {
+ cre->DeleteThreatList();
+ HostileRefManager& mgr = cre->getHostileRefManager();
+ if (!mgr.isEmpty())
+ {
+ std::set<Unit*> Set;
+ HostileReference* ref = mgr.getFirst();
+ while (ref)
+ {
+ if (ref->getSource() && ref->getSource()->getOwner())
+ Set.insert(ref->getSource()->getOwner());
+ ref = ref->next();
+ }
+ for (std::set<Unit*>::const_iterator i = Set.begin(); i != Set.end(); ++i)
+ {
+ Unit* unit = (*i);
+ if (!unit->InSamePhase(me)) continue;
+ if (/*unit->IsFriendlyTo(master)*/IsInBotParty(unit) || !unit->isInCombat())
+ {
+ //ch.PSendSysMessage("_OnEvade(): %s's hostile reference is removed from %s!", unit->GetName().c_str(), cre->GetName().c_str());
+ mgr.deleteReference(unit);
+ }
+ }
+ }
+ //if (mgr.isEmpty())// has empty threat list and no hostile refs - we have all rights to stop combat
+ //{
+ // if (cre->isInCombat())
+ // {
+ // //ch.PSendSysMessage("_OnEvade(): %s's HostileRef is empty! Combatstop!", cre->GetName().c_str());
+ // cre->ClearInCombat();
+ // }
+ //}
+ }
+
+ Creature* m_botsPet = cre->GetBotsPet();
+ if (!m_botsPet || !m_botsPet->isInCombat()) continue;
+ m_botsPet->DeleteThreatList();
+ HostileRefManager& mgr = m_botsPet->getHostileRefManager();
+ if (!mgr.isEmpty())
+ {
+ std::set<Unit*> Set;
+ HostileReference* ref = mgr.getFirst();
+ while (ref)
+ {
+ if (ref->getSource() && ref->getSource()->getOwner())
+ Set.insert(ref->getSource()->getOwner());
+ ref = ref->next();
+ }
+ for (std::set<Unit*>::const_iterator i = Set.begin(); i != Set.end(); ++i)
+ {
+ Unit* unit = (*i);
+ if (!unit->InSamePhase(me)) continue;
+ if (/*unit->IsFriendlyTo(master)*/IsInBotParty(unit) || !unit->isInCombat())
+ {
+ //ch.PSendSysMessage("_OnEvade(): %s's hostile reference is removed from %s!", unit->GetName().c_str(), m_botsPet->GetName().c_str());
+ mgr.deleteReference(unit);
+ }
+ }
+ }
+ //if (mgr.isEmpty())// has empty threat list and no hostile refs - we have all rights to stop combat
+ //{
+ // if (m_botsPet->isInCombat())
+ // {
+ // //ch.PSendSysMessage("_OnEvade(): %s's HostileRef is empty! Combatstop!", pet->GetName().c_str());
+ // m_botsPet->ClearInCombat();
+ // }
+ //}
+ }
+ }
+}
+//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();
+ //update stats
+ if (auraname == SPELL_AURA_MOD_STAT)
+ {
+ doHealth = true;
+ doMana = 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;
+ }
+ }
+}
+//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(me->GetBotClass(), master->getLevel());
+ }
+ if (doMana)
+ {
+ doMana = false;
+ _OnManaUpdate(me->GetBotClass(), master->getLevel());
+ }
+ CheckAuras(true);
+ //0 to 2 plus 1 for every 3 bots except first one
+ return (1 + (master->GetNpcBotsCount() - 1)/3 + (irand(0,100) <= 50)*int8(RAND(-1,1)));
+}
+//Damage Mods
+//1) Apply class-specified damage/crit chance/crit damage bonuses
+//2) Apply bot damage multiplier
+//3) Remove Creature damage multiplier (make independent from original config)
+//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
+{
+ ApplyClassDamageMultiplierMelee(damage, damageinfo);
+ damage = int32(float(damage)*dmgmult_melee/dmgmod_melee);
+}
+void bot_ai::ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const
+{
+ ApplyClassDamageMultiplierMelee(damage, damageinfo, spellInfo, attackType, crit);
+ damage = int32(float(damage)*dmgmult_melee/dmgmod_melee);
+}
+void bot_ai::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool& crit) const
+{
+ ApplyClassDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit);
+ damage = int32(float(damage)*dmgmult_spell/dmgmod_spell);
+}
+//////////
+//GOSSIP//
+//////////
+//Implemented: Mage,.. and nothing more...
+bool bot_minion_ai::OnGossipHello(Player* player, Creature* creature)
+{
+ switch (creature->GetBotClass())
+ {
+ case CLASS_MAGE:
+ if (creature->isInCombat())
+ {
+ player->CLOSE_GOSSIP_MENU();
+ break;
+ }
+ player->ADD_GOSSIP_ITEM(0, "I need food", 6001, GOSSIP_ACTION_INFO_DEF + 1);
+ player->ADD_GOSSIP_ITEM(0, "I need drink", 6001, GOSSIP_ACTION_INFO_DEF + 2);
+ player->PlayerTalkClass->SendGossipMenu(GOSSIP_SERVE_MASTER, creature->GetGUID());
+ break;
+ default:
+ player->CLOSE_GOSSIP_MENU();
+ break;
+ }
+ return true;
+}
+//GossipSelect
+//Mage: rations implemented
+bool bot_minion_ai::OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action)
+{
+ if (!IsInBotParty(player))
+ {
+ player->CLOSE_GOSSIP_MENU();
+ creature->MonsterWhisper("Get away from me!", player->GetGUID());
+ return false;
+ }
+ switch (creature->GetBotClass())
+ {
+ case CLASS_MAGE:
+ switch (sender)
+ {
+ case 6001:// food/drink
+ {
+ //Prevent high-leveled consumables for low-level characters
+ Unit* checker;
+ if (player->getLevel() < creature->getLevel())
+ checker = player;
+ else
+ checker = creature;
+
+ // 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";
+ creature->MonsterWhisper(errorstr.c_str(), player->GetGUID());
+ //player->PlayerTalkClass->ClearMenus();
+ //return OnGossipHello(player, creature);
+ player->CLOSE_GOSSIP_MENU();
+ return false;
+ }
+ player->CLOSE_GOSSIP_MENU();
+ SpellInfo const* Info = sSpellMgr->GetSpellInfo(food);
+ Spell* foodspell = new Spell(creature, Info, TRIGGERED_NONE, player->GetGUID());
+ SpellCastTargets targets;
+ targets.SetUnitTarget(player);
+ //TODO implement checkcast for bots
+ SpellCastResult result = creature->IsMounted() || CCed(creature) ? SPELL_FAILED_CUSTOM_ERROR : foodspell->CheckPetCast(player);
+ if (result != SPELL_CAST_OK)
+ {
+ foodspell->finish(false);
+ delete foodspell;
+ creature->MonsterWhisper("I can't do it right now", player->GetGUID());
+ creature->SendPetCastFail(food, result);
+ }
+ else
+ {
+ aftercastTargetGuid = player->GetGUID();
+ foodspell->prepare(&targets);
+ creature->MonsterWhisper("Here you go...", player->GetGUID());
+ }
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ 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<uint8>(master->getLevel(), 80);
+ uint32 originalentry = bot_pet_ai::GetPetOriginalEntry(entry);
+ if (!originalentry)
+ {
+ //annoy master
+ me->MonsterWhisper("Why am I trying to summon unknown pet!?", master->GetGUID());
+ return;
+ }
+ uint32 armor = 0;
+ 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)
+ {
+ me->MonsterWhisper("Failed to summon pet!", master->GetGUID());
+ 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->SetUInt64Value(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);
+ //petai->setStats(mylevel, bot_pet_ai::GetPetType(m_botsPet), true);
+ m_botsPet->SetBotCommandState(COMMAND_FOLLOW, true);
+
+ me->SetBotsPet(m_botsPet);
+
+ m_botsPet->SendUpdateToPlayer(master);
+}
+
+uint16 bot_ai::Rand() const
+{
+ return urand(0, 100 + (master->GetNpcBotsCount() - 1) * 10);
+}
+//Used for outside checks for druid
+uint8 bot_minion_ai::GetBotClassForCreature(Creature* bot)
+{
+ uint8 botClass = bot->GetBotClass();
+ switch (botClass)
+ {
+ case CAT: case BEAR:
+ return CLASS_DRUID;
+ default:
+ return botClass;
+ }
+}
+//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 CLASS_MAGE;
+ default:
+ return 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 = PVPTRINKET_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();
+ }
+ 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())
+ return;
+ if (InDuel(attacker))
+ return;
+ bool byspell = false, ranged = false;
+ switch (GetBotClassForCreature(me))
+ {
+ case CLASS_DRUID:
+ byspell = me->GetShapeshiftForm() == FORM_NONE ||
+ me->GetShapeshiftForm() == FORM_TREE ||
+ me->GetShapeshiftForm() == FORM_MOONKIN;
+ ranged = byspell;
+ break;
+ case CLASS_PRIEST:
+ case CLASS_MAGE:
+ case CLASS_WARLOCK:
+ case CLASS_SHAMAN:
+ byspell = true;
+ ranged = true;
+ break;
+ case CLASS_HUNTER:
+ ranged = true;
+ break;
+ default:
+ break;
+ }
+ float maxdist = InitAttackRange(float(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);
+}
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..fc2cfe9
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_ai.h
@@ -0,0 +1,355 @@
+#ifndef _BOT_AI_H
+#define _BOT_AI_H
+
+#include "ScriptedCreature.h"
+
+enum CommonValues
+{
+//COMMON SPELLS
+ MANAPOTION = 32453,//"???" forgot 0_o
+ 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
+//COMMON CDs
+ POTION_CD = 60000,//default 60sec potion cd
+ PVPTRINKET_CD = 120000,//default 2 min pvp trinket cd
+//COMMON PASSIVES
+ //1) "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
+ //2) Talents
+ HASTE /*Gift of the EarthMother*/= 51183,//rank 5 10% spell haste
+ HASTE2 /*Blood Frenzy - warrior*/ = 29859,//rank2 10% haste, bonus for rend (warriors only)//13789//rank 3 10% haste 6% dodge
+ CRITS /*Thundering Strikes-sham*/= 16305,//rank 5 5% crit
+ 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
+ PRECISION /*Precision - warrior*/ = 29592,//rank 3 3% melee hit
+ PRECISION2/*Precision - mage*/ = 29440,//rank 3 3% spell hit
+ DMG_TAKEN/*Deadened Nerves - rogue*/= 31383,//rank 3 (-6%)
+ //3) Pet/Special
+ THREAT /*Tank Class Passive*/ = 57339,//+43% threat
+ BOR /*Blood of Rhino - pet*/ = 53482,//rank 2 +40% healing taken
+ RCP /*Rogue Class Passive*/ = 21184,//-27% threat caused
+ DEFENSIVE_STANCE_PASSIVE = 7376, //+threat/damage reduction
+//COMMON GOSSIPS
+ GOSSIP_SERVE_MASTER = 2279 //"I live only to serve the master."
+};
+
+//TODO: slow fall / water walking for master
+//enum HoverSpells
+//{
+// LEVITATE = 1706,
+// SLOW_FALL = 130,
+// //WATER_WALKING = 546,
+//};
+
+enum DruidStances//bot's temp set class
+{
+ BEAR = 15,
+ CAT = 25,
+ //TRAVEL = 35, //NUY
+ //FLY = 45, //NUY
+};
+
+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 = 60237,
+ //PET_SUCCUBUS =
+};
+
+enum HunterBotPets
+{
+ PET_VULTURE = 60238
+};
+
+enum BotPetsOriginalEntries
+{
+ ORIGINAL_ENTRY_VOIDWALKER = 1860
+};
+
+class bot_ai : public ScriptedAI
+{
+ public:
+ virtual ~bot_ai();
+ bot_ai(Creature* creature);
+ //Player* GetMaster() const { return master; }
+ virtual bool IsMinionAI() const = 0;
+ virtual bool IsPetAI() const = 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; }
+ bool IsInBotParty(Unit* unit) const;
+ bool CanBotAttack(Unit* target, int8 byspell = 0) const;
+ bool InDuel(Unit* target) 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;
+ inline void SendPartyEvadeAbort() const;
+ inline void SetShouldUpdateStats() { shouldUpdateStats = true; }
+ inline void UpdateHealth() { doHealth = true; }
+ inline void UpdateMana() { doMana = true; }
+ inline void SetBotTank(Unit* newtank) { tank = newtank; m_TankGuid = newtank ? newtank->GetGUID() : 0; }
+ static Unit* GetBotGroupMainTank(Group* group) { return _GetBotGroupMainTank(group); }
+ inline float GetManaRegen() const { return regen_mp5; }
+ inline float GetHitRating() const { return hit; }
+ inline uint64 GetBotTankGuid() const { return m_TankGuid; }
+ inline int32 GetSpellPower() const { return m_spellpower; }
+ inline uint8 GetHaste() const { return haste; }
+
+ void ReceiveEmote(Player* player, uint32 emote);
+ void ApplyPassives(uint8 botOrPetType) const;
+
+ protected:
+ 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)) : true;
+ }
+ static uint32 InitSpell(Unit* caster, uint32 spell);
+
+ bool HasAuraName(Unit* unit, const std::string spell, uint64 casterGuid = 0, bool exclude = false) const;
+ bool HasAuraName(Unit* unit, uint32 spellId, uint64 casterGuid = 0, bool exclude = false) const;
+ bool RefreshAura(uint32 spell, int8 count = 1, Unit* target = NULL) 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, uint64 originalCaster = 0);
+ SpellCastResult checkBotCast(Unit* victim, uint32 spellId, uint8 botclass) const;
+ virtual void removeFeralForm(bool /*force*/ = false, bool /*init*/ = true, uint32 /*diff*/ = 0) {}
+
+ 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 == BEAR); }
+ 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->IsNonMeleeSpellCasted(false)); }
+
+ void GetInPosition(bool force = false, bool ranged = true, Unit* newtarget = NULL, Position* pos = NULL);
+ void OnSpellHit(Unit* caster, SpellInfo const* spell);
+ void FindTank();
+ void listAuras(Player* player, Unit* unit) const;
+ void CalculateAttackPos(Unit* target, Position &pos) const;
+
+ 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 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 const /*diff*/) {}
+ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {}
+ virtual void InitSpells() {}
+ virtual void _OnHealthUpdate(uint8 /*myclass*/, uint8 /*mylevel*/) const = 0;
+ virtual void _OnManaUpdate(uint8 /*myclass*/, uint8 /*mylevel*/) const = 0;
+ //virtual void _OnMeleeDamageUpdate(uint8 /*myclass*/) const = 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; }
+
+ uint8 GetWait();
+ inline float InitAttackRange(float origRange, bool ranged) const;
+ uint16 Rand() const;
+ static inline uint32 GetLostHP(Unit* unit) { return unit->GetMaxHealth() - unit->GetHealth(); }
+ static inline uint8 GetHealthPCT(Unit* hTarget) { if (!hTarget || hTarget->isDead()) return 100; return (hTarget->GetHealth()*100/hTarget->GetMaxHealth()); }
+ static inline uint8 GetManaPCT(Unit* hTarget) { if (!hTarget || hTarget->isDead() || hTarget->getPowerType() != POWER_MANA) return 100; return (hTarget->GetPower(POWER_MANA)*100/hTarget->GetMaxPower(POWER_MANA)); }
+
+ Unit* getTarget(bool byspell, bool ranged, bool &reset) const;
+
+ CommandStates GetBotCommandState() const { return m_botCommandState; }
+
+ typedef std::set<Unit*> AttackerSet;
+
+ Player* master;
+ Unit* opponent;
+ Unit* tank;
+ CommandStates m_botCommandState;
+ SpellInfo const* info;
+ Position pos, attackpos;
+ float stat, atpower, maxdist, regen_mp5, hit,
+ ap_mod, spp_mod, crit_mod;
+ uint64 aftercastTargetGuid;
+ int32 cost, value, sppower, m_spellpower;
+ uint32 GC_Timer, temptimer, checkAurasTimer, wait, currentSpell;
+ uint8 clear_cd, haste, healTargetIconFlags;
+ bool doHealth, doMana, shouldUpdateStats;
+
+ private:
+ static Unit* _GetBotGroupMainTank(Group* group);
+ static inline float _getAttackDistance(float distance) { return distance > 0.f ? distance*0.72 : 0.f; }
+ Unit* extank;
+ float dmgmult_melee, dmgmult_spell;
+ float dmgmod_melee, dmgmod_spell;
+ uint64 m_TankGuid;
+};
+
+class bot_minion_ai : public bot_ai
+{
+ public:
+ virtual ~bot_minion_ai();
+ bot_minion_ai(Creature* creature);
+ const bot_minion_ai* GetMinionAI() const { return this; }
+ bool IsMinionAI() const { return true; }
+ bool IsPetAI() const { return false; }
+ void SummonBotsPet(uint32 entry);
+ inline bool IAmDead() const { return (!master || me->isDead()); }
+ void SetBotCommandState(CommandStates st, bool force = false, Position* newpos = NULL);
+ //virtual bool HealTarget(Unit* /*target*/, uint8 /*pct*/, uint32 const /*diff*/) { return false; }
+ //virtual bool BuffTarget(Unit* /*target*/, uint32 const /*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 const /*diff*/) {}
+ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {}
+ void setStats(uint8 myclass, uint8 myrace, uint8 mylevel, bool force = false);
+
+ static bool OnGossipHello(Player* player, Creature* creature);
+ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action);
+
+ void InitSpells() {}
+ void _OnHealthUpdate(uint8 myclass, uint8 mylevel) const;
+ void _OnManaUpdate(uint8 myclass, uint8 mylevel) const;
+ void _OnMeleeDamageUpdate(uint8 myclass) const;
+
+ void OnOwnerDamagedBy(Unit* attacker);
+
+ inline void SetEvadeTimer(uint8 time) { evade_cd = time; }
+
+ static inline uint8 GetBotClassForCreature(Creature* bot);
+
+ protected:
+ 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);
+ }
+
+ inline void Evade() { _OnEvade(); }
+
+ virtual void BreakCC(uint32 diff);
+
+ void CommonTimers(uint32 diff)
+ {
+ if (pvpTrinket_cd > diff) pvpTrinket_cd -= diff;
+ if (Potion_cd > diff) Potion_cd -= diff;
+ if (GC_Timer > diff) GC_Timer -= diff;
+ if (temptimer > diff) temptimer -= diff;
+ if (checkAurasTimer != 0) --checkAurasTimer;
+ if (wait != 0) --wait;
+ if (evade_cd != 0) --evade_cd;
+ }
+
+ Unit* FindHostileDispelTarget(float dist = 30, bool stealable = false) const;
+ Unit* FindAffectedTarget(uint32 spellId, uint64 caster = 0, 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* FindRepentanceTarget(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 dist = 10, 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) const;
+
+ uint32 Potion_cd, pvpTrinket_cd;
+
+ private:
+ bool CanCureTarget(Unit* target, uint32 cureSpell, uint32 diff) const;
+ void CalculatePos(Position& pos);
+ void UpdateMountedState();
+ void UpdateStandState() const;
+ void UpdateRations() const;
+ void _OnEvade();
+ PlayerClassLevelInfo* classinfo;
+ float myangle, armor_mod, haste_mod, dodge_mod, parry_mod;
+ uint8 rezz_cd, evade_cd;
+};
+
+class bot_pet_ai : public bot_ai
+{
+ public:
+ virtual ~bot_pet_ai();
+ bot_pet_ai(Creature* creature);
+ const bot_pet_ai* GetPetAI() const { return this; }
+ Creature* GetCreatureOwner() const { return m_creatureOwner; }
+ bool IsMinionAI() const { return false; }
+ bool IsPetAI() const { return true; }
+ 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 const /*diff*/) { return false; }
+ //virtual bool BuffTarget(Unit* /*target*/, uint32 const /*diff*/) { return false; }
+ //void BuffAndHealGroup(Player* /*gPlayer*/, uint32 const /*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 const /*diff*/) {}
+ //bool CureTarget(Unit* /*target*/, uint32 /*cureSpell*/, uint32 const /*diff*/) { return false; }
+ void CheckAuras(bool force = false);
+ //virtual void DoNonCombatActions(uint32 const /*diff*/) {}
+ //virtual void StartAttack(Unit* /*u*/, bool /*force*/ = false) {}
+ void setStats(uint8 mylevel, uint8 petType, bool force = 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(uint8 petType, uint8 mylevel) const;
+ void _OnManaUpdate(uint8 petType, uint8 mylevel) const;
+ //void _OnMeleeDamageUpdate(uint8 /*myclass*/) const {}
+ void SetBaseArmor(uint32 armor) { basearmor = armor; }
+
+ protected:
+ void CommonTimers(uint32 diff)
+ {
+ if (GC_Timer > diff) GC_Timer -= diff;
+ if (temptimer > diff) temptimer -= diff;
+ if (checkAurasTimer != 0) --checkAurasTimer;
+ if (wait != 0) --wait;
+ }
+
+ Creature* m_creatureOwner;
+ private:
+ uint32 basearmor;
+};
+
+#endif
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..71e8328
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_druid_ai.cpp
@@ -0,0 +1,1092 @@
+#include "bot_ai.h"
+#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+#include "WorldSession.h"
+/*
+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);
+ }
+
+ 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;
+ }
+
+ struct bot_druid_ai : public bot_minion_ai
+ {
+ bot_druid_ai(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_DRUID) != SPELL_CAST_OK)
+ return false;
+
+ info = sSpellMgr->GetSpellInfo(spellId);
+ if (swiftness && info->CalcCastTime() > 0)
+ {
+ DoCast(victim, spellId, true);
+ me->RemoveAurasDueToSpell(NATURES_SWIFTNESS, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE);
+ me->RemoveAurasDueToSpell(CRIT_50, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE);
+ swiftness = false;
+ return true;
+ }
+ if (spellId == BEAR_FORM || spellId == CAT_FORM)
+ {
+ me->ModifyPower(POWER_MANA, - int32(info->CalcPowerCost(me, info->GetSchoolMask())));
+ mana = me->GetPower(POWER_MANA);
+ if (Unit* u = me->getVictim())
+ GetInPosition(true, false, u);
+ }
+
+ bool result = bot_ai::doCast(victim, spellId, triggered);
+
+ if (result &&
+ //spellId != BEAR_FORM && spellId != CAT_FORM &&
+ spellId != MANAPOTION && spellId != WARSTOMP &&
+ me->HasAura(OMEN_OF_CLARITY_BUFF))
+ {
+ cost = info->CalcPowerCost(me, info->GetSchoolMask());
+ clearcast = true;
+ power = me->getPowerType();
+ }
+ return result;
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { removeFeralForm(true, false); master->SetNpcBotDied(me->GetGUID()); }
+
+ void warstomp(uint32 diff)
+ {
+ if (me->getRace() != RACE_TAUREN) return;
+ if (Warstomp_Timer > diff) 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))
+ {
+ Warstomp_Timer = 30000; //30sec
+ 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))
+ Warstomp_Timer = 30000; //30sec
+ }
+ }
+ }
+
+ 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(BEAR_FORM);
+ break;
+ case FORM_CAT:
+ me->RemoveAurasDueToSpell(CAT_FORM);
+ me->RemoveAurasDueToSpell(ENERGIZE);
+ break;
+ default:
+ break;
+ }
+ SetStats(CLASS_DRUID, init);
+ }
+ }
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == COMMAND_ATTACK && !force) return;
+ Aggro(u);
+ SetBotCommandState(COMMAND_ATTACK);
+ GetInPosition(force, me->GetShapeshiftForm() == FORM_NONE);
+ }
+
+ void doBearActions(uint32 diff)
+ {
+ if (me->getPowerType() != POWER_RAGE) return;
+
+ if (GetHealthPCT(me) < 75)
+ HealTarget(me, GetHealthPCT(me), diff);
+ opponent = me->getVictim();
+ if (opponent)
+ StartAttack(opponent, true);
+ else
+ return;
+
+ //range check (melee) to prevent fake casts
+ if (me->GetDistance(opponent) > 5) return;
+
+ if (MangleB_Timer <= diff && rage >= 200 && doCast(opponent, MANGLE_BEAR))
+ {
+ MangleB_Timer = 6000 - me->getLevel()/4 * 100;
+ return;
+ }
+
+ if (GC_Timer <= diff && rage >= 200 && doCast(opponent, SWIPE))
+ return;
+
+ }//end doBearActions
+
+ void doCatActions(uint32 diff)
+ {
+ if (GetHealthPCT(me) < 75)
+ HealTarget(me, GetHealthPCT(me), diff);
+ opponent = me->getVictim();
+ if (opponent)
+ StartAttack(opponent, true);
+ else
+ return;
+ uint32 energy = me->GetPower(POWER_ENERGY);
+
+ if (MoveBehind(*opponent))
+ wait = 5;
+ //{ wait = 5; return; }
+
+ //range check (melee) to prevent fake casts
+ if (me->GetDistance(opponent) > 5) return;
+
+ if (Mangle_Cat_Timer <= diff && energy > 45 && doCast(opponent, MANGLE_CAT))
+ Mangle_Cat_Timer = 6000;
+ if (Rake_Timer <= diff && energy > 40 && doCast(opponent, RAKE))
+ Rake_Timer = 10000;
+ if (Shred_Timer <= diff && energy > 60 && !opponent->HasInArc(M_PI, me) && doCast(opponent, SHRED))
+ Shred_Timer = 12000;
+ if (Rip_Timer <= diff && energy > 30 && doCast(opponent, RIP))
+ Rip_Timer = 15000;
+ if (Claw_Timer <= diff && energy > 45 && doCast(opponent, CLAW))
+ Claw_Timer = GC_Timer;
+ }//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 (melee) to prevent fake casts
+ if (me->GetExactDist(opponent) > 30 || !DamagePossible()) return;
+
+ if (HURRICANE && Hurricane_Timer <= diff && GC_Timer <= diff && Rand() > 35 && !IsCasting())
+ {
+ Unit* target = FindAOETarget(30, true);
+ if (target && doCast(target, HURRICANE))
+ {
+ Hurricane_Timer = 5000;
+ return;
+ }
+ Hurricane_Timer = 2000;//fail
+ }
+ if (GC_Timer <= diff && !opponent->HasAura(FAIRIE_FIRE))
+ if (doCast(opponent, FAIRIE_FIRE))
+ return;
+ if (Rand() > 30 && Moonfire_Timer <= diff && GC_Timer <= diff &&
+ !opponent->HasAura(MOONFIRE, me->GetGUID()))
+ if (doCast(opponent, MOONFIRE))
+ {
+ Moonfire_Timer = 5000;
+ return;
+ }
+ if (Rand() > 30 && Starfire_Timer <= diff && GC_Timer <= diff &&
+ doCast(opponent, STARFIRE))
+ {
+ Starfire_Timer = 11000;
+ return;
+ }
+ if (Rand() > 50 && Wrath_Timer <= diff && GC_Timer <= diff &&
+ doCast(opponent, WRATH))
+ {
+ Wrath_Timer = uint32(sSpellMgr->GetSpellInfo(WRATH)->CalcCastTime()/100 * me->GetFloatValue(UNIT_MOD_CAST_SPEED) + 1);
+ return;
+ }
+ }
+
+ bool MassGroupHeal(Player* gPlayer, uint32 diff)
+ {
+ if (!gPlayer || GC_Timer > diff) return false;
+ if (!TRANQUILITY && !WILD_GROWTH) return false;
+ if (Tranquility_Timer > diff && Wild_Growth_Timer > diff) return false;
+ if (Rand() > 30) return false;
+ if (IsCasting()) return false; // if I'm already casting
+ 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->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())
+ {
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ 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 && TRANQUILITY && Tranquility_Timer <= diff &&
+ doCast(me, TRANQUILITY))
+ { Tranquility_Timer = 45000; return true; }
+ if (LHPcount > 0 && WILD_GROWTH && Wild_Growth_Timer <= diff &&
+ doCast(healTarget, WILD_GROWTH))
+ { Wild_Growth_Timer = 6000; return true; }
+ return false;
+ }//end MassGroupHeal
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if ((me->GetShapeshiftForm() == FORM_DIREBEAR || me->GetShapeshiftForm() == FORM_BEAR) &&
+ me->getPowerType() != POWER_RAGE)
+ me->setPowerType(POWER_RAGE);
+ if (me->GetShapeshiftForm() == FORM_CAT && me->getPowerType() != POWER_ENERGY)
+ me->setPowerType(POWER_ENERGY);
+ if (me->GetShapeshiftForm() == FORM_NONE && me->getPowerType() != POWER_MANA)
+ me->setPowerType(POWER_MANA);
+ if (IAmDead()) return;
+ if (!me->getVictim())
+ Evade();
+ if (me->GetShapeshiftForm() == FORM_DIREBEAR || me->GetShapeshiftForm() == FORM_BEAR)
+ {
+ rage = me->GetPower(POWER_RAGE);
+ if (ragetimer2 <= diff)
+ {
+ if (me->isInCombat() && me->getLevel() >= 30)
+ {
+ if (rage < 990 && rage >= 0)
+ me->SetPower(POWER_RAGE, 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())
+ {
+ 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);//min
+ }
+ 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->IsNonMeleeSpellCasted(false))
+ {
+ me->ModifyPower(POWER_MANA, cost);
+ me->RemoveAurasDueToSpell(OMEN_OF_CLARITY_BUFF,me->GetGUID(),0,AURA_REMOVE_BY_EXPIRE);
+ clearcast = false;
+ }
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ BreakCC(diff);
+ if (CCed(me)) return;
+ warstomp(diff);
+
+ if (me->getPowerType() == POWER_MANA && GetManaPCT(me) < 20 && Potion_cd <= diff)
+ {
+ 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
+ if (INNERVATE && Innervate_Timer <= diff && GC_Timer <= diff)
+ {
+ doInnervate();
+ if (Innervate_Timer <= diff)//if failed or not found target
+ Innervate_Timer = 3000;//set delay
+ }
+
+ MassGroupHeal(master, diff);
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+ else
+ CheckBattleRez(diff);
+ BuffAndHealGroup(master, diff);
+ CureTarget(master, CURE_POISON, diff);
+ CureGroup(master, CURE_POISON, diff);
+
+ if (!CheckAttackTarget(CLASS_DRUID))
+ return;
+
+ if (GetHealthPCT(me) < 75)
+ {
+ HealTarget(me, GetHealthPCT(me), diff);
+ return;
+ }
+
+ if (IsCasting()) return;//Casting heal or something
+ CheckRoots(diff);
+
+ if (DamagePossible())
+ {
+ Unit* u = opponent->getVictim();
+ //if the target is attacking us, we want to go bear
+ if (BEAR_FORM && !CCed(opponent) &&
+ (u == me || (tank == me && 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, BEAR_FORM))
+ {
+ SetStats(BEAR);
+ formtimer = 1500;
+ }
+ if (me->GetShapeshiftForm() == FORM_DIREBEAR ||
+ me->GetShapeshiftForm() == FORM_BEAR)
+ {
+ doBearActions(diff);
+ ScriptedAI::UpdateAI(diff);
+ }
+ }
+ else
+ if (CAT_FORM && master->getVictim() != opponent && tank &&
+ u == tank && u != me &&
+ opponent->GetMaxHealth() < tank->GetMaxHealth()*3)
+ {
+ //if we don't have cat yet
+ if (me->GetShapeshiftForm() != FORM_CAT && formtimer <= diff)
+ {
+ if (doCast(me, CAT_FORM))
+ {
+ SetStats(CAT);
+ formtimer = 1500;
+ }
+ }
+ if (me->GetShapeshiftForm() == FORM_CAT)
+ {
+ doCatActions(diff);
+ ScriptedAI::UpdateAI(diff);
+ }
+ }
+ else if (tank != me)
+ {
+ doBalanceActions(diff);
+ }
+ }
+ else if (tank != me)
+ {
+ doBalanceActions(diff);
+ }
+ }
+
+ bool HealTarget(Unit* target, uint8 hp, uint32 diff)
+ {
+ if (hp > 95) return false;
+ if (!target || target->isDead()) return false;
+ if (tank == me && 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 ((hp < 15 || (hp < 35 && target->getAttackers().size() > 2)) &&
+ Nature_Swiftness_Timer <= diff && (target->isInCombat() || !target->getAttackers().empty()))
+ {
+ if (me->IsNonMeleeSpellCasted(false))
+ me->InterruptNonMeleeSpells(false);
+ if (NATURES_SWIFTNESS && doCast(me, NATURES_SWIFTNESS) && RefreshAura(CRIT_50, 2))//need to be critical
+ {
+ swiftness = true;
+ if (doCast(target, HEALING_TOUCH, true))
+ {
+ Nature_Swiftness_Timer = 120000;//2 min
+ Heal_Timer = 3000;
+ return true;
+ }
+ }
+ }
+ if (SWIFTMEND && (hp < 25 || GetLostHP(target) > 5000) && Swiftmend_Timer <= 3000 &&
+ (HasAuraName(target, REGROWTH) || HasAuraName(target, REJUVENATION)))
+ {
+ if (doCast(target, SWIFTMEND))
+ {
+ Swiftmend_Timer = 10000;
+ if (GetHealthPCT(target) > 75)
+ return true;
+ else if (!target->getAttackers().empty())
+ {
+ if (doCast(target, REGROWTH))
+ {
+ GC_Timer = 300;
+ return true;
+ }
+ }
+ }
+ }
+ if (hp > 35 && (hp < 75 || GetLostHP(target) > 3000) && Heal_Timer <= diff && NOURISH)
+ {
+ switch (urand(1,3))
+ {
+ case 1:
+ case 2:
+ if (doCast(target, NOURISH))
+ {
+ Heal_Timer = 3000;
+ return true;
+ }
+ break;
+ case 3:
+ if (doCast(target, HEALING_TOUCH))
+ {
+ 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 = tank == target && boss;
+ if (( (hp < 80 || GetLostHP(target) > 3500 || tanking) &&
+ Regrowth_Timer <= diff && GC_Timer <= diff && !target->HasAura(REGROWTH, me->GetGUID()) )
+ ||
+ (target->HasAura(REGROWTH, me->GetGUID()) && target->HasAura(REJUVENATION, me->GetGUID()) &&
+ (hp < 70 || GetLostHP(target) > 3000) && Regrowth_Timer <= diff && GC_Timer <= diff))
+ {
+ if (doCast(target, REGROWTH))
+ { Regrowth_Timer = 2000; return true; }
+ }
+ if (hp > 25 && (hp < 90 || GetLostHP(target) > 2000 || tanking) && GC_Timer <= diff &&
+ !HasAuraName(target, REJUVENATION, me->GetGUID()))
+ {
+ if (doCast(target, REJUVENATION))
+ {
+ if (!target->getAttackers().empty() && (hp < 75 || GetLostHP(target) > 4000))
+ if (SWIFTMEND && Swiftmend_Timer <= diff && doCast(target, SWIFTMEND))
+ Swiftmend_Timer = 10000;
+ GC_Timer = 500;
+ return true;
+ }
+ }
+ if (LIFEBLOOM != 0 && GC_Timer <= diff &&
+ ((hp < 85 && hp > 40) || (hp > 70 && tanking) ||
+ (hp < 70 && hp > 25 && HasAuraName(target, REGROWTH) && HasAuraName(target, REJUVENATION)) ||
+ (GetLostHP(target) > 1500 && hp > 35)))
+ {
+ Aura* bloom = target->GetAura(LIFEBLOOM, me->GetGUID());
+ if ((!bloom || bloom->GetStackAmount() < 3) && doCast(target, LIFEBLOOM))
+ return true;
+ }
+ if (hp > 30 && (hp < 70 || GetLostHP(target) > 3000) && Heal_Timer <= diff &&
+ doCast(target, HEALING_TOUCH))
+ {
+ Heal_Timer = 3000;
+ return true;
+ }
+ return false;
+ }
+
+ bool BuffTarget(Unit* target, uint32 diff)
+ {
+ if (GC_Timer > diff || Rand() > 40) return false;
+ if (me->isInCombat() && !master->GetMap()->IsRaid()) return false;
+ if (target && target->isAlive() && me->GetExactDist(target) < 30)
+ {
+ if (!HasAuraName(target, MARK_OF_THE_WILD))
+ if (doCast(target, MARK_OF_THE_WILD))
+ return true;
+ if (!HasAuraName(target, THORNS))
+ if (doCast(target, THORNS))
+ return true;
+ }
+ return false;
+ }
+
+ void DoNonCombatActions(uint32 diff)
+ {
+ //if eating or drinking don't do anything
+ if (GC_Timer > diff || me->IsMounted()) return;
+
+ RezGroup(REVIVE, master);
+
+ if (Feasting()) return;
+
+ if (BuffTarget(master, diff))
+ {
+ /*GC_Timer = 800;*/
+ return;
+ }
+ if (BuffTarget(me, diff))
+ {
+ /*GC_Timer = 800;*/
+ return;
+ }
+ }
+
+ void doInnervate(uint8 minmanaval = 30)
+ {
+ Unit* iTarget = NULL;
+
+ if (master->isInCombat() && GetManaPCT(master) < 20)
+ iTarget = master;
+ else if (me->isInCombat() && GetManaPCT(me) < 20)
+ iTarget = me;
+
+ Group* group = master->GetGroup();
+ if (!iTarget && !group)//first check master's bots
+ {
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = master->GetBotMap(i)->_Cre();
+ if (!bot || !bot->isInCombat() || bot->isDead()) continue;
+ if (me->GetExactDist(bot) > 30) continue;
+ if (GetManaPCT(bot) < minmanaval)
+ {
+ iTarget = bot;
+ break;
+ }
+ }
+ }
+ if (!iTarget)//cycle through player members...
+ {
+ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* tPlayer = itr->getSource();
+ if (tPlayer == NULL || !tPlayer->isInCombat() || tPlayer->isDead()) continue;
+ if (me->GetExactDist(tPlayer) > 30) continue;
+ if (GetManaPCT(tPlayer) < minmanaval)
+ {
+ iTarget = tPlayer;
+ break;
+ }
+ }
+ }
+ if (!iTarget)//... and their bots.
+ {
+ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* tPlayer = itr->getSource();
+ if (tPlayer == NULL || !tPlayer->HaveBot()) continue;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ if (!bot || bot->isDead()) continue;
+ if (me->GetExactDist(bot) > 30) continue;
+ if (GetManaPCT(bot) < minmanaval)
+ {
+ iTarget = bot;
+ break;
+ }
+ }
+ }
+ }
+
+ if (iTarget && !iTarget->HasAura(INNERVATE) && doCast(iTarget, INNERVATE))
+ {
+ if (iTarget->GetTypeId() == TYPEID_PLAYER)
+ me->MonsterWhisper("Innervate on You!", iTarget->GetGUID());
+ Innervate_Timer = iTarget->GetTypeId() == TYPEID_PLAYER ? 60000 : 30000;//1 min if player and 30 sec if bot
+ }
+ }
+
+ void CheckRoots(uint32 diff)
+ {
+ if (!ENTANGLING_ROOTS || GC_Timer > diff) return;
+ if (me->GetShapeshiftForm() != FORM_NONE) 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 (!REBIRTH || Rebirth_Timer > diff || Rand() > 10 || IsCasting() || me->IsMounted()) return;
+ Group* gr = master->GetGroup();
+ if (!gr)
+ {
+ Unit* target = master;
+ if (master->isAlive()) return;
+ if (master->isRessurectRequested()) return; //ressurected
+ if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ target = (Unit*)master->GetCorpse();
+ if (me->GetExactDist(target) > 30)
+ {
+ me->GetMotionMaster()->MovePoint(master->GetMapId(), *target);
+ Rebirth_Timer = 1500;
+ return;
+ }
+ else if (!target->IsWithinLOSInMap(me))
+ me->Relocate(*target);
+
+ if (doCast(target, REBIRTH))//rezzing
+ {
+ me->MonsterWhisper("Rezzing You", master->GetGUID());
+ Rebirth_Timer = me->getLevel() >= 60 ? 300000 : 600000; //5-10 min (improved possible)
+ }
+ 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->isRessurectRequested()) continue; //ressurected
+ if (tPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ target = (Unit*)tPlayer->GetCorpse();
+ if (master->GetMap() != target->FindMap()) continue;
+ if (!target->IsInWorld()) continue;
+ if (me->GetExactDist(target) > 30)
+ {
+ me->GetMotionMaster()->MovePoint(target->GetMapId(), *target);
+ Rebirth_Timer = 1500;
+ return;
+ }
+ else if (!target->IsWithinLOSInMap(me))
+ me->Relocate(*target);
+
+ if (doCast(target, REBIRTH))//rezzing
+ {
+ me->MonsterWhisper("Rezzing You", tPlayer->GetGUID());
+ Rebirth_Timer = me->getLevel() >= 60 ? 300000 : 600000; //5-10 min (improved possible)
+ return;
+ }
+ }
+ }
+
+ void SetStats(uint8 form, bool init = true)
+ {
+ switch (form)
+ {
+ case BEAR:
+ me->SetBotClass(BEAR);
+ if (me->getPowerType() != POWER_RAGE)
+ {
+ me->setPowerType(POWER_RAGE);
+ me->SetMaxPower(POWER_RAGE, 1000);
+ }
+ if (me->getLevel() >= 15)
+ me->SetPower(POWER_RAGE, 200);
+ else
+ me->SetPower(POWER_RAGE, 0);
+ if (me->getLevel() >= 40 && !me->HasAura(LEADER_OF_THE_PACK))
+ RefreshAura(LEADER_OF_THE_PACK);
+ setStats(BEAR, me->getRace(), master->getLevel());
+ break;
+ case CAT:
+ me->SetBotClass(CAT);
+ if (me->getPowerType() != POWER_ENERGY)
+ {
+ me->setPowerType(POWER_ENERGY);
+ me->SetMaxPower(POWER_ENERGY, 100);
+ me->SetPower(POWER_ENERGY, 0);
+ }
+ if (me->getLevel() >= 15)
+ me->SetPower(POWER_ENERGY, 60);
+ else
+ me->SetPower(POWER_ENERGY, 0);
+ if (me->getLevel() >= 40 && !me->HasAura(LEADER_OF_THE_PACK))
+ RefreshAura(LEADER_OF_THE_PACK);
+ RefreshAura(ENERGIZE, me->getLevel()/40 + master->Has310Flyer(false));
+ setStats(CAT, me->getRace(), master->getLevel());
+ break;
+ case CLASS_DRUID:
+ me->SetBotClass(CLASS_DRUID);
+ if (me->getPowerType() != POWER_MANA)
+ me->setPowerType(POWER_MANA);
+ if (init)
+ me->SetPower(POWER_MANA, mana);
+ setStats(CLASS_DRUID, me->getRace(), master->getLevel());
+ break;
+ }
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Heal_Timer = 0;
+ Regrowth_Timer = 0;
+ Swiftmend_Timer = 0;
+ Wild_Growth_Timer = 0;
+ Tranquility_Timer = 0;
+ Nature_Swiftness_Timer = 0;
+ Rebirth_Timer = 0;
+ Warstomp_Timer = 0;
+ MangleB_Timer = 0;
+ Claw_Timer = 0;
+ Rake_Timer = 0;
+ Shred_Timer = 0;
+ Rip_Timer = 0;
+ Mangle_Cat_Timer = 0;
+ Moonfire_Timer = 0;
+ Starfire_Timer = 0;
+ Wrath_Timer = 0;
+ Hurricane_Timer = 0;
+ Innervate_Timer = 0;
+ formtimer = 0;
+ clearcast = false;
+ swiftness = false;
+ power = POWER_MANA;
+ mana = 0;
+ rage = 0;
+ rageIncomeMult = sWorld->getRate(RATE_POWER_RAGE_INCOME);
+ rageLossMult = sWorld->getRate(RATE_POWER_RAGE_LOSS);
+ ragetimer = 0;
+ ragetimer2 = 0;
+
+ if (master)
+ {
+ setStats(CLASS_DRUID, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_DRUID);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (MangleB_Timer > diff) MangleB_Timer -= diff;
+ if (Claw_Timer > diff) Claw_Timer -= diff;
+ if (Rake_Timer > diff) Rake_Timer -= diff;
+ if (Shred_Timer > diff) Shred_Timer -= diff;
+ if (Mangle_Cat_Timer > diff) Mangle_Cat_Timer -= diff;
+ if (Moonfire_Timer > diff) Moonfire_Timer -= diff;
+ if (Starfire_Timer > diff) Starfire_Timer -= diff;
+ if (Wrath_Timer > diff) Wrath_Timer -= diff;
+ if (Hurricane_Timer > diff) Hurricane_Timer -= diff;
+ if (Innervate_Timer > diff) Innervate_Timer -= diff;
+ if (Rip_Timer > diff) Rip_Timer -= diff;
+ if (Regrowth_Timer > diff) Regrowth_Timer -= diff;
+ if (Heal_Timer > diff) Heal_Timer -= diff;
+ if (Swiftmend_Timer > diff) Swiftmend_Timer -= diff;
+ if (Wild_Growth_Timer > diff) Wild_Growth_Timer -= diff;
+ if (Nature_Swiftness_Timer > diff) Nature_Swiftness_Timer -= diff;
+ if (Tranquility_Timer > diff) Tranquility_Timer -= diff;
+ if (Rebirth_Timer > diff) Rebirth_Timer -= diff;
+ if (Warstomp_Timer > diff) Warstomp_Timer -= diff;
+ if (formtimer > diff) formtimer -= diff;
+ if (ragetimer > diff) ragetimer -= diff;
+ if (ragetimer2 > diff) ragetimer2 -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ MARK_OF_THE_WILD = InitSpell(me, MARK_OF_THE_WILD_1);
+ THORNS = InitSpell(me, THORNS_1);
+ HEALING_TOUCH = InitSpell(me, HEALING_TOUCH_1);
+ REGROWTH = InitSpell(me, REGROWTH_1);
+ REJUVENATION = InitSpell(me, REJUVENATION_1);
+ LIFEBLOOM = InitSpell(me, LIFEBLOOM_1);
+ NOURISH = InitSpell(me, NOURISH_1);
+ /*tal*/WILD_GROWTH = lvl >= 60 ? InitSpell(me, WILD_GROWTH_1) : 0;
+ /*tal*/SWIFTMEND = lvl >= 40 ? InitSpell(me, SWIFTMEND_1) : 0;
+ TRANQUILITY = InitSpell(me, TRANQUILITY_1);
+ REVIVE = InitSpell(me, REVIVE_1);
+ REBIRTH = InitSpell(me, REBIRTH_1);
+ BEAR_FORM = InitSpell(me, BEAR_FORM_1);
+ SWIPE = InitSpell(me, SWIPE_1);
+ /*tal*/MANGLE_BEAR = lvl >= 50 ? InitSpell(me, MANGLE_BEAR_1) : 0;
+ BASH = InitSpell(me, BASH_1);
+ CAT_FORM = InitSpell(me, CAT_FORM_1);
+ CLAW = InitSpell(me, CLAW_1);
+ RAKE = InitSpell(me, RAKE_1);
+ SHRED = InitSpell(me, SHRED_1);
+ RIP = InitSpell(me, RIP_1);
+ /*tal*/MANGLE_CAT = lvl >= 50 ? InitSpell(me, MANGLE_CAT_1) : 0;
+ MOONFIRE = InitSpell(me, MOONFIRE_1);
+ STARFIRE = InitSpell(me, STARFIRE_1);
+ WRATH = InitSpell(me, WRATH_1);
+ HURRICANE = InitSpell(me, HURRICANE_1);
+ FAIRIE_FIRE = InitSpell(me, FAIRIE_FIRE_1);
+ CURE_POISON = InitSpell(me, CURE_POISON_1);
+ INNERVATE = InitSpell(me, INNERVATE_1);
+ ENTANGLING_ROOTS = InitSpell(me, ENTANGLING_ROOTS_1);
+ /*tal*/NATURES_SWIFTNESS = lvl >= 30 ? InitSpell(me, NATURES_SWIFTNESS_1) : 0;
+ WARSTOMP = WARSTOMP_1;
+ }
+
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+ if (level >= 78)
+ RefreshAura(SPELLDMG2, 3); //+18%
+ else if (level >= 65)
+ RefreshAura(SPELLDMG2, 2); //+12%
+ else if (level >= 50)
+ RefreshAura(SPELLDMG2); //+6%
+ if (level >= 45)
+ RefreshAura(NATURAL_PERFECTION3); //4%
+ else if (level >= 43)
+ RefreshAura(NATURAL_PERFECTION2); //3%
+ else if (level >= 41)
+ RefreshAura(NATURAL_PERFECTION1); //2%
+ if (level >= 50)
+ RefreshAura(LIVING_SEED3); //100%
+ else if (level >= 48)
+ RefreshAura(LIVING_SEED2); //66%
+ else if (level >= 46)
+ RefreshAura(LIVING_SEED1); //33%
+ if (level >= 55)
+ RefreshAura(REVITALIZE3, 5); //75% (15%)x5
+ else if (level >= 53)
+ RefreshAura(REVITALIZE2, 3); //30% (10%)x3
+ else if (level >= 51)
+ RefreshAura(REVITALIZE1, 3); //15% (5%)x3
+ if (level >= 70)
+ RefreshAura(OMEN_OF_CLARITY, 3); //x3
+ else if (level >= 40)
+ RefreshAura(OMEN_OF_CLARITY, 2); //x2
+ else if (level >= 20)
+ RefreshAura(OMEN_OF_CLARITY); //x1
+ if (level >= 45)
+ RefreshAura(GLYPH_SWIFTMEND); //no comsumption
+ if (level >= 40)
+ RefreshAura(GLYPH_INNERVATE); //no comsumption
+ if (level >= 20)
+ RefreshAura(NATURESGRACE);
+ if (level >= 78)
+ {
+ RefreshAura(T9_RESTO_P4_BONUS);
+ RefreshAura(T8_RESTO_P4_BONUS);
+ RefreshAura(T9_BALANCE_P2_BONUS);
+ RefreshAura(T10_BALANCE_P2_BONUS);
+ RefreshAura(T10_BALANCE_P4_BONUS);
+ }
+ }
+
+ private:
+ uint32
+ /*Buffs*/MARK_OF_THE_WILD, THORNS,
+/*Heal/Rez*/HEALING_TOUCH, REGROWTH, REJUVENATION, LIFEBLOOM, NOURISH, WILD_GROWTH, SWIFTMEND, TRANQUILITY, REVIVE, REBIRTH,
+ /*Bear*/BEAR_FORM, SWIPE, MANGLE_BEAR, BASH,
+ /*Cat*/CAT_FORM, CLAW, RAKE, SHRED, RIP, MANGLE_CAT,
+ /*Balance*/MOONFIRE, STARFIRE, WRATH, HURRICANE, FAIRIE_FIRE,
+ /*Misc*/CURE_POISON, INNERVATE, ENTANGLING_ROOTS, NATURES_SWIFTNESS, WARSTOMP;
+ //Timers/other
+/*Heal*/uint32 Heal_Timer, Regrowth_Timer, Swiftmend_Timer, Wild_Growth_Timer,
+/*Heal*/ Tranquility_Timer, Nature_Swiftness_Timer, Rebirth_Timer;
+/*Bear*/uint32 MangleB_Timer;
+/*Cat*/ uint32 Claw_Timer, Rake_Timer, Shred_Timer, Rip_Timer, Mangle_Cat_Timer;
+/*Bal*/ uint32 Moonfire_Timer, Starfire_Timer, Wrath_Timer, Hurricane_Timer, Innervate_Timer;
+/*Misc*/uint32 formtimer, ragetimer, ragetimer2, Warstomp_Timer;
+/*Chck*/bool clearcast, swiftness;
+/*Misc*/Powers power; uint32 mana, 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,
+ CAT_FORM_1 = 768,
+ CLAW_1 = 1082,
+ RAKE_1 = 1822,
+ SHRED_1 = 5221,
+ RIP_1 = 1079,
+ /*tal*/MANGLE_CAT_1 = 33876,
+ MOONFIRE_1 = 8921,
+ STARFIRE_1 = 2912,
+ WRATH_1 = 5176,
+ HURRICANE_1 = 16914,
+ FAIRIE_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
+ /*Talent*/LEADER_OF_THE_PACK = 24932,
+ //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
+ {
+ //NATURESGRACEBUFF = 16886,
+ OMEN_OF_CLARITY_BUFF = 16870,
+ };
+ };
+};
+
+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..ef321bb
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp
@@ -0,0 +1,340 @@
+#include "bot_ai.h"
+//#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+//#include "SpellAuras.h"
+/*
+Complete - 1%
+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);
+ }
+
+ 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;
+ }
+
+ struct hunter_botAI : public bot_minion_ai
+ {
+ hunter_botAI(Creature* creature) : bot_minion_ai(creature)
+ {
+ Reset();
+ }
+
+ //void CreatePet()
+ //{
+
+ // pet = me->GetBotsPet(60238);
+
+ // if (pet == NULL)
+ // return;
+
+ // pet->UpdateCharmAI();
+ // pet->setFaction(me->getFaction());
+ // pet->SetReactState(REACT_DEFENSIVE);
+ // pet->GetMotionMaster()->MoveFollow(me, PET_FOLLOW_DIST*urand(1, 2),PET_FOLLOW_ANGLE);
+ // CharmInfo* charmInfonewbot = pet->InitCharmInfo();
+ // pet->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW);
+ // pet->UpdateStats(STAT_STRENGTH);
+ // pet->UpdateStats(STAT_AGILITY);
+ // pet->SetLevel(master->getLevel());
+
+ // /*float val2 = master->getLevel()*4.f + pet->GetStat(STAT_STRENGTH)*5.f;
+
+ // val2=100.0;
+ // uint32 attPowerMultiplier=1;
+ // pet->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, uint32(val2));
+ // pet->UpdateAttackPowerAndDamage();
+ // pet->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, uint32(val2 * attPowerMultiplier));
+ // pet->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, uint32(val2 * attPowerMultiplier)*3+master->getLevel());
+ // pet->UpdateDamagePhysical(BASE_ATTACK);*/
+
+ //}
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_HUNTER) != 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*) { master->SetNpcBotDied(me->GetGUID()); }
+ void DoNonCombatActions(uint32 const /*diff*/)
+ {}
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+
+ if (IAmDead()) return;
+
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+
+ //if (pet && pet != NULL && pet->isDead())
+ //{
+ // me->SetBotsPetDied();
+ // pet = NULL;
+ //}
+
+ //if we think we have a pet, but master doesn't, it means we teleported
+ //if (pet && !me->getBotsPet())
+ //{
+ // me->SetBotsPetDied();
+ // pet = NULL;
+ //}
+
+ DoNormalAttack(diff);
+ ScriptedAI::UpdateAI(diff);
+
+ //if low on health, drink a potion
+ if (GetHealthPCT(me) < 65)
+ {
+ if (doCast(me, HEALINGPOTION))
+ Potion_cd = POTION_CD;
+ }
+
+ //if low on mana, drink a potion
+ if (GetManaPCT(me) < 65 && Potion_cd <= diff)
+ {
+ if (doCast(me, MANAPOTION))
+ Potion_cd = POTION_CD;
+ }
+
+ opponent = SelectTarget(SELECT_TARGET_TOPAGGRO, 0);
+ if (!opponent && !me->getVictim())
+ {
+ me->CombatStop();
+ //ResetOrGetNextTarget();
+
+ //to reduce the number of crashes, remove pet whenever we are not in combat
+ //if (pet != NULL && pet->isAlive())
+ //{
+ // me->SetBotsPetDied();
+ // pet = NULL;
+ //}
+ return;
+ }
+
+ //if (pet == NULL)
+ // CreatePet();
+
+ //if (pet && pet->isAlive() &&
+ // !pet->isInCombat() &&
+ // me->getVictim())
+ //{
+ // pet->Attack (me->getVictim(), true);
+ // pet->GetMotionMaster()->MoveChase(me->getVictim(), 1, 0);
+ //}
+ }
+
+ void DoNormalAttack(uint32 diff)
+ {
+ AttackerSet m_attackers = master->getAttackers();
+ if (!opponent || opponent->isDead()) return;
+
+ // try to get rid of enrage effect
+ if (TRANQ_SHOT && (HasAuraName(opponent, "Enrage") || (HasAuraName(opponent, "Frenzy"))))
+ {
+ me->InterruptNonMeleeSpells(true, AUTO_SHOT);
+ me->MonsterSay("Tranquil shot!", LANG_UNIVERSAL, opponent->GetGUID());
+ doCast(opponent, TRANQ_SHOT, true);
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ // silence it
+ if (SILENCING_SHOT && opponent->HasUnitState(UNIT_STATE_CASTING) && SilencingShot_Timer <= diff)
+ {
+ doCast(opponent, SILENCING_SHOT);
+ SilencingShot_Timer = 25000;
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ // mark it
+ if (!HasAuraName(opponent, "Hunter's Mark"))
+ {
+ doCast(opponent, HUNTERS_MARK);
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ // sting it
+ if (SCORPID_STING && !opponent->HasAura(SCORPID_STING, me->GetGUID()))
+ {
+ me->InterruptNonMeleeSpells(true, AUTO_SHOT);
+ doCast(opponent, SCORPID_STING);
+ // me->MonsterSay("Scorpid Sting!", LANG_UNIVERSAL, NULL);
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ if (CHIMERA_SHOT && ChimeraShot_Timer <= diff && GC_Timer <= diff)
+ {
+ me->InterruptNonMeleeSpells(true, AUTO_SHOT);
+ doCast(opponent, CHIMERA_SHOT);
+ ChimeraShot_Timer = 10000;
+ // me->MonsterSay("Chimera Sting!", LANG_UNIVERSAL, NULL);
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ if (ARCANE_SHOT && ArcaneShot_cd <= diff && GC_Timer <= diff)
+ {
+ me->InterruptNonMeleeSpells(true, AUTO_SHOT);
+ doCast(opponent, ARCANE_SHOT);
+ // me->MonsterSay("Arcane shot!", LANG_UNIVERSAL, NULL);
+ ArcaneShot_cd = 60;
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+
+ if (AIMED_SHOT && AimedShot_Timer <= diff && GC_Timer <= diff)
+ {
+ me->InterruptNonMeleeSpells( true, AUTO_SHOT );
+ doCast(opponent, AIMED_SHOT);
+ // me->MonsterSay("Aimed shot!", LANG_UNIVERSAL, NULL);
+ AimedShot_Timer = 120;
+ // doCast(opponent, AUTO_SHOT);
+ // return;
+ }
+ //Temp Feign death For Debug
+ AttackerSet b_attackers = me->getAttackers();
+ if (!b_attackers.empty())
+ {
+ for(AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter)
+ if (*iter && (*iter)->getVictim()->GetGUID() == me->GetGUID() &&
+ me->GetDistance(*iter) < 10 &&
+ Feign_Death_Timer <= diff && GC_Timer <= diff)
+ {
+ doCast(me, FEIGN_DEATH, true);
+ opponent->AddThreat(me, -100000);
+ me->CombatStop();
+ Feign_Death_Timer = 25000;
+ me->CombatStart(opponent);
+ }
+ }
+
+ doCast(opponent, AUTO_SHOT);
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ ArcaneShot_cd = 0;
+ ChimeraShot_Timer = 0;
+ SilencingShot_Timer = 0;
+ AimedShot_Timer = 0;
+ Feign_Death_Timer = 0;
+
+ if (master)
+ {
+ setStats(CLASS_HUNTER, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_HUNTER);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (ArcaneShot_cd > diff) ArcaneShot_cd -= diff;
+ if (ChimeraShot_Timer > diff) ChimeraShot_Timer -= diff;
+ if (SilencingShot_Timer > diff) SilencingShot_Timer -= diff;
+ if (AimedShot_Timer > diff) AimedShot_Timer -= diff;
+ if (Feign_Death_Timer > diff) Feign_Death_Timer -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ AUTO_SHOT = AUTO_SHOT_1;
+ TRANQ_SHOT = InitSpell(me, TRANQ_SHOT_1);
+ SCORPID_STING = InitSpell(me, SCORPID_STING_1);
+ HUNTERS_MARK = InitSpell(me, HUNTERS_MARK_1);
+ ARCANE_SHOT = InitSpell(me, ARCANE_SHOT_1);
+ CHIMERA_SHOT = lvl >= 60 ? CHIMERA_SHOT_1 : 0;
+ AIMED_SHOT = lvl >= 20 ? InitSpell(me, AIMED_SHOT_1) : 0;
+ SILENCING_SHOT = lvl >= 50 ? SILENCING_SHOT_1 : 0;
+ ASPECT_OF_THE_DRAGONHAWK = InitSpell(me, ASPECT_OF_THE_DRAGONHAWK_1);
+ FEIGN_DEATH = InitSpell(me, FEIGN_DEATH_1);
+ }
+
+ void ApplyClassPassives()
+ { }
+
+ private:
+ uint32
+ AUTO_SHOT, TRANQ_SHOT, SCORPID_STING, HUNTERS_MARK, ARCANE_SHOT, CHIMERA_SHOT, AIMED_SHOT,
+ SILENCING_SHOT, ASPECT_OF_THE_DRAGONHAWK, FEIGN_DEATH;
+ //Timers
+ uint32 ArcaneShot_cd, ChimeraShot_Timer, SilencingShot_Timer, AimedShot_Timer, Feign_Death_Timer;
+
+ enum HunterBaseSpells
+ {
+ AUTO_SHOT_1 = 75,
+ TRANQ_SHOT_1 = 19801,
+ SCORPID_STING_1 = 3043,
+ HUNTERS_MARK_1 = 14325,
+ ARCANE_SHOT_1 = 3044,
+ CHIMERA_SHOT_1 = 53209,
+ AIMED_SHOT_1 = 19434,
+ SILENCING_SHOT_1 = 34490,
+ ASPECT_OF_THE_DRAGONHAWK_1 = 61846,
+ FEIGN_DEATH_1 = 5384,
+ };
+
+ enum HunterPassives
+ {
+ };
+
+ enum HunterSpecial
+ {
+ };
+ };
+};
+
+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..1288f4d
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_mage_ai.cpp
@@ -0,0 +1,935 @@
+#include "bot_ai.h"
+#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+#include "WorldSession.h"
+/*
+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);
+ }
+
+ 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;
+ }
+
+ struct mage_botAI : public bot_minion_ai
+ {
+ mage_botAI(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_MAGE) != SPELL_CAST_OK)
+ return false;
+ bool result = bot_ai::doCast(victim, spellId, triggered);
+
+ if (result && spellId != MANAPOTION && me->HasAura(CLEARCASTBUFF))
+ {
+ cost = info->CalcPowerCost(me, info->GetSchoolMask());
+ clearcast = true;
+ }
+
+ return result;
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { master->SetNpcBotDied(me->GetGUID()); }
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == (COMMAND_ATTACK) && !force) return;
+ Aggro(u);
+ SetBotCommandState(COMMAND_ATTACK);
+ GetInPosition(force, true);
+ }
+
+ void Counter()
+ {
+ Unit* u = me->getVictim();
+ bool cSpell = CounterSpell_cd <= 5000;
+ bool blast = FireBlast_cd <= 3000 && !(u && u->ToCreature() && (u->ToCreature()->isWorldBoss() || u->ToCreature()->IsDungeonBoss())) && me->HasAura(IMPACT_BUFF);
+ if (!cSpell && !blast) return;
+ if (u && u->IsNonMeleeSpellCasted(false))
+ {
+ temptimer = GC_Timer;
+ if (me->IsNonMeleeSpellCasted(false))
+ me->InterruptNonMeleeSpells(false);
+ if (cSpell && doCast(u, COUNTERSPELL))
+ CounterSpell_cd = 15000;
+ else if (blast && doCast(u, FIREBLAST))
+ FireBlast_cd = 6000;
+ GC_Timer = temptimer;
+ }
+ else if (cSpell)
+ {
+ if (Unit* target = FindCastingTarget(30))
+ {
+ temptimer = GC_Timer;
+ if (me->IsNonMeleeSpellCasted(false))
+ me->InterruptNonMeleeSpells(false);
+ if (doCast(target, COUNTERSPELL))
+ {
+ CounterSpell_cd = 15000;
+ GC_Timer = temptimer;
+ }
+ }
+ }
+ }
+
+ void CheckSpellSteal(uint32 diff)
+ {
+ if (!SPELLSTEAL || Rand() > 25 || GC_Timer > diff || IsCasting()) return;
+ Unit* target = FindHostileDispelTarget(30, true);
+ if (target && doCast(target, SPELLSTEAL))
+ GC_Timer = 800;
+ }
+
+ void DoNonCombatActions(uint32 diff)
+ {
+ if (GC_Timer > diff || me->IsMounted()) return;
+ if (Feasting()) return;
+
+ if (!HasAuraName(me, DAMPENMAGIC) &&
+ doCast(me, DAMPENMAGIC))
+ { /*GC_Timer = 800;*/ return; }
+
+ if (!HasAuraName(me, ICEARMOR) &&
+ doCast(me, ICEARMOR))
+ { /*GC_Timer = 800;*/ return; }
+ }
+
+ bool BuffTarget(Unit* target, uint32 diff)
+ {
+ if (GC_Timer > diff || !target || target->isDead() || Rand() > 50) return false;
+ if (me->isInCombat() && !master->GetMap()->IsRaid()) return false;
+ if (me->GetExactDist(target) > 30) return false;
+ if (target->getPowerType() == POWER_MANA &&
+ !HasAuraName(target, ARCANEINTELLECT) &&
+ doCast(target, ARCANEINTELLECT))
+ {
+ /*GC_Timer = 800;*/
+ return true;
+ }
+ return false;
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (IAmDead()) return;
+ if (!me->getVictim())
+ Evade();
+ if (clearcast && me->HasAura(CLEARCASTBUFF) && !me->IsNonMeleeSpellCasted(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;
+ }
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ BreakCC(diff);
+ if (CCed(me) && (!ICEBLOCK || !me->HasAura(ICEBLOCK))) return;//TODO
+
+ CheckBlink(diff);
+ CheckPoly(diff);
+ CheckPots(diff);
+ CureTarget(master, REMOVE_CURSE, diff);
+ CureTarget(me, REMOVE_CURSE, diff);
+ CureGroup(master, REMOVE_CURSE, diff);
+
+ FocusMagic(diff);
+ BuffAndHealGroup(master, diff);
+
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+
+ if (!CheckAttackTarget(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
+ if (ICE_BARRIER && Ice_Barrier_cd <= diff && u && u->getVictim() == me &&
+ u->GetDistance(me) < 8 && !me->HasAura(ICE_BARRIER))
+ {
+ if (me->IsNonMeleeSpellCasted(true))
+ me->InterruptNonMeleeSpells(true);
+ if (doCast(me, ICE_BARRIER))
+ {
+ Ice_Barrier_cd = 41000 - me->getLevel()*200;//down to 25 sec on 80
+ GC_Timer = 800;
+ return;
+ }
+ }
+ if ((!ICE_BARRIER || Ice_Barrier_cd > diff) &&
+ BLINK && Blink_cd < 3000 && u && u->getVictim() == me &&
+ !me->HasAura(ICE_BARRIER) && u->GetDistance(me) < 6)
+ {
+ if (me->IsNonMeleeSpellCasted(true))
+ me->InterruptNonMeleeSpells(true);
+ if (doCast(me, BLINK))
+ {
+ Blink_cd = 15000 - me->getLevel()/4 * 100;
+ GC_Timer = 800;
+ return;
+ }
+ }
+
+ if (me->HasAura(ICEBLOCK))
+ if (((GetManaPCT(me) > 45 && GetHealthPCT(me) > 80) || b_attackers.empty()) && Iceblock_cd <= 57000 && tank)
+ me->RemoveAurasDueToSpell(ICEBLOCK);
+ //ICEBLOCK
+ if (ICEBLOCK && Rand() < 50 && !b_attackers.empty() && tank && Iceblock_cd <= diff &&
+ (GetManaPCT(me) < 15 || GetHealthPCT(me) < 45 || b_attackers.size() > 4) &&
+ !me->HasAura(ICEBLOCK))
+ {
+ if (me->IsNonMeleeSpellCasted(true))
+ me->InterruptNonMeleeSpells(true);
+ if (doCast(me, ICEBLOCK))
+ {
+ Iceblock_cd = 60000;
+ return;
+ }
+ }
+
+ if (IsCasting()) return;
+
+ BOLT = (CCed(opponent, true) || (opponent->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED) && me->HasAura(COMBUSTION))) ? FIREBALL : FROSTBOLT;
+ NOVA = BOLT == FIREBALL && BLASTWAVE ? BLASTWAVE : FROSTNOVA ? FROSTNOVA : 0;
+
+ float dist = me->GetExactDist(opponent);
+ if (dist > 30)
+ return;
+
+ if (COMBUSTION && Rand() < 15 &&
+ (opponent->GetMaxHealth() > master->GetMaxHealth()*10 ||
+ m_attackers.size() > 1 || b_attackers.size() > 2))
+ {
+ if (!HasAuraName(me, "Combustion") && Combustion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, COMBUSTION))
+ {
+ Combustion_cd = 60000;
+ //Reset timers for fun
+ Nova_cd = 0; FireBlast_cd = 0; DragonBreath_cd = 0;
+ }
+ GC_Timer = temptimer;
+ }
+ }
+ //DAMAGE
+ //PYROBLAST
+ if (PYROBLAST && Rand() < 75 && Pyroblast_cd <= diff && GC_Timer <= diff &&
+ b_attackers.size() < 2 && dist < 30 && opponent->IsPolymorphed() &&
+ doCast(opponent, PYROBLAST))
+ Pyroblast_cd = 50;
+ //nova //TODO: SEPARATE
+ u = me->SelectNearestTarget(7);
+ if (u && NOVA && Nova_cd <= diff && !CCed(u, true) && IsInBotParty(u->getVictim()))
+ {
+ Unit* tar = u->getVictim();
+ if (tar && IsInBotParty(tar) && doCast(me, NOVA))
+ {
+ Nova_cd = 15000;
+ return;
+ }
+ }
+ //living bomb
+ if (LIVINGBOMB && Rand() < 25 && Living_Bomb_cd <= diff && GC_Timer <= diff &&
+ dist < 30 && opponent->GetHealth() > me->GetHealth()/2 &&
+ !opponent->HasAura(LIVINGBOMB, me->GetGUID()) &&
+ doCast(opponent, LIVINGBOMB))
+ {
+ Living_Bomb_cd = 6000;
+ GC_Timer = 500;
+ return;
+ }
+ //cone of cold
+ if (CONEOFCOLD && ConeofCold_cd <= diff && GC_Timer <= diff && dist < 7 &&
+ me->HasInArc(M_PI, opponent) &&
+ doCast(opponent, CONEOFCOLD))
+ {
+ ConeofCold_cd = 14000;
+ GC_Timer = 500;
+ return;
+ }
+ //dragon's breath
+ u = me->SelectNearestTarget(7);
+ if (DRAGONBREATH && u && DragonBreath_cd <= diff && GC_Timer <= diff &&
+ me->HasInArc(M_PI, opponent) && !HasAuraName(u, FROSTNOVA) &&
+ doCast(opponent, DRAGONBREATH))
+ {
+ DragonBreath_cd = 25000;
+ 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 (FireBlast_cd <= diff && GC_Timer <= diff && dist < 20 &&
+ Rand() < 20 + 80*(!opponent->HasAuraType(SPELL_AURA_MOD_STUN) && me->HasAura(IMPACT_BUFF)) &&
+ doCast(opponent, FIREBLAST))
+ {
+ FireBlast_cd = 6000;
+ GC_Timer = 500;
+ return;
+ }
+ //flamestrike
+ if (GC_Timer <= diff && Rand() < 60 && me->HasAura(FIRESTARTERBUFF))
+ {
+ Unit* FStarget = FindAOETarget(30, true, false);
+ if (FStarget && doCast(FStarget, FLAMESTRIKE, true))
+ {
+ me->RemoveAurasDueToSpell(FIRESTARTERBUFF);
+ GC_Timer = 0;
+ return;
+ }
+ }
+ //blizzard
+ if (BLIZZARD && Rand() < 80 && Blizzard_cd <= diff)
+ {
+ Unit* blizztarget = FindAOETarget(30, true);
+ if (blizztarget && doCast(blizztarget, BLIZZARD))
+ {
+ Blizzard_cd = 5000;
+ return;
+ }
+ Blizzard_cd = 2000;//fail
+ }
+ //Frost of Fire Bolt
+ if (Rand() < 75 && Bolt_cd <= diff && dist < 30 &&
+ doCast(opponent, BOLT))
+ {
+ Bolt_cd = uint32(float(sSpellMgr->GetSpellInfo(BOLT)->CalcCastTime()/100) * me->GetFloatValue(UNIT_MOD_CAST_SPEED) + 200);
+ return;
+ }
+ //Arcane Missiles
+ if (Rand() < 10 && GC_Timer <= diff && !me->isMoving() && dist < 20 &&
+ doCast(opponent, ARCANEMISSILES))
+ return;
+ }
+
+ void CheckPoly(uint32 diff)
+ {
+ if (polyCheckTimer <= diff)
+ {
+ Polymorph = FindAffectedTarget(POLYMORPH, me->GetGUID());
+ polyCheckTimer = 2000;
+ }
+ }
+
+ void CheckPoly2()
+ {
+ if (Polymorph == false && GC_Timer < 500)
+ {
+ if (Unit* target = FindPolyTarget(30, me->getVictim()))
+ {
+ if (doCast(target, POLYMORPH))
+ {
+ Polymorph = true;
+ polyCheckTimer = 2000;
+ }
+ }
+ }
+ }
+
+ void CheckPots(uint32 diff)
+ {
+ if (GetHealthPCT(me) < 70 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, HEALINGPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ if (GetManaPCT(me) < 35)
+ {
+ if (Evocation_cd <= diff && !me->isMoving() && me->getAttackers().empty() && doCast(me, EVOCATION))
+ Evocation_cd = 60000;
+ else 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()) return;
+ if (Blink_cd > diff || me->getLevel() < 20 || IsCasting()) return;
+ if (me->GetExactDist(master) > std::max(float(master->GetBotFollowDist()), 25.f)/* && me->IsWithinLOSInMap(master)*/)
+ {
+ me->SetFacingTo(me->GetAngle(master));
+ if (doCast(me, BLINK))
+ {
+ Blink_cd = 15000 - me->getLevel()/4 * 100; //13 sec with improved
+ GC_Timer = 500;
+ }
+ return;
+ }
+ if (!me->getAttackers().empty() && me->GetExactDist(master) > 15)
+ {
+ if (Unit* op = me->SelectNearestTarget(10))
+ {
+ if (op->getVictim() == me)
+ {
+ me->SetFacingTo(me->GetAngle(master));
+ if (doCast(me, BLINK))
+ {
+ Blink_cd = 15000 - me->getLevel()/4 * 100; //13 sec with improved
+ GC_Timer = 500;
+ }
+ }
+ }
+ }
+ }
+
+ void FocusMagic(uint32 diff)
+ {
+ if (!FOCUSMAGIC || me->getLevel() < 20 || fmCheckTimer > diff || GC_Timer > diff || Rand() < 50 || IsCasting())
+ return;
+ if (Unit* target = FindAffectedTarget(FOCUSMAGIC, me->GetGUID(), 70, 2))
+ {
+ fmCheckTimer = 30000;
+ return;
+ }
+ else
+ {
+ Group* pGroup = master->GetGroup();
+ if (!pGroup)
+ {
+ if (master->getPowerType() == POWER_MANA && !master->HasAura(FOCUSMAGIC) && me->GetExactDist(master) < 30)
+ target = master;
+ }
+ else
+ {
+ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* pPlayer = itr->getSource();
+ if (!pPlayer || pPlayer->isDead()) continue;
+ if (me->GetMapId() != pPlayer->GetMapId()) continue;
+ if ((pPlayer->getClass() == CLASS_MAGE ||
+ pPlayer->getClass() == CLASS_PRIEST ||
+ pPlayer->getClass() == CLASS_SHAMAN ||
+ pPlayer->getClass() == CLASS_DRUID ||
+ pPlayer->getClass() == CLASS_PALADIN ||
+ pPlayer->getClass() == CLASS_WARLOCK) &&
+ !pPlayer->HasAura(FOCUSMAGIC) && me->GetExactDist(pPlayer) < 30)
+ {
+ target = pPlayer;
+ break;
+ }
+ }
+ if (!target)
+ {
+ for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* pPlayer = itr->getSource();
+ if (!pPlayer || !pPlayer->HaveBot()) continue;
+ if (me->GetMapId() != pPlayer->GetMapId()) continue;
+ for (uint8 i = 0; i != pPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = pPlayer->GetBotMap(i)->_Cre();
+ if (!cre || cre == me || cre->isDead() || cre->getPowerType() != POWER_MANA) continue;
+ if ((cre->GetBotClass() == CLASS_MAGE ||
+ cre->GetBotClass() == CLASS_PRIEST ||
+ cre->GetBotClass() == CLASS_SHAMAN ||
+ cre->GetBotClass() == CLASS_DRUID ||
+ cre->GetBotClass() == CLASS_WARLOCK) &&
+ !cre->HasAura(FOCUSMAGIC) && me->GetExactDist(cre) < 30)
+ {
+ 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 == FIREBLAST ||
+ spellId == CONEOFCOLD/* ||
+ 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 == FLAMESTRIKE ||
+ spellId == PYROBLAST ||
+ spellId == BLASTWAVE ||
+ spellId == DRAGONBREATH/* ||
+ 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 == CONEOFCOLD)
+ 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 != 0)
+ {
+ //only players for now
+ if (!IS_PLAYER_GUID(aftercastTargetGuid))
+ {
+ aftercastTargetGuid = 0;
+ return;
+ }
+ Player* pTarget = sObjectAccessor->FindPlayer(aftercastTargetGuid);
+ aftercastTargetGuid = 0;
+ 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->GetGUIDLow());
+
+ pTarget->SendNewItem(pItem, count, true, false, true);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Pyroblast_cd = 0;
+ FireBlast_cd = 0;
+ DragonBreath_cd = 0;
+ Combustion_cd = 30000;//30 sec for reset
+ Ice_Barrier_cd = 0;
+ Iceblock_cd = 0;
+ ConeofCold_cd = 0;
+ Blizzard_cd = 10000;
+ CounterSpell_cd = 0;
+ Evocation_cd = 0;
+ Blink_cd = 0;
+ Bolt_cd = 0;
+ Nova_cd = 0;
+ polyCheckTimer = 0;
+ fmCheckTimer = 0;
+ Polymorph = false;
+ clearcast = false;
+ BOLT = FROSTBOLT;//default frost
+ NOVA = BLASTWAVE != 0 ? BLASTWAVE : FROSTNOVA;
+
+ if (master)
+ {
+ setStats(CLASS_MAGE, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_MAGE);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Pyroblast_cd > diff) Pyroblast_cd -= diff;
+ if (Ice_Barrier_cd > diff) Ice_Barrier_cd -= diff;
+ if (Iceblock_cd > diff) Iceblock_cd -= diff;
+ if (ConeofCold_cd > diff) ConeofCold_cd -= diff;
+ if (Living_Bomb_cd > diff) Living_Bomb_cd -= diff;
+ if (FireBlast_cd > diff) FireBlast_cd -= diff;
+ if (Bolt_cd > diff) Bolt_cd -= diff;
+ if (Blizzard_cd > diff) Blizzard_cd -= diff;
+ if (CounterSpell_cd > diff) CounterSpell_cd -= diff;
+ if (Nova_cd > diff) Nova_cd -= diff;
+ if (DragonBreath_cd > diff) DragonBreath_cd -= diff;
+ if (Blink_cd > diff) Blink_cd -= diff;
+ if (Combustion_cd > diff) Combustion_cd -= diff;
+ if (Evocation_cd > diff) Evocation_cd -= diff;
+ if (polyCheckTimer > diff) polyCheckTimer -= diff;
+ if (fmCheckTimer > diff) fmCheckTimer -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ DAMPENMAGIC = InitSpell(me, DAMPENMAGIC_1);
+ ARCANEINTELLECT = InitSpell(me, ARCANEINTELLECT_1);
+ ARCANEMISSILES = InitSpell(me, ARCANEMISSILES_1);
+ POLYMORPH = InitSpell(me, POLYMORPH_1);
+ COUNTERSPELL = InitSpell(me, COUNTERSPELL_1);
+ SPELLSTEAL = InitSpell(me, SPELLSTEAL_1);
+ EVOCATION = InitSpell(me, EVOCATION_1);
+ BLINK = InitSpell(me, BLINK_1);
+ REMOVE_CURSE = InitSpell(me, REMOVE_CURSE_1);
+ //INVISIBILITY = InitSpell(me, INVISIBILITY_1);
+ FIREBALL = InitSpell(me, FIREBALL_1);
+ BLASTWAVE = lvl >= 30 ? InitSpell(me, BLASTWAVE_1) : 0;
+ DRAGONBREATH = lvl >= 40 ? InitSpell(me, DRAGONBREATH_1) : 0;
+ FIREBLAST = InitSpell(me, FIREBLAST_1);
+ PYROBLAST = lvl >= 20 ? InitSpell(me, PYROBLAST_1) : 0;
+ LIVINGBOMB = lvl >= 60 ? InitSpell(me, LIVINGBOMB_1) : 0;
+ FLAMESTRIKE = InitSpell(me, DAMPENMAGIC_1);
+ COMBUSTION = lvl >= 50 ? COMBUSTION_1 : 0;
+ FROSTBOLT = InitSpell(me, FROSTBOLT_1);
+ FROSTNOVA = InitSpell(me, FROSTNOVA_1);
+ CONEOFCOLD = InitSpell(me, CONEOFCOLD_1);
+ BLIZZARD = InitSpell(me, BLIZZARD_1);
+ ICEARMOR = lvl >= 20 ? InitSpell(me, ICEARMOR_1) : InitSpell(me, FROSTARMOR_1);
+ ICE_BARRIER = lvl >= 40 ? InitSpell(me, ICE_BARRIER_1) : 0;
+ ICEBLOCK = InitSpell(me, ICEBLOCK_1);
+ FOCUSMAGIC = lvl >= 20 ? FOCUSMAGIC_1 : 0;
+ }
+
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+ //Dam+(-Hit)
+ if (level >= 50)
+ RefreshAura(ARCTIC_WINDS,3); //+15%/-15%
+ else if (level >= 25)
+ RefreshAura(ARCTIC_WINDS,2); //+10%/-10%
+ else if (level >= 10)
+ RefreshAura(ARCTIC_WINDS); //+5%/-5%
+ //CHILL
+ if (level >= 30)
+ RefreshAura(WINTERS_CHILL3); //100%
+ else if (level >= 25)
+ RefreshAura(WINTERS_CHILL2); //66%
+ else if (level >= 20)
+ RefreshAura(WINTERS_CHILL1); //33%
+ //Imp Blizzard
+ if (level >= 20)
+ RefreshAura(IMPROVED_BLIZZARD); //50% slow
+ //Frostbite
+ if (level >= 80)
+ FROSTBITE = FROSTBITE3;
+ else if (level >= 50)
+ FROSTBITE = FROSTBITE2;
+ else if (level >= 10)
+ FROSTBITE = FROSTBITE1;
+ if (level >= 60)
+ RefreshAura(FROSTBITE,3);//3x
+ else if (level >= 30)
+ RefreshAura(FROSTBITE,2);//2x
+ else if (level >= 10)
+ RefreshAura(FROSTBITE);//1x
+ //Shattered Barrier
+ if (level >= 45)
+ RefreshAura(SHATTERED_BARRIER);
+ //Bonus
+ if (level >= 65)
+ RefreshAura(ARCANE_INSTABILITY,4); //+12%dmg crit
+ else if (level >= 55)
+ RefreshAura(ARCANE_INSTABILITY,3); //+9%dmg crit
+ else if (level >= 45)
+ RefreshAura(ARCANE_INSTABILITY,2); //+6%dmg crit
+ else if (level >= 35)
+ RefreshAura(ARCANE_INSTABILITY); //+3%dmg crit
+ //Absorb
+ if (level >= 50)
+ RefreshAura(INCANTERS_ABSORPTION3);
+ else if (level >= 45)
+ RefreshAura(INCANTERS_ABSORPTION2);
+ else if (level >= 40)
+ RefreshAura(INCANTERS_ABSORPTION1);
+ //Shatter
+ if (level >= 35)
+ RefreshAura(SHATTER3);
+ else if (level >= 30)
+ RefreshAura(SHATTER2);
+ else if (level >= 25)
+ RefreshAura(SHATTER1);
+ //ClearCasting
+ if (level >= 75)
+ RefreshAura(CLEARCAST,3);//30%
+ else if (level >= 40)
+ RefreshAura(CLEARCAST,2);//20%
+ else if (level >= 15)
+ RefreshAura(CLEARCAST);//10%
+ //Fingers
+ if (level >= 45)
+ RefreshAura(FINGERS_OF_FROST);//15%
+ //Potency
+ if (level >= 40)
+ RefreshAura(ARCANE_POTENCY2);//30% bonus
+ else if (level >= 35)
+ RefreshAura(ARCANE_POTENCY1);//15% bonus
+ //Ignite
+ if (level >= 15)
+ RefreshAura(IGNITE);
+ //Impact
+ if (level >= 60)
+ RefreshAura(IMPACT,2);
+ else if (level >= 20)
+ RefreshAura(IMPACT);
+ //Imp. Counterspell
+ if (level >= 35)
+ RefreshAura(IMPROVED_COUNTERSPELL2);//4 sec
+ else if (level >= 25)
+ RefreshAura(IMPROVED_COUNTERSPELL1);//2 sec
+ //Firestarter
+ if (level >= 55)
+ RefreshAura(FIRESTARTER2);//100% chance
+ else if (level >= 45)
+ RefreshAura(FIRESTARTER1);//50% chance
+ //Spells
+ if (LIVINGBOMB != 0)
+ RefreshAura(GLYPH_LIVING_BOMB);
+ if (POLYMORPH != 0)
+ RefreshAura(GLYPH_POLYMORPH);
+ }
+
+ private:
+ uint32
+ /*Arcane*/DAMPENMAGIC, ARCANEINTELLECT, ARCANEMISSILES, POLYMORPH, COUNTERSPELL, FOCUSMAGIC,
+ /*Arcane*/SPELLSTEAL, EVOCATION, BLINK, REMOVE_CURSE, /*INVISIBILITY,*/
+ /*Fire*/FIREBALL, FIREBLAST, FLAMESTRIKE, PYROBLAST, COMBUSTION, BLASTWAVE, DRAGONBREATH, LIVINGBOMB,
+ /*Frost*/FROSTBOLT, FROSTNOVA, CONEOFCOLD, BLIZZARD, ICEARMOR, ICEBLOCK, ICE_BARRIER, FROSTBITE;
+ //Timers
+/*fire*/uint32 Pyroblast_cd, FireBlast_cd, DragonBreath_cd, Living_Bomb_cd, Combustion_cd;
+/*frst*/uint32 Ice_Barrier_cd, ConeofCold_cd, Blizzard_cd, Iceblock_cd;
+/*arcn*/uint32 CounterSpell_cd, Blink_cd, Evocation_cd;
+/*exc.*/uint32 Bolt_cd, Nova_cd;
+/*exc.*/uint32 BOLT, NOVA;
+/*exc.*/uint32 polyCheckTimer, fmCheckTimer;
+ //Check
+/*exc.*/bool Polymorph, clearcast;
+
+ enum MageBaseSpells// all orignals
+ {
+ 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
+ {
+ 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,
+ 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..9bc0ffa
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp
@@ -0,0 +1,1014 @@
+#include "bot_ai.h"
+#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuraEffects.h"
+#include "WorldSession.h"
+/*
+Complete - Around 45-50%
+TODO: Repentance Work Improve, 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);
+ }
+
+ 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;
+ }
+
+ struct paladin_botAI : public bot_minion_ai
+ {
+ paladin_botAI(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_PALADIN) != SPELL_CAST_OK)
+ return false;
+ return bot_ai::doCast(victim, spellId, triggered);
+ }
+
+ void HOFGroup(Player* pTarget, uint32 diff)
+ {
+ if (!HOF || HOF_Timer > diff || GC_Timer > diff || Rand() > 60) return;
+ if (IsCasting()) return;//I'm busy
+
+ 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;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = tPlayer->GetBotMap(i)->_Cre();
+ if (!cre || !cre->IsInWorld()) continue;
+ if (HOFTarget(cre, diff))
+ return;
+ }
+ }
+ }
+ }
+
+ bool HOFTarget(Unit* target, uint32 diff)
+ {
+ if (!HOF || HOF_Timer > diff || GC_Timer > diff) return false;
+ if (!target || target->isDead()) return false;
+ if (target->ToCreature() && Rand() > 25) return false;
+ if (me->GetExactDist(target) > 30) return false;//too far away
+ if (HasAuraName(target, HOF)) 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 (me->getLevel() >= 40 && (spellInfo->GetAllEffectsMechanicMask() & (1<<MECHANIC_STUN)))
+ {
+ if (doCast(target, HOF))
+ {
+ if (target->ToCreature())
+ HOF_Timer = 10000;//10 sec for selfcast after stun
+ else
+ HOF_Timer = 15000;//improved
+ HOFGuid = target->GetGUID();
+ return true;
+ }
+ }
+ /*else */if (spellInfo->GetAllEffectsMechanicMask() & (1<<MECHANIC_SNARE) ||
+ spellInfo->GetAllEffectsMechanicMask() & (1<<MECHANIC_ROOT))
+ {
+ uint32 spell = (spellInfo->Dispel == DISPEL_MAGIC || spellInfo->Dispel == DISPEL_DISEASE || spellInfo->Dispel == DISPEL_POISON) && CLEANSE ? CLEANSE : HOF;
+ if (doCast(target, spell))
+ {
+ if (spell == HOF)
+ {
+ if (target->ToCreature())
+ HOF_Timer = 5000;//5 sec for bots
+ else
+ HOF_Timer = 15000;//improved
+ if (me->getLevel() >= 40)
+ HOFGuid = target->GetGUID();
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void HOSGroup(Player* hTarget, uint32 diff)
+ {
+ if (!HOS || HOS_Timer > diff || GC_Timer > diff || Rand() > 30) return;
+ if (IsCasting()) 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 (tank && HOSPlayer == tank) continue;//tanks do not need it
+ if (master->GetMap() != HOSPlayer->FindMap() || !HOSPlayer->IsInWorld() || me->GetExactDist(HOSPlayer) > 30) continue;
+ if (HasAuraName(HOSPlayer, HOS)) 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)->getThreatManager().getThreat(tank) < threat * 0.33f) 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;
+ for (uint8 i = 0; i != pl->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = pl->GetBotMap(i)->_Cre();
+ if (!cre || cre->isDead()) continue;
+ if (tank && cre == tank) continue;
+ if (me->GetExactDist(cre) > 30) continue;
+ if (HasAuraName(cre, HOS)) 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)->getThreatManager().getThreat(tank) < threat * 0.33f) 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 (!HOS || HOS_Timer > diff || GC_Timer > diff || Rand() > 50) return false;
+ if (tank && target == tank) 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)) 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)->getThreatManager().getThreat(tank) < threat * 0.33f) continue;//would be useless
+ if (target->GetDistance((*iter)) <= 10)
+ Tattackers++;
+ }
+ if (Tattackers > 0 && doCast(target, HOS))
+ {
+ 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
+ HOS_Timer = 25000 - 20000*IS_CREATURE_GUID(target->GetGUID());
+ return true;
+ }
+ return false;
+ }
+ //Holy_Shock setup (Modify HERE)
+ bool HS(Unit* target, uint32 diff)
+ {
+ if (!target || target->isDead()) return false;
+ if (!HOLY_SHOCK || HS_Timer > diff || GC_Timer > 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, HOLY_SHOCK))
+ {
+ HS_Timer = target->ToCreature() ? 3500 : 5000;
+ return true;
+ }
+ return false;
+ }
+
+ bool HealTarget(Unit* target, uint8 hp, uint32 diff)
+ {
+ 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 (HAND_OF_PROTECTION && BOP_Timer <= diff && IS_PLAYER_GUID(target->GetGUID()) &&
+ (master->GetGroup() && master->GetGroup()->IsMember(target->GetGUID()) || target == master) &&
+ ((hp < 30 && !target->getAttackers().empty()) || (hp < 50 && target->getAttackers().size() > 3)) &&
+ me->GetExactDist(target) < 30 &&
+ !HasAuraName(target, HAND_OF_PROTECTION) &&
+ !HasAuraName(target, "Forbearance"))
+ {
+ if (doCast(target, HAND_OF_PROTECTION))
+ {
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ me->MonsterWhisper("BOP on you!", target->GetGUID());
+ BOP_Timer = 60000; //1 min
+ if (!HasAuraName(target, "Forbearance"))
+ me->AddAura(25771, target);//Forbearance
+ if (HasAuraName(target, "Forbearance") && !target->HasAura(HAND_OF_PROTECTION))
+ me->AddAura(HAND_OF_PROTECTION, target);
+ }
+ return true;
+ }
+ else if (hp < 20 && !HasAuraName(target, HAND_OF_PROTECTION))
+ {
+ // 20% to cast loh, else just do a Shock
+ switch (rand()%3)
+ {
+ case 1:
+ if (LAY_ON_HANDS && LOH_Timer <= diff && hp < 20 &&
+ target->GetTypeId() == TYPEID_PLAYER &&
+ (target->isInCombat() || !target->getAttackers().empty()) &&
+ !HasAuraName(target, "Forbearance"))
+ {
+ if (doCast(target, LAY_ON_HANDS))
+ {
+ me->MonsterWhisper("Lay of Hands on you!", target->GetGUID());
+ LOH_Timer = 60000; //1 min
+ return true;
+ }
+ }
+ case 2:
+ if (GC_Timer > diff) return false;
+ if (FLASH_OF_LIGHT && doCast(target, FLASH_OF_LIGHT))
+ return true;
+ case 3:
+ if (GC_Timer > diff) return false;
+ if (HOLY_SHOCK && HS_Timer <= diff && HS(target, diff))
+ return true;
+ }
+ }
+ if (GC_Timer > diff) return false;
+ Unit* u = target->getVictim();
+ if (SACRED_SHIELD && SSH_Timer <= diff && target->GetTypeId() == TYPEID_PLAYER &&
+ (hp < 65 || target->getAttackers().size() > 1 || (u && u->GetMaxHealth() > target->GetMaxHealth()*10 && target->isInCombat())) &&
+ !target->HasAura(SACRED_SHIELD) &&
+ ((master->GetGroup() && master->GetGroup()->IsMember(target->GetGUID())) || target == master))
+ {
+ Unit* aff = FindAffectedTarget(SACRED_SHIELD, me->GetGUID(), 50, 1);//use players since we cast only on them
+ if ((!aff || (aff->getAttackers().empty() && tank != aff)) &&
+ doCast(target, SACRED_SHIELD))
+ {
+ SSH_Timer = 3000;
+ return true;
+ }
+ }
+ if (HOLY_SHOCK && (hp < 85 || GetLostHP(target) > 6000) && HS_Timer <= diff)
+ if (HS(target, diff))
+ return true;
+ if ((hp > 35 && (hp < 75 || GetLostHP(target) > 8000)) || (!FLASH_OF_LIGHT && hp < 85))
+ if (doCast(target, HOLY_LIGHT))
+ return true;
+ if (FLASH_OF_LIGHT && (hp < 90 || GetLostHP(target) > 1500))
+ if (doCast(target, FLASH_OF_LIGHT))
+ return true;
+ return false;
+ }//end HealTarget
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == COMMAND_ATTACK && !force) return;
+ Aggro(u);
+ SetBotCommandState(COMMAND_ATTACK);
+ GetInPosition(force, false);
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { master->SetNpcBotDied(me->GetGUID()); }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (HOFGuid != 0)
+ {
+ if (Unit* ally = sObjectAccessor->FindUnit(HOFGuid))
+ if (Aura* hof = ally->GetAura(HOF, me->GetGUID()))
+ hof->SetDuration(hof->GetDuration() + 4000);//Guardian's Favor part 2 (handled separately)
+ HOFGuid = 0;
+ }
+ if (IAmDead()) return;
+ if (me->getVictim())
+ DoMeleeAttackIfReady();
+ else
+ Evade();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ BreakCC(diff);
+ //HOFTarget(me, diff);//self stun cure goes FIRST
+ if (CCed(me)) return;
+
+ if (GetManaPCT(me) < 30 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, MANAPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ if (GetManaPCT(me) < 40 && DIVINE_PLEA && Divine_Plea_Timer <= diff)
+ if (doCast(me, DIVINE_PLEA))
+ Divine_Plea_Timer = 45000;
+
+ CureTarget(me, CLEANSE, diff);//maybe unnecessary but this goes FIRST
+ HOFTarget(master, diff);//maybe unnecessary
+ CureTarget(master, CLEANSE, diff);//maybe unnecessary
+ BuffAndHealGroup(master, diff);
+ HOSTarget(master, diff);
+ CureGroup(master, CLEANSE, diff);
+ HOFGroup(master, diff);
+ HOSGroup(master, diff);
+
+ if (GetHealthPCT(me) < 50 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, HEALINGPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+ //buff
+ if (SEAL_OF_COMMAND && GC_Timer <= diff && !me->HasAura(SEAL_OF_COMMAND) &&
+ doCast(me, SEAL_OF_COMMAND))
+ GC_Timer = 500;
+
+ // Heal myself
+ if (GetHealthPCT(me) < 80)
+ HealTarget(me, GetHealthPCT(me), diff);
+
+ if (!CheckAttackTarget(CLASS_PALADIN))
+ return;
+
+ Repentance(diff);
+ //Counter(diff);
+ DoNormalAttack(diff);
+ }
+
+ void DoNonCombatActions(uint32 diff)
+ {
+ if (GC_Timer > diff || me->IsMounted()) return;
+
+ RezGroup(REDEMPTION, master);
+
+ if (Feasting()) return;
+
+ //aura
+ if (master->isAlive() && me->GetExactDist(master) < 20)
+ {
+ uint8 myAura;
+ 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() == CLASS_MAGE ||
+ master->getClass() == CLASS_PRIEST ||
+ master->getClass() == CLASS_WARLOCK ||
+ master->getClass() == 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", me->GetGUID()) ||
+ HasAuraName(target, "Blessing of Might", me->GetGUID()) ||
+ HasAuraName(target, "Blessing of Kings", me->GetGUID()) ||
+ HasAuraName(target, "Blessing of Sanctuary", 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;
+ bool wisdom = HasAuraName(target, BLESSING_OF_WISDOM) || HasAuraName(target, "Greater Blessing of Wisdom");
+ bool kings = HasAuraName(target, BLESSING_OF_KINGS) || HasAuraName(target, "Greater Blessing of Kings");
+ bool sanctuary = HasAuraName(target, BLESSING_OF_SANCTUARY) || HasAuraName(target, "Greater Blessing of Sanctuary");
+ bool might = (HasAuraName(target, BLESSING_OF_MIGHT) || HasAuraName(target, "Greater Blessing of Might") || HasAuraName(target, "Battle Shout"));
+
+ uint8 Class = 0;
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ Class = target->ToPlayer()->getClass();
+ else if (target->ToCreature())
+ Class = target->ToCreature()->GetBotClass();
+ switch (Class)
+ {
+ case 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;
+ break;
+ case CLASS_DEATH_KNIGHT:
+ case CLASS_WARRIOR:
+ case CLASS_PALADIN:
+ case CLASS_ROGUE:
+ case CLASS_HUNTER:
+ case CLASS_SHAMAN:
+ if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS))
+ return true;
+ else if (!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 (!might && doCast(target, BLESSING_OF_MIGHT))
+ return true;
+ break;
+ }
+ return false;
+ }
+
+ void Repentance(uint32 diff, Unit* target = NULL)
+ {
+ if (target && Repentance_Timer < 25000 && doCast(target, REPENTANCE))
+ {
+ temptimer = GC_Timer;
+ Repentance_Timer = 45000;
+ GC_Timer = temptimer;
+ return;
+ }
+ if (REPENTANCE && Repentance_Timer <= diff)
+ {
+ Unit* u = FindRepentanceTarget();
+ if (u && u->getVictim() != me && doCast(u, REPENTANCE))
+ Repentance_Timer = 45000;
+ }
+ }
+
+ void Counter(uint32 diff)
+ {
+ if (Rand() > 60 || IsCasting()) return;
+ Unit* target = Repentance_Timer < 25000 ? FindCastingTarget(20, false, REPENTANCE) : NULL;
+ if (target)
+ Repentance(diff, target);//first check repentance
+ else if (TURN_EVIL && Turn_Evil_Timer < 1500)
+ {
+ target = FindCastingTarget(20, false, TURN_EVIL);
+ temptimer = GC_Timer;
+ if (target && doCast(target, TURN_EVIL, true))
+ Turn_Evil_Timer = 3000;
+ GC_Timer = temptimer;
+ }
+ else if (HOLY_WRATH && Holy_Wrath_Timer < 8000)
+ {
+ target = FindCastingTarget(8, false, TURN_EVIL);//here we check target as with turn evil cuz of same requirements
+ temptimer = GC_Timer;
+ if (target && doCast(me, HOLY_WRATH))
+ Holy_Wrath_Timer = 23000 - me->getLevel() * 100; //23 - 0...8 sec (15 sec on 80 as with glyph)
+ GC_Timer = temptimer;
+ }
+ else if (HAMMER_OF_JUSTICE && HOJ_Timer <= 7000/* && GC_Timer <= diff*/)
+ {
+ target = FindCastingTarget(10);
+ if (target && doCast(opponent, HAMMER_OF_JUSTICE))
+ HOJ_Timer = 65000 - master->getLevel()*500; //25 sec on 80
+ }
+ }
+
+ void TurnEvil(uint32 diff)
+ {
+ if (!TURN_EVIL || Turn_Evil_Timer > diff || GC_Timer > diff || Rand() > 50 ||
+ FindAffectedTarget(TURN_EVIL, me->GetGUID(), 50))
+ return;
+ Unit* target = FindUndeadCCTarget(20, TURN_EVIL);
+ if (target &&
+ (target != me->getVictim() || GetHealthPCT(me) < 70 || target->getVictim() == master) &&
+ doCast(target, TURN_EVIL, true))
+ {
+ Turn_Evil_Timer = 3000;
+ return;
+ }
+ else
+ if ((opponent->GetCreatureType() == CREATURE_TYPE_UNDEAD || opponent->GetCreatureType() == CREATURE_TYPE_DEMON) &&
+ !CCed(opponent) &&
+ opponent->getVictim() && tank && opponent->getVictim() != tank && opponent->getVictim() != me &&
+ GetHealthPCT(me) < 90 &&
+ doCast(opponent, TURN_EVIL, true))
+ Turn_Evil_Timer = 3000;
+ }
+
+ void Wrath(uint32 diff)
+ {
+ if (!HOLY_WRATH || Holy_Wrath_Timer > diff || GC_Timer > diff || Rand() > 50)
+ return;
+ if ((opponent->GetCreatureType() == CREATURE_TYPE_UNDEAD || opponent->GetCreatureType() == CREATURE_TYPE_DEMON) &&
+ me->GetExactDist(opponent) <= 8 && doCast(me, HOLY_WRATH))
+ Holy_Wrath_Timer = 23000 - me->getLevel() * 100; //23 - 0...8 sec (15 sec on 80 as with glyph)
+ else
+ {
+ Unit* target = FindUndeadCCTarget(8, HOLY_WRATH);
+ if (target && doCast(me, HOLY_WRATH))
+ Holy_Wrath_Timer = 23000 - me->getLevel() * 100; //23 - 0...8 sec (15 sec on 80 as with glyph)
+ }
+ }
+
+ 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;
+ //{ wait = 5; return; }
+
+ if (HOW && HOW_Timer <= diff && GC_Timer <= diff && Rand() < 50 && GetHealthPCT(opponent) < 20 &&
+ me->GetExactDist(opponent) < 30)
+ if (doCast(opponent, HOW))
+ HOW_Timer = 6000; //6 sec
+
+ Unit* u = opponent->getVictim();
+ if (Rand() < 50 && HANDOFRECKONING && Hand_Of_Reckoning_Timer <= diff && me->GetExactDist(opponent) < 30 &&
+ u && u != me && u != tank && (IsInBotParty(u) || tank == me))//No GCD
+ {
+ Creature* cre = opponent->ToCreature();
+ temptimer = GC_Timer;
+ if (((cre && cre->isWorldBoss() && !isMeleeClass(u->getClass())) ||
+ GetHealthPCT(u) < GetHealthPCT(me) - 5 || tank == me) &&
+ doCast(opponent, HANDOFRECKONING))
+ Hand_Of_Reckoning_Timer = 8000 - (me == tank)*2000;
+ GC_Timer = temptimer;
+ }
+
+ if (Rand() < 20 && HAMMER_OF_JUSTICE && HOJ_Timer <= diff && GC_Timer <= diff &&
+ !CCed(opponent) && me->GetExactDist(opponent) < 10)
+ if (doCast(opponent, HAMMER_OF_JUSTICE))
+ HOJ_Timer = 65000 - master->getLevel()*500; //25 sec on 80
+
+ if (JUDGEMENT_OF_LIGHT && JOL_Timer <= diff && GC_Timer <= diff && Rand() < 50 &&
+ me->GetExactDist(opponent) < 10 && me->HasAura(SEAL_OF_COMMAND))
+ if (doCast(opponent, JUDGEMENT_OF_LIGHT))
+ JOL_Timer = 8000;
+
+ if (Rand() < 50 && CONSECRATION && Consecration_cd <= diff && GC_Timer <= diff &&
+ me->GetDistance(opponent) < 7 && !opponent->isMoving())
+ if (doCast(me, CONSECRATION))
+ Consecration_cd = 8000;
+
+ if (Rand() < 25 && AVENGING_WRATH && AW_Timer <= diff &&
+ (opponent->GetHealth() > master->GetMaxHealth()*2/3))
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, AVENGING_WRATH))
+ AW_Timer = 60000; //1 min
+ GC_Timer = temptimer;
+ }
+
+ if (CRUSADER_STRIKE && Crusader_cd <= diff && GC_Timer <= diff && me->GetDistance(opponent) < 5)
+ if (doCast(opponent, CRUSADER_STRIKE))
+ Crusader_cd = 12000 - me->getLevel() * 100;//4 sec on 80
+
+ if (EXORCISM && Exorcism_Timer <= diff && GC_Timer <= diff && me->GetExactDist(opponent) < 30 &&
+ (tank != me || opponent->getVictim() == me || opponent->IsVehicle() || opponent->ToPlayer()))
+ if (doCast(opponent, EXORCISM/*, true)*/))//possible instacast here
+ Exorcism_Timer = 15000;
+
+ Wrath(diff);
+
+ if (DIVINE_STORM && DS_Timer <= diff && GC_Timer <= diff && me->GetExactDist(opponent) < 7)
+ if (doCast(opponent, DIVINE_STORM))
+ DS_Timer = 10000 - me->getLevel()/4 * 100; //10 - 2 sec
+ }
+
+ 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->Category == 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 == EXORCISM)
+ pctbonus += 0.15f;
+ //The Art of War (damage part): 10% bonus damage for Judgements, Crusader Strike and Divine Storm
+ if (lvl >= 40 &&
+ (spellInfo->Category == SPELLCATEGORY_JUDGEMENT ||
+ spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_JUDGEMENT ||
+ spellId == CRUSADER_STRIKE ||
+ spellId == DIVINE_STORM))
+ pctbonus += 0.1f;
+
+ 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 == HOW)
+ aftercrit += 50.f;
+
+ if (aftercrit > 0.f)
+ crit = roll_chance_f(aftercrit);
+ }
+
+ //2) apply bonus damage mods
+ float pctbonus = 0.0f;
+ //if (crit)
+ //{
+ //}
+
+ damage = int32(fdamage * (1.0f + pctbonus));
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Crusader_cd = 0;
+ Consecration_cd = 0;
+ LOH_Timer = 0;
+ HOJ_Timer = 0;
+ HOF_Timer = 0;
+ JOL_Timer = 0;
+ HS_Timer = 0;
+ BOP_Timer = 0;
+ HOW_Timer = 0;
+ DS_Timer = 0;
+ AW_Timer = 10000;
+ HOS_Timer = 0;
+ SSH_Timer = 0;
+ Hand_Of_Reckoning_Timer = 0;
+ Divine_Plea_Timer = 0;
+ Repentance_Timer = 0;
+ Exorcism_Timer = 0;
+ Holy_Wrath_Timer = 0;
+ Turn_Evil_Timer = 0;
+
+ HOFGuid = 0;
+
+ if (master)
+ {
+ setStats(CLASS_PALADIN, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_PALADIN);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (HOW_Timer > diff) HOW_Timer -= diff;
+ if (DS_Timer > diff) DS_Timer -= diff;
+ if (AW_Timer > diff) AW_Timer -= diff;
+ if (HOS_Timer > diff) HOS_Timer -= diff;
+ if (HS_Timer > diff) HS_Timer -= diff;
+ if (BOP_Timer > diff) BOP_Timer -= diff;
+ if (Consecration_cd > diff) Consecration_cd -= diff;
+ if (Crusader_cd > diff) Crusader_cd -= diff;
+ if (LOH_Timer > diff) LOH_Timer -= diff;
+ if (HOJ_Timer > diff) HOJ_Timer -= diff;
+ if (HOF_Timer > diff) HOF_Timer -= diff;
+ if (JOL_Timer > diff) JOL_Timer -= diff;
+ if (SSH_Timer > diff) SSH_Timer -= diff;
+ if (Hand_Of_Reckoning_Timer > diff) Hand_Of_Reckoning_Timer -= diff;
+ if (Divine_Plea_Timer > diff) Divine_Plea_Timer -= diff;
+ if (Repentance_Timer > diff) Repentance_Timer -= diff;
+ if (Exorcism_Timer > diff) Exorcism_Timer -= diff;
+ if (Holy_Wrath_Timer > diff) Holy_Wrath_Timer -= diff;
+ if (Turn_Evil_Timer > diff) Turn_Evil_Timer -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ FLASH_OF_LIGHT = InitSpell(me, FLASH_OF_LIGHT_1);
+ HOLY_LIGHT = InitSpell(me, HOLY_LIGHT_1);
+ LAY_ON_HANDS = InitSpell(me, LAY_ON_HANDS_1);
+ SACRED_SHIELD = InitSpell(me, SACRED_SHIELD_1);
+ HOLY_SHOCK = lvl >= 40 ? InitSpell(me, HOLY_SHOCK_1) : 0;
+ CLEANSE = InitSpell(me, CLEANSE_1);
+ REDEMPTION = InitSpell(me, REDEMPTION_1);
+ HAMMER_OF_JUSTICE = InitSpell(me, HAMMER_OF_JUSTICE_1);
+ REPENTANCE = lvl >= 45 ? REPENTANCE_1 : 0;
+ TURN_EVIL = InitSpell(me, TURN_EVIL_1);
+ HOLY_WRATH = InitSpell(me, HOLY_WRATH_1);
+ EXORCISM = InitSpell(me, EXORCISM_1);
+ SEAL_OF_COMMAND = lvl >= 25 ? SEAL_OF_COMMAND_1 : 0;
+ CRUSADER_STRIKE = lvl >= 20 ? CRUSADER_STRIKE_1 : 0;//exception
+ JUDGEMENT_OF_LIGHT = InitSpell(me, JUDGEMENT_OF_LIGHT_1);
+ CONSECRATION = InitSpell(me, CONSECRATION_1);
+ DIVINE_STORM = lvl >= 60 ? DIVINE_STORM_1 : 0;
+ HOW /*Hammer of Wrath*/ = InitSpell(me, HOW_1);
+ AVENGING_WRATH = InitSpell(me, AVENGING_WRATH_1);
+ BLESSING_OF_MIGHT = InitSpell(me, BLESSING_OF_MIGHT_1);
+ BLESSING_OF_WISDOM = InitSpell(me, BLESSING_OF_WISDOM_1);
+ BLESSING_OF_KINGS = InitSpell(me, BLESSING_OF_KINGS_1);
+ BLESSING_OF_SANCTUARY = lvl >= 30 ? BLESSING_OF_SANCTUARY_1 : 0;
+ DEVOTION_AURA = InitSpell(me, DEVOTION_AURA_1);
+ CONCENTRATION_AURA = InitSpell(me, CONCENTRATION_AURA_1);
+ DIVINE_PLEA = InitSpell(me, DIVINE_PLEA_1);
+ HAND_OF_PROTECTION = InitSpell(me, HAND_OF_PROTECTION_1);
+ HOF/*Hand of Freedom*/ = InitSpell(me, HOF_1);
+ HOS /*Hand of salvation*/ = InitSpell(me, HOS_1);
+ HANDOFRECKONING = InitSpell(me, HANDOFRECKONING_1);
+ }
+
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+ //1 - SPD 3% crit 3%
+ if (level >= 78)
+ RefreshAura(SPELLDMG,5); //+15%
+ else if (level >= 75)
+ RefreshAura(SPELLDMG,4); //+12%
+ else if (level >= 55)
+ RefreshAura(SPELLDMG,3); //+9%
+ else if (level >= 35)
+ RefreshAura(SPELLDMG,2); //+6%
+ else if (level >= 15)
+ RefreshAura(SPELLDMG); //+3%
+ //2 - SPD 6%
+ if (level >= 55)
+ RefreshAura(SPELLDMG2,3); //+18%
+ else if (level >= 35)
+ RefreshAura(SPELLDMG2,2); //+12%
+ else if (level >= 15)
+ RefreshAura(SPELLDMG2); //+6%
+ //Talents
+ if (level >= 55)
+ RefreshAura(PURE);
+ if (level >= 35)
+ RefreshAura(WISE);
+ if (level >= 50)
+ RefreshAura(RECKONING5); //10%
+ else if (level >= 45)
+ RefreshAura(RECKONING4); //8%
+ else if (level >= 40)
+ RefreshAura(RECKONING3); //6%
+ else if (level >= 35)
+ RefreshAura(RECKONING2); //4%
+ else if (level >= 30)
+ RefreshAura(RECKONING1); //2%
+ //if (level >= 50)
+ // RefreshAura(RIGHTEOUS_VENGEANCE3);
+ //else if (level >= 47)
+ // RefreshAura(RIGHTEOUS_VENGEANCE2);
+ //else if (level >= 45)
+ // RefreshAura(RIGHTEOUS_VENGEANCE1);
+ if (level >= 30)
+ RefreshAura(VENGEANCE3);
+ else if (level >= 27)
+ RefreshAura(VENGEANCE2);
+ else if (level >= 25)
+ RefreshAura(VENGEANCE1);
+ if (level >= 60)
+ RefreshAura(SHOFL3);
+ else if (level >= 55)
+ RefreshAura(SHOFL2);
+ else if (level >= 50)
+ RefreshAura(SHOFL1);
+ if (level >= 45)
+ RefreshAura(SACRED_CLEANSING);
+ if (level >= 35)
+ RefreshAura(DIVINE_PURPOSE);
+ if (level >= 25)
+ RefreshAura(VINDICATION2);
+ else if (level >= 20)
+ RefreshAura(VINDICATION1);
+ if (level >= 30)
+ RefreshAura(LAYHANDS);
+ if (level >= 20)
+ RefreshAura(FANATICISM,2); //-60% aggro
+ if (level >= 15)
+ RefreshAura(GLYPH_HOLY_LIGHT); //10% heal
+ //if (level >= 70)
+ // RefreshAura(PALADIN_T9_2P_BONUS); //Righteous Vengeance Crits
+ }
+
+ private:
+ uint32
+ /*Heals*/FLASH_OF_LIGHT, HOLY_LIGHT, HOLY_SHOCK, LAY_ON_HANDS, SACRED_SHIELD,
+ /*CC*/HAMMER_OF_JUSTICE, REPENTANCE, TURN_EVIL,
+ /*Damage*/SEAL_OF_COMMAND, HOLY_WRATH, EXORCISM, CRUSADER_STRIKE, JUDGEMENT_OF_LIGHT,
+ /*Damage*/CONSECRATION, DIVINE_STORM, AVENGING_WRATH, HOW,//hammer of wrath
+/*Blessing*/BLESSING_OF_MIGHT, BLESSING_OF_WISDOM, BLESSING_OF_KINGS, BLESSING_OF_SANCTUARY,
+ /*Auras*/DEVOTION_AURA, CONCENTRATION_AURA,
+ /*Hands*/HAND_OF_PROTECTION, HOF, HOS, HANDOFRECKONING,
+ /*Misc*/CLEANSE, REDEMPTION, DIVINE_PLEA;
+ //Timers
+ uint32 Crusader_cd, Consecration_cd, Exorcism_Timer, Holy_Wrath_Timer, JOL_Timer, HOF_Timer,
+ HS_Timer, HOW_Timer, DS_Timer, HOS_Timer, SSH_Timer, Hand_Of_Reckoning_Timer, Turn_Evil_Timer,
+ LOH_Timer, HOJ_Timer, BOP_Timer, AW_Timer, Divine_Plea_Timer, Repentance_Timer;
+ uint64 HOFGuid;
+
+ 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_OF_LIGHT_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,
+ PURE/*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
+ //Glyphs
+ GLYPH_HOLY_LIGHT = 54937,
+ //other
+ SPELLDMG/*Arcane Instability-mage*/ = 15060,//rank3 3% dam/crit
+ SPELLDMG2/*Earth and Moon - druid*/ = 48511,//rank3 6% dam
+ //PALADIN_T9_2P_BONUS = 67188,//Righteous Vengeance Crits
+ };
+
+ enum PaladinSpecial
+ {
+ NOAURA,
+ DEVOTIONAURA,
+ CONCENTRATIONAURA,
+ };
+ };
+};
+
+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..f47aa65
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_priest_ai.cpp
@@ -0,0 +1,859 @@
+#include "bot_ai.h"
+#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+//#include "SpellAuras.h"
+#include "WorldSession.h"
+/*
+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);
+ }
+
+ 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;
+ }
+
+ struct priest_botAI : public bot_minion_ai
+ {
+ priest_botAI(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_PRIEST) != SPELL_CAST_OK)
+ return false;
+ return bot_ai::doCast(victim, spellId, triggered);
+ }
+
+ bool MassGroupHeal(Player* player, uint32 diff)
+ {
+ if (!PRAYER_OF_HEALING && !DIVINE_HYMN) return false;
+ if (!player->GetGroup()) return false;
+ if (Rand() > 30) return false;
+ if (IsCasting()) return false;
+
+ if (DIVINE_HYMN && Divine_Hymn_Timer <= diff)
+ {
+ Group* gr = player->GetGroup();
+ uint8 LHPcount = 0;
+ for (GroupReference* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* tPlayer = itr->getSource();
+ if (!tPlayer || me->GetMap() != tPlayer->FindMap() ||
+ !tPlayer->IsInWorld() || 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;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ if (bot && GetHealthPCT(bot) < 40 && me->GetExactDist(bot) < 30)
+ ++LHPcount;
+ if (LHPcount > 1)
+ break;
+ }
+ }
+ if (LHPcount > 1 && doCast(me, DIVINE_HYMN))
+ {
+ Divine_Hymn_Timer = 180000; //3 min
+ return true;
+ }
+ }
+ if (PRAYER_OF_HEALING)
+ {
+ 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 || me->GetMap() != tPlayer->GetMap() ||
+ !tPlayer->IsInWorld() || 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;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ 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, PRAYER_OF_HEALING))
+ return true;
+ }
+ return false;
+ }//end MassGroupHeal
+
+ bool ShieldTarget(Unit* target, uint32 diff)
+ {
+ if (PWS_Timer > diff || Rand() > 50 || IsCasting()) return false;
+ if (target->HasAura(WEAKENED_SOUL)) return false;
+ if (HasAuraName(target, PW_SHIELD)) return false;
+ //if (me->GetExactDist(target) > 40) return false;//checked already in HealTarget()
+
+ if (!target->getAttackers().empty() || GetHealthPCT(target) < 33 || target->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))
+ {
+ if (doCast(target, PW_SHIELD))
+ {
+ if (me->getLevel() >= 30 || // improved
+ (target->ToCreature() && target->ToCreature()->GetIAmABot()))
+ PWS_Timer = 0;
+ else
+ PWS_Timer = 4000;
+ GC_Timer = 800;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == COMMAND_ATTACK && !force) return;
+ Aggro(u);
+ GetInPosition(force, true);
+ SetBotCommandState(COMMAND_ATTACK);
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { master->SetNpcBotDied(me->GetGUID()); }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (IAmDead()) return;
+ if (!me->getVictim())
+ Evade();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ Disperse(diff);
+ BreakCC(diff);
+ if (CCed(me)) return;
+ DoDevCheck(diff);
+
+ if (GetManaPCT(me) < 33 && Potion_cd <= diff)
+ {
+ 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, DISPELMAGIC, diff);
+ CureGroup(master, CURE_DISEASE, diff);
+ //ShieldGroup(master);
+ if (master->isInCombat() || me->isInCombat())
+ {
+ CheckDispel(diff);
+ CheckSilence(diff);
+ }
+ if (me->isInCombat())
+ CheckShackles(diff);
+
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+
+ if (!CheckAttackTarget(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;
+ bool isBoss = opponent->GetTypeId() == TYPEID_UNIT ? opponent->ToCreature()->isWorldBoss() : false;
+ if (me->GetExactDist(opponent) < 30)
+ {
+ if (SW_DEATH && Rand() < 50 && SW_Death_Timer <= diff &&
+ (GetHealthPCT(opponent) < 15 || opponent->GetHealth() < me->GetMaxHealth()/6) &&
+ doCast(opponent, SW_DEATH))
+ {
+ SW_Death_Timer = 10000;
+ return;
+ }
+ if (Rand() < 30 && GC_Timer <= diff && !opponent->HasAura(SW_PAIN, me->GetGUID()) &&
+ opponent->GetHealth() > me->GetMaxHealth()/4 &&
+ doCast(opponent, SW_PAIN))
+ return;
+ if (VAMPIRIC_TOUCH && GC_Timer <= diff && !isBoss && Rand() < 50 && !opponent->HasAura(VAMPIRIC_TOUCH, me->GetGUID()) &&
+ opponent->GetHealth() > me->GetMaxHealth()/4 &&
+ doCast(opponent, VAMPIRIC_TOUCH))
+ return;
+ if (DEVOURING_PLAGUE && GC_Timer <= diff && !isBoss && Rand() < 30 && !Devcheck && !opponent->HasAura(DEVOURING_PLAGUE, me->GetGUID()) &&
+ opponent->GetHealth() > me->GetMaxHealth()/3 &&
+ doCast(opponent, DEVOURING_PLAGUE))
+ return;
+ if (Mind_Blast_Timer <= diff && GC_Timer <= 300 && !isBoss && Rand() < 50 && (!VAMPIRIC_TOUCH || HasAuraName(opponent, VAMPIRIC_TOUCH)) &&
+ doCast(opponent, MIND_BLAST))
+ {
+ Mind_Blast_Timer = 7500 - me->getLevel()/4*100;//5.5 sec on 80 lvl (as improved)
+ return;
+ }
+ if (MIND_FLAY && Mind_Flay_Timer <= diff && GC_Timer <= 300 && !isBoss && !me->isMoving() && Rand() < 40 && me->GetExactDist(opponent) < 30 &&
+
+ (opponent->isMoving() || opponent->GetHealth() < me->GetMaxHealth()/3 ||
+ (opponent->HasAura(SW_PAIN, me->GetGUID()) &&
+ opponent->HasAura(DEVOURING_PLAGUE, me->GetGUID()))) &&
+ doCast(opponent, MIND_FLAY))
+ {
+ Mind_Flay_Timer = 3000;
+ return;
+ }
+ if (MIND_SEAR && GC_Timer <= diff && !me->isMoving() && !opponent->isMoving() && Rand() < 50 &&
+ opponent->HasAura(SW_PAIN, me->GetGUID()) &&
+ opponent->HasAura(DEVOURING_PLAGUE, me->GetGUID()))
+ if (Unit* u = FindSplashTarget(30, opponent))
+ if (doCast(u, MIND_SEAR))
+ return;
+ }//endif opponent
+ }//endif damage
+ //check horror after dots/damage
+ if (PSYCHIC_HORROR && Psychic_Horror_Timer <= diff && Rand() < 30 &&
+ opponent->GetCreatureType() != CREATURE_TYPE_UNDEAD &&
+ opponent->GetHealth() > me->GetMaxHealth()/5 &&
+ me->GetExactDist(opponent) < 30 && !HasAuraName(opponent, PSYCHIC_HORROR) &&
+ !CCed(opponent))
+ {
+ if (doCast(opponent, PSYCHIC_HORROR))
+ {
+ Psychic_Horror_Timer = 60000;
+ return;
+ }
+ }
+ }//end UpdateAI
+
+ bool HealTarget(Unit* target, uint8 hp, uint32 diff)
+ {
+ if (hp > 98) return false;
+ if (!target || target->isDead() || me->GetExactDist(target) > 40)
+ return false;
+ if (Rand() > 50 + 20*target->isInCombat() + 50*master->GetMap()->IsRaid()) return false;
+
+ //GUARDIAN SPIRIT
+ if (GUARDIAN_SPIRIT && Guardian_Spirit_Timer <= diff && Rand() < 70 &&
+ target->isInCombat() && !target->getAttackers().empty() &&
+ hp < (5 + std::min(20, uint8(target->getAttackers().size())*5)) &&
+ (master->GetGroup() && master->GetGroup()->IsMember(target->GetGUID()) || target == master) &&
+ !target->HasAura(GUARDIAN_SPIRIT))
+ {
+ temptimer = GC_Timer;
+ if (me->IsNonMeleeSpellCasted(true))
+ me->InterruptNonMeleeSpells(true);
+ if (doCast(target, GUARDIAN_SPIRIT))
+ {
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (target->HasAura(GUARDIAN_SPIRIT, me->GetGUID()))
+ me->MonsterWhisper("Guardin Spirit on you!", target->GetGUID());
+ Guardian_Spirit_Timer = 90000;//1.5 min
+ }
+ else
+ Guardian_Spirit_Timer = 30000;//30 sec for creatures
+ GC_Timer = temptimer;
+ return true;
+ }
+ }
+
+ if (IsCasting()) return false;
+
+ //PAIN SUPPRESSION
+ if (hp < 35 && PAIN_SUPPRESSION && Pain_Suppression_Timer <= diff && Rand() < 50 &&
+ (target->isInCombat() || !target->getAttackers().empty()) &&
+ !target->HasAura(PAIN_SUPPRESSION))
+ {
+ temptimer = GC_Timer;
+ if (doCast(target, PAIN_SUPPRESSION))
+ {
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (target->HasAura(PAIN_SUPPRESSION, me->GetGUID()))
+ me->MonsterWhisper("Pain Suppression on you!", target->GetGUID());
+ Pain_Suppression_Timer = 45000;//45 sec
+ }
+ else
+ Pain_Suppression_Timer = 15000;//15 sec for creatures
+ GC_Timer = temptimer;
+ return true;
+ }
+ }
+
+ //Now Heals Requires GCD
+ if ((hp < 80 || !target->getAttackers().empty()) &&
+ PWS_Timer <= diff && ShieldTarget(target, diff))
+ return true;
+
+ //PENANCE/Greater Heal
+ if (hp < 75 || GetLostHP(target) > 4000)
+ {
+ if (PENANCE && Penance_Timer <= diff &&
+ !me->isMoving() && //better check then stop moving every try (furthermore it doesn't always work properly)
+ (target->GetTypeId() != TYPEID_PLAYER || !(target->ToPlayer()->isCharmed() || target->ToPlayer()->isPossessed())) &&
+ doCast(target, PENANCE))
+ {
+ Penance_Timer = 8000;
+ return true;
+ }
+ else if (Heal_Timer <= diff && GC_Timer <= diff && hp > 50 && doCast(target, HEAL))
+ {
+ Heal_Timer = 2500;
+ return true;
+ }
+ }
+ //Flash Heal
+ if (((hp > 75 && hp < 90) || hp < 50 || GetLostHP(target) > 1500) &&
+ GC_Timer <= diff && FLASH_HEAL &&
+ doCast(target, FLASH_HEAL))
+ return true;
+ //maintain HoTs
+ Unit* u = target->getVictim();
+ Creature* boss = u && u->ToCreature() && u->ToCreature()->isWorldBoss() ? u->ToCreature() : NULL;
+ bool tanking = tank == target && boss;
+ //Renew
+ if (((hp < 98 && hp > 70) || GetLostHP(target) > 500 || tanking) &&
+ !HasAuraName(target, RENEW, me->GetGUID()) &&
+ GC_Timer <= diff && doCast(target, RENEW))
+ {
+ GC_Timer = 800;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool BuffTarget(Unit* target, uint32 diff)
+ {
+ if (GC_Timer > diff || Rand() > 60) return false;
+ if (!target || target->isDead() || me->GetExactDist(target) > 30) return false;
+
+ if (Fear_Ward_Timer <= diff && !HasAuraName(target, FEAR_WARD) && doCast(target, FEAR_WARD))
+ {
+ Fear_Ward_Timer = target->GetTypeId() == TYPEID_PLAYER ? 60000 : 30000;//30sec for bots
+ GC_Timer = 800;
+ return true;
+ }
+
+ if (target == me)
+ {
+ if (!me->HasAura(INNER_FIRE) && doCast(me, INNER_FIRE))
+ {
+ GC_Timer = 800;
+ return true;
+ }
+ 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 (Rand() < 70 && !HasAuraName(target, PW_FORTITUDE) && doCast(target, PW_FORTITUDE))
+ {
+ /*GC_Timer = 800;*/
+ return true;
+ }
+ if (Rand() < 30 && !HasAuraName(target, SHADOW_PROTECTION) && doCast(target, SHADOW_PROTECTION))
+ {
+ /*GC_Timer = 800;*/
+ return true;
+ }
+ if (Rand() < 30 && !HasAuraName(target, DIVINE_SPIRIT) && doCast(target, DIVINE_SPIRIT))
+ {
+ /*GC_Timer = 800;*/
+ return true;
+ }
+ return false;
+ }
+
+ void DoNonCombatActions(uint32 diff)
+ {
+ if (Rand() > 50 || GC_Timer > diff || me->IsMounted()) return;
+
+ RezGroup(RESURRECTION, 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;
+ Unit* target = FindHostileDispelTarget();
+ if (target && doCast(target, DISPELMAGIC))
+ {
+ CheckDispelTimer = 3000;
+ GC_Timer = 500;
+ }
+ CheckDispelTimer = 1000;
+ }
+
+ void CheckShackles(uint32 diff)
+ {
+ if (!SHACKLE_UNDEAD || ShackleTimer > diff || GC_Timer > diff || IsCasting())
+ return;
+ if (FindAffectedTarget(SHACKLE_UNDEAD, me->GetGUID()))
+ return;
+ Unit* target = FindUndeadCCTarget(30, SHACKLE_UNDEAD);
+ if (target && doCast(target, SHACKLE_UNDEAD))
+ {
+ ShackleTimer = 3000;
+ GC_Timer = 800;
+ }
+ }
+
+ void CheckSilence(uint32 diff)
+ {
+ if (IsCasting()) return;
+ temptimer = GC_Timer;
+ if (SILENCE && Silence_Timer <= diff)
+ {
+ if (Unit* target = FindCastingTarget(30))
+ if (doCast(target, SILENCE))
+ Silence_Timer = 30000;
+ }
+ else if (PSYCHIC_HORROR && Psychic_Horror_Timer <= 20000)
+ {
+ if (Unit* target = FindCastingTarget(30))
+ if (doCast(target, PSYCHIC_HORROR))
+ Psychic_Horror_Timer = 60000;
+ }
+ GC_Timer = temptimer;
+ }
+
+ void doDefend(uint32 diff)
+ {
+ AttackerSet m_attackers = master->getAttackers();
+ AttackerSet b_attackers = me->getAttackers();
+ //fear master's attackers
+ if (!m_attackers.empty() && PSYCHIC_SCREAM && Fear_Timer <= diff &&
+ (master != tank || GetHealthPCT(master) < 75))
+ {
+ uint8 tCount = 0;
+ 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)) > 7) continue;
+ if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue;
+ if (me->IsValidAttackTarget(*iter))
+ ++tCount;
+ }
+ if (tCount > 1 && doCast(me, PSYCHIC_SCREAM))
+ {
+ Fear_Timer = 24000;//with improved 24 sec
+ return;
+ }
+ }
+
+ // Defend myself (psychic horror)
+ if (!b_attackers.empty() && PSYCHIC_SCREAM && Fear_Timer <= diff)
+ {
+ uint8 tCount = 0;
+ 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)) > 7) continue;
+ if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue;
+ if (me->IsValidAttackTarget(*iter))
+ ++tCount;
+ }
+ if (tCount > 0 && doCast(me, PSYCHIC_SCREAM))
+ {
+ Fear_Timer = 24000;//with improved 24 sec
+ return;
+ }
+ }
+ // Heal myself
+ if (GetHealthPCT(me) < 99 && !b_attackers.empty())
+ {
+ if (ShieldTarget(me, diff)) return;
+
+ if (FADE && Fade_Timer <= diff && me->isInCombat())
+ {
+ if (b_attackers.empty()) return; //no aggro
+ 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, FADE))
+ {
+ for (AttackerSet::iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter)
+ if ((*iter)->getThreatManager().getThreat(me) > 0.f)
+ (*iter)->getThreatManager().modifyThreatPercent(me, -50);
+ Fade_Timer = 6000;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ }
+ if (GetHealthPCT(me) < 90 && HealTarget(me, GetHealthPCT(me), diff))
+ return;
+ }
+ }
+
+ void DoDevCheck(uint32 diff)
+ {
+ if (DevcheckTimer <= diff)
+ {
+ Devcheck = FindAffectedTarget(DEVOURING_PLAGUE, me->GetGUID());
+ DevcheckTimer = 5000;
+ }
+ }
+
+ void Disperse(uint32 diff)
+ {
+ if (!DISPERSION || GC_Timer > diff || Dispersion_Timer > diff || IsCasting()) return;
+ //attackers case
+ if ((me->getAttackers().size() > 3 && Fade_Timer > diff && GetHealthPCT(me) < 90) ||
+ (GetHealthPCT(me) < 20 && me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) ||
+ (GetManaPCT(me) < 30) ||
+ (me->getAttackers().size() > 1 && me->HasAuraWithMechanic((1<<MECHANIC_SNARE)|(1<<MECHANIC_ROOT))))
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, DISPERSION))
+ Dispersion_Timer = 75000;//with glyph
+ GC_Timer = temptimer;
+ return;
+ }
+ Dispersion_Timer = 2000;//fail
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Heal_Timer = 0;
+ Divine_Hymn_Timer = 0;
+ Pain_Suppression_Timer = 0;
+ Guardian_Spirit_Timer = 0;
+ PWS_Timer = 0;
+ Fade_Timer = 0;
+ Fear_Timer = 0;
+ Mind_Blast_Timer = 0;
+ SW_Death_Timer = 0;
+ Fear_Ward_Timer = 0;
+ Psychic_Horror_Timer = 0;
+ Silence_Timer = 0;
+ Dispersion_Timer = 0;
+ Mind_Flay_Timer = 0;
+ Penance_Timer = 0;
+ CheckDispelTimer = 0;
+ ShackleTimer = 0;
+ DevcheckTimer = 20;
+ Devcheck = false;
+
+ if (master)
+ {
+ setStats(CLASS_PRIEST, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_PRIEST);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Heal_Timer > diff) Heal_Timer -= diff;
+ if (Fade_Timer > diff) Fade_Timer -= diff;
+ if (Divine_Hymn_Timer > diff) Divine_Hymn_Timer -= diff;
+ if (Pain_Suppression_Timer > diff) Pain_Suppression_Timer -= diff;
+ if (Guardian_Spirit_Timer > diff) Guardian_Spirit_Timer -= diff;
+ if (PWS_Timer > diff) PWS_Timer -= diff;
+ if (Fear_Timer > diff) Fear_Timer -= diff;
+ if (Mind_Blast_Timer > diff) Mind_Blast_Timer -= diff;
+ if (SW_Death_Timer > diff) SW_Death_Timer -= diff;
+ if (Fear_Ward_Timer > diff) Fear_Ward_Timer -= diff;
+ if (Psychic_Horror_Timer > diff) Psychic_Horror_Timer -= diff;
+ if (Silence_Timer > diff) Silence_Timer -= diff;
+ if (Dispersion_Timer > diff) Dispersion_Timer -= diff;
+ if (Mind_Flay_Timer > diff) Mind_Flay_Timer -= diff;
+ if (Penance_Timer > diff) Penance_Timer -= diff;
+ if (CheckDispelTimer > diff) CheckDispelTimer -= diff;
+ if (ShackleTimer > diff) ShackleTimer -= diff;
+ if (DevcheckTimer > diff) DevcheckTimer -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ DISPELMAGIC = lvl >= 70 ? MASS_DISPEL_1 : InitSpell(me, DISPEL_MAGIC_1);
+ CURE_DISEASE = InitSpell(me, CURE_DISEASE_1);
+ FEAR_WARD = InitSpell(me, FEAR_WARD_1);
+ /*Talent*/PAIN_SUPPRESSION = lvl >= 50 ? PAIN_SUPPRESSION_1 : 0;
+ PSYCHIC_SCREAM = InitSpell(me, PSYCHIC_SCREAM_1);
+ FADE = InitSpell(me, FADE_1);
+ /*Talent*/PSYCHIC_HORROR = lvl >= 50 ? PSYCHIC_HORROR_1 : 0;
+ /*Talent*/SILENCE = lvl >= 30 ? SILENCE_1 : 0;
+ /*Talent*/PENANCE = lvl >= 60 ? InitSpell(me, PENANCE_1) : 0;
+ /*Talent*/VAMPIRIC_EMBRACE = lvl >= 30 ? VAMPIRIC_EMBRACE_1 : 0;
+ /*Talent*/DISPERSION = lvl >= 60 ? DISPERSION_1 : 0;
+ MIND_SEAR = InitSpell(me, MIND_SEAR_1);
+ /*Talent*/GUARDIAN_SPIRIT = lvl >= 60 ? GUARDIAN_SPIRIT_1 : 0;
+ SHACKLE_UNDEAD = InitSpell(me, SHACKLE_UNDEAD_1);
+ HEAL = lvl >= 40 ? InitSpell(me, GREATER_HEAL_1) : lvl >= 16 ? InitSpell(me, NORMAL_HEAL_1) : InitSpell(me, LESSER_HEAL_1);
+ RENEW = InitSpell(me, RENEW_1);
+ FLASH_HEAL = InitSpell(me, FLASH_HEAL_1);
+ PRAYER_OF_HEALING = InitSpell(me, PRAYER_OF_HEALING_1);
+ DIVINE_HYMN = InitSpell(me, DIVINE_HYMN_1);
+ RESURRECTION = InitSpell(me, RESURRECTION_1);
+ PW_SHIELD = InitSpell(me, PW_SHIELD_1);
+ INNER_FIRE = InitSpell(me, INNER_FIRE_1);
+ PW_FORTITUDE = InitSpell(me, PW_FORTITUDE_1);
+ SHADOW_PROTECTION = InitSpell(me, SHADOW_PROTECTION_1);
+ DIVINE_SPIRIT = InitSpell(me, DIVINE_SPIRIT_1);
+ SW_PAIN = InitSpell(me, SW_PAIN_1);
+ MIND_BLAST = InitSpell(me, MIND_BLAST_1);
+ SW_DEATH = InitSpell(me, SW_DEATH_1);
+ DEVOURING_PLAGUE = InitSpell(me, DEVOURING_PLAGUE_1);
+ /*Talent*/MIND_FLAY = lvl >= 20 ? InitSpell(me, MIND_FLAY_1) : 0;
+ /*Talent*/VAMPIRIC_TOUCH = lvl >= 50 ? InitSpell(me, VAMPIRIC_TOUCH_1) : 0;
+ }
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+ if (level >= 65)
+ RefreshAura(BORROWED_TIME); //25%haste/40%bonus
+ if (level >= 55)
+ RefreshAura(DIVINE_AEGIS); //30%
+ if (level >= 55)
+ RefreshAura(EMPOWERED_RENEW3); //15%
+ else if (level >= 50)
+ RefreshAura(EMPOWERED_RENEW2); //10%
+ else if (level >= 45)
+ RefreshAura(EMPOWERED_RENEW1); //5%
+ if (level >= 45)
+ RefreshAura(BODY_AND_SOUL1); //30%
+ if (level >= 50)
+ RefreshAura(PAINANDSUFFERING3); //100%
+ else if (level >= 48)
+ RefreshAura(PAINANDSUFFERING2); //66%
+ else if (level >= 45)
+ RefreshAura(PAINANDSUFFERING1); //33%
+ if (level >= 50)
+ RefreshAura(MISERY3); //3%
+ else if (level >= 48)
+ RefreshAura(MISERY2); //2%
+ else if (level >= 45)
+ RefreshAura(MISERY1); //1%
+ if (level >= 45)
+ RefreshAura(GRACE); //100%
+ if (level >= 35)
+ RefreshAura(IMP_DEV_PLAG); //30%
+ if (level >= 25)
+ RefreshAura(INSPIRATION3); //10%
+ else if (level >= 23)
+ RefreshAura(INSPIRATION2); //6%
+ else if (level >= 20)
+ RefreshAura(INSPIRATION1); //3%
+ if (level >= 30)
+ RefreshAura(SHADOW_WEAVING3); //100%
+ else if (level >= 28)
+ RefreshAura(SHADOW_WEAVING2); //66%
+ else if (level >= 25)
+ RefreshAura(SHADOW_WEAVING1); //33%
+ if (level >= 15)
+ {
+ RefreshAura(GLYPH_SW_PAIN);
+ RefreshAura(GLYPH_PW_SHIELD); //20% heal
+ }
+ if (level >= 40)
+ RefreshAura(SHADOWFORM); //allows dots to crit, passive
+ if (level >= 70)
+ RefreshAura(PRIEST_T10_2P_BONUS);
+ }
+
+ private:
+ uint32
+ /*Buffs*/INNER_FIRE, PW_FORTITUDE, DIVINE_SPIRIT, SHADOW_PROTECTION,
+ /*Disc*/FEAR_WARD, PAIN_SUPPRESSION, SHACKLE_UNDEAD, PW_SHIELD, DISPELMAGIC, CURE_DISEASE, PENANCE,
+ /*Holy*/HEAL, FLASH_HEAL, RENEW, PRAYER_OF_HEALING, DIVINE_HYMN, GUARDIAN_SPIRIT, RESURRECTION,
+ /*Shadow*/SW_PAIN, MIND_BLAST, SW_DEATH, DEVOURING_PLAGUE, MIND_FLAY, VAMPIRIC_TOUCH,
+ /*Shadow*/PSYCHIC_SCREAM, FADE, PSYCHIC_HORROR, VAMPIRIC_EMBRACE, DISPERSION, MIND_SEAR, SILENCE;
+ //Timers/other
+/*Disc*/uint32 Penance_Timer, PWS_Timer, Pain_Suppression_Timer, Fear_Ward_Timer;
+/*Holy*/uint32 Heal_Timer, Divine_Hymn_Timer, Guardian_Spirit_Timer;
+/*Shdw*/uint32 Fade_Timer, Fear_Timer, Mind_Blast_Timer, SW_Death_Timer, Mind_Flay_Timer,
+/*Shdw*/ Psychic_Horror_Timer, Silence_Timer, Dispersion_Timer;
+/*Misc*/uint16 CheckDispelTimer, ShackleTimer, 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
+ IMP_DEV_PLAG = 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,
+ //Glyphs
+ GLYPH_SW_PAIN = 55681,
+ GLYPH_PW_SHIELD = 55672,
+ //other
+ PRIEST_T10_2P_BONUS = 70770,//33% renew
+ };
+ enum PriestSpecial
+ {
+ WEAKENED_SOUL = 6788,
+ };
+ }; //end priest_bot
+};
+
+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..daa7b10
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp
@@ -0,0 +1,894 @@
+#include "bot_ai.h"
+//#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+/*
+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);
+ }
+
+ 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;
+ }
+
+ struct rogue_botAI : public bot_minion_ai
+ {
+ rogue_botAI(Creature* creature) : bot_minion_ai(creature)
+ {
+ Reset();
+ }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, 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);
+ GetInPosition(force, false);
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { comboPoints = 0; tempComboPoints = 0; master->SetNpcBotDied(me->GetGUID()); }
+ void DoNonCombatActions(const 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)))
+ return;
+
+ if (set)
+ energy = mod;
+ else
+ energy += mod;
+
+ energy = std::min<uint32>(energy, 100);
+ me->SetPower(POWER_ENERGY, energy);
+ }
+
+ uint32 getenergy()
+ {
+ energy = me->GetPower(POWER_ENERGY);
+ return energy;
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (KidneyTarget)
+ {
+ //kidney shot is casted as rank 1 (1 or 2 secs) so add duration accordingly
+ //i.e. kedney rank 1, points = 5, we have aura with duration 1 sec so add 4 more secs
+ //tempComboPoints -= 1;
+ if (tempComboPoints)
+ {
+ if (Unit* u = sObjectAccessor->FindUnit(KidneyTarget))
+ {
+ if (Aura* kidney = u->GetAura(KIDNEY_SHOT, me->GetGUID()))
+ {
+ uint32 dur = kidney->GetDuration() + tempComboPoints*1000;
+ kidney->SetDuration(dur);
+ kidney->SetMaxDuration(dur);
+ }
+ }
+ else//spell is failed to hit: restore cp
+ {
+ if (comboPoints == 0)
+ comboPoints = tempComboPoints;
+ }
+ tempComboPoints = 0;
+ }
+ KidneyTarget = 0;
+ }
+ else if (RuptureTarget)
+ {
+ //tempComboPoints -= 1;
+ if (tempComboPoints)
+ {
+ if (Unit* u = sObjectAccessor->FindUnit(RuptureTarget))
+ {
+ if (Aura* rupture = u->GetAura(RUPTURE, me->GetGUID()))
+ {
+ uint32 dur = rupture->GetDuration() + tempComboPoints*2000;
+ rupture->SetDuration(dur);
+ rupture->SetMaxDuration(dur);
+ }
+ }
+ else//spell is failed to hit: restore cp
+ {
+ if (comboPoints == 0)
+ comboPoints = tempComboPoints;
+ }
+ tempComboPoints = 0;
+ }
+ RuptureTarget = 0;
+ }
+ else if (tempDICE)
+ {
+ //tempComboPoints -= 1;
+ if (tempComboPoints)
+ {
+ if (Aura* dice = me->GetAura(SLICE_DICE))
+ {
+ uint32 dur = (dice->GetDuration()*3)/2 + tempComboPoints*4500;//with Improved Slice and Dice
+ dice->SetDuration(dur);
+ dice->SetMaxDuration(dur);
+ }
+ tempComboPoints = 0;
+ }
+ tempDICE = false;
+ }
+ if (IAmDead()) return;
+ if (me->getVictim())
+ DoMeleeAttackIfReady();
+ else
+ Evade();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ if (checkAurasTimer == 0)
+ CheckAuras();
+ BreakCC(diff);
+ if (CCed(me)) return;
+
+ if (GetHealthPCT(me) < 33 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, HEALINGPOTION))
+ {
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ }
+
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+
+ if (!CheckAttackTarget(CLASS_ROGUE))
+ return;
+
+ Attack(diff);
+ }
+
+ void Attack(const uint32 diff)
+ {
+ opponent = me->getVictim();
+ if (opponent)
+ {
+ if (!IsCasting())
+ StartAttack(opponent, true);
+ }
+ else
+ return;
+
+ comboPoints = std::min<uint8>(comboPoints, 5);
+ //AttackerSet m_attackers = master->getAttackers();
+ AttackerSet b_attackers = me->getAttackers();
+ float dist = me->GetExactDist(opponent);
+ float meleedist = me->GetDistance(opponent);
+
+ if (BLADE_FLURRY && Blade_Flurry_Timer <= diff && meleedist <= 5 &&
+ Rand() < 30 && getenergy() >= 25 && FindSplashTarget(7, opponent))
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, BLADE_FLURRY))
+ {
+ Blade_Flurry_Timer = 90000;
+ GC_Timer = temptimer;
+ return;//return here to allow cast only on next update
+ }
+ }
+
+ if (MoveBehind(*opponent))
+ wait = 5;
+
+ //KICK
+ if (KICK && Kick_Timer <= diff && meleedist <= 5 && Rand() < 80 && getenergy() >= 25 &&
+ opponent->IsNonMeleeSpellCasted(false))
+ {
+ temptimer = GC_Timer;
+ if (doCast(opponent, KICK))
+ {
+ Kick_Timer = 8000;//improved
+ GC_Timer = temptimer;
+ //return;
+ }
+ }
+ //SHADOWSTEP
+ if (SHADOWSTEP && Shadowstep_Timer <= diff && dist < 25 &&
+ (opponent->getVictim() != me || opponent->GetTypeId() == TYPEID_PLAYER) &&
+ Rand() < 30 && getenergy() >= 10)
+ {
+ temptimer = GC_Timer;
+ if (doCast(opponent, SHADOWSTEP))
+ {
+ Shadowstep_Timer = 20000;
+ GC_Timer = temptimer;
+ //return;
+ }
+ //doCast(opponent, BACKSTAB, true);
+ }
+ //BACKSTAB
+ if (BACKSTAB && GC_Timer <= diff && meleedist <= 5 && comboPoints < 5 &&
+ /*Rand() < 90 && */getenergy() >= 60 && !opponent->HasInArc(M_PI, me))
+ {
+ if (doCast(opponent, BACKSTAB))
+ return;
+ }
+ //SINISTER STRIKE
+ if (SINISTER_STRIKE && GC_Timer <= diff && meleedist <= 5 && comboPoints < 5 &&
+ Rand() < 25 && getenergy() >= 45)
+ {
+ if (doCast(opponent, SINISTER_STRIKE))
+ return;
+ }
+ //SLICE AND DICE
+ if (SLICE_DICE && Slice_Dice_Timer <= diff && GC_Timer <= diff && dist < 20 && comboPoints > 1 &&
+ (b_attackers.size() <= 1 || Blade_Flurry_Timer > 80000) && Rand() < 30 && getenergy() >= 25)
+ {
+ if (doCast(opponent, SLICE_DICE))
+ {
+ //DICE = true;
+ //tempDICE = true;
+ //tempComboPoints = comboPoints;
+ ////comboPoints = 0;
+ return;
+ }
+ }
+ //KIDNEY SHOT
+ if (KIDNEY_SHOT && GC_Timer <= diff && Kidney_Timer <= diff && meleedist <= 5 && comboPoints > 0 &&
+ !CCed(opponent) && getenergy() >= 25 && ((Rand() < 15 + comboPoints*15 && opponent->getVictim() == me && comboPoints > 2) || opponent->IsNonMeleeSpellCasted(false)))
+ {
+ if (doCast(opponent, KIDNEY_SHOT))
+ {
+ KidneyTarget = opponent->GetGUID();
+ tempComboPoints = comboPoints;
+ //comboPoints = 0;
+ Kidney_Timer = 20000;
+ return;
+ }
+ }
+ //EVISCERATE
+ if (EVISCERATE && GC_Timer <= diff && meleedist <= 5 && comboPoints > 2 &&
+ getenergy() >= 35 && Rand() < comboPoints*15)
+ {
+ 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
+
+ //PlaceHolder::damage bonuses
+ // Eviscerate and Envenom Bonus Damage (Deathmantle item set effect)
+ //if (me->HasAura(37169))
+ // damage += comboPoints*100;
+
+ 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 (RUPTURE && Rupture_Timer <= diff && GC_Timer <= diff && meleedist <= 5 && comboPoints > 2 &&
+ opponent->GetHealth() > me->GetMaxHealth()/3 && getenergy() >= 25 && Rand() < 50 + 50*opponent->isMoving())
+ {
+ //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;
+ //if (doCast(opponent, RUPTURE))
+ //{
+ // RuptureTarget = opponent->GetGUID();
+ // tempComboPoints = comboPoints;
+ // Rupture_Timer = 6000 + (comboPoints-1)*2000;
+ // //comboPoints = 0;
+ // return;
+ //}
+ }
+ //DISMANTLE
+ if (DISMANTLE && Dismantle_Timer <= diff && meleedist <= 5 &&
+ opponent->GetTypeId() == TYPEID_PLAYER &&
+ Rand() < 20 && getenergy() >= 25 && !CCed(opponent) &&
+ !opponent->HasAuraType(SPELL_AURA_MOD_DISARM) &&
+ opponent->ToPlayer()->GetWeaponForAttack(BASE_ATTACK))
+ {
+ temptimer = GC_Timer;
+ if (doCast(opponent, DISMANTLE))
+ {
+ Dismantle_Timer = 60000;
+ GC_Timer = temptimer;
+ }
+ }
+ }
+
+ 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 == BACKSTAB)
+ aftercrit += 30.f;
+ //Puncturing Wounds: 15% additional critical chance for Mutilate
+ else if (spellId == MUTILATE)
+ aftercrit += 15.f;
+ //Glyph of Eviscerate: 10% additional critical chance for Eviscerate
+ else if (spellId == EVISCERATE)
+ aftercrit += 10.f;
+ //Improved Ambush: 50% additional critical chance for Ambush
+ //else if (spellId == AMBUSH)
+ // crit_chance += 50.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 == EVISCERATE)
+ pctbonus += 0.15f;
+ //Imoroved Eviscerate: 20% damage bonus for Eviscerate
+ if (spellId == EVISCERATE)
+ pctbonus += 0.2f;
+ //Opportunity: 20% damage bonus for Backstab, Mutilate, Garrote and Ambush
+ if (spellId == BACKSTAB || spellId == MUTILATE/* ||
+ spellId == GARROTE || spellId == AMBUSH*/)
+ pctbonus += 0.2f;
+ //Aggression: 15% damage bonus for Sinister Strike, Backstab and Eviscerate
+ if (lvl >= 30 && (spellId == SINISTER_STRIKE || spellId == BACKSTAB || spellId == EVISCERATE))
+ pctbonus += 0.15f;
+ //Blood Spatter: 30% bonus damage for Rupture and Garrote
+ if (lvl >= 15 && (spellId == RUPTURE/* || spellId == GARROTE*/))
+ pctbonus += 0.3f;
+ //Serrated Blades: 30% bonus damage for Rupture
+ if (lvl >= 20 && spellId == RUPTURE)
+ pctbonus += 0.3f;
+ //Surprise Attacks: 10% bonus damage for Sinister Strike, Backstab, Shiv, Hemmorhage and Gouge
+ if (lvl >= 50 && (spellId == SINISTER_STRIKE || spellId == BACKSTAB/* ||
+ spellId == SHIV || spellId == HEMMORHAGE || spellId == GOUGE*/))
+ pctbonus += 0.1f;
+
+ damage = int32(fdamage * (1.0f + pctbonus));
+ }
+
+ void DamageDealt(Unit* victim, uint32& /*damage*/, DamageEffectType damageType)
+ {
+ if (!WOUND_POISON && !MIND_NUMBING_POISON)
+ return;
+
+ if (damageType == DIRECT_DAMAGE/* || damageType == SPELL_DIRECT_DAMAGE*/)
+ {
+ if (victim && me->GetExactDist(victim) <= 30)
+ {
+ 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;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void SpellHitTarget(Unit* target, SpellInfo const* spell)
+ {
+ uint32 spellId = spell->Id;
+ if (currentSpell == 0)
+ return;
+
+ if (spellId == SLICE_DICE)
+ {
+ tempDICE = true;
+ tempComboPoints = comboPoints;
+ Slice_Dice_Timer = 15000 + (comboPoints-1)*4500;
+ }
+ else if (spellId == RUPTURE)
+ {
+ RuptureTarget = target->GetGUID();
+ tempComboPoints = comboPoints;
+ Rupture_Timer = 8000 + (comboPoints-1)*2000;
+ GC_Timer = 800;
+ }
+ else if (spellId == EVISCERATE)
+ GC_Timer = 800;
+
+ //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<uint32>(comboPoints,5));
+ //me->MonsterWhisper(msg.str().c_str(), master->GetGUID());
+ if (rand()%100 < 1 + 20*(comboPoints > 5 ? 5 : comboPoints))
+ 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->MonsterWhisper(msg2.str().c_str(), master->GetGUID());
+ }
+ else if (spellId == SINISTER_STRIKE ||
+ spellId == BACKSTAB/* ||
+ spellId == GOUGE ||
+ spellId == HEMORRHAGE ||
+ spellId == SHOSTLY_STRIKE*/)
+ {
+ ++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->MonsterWhisper(msg.str().c_str(), master->GetGUID());
+ if (tempAddCP)
+ {
+ comboPoints += tempAddCP;
+ tempAddCP = 0;
+ }
+ }
+ else if (spellId == MUTILATE/* ||
+ spellId == AMBUSH*/)
+ {
+ comboPoints += 2;
+ //std::ostringstream msg;
+ //msg << "2 cp generated (Mutilate), set to " << uint32(comboPoints);
+ //if (tempAddCP)
+ // msg << " + " << uint32(tempAddCP) << " (triggered)";
+ //me->MonsterWhisper(msg.str().c_str(), 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->MonsterWhisper(msg.str().c_str(), master->GetGUID());
+ }
+
+ if (spellId == SINISTER_STRIKE)
+ {
+ //Improved Sinister Strike
+ //instead of restoring energy we should override current value
+ if (me->getLevel() >= 10)
+ modenergy(-40, true);//45 - 5
+ }
+ //Glyph of Sinister Strike (50% to add cp on crit)
+ //Seal Fate means crit so this glyph is enabled from lvl 35)
+ if (spellId == SEAL_FATE_EFFECT && currentSpell == SINISTER_STRIKE && rand()%100 >= 50)
+ {
+ ++tempAddCP;
+ //me->MonsterWhisper("1 temp cp generated (glyph of SS)", master->GetGUID());
+ }
+ //Slaughter from the Shadows energy restore
+ //instead of restoring energy we should override current value
+ if (me->getLevel() >= 55)
+ {
+ if (spellId == BACKSTAB/* || spellId == AMBUSH*/)
+ modenergy(-40, true);
+ //else if (spellId == HEMORRHAGE)
+ // modenergy(-30, true);
+ }
+
+ //if (spellId == SHADOWSTEP)
+ //{
+ // Shadowstep_eff_Timer = 10000;
+ // shadowstep = true;
+ //}
+
+ //move behind on Kidney Shot and Gouge (optionally)
+ if (spellId == KIDNEY_SHOT/* || spellId == GOUGE*/)
+ if (MoveBehind(*target))
+ wait = 3;
+
+ if (spellId == currentSpell)
+ currentSpell = 0;
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Mutilate_Timer = 0;
+ Rupture_Timer = 0;
+ Dismantle_Timer = 0;
+ Kick_Timer = 0;
+ Kidney_Timer = 0;
+ Shadowstep_Timer = 0;
+ Blade_Flurry_Timer = 0;
+ Slice_Dice_Timer = 0;
+ //Shadowstep_eff_Timer = 0;
+
+ comboPoints = 0;
+ tempComboPoints = 0;
+ tempAddCP = 0;
+
+ KidneyTarget = 0;
+ RuptureTarget = 0;
+
+ tempDICE = false;
+ spellHitTarget = true;
+ //shadowstep = false;
+
+ me->setPowerType(POWER_ENERGY);
+ //10 energy gained per stack
+ RefreshAura(GLADIATOR_VIGOR, 10);
+
+ if (master)
+ {
+ setStats(CLASS_ROGUE, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_ROGUE);
+ }
+
+ me->SetPower(POWER_ENERGY, me->GetMaxPower(POWER_ENERGY));
+ }
+
+ void ReduceCD(const uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Kick_Timer > diff) Kick_Timer -= diff;
+ if (Rupture_Timer > diff) Rupture_Timer -= diff;
+ if (Shadowstep_Timer > diff) Shadowstep_Timer -= diff;
+ if (Mutilate_Timer > diff) Mutilate_Timer -= diff;
+ if (Kidney_Timer > diff) Kidney_Timer -= diff;
+ if (Dismantle_Timer > diff) Dismantle_Timer -= diff;
+ if (Blade_Flurry_Timer > diff) Blade_Flurry_Timer -= diff;
+ if (Slice_Dice_Timer > diff) Slice_Dice_Timer -= diff;
+ //if (Shadowstep_eff_Timer > diff) Shadowstep_eff_Timer -= diff;
+ //else if (shadowstep) shadowstep = false;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ BACKSTAB = InitSpell(me, BACKSTAB_1);
+ SINISTER_STRIKE = InitSpell(me, SINISTER_STRIKE_1);
+ SLICE_DICE = InitSpell(me, SLICE_DICE_1);
+ EVISCERATE = InitSpell(me, EVISCERATE_1);
+ KICK = InitSpell(me, KICK_1);
+ RUPTURE = InitSpell(me, RUPTURE_1);
+ KIDNEY_SHOT = InitSpell(me, KIDNEY_SHOT_1);
+ MUTILATE = lvl >= 50 ? InitSpell(me, MUTILATE_1) : 0;
+ SHADOWSTEP = lvl >= 50 ? SHADOWSTEP_1 : 0;
+ DISMANTLE = InitSpell(me, DISMANTLE_1);
+ BLADE_FLURRY = lvl >= 30 ? BLADE_FLURRY_1 : 0;
+
+ WOUND_POISON = InitSpell(me, WOUND_POISON_1);
+ MIND_NUMBING_POISON = InitSpell(me, MIND_NUMBING_POISON_1);
+ }
+
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+
+ //if (level >= 78)
+ // RefreshAura(ROGUE_ARMOR_ENERGIZE,2);
+ //else if (level >= 60)
+ // RefreshAura(ROGUE_ARMOR_ENERGIZE);
+ if (level >= 70)
+ RefreshAura(COMBAT_POTENCY5,2);
+ else if (level >= 55)
+ RefreshAura(COMBAT_POTENCY5);
+ else if (level >= 52)
+ RefreshAura(COMBAT_POTENCY4);
+ else if (level >= 49)
+ RefreshAura(COMBAT_POTENCY3);
+ else if (level >= 47)
+ RefreshAura(COMBAT_POTENCY2);
+ else if (level >= 45)
+ RefreshAura(COMBAT_POTENCY1);
+ if (level >= 35)
+ RefreshAura(SEAL_FATE5);
+ else if (level >= 32)
+ RefreshAura(SEAL_FATE4);
+ else if (level >= 29)
+ RefreshAura(SEAL_FATE3);
+ else if (level >= 27)
+ RefreshAura(SEAL_FATE2);
+ else if (level >= 25)
+ RefreshAura(SEAL_FATE1);
+ if (level >= 78)
+ RefreshAura(VITALITY,4);
+ else if (level >= 70)
+ RefreshAura(VITALITY,3);
+ else if (level >= 55)
+ RefreshAura(VITALITY,2);
+ else if (level >= 40)
+ RefreshAura(VITALITY);
+ if (level >= 55)
+ RefreshAura(TURN_THE_TABLES);
+ if (level >= 40)
+ RefreshAura(DEADLY_BREW);
+ if (level >= 35)
+ RefreshAura(BLADE_TWISTING1);//allow rank 1 only
+ if (level >= 35)
+ RefreshAura(QUICK_RECOVERY2);
+ else if (level >= 30)
+ RefreshAura(QUICK_RECOVERY1);
+ if (level >= 30)
+ RefreshAura(IMPROVED_KIDNEY_SHOT);
+ if (level >= 10)
+ RefreshAura(GLYPH_BACKSTAB);
+ if (level >= 10)
+ RefreshAura(SURPRISE_ATTACKS);
+
+ //On 25 get Glyph of Vigor
+ if (level >= 25)
+ RefreshAura(ROGUE_VIGOR,2);
+ else if (level >= 20)
+ RefreshAura(ROGUE_VIGOR);
+ }
+
+ private:
+ uint32
+ BACKSTAB, SINISTER_STRIKE, SLICE_DICE, EVISCERATE, KICK, RUPTURE, KIDNEY_SHOT, MUTILATE,
+ SHADOWSTEP, DISMANTLE, BLADE_FLURRY,
+ WOUND_POISON, MIND_NUMBING_POISON;
+ //Timers/other
+ uint64 KidneyTarget, RuptureTarget;
+ uint32 Rupture_Timer, Dismantle_Timer,
+ Kick_Timer, Shadowstep_Timer, Mutilate_Timer, Kidney_Timer,
+ Blade_Flurry_Timer, Slice_Dice_Timer/*, Shadowstep_eff_Timer*/;
+ uint32 energy;
+ uint8 comboPoints, tempComboPoints, tempAddCP;
+ bool tempDICE, spellHitTarget/*, shadowstep*/;
+
+ enum RogueBaseSpells
+ {
+ BACKSTAB_1 = 53,
+ SINISTER_STRIKE_1 = 1757,
+ SLICE_DICE_1 = 5171,
+ EVISCERATE_1 = 2098,//11300
+ KICK_1 = 1766,
+ RUPTURE_1 = 1943,
+ KIDNEY_SHOT_1 = 408,//8643
+ /*Talent*/MUTILATE_1 = 1329,//48666
+ /*Talent*/SHADOWSTEP_1 = 36554,
+ DISMANTLE_1 = 51722,
+ BLADE_FLURRY_1 = 33735,
+
+ 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
+ {
+ //WOUND_POISON = 13218,
+ //DEADLY_POISON = 2818,
+ //MIND_NUMBING_POISON = 5760,
+ RELENTLESS_STRIKES_EFFECT = 14181,
+ RUTHLESSNESS_EFFECT = 14157,
+ SEAL_FATE_EFFECT = 14189,
+ //SHADOWSTEP_EFFECT_DAMAGE = 36563,
+ TURN_THE_TABLES_EFFECT = 52910
+ };
+ };
+};
+
+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..f9e7e8b
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp
@@ -0,0 +1,390 @@
+#include "bot_ai.h"
+//#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+/*
+Complete - 1%
+TODO:
+*/
+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);
+ }
+
+ 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;
+ }
+
+ struct shaman_botAI : public bot_minion_ai
+ {
+ shaman_botAI(Creature* creature) : bot_minion_ai(creature)
+ {
+ Reset();
+ }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_SHAMAN) != SPELL_CAST_OK)
+ return false;
+ uint64 originalCaster = 0;
+ if (spellId == STONESKIN_TOTEM ||
+ spellId == SEARING_TOTEM ||
+ spellId == WINDFURY_TOTEM ||
+ spellId == HEALINGSTREAM_TOTEM ||
+ spellId == MANASPRING_TOTEM)
+ originalCaster = master->GetGUID();
+ return bot_ai::doCast(victim, spellId, triggered, originalCaster);
+ }
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == COMMAND_ATTACK && !force) return;
+ Aggro(u);
+ GetInPosition(force, false/*STYLE == MELEE*//*SPEC == ENCHANCEMENT*/);
+ SetBotCommandState(COMMAND_ATTACK);
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { master->SetNpcBotDied(me->GetGUID()); }
+
+ void CheckTotems(uint32 diff)
+ {
+ if (GC_Timer > diff || Rand() > 50 || IsCasting() || me->GetDistance(master) > 20 || Feasting())
+ return;
+ if (me->isInCombat())
+ {
+ if (WINDFURY_TOTEM && !HasAuraName(master, WINDFURY_TOTEM))
+ {
+ if (doCast(me, WINDFURY_TOTEM))
+ return;
+ }
+ else if (STONESKIN_TOTEM && !HasAuraName(master, STONESKIN_AURA))
+ {
+ if (doCast(me, STONESKIN_TOTEM))
+ return;
+ }
+
+ if (Unit* u = me->getVictim())
+ {
+ if (SEARING_TOTEM && Searing_Totem_Timer <= diff)
+ {
+ if (me->GetExactDist(u) < (u->isMoving() ? 10 : 25))
+ {
+ if (doCast(me, SEARING_TOTEM))
+ {
+ Searing_Totem_Timer = 30000;
+ return;
+ }
+ }
+ }
+ }
+ }
+ else if (!me->isMoving() && !master->isMoving())
+ {
+ uint8 manapct = GetManaPCT(master);
+ uint8 hppct = GetHealthPCT(master);
+ if (HEALINGSTREAM_TOTEM &&
+ (master->getPowerType() != POWER_MANA || hppct < 25 || manapct > hppct) &&
+ hppct < 98 && !HasAuraName(master, HEALINGSTREAM_AURA))
+ {
+ if (doCast(me, HEALINGSTREAM_TOTEM))
+ return;
+ }
+ else if (MANASPRING_TOTEM && manapct < 98 && !HasAuraName(master, MANASPRING_AURA))
+ {
+ if (doCast(me, MANASPRING_TOTEM))
+ return;
+ }
+ }
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (IAmDead()) return;
+ if (!me->getVictim()) Evade();
+ if (me->getVictim())
+ DoMeleeAttackIfReady();
+ else
+ Evade();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ BreakCC(diff);
+ if (CCed(me)) return;
+
+ if (GetManaPCT(me) < 30 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, MANAPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+
+ BuffAndHealGroup(master, diff);
+ CheckTotems(diff);
+
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+ //buff myself
+ if (LIGHTNING_SHIELD && GC_Timer <= diff && !me->HasAura(LIGHTNING_SHIELD))
+ if (doCast(me, LIGHTNING_SHIELD))
+ GC_Timer = 500;
+
+ if (!CheckAttackTarget(CLASS_SHAMAN))
+ return;
+
+ //Counter(diff);
+ DoNormalAttack(diff);
+ }
+
+ void Counter(uint32 const /*diff*/)
+ {}
+
+ 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 (Shock_Timer <= diff && GC_Timer <= diff && dist < 20)
+ {
+ if (!opponent->HasAura(FLAME_SHOCK, me->GetGUID()))
+ {
+ if (doCast(opponent, FLAME_SHOCK))
+ {
+ Shock_Timer = 9000;
+ return;
+ }
+ }
+ else if (!opponent->HasAura(EARTH_SHOCK))
+ {
+ if (doCast(opponent, EARTH_SHOCK))
+ {
+ Shock_Timer = 9000;
+ return;
+ }
+ }
+ }
+
+ if (Lightning_Bolt_Timer <= diff && GC_Timer <= diff && dist < 25)
+ {
+ if (doCast(opponent, LIGHTNING_BOLT))
+ {
+ Lightning_Bolt_Timer = uint32(float(sSpellMgr->GetSpellInfo(LIGHTNING_BOLT)->CalcCastTime()/100) * me->GetFloatValue(UNIT_MOD_CAST_SPEED) + 200);
+ return;
+ }
+ }
+ }
+
+ void DoNonCombatActions(uint32 diff)
+ {
+ if (GC_Timer > diff || Rand() > 50 || me->IsMounted()) return;
+
+ RezGroup(ANCESTRAL_SPIRIT, master);
+ //BuffAndHealGroup(master, diff);
+ // CureGroup(master, diff);
+ }
+
+ bool HealTarget(Unit* target, uint8 hp, uint32 diff)
+ {
+ if (hp > 95) return false;
+ if (!target || target->isDead() || 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 (hp < 70 && Heal_Timer <= diff)
+ {
+ doCast(target, HEALING_WAVE);
+ }
+ else */if (hp < 90 && Heal_Timer <= diff)
+ {
+ doCast(target, CHAIN_HEAL);
+ }
+ else if (hp < 95)
+ {
+ doCast(target, LESSER_HEAL);
+ return true;
+ }
+ return true;
+ }
+
+ //void ApplyBotDamageMultiplierMelee(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 new chance roll will replace original (balance unsafe)
+ // if (!crit)
+ // {
+ // float crit_chance = me->GetUnitCriticalChance(BASE_ATTACK, me);
+ // float aftercrit = crit_chance;
+
+ // //second roll (may be illogical)
+ // if (aftercrit > crit_chance)
+ // crit = roll_chance_f(aftercrit);
+ // }
+
+ // 2) apply bonus damage mods
+ // float pctbonus = 0.0f;
+ // if (crit)
+ // {
+ // }
+
+ // fdamage *= (1.0f + pctbonus);
+ // damage = int32(fdamage);
+ // //last: overall multiplier
+ // bot_ai::ApplyBotDamageMultiplierMelee(damage, damageinfo, spellInfo, attackType, crit);
+ //}
+
+ void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/)
+ {
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Heal_Timer = 0;
+ Shock_Timer = 0;
+ Lightning_Bolt_Timer = 0;
+ Searing_Totem_Timer = 0;
+
+ if (master)
+ {
+ setStats(CLASS_SHAMAN, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_SHAMAN);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Heal_Timer > diff) Heal_Timer -= diff;
+ if (Shock_Timer > diff) Shock_Timer -= diff;
+ if (Lightning_Bolt_Timer > diff) Lightning_Bolt_Timer -= diff;
+ if (Searing_Totem_Timer > diff) Searing_Totem_Timer -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ //uint8 lvl = me->getLevel();
+ CHAIN_HEAL = InitSpell(me, CHAIN_HEAL_1);
+ LESSER_HEAL = InitSpell(me, LESSER_HEAL_1);
+ ANCESTRAL_SPIRIT = InitSpell(me, ANCESTRAL_SPIRIT_1);
+ FLAME_SHOCK = InitSpell(me, FLAME_SHOCK_1);
+ EARTH_SHOCK = InitSpell(me, EARTH_SHOCK_1);
+ LIGHTNING_BOLT = InitSpell(me, LIGHTNING_BOLT_1);
+ LIGHTNING_SHIELD = InitSpell(me, LIGHTNING_SHIELD_1);
+ STONESKIN_TOTEM = InitSpell(me, STONESKIN_TOTEM_1);
+ HEALINGSTREAM_TOTEM = InitSpell(me, HEALINGSTREAM_TOTEM_1);
+ MANASPRING_TOTEM = InitSpell(me, MANASPRING_TOTEM_1);
+ SEARING_TOTEM = InitSpell(me, SEARING_TOTEM_1);
+ WINDFURY_TOTEM = InitSpell(me, WINDFURY_TOTEM_1);
+ }
+
+ void ApplyClassPassives()
+ {
+ }
+
+ private:
+ uint32
+ CHAIN_HEAL, LESSER_HEAL, ANCESTRAL_SPIRIT,
+ FLAME_SHOCK, EARTH_SHOCK, LIGHTNING_BOLT, LIGHTNING_SHIELD,
+ STONESKIN_TOTEM, HEALINGSTREAM_TOTEM, MANASPRING_TOTEM, SEARING_TOTEM, WINDFURY_TOTEM;
+ //Timers
+ uint32 Heal_Timer, Shock_Timer, Lightning_Bolt_Timer, Searing_Totem_Timer;
+
+ enum ShamanBaseSpells
+ {
+ CHAIN_HEAL_1 = 1064,
+ LESSER_HEAL_1 = 8004,
+ ANCESTRAL_SPIRIT_1 = 2008,
+ FLAME_SHOCK_1 = 8050,
+ EARTH_SHOCK_1 = 8042,
+ LIGHTNING_BOLT_1 = 403,
+ LIGHTNING_SHIELD_1 = 324,
+ STONESKIN_TOTEM_1 = 8071,
+ HEALINGSTREAM_TOTEM_1 = 5394,
+ MANASPRING_TOTEM_1 = 5675,
+ SEARING_TOTEM_1 = 3599,
+ WINDFURY_TOTEM_1 = 8512,
+ };
+
+ enum ShamanPassives
+ {
+ };
+
+ enum ShamanSpecial
+ {
+ STONESKIN_AURA = 8072,
+ HEALINGSTREAM_AURA = 5672,
+ MANASPRING_AURA = 5677,
+ };
+ };
+};
+
+
+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..b5fc80b
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp
@@ -0,0 +1,458 @@
+#include "bot_ai.h"
+//#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+
+/*
+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);
+ }
+
+ 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;
+ }
+
+ struct warlock_botAI : public bot_minion_ai
+ {
+ warlock_botAI(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_WARRIOR) != 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*) { me->SetBotsPetDied(); master->SetNpcBotDied(me->GetGUID()); }
+ void DoNonCombatActions()
+ {}
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == COMMAND_ATTACK && !force) return;
+ Aggro(u);
+ GetInPosition(force, true);
+ SetBotCommandState(COMMAND_ATTACK);
+ fear_cd = std::max<uint32>(fear_cd, 1000);
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (IAmDead()) return;
+ if (!me->getVictim())
+ Evade();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ 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 (GetHealthPCT(me) < 50 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, HEALINGPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ if (GetManaPCT(me) < 50 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, MANAPOTION))
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ if (!me->isInCombat())
+ DoNonCombatActions();
+
+ if (!CheckAttackTarget(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 (fear_cd <= diff && GC_Timer <= diff)
+ { CheckFear(); fear_cd = 2000; }
+
+ if (RAIN_OF_FIRE && Rand() < 25 && Rain_of_fire_cd <= diff && GC_Timer <= diff)
+ {
+ Unit* blizztarget = FindAOETarget(30, true);
+ if (blizztarget && doCast(blizztarget, RAIN_OF_FIRE))
+ {
+ Rain_of_fire_cd = 5000;
+ return;
+ }
+ Rain_of_fire_cd = 2000;//fail
+ }
+
+ if (Rand() < 25 && CURSE_OF_THE_ELEMENTS && GC_Timer <= diff && !HasAuraName(opponent, CURSE_OF_THE_ELEMENTS) &&
+ doCast(opponent, CURSE_OF_THE_ELEMENTS))
+ {
+ GC_Timer = 800;
+ return;
+ }
+
+ if (GC_Timer <= 0 && Rand() < 25 && !opponent->HasAura(CORRUPTION, me->GetGUID()) &&
+ doCast(opponent, CORRUPTION))
+ { return; }
+
+ if (HAUNT && Rand() < 25 && Haunt_cd <= diff && GC_Timer <= diff && !opponent->HasAura(HAUNT, me->GetGUID()) &&
+ doCast(opponent, HAUNT))
+ {
+ Haunt_cd = 8000;
+ return;
+ }
+
+ if (GC_Timer <= diff && Rand() < 15 && !Afflicted(opponent))
+ {
+ if (conflagarate_cd <= 8000 && doCast(opponent, IMMOLATE))
+ return;
+ else if (UNSTABLE_AFFLICTION && doCast(opponent, UNSTABLE_AFFLICTION))
+ return;
+ }
+
+ if (CONFLAGRATE && Rand() < 35 && conflagarate_cd <= diff && GC_Timer <= diff &&
+ HasAuraName(opponent, IMMOLATE) &&
+ doCast(opponent, CONFLAGRATE))
+ {
+ conflagarate_cd = 10000;
+ return;
+ }
+
+ if (CHAOS_BOLT && Rand() < 50 && chaos_bolt_cd <= diff && GC_Timer <= diff && doCast(opponent, CHAOS_BOLT))
+ {
+ chaos_bolt_cd = 16000 - me->getLevel() * 100;
+ return;
+ }
+
+ if (GC_Timer <= diff && doCast(opponent, SHADOW_BOLT))
+ return;
+
+ }
+
+ uint8 Afflicted(Unit* target)
+ {
+ if (!target || target->isDead()) return 0;
+ bool aff = HasAuraName(target, UNSTABLE_AFFLICTION, me->GetGUID());
+ bool imm = HasAuraName(target, IMMOLATE, me->GetGUID());
+ if (imm) return 1;
+ if (aff) return 2;
+ return 0;
+ }
+
+ void CheckFear()
+ {
+ 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)) {}
+ }
+
+ //void SummonedCreatureDies(Creature* summon, Unit* /*killer*/)
+ //{
+ // if (summon == me->GetBotsPet())
+ // me->SetBotsPetDied();
+ //}
+
+ //void SummonedCreatureDespawn(Creature* summon)
+ //{
+ // if (summon == me->GetBotsPet())
+ // me->SetBotsPet(NULL);
+ //}
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ Rain_of_fire_cd = 0;
+ Haunt_cd = 0;
+ conflagarate_cd = 0;
+ chaos_bolt_cd = 0;
+ fear_cd = 0;
+
+ if (master)
+ {
+ setStats(CLASS_WARLOCK, me->getRace(), master->getLevel(), true);
+ //TODO: passives
+ ApplyClassPassives();
+ ApplyPassives(CLASS_WARLOCK);
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Rain_of_fire_cd > diff) Rain_of_fire_cd -= diff;
+ if (Haunt_cd > diff) Haunt_cd -= diff;
+ if (conflagarate_cd > diff) conflagarate_cd -= diff;
+ if (chaos_bolt_cd > diff) chaos_bolt_cd -= diff;
+ if (fear_cd > diff) fear_cd -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ CURSE_OF_THE_ELEMENTS = InitSpell(me, CURSE_OF_THE_ELEMENTS_1);
+ SHADOW_BOLT = InitSpell(me, SHADOW_BOLT_1);
+ IMMOLATE = InitSpell(me, IMMOLATE_1);
+ CONFLAGRATE = lvl >= 40 ? CONFLAGRATE_1 : 0;
+ /*Talent*/CHAOS_BOLT = lvl >= 60 ? InitSpell(me, CHAOS_BOLT_1) : 0;
+ RAIN_OF_FIRE = InitSpell(me, RAIN_OF_FIRE_1);
+ /*Talent*/HAUNT = lvl >= 60 ? InitSpell(me, HAUNT_1) : 0;
+ CORRUPTION = InitSpell(me, CORRUPTION_1);
+ /*Talent*/UNSTABLE_AFFLICTION = lvl >= 50 ? InitSpell(me, UNSTABLE_AFFLICTION_1) : 0;
+ FEAR = InitSpell(me, FEAR_1);
+ }
+
+ void ApplyClassPassives() {}
+
+ private:
+ uint32
+ /*Curses*/CURSE_OF_THE_ELEMENTS,
+/*Destruct*/SHADOW_BOLT, IMMOLATE, CONFLAGRATE, CHAOS_BOLT, RAIN_OF_FIRE,
+ /*Afflict*/HAUNT, CORRUPTION, UNSTABLE_AFFLICTION,
+ /*Other*/FEAR;
+ //Timers
+ uint32 Rain_of_fire_cd, Haunt_cd, conflagarate_cd, chaos_bolt_cd, fear_cd;
+
+ enum WarlockBaseSpells// all orignals
+ {
+ 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) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, 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);
+ GetInPosition(force, false);
+ SetBotCommandState(COMMAND_ATTACK);
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (IAmDead()) return;
+ if (me->getVictim())
+ DoMeleeAttackIfReady();
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ CheckAuras();
+ 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;
+
+ //TORMENT
+ if (Torment_cd <= diff && me->GetDistance(opponent) <= 5 && (!tank || tank == me || opponent->getVictim() == m_creatureOwner))
+ {
+ temptimer = GC_Timer;
+ if (doCast(opponent, TORMENT))
+ Torment_cd = 5000;
+ GC_Timer = temptimer;
+ }
+ }
+
+ 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()
+ {
+ Torment_cd = 0;
+
+ if (master && m_creatureOwner)
+ {
+ setStats(master->getLevel(), PET_TYPE_VOIDWALKER, true);
+ ApplyPassives(PET_TYPE_VOIDWALKER);
+ ApplyClassPassives();
+ SetBaseArmor(162 * master->getLevel());
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (Torment_cd > diff) Torment_cd -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ TORMENT = InitSpell(me, TORMENT_1);
+ }
+
+ void ApplyClassPassives() {}
+
+ private:
+ uint32
+ TORMENT;
+ //Timers
+ uint32 Torment_cd;
+
+ 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..e54ddce
--- /dev/null
+++ b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp
@@ -0,0 +1,1192 @@
+#include "bot_ai.h"
+#include "Group.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "SpellAuras.h"
+#include "WorldSession.h"
+/*
+Complete - Around 50-55%
+TODO: Solve 'DeathWish + Enrage', Thunder Clap, Piercing Howl, Challenging Shout, other tanking stuff
+*/
+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);
+ }
+
+ 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;
+ }
+
+ struct warrior_botAI : public bot_minion_ai
+ {
+ warrior_botAI(Creature* creature) : bot_minion_ai(creature) { }
+
+ bool doCast(Unit* victim, uint32 spellId, bool triggered = false)
+ {
+ if (checkBotCast(victim, spellId, CLASS_WARRIOR) != SPELL_CAST_OK)
+ return false;
+ return bot_ai::doCast(victim, spellId, triggered);
+ }
+
+ void UpdateAI(uint32 diff)
+ {
+ ReduceCD(diff);
+ if (rendTarget)
+ {
+ //Glyph of Rending: Increases Rend duration by 6 sec.
+ if (Unit* u = sObjectAccessor->FindUnit(rendTarget))
+ {
+ if (Aura* rend = u->GetAura(REND, me->GetGUID()))
+ {
+ uint32 dur = rend->GetDuration() + 6000;
+ rend->SetDuration(dur);
+ rend->SetMaxDuration(dur);
+ }
+ }
+ rendTarget = 0;
+ }
+ if (IAmDead()) return;
+ if (me->getVictim())
+ DoMeleeAttackIfReady();
+ else
+ Evade();
+
+ if (ragetimer2 <= diff)
+ {
+ if (me->isInCombat() && me->getLevel() >= 20)
+ {
+ if (getrage() < 990)
+ me->SetPower(POWER_RAGE, 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 (getrage() > uint32(10.f*rageLossMult))
+ me->SetPower(POWER_RAGE, rage - uint32(10.f*rageLossMult));//-1 rage per 1.5 sec
+ else
+ me->SetPower(POWER_RAGE, 0);//min
+ }
+ ragetimer = 1500;
+ //if (getrage() > 1000) me->SetPower(POWER_RAGE, 1000);
+ //if (getrage() < 10) me->SetPower(POWER_RAGE, 0);
+ }
+
+ if (wait == 0)
+ wait = GetWait();
+ else
+ return;
+ if (checkAurasTimer == 0)
+ {
+ SS = SWEEPING_STRIKES && me->HasAura(SWEEPING_STRIKES);
+ CheckAuras();
+ }
+ BreakCC(diff);
+ if (CCed(me)) return;
+
+ if (GetHealthPCT(me) < 67 && Potion_cd <= diff)
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, HEALINGPOTION))
+ {
+ Potion_cd = POTION_CD;
+ GC_Timer = temptimer;
+ }
+ }
+ CheckIntervene(diff);
+ if (!me->isInCombat())
+ DoNonCombatActions(diff);
+
+ if (!CheckAttackTarget(CLASS_WARRIOR))
+ {
+ if (tank != me && !me->isInCombat() && battleStance != true && master->getAttackers().empty() && stancetimer <= diff)
+ stanceChange(diff, 1);
+ return;
+ }
+
+ if (Rand() < 30 && battleShout_cd <= diff && GC_Timer <= diff && getrage() > 100 &&
+ !HasAuraName(master, BATTLESHOUT) &&
+ !HasAuraName(master, "Blessing of Might") &&
+ !HasAuraName(master, "Greater Blessing of Might") &&
+ master->IsWithinLOSInMap(me) &&
+ doCast(me, BATTLESHOUT))
+ battleShout_cd = BATTLESHOUT_CD;
+
+ if (Rand() < 20 && BLOODRAGE && bloodrage_cd <= diff && me->isInCombat() && getrage() < 500 &&
+ !me->HasAura(ENRAGED_REGENERATION))
+ {
+ temptimer = GC_Timer;
+ if (doCast(me, BLOODRAGE))
+ {
+ bloodrage_cd = BLOODRAGE_CD;
+ GC_Timer = temptimer;
+ }
+ }
+
+ Attack(diff);
+ }
+
+ void StartAttack(Unit* u, bool force = false)
+ {
+ if (GetBotCommandState() == (COMMAND_ATTACK) && !force) return;
+ Aggro(u);
+ SetBotCommandState(COMMAND_ATTACK);
+ GetInPosition(force, false);
+ }
+
+ void EnterCombat(Unit*) { }
+ void Aggro(Unit*) { }
+ void AttackStart(Unit*) { }
+ void KilledUnit(Unit*) { }
+ void EnterEvadeMode() { }
+ void MoveInLineOfSight(Unit*) { }
+ void JustDied(Unit*) { master->SetNpcBotDied(me->GetGUID()); }
+ void DoNonCombatActions(uint32 const /*diff*/)
+ {}
+
+ void modrage(int32 mod, bool set = false)
+ {
+ if (set && mod < 0)
+ return;
+ if (mod < 0 && rage < uint32(abs(mod)))
+ return;
+
+ if (set)
+ rage = mod*10;
+ else
+ rage += mod*10;
+ me->SetPower(POWER_RAGE, rage);
+ }
+
+ uint32 getrage()
+ {
+ rage = me->GetPower(POWER_RAGE);
+ return rage;
+ }
+
+ void BreakCC(uint32 diff)
+ {
+ if (me->HasAuraWithMechanic((1<<MECHANIC_FEAR)|(1<<MECHANIC_SAPPED)|(1<<MECHANIC_DISORIENTED)))
+ {
+ if (BERSERKERRAGE && !me->HasAura(ENRAGED_REGENERATION) &&
+ berserkerRage_cd <= diff && GC_Timer <= diff && doCast(me, BERSERKERRAGE))
+ {
+ berserkerRage_cd = BERSERKERRAGE_CD;
+ if (me->getLevel() >= 40)
+ modrage(20);
+ return;
+ }
+ }
+ bot_minion_ai::BreakCC(diff);
+ }
+
+ void Attack(uint32 diff)
+ {
+ opponent = me->getVictim();
+ if (opponent)
+ {
+ if (!IsCasting())
+ StartAttack(opponent, true);
+ }
+ else
+ return;
+ //Keep stance in raid if tank
+ if (master->GetBotTankGuid() == me->GetGUID() &&
+ defensiveStance != true && stancetimer <= diff/* &&
+ (!master->GetGroup() || master->GetGroup()->isRaidGroup())*/)
+ stanceChange(diff, 2);
+ //SelfHeal
+ if (ENRAGED_REGENERATION)
+ {
+ if (Rand() < 20 && GetHealthPCT(me) < 40 && getrage() > 150 && regen_cd <= diff &&
+ me->HasAuraWithMechanic(MECHANIC_ENRAGED))//no GC_Timer
+ {
+ temptimer = 0;
+ if (doCast(me, ENRAGED_REGENERATION))
+ {
+ regen_cd = ENRAGED_REGENERATION_CD;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ //maybe not needed part
+ if (me->HasAura(ENRAGED_REGENERATION))
+ {
+ if (HasAuraName(me, "Enrage"))
+ me->RemoveAurasWithMechanic(MECHANIC_ENRAGED);
+ if (HasAuraName(me, BLOODRAGE))
+ me->RemoveAurasDueToSpell(BLOODRAGE);
+ if (HasAuraName(me, DEATHWISH))
+ me->RemoveAurasDueToSpell(DEATHWISH);
+ if (HasAuraName(me, BERSERKERRAGE))
+ me->RemoveAurasDueToSpell(BERSERKERRAGE);
+ }
+ }//end SelfHeal
+
+ AttackerSet m_attackers = master->getAttackers();
+ AttackerSet b_attackers = me->getAttackers();
+ float dist = me->GetExactDist(opponent);
+ float meleedist = me->GetDistance(opponent);
+ //charge + warbringer
+ if (CHARGE && charge_cd <= diff && dist > 11 && dist < 25 && me->HasInArc(M_PI, opponent) &&
+ (me->getLevel() >= 50 ||
+ (!me->isInCombat() && (battleStance == true || stanceChange(diff, 1)))))
+ {
+ temptimer = GC_Timer;
+ if (me->getLevel() >= 50)
+ me->RemoveMovementImpairingAuras();
+ if (doCast(opponent, CHARGE, me->isInCombat()))
+ {
+ charge_cd = CHARGE_CD;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ //intercept
+ if (INTERCEPT && intercept_cd <= diff &&
+ getrage() > 100 && dist > 11 && dist < 25 && me->HasInArc(M_PI, opponent) &&
+ !CCed(opponent) && (berserkerStance == true || stanceChange(diff, 3)))
+ {
+ if (doCast(opponent, INTERCEPT))
+ {
+ intercept_cd = INTERCEPT_CD;
+ //modrage(-10);
+ return;
+ }
+ }
+ //FEAR
+ if (Rand() < 70 && INTIMIDATING_SHOUT && getrage() > 250 && intimidatingShout_cd <= diff && GC_Timer <= diff)
+ {
+ if (opponent->IsNonMeleeSpellCasted(false, false, true) && dist <= 8 &&
+ !(opponent->ToCreature() && opponent->ToCreature()->GetCreatureType() == CREATURE_TYPE_UNDEAD))
+ {
+ if (doCast(opponent, INTIMIDATING_SHOUT))
+ {
+ intimidatingShout_cd = INTIMIDATINGSHOUT_CD;
+ return;
+ }
+ }
+ Unit* fearTarget = NULL;
+ bool triggered = false;
+ uint8 tCount = 0;
+ //fear master's attackers
+ if (!m_attackers.empty() &&
+ ((master->getClass() != CLASS_DEATH_KNIGHT &&
+ master->getClass() != CLASS_WARRIOR &&
+ master->getClass() != 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, INTIMIDATING_SHOUT, triggered))
+ {
+ intimidatingShout_cd = INTIMIDATINGSHOUT_CD;
+ if (triggered)
+ modrage(-25);
+ 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 > 0) break;
+ }
+ }
+ if (tCount > 0 && !fearTarget)
+ {
+ fearTarget = opponent;
+ triggered = true;
+ }
+ if (tCount > 0 && doCast(fearTarget, INTIMIDATING_SHOUT, triggered))
+ {
+ intimidatingShout_cd = INTIMIDATINGSHOUT_CD;
+ if (triggered)
+ modrage(-25);
+ return;
+ }
+ }
+ }//end FEAR
+ //OVERPOWER
+ if (OVERPOWER && getrage() > 50 && meleedist <= 5 && GC_Timer <= diff && (battleStance == true || stancetimer <= diff))
+ {
+ bool blood = me->HasAura(TASTE_FOR_BLOOD_BUFF);
+ if ((((opponent->GetTypeId() == TYPEID_PLAYER && UNRELENTING_ASSAULT && blood && opponent->IsNonMeleeSpellCasted(false) && overpower_cd <= 3000) ||
+ (opponent->IsNonMeleeSpellCasted(false) && blood) ||
+ (overpower_cd <= diff && blood))))
+ {
+ if (battleStance == true || stanceChange(diff, 1))
+ {
+ if (doCast(opponent, OVERPOWER))
+ {
+ overpower_cd = 5000;
+ if (blood)
+ me->RemoveAura(TASTE_FOR_BLOOD_BUFF);
+ return;
+ }
+ }
+ }
+ }
+
+ if (MoveBehind(*opponent))
+ wait = 5;
+ //{ wait = 5; return; }
+ //HAMSTRING
+ if (HAMSTRING && Rand() < 50 && getrage() > 100 && GC_Timer <= diff && meleedist <= 5 && opponent->isMoving() &&
+ (battleStance == true || berserkerStance == true || stancetimer <= diff) &&
+ !opponent->HasAuraWithMechanic((1<<MECHANIC_SNARE)|(1<<MECHANIC_ROOT)))
+ {
+ if (battleStance == true || berserkerStance == true || stanceChange(diff, 5))
+ if (doCast(opponent, HAMSTRING))
+ return;
+ }
+ //UBERS
+ //Dont use RETA unless capable circumstances
+ if (Rand() < 20 && uber_cd <= diff && GC_Timer <= diff)//mod here
+ {
+ if (RETALIATION && b_attackers.size() > 4 &&
+ (battleStance == true || stanceChange(diff, 1)))
+ {
+ if (doCast(me, RETALIATION))
+ {
+ uber_cd = UBER_CD;
+ return;
+ }
+ }
+ //Dont use RECKL unless capable circumstances
+ if (RECKLESSNESS && tank != me &&
+ (m_attackers.size() > 3 || opponent->GetHealth() > me->GetHealth()*10) &&
+ (berserkerStance == true || stanceChange(diff, 3)))
+ {
+ if (doCast(me, RECKLESSNESS))
+ {
+ uber_cd = UBER_CD;
+ return;
+ }
+ }
+ }
+ //DEATHWISH
+ if (DEATHWISH && Rand() < 20 && deathwish_cd <= diff && GC_Timer <= diff &&
+ getrage() > 100 && opponent->GetHealth() > me->GetHealth()/2 &&
+ !me->HasAura(ENRAGED_REGENERATION))//mod here
+ {
+ if (doCast(me, DEATHWISH))
+ {
+ //modrage(-10);
+ deathwish_cd = DEATHWISH_CD;
+ return;
+ }
+ }
+ //TAUNT
+ Unit* u = opponent->getVictim();
+ if (TAUNT && taunt_cd <= diff && u && u != me && u != tank && dist <= 30 &&
+ (IsInBotParty(u) || tank == me) && !CCed(opponent) &&
+ (defensiveStance == true || (stancetimer <= diff && stanceChange(diff, 2))))//No GCD
+ {
+ temptimer = GC_Timer;
+ if (doCast(opponent, TAUNT, true))
+ {
+ taunt_cd = TAUNT_CD;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ //EXECUTE
+ if (EXECUTE && tank != me && Rand() < 70 && GC_Timer <= diff && getrage() > 150 && meleedist <= 5 && GetHealthPCT(opponent) < 20 &&
+ (battleStance == true || berserkerStance == true || stancetimer <= diff))
+ {
+ if ((battleStance == true || berserkerStance == true || stanceChange(diff, 5)) &&
+ doCast(opponent, EXECUTE))
+ {
+ //sudden death
+ if (me->getLevel() >= 50 && getrage() <= 400)
+ modrage(10, true);
+ else if (getrage() > 300)
+ modrage(-30);
+ else
+ modrage(0, true);
+ return;
+ }
+ }
+ //SUNDER
+ if (SUNDER && Rand() < 35 && meleedist <= 5 && tank == me &&
+ opponent->GetHealth() > me->GetMaxHealth() &&
+ GC_Timer <= diff && getrage() > 150 && (sunder_cd <= diff || getrage() > 500))
+ {
+ Aura* sunder = opponent->GetAura(SUNDER, me->GetGUID());
+ if ((!sunder || sunder->GetStackAmount() < 5 || sunder->GetDuration() < 15000) &&
+ doCast(opponent, SUNDER))
+ {
+ sunder_cd = SUNDER_CD;
+ GC_Timer = 1000;
+ return;
+ }
+ }
+ //SS
+ if (SWEEPING_STRIKES && sweeping_strikes_cd <= diff && tank != me && Rand() < 20 &&
+ (battleStance == true || berserkerStance == true || stancetimer <= diff) &&
+ (b_attackers.size() > 1 || FindSplashTarget(7, opponent)))
+ {
+ temptimer = GC_Timer;
+ if ((battleStance == true || berserkerStance == true || stanceChange(diff, 5)) &&
+ doCast(me, SWEEPING_STRIKES, true))//no rage use as with glyph
+ {
+ SS = true;
+ sweeping_strikes_cd = SWEEPING_STRIKES_CD;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ //WHIRLWIND
+ if (WHIRLWIND && Rand() < 50 && whirlwind_cd <= diff && GC_Timer <= diff && getrage() >= 250 &&
+ (FindSplashTarget(7, opponent) || (getrage() > 500 && dist <= 7)) &&
+ (berserkerStance == true || stancetimer <= diff))
+ {
+ if ((berserkerStance == true || stanceChange(diff, 3)) &&
+ doCast(me, WHIRLWIND))
+ {
+ whirlwind_cd = WHIRLWIND_CD;
+ return;
+ }
+ }
+ //BLADESTORM
+ if (BLADESTORM && bladestorm_cd <= diff && GC_Timer <= diff &&
+ getrage() >= 250 && (Rand() < 20 || me->HasAura(RECKLESSNESS)) &&
+ (b_attackers.size() > 1 || opponent->GetHealth() > me->GetMaxHealth()))
+ {
+ if (doCast(me, BLADESTORM))
+ {
+ bladestorm_cd = BLADESTORM_CD;
+ return;
+ }
+ }
+ //Mortal Strike
+ if (MORTALSTRIKE && getrage() > 300 && meleedist <= 5 && mortalStrike_cd <= diff && GC_Timer <= diff)
+ {
+ if (doCast(opponent, MORTALSTRIKE/*, true*/))
+ {
+ mortalStrike_cd = MORTALSTRIKE_CD;
+ slam_cd = 0;//reset here
+ }
+ }
+ //Slam
+ bool triggered = mortalStrike_cd == 7000;
+ if (SLAM && Rand() < (30 + triggered*60) && slam_cd <= diff && getrage() > 150 && meleedist <= 5)
+ {
+ if (doCast(opponent, SLAM, triggered))
+ {
+ slam_cd = 4500;//4.5sec (must be > MORTALSTRIKE_CD/2)
+ if (triggered)
+ modrage(-15);
+ return;
+ }
+ }
+ //PUMMEL
+ if (PUMMEL && Rand() < 80 && pummel_cd <= diff && getrage() > 100 && meleedist <= 5 &&
+ opponent->IsNonMeleeSpellCasted(false) &&
+ (berserkerStance == true || stancetimer <= diff))
+ {
+ temptimer = GC_Timer;
+ if ((berserkerStance == true || stanceChange(diff, 3)) &&
+ doCast(opponent, PUMMEL))
+ {
+ pummel_cd = PUMMEL_CD;
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ //REND
+ if (REND && Rand() < 30 && getrage() > 100 && GC_Timer <= diff && meleedist <= 5 &&
+ opponent->GetHealth() > me->GetHealth()/2 &&
+ (battleStance == true || defensiveStance == true || stancetimer <= diff) &&
+ !opponent->HasAura(REND, me->GetGUID()))
+ {
+ if ((battleStance == true || defensiveStance == true || stanceChange(diff, 1)) &&
+ doCast(opponent, REND))
+ {
+ rendTarget = opponent->GetGUID();
+ return;
+ }
+ }
+ //Cleave
+ if (Rand() < 30 && CLEAVE && cleave_cd <= diff && me->getLevel() >= 20 && getrage() > 200 && meleedist <= 5)//noGC_Timer
+ {
+ temptimer = GC_Timer;
+ u = FindSplashTarget(5);
+ if (u && doCast(opponent, CLEAVE))
+ {
+ rage -= 200;//not visible
+ cleave_cd = me->getAttackTimer(BASE_ATTACK);//once per swing, prevents rage loss
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ else {}//HEROIC STRIKE placeholder
+ //DISARM DEPRECATED
+ /*if (disarm_cd <= diff && meleedist < 5 &&
+ (opponent->getVictim()->GetGUID() == master->GetGUID() ||
+ opponent->getVictim()->GetGUID() == m_creature->GetGUID()) &&
+ getrage() > 15 &&
+ !HasAuraName(opponent, GetSpellName(DISARM)) &&
+ GC_Timer <= diff)
+ {
+ if (opponent->getClass() == CLASS_ROGUE ||
+ opponent->getClass() == CLASS_WARRIOR ||
+ opponent->getClass() == CLASS_SHAMAN ||
+ opponent->getClass() == CLASS_PALADIN)
+ {
+ if (defensiveStance == true)
+ {
+ doCast(opponent, DISARM, true);
+ //rage -= 100;
+ disarm_cd = DISARM_CD;
+ }
+ else stanceChange(diff, 2);
+ }
+ }*/
+ }//end Attack
+
+ void CheckIntervene(uint32 diff)
+ {
+ if (INTERVENE && intervene_cd <= diff && getrage() > 100 && Rand() < ((tank == me) ? 80 : 30) &&
+ (defensiveStance == true || (stancetimer <= diff && !SS)))
+ {
+ if (!master->isInCombat() && master->getAttackers().empty() && master->isMoving())
+ {
+ float mydist = me->GetExactDist(master);
+ if (mydist < 25 && mydist > 18 && (defensiveStance == true || stanceChange(diff, 2)))
+ {
+ temptimer = GC_Timer;
+ if (doCast(master, INTERVENE))
+ {
+ //modrage(-10);
+ intervene_cd = INTERVENE_CD;
+ GC_Timer = temptimer;
+ Follow(true);
+ return;
+ }
+ }
+ }
+ //sLog->outBasic("%s: Intervene check.", me->GetName().c_str());
+ 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 == true || stanceChange(diff, 2))) return;
+ temptimer = GC_Timer;
+ if (doCast(master, INTERVENE))
+ {
+ //modrage(-10);
+ intervene_cd = INTERVENE_CD;
+ 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->FindMap() != me->GetMap()) continue;
+ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue;
+ //sLog->outBasic("checking player %s", tPlayer->GetName().c_str());
+ if (tPlayer->HaveBot())
+ Bots = true;
+ if (tPlayer->isDead() || GetHealthPCT(tPlayer) > 90 || tank == tPlayer) continue;
+ //sLog->outBasic("alive and health < 80%");
+ if (tPlayer->getAttackers().size() < me->getAttackers().size()) continue;
+ //sLog->outBasic("attackers checked");
+ dist = me->GetExactDist(tPlayer);
+ if (dist > 25 || dist < 10) continue;
+ //sLog->outBasic("and whithin reach");
+ if ((defensiveStance == true || stanceChange(diff, 2)))
+ {
+ //sLog->outBasic("defensive stance acuired, attempt cast");
+ temptimer = GC_Timer;
+ if (doCast(tPlayer, INTERVENE))
+ {
+ //sLog->outBasic("cast succeed");
+ //modrage(-10);
+ intervene_cd = INTERVENE_CD;
+ 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->FindMap() != me->GetMap()) continue;
+ if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = tPlayer->GetBotMap(i)->_Cre();
+ if (!bot || bot == me || bot->isDead()) continue;
+ if (GetHealthPCT(bot) > 90 || tank == bot) continue;
+ dist = me->GetExactDist(bot);
+ if (dist > 25 || dist < 10) continue;
+ if (bot->getAttackers().size() <= me->getAttackers().size()) continue;
+ if ((defensiveStance == true || stanceChange(diff, 2)))
+ {
+ //sLog->outBasic("defensive stance acuired, attempt cast");
+ temptimer = GC_Timer;
+ if (doCast(bot, INTERVENE))
+ {
+ //sLog->outBasic("cast succeed");
+ //modrage(-10);
+ intervene_cd = INTERVENE_CD/2; //half for bot
+ GC_Timer = temptimer;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void SpellHitTarget(Unit* target, SpellInfo const* spell)
+ {
+ switch (spell->Id)
+ {
+ case OVERPOWER_1:
+ if (target->GetTypeId() != TYPEID_UNIT || //only creatures lol
+ !UNRELENTING_ASSAULT)
+ return;
+ if (target->HasUnitState(UNIT_STATE_CASTING))
+ {
+ uint32 spell = 0;
+ if (me->HasAura(UNRELENTING_ASSAULT2))
+ spell = UNRELENTING_ASSAULT_SPELL2;
+ else if (me->HasAura(UNRELENTING_ASSAULT1))
+ spell = UNRELENTING_ASSAULT_SPELL1;
+ if (!spell)
+ return;
+ target->CastSpell(target, spell, true);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ bool stanceChange(uint32 diff, uint8 stance)
+ {
+ if (!(stancetimer <= diff) || !stance)
+ return false;
+
+ if (stance == 5)
+ {
+ switch (urand(0,1))
+ {
+ case 0:
+ stance = 1;
+ break;
+ case 1:
+ stance = me->getLevel() < 30 ? 1 : 3;
+ break;
+ }
+ }
+ if (stance == 2 && (me->getLevel() < 10 || SS))
+ return false;
+ if (stance == 3 && me->getLevel() < 30)
+ return false;
+
+ temptimer = GC_Timer;
+ uint32 temprage = 0;
+ uint32 myrage = me->GetPower(POWER_RAGE);
+ if (me->getLevel() >= 15)
+ temprage = myrage > 250 ? 250 : myrage;
+ else if (me->getLevel() >= 10)
+ temprage = myrage > 100 ? 100 : myrage;
+ switch (stance)
+ {
+ case 1:
+ if (doCast(me, BATTLESTANCE))
+ {
+ if (me->HasAura(BATTLESTANCE))
+ {
+ battleStance = true;
+ defensiveStance = false;
+ berserkerStance = false;
+ me->SetPower(POWER_RAGE, temprage);
+ stancetimer = 2100 - me->getLevel()*20;//2100-1600 on 80
+ GC_Timer = temptimer;
+ return true;
+ }
+ }
+ break;
+ case 2:
+ if (doCast(me, DEFENSIVESTANCE))
+ {
+ if (me->HasAura(DEFENSIVESTANCE))
+ {
+ defensiveStance = true;
+ battleStance = false;
+ berserkerStance = false;
+ me->SetPower(POWER_RAGE, temprage);
+ stancetimer = 2100 - me->getLevel()*20;//2100-1600 on 80
+ GC_Timer = temptimer;
+ return true;
+ }
+ }
+ break;
+ case 3:
+ if (doCast(me, BERSERKERSTANCE))
+ {
+ if (me->HasAura(BERSERKERSTANCE))
+ {
+ berserkerStance = true;
+ battleStance = false;
+ defensiveStance = false;
+ me->SetPower(POWER_RAGE, temprage);
+ stancetimer = 2100 - me->getLevel()*20;//2100-1600 on 80
+ GC_Timer = temptimer;
+ return true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ GC_Timer = temptimer;
+ return false;
+ }
+
+ 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;
+ //Improved Overpower: 50% additional critical chance for Overpower
+ if (lvl >= 20 && spellId == OVERPOWER)
+ aftercrit += 50.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 == REND)
+ pctbonus += 0.2f;
+ //Improved Whirlwind: 20% bonus damage for Whirlwind
+ if (lvl >= 40 && spellId == WHIRLWIND)
+ pctbonus += 0.2f;
+ //Glyph of Mortal Strike: 10% bonus damage for Mortal Strike
+ if (lvl >= 40 && spellId == MORTALSTRIKE)
+ pctbonus += 0.1f;
+ //Unrelenting Assault (part 2): 20% bonus damage for Overpower and Revenge
+ if (lvl >= 45 && (spellId == OVERPOWER/* || spellId == REVENGE*/))
+ pctbonus += 0.2f;
+ //Improved Mortal Strike: 10% bonus damage for Mortal Strike
+ if (lvl >= 45 && spellId == MORTALSTRIKE)
+ pctbonus += 0.1f;
+ //Undending Fury: 10% bonus damage for Whirlwind, Slam and Bloodthirst
+ if (lvl >= 55 && (spellId == WHIRLWIND || spellId == SLAM /*|| spellId == BLOODTHIRST*/))
+ pctbonus += 0.1f;
+
+ damage = int32(fdamage * (1.0f + pctbonus));
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell)
+ {
+ OnSpellHit(caster, spell);
+ }
+
+ void DamageTaken(Unit* u, uint32& /*damage*/)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void OwnerAttackedBy(Unit* u)
+ {
+ OnOwnerDamagedBy(u);
+ }
+
+ void Reset()
+ {
+ slam_cd = 0;
+ regen_cd = 20000;
+ sweeping_strikes_cd = 0;
+ charge_cd = 0;
+ deathwish_cd = 0;
+ mortalStrike_cd = 0;
+ overpower_cd = 0;
+ uber_cd = 0;
+ berserkerRage_cd = 0;
+ battleShout_cd = 0;
+ intercept_cd = 0;
+ intimidatingShout_cd = 0;
+ pummel_cd = 0;
+ whirlwind_cd = 5000;
+ cleave_cd = 0;
+ bladestorm_cd = 10000;
+ bloodrage_cd = 0;
+ intervene_cd = 0;
+ taunt_cd = 0;
+ sunder_cd = 0;
+ stancetimer = 0;
+ ragetimer = 1500;
+ ragetimer2 = 3000;
+
+ rendTarget = 0;
+
+ 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;
+
+ if (master)
+ {
+ setStats(CLASS_WARRIOR, me->getRace(), master->getLevel(), true);
+ ApplyClassPassives();
+ ApplyPassives(CLASS_WARRIOR);
+ //mob generates abnormal amounts rage so increase/reduce rate with level(from 188% down to 30% at level 80)//not seems to work
+ //for (int8 i = 0; i < 3; ++i)
+ // me->ApplyEffectModifiers(sSpellMgr->GetSpellInfo(29623), i, float(90 - master->getLevel()*2));
+ }
+ }
+
+ void ReduceCD(uint32 diff)
+ {
+ CommonTimers(diff);
+ if (regen_cd > diff) regen_cd -= diff;
+ if (slam_cd > diff) slam_cd -= diff;
+ if (battleShout_cd > diff) battleShout_cd -= diff;
+ if (sweeping_strikes_cd > diff) sweeping_strikes_cd -= diff;
+ if (deathwish_cd > diff) deathwish_cd -= diff;
+ if (mortalStrike_cd > diff) mortalStrike_cd -= diff;
+ if (overpower_cd > diff) overpower_cd -= diff;
+ if (uber_cd > diff) uber_cd -= diff;
+ if (berserkerRage_cd > diff) berserkerRage_cd -= diff;
+ if (charge_cd > diff) charge_cd -= diff;
+ if (intercept_cd > diff) intercept_cd -= diff;
+ if (intimidatingShout_cd > diff) intimidatingShout_cd -= diff;
+ if (pummel_cd > diff) pummel_cd -= diff;
+ if (whirlwind_cd > diff) whirlwind_cd -= diff;
+ if (bladestorm_cd > diff) bladestorm_cd -= diff;
+ if (cleave_cd > diff) cleave_cd -= diff;
+ if (bloodrage_cd > diff) bloodrage_cd -= diff;
+ if (intervene_cd > diff) intervene_cd -= diff;
+ if (taunt_cd > diff) taunt_cd -= diff;
+ if (sunder_cd > diff) sunder_cd -= diff;
+
+ if (stancetimer > diff) stancetimer -= diff;
+ if (ragetimer > diff) ragetimer -= diff;
+ if (ragetimer2 > diff) ragetimer2 -= diff;
+ }
+
+ bool CanRespawn()
+ {return false;}
+
+ void InitSpells()
+ {
+ uint8 lvl = me->getLevel();
+ //CHALLENGING_SHOUT = InitSpell(me, CHALLENGING_SHOUT_1);
+ INTIMIDATING_SHOUT = InitSpell(me, INTIMIDATING_SHOUT_1);
+ ENRAGED_REGENERATION = InitSpell(me, ENRAGED_REGENERATION_1);
+ CHARGE = InitSpell(me, CHARGE_1);
+ OVERPOWER = InitSpell(me, OVERPOWER_1);
+ /*Quest*/TAUNT = lvl >= 10 ? TAUNT_1 : 0;
+ //DISARM = InitSpell(DISARM_1);
+ BLOODRAGE = InitSpell(me, BLOODRAGE_1);
+ BERSERKERRAGE = InitSpell(me, BERSERKERRAGE_1);
+ INTERCEPT = InitSpell(me, INTERCEPT_1);
+ CLEAVE = InitSpell(me, CLEAVE_1);
+ HAMSTRING = InitSpell(me, HAMSTRING_1);
+ INTERVENE = InitSpell(me, INTERVENE_1);
+ WHIRLWIND = InitSpell(me, WHIRLWIND_1);
+ /*Talent*/BLADESTORM = lvl >= 60 ? BLADESTORM_1 : 0;
+ BATTLESHOUT = InitSpell(me, BATTLESHOUT_1);
+ REND = InitSpell(me, REND_1);
+ EXECUTE = InitSpell(me, EXECUTE_1);
+ PUMMEL = InitSpell(me, PUMMEL_1);
+ /*Talent*/MORTALSTRIKE = lvl >= 40 ? InitSpell(me, MORTALSTRIKE_1) : 0;
+ SLAM = InitSpell(me, SLAM_1);
+ /*Quest*/SUNDER = lvl >= 10 ? InitSpell(me, SUNDER_1) : 0;
+ /*Talent*/SWEEPING_STRIKES = lvl >= 30 ? SWEEPING_STRIKES_1 : 0;
+ BATTLESTANCE = BATTLESTANCE_1;
+ /*Quest*/DEFENSIVESTANCE = lvl >= 10 ? DEFENSIVESTANCE_1 : 0;
+ /*Quest*/BERSERKERSTANCE = lvl >= 30 ? BERSERKERSTANCE_1 : 0;
+ RECKLESSNESS = InitSpell(me, RECKLESSNESS_1);
+ RETALIATION = InitSpell(me, RETALIATION_1);
+ /*Talent*/DEATHWISH = lvl >= 30 ? DEATHWISH_1 : 0;
+ }
+
+ void ApplyClassPassives()
+ {
+ uint8 level = master->getLevel();
+ if (level >= 70)
+ RefreshAura(WC5); //10%
+ else if (level >= 68)
+ RefreshAura(WC4); //8%
+ else if (level >= 66)
+ RefreshAura(WC3); //6%
+ else if (level >= 64)
+ RefreshAura(WC2); //4%
+ else if (level >= 62)
+ RefreshAura(WC1); //2%
+ if (level >= 39)
+ RefreshAura(FLURRY5); //30%
+ else if (level >= 38)
+ RefreshAura(FLURRY4); //24%
+ else if (level >= 37)
+ RefreshAura(FLURRY3); //18%
+ else if (level >= 36)
+ RefreshAura(FLURRY2); //12%
+ else if (level >= 35)
+ RefreshAura(FLURRY1); //6%
+ if (level >= 60)
+ RefreshAura(SWORD_SPEC5,2);//twice
+ else if (level >= 50)
+ RefreshAura(SWORD_SPEC5);//once
+ else if (level >= 45)
+ RefreshAura(SWORD_SPEC4);//once
+ else if (level >= 40)
+ RefreshAura(SWORD_SPEC3);//once
+ else if (level >= 35)
+ RefreshAura(SWORD_SPEC2);//once
+ else if (level >= 30)
+ RefreshAura(SWORD_SPEC1);//once
+ if (level >= 60)
+ RefreshAura(RAMPAGE);
+ if (level >= 55)
+ RefreshAura(TRAUMA2);//30%
+ else if (level >= 35)
+ RefreshAura(TRAUMA1);//15%
+ if (level >= 50)
+ {
+ RefreshAura(UNRELENTING_ASSAULT2);
+ UNRELENTING_ASSAULT = true;
+ }
+ else if (level >= 45)
+ {
+ RefreshAura(UNRELENTING_ASSAULT1);
+ UNRELENTING_ASSAULT = true;
+ }
+ if (level >= 45)
+ RefreshAura(BLOOD_FRENZY);
+ if (level >= 40)
+ RefreshAura(SECOND_WIND);
+ if (level >= 40)
+ RefreshAura(TOUGHNESS,2);//-60%
+ else if (level >= 15)
+ RefreshAura(TOUGHNESS);//-30%
+ if (level >= 40)
+ RefreshAura(IMP_HAMSTRING,2);//30%
+ else if (level >= 35)
+ RefreshAura(IMP_HAMSTRING);//15%
+ if (level >= 30)
+ RefreshAura(TASTE_FOR_BLOOD3);//100%
+ else if (level >= 28)
+ RefreshAura(TASTE_FOR_BLOOD2);//66%
+ else if (level >= 25)
+ RefreshAura(TASTE_FOR_BLOOD1);//33%
+ if (level >= 30)
+ RefreshAura(BLOOD_CRAZE3);
+ else if (level >= 25)
+ RefreshAura(BLOOD_CRAZE2);
+ else if (level >= 20)
+ RefreshAura(BLOOD_CRAZE1);
+ //BloodRage Absorb
+ if (level >= 60)
+ RefreshAura(WARRIOR_T10_4P);
+ }
+
+ private:
+ uint32
+ /*Shouts*/INTIMIDATING_SHOUT, BATTLESHOUT, CHALLENGING_SHOUT,
+ /*Charges*/CHARGE, INTERCEPT, INTERVENE,
+ /*Damage*/OVERPOWER, CLEAVE, REND, EXECUTE, WHIRLWIND, BLADESTORM, MORTALSTRIKE, SLAM,
+ /*Stances*/BATTLESTANCE, DEFENSIVESTANCE, BERSERKERSTANCE,
+ /*Ubers*/RECKLESSNESS, RETALIATION, DEATHWISH,
+ /*Others*/TAUNT, DISARM, BLOODRAGE, ENRAGED_REGENERATION, BERSERKERRAGE, HAMSTRING, PUMMEL, SUNDER, SWEEPING_STRIKES;
+
+ //CDs/Timers/misc
+/*shts*/uint32 battleShout_cd, intimidatingShout_cd;
+/*chrg*/uint32 charge_cd, intercept_cd, intervene_cd;;
+ /*Dmg*/uint32 mortalStrike_cd, overpower_cd, slam_cd, whirlwind_cd, cleave_cd, bladestorm_cd;
+/*else*/uint32 regen_cd, sweeping_strikes_cd, deathwish_cd, uber_cd, berserkerRage_cd, pummel_cd,
+ bloodrage_cd, taunt_cd, sunder_cd;
+/*tmrs*/uint32 stancetimer, ragetimer, ragetimer2;
+/*misc*/uint64 rendTarget;
+/*misc*/uint32 rage;
+/*misc*/float rageIncomeMult, rageLossMult;
+/*Chck*/bool battleStance, defensiveStance, berserkerStance, SS, UNRELENTING_ASSAULT;
+
+ enum WarriorBaseSpells
+ {
+ //CHALLENGING_SHOUT_1 = 1161,
+ INTIMIDATING_SHOUT_1 = 5246,
+ ENRAGED_REGENERATION_1 = 55694,
+ CHARGE_1 = 11578,
+ OVERPOWER_1 = 7384,
+ TAUNT_1 = 355,
+ //DISARM_1 = 676,
+ BLOODRAGE_1 = 2687,
+ BERSERKERRAGE_1 = 18499,
+ INTERCEPT_1 = 20252,
+ CLEAVE_1 = 845,//59992,
+ HAMSTRING_1 = 1715,
+ INTERVENE_1 = 3411,
+ WHIRLWIND_1 = 1680,
+ BLADESTORM_1 = 46924,//67541,
+ BATTLESHOUT_1 = 6673,
+ REND_1 = 772,
+ EXECUTE_1 = 5308,
+ PUMMEL_1 = 6552,
+ MORTALSTRIKE_1 = 12294,
+ SLAM_1 = 1464,
+ SUNDER_1 = 7386,//16145,
+ SWEEPING_STRIKES_1 = 12328,
+ BATTLESTANCE_1 = 2457,//7165, //2457, original warrior one
+ DEFENSIVESTANCE_1 = 71,//71, original warrior one
+ BERSERKERSTANCE_1 = 2458,//7366, //2458, original warrior spell
+ RECKLESSNESS_1 = 13847,//1719, original warrior spell
+ RETALIATION_1 = 22857,//20230, original warrior spell
+ DEATHWISH_1 = 12292,
+ };
+ 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
+ //other
+ WARRIOR_T10_4P = 70844,
+ };
+ enum WarriorSpecial
+ {
+ TASTE_FOR_BLOOD_BUFF = 60503,
+ UNRELENTING_ASSAULT_SPELL1 = 64849,
+ UNRELENTING_ASSAULT_SPELL2 = 64850,
+ };
+ enum WarriorCooldowns
+ {
+ ENRAGED_REGENERATION_CD = 90000, //1.5 min
+ SWEEPING_STRIKES_CD = 30000,
+ CHARGE_CD = 15000,
+ DEATHWISH_CD = 90000, //1.5 min
+ MORTALSTRIKE_CD = 7000,
+ UBER_CD = 150000, //RETALIATION_RECKLESSNESS_SHIELDWALL 2.5 min NEED SEPARATE
+ BERSERKERRAGE_CD = 25000,
+ INTERCEPT_CD = 15000,
+ INTIMIDATINGSHOUT_CD = 45000,
+ PUMMEL_CD = 10000,
+ WHIRLWIND_CD = 8000,
+ BLADESTORM_CD = 60000,
+ BLOODRAGE_CD = 40000,
+ //DISARM_CD = 40000,
+ INTERVENE_CD = 25000,
+ BATTLESHOUT_CD = 25000,
+ //SPELLREFLECTION_CD = 8000,
+ TAUNT_CD = 8000,
+ SUNDER_CD = 7000,
+ };
+ };
+};
+
+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..bc106d6
--- /dev/null
+++ b/src/server/game/AI/NpcBots/botcommands.cpp
@@ -0,0 +1,851 @@
+/*
+Name: script_bot_commands
+%Complete: ???
+Comment: Playerbot and Npcbot related commands
+Category: commandscripts/custom/
+*/
+
+#include "bot_ai.h"
+#include "bp_ai.h"
+#include "bp_mgr.h"
+#include "Chat.h"
+#include "Config.h"
+#include "Group.h"
+#include "Language.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+
+class script_bot_commands : public CommandScript
+{
+public:
+ script_bot_commands() : CommandScript("script_bot_commands") { }
+
+ ChatCommand* GetCommands() const
+ {
+ static ChatCommand npcbotCommandTable[] =
+ {
+ { "info", SEC_PLAYER, false, &HandleNpcBotInfoCommand, "", NULL },
+ { "add", SEC_PLAYER, false, &HandleNpcBotAddCommand, "", NULL },
+ { "revive", SEC_MODERATOR, false, &HandleNpcBotReviveCommand, "", NULL },
+ { "remove", SEC_PLAYER, false, &HandleNpcBotRemoveCommand, "", NULL },
+ { "reset", SEC_PLAYER, false, &HandleNpcBotResetCommand, "", NULL },
+ { "command", SEC_PLAYER, false, &HandleNpcBotCommandCommand, "", NULL },
+ { "distance", SEC_PLAYER, false, &HandleNpcBotDistanceCommand, "", NULL },
+ //{ "reloadequips", SEC_ADMINISTRATOR, false, &HandleReloadEquipsCommand, "", NULL },
+ { NULL, 0, false, NULL, "", NULL }
+ };
+ static ChatCommand commandTable[] =
+ {
+ { "bot", SEC_ADMINISTRATOR, false, &HandlePlayerbotCommand, "", NULL },
+ { "maintank", SEC_PLAYER, false, &HandleMainTankCommand, "", NULL },
+ { "mt", SEC_PLAYER, false, &HandleMainTankCommand, "", NULL },
+ { "npcbot", SEC_PLAYER, false, NULL, "", npcbotCommandTable },
+ { NULL, 0, false, NULL, "", NULL }
+ };
+ return commandTable;
+ }
+
+ //static bool HandleReloadEquipsCommand(ChatHandler* handler, const char* /*args*/)
+ //{
+ // sLog->outInfo(LOG_FILTER_GENERAL, "Re-Loading Creature Equips...");
+ // sObjectMgr->LoadEquipmentTemplates();
+ // handler->SendGlobalGMSysMessage("DB table `creature_equip_template` (creature equipment) reloaded.");
+ // return true;
+ //}
+
+ static bool HandlePlayerbotCommand(ChatHandler* handler, const char* args)
+ {
+ if (!handler->GetSession())
+ {
+ handler->PSendSysMessage("You may only add bots from an active session");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ bool allowPBots = ConfigMgr::GetBoolDefault("Bot.EnablePlayerBots", false);
+ if (allowPBots == false)
+ {
+ handler->PSendSysMessage("Playerbot system is disabled");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (!*args)
+ {
+ handler->PSendSysMessage("usage: add PLAYERNAME or remove PLAYERNAME");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ Player* player = handler->GetSession()->GetPlayer();
+
+ char* cmd = strtok ((char*)args, " ");
+ if (!cmd)
+ {
+ handler->PSendSysMessage("usage: add PLAYERNAME or remove PLAYERNAME");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ std::string cmdStr = cmd;
+
+ //if (cmdStr.compare("tele") == 0 || cmdStr.compare("teleport") == 0 || cmdStr.compare("summ") == 0 || cmdStr.compare("summon") == 0)
+ //{
+ // if (handler->GetSession()->m_playerBots.empty())
+ // {
+ // handler->PSendSysMessage("You Have No Playerbots!");
+ // handler->SetSentErrorMessage(true);
+ // return false;
+ // }
+ // PlayerbotChatHandler ch(player);
+ // for (PlayerBotMap::const_iterator itr = handler->GetSession()->GetPlayerBotsBegin(); itr != handler->GetSession()->GetPlayerBotsEnd(); ++itr)
+ // {
+ // Player* botPlayer = itr->second;
+ // if (!botPlayer)
+ // continue;
+ // ch.teleport(*botPlayer);
+ // //botPlayer->TeleportTo(*player);
+ // }
+ // return true;
+ //}
+ char* charname = strtok (NULL, " ");
+ if (!charname)
+ {
+ handler->PSendSysMessage("usage: add PLAYERNAME or remove PLAYERNAME");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ std::string charnameStr = charname;
+
+ PlayerbotMgr* mgr = player->GetPlayerbotMgr();
+ if (!mgr)
+ {
+ mgr = new PlayerbotMgr(player);
+ player->SetPlayerbotMgr(mgr);
+ }
+
+ uint64 guid;
+
+ if (charnameStr.compare("all") != 0)
+ {
+ if (!normalizePlayerName(charnameStr))
+ return false;
+
+ guid = sObjectMgr->GetPlayerGUIDByName(charnameStr.c_str());
+ if (guid == 0 || (guid == handler->GetSession()->GetPlayer()->GetGUID()))
+ {
+ handler->PSendSysMessage(LANG_PLAYER_NOT_FOUND);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ uint32 accountId = sObjectMgr->GetPlayerAccountIdByGUID(guid);
+ if (accountId != handler->GetSession()->GetAccountId())
+ {
+ handler->PSendSysMessage("You may only add bots from the same account.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ }
+
+ if (cmdStr.compare("add") == 0 || cmdStr.compare("login") == 0)
+ {
+ if (charnameStr.compare("all") == 0)
+ {
+ std::string plName;
+ std::list<std::string>* names = new std::list<std::string>;
+ uint32 accId = player->GetSession()->GetAccountId();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYERBOTS);
+ stmt->setUInt32(0, accId);
+ stmt->setUInt32(1, player->GetGUIDLow());
+ PreparedQueryResult results = CharacterDatabase.Query(stmt);
+ //CharacterDatabase.PQuery("SELECT name FROM characters WHERE account = '%u' AND guid != '%u'", accId, player->GetGUIDLow());
+ if (results)
+ {
+ do
+ {
+ Field* fields = results->Fetch();
+ plName = fields[0].GetString();
+ if (ObjectAccessor::FindPlayerByName(plName))
+ continue;
+ names->insert(names->end(), plName);
+ } while(results->NextRow());
+ }
+ std::list<std::string>::iterator iter,next;
+ for (iter = names->begin(); iter != names->end(); iter++)
+ {
+ std::stringstream arg;
+ arg << "add " << (*iter).c_str();
+ HandlePlayerbotCommand(handler, arg.str().c_str());
+ }
+ handler->PSendSysMessage("Bots added successfully.");
+ delete names;
+ return true;
+ }
+ else
+ {
+ guid = sObjectMgr->GetPlayerGUIDByName(charnameStr.c_str());
+ if (mgr->GetPlayerBot(guid) != NULL)
+ {
+ handler->PSendSysMessage("Bot already exists in world.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ mgr->AddPlayerBot(guid);
+ }
+ }
+ else if (cmdStr.compare("remove") == 0 || cmdStr.compare("logout") == 0)
+ {
+ if (charnameStr.compare("all") == 0)
+ {
+ std::list<std::string>* names = new std::list<std::string>;
+ for (PlayerBotMap::const_iterator iter = mgr->GetPlayerBotsBegin(); iter != mgr->GetPlayerBotsEnd(); ++iter)
+ {
+ names->push_back(iter->second->GetName());
+ }
+ std::list<std::string>::iterator iter, next;
+ for (iter = names->begin(); iter != names->end(); iter++)
+ {
+ std::stringstream arg;
+ arg << "remove " << (*iter).c_str();
+ HandlePlayerbotCommand(handler, arg.str().c_str());
+ }
+ delete names;
+ return true;
+ }
+ else
+ {
+ guid = sObjectMgr->GetPlayerGUIDByName(charnameStr.c_str());
+ if (!mgr->GetPlayerBot(guid))
+ {
+ handler->PSendSysMessage("Bot can not be removed because bot does not exist in world.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ mgr->LogoutPlayerBot(guid);
+ handler->PSendSysMessage("Bot removed successfully.");
+ return true;
+ }
+ }
+ else if (cmdStr == "co" || cmdStr == "combatorder")
+ {
+ Unit* target = NULL;
+ char* orderChar = strtok(NULL, " ");
+ if (!orderChar || mgr->getPlayerbots().empty())
+ {
+ handler->PSendSysMessage("|cffff0000Syntax error:|cffffffff .bot co <botName> <order=reset|tank|assist|heal|protect> [targetPlayer]");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ std::string orderStr = orderChar;
+ if (orderStr == "protect" || orderStr == "assist")
+ {
+ char* targetChar = strtok(NULL, " ");
+ uint64 targetGUID = handler->GetSession()->GetPlayer()->GetSelection();
+ if (!targetChar && !targetGUID)
+ {
+ handler->PSendSysMessage("|cffff0000Combat orders protect and assist expect a target either by selection or by giving target player in command string!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (targetChar)
+ {
+ std::string targetStr = targetChar;
+ targetGUID = sObjectMgr->GetPlayerGUIDByName(targetStr.c_str());
+ }
+ target = ObjectAccessor::GetUnit(*handler->GetSession()->GetPlayer(), targetGUID);
+ if (!target)
+ {
+ handler->PSendSysMessage("|cffff0000Invalid target for combat order protect or assist!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ }
+ //if (handler->GetSession()->GetPlayerBot(guid) == NULL)
+ //{
+ // handler->PSendSysMessage("|cffff0000Bot can not receive combat order because bot does not exist in world.");
+ // handler->SetSentErrorMessage(true);
+ // return false;
+ //}
+ //if (mgr)
+ //for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ // if (Player* bot = ObjectAccessor::GetPlayer(*player, itr->first))
+ // bot->GetPlayerbotAI()->SetCombatOrderByStr(orderStr, target);
+ }
+ return true;
+ }
+
+ static bool HandleMainTankCommand(ChatHandler* handler, const char* args)
+ {
+ Group* group = handler->GetSession()->GetPlayer()->GetGroup();
+ if (!group)
+ {
+ handler->PSendSysMessage("Must be in a group to use main tank command.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ uint64 myguid = handler->GetSession()->GetPlayer()->GetGUID();
+ if (!group->IsLeader(myguid) && !group->IsAssistant(myguid))
+ {
+ handler->PSendSysMessage("you have no permission to set main tank.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (!*args)
+ {
+ if (uint64 selection = handler->GetSession()->GetPlayer()->GetSelection())
+ {
+ if (group->IsMember(selection))
+ {
+ if (Unit* u = ObjectAccessor::FindUnit(selection))
+ {
+ bool isabot = u->GetTypeId() == TYPEID_UNIT && u->ToCreature()->GetIAmABot();
+ if (isabot && group->GetMemberSlots().size() < 3 && handler->GetSession()->GetSecurity() == SEC_PLAYER)
+ {
+ handler->PSendSysMessage("Your party is too small to set a npcbot main tank.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ group->RemoveUniqueGroupMemberFlag(MEMBER_FLAG_MAINTANK);
+ Group::MemberSlotList const& members = group->GetMemberSlots();
+ for (Group::MemberSlotList::const_iterator itr = members.begin(); itr != members.end(); ++itr)
+ {
+ uint8 flags = itr->flags;
+ if (group->isRaidGroup())
+ {
+ //try to set flags in group (will fail if not raid)
+ group->SetGroupMemberFlag(itr->guid, itr->guid == selection, MEMBER_FLAG_MAINTANK);
+ }
+ else //force flags for non-raid group (DB only) this will allow bots to find tank
+ {
+ if (itr->guid == selection && !(flags & MEMBER_FLAG_MAINTANK))
+ flags |= MEMBER_FLAG_MAINTANK;
+ }
+ //store result if DB
+ if (itr->guid != selection || !group->isRaidGroup())
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_FLAG);
+ stmt->setUInt8(0, flags);
+ stmt->setUInt32(1, GUID_LOPART(itr->guid));
+ CharacterDatabase.Execute(stmt);
+ }
+ //send result to players and their bots
+ if (!IS_PLAYER_GUID(itr->guid))
+ continue;
+ if (Player* player = ObjectAccessor::FindPlayer(itr->guid))
+ {
+ ChatHandler chp(player->GetSession());
+ chp.PSendSysMessage("Main tank is set to %s.", u->GetName().c_str());
+ player->SetBotTank(selection);
+ if (player->HaveBot())
+ {
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = player->GetBotMap(i)->_Cre();
+ if (cre)
+ cre->SetBotTank(u);
+ }
+ }
+ }
+ }
+ u->HandleEmoteCommand(EMOTE_ONESHOT_CHEER);
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ }
+ }
+ if (Unit* unit = bot_ai::GetBotGroupMainTank(group))
+ {
+ bool bot = unit->GetTypeId() == TYPEID_UNIT && unit->ToCreature()->GetIAmABot();
+ handler->PSendSysMessage("Main tank is %s (%s%s).", unit->GetName().c_str(), (bot ? "npcbot" : "player"), (unit->isAlive() ? "" : ", dead"));
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ handler->PSendSysMessage(".maintank");
+ handler->PSendSysMessage("Allows to set a main tank in bot party (can be used on npcbots). Determines npcbots' actions");
+ handler->PSendSysMessage("Npcbot maintank also receives damage reduction, avoidance and threat generation bonus");
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ else
+ {
+ //clear tank in whole bot party
+ std::string cmdStr = strtok((char*)args, " ");
+ if (!cmdStr.compare("clear") || !cmdStr.compare("cl") || !cmdStr.compare("cle") ||
+ !cmdStr.compare("reset") || !cmdStr.compare("res"))
+ {
+ Group::MemberSlotList const& members = group->GetMemberSlots();
+ for (Group::MemberSlotList::const_iterator itr = members.begin(); itr != members.end(); ++itr)
+ {
+ uint8 flags = itr->flags;
+ if (group->isRaidGroup())
+ {
+ if (flags & MEMBER_FLAG_MAINTANK)
+ group->SetGroupMemberFlag(itr->guid, false, MEMBER_FLAG_MAINTANK);
+ }
+ else
+ {
+ if (itr->flags & MEMBER_FLAG_MAINTANK)
+ flags &= ~MEMBER_FLAG_MAINTANK;
+ }
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_FLAG);
+ stmt->setUInt8(0, flags);
+ stmt->setUInt32(1, GUID_LOPART(itr->guid));
+ CharacterDatabase.Execute(stmt);
+ if (!IS_PLAYER_GUID(itr->guid))
+ continue;
+ Player* player = ObjectAccessor::FindPlayer(itr->guid);
+ if (!player) continue;
+ ChatHandler(player->GetSession()).PSendSysMessage("Main tank has been reset by %s.", handler->GetSession()->GetPlayer()->GetName().c_str());
+ player->SetBotTank(0);
+ if (player->HaveBot())
+ {
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = player->GetBotMap(i)->_Cre();
+ if (cre)
+ cre->SetBotTank(NULL);
+ }
+ }
+ }
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ }
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ static bool HandleNpcBotInfoCommand(ChatHandler* handler, const char* /*args*/)
+ {
+ Player* owner = handler->GetSession()->GetPlayer();
+ if (!owner->GetSelection())
+ {
+ handler->PSendSysMessage(".npcbot info");
+ handler->PSendSysMessage("Lists NpcBots count of each class owned by selected player. You can use this on self and your Playerbots");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ Player* master = owner->GetSelectedPlayer();
+ if (!master || (master && master != owner && master->GetSession()->m_master != owner))
+ {
+ handler->PSendSysMessage("You should select one of your PlayerBots or self.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ //create list
+ std::set<Player*> Players;
+ Players.insert(master);
+ PlayerbotMgr* mgr = master->GetPlayerbotMgr();
+ if (mgr && !mgr->getPlayerbots().empty())
+ for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ Players.insert(itr->second);
+ //cycle through
+ for (std::set<Player*>::const_iterator it = Players.begin(); it != Players.end(); ++it)
+ {
+ Player* pl = *it;
+ if (!pl->HaveBot())
+ {
+ handler->PSendSysMessage("%s has no NpcBots!", pl->GetName().c_str());
+ continue;
+ }
+ const std::string plType = pl == owner ? ", player" : ", playerbot";
+ handler->PSendSysMessage("Listing NpcBots for %s%s", pl->GetName().c_str(), plType.c_str());
+ handler->PSendSysMessage("Owned NpcBots: %u", pl->GetNpcBotsCount());
+ for (uint8 i = CLASS_WARRIOR; i != MAX_CLASSES; ++i)
+ {
+ uint8 count = 0;
+ uint8 alivecount = 0;
+ for (uint8 pos = 0; pos != pl->GetMaxNpcBots(); ++pos)
+ {
+ if (Creature* cre = pl->GetBotMap(pos)->_Cre())
+ {
+ if (cre->GetBotClass() == i)
+ {
+ ++count;
+ if (cre->isAlive())
+ ++alivecount;
+ }
+ }
+ }
+ char const* bclass;
+ switch (i)
+ {
+ case CLASS_WARRIOR: bclass = "Warriors"; break;
+ case CLASS_PALADIN: bclass = "Paladins"; break;
+ case CLASS_MAGE: bclass = "Mages"; break;
+ case CLASS_PRIEST: bclass = "Priests"; break;
+ case CLASS_WARLOCK: bclass = "Warlocks"; break;
+ case CLASS_DRUID: bclass = "Druids"; break;
+ case CLASS_DEATH_KNIGHT: bclass = "DeathKnights"; break;
+ case CLASS_ROGUE: bclass = "Rogues"; break;
+ case CLASS_SHAMAN: bclass = "Shamans"; break;
+ case CLASS_HUNTER: bclass = "Hunters"; break;
+ default: bclass = "Unknown Class"; break;
+ }
+ if (count > 0)
+ 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())
+ {
+ for (uint8 i = 0; i != owner->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = owner->GetBotMap(i)->_Cre();
+ if (!cre || !cre->IsInWorld()) continue;
+ owner->SendBotCommandState(cre, COMMAND_FOLLOW);
+ }
+ }
+ PlayerbotMgr* mgr = owner->GetPlayerbotMgr();
+ if (mgr && !mgr->getPlayerbots().empty())
+ {
+ for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ if (Player* bot = itr->second)
+ {
+ bot->SetBotFollowDist(dist);
+ if (!bot->isInCombat() && bot->HaveBot())
+ {
+ for (uint8 i = 0; i != bot->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = bot->GetBotMap(i)->_Cre();
+ if (!cre || !cre->IsInWorld()) continue;
+ bot->SendBotCommandState(cre, 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 <command>");
+ handler->PSendSysMessage("Forces npcbots to either follow you or hold position. Can be used on Playerbots");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ Player* master = owner->GetSelectedPlayer();
+ if (!master || (master && master != owner && master->GetSession()->m_master != owner))
+ master = owner;
+ 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 && master->HaveBot())
+ {
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ Creature* cre = master->GetBotMap(i)->_Cre();
+ if (!cre || !cre->IsInWorld()) continue;
+ master->SendBotCommandState(cre, CommandStates(state));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ static bool HandleNpcBotRemoveCommand(ChatHandler* handler, const char* /*args*/)
+ {
+ Player* owner = handler->GetSession()->GetPlayer();
+ uint64 guid = owner->GetSelection();
+ if (!guid)
+ {
+ handler->PSendSysMessage(".npcbot remove");
+ handler->PSendSysMessage("Remove npcbots for selected Playerbot, you can also remove npcbots manually");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ Player* master = ObjectAccessor::GetPlayer(*owner, guid);
+ if (master)
+ {
+ if (master != owner && master->GetSession()->m_master != owner)
+ {
+ handler->PSendSysMessage("You can only remove bots from self and your own playerbots!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (master->HaveBot())
+ {
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ master->RemoveBot(master->GetBotMap(i)->_Guid(), true);
+ }
+ if (!master->HaveBot())
+ {
+ handler->PSendSysMessage("Npcbots successfully removed");
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ handler->PSendSysMessage("Error!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ handler->PSendSysMessage("Npcbots are not found!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ Creature* cre = ObjectAccessor::GetCreature(*owner, guid);
+ if (cre && cre->GetIAmABot())
+ {
+ master = cre->GetBotOwner();
+ if (!master || (master && master != owner && master->GetSession()->m_master != owner))
+ {
+ handler->PSendSysMessage("You can only remove bots from self and your own playerbots");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ uint8 pos = master->GetNpcBotSlot(guid);
+ master->RemoveBot(cre->GetGUID(), true);
+ if (master->GetBotMap(pos)->_Cre() == NULL)
+ {
+ handler->PSendSysMessage("NpcBot successfully removed");
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ handler->PSendSysMessage("NpcBot is NOT removed for some reason!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ handler->PSendSysMessage("You should select Player or it's 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;
+ uint64 guid = owner->GetSelection();
+ if (!guid)
+ {
+ handler->PSendSysMessage(".npcbot reset");
+ handler->PSendSysMessage("Reset selected npcbot or npcbots for selected Playerbot, you can also reset your npcbots");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (IS_PLAYER_GUID(guid))
+ {
+ master = ObjectAccessor::FindPlayer(guid);
+ all = true;
+ }
+ else if (IS_CREATURE_GUID(guid))
+ {
+ if (Creature* cre = ObjectAccessor::GetCreature(*owner, guid))
+ master = cre->GetBotOwner();
+ }
+ if (master && (master == owner || master->GetSession()->m_master == owner))
+ {
+ if (master->isInCombat() && master->GetSession()->GetSecurity() == SEC_PLAYER)
+ {
+ handler->PSendSysMessage("Cannot reset bots in combat!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (!master->HaveBot())
+ {
+ handler->PSendSysMessage("Npcbots are not found!");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ {
+ if (all)
+ master->RemoveBot(master->GetBotMap(i)->_Guid());
+ else if (master->GetBotMap(i)->_Guid() == guid)
+ {
+ master->RemoveBot(guid);
+ break;
+ }
+ }
+ return true;
+ }
+ handler->PSendSysMessage(".npcbot reset");
+ handler->PSendSysMessage("Reset selected npcbot or npcbot for selected Playerbot, you can also reset your npcbot. Cannot be used in combat");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ //For debug purposes only
+ static bool HandleNpcBotReviveCommand(ChatHandler* handler, const char* /*args*/)
+ {
+ if (handler->GetSession()->GetSecurity() == SEC_PLAYER)
+ {
+ handler->PSendSysMessage("Revive command is disabled");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ Player* owner = handler->GetSession()->GetPlayer();
+ if (owner->InBattleground())
+ {
+ handler->PSendSysMessage("Bot revival is disabled in pvp matches");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (owner->isInCombat())
+ {
+ handler->PSendSysMessage("Bot revival is disabled in combat");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (owner->isInFlight())
+ {
+ handler->PSendSysMessage("Bot revival is disabled in flight");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (owner->HaveBot())
+ {
+ for (uint8 i = 0; i != owner->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = owner->GetBotMap(i)->_Cre();
+ if (!bot) continue;
+ if (bot->isDead())
+ {
+ owner->SetBot(bot);
+ owner->CreateBot(0, 0, 0, false, true);
+ }
+ }
+ handler->PSendSysMessage("NpcBots revived");
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+ handler->PSendSysMessage(".npcbot revive");
+ handler->PSendSysMessage("Revive your npcbots if you are all hopelessly dead");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ static bool HandleNpcBotAddCommand(ChatHandler* handler, const char* args)
+ {
+ Player* owner = handler->GetSession()->GetPlayer();
+ Player* master = owner->GetSelectedPlayer();
+ if (!master || !*args || (master != owner && master->GetSession()->m_master != owner))
+ {
+ handler->PSendSysMessage(".npcbot add");
+ handler->PSendSysMessage("Allows to create npcbot of given class for targeted Playerbot, can be also used on self");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (master->RestrictBots())
+ {
+ handler->GetSession()->SendNotification("This place is restricted for NpcBots");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (master->isDead())
+ {
+ if (master == owner)
+ owner->GetSession()->SendNotification("You're dead!");
+ else
+ owner->GetSession()->SendNotification("%s is dead!", master->GetName().c_str());
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (master->GetGroup() && master->GetGroup()->isRaidGroup() && master->GetGroup()->IsFull())
+ {
+ handler->PSendSysMessage("Group is full, aborted");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+ if (master->GetNpcBotsCount() >= master->GetMaxNpcBots())
+ {
+ handler->PSendSysMessage("NpcBots limit exceed");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ char* bclass = strtok((char*)args, " ");
+ uint8 botclass = CLASS_NONE;
+
+ if (!strncmp(bclass, "deathknight", 12) || !strncmp(bclass, "dk", 3) || !strncmp(bclass, "de", 3))
+ botclass = CLASS_DEATH_KNIGHT;
+ else if (!strncmp(bclass, "druid", 6) || !strncmp(bclass, "dru", 4) || !strncmp(bclass, "dr", 3))
+ botclass = CLASS_DRUID;
+ else if (!strncmp(bclass, "hunter", 7) || !strncmp(bclass, "hunt", 5) || !strncmp(bclass, "hu", 3))
+ botclass = CLASS_HUNTER;
+ else if (!strncmp(bclass, "mage", 5) || !strncmp(bclass, "ma", 3))
+ botclass = CLASS_MAGE;
+ else if (!strncmp(bclass, "paladin", 8) || !strncmp(bclass, "pal", 4) || !strncmp(bclass, "pa", 3))
+ botclass = CLASS_PALADIN;
+ else if (!strncmp(bclass, "priest", 7) || !strncmp(bclass, "pri", 4) || !strncmp(bclass, "pr", 3))
+ botclass = CLASS_PRIEST;
+ else if (!strncmp(bclass, "rogue", 6) || !strncmp(bclass, "rog", 4) || !strncmp(bclass, "ro", 3))
+ botclass = CLASS_ROGUE;
+ else if (!strncmp(bclass, "shaman", 7) || !strncmp(bclass, "sha", 4) || !strncmp(bclass, "sh", 3))
+ botclass = CLASS_SHAMAN;
+ else if (!strncmp(bclass, "warlock", 8) || !strncmp(bclass, "warl", 5) || !strncmp(bclass, "lock", 5))
+ botclass = CLASS_WARLOCK;
+ else if (!strncmp(bclass, "warrior", 8) || !strncmp(bclass, "warr", 5))
+ botclass = CLASS_WARRIOR;
+
+ if (botclass == CLASS_NONE)
+ {
+ handler->PSendSysMessage("Wrong bot class");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ uint8 bots = master->GetNpcBotsCount();
+ master->CreateNPCBot(botclass);
+ master->RefreshBot(0);
+ if (master->GetNpcBotsCount() > bots)
+ {
+ if (master->isInCombat())
+ handler->PSendSysMessage("NpcBot successfully created (%s). Will appear out of combat", master->GetName().c_str());
+ else
+ handler->PSendSysMessage("NpcBot successfully created (%s).", master->GetName().c_str());
+ handler->SetSentErrorMessage(true);
+ 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/botgiver.cpp b/src/server/game/AI/NpcBots/botgiver.cpp
new file mode 100644
index 0000000..7fe13f8
--- /dev/null
+++ b/src/server/game/AI/NpcBots/botgiver.cpp
@@ -0,0 +1,643 @@
+#include "bp_mgr.h"
+#include "Config.h"
+#include "Group.h"
+#include "ObjectMgr.h"
+#include "Player.h"
+#include "ScriptedGossip.h"
+#include "ScriptMgr.h"
+#include "WorldSession.h"
+/*
+Complete - ???
+Category - creature_cripts/custom/bots/
+*/
+const uint8 GroupIcons[TARGETICONCOUNT] =
+{
+ /*STAR = */0x001,
+ /*CIRCLE = */0x002,
+ /*DIAMOND = */0x004,
+ /*TRIANGLE = */0x008,
+ /*MOON = */0x010,
+ /*SQUARE = */0x020,
+ /*CROSS = */0x040,
+ /*SKULL = */0x080,
+};
+
+enum GossipActions
+{
+ CREATE_NBOT_MENU = 1,
+ CREATE_NBOT = 2,
+ CREATE_PBOT_MENU = 3,
+ CREATE_PBOT = 4,
+
+ REMOVE_PBOT_MENU = 5,
+ REMOVE_PBOT = 6,
+ REMOVE_NBOT_MENU = 7,
+ REMOVE_NBOT = 8,
+
+ INFO_WHISPER = 9
+};
+
+enum BotgiverTexIDs
+{
+ ABANDON_PLAYER = 1,
+ RECRUIT_PLAYER = 2,
+ ABANDON_MINION = 3,
+ RECRUIT_MINION = 4,
+ ABOUT_STR = 5,
+ ADD_ALL = 6,
+ REMOVE_ALL = 7,
+ RECRUIT_WARRIOR = 8,
+ RECRUIT_HUNTER = 9,
+ RECRUIT_PALADIN = 10,
+ RECRUIT_SHAMAN = 11,
+ RECRUIT_ROGUE = 12,
+ RECRUIT_DRUID = 13,
+ RECRUIT_MAGE = 14,
+ RECRUIT_PRIEST = 15,
+ RECRUIT_WARLOCK = 16,
+ RECRUIT_DEATH_KNIGHT = 17,
+ ABOUT_BASIC_STR1 = 18,
+ ABOUT_BASIC_STR2 = 19,
+ ABOUT_BASIC_STR3 = 20,
+ ABOUT_ICONS_STR1 = 21,
+ ABOUT_ICONS_STR2 = 22,
+ ICON_STRING_STAR = 23,
+ ICON_STRING_CIRCLE = 24,
+ ICON_STRING_DIAMOND = 25,
+ ICON_STRING_TRIANGLE = 26,
+ ICON_STRING_MOON = 27,
+ ICON_STRING_SQUARE = 28,
+ ICON_STRING_CROSS = 29,
+ ICON_STRING_SKULL = 30,
+ ICON_STRING_UNKNOWN = 31,
+ NO_MORE_AVAILABLE = 32,
+ ONE_MORE_AVAILABLE = 33,
+ SOME_MORE_AVAILABLE = 34,
+ ONE_AVAILABLE = 35,
+ SOME_AVAILABLE = 36,
+ MAX_STRINGS
+};
+
+class script_bot_giver : public CreatureScript
+{
+ uint8 maxPBcount;
+ uint8 maxNBcount;
+
+ bool allowPBots;
+ bool allowNBots;
+
+public:
+ script_bot_giver() : CreatureScript("script_bot_giver") { }
+
+ bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action)
+ {
+ switch (sender)
+ {
+ case CREATE_NBOT_MENU: SendCreateNPCBotMenu(player, creature, action); break;
+ case CREATE_NBOT: SendCreateNPCBot(player, creature, action); break;
+ case CREATE_PBOT_MENU: SendCreatePlayerBotMenu(player, creature, action); break;
+ case CREATE_PBOT: SendCreatePlayerBot(player, creature, action); break;
+
+ case REMOVE_PBOT_MENU: SendRemovePlayerBotMenu(player, creature, action); break;
+ case REMOVE_PBOT: SendRemovePlayerBot(player, creature, action); break;
+ case REMOVE_NBOT_MENU: SendRemoveNPCBotMenu(player, creature, action); break;
+ case REMOVE_NBOT: SendRemoveNPCBot(player, creature, action); break;
+
+ case INFO_WHISPER: SendBotHelpWhisper(player, creature, action); break;
+ }
+ return true;
+ }
+
+ bool OnGossipHello(Player* player, Creature* creature)
+ {
+ uint8 count = 0;
+
+ maxPBcount = ConfigMgr::GetIntDefault("Bot.MaxPlayerbots", 9);
+ maxNBcount = player->GetMaxNpcBots();
+
+ allowPBots = ConfigMgr::GetBoolDefault("Bot.EnablePlayerBots", false);
+ allowNBots = ConfigMgr::GetBoolDefault("Bot.EnableNpcBots", true) && !player->RestrictBots();
+
+ std::string tempstr;
+
+ if (PlayerbotMgr* mgr = player->GetPlayerbotMgr())
+ {
+ for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ if (count == 0)
+ {
+ tempstr = "Abandon my Player";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, ABANDON_PLAYER, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), REMOVE_PBOT_MENU, GOSSIP_ACTION_INFO_DEF + 100);
+ }
+ ++count;
+ }
+ }
+ if (count < maxPBcount && allowPBots)
+ {
+ tempstr = "Recruit a Player";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, RECRUIT_PLAYER, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), CREATE_PBOT_MENU, GOSSIP_ACTION_INFO_DEF + 1);
+ }
+
+ if (player->HaveBot())
+ {
+ count = player->GetNpcBotsCount();
+ if (count > 0)
+ {
+ tempstr = "Abandon my Minion";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, ABANDON_MINION, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), REMOVE_NBOT_MENU, GOSSIP_ACTION_INFO_DEF + 101);
+ }
+ if (count < maxNBcount && allowNBots)
+ {
+ tempstr = "Recruit a Minion";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, RECRUIT_MINION, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), CREATE_NBOT_MENU, GOSSIP_ACTION_INFO_DEF + 2);
+ }
+ }
+ else if (allowNBots && maxNBcount != 0)
+ {
+ tempstr = "Recruit a Minion";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, RECRUIT_MINION, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), CREATE_NBOT_MENU, GOSSIP_ACTION_INFO_DEF + 2);
+ }
+
+ tempstr = "Tell me about these bots";
+ player->ADD_GOSSIP_ITEM(0, GetLocaleStringForTextID(tempstr, ABOUT_STR, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), INFO_WHISPER, GOSSIP_ACTION_INFO_DEF + 200);
+
+ player->PlayerTalkClass->SendGossipMenu(8446, creature->GetGUID());
+ return true;
+ }
+
+private:
+ static void SendCreatePlayerBot(Player* player, Creature* /*creature*/, uint32 action)
+ {
+ std::string plName;
+ std::list<std::string>* names = new std::list<std::string>;
+ uint32 accId = player->GetSession()->GetAccountId();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYERBOTS);
+ stmt->setUInt32(0, accId);
+ stmt->setUInt32(1, player->GetGUIDLow());
+ PreparedQueryResult results = CharacterDatabase.Query(stmt);
+ //QueryResult results = CharacterDatabase.PQuery("SELECT name FROM characters WHERE account = '%u' AND guid != '%u'", accId, player->GetGUIDLow());
+ if (results)
+ {
+ do
+ {
+ Field* fields = results->Fetch();
+ plName = fields[0].GetString();
+ if (ObjectAccessor::FindPlayerByName(plName))
+ continue;
+ names->insert(names->end(), plName);
+ } while (results->NextRow());
+ }
+
+ if (names->empty())
+ {
+ delete names;
+ player->CLOSE_GOSSIP_MENU();
+ return;
+ }
+
+ PlayerbotMgr* mgr = player->GetPlayerbotMgr();
+ if (!mgr)
+ {
+ mgr = new PlayerbotMgr(player);
+ player->SetPlayerbotMgr(mgr);
+ }
+
+ int8 x = action - GOSSIP_ACTION_INFO_DEF - 1;
+
+ for (std::list<std::string>::iterator iter = names->begin(); iter != names->end(); iter++)
+ {
+ if (x == 0)
+ {
+ uint64 guid = sObjectMgr->GetPlayerGUIDByName((*iter).c_str());
+ if (mgr->GetPlayerBot(guid) != NULL)
+ continue;
+ mgr->AddPlayerBot(guid);
+ }
+ else
+ {
+ if (x == 1)
+ {
+ uint64 guid = sObjectMgr->GetPlayerGUIDByName((*iter).c_str());
+ if (mgr->GetPlayerBot(guid) != NULL)
+ break;
+ mgr->AddPlayerBot(guid);
+ break;
+ }
+ --x;
+ }
+ }
+ player->CLOSE_GOSSIP_MENU();
+ delete names;
+ }
+
+ void SendCreatePlayerBotMenu(Player* player, Creature* creature, uint32 /*action*/)
+ {
+ std::string plName;
+ std::list<std::string>* names = new std::list<std::string>;
+ uint32 accId = player->GetSession()->GetAccountId();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYERBOTS);
+ stmt->setUInt32(0, accId);
+ stmt->setUInt32(1, player->GetGUIDLow());
+ PreparedQueryResult results = CharacterDatabase.Query(stmt);
+ //QueryResult results = CharacterDatabase.PQuery("SELECT name FROM characters WHERE account = '%u' AND guid != '%u'", accId, player->GetGUIDLow());
+ if (results)
+ {
+ do
+ {
+ Field* fields = results->Fetch();
+ plName = fields[0].GetString();
+ if (sObjectAccessor->FindPlayerByName(plName))
+ continue;
+ names->insert(names->end(), plName);
+ } while (results->NextRow());
+ }
+
+ if (names->empty())
+ {
+ delete names;
+ player->CLOSE_GOSSIP_MENU();
+ return;
+ }
+
+ player->PlayerTalkClass->ClearMenus();
+ std::string tempstr;
+ PlayerbotMgr* mgr = player->GetPlayerbotMgr();
+ uint8 bots = !mgr ? 0 : mgr->GetPlayerBotsCount();
+ uint32 freePBSlots = maxPBcount - bots;
+ if (freePBSlots != 1 && freePBSlots >= names->size())
+ {
+ tempstr = "ADD ALL";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, ADD_ALL, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), CREATE_PBOT, GOSSIP_ACTION_INFO_DEF + 1);
+ }
+
+ int8 x = 2;
+ for (std::list<std::string>::iterator iter = names->begin(); iter != names->end(); iter++)
+ {
+ player->ADD_GOSSIP_ITEM(9, (*iter).c_str() , CREATE_PBOT, GOSSIP_ACTION_INFO_DEF + x);
+ ++x;
+ }
+
+ std::ostringstream buff;
+ if (freePBSlots == 0)
+ {
+ tempstr = "no more bots available";
+ buff << GetLocaleStringForTextID(tempstr, NO_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ buff << freePBSlots;
+ buff << ' ';
+ if (freePBSlots == 1)
+ {
+ if (bots == 0)
+ {
+ tempstr = "bot available";
+ buff << GetLocaleStringForTextID(tempstr, ONE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ tempstr = "more bot available";
+ buff << GetLocaleStringForTextID(tempstr, ONE_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ }
+ else
+ {
+ if (bots == 0)
+ {
+ tempstr = "bots available";
+ buff << GetLocaleStringForTextID(tempstr, SOME_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ tempstr = "more bots available";
+ buff << GetLocaleStringForTextID(tempstr, SOME_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ }
+ }
+
+ player->ADD_GOSSIP_ITEM(0, buff.str(), CREATE_PBOT_MENU, GOSSIP_ACTION_INFO_DEF + 1);
+
+ player->PlayerTalkClass->SendGossipMenu(8446, creature->GetGUID());
+
+ delete names;
+ } //end SendCreatePlayerBotMenu
+
+ static void SendRemovePlayerBotAll(Player* player, Creature* creature)
+ {
+ for (int8 x = 2; x<=10; x++ )
+ SendRemovePlayerBot(player, creature, GOSSIP_ACTION_INFO_DEF + 2);
+ }
+
+ static void SendRemovePlayerBot(Player* player, Creature* creature, uint32 action)
+ {
+ int8 x = action - GOSSIP_ACTION_INFO_DEF - 1;
+
+ if (x == 0)
+ {
+ SendRemovePlayerBotAll(player, creature);
+ return;
+ }
+
+ if (PlayerbotMgr* mgr = player->GetPlayerbotMgr())
+ {
+ for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ if (x == 1 && itr->second)
+ {
+ mgr->LogoutPlayerBot(itr->second->GetGUID());
+ break;
+ }
+ --x;
+ }
+ }
+ player->CLOSE_GOSSIP_MENU();
+ } //end SendRemovePlayerBot
+
+ static void SendRemovePlayerBotMenu(Player* player, Creature* creature, uint32 /*action*/)
+ {
+ player->PlayerTalkClass->ClearMenus();
+ PlayerbotMgr* mgr = player->GetPlayerbotMgr();
+ if (mgr->GetPlayerBotsCount() != 1)
+ {
+ std::string tempstr = "REMOVE ALL";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, REMOVE_ALL, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), REMOVE_PBOT, GOSSIP_ACTION_INFO_DEF + 1);
+ }
+
+ uint8 x = 2;
+ for (PlayerBotMap::const_iterator itr = mgr->GetPlayerBotsBegin(); itr != mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ Player* bot = itr->second;
+ player->ADD_GOSSIP_ITEM(9, bot->GetName(), REMOVE_PBOT, GOSSIP_ACTION_INFO_DEF + x);
+ ++x;
+ }
+ player->PlayerTalkClass->SendGossipMenu(8446, creature->GetGUID());
+ } //end SendRemovePlayerBotMenu
+
+ static void SendRemoveNPCBot(Player* player, Creature* /*creature*/, uint32 action)
+ {
+ int8 x = action - GOSSIP_ACTION_INFO_DEF;
+ if (x == 1)
+ {
+ player->CLOSE_GOSSIP_MENU();
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ player->RemoveBot(player->GetBotMap(i)->_Guid(), true);
+ return;
+ }
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ {
+ if (!player->GetBotMap(i)->_Cre())
+ continue;
+ if (x == 2)
+ {
+ player->RemoveBot(player->GetBotMap(i)->_Guid(), true);
+ break;
+ }
+ --x;
+ }
+ player->CLOSE_GOSSIP_MENU();
+ }
+
+ static void SendRemoveNPCBotMenu(Player* player, Creature* creature, uint32 /*action*/)
+ {
+ player->PlayerTalkClass->ClearMenus();
+ if (player->GetNpcBotsCount() == 1)
+ {
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ player->RemoveBot(player->GetBotMap(i)->_Guid(), true);
+ player->CLOSE_GOSSIP_MENU();
+ return;
+ }
+ std::string tempstr = "REMOVE ALL";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, REMOVE_ALL, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()), REMOVE_NBOT, GOSSIP_ACTION_INFO_DEF + 1);
+
+ uint8 x = 2;
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = player->GetBotMap(i)->_Cre();
+ if (!bot) continue;
+ player->ADD_GOSSIP_ITEM(9, bot->GetName(), REMOVE_NBOT, GOSSIP_ACTION_INFO_DEF + x);
+ ++x;
+ }
+ player->PlayerTalkClass->SendGossipMenu(8446, creature->GetGUID());
+ }
+
+ static void SendCreateNPCBot(Player* player, Creature* /*creature*/, uint32 action)
+ {
+ uint8 bot_class = 0;
+ if (action == GOSSIP_ACTION_INFO_DEF + 1)//"Back"
+ {
+ player->CLOSE_GOSSIP_MENU();
+ return;
+ }
+ else if (action == GOSSIP_ACTION_INFO_DEF + 2)
+ bot_class = CLASS_WARRIOR;
+ //else if (action == GOSSIP_ACTION_INFO_DEF + 3)
+ // bot_class = CLASS_HUNTER;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 4)
+ bot_class = CLASS_PALADIN;
+ //else if (action == GOSSIP_ACTION_INFO_DEF + 5)
+ // bot_class = CLASS_SHAMAN;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 6)
+ bot_class = CLASS_ROGUE;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 7)
+ bot_class = CLASS_DRUID;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 8)
+ bot_class = CLASS_MAGE;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 9)
+ bot_class = CLASS_PRIEST;
+ else if (action == GOSSIP_ACTION_INFO_DEF + 10)
+ bot_class = CLASS_WARLOCK;
+ //else if (action == GOSSIP_ACTION_INFO_DEF + 11)
+ // bot_class = CLASS_DEATH_KNIGHT;
+
+ if (bot_class != 0)
+ player->CreateNPCBot(bot_class);
+ player->CLOSE_GOSSIP_MENU();
+ return;
+ }
+
+ void SendCreateNPCBotMenu(Player* player, Creature* creature, uint32 /*action*/)
+ {
+ std::string cost = player->GetNpcBotCostStr();
+ player->PlayerTalkClass->ClearMenus();
+
+ std::string tempstr = "Recruit a Warrior ";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_WARRIOR, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 2);
+ //tempstr = "Recruit a Hunter ";
+ //player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_HUNTER, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 3);
+ tempstr = "Recruit a Paladin ";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_PALADIN, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 4);
+ //tempstr = "Recruit a Shaman ";
+ //player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_SHAMAN, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 5);
+ tempstr = "Recruit a Rogue ";
+ player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_ROGUE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 6);
+ tempstr = "Recruit a Druid ";
+ player->ADD_GOSSIP_ITEM(3, GetLocaleStringForTextID(tempstr, RECRUIT_DRUID, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 7);
+ tempstr = "Recruit a Mage ";
+ player->ADD_GOSSIP_ITEM(3, GetLocaleStringForTextID(tempstr, RECRUIT_MAGE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 8);
+ tempstr = "Recruit a Priest ";
+ player->ADD_GOSSIP_ITEM(3, GetLocaleStringForTextID(tempstr, RECRUIT_PRIEST, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 9);
+ tempstr = "Recruit a Warlock ";
+ player->ADD_GOSSIP_ITEM(3, GetLocaleStringForTextID(tempstr, RECRUIT_WARLOCK, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 10);
+ //tempstr = "Recruit a Death Knight ";
+ //player->ADD_GOSSIP_ITEM(9, GetLocaleStringForTextID(tempstr, RECRUIT_DEATH_KNIGHT, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex()) + cost, CREATE_NBOT, GOSSIP_ACTION_INFO_DEF + 11);
+
+ std::ostringstream buff;
+ uint8 bots = player->GetNpcBotsCount();
+ uint32 freeNBSlots = maxNBcount - bots;
+
+ if (freeNBSlots == 0)
+ {
+ tempstr = "no more bots available";
+ buff << GetLocaleStringForTextID(tempstr, NO_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ buff << freeNBSlots;
+ buff << ' ';
+ if (freeNBSlots == 1)
+ {
+ if (bots == 0)
+ {
+ tempstr = "bot available";
+ buff << GetLocaleStringForTextID(tempstr, ONE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ tempstr = "more bot available";
+ buff << GetLocaleStringForTextID(tempstr, ONE_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ }
+ else
+ {
+ if (bots == 0)
+ {
+ tempstr = "bots available";
+ buff << GetLocaleStringForTextID(tempstr, SOME_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ else
+ {
+ tempstr = "more bots available";
+ buff << GetLocaleStringForTextID(tempstr, SOME_MORE_AVAILABLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ }
+ }
+ }
+ player->ADD_GOSSIP_ITEM(0, buff.str(), CREATE_NBOT_MENU, GOSSIP_ACTION_INFO_DEF + 2);
+
+ player->PlayerTalkClass->SendGossipMenu(8446, creature->GetGUID());
+ }
+
+ static void SendBotHelpWhisper(Player* player, Creature* creature, uint32 /*action*/)
+ {
+ player->CLOSE_GOSSIP_MENU();
+ //Basic
+ std::string tempstr = "To see list of Playerbot commands whisper 'help' to one of your playerbots";
+ std::string msg1 = GetLocaleStringForTextID(tempstr, ABOUT_BASIC_STR1, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ tempstr = "To see list of available npcbot commands type .npcbot or .npcb";
+ std::string msg2 = GetLocaleStringForTextID(tempstr, ABOUT_BASIC_STR2, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ tempstr = "You can also use .maintank (or .mt or .main) command on any party member (even npcbot) so your bots will stick to your plan";
+ std::string msg3 = GetLocaleStringForTextID(tempstr, ABOUT_BASIC_STR3, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ creature->MonsterWhisper(msg1.c_str(), player->GetGUID());
+ creature->MonsterWhisper(msg2.c_str(), player->GetGUID());
+ creature->MonsterWhisper(msg3.c_str(), player->GetGUID());
+ //Heal Icons
+ uint8 mask = ConfigMgr::GetIntDefault("Bot.HealTargetIconsMask", 8);
+ std::string msg4 = "";
+ if (mask == 255)
+ {
+ tempstr = "If you want your npcbots to heal someone out of your party set any raid target icon on them";
+ msg4 = GetLocaleStringForTextID(tempstr, ABOUT_ICONS_STR1, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ creature->MonsterWhisper(msg4.c_str(), player->GetGUID());
+ }
+ else if (mask != 0)
+ {
+ tempstr = "If you want your npcbots to heal someone out of your party set proper raid target icon on them, one of these: ";
+ msg4 = GetLocaleStringForTextID(tempstr, ABOUT_ICONS_STR2, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ std::string iconrow = "";
+ uint8 count = 0;
+ for (uint8 i = 0; i != TARGETICONCOUNT; ++i)
+ {
+ if (mask & GroupIcons[i])
+ {
+ if (count != 0)
+ iconrow += ", ";
+ ++count;
+ switch (i)
+ {
+ case 0:
+ tempstr = "star";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_STAR, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 1:
+ tempstr = "circle";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_CIRCLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 2:
+ tempstr = "diamond";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_DIAMOND, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 3:
+ tempstr = "triangle";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_TRIANGLE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 4:
+ tempstr = "moon";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_MOON, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 5:
+ tempstr = "square";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_SQUARE, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 6:
+ tempstr = "cross";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_CROSS, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ case 7:
+ tempstr = "skull";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_SKULL, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ default:
+ tempstr = "unknown icon";
+ iconrow += GetLocaleStringForTextID(tempstr, ICON_STRING_UNKNOWN, creature->GetEntry(), player->GetSession()->GetSessionDbLocaleIndex());
+ break;
+ }
+ }
+ }
+ msg4 += iconrow;
+ creature->MonsterWhisper(msg4.c_str(), player->GetGUID());
+ }
+ }
+
+ static std::string GetLocaleStringForTextID(std::string& textValue, uint32 textId, uint32 botgiverEntry, int32 localeIdx)
+ {
+ if (textId >= MAX_STRINGS)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "botgiver:GetLocaleStringForTextID:: unknown text id: %u!", uint32(textId));
+ return textValue;
+ }
+
+ if (localeIdx == DEFAULT_LOCALE)
+ return textValue; //use default
+
+ if (localeIdx < 0)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "botgiver:GetLocaleStringForTextID:: unknown locale: %i! Sending default locale text...", localeIdx);
+ return textValue;
+ }
+
+ uint32 idxEntry = MAKE_PAIR32(botgiverEntry, textId);
+ if (GossipMenuItemsLocale const* no = sObjectMgr->GetGossipMenuItemsLocale(idxEntry))
+ {
+ ObjectMgr::GetLocaleString(no->OptionText, localeIdx, textValue);
+ //ObjectMgr::GetLocaleString(no->BoxText, locale, strBoxText);
+ }
+ return textValue;
+ }
+};
+
+//This function is called when the player clicks an option on the gossip menu
+void AddSC_script_bot_giver()
+{
+ new script_bot_giver();
+}
diff --git a/src/server/game/AI/PlayerBots/bp_ai.cpp b/src/server/game/AI/PlayerBots/bp_ai.cpp
new file mode 100644
index 0000000..ea78334
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_ai.cpp
@@ -0,0 +1,1888 @@
+/*
+Original source: https://github.com/blueboy/portal/commits/new-ai
+Type: Rewrite project
+Complete: ~10%
+TODO:
+Everything but this
+*/
+
+//#include "Common.h"
+#include "ItemPrototype.h"
+//#include "World.h"
+//#include "SpellMgr.h"
+//#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "bot_GridNotifiers.h"
+#include "CellImpl.h"
+//#include "ProgressBar.h"
+#include "Chat.h"
+#include "bp_ai.h"
+#include "bp_mgr.h"
+#include "bp_dk_ai.h"
+#include "bp_dru_ai.h"
+#include "bp_hun_ai.h"
+#include "bp_mag_ai.h"
+#include "bp_pal_ai.h"
+#include "bp_pri_ai.h"
+#include "bp_rog_ai.h"
+#include "bp_sha_ai.h"
+#include "bp_warl_ai.h"
+#include "bp_warr_ai.h"
+#include "InstanceSaveMgr.h"
+#include "Player.h"
+#include "Group.h"
+#include "GroupMgr.h"
+#include "Pet.h"
+//#include "ReputationMgr.h"
+#include "ObjectMgr.h"
+#include "WorldSession.h"
+//#include "Spell.h"
+//#include "Unit.h"
+//#include "SpellAuras.h"
+#include "SpellAuraEffects.h"
+//#include "SharedDefines.h"
+//#include "Log.h"
+#include "GossipDef.h"
+//#include "MotionMaster.h"
+#include "AuctionHouseMgr.h"
+//#include "Mail.h"
+#include "Guild.h"
+//#include "GuildMgr.h"
+#include "Language.h"
+#include <iomanip>
+//#include <iostream>
+
+const uint32 VENDOR_MASK = (UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_VENDOR_AMMO | UNIT_NPC_FLAG_VENDOR_FOOD | UNIT_NPC_FLAG_VENDOR_POISON | UNIT_NPC_FLAG_VENDOR_REAGENT);
+Player::BoundInstancesMap _botBoundInstances[MAX_DIFFICULTY];
+
+AfterCast::AfterCast()
+{
+ _afterCastTarget = NULL;
+}
+inline Unit* AfterCast::GetTarget() const
+{
+ return _afterCastTarget;
+}
+inline void AfterCast::SetTarget(Unit* target)
+{
+ _afterCastTarget = target;
+}
+inline void AfterCast::SetAfterCastCommand(void (*newAfterCast)(Unit*))
+{
+ afterCast = newAfterCast;
+}
+inline void AfterCast::LaunchAfterCastCommand()
+{
+ ASSERT(_afterCastTarget);
+
+ return (*afterCast)(_afterCastTarget);
+}
+
+class PlayerbotChatHandler : public ChatHandler
+{
+public:
+ explicit PlayerbotChatHandler(WorldSession* masterSession) : ChatHandler(masterSession) {}
+ bool revive(Player& botPlayer) { return HandleReviveCommand(this, botPlayer.GetName().c_str()); }
+ bool teleport(Player& botPlayer) { return HandleSummonCommand(this, botPlayer.GetName().c_str()); }
+ //bool teleport(Player& botPlayer, WorldObject &obj) { return botPlayer.TeleportTo(obj); }
+ void sysmessage(char const* str) { SendSysMessage(str); }
+ bool dropQuest(char const* str) { return HandleQuestRemove(this, str); }
+private:
+ static bool HandleReviveCommand(ChatHandler* handler, char const* args)
+ {
+ Player* target;
+ uint64 targetGuid;
+ std::string targetName;
+ if (!handler->extractPlayerTarget((char*)args, &target, &targetGuid, &targetName))
+ return false;
+
+ //Player* _player = handler->GetSession()->GetPlayer();
+ if (!target || target->isAlive() || target->InArena())
+ {
+ handler->PSendSysMessage("Resurrection player %s, failed", uint32(GUID_LOPART(targetGuid)));
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ target->ResurrectPlayer(target->InBattleground() ? 1.0f : 0.5f);
+ target->SpawnCorpseBones();
+
+ handler->SetSentErrorMessage(true);
+ return true;
+ }
+
+ static bool HandleSummonCommand(ChatHandler* handler, char const* args)
+ {
+ Player* target;
+ uint64 targetGuid;
+ std::string targetName;
+ if (!handler->extractPlayerTarget((char*)args, &target, &targetGuid, &targetName))
+ return false;
+
+ Player* _player = handler->GetSession()->GetPlayer();
+ if (target == _player || targetGuid == _player->GetGUID())
+ {
+ handler->PSendSysMessage(LANG_CANT_TELEPORT_SELF);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target)
+ {
+ std::string nameLink = handler->playerLink(targetName);
+
+ if (target->IsBeingTeleported())
+ {
+ handler->PSendSysMessage(LANG_IS_TELEPORTED, nameLink.c_str());
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ Map* map = handler->GetSession()->GetPlayer()->GetMap();
+
+ if (map->IsBattlegroundOrArena())
+ {
+ //// only allow if gm mode is on
+ //if (!_player->isGameMaster())
+ //{
+ // handler->PSendSysMessage(LANG_CANNOT_GO_TO_BG_GM, nameLink.c_str());
+ // handler->SetSentErrorMessage(true);
+ // return false;
+ //}
+ // if both players are in different bgs
+ /*else */if (target->GetBattlegroundId() && handler->GetSession()->GetPlayer()->GetBattlegroundId() != target->GetBattlegroundId())
+ target->LeaveBattleground(false); // Note: should be changed so target gets no Deserter debuff
+
+ // all's well, set bg id
+ // when porting out from the bg, it will be reset to 0
+ target->SetBattlegroundId(handler->GetSession()->GetPlayer()->GetBattlegroundId(), handler->GetSession()->GetPlayer()->GetBattlegroundTypeId());
+ // remember current position as entry point for return at bg end teleportation
+ if (!target->GetMap()->IsBattlegroundOrArena())
+ target->SetBattlegroundEntryPoint();
+ }
+ else if (map->IsDungeon())
+ {
+ Map* map = target->GetMap();
+
+ if (map->Instanceable() && map->GetInstanceId() != map->GetInstanceId())
+ target->UnbindInstance(map->GetInstanceId(), target->GetDungeonDifficulty(), true);
+
+ //// we are in instance, and can summon only player in our group with us as lead
+ //if (!handler->GetSession()->GetPlayer()->GetGroup() || !target->GetGroup() ||
+ // (target->GetGroup()->GetLeaderGUID() != handler->GetSession()->GetPlayer()->GetGUID()) ||
+ // (handler->GetSession()->GetPlayer()->GetGroup()->GetLeaderGUID() != handler->GetSession()->GetPlayer()->GetGUID()))
+ // // the last check is a bit excessive, but let it be, just in case
+ //{
+ // handler->PSendSysMessage(LANG_CANNOT_SUMMON_TO_INST, nameLink.c_str());
+ // handler->SetSentErrorMessage(true);
+ // return false;
+ //}
+ }
+
+ //handler->PSendSysMessage(LANG_SUMMONING, nameLink.c_str(), "");
+ //if (handler->needReportToTarget(target))
+ // ChatHandler(target).PSendSysMessage(LANG_SUMMONED_BY, handler->playerLink(_player->GetName()).c_str());
+
+ // stop flight if need
+ if (target->isInFlight())
+ {
+ target->GetMotionMaster()->MovementExpired();
+ target->CleanupAfterTaxiFlight();
+ }
+ // save only in non-flight case
+ else
+ target->SaveRecallPosition();
+
+ // before GM
+ float x, y, z;
+ handler->GetSession()->GetPlayer()->GetClosePoint(x, y, z, target->GetObjectSize());
+ target->TeleportTo(handler->GetSession()->GetPlayer()->GetMapId(), x, y, z, target->GetOrientation(), TELE_TO_GM_MODE);
+ target->SetPhaseMask(handler->GetSession()->GetPlayer()->GetPhaseMask(), true);
+ }
+ else
+ {
+ //std::string nameLink = handler->playerLink(targetName);
+
+ //handler->PSendSysMessage(LANG_SUMMONING, nameLink.c_str(), handler->GetTrinityString(LANG_OFFLINE));
+
+ // in point where GM stay
+ Player::SavePositionInDB(handler->GetSession()->GetPlayer()->GetMapId(),
+ handler->GetSession()->GetPlayer()->GetPositionX(),
+ handler->GetSession()->GetPlayer()->GetPositionY(),
+ handler->GetSession()->GetPlayer()->GetPositionZ(),
+ handler->GetSession()->GetPlayer()->GetOrientation(),
+ handler->GetSession()->GetPlayer()->GetZoneId(),
+ targetGuid);
+ }
+
+ return true;
+ }
+
+ static bool HandleQuestRemove(ChatHandler* handler, const char* args)
+ {
+ Player* player = handler->getSelectedPlayer();
+ if (!player)
+ {
+ handler->SendSysMessage(LANG_NO_CHAR_SELECTED);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ // .removequest #entry'
+ // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r
+ char* cId = handler->extractKeyFromLink((char*)args, "Hquest");
+ if (!cId)
+ return false;
+
+ uint32 entry = atol(cId);
+
+ Quest const* quest = sObjectMgr->GetQuestTemplate(entry);
+
+ if (!quest)
+ {
+ handler->PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ // remove all quest entries for 'entry' from quest log
+ for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
+ {
+ uint32 logQuest = player->GetQuestSlotQuestId(slot);
+ if (logQuest == entry)
+ {
+ player->SetQuestSlot(slot, 0);
+
+ // we ignore unequippable quest items in this case, its' still be equipped
+ player->TakeQuestSourceItem(logQuest, false);
+ }
+ }
+
+ player->RemoveActiveQuest(entry);
+ player->RemoveRewardedQuest(entry);
+
+ handler->SendSysMessage(LANG_COMMAND_QUEST_REMOVED);
+ return true;
+ }
+};
+
+PlayerbotAI::PlayerbotAI(PlayerbotMgr* const mgr, Player* const bot) : _mgr(mgr), me(bot), _classAI(NULL)
+//m_combatOrder(ORDERS_NONE), m_ScenarioType(SCENARIO_PVE),
+//m_TimeDoneEating(0), m_TimeDoneDrinking(0),
+//m_CurrentlyCastingSpellId(0),
+//m_CraftSpellId(0), m_spellIdCommand(0),
+//m_targetGuidCommand(0),
+//m_taxiMaster(0)
+{
+ //m_bDebugCommandChat = _mgr->m_confDebugWhisper;
+
+ //m_targetChanged = false;
+ //m_targetType = TARGET_NORMAL;
+ //m_targetCombat = 0;
+ //m_targetAssist = 0;
+ //m_targetProtect = 0;
+
+ // set collection options
+ //m_collectionFlags = 0;
+ //m_collectDist = _mgr->m_confCollectDistance;
+ //if (_mgr->m_confCollectCombat)
+ // SetCollectFlag(COLLECT_FLAG_COMBAT);
+ //if (_mgr->m_confCollectQuest)
+ // SetCollectFlag(COLLECT_FLAG_QUEST);
+ //if (_mgr->m_confCollectProfession)
+ // SetCollectFlag(COLLECT_FLAG_PROFESSION);
+ //if (_mgr->m_confCollectLoot)
+ // SetCollectFlag(COLLECT_FLAG_LOOT);
+ //if (_mgr->m_confCollectSkin && m_bot->HasSkill(SKILL_SKINNING))
+ // SetCollectFlag(COLLECT_FLAG_SKIN);
+ //if (_mgr->m_confCollectObjects)
+ // SetCollectFlag(COLLECT_FLAG_NEAROBJECT);
+
+ //// set needed item list
+ //SetQuestNeedItems();
+ //SetQuestNeedCreatures();
+
+ //// start following master (will also teleport bot to master)
+ //m_dropWhite = false;
+ //m_AutoEquipToggle = false;
+ //m_FollowAutoGo = FOLLOWAUTOGO_OFF; //turn on bot auto follow distance can be turned off by player
+ //DistOverRide = 0; //set initial adjustable follow settings
+ //IsUpOrDown = 0;
+ //gTempDist = 0.5f;
+ //gTempDist2 = 1.0f;
+ //BotDataRestore();
+ //ClearActiveTalentSpec();
+
+ _botStates = 0;
+ _combatStates = 0;
+ _movementFlags = 0;
+ _followTimer = 0;
+ _followTargetGUID = 0;
+ _waitTimer = 0;
+ _mountTimer = 0;
+ _cureTimer = 0;
+ _selfResTimer = 0;
+ _opponent = NULL;
+ _afterCast = NULL;
+
+ _canSelfRes = false;
+
+ //get class specific ai
+ ReloadClassAI();
+ //init motion
+ AddBotState(BOTSTATE_FOLLOW);
+ //init group (temp)
+ //if (me->GetTeamId() != GetMaster()->GetTeamId())
+ // me->setFaction(GetMaster()->getFaction());
+ _InviteToMastersGroup();
+ //save instance bounds
+ for (uint8 i = REGULAR_DIFFICULTY; i != MAX_DIFFICULTY; ++i)
+ {
+ for (Player::BoundInstancesMap::iterator itr = me->GetBoundInstances(Difficulty(i)).begin(); itr != me->GetBoundInstances(Difficulty(i)).end(); ++itr)
+ {
+ _botBoundInstances[Difficulty(i)][itr->first].perm = itr->second.perm;
+ //reserve current save by creating new (fake) save, any of bot's saves can become invalid in time
+ _botBoundInstances[Difficulty(i)][itr->first].save = new InstanceSave(itr->second.save->GetMapId(), itr->second.save->GetInstanceId(), Difficulty(i), itr->second.save->GetResetTime(), itr->second.save->CanReset());
+ }
+ }
+}
+
+PlayerbotAI::~PlayerbotAI()
+{
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ for (uint8 i = REGULAR_DIFFICULTY; i != MAX_DIFFICULTY; ++i)
+ {
+ Difficulty diff = Difficulty(i);
+ for (Player::BoundInstancesMap::iterator itr = _botBoundInstances[i].begin(); itr != _botBoundInstances[i].end(); ++itr)
+ {
+ if (me->GetBoundInstance(itr->first, diff))
+ me->UnbindInstance(itr->first, diff);
+
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE);
+
+ stmt->setUInt32(0, me->GetGUIDLow());
+ stmt->setUInt32(1, itr->second.save->GetInstanceId());
+ stmt->setBool(2, itr->second.perm);
+
+ trans->Append(stmt);
+ //if after unbind old save is not unloaded bind to old save, else create new save and save to instance mgr
+ //if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(itr->second.save->GetMapId(), itr->second.save->GetInstanceId(), diff, itr->second.save->GetResetTime(), itr->second.save->CanReset(), true))
+ // me->BindToInstance(save, itr->second.perm);
+
+ delete itr->second.save; //delete our old fake save
+ }
+
+ _botBoundInstances[diff].clear();
+ }
+
+ CharacterDatabase.CommitTransaction(trans);
+
+ if (_classAI) delete _classAI;
+ if (_afterCast) delete _afterCast;
+}
+
+void PlayerbotAI::ReloadClassAI()
+{
+ if (_classAI)
+ delete _classAI;
+ _mySpec = PlayerbotMgr::GetSpec(me);
+ switch (me->getClass())
+ {
+ case CLASS_PRIEST:
+ _classAI = (PlayerbotClassAI*) new PlayerbotPriestAI(GetMaster(), me, this);
+ break;
+ case CLASS_MAGE:
+ _classAI = (PlayerbotClassAI*) new PlayerbotMageAI(GetMaster(), me, this);
+ break;
+ case CLASS_WARLOCK:
+ _classAI = (PlayerbotClassAI*) new PlayerbotWarlockAI(GetMaster(), me, this);
+ break;
+ case CLASS_WARRIOR:
+ _classAI = (PlayerbotClassAI*) new PlayerbotWarriorAI(GetMaster(), me, this);
+ break;
+ case CLASS_SHAMAN:
+ _classAI = (PlayerbotClassAI*) new PlayerbotShamanAI(GetMaster(), me, this);
+ break;
+ case CLASS_PALADIN:
+ _classAI = (PlayerbotClassAI*) new PlayerbotPaladinAI(GetMaster(), me, this);
+ break;
+ case CLASS_ROGUE:
+ _classAI = (PlayerbotClassAI*) new PlayerbotRogueAI(GetMaster(), me, this);
+ break;
+ case CLASS_DRUID:
+ _classAI = (PlayerbotClassAI*) new PlayerbotDruidAI(GetMaster(), me, this);
+ break;
+ case CLASS_HUNTER:
+ _classAI = (PlayerbotClassAI*) new PlayerbotHunterAI(GetMaster(), me, this);
+ break;
+ case CLASS_DEATH_KNIGHT:
+ _classAI = (PlayerbotClassAI*) new PlayerbotDeathKnightAI(GetMaster(), me, this);
+ break;
+ }
+
+ //HERB_GATHERING = initSpell(HERB_GATHERING_1);
+ //MINING = initSpell(MINING_1);
+ //SKINNING = initSpell(SKINNING_1);
+}
+
+void PlayerbotAI::UpdateAI(uint32 diff)
+{
+ _doTimers(diff);
+
+ //disabled AI check point 1
+ if (!_classAI || me->IsBeingTeleported() || !me->IsInWorld() || !me->FindMap())
+ return;
+
+ //debug remove root
+ if (!me->HasUnitState(UNIT_STATE_CASTING))
+ {
+ if (me->HasAura(SPELL_ROOT))
+ me->RemoveAura(SPELL_ROOT);
+ //process aftercast
+ //Aftercast should be created in certain cast action
+ //TODO: aftercast pack
+ if (_afterCast != NULL)
+ {
+ _afterCast->LaunchAfterCastCommand();
+ delete _afterCast;
+ _afterCast = NULL;
+ }
+ }
+
+ if (_waitTimer > diff)
+ return;
+
+ _waitTimer = 600 + (_mgr->GetPlayerBotsCount() - 1) * 50;//up to a sec
+
+ //DEATH BEHAVIOUR 99.5%
+ if (me->isDead())
+ {
+ UpdateDeadActions(diff);
+ return;
+ }
+
+ //disabled AI check point 2
+ if (me->isInFlight() || me->isCharmed() || me->GetTrader())
+ return;
+
+ //BreakCC(diff);
+ if (CCed(me)) return;
+
+ //POTIONS/FLASKS/etc. (opt)
+
+ //CURE / HEAL / CLASS SPECIFIC / NON-COMBAT ACTIONS
+ _classAI->UpdateGroupActions(diff);
+
+ //MISC ACTIONS
+
+ //INCOMBAT UPDATE PAI 70% else is CAI
+ if (HasAttackTarget(me->getClass(), _mySpec))
+ {
+ if (me->IsMounted())
+ UpdateMountedState(diff);
+
+ UpdateIncombatActions();
+ return;
+ }
+
+ //OUT OF COMBAT BEHAVIOUR 1%
+ if (HasBotState(BOTSTATE_LOOTING))
+ {
+ //TODO: LOOT
+ }
+ else if (!me->isInCombat())
+ {
+ //TODO: UpdateNonCombatActions();
+ //TODO: UpdateRations();
+ AddBotState(BOTSTATE_FOLLOW);
+ }
+
+ if (HasBotState(BOTSTATE_FOLLOW))
+ UpdateFollowActions(diff);
+
+ UpdateStandState();
+
+ //DELAYED OPERATIONS
+
+ //TODO: TAME
+
+ //TODO: DIRECT CAST
+
+ //TODO: CRAFTING
+
+ //TODO: ORDERS (opt)
+}
+
+void PlayerbotAI::UpdateDeadActions(uint32 diff)
+{
+ if (!_canSelfRes)
+ {
+ _selfResTimer = GetMaster()->isInCombat() ? 2000 : 10000;
+ if (Map* map = GetMaster()->FindMap())
+ if (map->IsRaidOrHeroicDungeon())
+ _selfResTimer /= 2;
+ _canSelfRes = true;
+ return;
+ }
+
+ if (_selfResTimer > diff)
+ return;
+
+ if (me->getDeathState() == CORPSE)
+ {
+ //debug
+ //if (me->GetCorpse())
+ //{
+ // sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: UpdateDeadActions - %s already has a corpse!", me->GetName().c_str());
+ // //m_bot->setDeathState(DEAD);
+ // return;
+ //}
+ //clear loot
+ //m_lootTargets.clear();
+ //m_lootCurrent = 0;
+
+ //has selfRezz
+ if (me->GetUInt32Value(PLAYER_SELF_RES_SPELL))
+ {
+ WorldPacket p;//not really used
+ me->GetSession()->HandleSelfResOpcode(p);
+ return;
+ }
+
+ me->BuildPlayerRepop();
+ me->setDeathState(DEAD);
+ //me->ResetDeathTimer();//release
+ //me->RepopAtGraveyard();
+ }
+ //else if (m_bot->getDeathState() == DEAD) //appears to be unused for players
+ else if (me->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ {
+ Corpse* corpse = me->GetCorpse();
+ //debug
+ if (!corpse)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: UpdateAI - %s has no corpse!", me->GetName().c_str());
+ //m_bot->setDeathState(CORPSE);
+ return;
+ }
+ //check reclaim distance, teleport if needed
+ if (me->GetDistance(corpse) > CORPSE_RECLAIM_RADIUS)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: UpdateAI - Teleport %s to corpse...", me->GetName().c_str());
+ me->TeleportTo(*corpse, TELE_TO_GM_MODE);
+ }
+ //check reclaim delay
+ int32 delay = (corpse->GetGhostTime() + me->GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP)) - time(NULL);
+ if (delay > 0)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: UpdateAI - %s has to wait for %u seconds to revive...", me->GetName().c_str(), uint32(delay));
+ return;
+ }
+ //revive by master
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: UpdateAI - Reviving %s...", me->GetName().c_str());
+ if (!PlayerbotChatHandler(GetMaster()->GetSession()).revive(*me))
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s could not be revived ...", me->GetName().c_str());
+ return;
+ }
+ }
+ _canSelfRes = false;
+}
+
+void PlayerbotAI::UpdateIncombatActions()
+{
+ //SHEATH
+ if (!me->HasUnitState(UNIT_STATE_CASTING) && !me->IsMounted())
+ {
+ SheathState sheath = ShouldRanged() ? SHEATH_STATE_RANGED : SHEATH_STATE_MELEE;
+ if (me->GetSheath() != sheath)
+ me->SetSheath(sheath);
+ }
+
+ //CC CAI full
+ { }
+
+ //handle target
+ if (Unit* u = me->getVictim())
+ {
+ //POSITION
+ if (me->GetDistance2d(u) > GetMinAttackRange(u) && !ShouldStay())
+ {
+ GetInPosition(true, ShouldRanged());
+ //return;
+ }
+ //ANGLE (emulated)
+ else if (!me->HasInArc(M_PI*0.5f, u))
+ {
+ float x = me->GetPositionX() - (me->GetPositionX() - u->GetPositionX())*0.05f;
+ float y = me->GetPositionY() - (me->GetPositionY() - u->GetPositionY())*0.05f;
+ float z = me->GetPositionZ() - (me->GetPositionZ() - u->GetPositionZ())*0.05f;
+ me->GetMotionMaster()->MovePoint(me->GetMapId(), x, y, z);
+ //me->SetFacingToObject(u);
+ }
+
+ //ATTACK CAI full
+ _classAI->DoCombatActions();
+
+ return;
+ }
+
+ //no target
+ ClearBotState(BOTSTATE_COMBAT);
+}
+
+void PlayerbotAI::UpdateFollowActions(uint32 diff)
+{
+ if (_followTimer > diff)
+ return;
+
+ _followTimer = 2000;
+
+ if (me->HasUnitState(UNIT_STATE_CASTING) || CCed(me, true))
+ return;
+
+ if (me->getVictim())
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s tried following while still fighting!", me->GetName().c_str());
+ return;
+ }
+
+ //debug
+ if ((CanFollowOthers()) != (_followTargetGUID != 0))
+ {
+ if (CanFollowOthers() && _followTargetGUID == 0)
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s should follow someone else but has no target specified!", me->GetName().c_str());
+ else if (!CanFollowOthers() && _followTargetGUID != 0)
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s has follow target specified but cannot follow it!", me->GetName().c_str());
+ //force follow master
+ _followTargetGUID = 0;
+ ClearMovementFlag(MOVEMENT_FLAG_TARGET_CHANGED);
+ }
+
+ //get follow target:
+ //a) current follow target (selected)
+ //b) follow target set by master
+ //c) master
+ //if player target is dead use it's corpse as follow target
+ WorldObject* fTarget = NULL;
+ if (uint64 sel = me->GetSelection())
+ {
+ if (!me->GetGroup() || me->GetGroup()->IsMember(sel))
+ fTarget = sObjectAccessor->GetPlayer(*me, sel);
+ }
+ else if (Group* group = me->GetGroup())
+ {
+ if (group == GetMaster()->GetGroup())
+ fTarget = GetMaster();
+ }
+ else if (_followTargetGUID != 0)
+ {
+ fTarget = sObjectAccessor->GetPlayer(*me, _followTargetGUID);
+ if (fTarget->GetTypeId() == TYPEID_PLAYER && fTarget->ToPlayer()->isDead() && fTarget->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ fTarget = fTarget->ToPlayer()->GetCorpse();
+ }
+ else
+ {
+ if (GetMaster()->isDead() && GetMaster()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
+ fTarget = GetMaster()->GetCorpse();
+ else
+ fTarget = GetMaster();
+ }
+
+ if (!fTarget)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s has no follow target! Set to master...", me->GetName().c_str());
+ fTarget = GetMaster();
+ _followTargetGUID = 0;
+ ClearMovementFlag(MOVEMENT_FLAG_TARGET_CHANGED);
+ }
+
+ //unsafe
+ if (me->GetPhaseMask() != fTarget->GetPhaseMask())
+ me->SetPhaseMask(fTarget->GetPhaseMask(), true);
+
+ Player* fPlayer = fTarget->ToPlayer();
+
+ //flight check (all such checks should go here)
+ if (!fTarget->IsInWorld() || !fTarget->FindMap() ||
+ (fPlayer && (fPlayer->isInFlight() || fPlayer->IsBeingTeleported())))
+ {
+ _followTimer = 5000;
+ return;
+ }
+
+ //teleport if too far away
+ Map const* const mymap = me->GetMap();
+ Map const* const tarmap = fTarget->GetMap();
+ if (mymap != tarmap || me->GetDistance2d(fTarget) > sWorld->GetMaxVisibleDistanceOnContinents())
+ {
+ if (fPlayer && tarmap->Instanceable() && mymap != tarmap)
+ {
+ Difficulty diff = fPlayer->GetDifficulty(tarmap->IsRaid());
+ if (InstancePlayerBind* bind = me->GetBoundInstance(tarmap->GetId(), diff))
+ _UnbindInstance(tarmap->GetId(), diff);
+ }
+ me->TeleportTo(*fTarget, TELE_TO_GM_MODE);
+ return;
+
+ }
+
+ //sheath additive
+ if (me->GetSheath() != SHEATH_STATE_UNARMED && _mgr->Rand() < 30)
+ me->SetSheath(SHEATH_STATE_UNARMED);
+
+ if (!me->HasUnitState(UNIT_STATE_FOLLOW))
+ SetMovement(fTarget);
+
+ //only if following master
+ if (fTarget->GetGUID() == GetMaster()->GetGUID())
+ UpdateMountedState(diff);
+}
+
+void PlayerbotAI::UpdateStandState()
+{
+ //only if following master
+ if (_followTargetGUID != 0)
+ return;
+
+ if (GetMaster()->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 (GetMaster()->getStandState() == UNIT_STAND_STATE_SIT && me->getStandState() == UNIT_STAND_STATE_STAND &&
+ !me->isInCombat() && !me->isMoving() && !me->IsMounted())
+ me->SetStandState(UNIT_STAND_STATE_SIT);
+}
+
+void PlayerbotAI::UpdateMountedState(uint32 diff)
+{
+ //dismount
+ if (!GetMaster()->IsMounted() || ((me->isInCombat() || !me->getAttackers().empty()) && HasBotState(BOTSTATE_COMBAT)))
+ {
+ if (me->HasAuraType(SPELL_AURA_MOUNTED))
+ me->RemoveAurasByType(SPELL_AURA_MOUNTED);
+ return;
+ }
+
+ if (_mountTimer > diff)
+ return;
+
+ _mountTimer = 500;
+
+ //cannot mount in water, in combat, if mounted somehow (some encounter)
+ if (me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) || me->isInCombat() || me->IsMounted())
+ return;
+
+ //TODO: HUGE
+ //Player Part
+ Unit::AuraEffectList const& auraList = GetMaster()->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
+ if (!auraList.empty())
+ {
+ SpellInfo const* spellInfo = auraList.front()->GetSpellInfo();
+ if (!spellInfo)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s tried to mount but master %s is mounted by spell with no spellInfo!", me->GetName().c_str(), GetMaster()->GetName().c_str());
+ return;
+ }
+
+ //Bot Part
+ uint32 spellMount = 0;
+ //cheap check if we know this spell
+ for (PlayerSpellMap::iterator itr = me->GetSpellMap().begin(); itr != me->GetSpellMap().end(); ++itr)
+ {
+ //if (itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled)
+ // continue;
+
+ //cheap check if we just have the same mount
+ uint32 spellId = itr->first;
+ if (spellInfo->Id == spellId)
+ {
+ spellMount = spellId;
+ break;
+ }
+ }
+ if (!spellMount)
+ {
+ //analyze and find proper mount spell
+ for (PlayerSpellMap::iterator itr = me->GetSpellMap().begin(); itr != me->GetSpellMap().end(); ++itr)
+ {
+ //if (itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled)
+ // continue;
+ uint32 spellId = itr->first;
+ SpellInfo const* bSpellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!bSpellInfo || bSpellInfo->IsPassive())
+ continue;
+
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ {
+ if (bSpellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOUNTED)
+ {
+ //arrange values
+ int8 j = i-1, k = i+1;
+ if (j < 0)// i == 0
+ j = k+1;//2
+ else if (k >= MAX_SPELL_EFFECTS)// i == 2
+ k = j-1;//0
+
+ if (bSpellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)
+ {
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].BasePoints == bSpellInfo->Effects[j].BasePoints)
+ {
+ spellMount = spellId;
+ break;
+ }
+ }
+ if (spellMount)
+ break;
+ }
+ else if (bSpellInfo->Effects[k].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)
+ {
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].BasePoints == bSpellInfo->Effects[k].BasePoints)
+ {
+ spellMount = spellId;
+ break;
+ }
+ }
+ if (spellMount)
+ break;
+ }
+ else if (bSpellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED)
+ {
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].BasePoints == bSpellInfo->Effects[j].BasePoints)
+ {
+ spellMount = spellId;
+ break;
+ }
+ }
+ if (spellMount)
+ break;
+ }
+ else if (bSpellInfo->Effects[k].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED)
+ {
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].BasePoints == bSpellInfo->Effects[k].BasePoints)
+ {
+ spellMount = spellId;
+ break;
+ }
+ }
+ if (spellMount)
+ break;
+ }
+ }
+ }
+ if (spellMount)
+ break;
+ }
+ }
+ if (spellMount)
+ {
+ CastSpell(me, spellMount);
+ }
+ else
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s cannot find a proper mount!", me->GetName().c_str());
+ //SendWhisper("Cannot find approriate mount!", *GetMaster());
+ }
+ }
+}
+
+bool PlayerbotAI::HasAttackTarget(uint8 /*myclass*/, uint32 myspec)
+{
+ bool ranged = false;
+ bool byspell = false;
+ bool reset = false;
+
+ //choose proper targeting mode
+ //step 1: by spec
+ //step 2: check conditions
+ switch (MainSpec(myspec))
+ {
+ case MAGE_SPEC_FIRE:
+ case MAGE_SPEC_FROST:
+ case MAGE_SPEC_ARCANE:
+ case PRIEST_SPEC_DISCIPLINE:
+ case PRIEST_SPEC_HOLY:
+ case PRIEST_SPEC_SHADOW:
+ case SHAMAN_SPEC_ELEMENTAL:
+ case SHAMAN_SPEC_RESTORATION:
+ case DRUID_SPEC_RESTORATION:
+ case DRUID_SPEC_BALANCE:
+ case WARLOCK_SPEC_DESTRUCTION:
+ case WARLOCK_SPEC_AFFLICTION:
+ case WARLOCK_SPEC_DEMONOLOGY:
+ {
+ ranged = true;
+ byspell = true;
+ break;
+ }
+ case PALADIN_SPEC_HOLY:
+ //case PALADIN_SPEC_PROTECTION:
+ {
+ byspell = true;
+ break;
+ }
+ case HUNTER_SPEC_BEASTMASTERY:
+ case HUNTER_SPEC_SURVIVAL:
+ case HUNTER_SPEC_MARKSMANSHIP:
+ {
+ ranged = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ //TODO: wand (casters) / melee (hunter)
+ //switch (myclass)
+ //{
+ ////wand users
+ //case CLASS_WARLOCK:
+ //case CLASS_PRIEST:
+ //case CLASS_MAGE:
+ //{
+ // if (GetManaPCT(me) < 5)
+ // {
+ // }
+ //}
+
+ //}
+
+ _opponent = GetAttackTarget(byspell, ranged, reset);
+
+ if (!_opponent)
+ {
+ me->AttackStop();
+ ClearBotState(BOTSTATE_COMBAT);
+ return false;
+ }
+
+ AddBotState(BOTSTATE_COMBAT);
+ if (_opponent->GetTypeId() == TYPEID_PLAYER)
+ AddCombatState(COMBAT_STATE_PVP);
+ ClearBotState(BOTSTATE_FOLLOW);
+ if (reset)
+ AddCombatState(COMBAT_STATE_RESET);
+
+ if (me->IsMounted())
+ UpdateMountedState(0);//now
+
+ if (!me->IsMounted() && _opponent != me->getVictim())
+ me->Attack(_opponent, !ranged);
+
+ return true;
+}
+
+Unit* PlayerbotAI::GetAttackTarget(bool /*ranged*/, bool byspell, bool &reset) const
+{
+ //Check if need to assist master
+ Unit* u = GetMaster()->getVictim();
+ Unit* mytar = me->getVictim();
+
+ //1) heck for common target
+ if (u && u == mytar)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): bot %s continues attack common target %s", me->GetName().c_str(), u->GetName().c_str());
+ return u;
+ }
+
+ //TODO:
+ ////Follow if...
+ //uint8 followdist = master->GetBotFollowDist();
+ //float foldist = _getAttackDistance(float(followdist));
+ //if (!u && master->isAlive() && (me->GetDistance(master) > foldist || (mytar && master->GetDistance(mytar) > foldist && me->GetDistance(master) > foldist)))
+ //{
+ // //sLog->outError(LOG_FILTER_PLAYER, "bot %s cannot attack target %s, too far away", me->GetName().c_str(), mytar ? mytar->GetName().c_str() : "");
+ // return NULL;
+ //}
+
+ //2) accuring master's target (new target, if have no target or attacking something else)
+ if (u && (GetMaster()->isInCombat() || u->isInCombat()) && !IsUnitInDuel(u, GetMaster()) && !IsUnitInPlayersParty(u, GetMaster()))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): bot %s starts attack master's target %s", me->GetName().c_str(), u->GetName().c_str());
+ return u;
+ }
+
+ if (CanPlayerbotAttack(me, mytar, byspell)/* && !IsUnitInDuel(mytar, GetMaster())*/)
+ {
+ //if (me->GetDistance(mytar) > (ranged ? 20.f : 5.f) && m_botCommandState != COMMAND_STAY && m_botCommandState != COMMAND_FOLLOW)
+ reset = (me->GetDistance(mytar) > GetMinAttackRange(mytar));
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): bot %s continues attack its target %s (reset = %u)", me->GetName().c_str(), mytar->GetName().c_str(), uint8(reset));
+ return mytar;
+ }
+
+ //if (followdist == 0 && master->isAlive())
+ // return NULL; //do not bother
+
+ //Check group
+ //1) create playerlist
+ std::set<Player*> playerSet;
+ Group const* gr = GetMaster()->GetGroup();
+ if (!gr)
+ {
+ playerSet.insert(GetMaster());
+ for (PlayerBotMap::const_iterator itr = _mgr->GetPlayerBotsBegin(); itr != _mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ Player* pl = itr->second;
+ if (!pl || !pl->IsInWorld() || pl->IsBeingTeleported() || me->GetMap() != pl->FindMap() || !pl->InSamePhase(me))
+ continue;
+ playerSet.insert(itr->second);
+ }
+ }
+ else
+ {
+ for (GroupReference const* ref = gr->GetFirstMember(); ref != NULL; ref = ref->next())
+ {
+ Player* pl = ref->getSource();
+ if (!pl || !pl->IsInWorld() || pl->IsBeingTeleported() || me->GetMap() != pl->FindMap() || !pl->InSamePhase(me))
+ continue;
+ playerSet.insert(pl);
+ for (PlayerBotMap::const_iterator itr = _mgr->GetPlayerBotsBegin(); itr != _mgr->GetPlayerBotsEnd(); ++itr)
+ {
+ pl = itr->second;
+ if (!pl || !pl->IsInWorld() || pl->IsBeingTeleported() || me->GetMap() != pl->FindMap() || !pl->InSamePhase(me))
+ continue;
+ playerSet.insert(itr->second);
+ }
+ }
+ }
+ //2) cycle through to find proper target; check players and their npcbots (and their bot pets)
+ for (std::set<Player*>::const_iterator itr = playerSet.begin(); itr != playerSet.end(); ++itr)
+ {
+ Player* pl = (*itr);
+ u = pl->getVictim();
+ if (u && pl != GetMaster() && CanPlayerbotAttack(me, u, byspell) && (pl->isInCombat() || u->isInCombat()))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): 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;
+ for (uint8 i = 0; i != pl->GetMaxNpcBots(); ++i)
+ {
+ Creature* bot = pl->GetBotMap(i)->_Cre();
+ if (!bot || !bot->IsInWorld() || me->GetMap() != bot->FindMap() || !bot->InSamePhase(me))
+ continue;
+ u = bot->getVictim();
+ if (u && CanPlayerbotAttack(me, u, byspell) && (bot->isInCombat() || u->isInCombat()))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): 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 && CanPlayerbotAttack(me, u, byspell) && (pet->isInCombat() || u->isInCombat()))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): 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 = sWorld->GetMaxVisibleDistanceOnContinents();
+ //first cycle we search non-cced target, then, if not found, check all
+ for (uint8 i = 0; i != 2; ++i)
+ {
+ if (!t)
+ {
+ bool attackCC = bool(i);
+
+ CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY()));
+ Cell cell(p);
+ cell.SetNoCreate();
+
+ NearestHostileUnitCheck check(me, maxdist, byspell, NULL, attackCC);
+ Trinity::UnitLastSearcher <NearestHostileUnitCheck> searcher(me, t, check);
+ me->VisitNearbyWorldObject(maxdist, searcher);
+
+ TypeContainerVisitor<Trinity::UnitLastSearcher <NearestHostileUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
+ TypeContainerVisitor<Trinity::UnitLastSearcher <NearestHostileUnitCheck>, 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);
+ }
+ }
+
+ //Duel check, should come after common target
+ //here we don't check anything (yet)
+ if (!t)
+ {
+ t = me->duel ? me->duel->opponent : NULL;
+ }
+
+ if (t)
+ {
+ if (_opponent && t != _opponent)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::GetAttackTarget(): bot %s has Found a new target %s", me->GetName().c_str(), t->GetName().c_str());
+ reset = true;
+ }
+ }
+
+ return t;
+}
+
+void PlayerbotAI::GetInPosition(bool force, bool ranged, Unit* target, Position* /*pos*/)
+{
+ if (me->HasUnitState(UNIT_STATE_CASTING) || CCed(me, true))
+ return;
+ if (!target)
+ target = me->getVictim();
+ if (!target)
+ return;
+ if (!target->isInCombat() && !force)
+ return;
+
+ me->GetMotionMaster()->MoveChase(target, ShouldRanged() ? GetMinAttackRange(target) : 0);
+
+ //TODO: Position
+ if (!me->HasUnitState(UNIT_STATE_MELEE_ATTACKING) || target != me->getVictim())
+ me->Attack(target, !ranged);
+}
+
+void PlayerbotAI::SetMovement(WorldObject* followTarget)
+{
+ if (!followTarget || !followTarget->IsInWorld() || me->GetMap() != followTarget->FindMap())
+ return;
+
+ //outrun case should be handled outside, with teleport
+ if (me->GetDistance(followTarget) > sWorld->GetMaxVisibleDistanceOnContinents())
+ return;
+
+ if (me->HasUnitState(UNIT_STATE_CASTING) || CCed(me, true))
+ return;
+
+ //force selection
+ //if (me->GetSelection() != followTarget->GetGUID())
+ // me->SetSelection(followTarget->GetGUID());
+
+ //can we actually move?
+ bool canMove = me->isInCombat() ? !ShouldStay() : CanMove();
+ if (!canMove)
+ {
+ if (!me->HasInArc(M_PI*0.8f, followTarget))
+ {
+ me->SetFacingToObject(followTarget);
+ me->SendUpdateToPlayer(GetMaster());
+ }
+ return;
+ }
+
+ //pick proper follow mode
+ bool chase = false;
+ Unit* unit = followTarget->ToUnit();
+ if (unit)
+ chase = HasBotState(BOTSTATE_COMBAT);
+ if (chase)
+ {
+ me->GetMotionMaster()->MoveChase(unit);
+ }
+ else
+ {
+ //TODO: angle
+ if (unit)
+ me->GetMotionMaster()->MoveFollow(unit, frand(_mgr->m_confFollowDistance[0], _mgr->m_confFollowDistance[1]), frand(0, 2*M_PI));
+ else //corpse etc.
+ {
+ float x(0),y(0),z(0);
+ followTarget->GetNearPoint(me, x, y, z, 0.f, frand(_mgr->m_confFollowDistance[0], _mgr->m_confFollowDistance[1]), frand(0, 2*M_PI));
+ me->GetMotionMaster()->MovePoint(followTarget->GetMapId(), x, y, z);
+ }
+ }
+}
+
+uint32 PlayerbotAI::InitSpell(Player* caster, uint32 baseSpell)
+{
+ if (baseSpell == 0 || !caster || !caster->HasSpell(baseSpell))
+ return 0;
+
+ uint32 next = 0;
+ SpellChainNode const* Node = sSpellMgr->GetSpellChainNode(baseSpell);
+ next = Node && Node->next ? Node->next->Id : 0;
+
+ if (next == 0 || !caster->HasSpell(next)) //next spell in chain doesn't exist or unavailable
+ return baseSpell;
+
+ //SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(baseSpell);
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI::InitSpell() proceed spell %u (%s): forwarding to %u (%s)", baseSpell, spellInfo->SpellName[0], next, sSpellMgr->GetSpellInfo(next)->SpellName[0]);
+ return InitSpell(caster, next);
+}
+
+bool PlayerbotAI::CastSpell(Unit* victim, uint32 spellId) const
+{
+ return CastSpell(victim, sSpellMgr->GetSpellInfo(spellId));
+}
+
+bool PlayerbotAI::CastSpell(Unit* victim, SpellInfo const* spellInfo) const
+{
+ ASSERT(victim);
+
+ if (!spellInfo)
+ return false;
+ if (!victim->IsInWorld() || me->GetPhaseMask() != victim->GetPhaseMask() || me->GetMap() != victim->FindMap())
+ return false;
+ //cooldown
+ if (me->HasSpellCooldown(spellInfo->Id))
+ return false;
+ //GCD
+ if (me->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
+ return false;
+ //power cost
+ if (int32(me->GetPower(Powers(spellInfo->PowerType))) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()))
+ return false;
+ //range
+ bool friendly = me->IsFriendlyTo(victim);
+ if (me->GetDistance(victim) > spellInfo->GetMaxRange(friendly, me))
+ return false;
+ //debug
+ if (spellInfo->HasEffect(SPELL_EFFECT_OPEN_LOCK) ||
+ spellInfo->HasEffect(SPELL_EFFECT_SKINNING) ||
+ spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM) ||
+ spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s trying to cast wrong spell on unit! %s (%u) on %s", me->GetName().c_str(), spellInfo->SpellName[uint8(GetMaster()->GetSession()->GetSessionDbcLocale())], spellInfo->Id, victim->GetName().c_str());
+ //return false;
+ }
+
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s casts: %s (%u) on %s", me->GetName().c_str(), spellInfo->SpellName[uint8(GetMaster()->GetSession()->GetSessionDbcLocale())], spellInfo->Id, victim->GetName().c_str());
+
+ //everything checked, prepare character for cast
+ //LoS - cheaty ;)
+ if (friendly && me->IsWithinLOSInMap(victim))
+ me->Relocate(victim);
+ //face target - just in case
+ if (victim != me/* && !me->HasInArc(M_PI * 0.25f, victim)*/)
+ {
+ me->SetFacingToObject(victim);
+ me->SendUpdateToPlayer(GetMaster());
+ }
+
+ Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
+
+ //stop moving
+ if (spellInfo->CalcCastTime(me, spell) != 0)
+ {
+ me->StopMoving();
+ //debug add root (removed in SPELL_GO)
+ me->AddAura(SPELL_ROOT, me);
+ }
+
+ delete spell;
+
+ me->CastSpell(victim, spellInfo);
+ //if (spell->CheckCast(true) != SPELL_CAST_OK)
+ //{
+ // spell->finish(false);
+ // delete spell;
+ // return false;
+ //}
+ //SpellCastTargets targets;
+ //targets.SetUnitTarget(victim);
+ //spell->prepare(&targets);
+
+ //check cast success
+ if (GetCurrentSpell() == spell)
+ {
+ //after cast action setup
+ //SHOULD BE MOVED TO CERTAIN AI
+ //if (afterCast && _afterCast == NULL)
+ //{
+ // _afterCast = new AfterCast();
+ // _afterCast->SetTarget(afterTarget);
+ // _afterCast->SetAfterCastCommand(afterCast);
+ //}
+ return true;
+ }
+
+ return false;
+}
+
+bool PlayerbotAI::CastSpell(Item* item, uint32 spellId) const
+{
+ return CastSpell(item, sSpellMgr->GetSpellInfo(spellId));
+}
+
+bool PlayerbotAI::CastSpell(Item* item, SpellInfo const* spellInfo) const
+{
+ ASSERT(item);
+
+ if (!spellInfo || me->HasSpellCooldown(spellInfo->Id) ||
+ me->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo) ||
+ int32(me->GetPower(Powers(spellInfo->PowerType))) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()))
+ return false;
+ //debug
+ if (!(spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM) ||
+ spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) ||
+ spellInfo->HasEffect(SPELL_EFFECT_DISENCHANT)))
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s trying to cast wrong spell on item! %s (%u) on %s", me->GetName().c_str(), spellInfo->SpellName[uint8(GetMaster()->GetSession()->GetSessionDbcLocale())], spellInfo->Id, item->GetTemplate()->Name1.c_str());
+ return false;
+ }
+
+ sLog->outError(LOG_FILTER_PLAYER, "PlayerbotAI: %s casts: %s (%u) on %s", me->GetName().c_str(), spellInfo->SpellName[uint8(GetMaster()->GetSession()->GetSessionDbcLocale())], spellInfo->Id, item->GetTemplate()->Name1.c_str());
+
+ Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
+
+ if (spellInfo->CalcCastTime(me, spell) != 0)
+ {
+ me->StopMoving();
+ me->AddAura(SPELL_ROOT, me);
+ }
+
+ delete spell;
+
+ SpellCastTargets targets;
+ targets.SetItemTarget(item);
+ targets.SetUnitTarget(item->GetOwner());
+ me->CastSpell(targets, spellInfo, NULL);
+
+ return (GetCurrentSpell() == spell);
+}
+
+void PlayerbotAI::CureGroup(Player* player, uint32 cureSpell, const uint32 diff)
+{
+ if (!cureSpell || !player)
+ return;
+
+ if (_cureTimer > diff)
+ return;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(cureSpell);
+ if (!spellInfo)
+ return;
+ if (me->HasSpellCooldown(spellInfo->Id))
+ return;
+ if (me->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
+ return;
+ if (int32(me->GetPower(Powers(spellInfo->PowerType))) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()))
+ return;
+ if (!IsUnitInPlayersParty(player, GetMaster()))
+ return;
+
+ Group const* gr = player->GetGroup();
+ if (!gr)
+ {
+ if (_CureTarget(player, spellInfo))
+ return;
+ if (Pet* pet = player->GetPet())
+ if (_CureTarget(pet, spellInfo))
+ return;
+ if (!player->HaveBot())
+ return;
+
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ if (_CureTarget(player->GetBotMap(i)->_Cre(), spellInfo))
+ return;
+ }
+ else
+ {
+ bool bots = false;
+ Player* tPlayer = NULL;
+ for (GroupReference const* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ tPlayer = itr->getSource();
+ if (!tPlayer || (!tPlayer->isAlive() && !tPlayer->HaveBot()))
+ continue;
+ if (!bots && tPlayer->HaveBot())
+ bots = true;
+ if (_CureTarget(tPlayer, spellInfo))
+ return;
+ if (Pet* pet = tPlayer->GetPet())
+ if (_CureTarget(pet, spellInfo))
+ return;
+ }
+ if (bots)
+ {
+ for (GroupReference const* itr = gr->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ tPlayer = itr->getSource();
+ if (!tPlayer || !tPlayer->HaveBot())
+ continue;
+ for (uint8 i = 0; i != tPlayer->GetMaxNpcBots(); ++i)
+ if (_CureTarget(tPlayer->GetBotMap(i)->_Cre(), spellInfo))
+ return;
+ }
+ }
+ }
+
+ //if no target found (basically) set check delay to prevent useless cycle
+ _cureTimer = 2000;
+}
+
+inline bool PlayerbotAI::_CureTarget(Unit* target, SpellInfo const* spellInfo) const
+{
+ return _CanCureTarget(target, spellInfo) ? CastSpell(target, spellInfo) : false;
+}
+
+bool PlayerbotAI::_CanCureTarget(Unit* target, SpellInfo const* spellInfo) const
+{
+ if (!target || !spellInfo)
+ return false;
+
+ if (!target->isAlive())
+ return false;
+
+ if (!target->IsInWorld() ||
+ me->GetMap() != target->FindMap() ||
+ me->GetPhaseMask() != target->GetPhaseMask())
+ return false;
+
+ if (Player* player = target->ToPlayer())
+ if (player->IsBeingTeleported())
+ return false;
+
+ if (me->GetDistance(target) > spellInfo->RangeEntry->maxRangeFriend)
+ return false;
+
+ if (!IsUnitInPlayersParty(target, GetMaster()))
+ return false;
+
+ uint32 dispelMask = 0;
+ for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_DISPEL)
+ dispelMask |= SpellInfo::GetDispelMask(DispelType(spellInfo->Effects[i].MiscValue));
+
+ if (dispelMask == 0)
+ return false;
+
+ DispelChargesList dispel_list;
+ target->GetDispellableAuraList(me, dispelMask, dispel_list);
+
+ return (!dispel_list.empty());
+}
+
+void PlayerbotAI::OnBotOutgoingPacket(WorldPacket const& packet)
+{
+ switch (packet.GetOpcode())
+ {
+ case SMSG_GROUP_INVITE:
+ {
+ if (me->GetGroup())
+ return;
+
+ if (Group* grp = me->GetGroupInvite())
+ {
+ //Player const* inviter = sObjectAccessor->FindPlayer(grp->GetLeaderGUID());
+ //if (!inviter)
+ // return;
+
+ WorldPacket p;
+ if (grp->GetLeaderGUID() != GetMaster()->GetGUID())//(!canObeyCommandFrom(*inviter))
+ {
+ std::string buf = "I can't accept your invite unless you first invite my master ";
+ buf += GetMaster()->GetName();
+ buf += ".";
+ me->Whisper(buf, LANG_UNIVERSAL, grp->GetLeaderGUID());//SendWhisper(buf, *inviter);
+ me->GetSession()->HandleGroupDeclineOpcode(p); // packet not used
+ }
+ else
+ {
+ //if (m_bot->IsFriendlyTo(inviter))
+ {
+ //DO NOT UNCOMMENT
+ //p.read_skip<uint32>();
+
+ grp->RemoveInvite(me);
+
+ if (grp->IsFull())
+ {
+ me->GetSession()->SendPartyResult(PARTY_OP_INVITE, "", ERR_GROUP_FULL);
+ return;
+ }
+
+ Player* leader = sObjectAccessor->FindPlayer(grp->GetLeaderGUID());
+
+ if (!grp->IsCreated())
+ {
+ if (!leader)
+ {
+ grp->RemoveAllInvites();
+ return;
+ }
+
+ grp->RemoveInvite(leader);
+ grp->Create(leader);
+ sGroupMgr->AddGroup(grp);
+ }
+
+ if (!grp->AddMember(me))
+ return;
+
+ grp->BroadcastGroupUpdate();
+ }
+ //else
+ //{
+ // //TODO: custom group section
+ //}
+ }
+ }
+ return;
+ }
+ }
+}
+
+void PlayerbotAI::HandleTeleportAck()
+{
+ me->GetMotionMaster()->Clear(true);
+ if (me->IsBeingTeleportedNear())
+ {
+ WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 8 + 4 + 4);
+ p.appendPackGUID(me->GetGUID());
+ p << uint32(0); // cast flags
+ p << uint32(time(NULL)); // time - not used
+ me->GetSession()->HandleMoveTeleportAck(p);
+ }
+ else if (me->IsBeingTeleportedFar())
+ me->GetSession()->HandleMoveWorldportAckOpcode();
+}
+
+bool PlayerbotAI::CanPlayerbotAttack(Player* bot, Unit* target, int8 byspell)
+{
+ if (!target || !bot || !bot->IsPlayerBot()) return false;
+ Player* master = bot->GetPlayerbotAI()->GetMaster();
+ if (!master)
+ return false;
+ return
+ (target->isAlive() && target->IsVisible() && target->isTargetableForAttack() && !IsUnitInPlayersParty(target, master) &&
+ (target->IsHostileTo(master) || target->IsHostileTo(bot) ||
+ (target->GetReactionTo(master) < REP_FRIENDLY && master->getVictim() == target && (master->isInCombat() || target->isInCombat()))) && //master has pointed this target
+ //target->IsWithinLOSInMap(me) &&
+ (byspell < 0 || !target->IsImmunedToDamage(byspell ? SPELL_SCHOOL_MASK_MAGIC : SPELL_SCHOOL_MASK_NORMAL)));
+}
+
+bool PlayerbotAI::IsUnitInDuel(Unit* target, Player* master)
+{
+ if (!target) return false;
+ bool isbot = target->GetTypeId() == TYPEID_UNIT && (target->ToCreature()->GetIAmABot() || target->ToCreature()->GetIAmABotsPet());
+ Player* player = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer() : isbot ? target->ToCreature()->GetBotOwner() : NULL;
+ if (!player)
+ {
+ if (!target->IsControlledByPlayer())
+ return false;
+ player = target->GetCharmerOrOwnerPlayerOrPlayerItself();
+ }
+
+ return (player && player->duel && (IsUnitInPlayersParty(player, master) || IsUnitInPlayersParty(player->duel->opponent, master)));
+}
+
+bool PlayerbotAI::IsUnitInPlayersParty(Unit* unit, Player* master)
+{
+ if (!unit || !master) return false;
+ if (unit == master) return true;
+
+ //group member case (any player / any npcbot)
+ if (Group* gr = master->GetGroup())
+ if (gr->IsMember(unit->GetGUID()))
+ return true;
+
+ //Player-controlled creature case
+ if (Creature* cre = unit->ToCreature())
+ {
+ //npcbot/npcbot's pet case
+ if (Player* owner = cre->GetBotOwner())
+ {
+ //master's npcbot's pet
+ if (owner == master)
+ return true;
+ else
+ {
+ //playerbot's npcbot's pet
+ if (owner->IsPlayerBot())
+ {
+ //master's playerbot's npcbot's pet
+ if (PlayerbotMgr* mgr = master->GetPlayerbotMgr())
+ if (mgr->GetPlayerBot(unit->GetGUID()))
+ return true;
+ //other player's playerbot's npcbot's pet
+ if (master->GetGroup() && master->GetGroup()->IsMember(owner->GetPlayerbotAI()->GetMaster()->GetGUID()))
+ return true;
+ }
+ }
+ }
+ //pets, minions, guardians etc.
+ else
+ {
+ uint64 ownerGuid = unit->GetOwnerGUID();
+ //controlled by group member
+ if (Group* gr = master->GetGroup())
+ if (gr->IsMember(ownerGuid))
+ return true;
+ //controlled by playerbot
+ owner = sObjectAccessor->FindPlayer(ownerGuid);
+ if (owner && owner->IsPlayerBot())
+ {
+ //master's playerbot
+ if (PlayerbotMgr* mgr = master->GetPlayerbotMgr())
+ if (mgr->GetPlayerBot(unit->GetGUID()))
+ return true;
+ //other player's playerbot
+ if (master->GetGroup() && master->GetGroup()->IsMember(owner->GetPlayerbotAI()->GetMaster()->GetGUID()))
+ return true;
+ }
+ }
+ }
+ //Player-controlled player case
+ else if (Player* bot = unit->ToPlayer())
+ {
+ //Playerbot case
+ if (bot->IsPlayerBot())
+ {
+ //master's playerbot
+ if (PlayerbotMgr* mgr = master->GetPlayerbotMgr())
+ if (mgr->GetPlayerBot(unit->GetGUID()))
+ return true;
+ //other player's playerbot
+ if (master->GetGroup() && master->GetGroup()->IsMember(bot->GetPlayerbotAI()->GetMaster()->GetGUID()))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline void PlayerbotAI::Tell(std::string const& text, uint64 targetGUID)
+{
+ if (!targetGUID)
+ targetGUID = GetMaster()->GetGUID();
+ if (text.empty() || !IS_PLAYER_GUID(targetGUID))
+ return;
+
+ me->Whisper(text, LANG_UNIVERSAL, targetGUID);
+}
+
+Player* PlayerbotAI::GetMaster() const
+{
+ return _mgr->GetMaster();
+}
+
+inline bool PlayerbotAI::IsMoving() const
+{
+ return (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE ? false : true);
+}
+
+inline Spell* PlayerbotAI::GetCurrentSpell() const
+{
+ for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i)
+ if (Spell* spell = me->GetCurrentSpell(CurrentSpellTypes(i)))
+ return spell;
+
+ return NULL;
+}
+
+inline uint32 PlayerbotAI::GetCurrentSpellId() const
+{
+ Spell* spell = GetCurrentSpell();
+ return spell ? spell->GetSpellInfo()->Id : 0;
+}
+
+inline bool PlayerbotAI::CCed(Unit* unit, bool root)
+{
+ return unit ? unit->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE) || (root && unit->HasUnitState(UNIT_STATE_ROOT)) : true;
+}
+uint32 PlayerbotAI::GetLostHP(Unit* unit)
+{
+ return unit->GetMaxHealth() - unit->GetHealth();
+}
+
+uint8 PlayerbotAI::GetHealthPCT(Unit* unit)
+{
+ return (!unit || unit->isDead()) ? 100 : (unit->GetHealth()*100/unit->GetMaxHealth());
+}
+
+uint8 PlayerbotAI::GetManaPCT(Unit* unit)
+{
+ return (!unit || unit->isDead() || unit->getPowerType() != POWER_MANA) ? 100 : (unit->GetPower(POWER_MANA)*100/unit->GetMaxPower(POWER_MANA));
+}
+
+inline float PlayerbotAI::GetMinAttackRange(Unit* target) const
+{
+ //TODO: range for ranged classes
+ return ShouldRanged() ? 19.f : target->GetMeleeReach() * 0.8f;
+}
+
+void PlayerbotAI::_UnbindInstance(uint32 mapId, Difficulty diff)
+{
+ for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
+ {
+ Player::BoundInstancesMap& binds = me->GetBoundInstances(Difficulty(i));
+ for (Player::BoundInstancesMap::iterator itr = binds.begin(); itr != binds.end();)
+ {
+ InstanceSave* save = itr->second.save;
+ if (itr->first != me->GetMapId() && mapId == itr->first && diff == save->GetDifficulty())
+ {
+ ChatHandler(GetMaster()->GetSession()).PSendSysMessage("PlayerbotAI: unbinding %s's map: %d inst: %d perm: %s diff: %d canReset: %s", me->GetName().c_str(), itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no");
+ me->UnbindInstance(itr, Difficulty(i));
+ break;
+ }
+ else
+ ++itr;
+ }
+ }
+}
+
+void PlayerbotAI::_InviteToMastersGroup()
+{
+ if (Group* gr = GetMaster()->GetGroup())
+ {
+ if (!gr->IsFull())
+ {
+ if (!gr->AddMember(me))
+ return;
+ }
+ else if (!gr->isRaidGroup()) //non-raid group is full
+ {
+ gr->ConvertToRaid();
+ if (!gr->AddMember(me))
+ return;
+ }
+ else //raid group is full
+ return;
+ }
+ else
+ {
+ gr = new Group;
+ if (!gr->Create(GetMaster()))
+ {
+ delete gr;
+ return;
+ }
+ sGroupMgr->AddGroup(gr);
+ if (!gr->AddMember(me))
+ return;
+ }
+}
+
+void PlayerbotAI::_SendDebugInfo()
+{
+ uint8 loc = uint8(me->GetSession()->GetSessionDbcLocale());
+ ChatHandler ch(GetMaster()->GetSession());
+
+ ch.PSendSysMessage("Listing stats for %s:", me->GetName().c_str());
+ std::ostringstream str1;
+ str1 << "1) Botstates: (";
+ if (HasBotState(BOTSTATE_FOLLOW))
+ str1 << "BOTSTATE_FOLLOW";
+ if (HasBotState(BOTSTATE_STAY))
+ str1 << " | BOTSTATE_STAY";
+ if (HasBotState(BOTSTATE_COMBAT))
+ str1 << " | BOTSTATE_COMBAT";
+ if (HasBotState(BOTSTATE_LOOTING))
+ str1 << " | BOTSTATE_LOOTING";
+ if (HasBotState(BOTSTATE_TAME))
+ str1 << " | BOTSTATE_TAME";
+ if (HasBotState(BOTSTATE_DELAYED))
+ str1 << " | BOTSTATE_DELAYED";
+ if (HasBotState(BOTSTATE_RESET))
+ str1 << " | BOTSTATE_RESET";
+ str1 << ')';
+ ch.PSendSysMessage(str1.str().c_str());
+
+ std::ostringstream str2;
+ str2 << "2) Combatstates: (";
+ if (HasCombatState(COMBAT_STATE_RANGED))
+ str2 << " | COMBAT_STATE_RANGED";
+ if (HasCombatState(COMBAT_STATE_STAY))
+ str2 << " | COMBAT_STATE_STAY";
+ if (HasCombatState(COMBAT_STATE_PVP))
+ str2 << " | COMBAT_STATE_PVP";
+ if (HasCombatState(COMBAT_STATE_RESET))
+ str2 << " | COMBAT_STATE_RESET";
+ str2 << ')';
+ ch.PSendSysMessage(str2.str().c_str());
+
+ std::ostringstream str3;
+ str3 << "3) MovementFlags: (";
+ if (HasMovementFlag(MOVEMENT_FLAG_TARGET_CHANGED))
+ str3 << " | MOVEMENT_FLAG_TARGET_CHANGED";
+ if (HasMovementFlag(MOVEMENT_FLAG_HOLD_GROUND))
+ str3 << " | MOVEMENT_FLAG_HOLD_GROUND";
+ if (HasMovementFlag(MOVEMENT_FLAG_RANDOM_MOVEMENT))
+ str3 << " | MOVEMENT_FLAG_RANDOM_MOVEMENT";
+ str3 << ')';
+ ch.PSendSysMessage(str3.str().c_str());
+
+ std::ostringstream str4;
+ str4 << "My spec: " << _mySpec;
+ ch.PSendSysMessage(str4.str().c_str());
+
+ ch.PSendSysMessage("Listing real spells:");
+ PlayerSpellMap const& spellMap = me->GetSpellMap();
+ for (PlayerSpellMap::const_iterator itr = spellMap.begin(); itr != spellMap.end(); ++itr)
+ {
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
+ if (!spellInfo)
+ continue;
+ PlayerSpell* spell = itr->second;
+ if (!spell)
+ continue;
+ if (spell->disabled || spell->state == PLAYERSPELL_REMOVED || spell->state == PLAYERSPELL_TEMPORARY)
+ continue;
+ //triggered or server-side spells
+ if (spellInfo->SpellLevel == 0)
+ continue;
+ //passive spell
+ if (spellInfo->IsPassive())
+ continue;
+ //spell is learned as other class spell
+ if (!me->IsSpellFitByClassAndRace(spellInfo->Id))
+ continue;
+ //is talent
+ if (GetTalentSpellPos(spellInfo->Id))
+ continue;
+ //not hightest rank known
+ if (spellInfo->GetNextRankSpell() && me->HasSpell(spellInfo->GetNextRankSpell()->Id))
+ continue;
+ ////check class family for spells
+ //ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(me->getClass());
+ //if (spellInfo->SpellFamilyName != classEntry->spellfamily)
+ // continue;
+
+ std::ostringstream str5;
+ str5 << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str5 << ' ' << localeNames[loc] << "]|h|r";
+ uint32 rank = !spellInfo->ChainEntry ? 0 : spellInfo->GetRank();
+ if (rank > 0)
+ str5 << " Rank " << rank;
+ ch.PSendSysMessage(str5.str().c_str());
+ }
+
+ ch.PSendSysMessage("Listing classAI spells:");
+ _classAI->SendClassDebugInfo();
+
+ ch.PSendSysMessage("Listing bot instance binds:");
+
+ for (uint8 i = 0; i != MAX_DIFFICULTY; ++i)
+ {
+ Player::BoundInstancesMap &binds = me->GetBoundInstances(Difficulty(i));
+ for (Player::BoundInstancesMap::const_iterator itr = binds.begin(); itr != binds.end(); ++itr)
+ if (InstanceSave* save = itr->second.save)
+ ch.PSendSysMessage("map: %d inst: %d perm: %s diff: %d canReset: %s", itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no");
+ }
+
+ ch.PSendSysMessage("Listing player instance binds:");
+
+ for (uint8 i = 0; i != MAX_DIFFICULTY; ++i)
+ {
+ Player::BoundInstancesMap &binds = _botBoundInstances[Difficulty(i)];
+ for (Player::BoundInstancesMap::const_iterator itr = binds.begin(); itr != binds.end(); ++itr)
+ if (InstanceSave* save = itr->second.save)
+ ch.PSendSysMessage("map: %d inst: %d perm: %s diff: %d canReset: %s", itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no");
+ }
+}
+
+inline void PlayerbotAI::_doTimers(uint32 diff)
+{
+ if (_waitTimer > diff) _waitTimer -= diff;
+ if (_followTimer > diff) _followTimer -= diff;
+ if (_mountTimer > diff) _mountTimer -= diff;
+ if (_cureTimer > diff) _cureTimer -= diff;
+ if (_selfResTimer > diff) _selfResTimer -= diff;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_ai.h b/src/server/game/AI/PlayerBots/bp_ai.h
new file mode 100644
index 0000000..b960918
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_ai.h
@@ -0,0 +1,611 @@
+#ifndef _PLAYERBOTAI_H
+#define _PLAYERBOTAI_H
+
+#include "Common.h"
+#include "DBCEnums.h"
+
+//class Group;
+class Item;
+class Player;
+class PlayerbotClassAI;
+class PlayerbotMgr;
+class Quest;
+class Spell;
+class SpellInfo;
+class Unit;
+class WorldObject;
+class WorldPacket;
+
+struct Position;
+
+enum BotSpecial
+{
+ SPELL_ROOT = 42716 // Netherspite SPELL_BANISH_ROOT "Self Root Forever (No Visual)"
+};
+
+enum BotStates
+{
+ //BOTSTATE_NORMAL = 0x000, //nothing
+ BOTSTATE_FOLLOW = 0x001, //following (deprecated?)
+ BOTSTATE_STAY = 0x002, //stay (not same as MOVEMENT_FLAG_HOLD_GROUND!)
+ BOTSTATE_COMBAT = 0x004, //do combat moves
+ BOTSTATE_LOOTING = 0x008, //need to perform loot sequence
+ BOTSTATE_TAME = 0x010, //has target to tame (deprecated?)
+ BOTSTATE_DELAYED = 0x020, //delayed operations queued (wtf?)
+ BOTSTATE_RESET = 0x040 //need to reset these flags
+};
+
+enum BotCombatStates
+{
+ COMBAT_STATE_RANGED = 0x001, //do ranged combat
+ COMBAT_STATE_STAY = 0x002, //stay in place
+ COMBAT_STATE_PVP = 0x004, //for PvP behaviour
+ COMBAT_STATE_RESET = 0x008 //free to change movement (used to chase current target)
+};
+
+enum BotMovementFlags
+{
+ MOVEMENT_FLAG_NONE = 0x000, //following normally
+ MOVEMENT_FLAG_TARGET_CHANGED = 0x001, //should be present to follow others
+ MOVEMENT_FLAG_HOLD_GROUND = 0x002, //rotate only
+ MOVEMENT_FLAG_RANDOM_MOVEMENT = 0x004 //move randomly while doing nothing
+};
+
+enum RacialTraits
+{
+ ARCANE_TORRENT_MANA_CLASSES = 28730,
+ ARCANE_TORRENT_DEATH_KNIGHT = 50613,
+ ARCANE_TORRENT_ROGUE = 25046,
+ BERSERKING_ALL = 26297,
+ BLOOD_FURY_MELEE_CLASSES = 20572,
+ BLOOD_FURY_WARLOCK = 33702,
+ BLOOD_FURY_SHAMAN = 33697,
+ ESCAPE_ARTIST_ALL = 20589,
+ EVERY_MAN_FOR_HIMSELF_ALL = 59752,
+ GIFT_OF_THE_NAARU_DEATH_KNIGHT = 59545,
+ GIFT_OF_THE_NAARU_HUNTER = 59543,
+ GIFT_OF_THE_NAARU_MAGE = 59548,
+ GIFT_OF_THE_NAARU_PALADIN = 59542,
+ GIFT_OF_THE_NAARU_PRIEST = 59544,
+ GIFT_OF_THE_NAARU_SHAMAN = 59547,
+ GIFT_OF_THE_NAARU_WARRIOR = 28880,
+ SHADOWMELD_ALL = 58984,
+ STONEFORM_ALL = 20594,
+ WAR_STOMP_ALL = 20549,
+ WILL_OF_THE_FORSAKEN_ALL = 7744
+};
+
+struct AfterCast
+{
+ AfterCast();
+
+ //WorldLocation* GetTravelLocation() const { return _travelDest; }
+ //void SetTravelLocation(WorldLocation* loc) { _travelDest = loc; }
+ inline Unit* GetTarget() const;
+ inline void SetTarget(Unit* target);
+ inline void SetAfterCastCommand(void (*newAfterCast)(Unit*));
+ inline void LaunchAfterCastCommand();
+
+ private:
+ //WorldLocation* _travelDest;
+ Unit* _afterCastTarget;
+ void (*afterCast)(Unit*);
+};
+
+class PlayerbotAI
+{
+ public:
+ PlayerbotAI(PlayerbotMgr* const mgr, Player* const bot);
+ virtual ~PlayerbotAI();
+
+ Player* GetMaster() const;
+ //inline BotState GetBotState() const { return m_botState; }
+ //inline void SetBotState(BotState state) { m_botState = state; }
+
+ //on world update tick
+ void UpdateAI(uint32 diff);
+ void ReloadClassAI();
+
+ //actions
+ void UpdateDeadActions(uint32 diff);
+ void UpdateIncombatActions();
+ void UpdateFollowActions(uint32 diff);
+
+ //states
+ void UpdateMountedState(uint32 diff);
+ void UpdateStandState();
+
+ inline uint8 GetBotState() const { return _botStates; }
+ inline bool HasBotState(BotStates state) const { return (_botStates & state); }
+ inline void AddBotState(BotStates state) { if (!(_botStates & state)) _botStates |= state; }
+ inline void ClearBotState(BotStates state) { if (_botStates & state) _botStates &= ~state; }
+
+ //casts
+ // units
+ bool CastSpell(Unit* victim, uint32 spellId) const;
+ bool CastSpell(Unit* victim, SpellInfo const* spellInfo) const;
+ // items
+ bool CastSpell(Item* item, uint32 spellId) const;
+ bool CastSpell(Item* item, SpellInfo const* spellInfo) const;
+ // cures
+ void CureGroup(Player* player, uint32 cureSpell, const uint32 diff);
+ private:
+ inline bool _CureTarget(Unit* target, SpellInfo const* spellInfo) const;
+ bool _CanCureTarget(Unit* target, SpellInfo const* spellInfo) const;
+ public:
+
+ //spells
+ static uint32 InitSpell(Player* caster, uint32 baseSpell);
+
+ inline bool IsMoving() const;
+
+ //currspell
+ inline Spell* GetCurrentSpell() const;
+ inline uint32 GetCurrentSpellId() const;
+
+ //movement
+ void SetMovement(WorldObject* followTarget);
+ inline uint8 GetMovementFlags() const { return _movementFlags; }
+ inline bool HasMovementFlag(BotMovementFlags flag) const { return (_movementFlags & flag); }
+ inline void AddMovementFlag(BotMovementFlags flag) { if (!(_movementFlags & flag)) _movementFlags |= flag; }
+ inline void ClearMovementFlag(BotMovementFlags flag) { if (_movementFlags & flag) _movementFlags &= ~flag; }
+ inline void RemoveMovementFlag(BotMovementFlags flag) { ClearMovementFlag(flag); }
+ inline bool CanMove() const { return (!(_movementFlags & MOVEMENT_FLAG_HOLD_GROUND)); }
+ inline bool CanFollowOthers() const { return (_movementFlags & MOVEMENT_FLAG_TARGET_CHANGED); }
+
+ //combat
+ inline uint8 GetCombatStates() const { return _combatStates; }
+ inline bool HasCombatState(BotCombatStates state) const { return (_combatStates & state); }
+ inline void AddCombatState(BotCombatStates state) { if (!(_combatStates & state)) _combatStates |= state; }
+ inline void ClearCombatState(BotCombatStates state) { if (_combatStates & state) _combatStates &= ~state; }
+ inline bool ShouldRanged() const { return (_combatStates & COMBAT_STATE_RANGED); }
+ inline bool ShouldStay() const { return (_combatStates & COMBAT_STATE_STAY); }
+
+ bool HasAttackTarget(uint8 myclass, uint32 myspec);
+ Unit* GetAttackTarget(bool ranged, bool byspell, bool &reset) const;
+ void GetInPosition(bool force, bool ranged, Unit* target = NULL, Position* pos = NULL);
+
+ //combat utils
+ static bool CanPlayerbotAttack(Player* bot, Unit* target, int8 byspell = 0);
+ static bool IsUnitInDuel(Unit* target, Player* master);
+ static bool IsUnitInPlayersParty(Unit* unit, Player* master);
+
+ //packets
+ void OnBotOutgoingPacket(WorldPacket const& packet);
+ void HandleTeleportAck();
+
+ //commands
+ void HandleCommand(std::string const& /*msg*/, Player& /*player*/) {}
+
+ //interraction
+ inline void Tell(std::string const& text, uint64 targetGUID = 0);
+
+ //utilities
+ static uint32 GetLostHP(Unit* unit);
+ static uint8 GetHealthPCT(Unit* unit);
+ static uint8 GetManaPCT(Unit* unit);
+
+ //temp / debug
+ //TODO: init range for classes
+ uint32 GetMySpec() const { return _mySpec; }
+ inline float GetMinAttackRange(Unit* target) const;
+ void InviteToMastersGroup() { _InviteToMastersGroup(); }
+ void SendDebugInfo() { _SendDebugInfo(); }
+ inline void GiveLevel(uint8 /*level*/) {}
+ inline void AcceptQuest(Quest const* /*quest*/, Player* /*divider*/) {}
+
+ protected:
+ static inline bool CCed(Unit* unit, bool root = false);
+
+ private:
+ inline void _doTimers(uint32 diff);
+
+ void _UnbindInstance(uint32 mapId, Difficulty diff);
+ void _InviteToMastersGroup();
+ void _SendDebugInfo();
+
+ Unit* _opponent;
+
+ PlayerbotMgr* const _mgr;
+ Player* const me;
+ PlayerbotClassAI* _classAI;
+
+ AfterCast* _afterCast;
+
+ uint32 _mySpec;
+
+ uint8 _botStates;
+ uint8 _combatStates;
+ uint8 _movementFlags;
+
+ uint32 _followTimer;
+ uint64 _followTargetGUID;
+
+ uint32 _waitTimer;
+ uint32 _mountTimer;
+ uint32 _cureTimer;
+ uint32 _selfResTimer;
+
+ bool _canSelfRes;
+
+ //// This is called from ChatHandler.cpp when there is an incoming message to the bot
+ //// from a whisper or from the party channel
+ //void HandleCommand(std::string const& text, Player& fromPlayer);
+
+ ////Packets
+ //void HandleBotOutgoingPacket(const WorldPacket& packet);
+
+ //// This is called by WorldSession.cpp
+ //// when it detects that a bot is being teleported. It acknowledges to the server to complete the
+ //// teleportation
+ //void HandleTeleportAck();
+
+ //// Returns what kind of situation we are in so the ai can react accordingly
+ //ScenarioType GetScenarioType() const { return m_ScenarioType; }
+ //CombatStyle GetCombatStyle() const { return m_combatStyle; }
+ //void SetCombatStyle(CombatStyle cs) { m_combatStyle = cs; }
+
+ //PlayerbotClassAI* GetClassAI() const { return _classAI; }
+ //PlayerbotMgr* GetManager() const { return _mgr; }
+
+ //// finds spell ID for matching substring args
+ //// in priority of full text match, spells not taking reagents, and highest rank
+ //uint32 getSpellId(const char* args, bool master = false) const;
+ //uint32 getPetSpellId(const char* args) const;
+ //// Initialize spell using rank 1 spell id
+ //uint32 initPetSpell(uint32 spellIconId);
+
+ //// extract mail ids from links
+ //void extractMailIds(std::string const& text, std::list<uint32>& mailIds) const;
+
+ //// extract quest ids from links
+ //void extractQuestIds(std::string const& text, std::list<uint32>& questIds) const;
+
+ //// extract auction ids from links
+ //void extractAuctionIds(std::string const& text, std::list<uint32>& auctionIds) const;
+
+ //// extracts talent ids to list
+ //void extractTalentIds(std::string const& text, std::list<talentPair>& talentIds) const;
+
+ //// extracts item ids from links
+ //void extractItemIds(std::string const& text, std::list<uint32>& itemIds) const;
+
+ //// extract spellid from links
+ //void extractSpellId(std::string const& text, uint32 &spellId) const;
+
+ //// extract spellids from links to list
+ //void extractSpellIdList(std::string const& text, BotEntryList& m_spellsToLearn) const;
+
+ //// extracts currency from a string as #g#s#c and returns the total in copper
+ //uint32 extractMoney(std::string const& text) const;
+
+ //// extracts gameobject info from link
+ //void extractGOinfo(std::string const& text, BotObjectList& m_lootTargets) const;
+
+ //// finds items in bots equipment and adds them to foundItemList, removes found items from itemIdSearchList
+ //void findItemsInEquip(std::list<uint32>& itemIdSearchList, std::list<Item*>& foundItemList) const;
+ //// finds items in bots inventory and adds them to foundItemList, removes found items from itemIdSearchList
+ //void findItemsInInv(std::list<uint32>& itemIdSearchList, std::list<Item*>& foundItemList) const;
+ //// finds nearby game objects that are specified in m_collectObjects then adds them to the m_lootTargets list
+ //void findNearbyGO();
+ //// finds nearby creatures, whose UNIT_NPC_FLAGS match the flags specified in item list m_itemIds
+ //void findNearbyCreature();
+ //// finds nearby corpse that is lootable
+ //void findNearbyCorpse();
+
+ //void GiveLevel(uint32 level);
+
+ //// Error check the TS DB. Should only be used when admins want to verify their new TS input
+ //uint32 TalentSpecDBContainsError();
+
+ //// Get talent specs or counts thereof
+ //uint32 GetTalentSpecsAmount();
+ //uint32 GetTalentSpecsAmount(uint32 specClass);
+ //std::list<TalentSpec> GetTalentSpecs(uint32 specClass);
+ //TalentSpec GetTalentSpec(uint32 specClass, uint32 choice);
+ //TalentSpec GetActiveTalentSpec() const { return m_activeTalentSpec; }
+ //void ClearActiveTalentSpec() { m_activeTalentSpec.specName = ""; m_activeTalentSpec.specClass = 0; m_activeTalentSpec.specPurpose = TSP_NONE; for (int i = 0; i < 71; i++) m_activeTalentSpec.talentId[i] = 0; for (int i = 0; i < 3; i++) { m_activeTalentSpec.glyphIdMajor[i] = 0; m_activeTalentSpec.glyphIdMinor[i] = 0; } }
+ //void SetActiveTalentSpec(TalentSpec ts) { m_activeTalentSpec = ts; }
+ //bool ApplyActiveTalentSpec();
+
+ //void MakeSpellLink(SpellInfo const* sInfo, std::ostringstream& out);
+ //void MakeWeaponSkillLink(SpellInfo const* sInfo, std::ostringstream& out, uint32 skillid);
+
+ //// currently bots only obey commands from the master
+ //bool canObeyCommandFrom(Player const& player) const;
+
+ //// get current casting spell (will return NULL if no spell!)
+
+ //bool HasAura(uint32 spellId, const Unit& player) const;
+ //bool HasAura(const char* spellName, const Unit& player) const;
+ //bool HasAura(const char* spellName) const;
+
+ //bool CanReceiveSpecificSpell(uint8 spec, Unit* target) const;
+
+ //bool HasTool(uint32 TC) const;
+ //bool HasSpellReagents(uint32 spellId) const;
+ //void ItemCountInInv(uint32 itemid, uint32& count);
+ //uint32 GetSpellCharges(uint32 spellId);
+
+ //Item* FindFood() const;
+ //Item* FindDrink() const;
+ //Item* FindBandage() const;
+ //Item* FindPoison() const;
+ //Item* FindMount(uint32 matchingRidingSkill) const;
+ //Item* FindItem(uint32 ItemId, bool Equipped_too = false);
+ //Item* FindItemInBank(uint32 ItemId);
+ //Item* FindKeyForLockValue(uint32 reqSkillValue);
+ //Item* FindBombForLockValue(uint32 reqSkillValue);
+ //Item* FindConsumable(uint32 displayId) const;
+ //uint8 _findItemSlot(Item* target);
+ //bool CanStore() const;
+
+ //// ******* Actions ****************************************
+ //// Your handlers can call these actions to make the bot do things.
+ //void TellMaster(std::string const& text) const;
+ //void TellMaster(const char* fmt, ...) const;
+ //void SendWhisper(std::string const& text, Player& player) const;
+ //bool CastSpell(const char* args);
+ //bool CastSpell(uint32 spellId);
+ //bool CastSpell(uint32 spellId, Unit& target);
+ //bool CheckBotCast(SpellInfo const* sInfo);
+ //bool CastPetSpell(uint32 spellId, Unit* target = NULL);
+ //bool Buff(uint32 spellId, Unit* target, void (*beforeCast)(Player *) = NULL);
+ //bool SelfBuff(uint32 spellId);
+ //bool IsInRange(Unit* Target, uint32 spellId);
+
+ //void UseItem(Item* item, uint32 targetFlag, uint64 targetGUID);
+ //void UseItem(Item* item, uint8 targetInventorySlot);
+ //void UseItem(Item* item, Unit* target);
+ //void UseItem(Item* item);
+
+ //void PlaySound(uint32 soundid);
+ //void Announce(AnnounceFlags msg);
+
+ //void EquipItem(Item* src_Item);
+ ////void Stay();
+ ////bool Follow(Player& player);
+ //void SendNotEquipList(Player& player);
+
+ //uint8 m_DelayAttack;
+ //Unit* gPrimtarget;
+ //Unit* gSectarget;
+ //uint32 gQuestFetch;
+
+ //bool m_AutoEquipToggle; //switch for autoequip
+ //uint32 SellWhite; //switch for white item auto sell
+ //uint8 DistOverRide;
+ //float gDist[2]; //gDist, gTemp vars are used for variable follow distance
+ //float gTempDist;
+ //float gTempDist2;
+ //uint8 m_FollowAutoGo;
+ //uint8 IsUpOrDown; //tracks variable follow distance
+ //void BotDataRestore();
+ //void CombatOrderRestore();
+ //void AutoUpgradeEquipment();
+ //void AutoEquipComparison(Item* pItem, Item* pItem2);
+ //bool ItemStatComparison(ItemTemplate const* pProto, ItemTemplate const* pProto2);
+ //void Feast();
+ //void InterruptCurrentCastingSpell();
+ //void Attack(Unit* forcedTarget = NULL);
+ //void GetCombatTarget(Unit* forcedTarget = 0);
+ //void GetDuelTarget(Unit* forcedTarget);
+ //Unit* GetCurrentTarget() const { return m_targetCombat; }
+ //void DoNextCombatManeuver();
+ //void DoCombatMovement();
+ ////void SetIgnoreUpdateTime(uint8 t = 0) { m_ignoreAIUpdatesUntilTime = time(NULL) + t; };
+ //void SetState(BotState state);
+ //void SetQuestNeedItems();
+ //void SetQuestNeedCreatures();
+ //void SendQuestNeedList();
+ //bool IsInQuestItemList(uint32 itemid) const { return m_needItemList.find(itemid) != m_needItemList.end(); };
+ //bool IsInQuestCreatureList(uint32 id) const { return m_needCreatureOrGOList.find(id) != m_needCreatureOrGOList.end(); };
+ //bool IsItemUseful(uint32 itemid);
+ //void SendOrders(Player& player);
+ //bool DoTeleport(WorldObject& obj);
+ //void DoLoot();
+ //void DoFlight();
+ //void GetTaxi(uint64 guid, BotTaxiNode& nodes);
+ //void BeingRolledOn(uint64 target) { m_being_rolled_on.push_back(target); };
+
+ //bool HasCollectFlag(uint16 flag) { return m_collectionFlags & flag; }
+ //void SetCollectFlag(uint16 flag)
+ //{
+ // if (HasCollectFlag(flag)) m_collectionFlags &= ~flag;
+ // else m_collectionFlags |= flag;
+ //}
+
+ //uint32 EstRepairAll();
+ //uint32 EstRepair(uint16 pos);
+
+ //void AcceptQuest(Quest const *qInfo, Player *pGiver);
+ //void TurnInQuests(WorldObject *questgiver);
+ //void ListQuests(WorldObject* questgiver);
+ //bool AddQuest(uint32 const entry, WorldObject* questgiver);
+ //void MakeQuestLink(Quest const* quest, std::ostringstream &out);
+
+ //bool IsInCombat();
+ //bool IsGroupInCombat();
+ //Player* GetGroupTank(); // TODO: didn't want to pollute non-playerbot code but this should really go in group.cpp
+ //void SetGroupCombatOrder(CombatOrderType co);
+ //void ClearGroupCombatOrder(CombatOrderType co);
+ //void SetGroupIgnoreUpdateTime(uint8 t);
+ //bool GroupHoTOnTank() const;
+ //bool CanPull(Player& fromPlayer);
+ //bool CastPull();
+ //bool GroupTankHoldsAggro();
+ //void UpdateAttackerInfo();
+ //Unit* FindAttacker(ATTACKERINFOTYPE ait = AIT_NONE, Unit* victim = NULL);
+ //uint32 GetAttackerCount() const { return m_attackerInfo.size(); };
+ //void SetCombatOrderByStr(std::string str, Unit* target = NULL);
+ //void SetCombatOrder(CombatOrderType co, Unit* target = NULL);
+ //void ClearCombatOrder(CombatOrderType co);
+ //CombatOrderType GetCombatOrder() const { return this->m_combatOrder; }
+ //bool IsTank() const { return (m_combatOrder & ORDERS_TANK) ? true : false; }
+ //bool IsHealer() const { return (m_combatOrder & ORDERS_HEAL) ? true : false; }
+ //bool IsDPS() const { return (m_combatOrder & ORDERS_ASSIST) ? true : false; }
+ //void SetMovementOrder(MovementOrderType mo, Unit* followTarget = NULL);
+ //MovementOrderType GetMovementOrder() const { return this->m_movementOrder; }
+ //void MovementReset();
+ //void MovementClear();
+
+ // void SetInFront(Unit const* obj);
+
+ // void ItemLocalization(std::string& itemName, uint32 const itemID) const;
+ // void QuestLocalization(std::string& questTitle, uint32 const questID) const;
+ // void CreatureLocalization(std::string& creatureName, uint32 const entry) const;
+ // void GameObjectLocalization(std::string& gameobjectName, uint32 const entry) const;
+
+ // uint32 GetFreeBagSpace() const;
+ // bool DropGarbage(bool bVerbose);
+ // void SellGarbage(Player& player, bool listNonTrash = true, bool bDetailTrashSold = false, bool verbose = true);
+ // void Sell(uint32 const itemid);
+ // void Buy(Creature* vendor, uint32 const itemid);
+ // std::string DropItem(uint32 const itemid);
+ // void AddAuction(uint32 const itemid, Creature* aCreature);
+ // void ListAuctions();
+ // bool RemoveAuction(uint32 const auctionid);
+ // void Repair(uint32 const itemid, Creature* rCreature);
+ // bool Talent(Creature* tCreature);
+ // void InspectUpdate();
+ // bool Withdraw(uint32 const itemid);
+ // bool Deposit(uint32 const itemid);
+ // void BankBalance();
+ // std::string Cash(uint32 copper);
+ // std::string AuctionResult(std::string subject, std::string body);
+
+ //protected:
+ // bool ValidateTalent(uint16 talent, uint32 charClass);
+ // bool ValidateGlyph(uint16 glyph, uint32 charClass);
+ // bool ValidateMajorGlyph(uint16 glyph, uint32 charClass);
+ // bool ValidateMinorGlyph(uint16 glyph, uint32 charClass);
+
+ //private:
+ // bool ExtractCommand(std::string const sLookingFor, std::string& text, bool bUseShort = false);
+ // // outsource commands for code clarity
+ // void _HandleCommandReset(std::string& text, Player& fromPlayer);
+ // void _HandleCommandOrders(std::string& text, Player& fromPlayer);
+ // void _HandleCommandFollow(std::string& text, Player& fromPlayer);
+ // void _HandleCommandStay(std::string& text, Player& fromPlayer);
+ // void _HandleCommandAttack(std::string& text, Player& fromPlayer);
+ // void _HandleCommandPull(std::string& text, Player& fromPlayer);
+ // void _HandleCommandCast(std::string& text, Player& fromPlayer);
+ // void _HandleCommandSell(std::string& text, Player& fromPlayer);
+ // void _HandleCommandBuy(std::string& text, Player& fromPlayer);
+ // void _HandleCommandDrop(std::string& text, Player& fromPlayer);
+ // void _HandleCommandRepair(std::string& text, Player& fromPlayer);
+ // void _HandleCommandAuction(std::string& text, Player& fromPlayer);
+ // void _HandleCommandMail(std::string& text, Player& fromPlayer);
+ // void _HandleCommandBank(std::string& text, Player& fromPlayer);
+ // void _HandleCommandTalent(std::string& text, Player& fromPlayer);
+ // void _HandleCommandUse(std::string& text, Player& fromPlayer);
+ // void _HandleCommandEquip(std::string& text, Player& fromPlayer);
+ // void _HandleCommandFind(std::string& text, Player& fromPlayer);
+ // void _HandleCommandGet(std::string& text, Player& fromPlayer);
+ // void _HandleCommandCollect(std::string& text, Player& fromPlayer);
+ // void _HandleCommandQuest(std::string& text, Player& fromPlayer);
+ // void _HandleCommandCraft(std::string& text, Player& fromPlayer);
+ // void _HandleCommandEnchant(std::string& text, Player& fromPlayer);
+ // void _HandleCommandProcess(std::string& text, Player& fromPlayer);
+ // void _HandleCommandPet(std::string& text, Player& fromPlayer);
+ // void _HandleCommandSpells(std::string& text, Player& fromPlayer);
+ // void _HandleCommandSurvey(std::string& text, Player& fromPlayer);
+ // void _HandleCommandSkill(std::string& text, Player& fromPlayer);
+ // bool _HandleCommandSkillLearnHelper(TrainerSpell const* tSpell, uint32 spellId, uint32 cost);
+ // void _HandleCommandStats(std::string& text, Player& fromPlayer);
+ // void _HandleCommandHelp(std::string& text, Player& fromPlayer);
+ // void _HandleCommandHelp(const char* szText, Player& fromPlayer) { std::string text = szText; _HandleCommandHelp(text, fromPlayer); }
+ // void _HandleCommandGM(std::string& text, Player& fromPlayer);
+ // std::string _HandleCommandHelpHelper(std::string sCommand, std::string sExplain, HELPERLINKABLES reqLink = HL_NONE, bool bReqLinkMultiples = false, bool bCommandShort = false);
+
+ //// ****** Closed Actions ********************************
+ //// These actions may only be called at special times.
+ //// Trade methods are only applicable when the trade window is open
+ //// and are only called from within HandleCommand.
+ //bool TradeItem(Item const& item, int8 slot = -1);
+ //bool TradeCopper(uint32 copper);
+
+ //// Helper routines not needed by class AIs.
+ //void UpdateAttackersForTarget(Unit* victim);
+
+ //void _doSellItem(Item const* item, std::ostringstream& report, std::ostringstream& canSell, uint32& TotalCost, uint32& TotalSold);
+ //void MakeItemLink(Item const* item, std::ostringstream& out, bool IncludeQuantity = true);
+ //void MakeItemText(Item const* item, std::ostringstream& out, bool IncludeQuantity = true);
+ //void MakeItemLink(ItemTemplate const* item, std::ostringstream& out);
+
+ // it is safe to keep these back reference pointers because m_bot
+ // owns the "this" object and m_master owns m_bot. The owner always cleans up.
+
+ // ignores AI updates until time specified
+ // no need to waste CPU cycles during casting etc
+ //time_t m_ignoreAIUpdatesUntilTime;
+
+ //CombatStyle m_combatStyle;
+ //CombatOrderType m_combatOrder;
+ //MovementOrderType m_movementOrder;
+
+ //TalentSpec m_activeTalentSpec;
+
+ //ScenarioType m_ScenarioType;
+
+ // defines the state of behaviour of the bot
+
+ //// list of items, creatures or gameobjects needed to fullfill quests
+ //BotNeedItem m_needItemList;
+ //BotNeedItem m_needCreatureOrGOList;
+
+ //// list of creatures we recently attacked and want to loot
+ //BotNPCList m_findNPC; // list of NPCs
+ //BotTaskList m_tasks; // list of tasks
+ //BotObjectList m_lootTargets; // list of targets
+ //BotEntryList m_spellsToLearn; // list of spells
+ //uint64 m_lootCurrent; // current remains of interest
+ //uint64 m_lootPrev; // previous loot
+ //BotEntryList m_collectObjects; // object entries searched for in findNearbyGO
+ //BotTaxiNode m_taxiNodes; // flight node chain
+ //BotEntryList m_noToolList; // list of required tools
+ //BotObjectList m_being_rolled_on; // list of targets currently involved in item rolls
+
+ //uint16 m_collectionFlags; // what the bot should look for to loot
+ //uint32 m_collectDist; // distance to collect objects
+ //bool m_inventory_full;
+ //uint32 m_itemTarget;
+ //bool m_dropWhite;
+
+ ////time_t m_TimeDoneEating;
+ ////time_t m_TimeDoneDrinking;
+ ////uint32 m_CurrentlyCastingSpellId;
+ //uint32 m_CraftSpellId;
+ ////bool m_IsFollowingMaster;
+
+ //// if master commands bot to do something, store here until updateAI
+ //// can do it
+ //uint32 m_spellIdCommand;
+ //uint64 m_targetGuidCommand;
+ //uint64 m_taxiMaster;
+
+ //BotObjectSet m_ignorePlayersChat; // list of players that the bot will not respond to
+
+ //AttackerInfoList m_attackerInfo;
+
+ //bool m_targetChanged;
+ //CombatTargetType m_targetType;
+
+ //Unit* m_targetCombat; // current combat target
+ //Unit* m_targetAssist; // get new target by checking attacker list of assisted player
+ //Unit* m_targetProtect; // check
+
+ //Unit* m_followTarget; // whom to follow in non combat situation?
+
+ //uint32 FISHING,
+ // HERB_GATHERING,
+ // MINING,
+ // SKINNING,
+ // ASPECT_OF_THE_MONKEY;
+
+ //float m_destX, m_destY, m_destZ; // latest coordinates for chase and point movement types
+
+ //bool m_bDebugCommandChat;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/server/game/AI/PlayerBots/bp_cai.cpp b/src/server/game/AI/PlayerBots/bp_cai.cpp
new file mode 100644
index 0000000..3b76ed6
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_cai.cpp
@@ -0,0 +1,14 @@
+#include "bp_cai.h"
+//#include "Common.h"
+//#include "World.h"
+//#include "SpellMgr.h"
+#include "Player.h"
+#include "Group.h"
+//#include "ObjectMgr.h"
+//#include "WorldPacket.h"
+//#include "Unit.h"
+#include "bp_ai.h"
+
+PlayerbotClassAI::PlayerbotClassAI(Player* const master, Player* const bot, PlayerbotAI* const ai) :
+m_master(master), me(bot), m_ai(ai) {}
+PlayerbotClassAI::~PlayerbotClassAI() {}
diff --git a/src/server/game/AI/PlayerBots/bp_cai.h b/src/server/game/AI/PlayerBots/bp_cai.h
new file mode 100644
index 0000000..6b21da4
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_cai.h
@@ -0,0 +1,118 @@
+#ifndef _PLAYERBOTCLASSAI_H
+#define _PLAYERBOTCLASSAI_H
+
+#include "Common.h"
+
+class PlayerbotAI;
+class Player;
+class Unit;
+
+enum CombatManeuverReturns
+{
+ // TODO: RETURN_NO_ACTION_UNKNOWN is not part of ANY_OK or ANY_ERROR. It's also bad form and should be eliminated ASAP.
+ RETURN_NO_ACTION_OK = 0x01, // No action taken during this combat maneuver, as intended (just wait, etc...)
+ RETURN_NO_ACTION_UNKNOWN = 0x02, // No action taken during this combat maneuver, unknown reason
+ RETURN_NO_ACTION_ERROR = 0x04, // No action taken due to error
+ RETURN_NO_ACTION_INVALIDTARGET = 0x08, // No action taken - invalid target
+ RETURN_FINISHED_FIRST_MOVES = 0x10, // Last action of first-combat-maneuver finished, continue onto next-combat-maneuver
+ RETURN_CONTINUE = 0x20, // Continue first moves; normal return value for next-combat-maneuver
+ RETURN_NO_ACTION_INSUFFICIENT_POWER = 0x40, // No action taken due to insufficient power (rage, focus, mana, runes)
+ RETURN_ANY_OK = 0x31, // All the OK values bitwise OR'ed
+ RETURN_ANY_ACTION = 0x30, // All returns that result in action (which should also be 'OK')
+ RETURN_ANY_ERROR = 0x4C // All the ERROR values bitwise OR'ed
+};
+
+enum MainSpec
+{
+ MAGE_SPEC_FIRE = 41,
+ MAGE_SPEC_FROST = 61,
+ MAGE_SPEC_ARCANE = 81,
+ WARRIOR_SPEC_ARMS = 161,
+ WARRIOR_SPEC_PROTECTION = 163,
+ WARRIOR_SPEC_FURY = 164,
+ ROGUE_SPEC_COMBAT = 181,
+ ROGUE_SPEC_ASSASSINATION = 182,
+ ROGUE_SPEC_SUBTELTY = 183,
+ PRIEST_SPEC_DISCIPLINE = 201,
+ PRIEST_SPEC_HOLY = 202,
+ PRIEST_SPEC_SHADOW = 203,
+ SHAMAN_SPEC_ELEMENTAL = 261,
+ SHAMAN_SPEC_RESTORATION = 262,
+ SHAMAN_SPEC_ENHANCEMENT = 263,
+ DRUID_SPEC_FERAL = 281,
+ DRUID_SPEC_RESTORATION = 282,
+ DRUID_SPEC_BALANCE = 283,
+ WARLOCK_SPEC_DESTRUCTION = 301,
+ WARLOCK_SPEC_AFFLICTION = 302,
+ WARLOCK_SPEC_DEMONOLOGY = 303,
+ HUNTER_SPEC_BEASTMASTERY = 361,
+ HUNTER_SPEC_SURVIVAL = 362,
+ HUNTER_SPEC_MARKSMANSHIP = 363,
+ PALADIN_SPEC_RETRIBUTION = 381,
+ PALADIN_SPEC_HOLY = 382,
+ PALADIN_SPEC_PROTECTION = 383,
+ DEATHKNIGHT_SPEC_BLOOD = 398,
+ DEATHKNIGHT_SPEC_FROST = 399,
+ DEATHKNIGHT_SPEC_UNHOLY = 400
+};
+
+class PlayerbotClassAI
+{
+ public:
+ PlayerbotClassAI(Player* const master, Player* const bot, PlayerbotAI* const ai);
+ virtual ~PlayerbotClassAI();
+
+ //// all combat actions go here
+ //CombatManeuverReturns DoFirstCombatManeuver(Unit*);
+ //CombatManeuverReturns DoNextCombatManeuver(Unit*);
+ //bool Pull() { /*DEBUG_LOG("[PlayerbotAI]: Warning: Using PlayerbotClassAI::Pull() rather than class specific function");*/ return false; }
+
+ //// all non combat actions go here, ex buffs, heals, rezzes
+ //virtual void DoNonCombatActions();
+ //bool EatDrinkBandage(bool bMana = true, uint8 foodPercent = 50, uint8 drinkPercent = 50, uint8 bandagePercent = 70);
+
+ // Utilities
+ Player* GetMaster() const { return m_master; }
+ Player* GetPlayerBot() const { return me; }
+ PlayerbotAI* GetAI() const { return m_ai; }
+ virtual void DoCombatActions() {}
+ virtual void UpdateGroupActions(uint32 const /*diff*/) {}
+ virtual void SendClassDebugInfo() {}
+ //bool CanPull() const;
+ //bool CastHoTOnTank();
+ //time_t GetWaitUntil() const { return m_WaitUntil; }
+ //void SetWait(uint8 t);
+ //void ClearWait() { m_WaitUntil = 0; }
+ ////void SetWaitUntil(time_t t) { m_WaitUntil = t; }
+
+ protected:
+ //CombatManeuverReturns DoFirstCombatManeuverPVE(Unit*);
+ //CombatManeuverReturns DoNextCombatManeuverPVE(Unit*);
+ //CombatManeuverReturns DoFirstCombatManeuverPVP(Unit*);
+ //CombatManeuverReturns DoNextCombatManeuverPVP(Unit*);
+
+ CombatManeuverReturns CastSpellNoRanged(uint32 /*nextAction*/, Unit* /*pTarget*/) { return RETURN_ANY_ERROR; }
+ CombatManeuverReturns CastSpellWand(uint32 /*nextAction*/, Unit* /*pTarget*/, uint32 /*SHOOT*/) { return RETURN_ANY_ERROR; }
+ //virtual CombatManeuverReturns HealPlayer(Player* target);
+ //CombatManeuverReturns Buff(bool (*BuffHelper)(PlayerbotAI*, uint32, Unit*), uint32 spellId, uint32 type = JOB_ALL, bool bMustBeOOC = true);
+ Player* GetHealTarget() { return NULL; }
+ //Player* GetResurrectionTarget(JOB_TYPE type = JOB_ALL, bool bMustBeOOC = true);
+ //JOB_TYPE GetTargetJob(Player* target);
+
+ //// These values are used in GetHealTarget and can be overridden per class (to accomodate healing spell health checks)
+ //uint8 m_MinHealthPercentTank;
+ //uint8 m_MinHealthPercentHealer;
+ //uint8 m_MinHealthPercentDPS;
+ //uint8 m_MinHealthPercentMaster;
+
+ //time_t m_WaitUntil;
+
+ Player* const m_master;
+ Player* const me;
+ PlayerbotAI* const m_ai;
+
+ //// first aid
+ //uint32 RECENTLY_BANDAGED;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_dk_ai.cpp b/src/server/game/AI/PlayerBots/bp_dk_ai.cpp
new file mode 100644
index 0000000..501ac70
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_dk_ai.cpp
@@ -0,0 +1,549 @@
+// a simple DK class by rrtn :)
+#include "bp_dk_ai.h"
+#include "bp_ai.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotDeathKnightAI::PlayerbotDeathKnightAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+
+ PLAGUE_STRIKE = PlayerbotAI::InitSpell(me, PLAGUE_STRIKE_1); // Unholy
+ DEATH_GRIP = PlayerbotAI::InitSpell(me, DEATH_GRIP_1);
+ DEATH_COIL = PlayerbotAI::InitSpell(me, DEATH_COIL_DEATH_KNIGHT_1);
+ DEATH_STRIKE = PlayerbotAI::InitSpell(me, DEATH_STRIKE_1);
+ UNHOLY_BLIGHT = 0; // Passive
+ SCOURGE_STRIKE = PlayerbotAI::InitSpell(me, SCOURGE_STRIKE_1);
+ DEATH_AND_DECAY = PlayerbotAI::InitSpell(me, DEATH_AND_DECAY_1);
+ CORPSE_EXPLOSION = PlayerbotAI::InitSpell(me, CORPSE_EXPLOSION_1);
+ BONE_SHIELD = PlayerbotAI::InitSpell(me, BONE_SHIELD_1); // buffs
+ ANTI_MAGIC_SHELL = PlayerbotAI::InitSpell(me, ANTI_MAGIC_SHELL_1);
+ ANTI_MAGIC_ZONE = PlayerbotAI::InitSpell(me, ANTI_MAGIC_ZONE_1);
+ GHOUL_FRENZY = PlayerbotAI::InitSpell(me, GHOUL_FRENZY_1);
+ RAISE_DEAD = PlayerbotAI::InitSpell(me, RAISE_DEAD_1); // pets
+ SUMMON_GARGOYLE = PlayerbotAI::InitSpell(me, SUMMON_GARGOYLE_1);
+ ARMY_OF_THE_DEAD = PlayerbotAI::InitSpell(me, ARMY_OF_THE_DEAD_1);
+ ICY_TOUCH = PlayerbotAI::InitSpell(me, ICY_TOUCH_1); // Frost
+ OBLITERATE = PlayerbotAI::InitSpell(me, OBLITERATE_1);
+ HOWLING_BLAST = PlayerbotAI::InitSpell(me, HOWLING_BLAST_1);
+ FROST_STRIKE = PlayerbotAI::InitSpell(me, FROST_STRIKE_1);
+ CHAINS_OF_ICE = PlayerbotAI::InitSpell(me, CHAINS_OF_ICE_1);
+ RUNE_STRIKE = PlayerbotAI::InitSpell(me, RUNE_STRIKE_1);
+ ICY_CLUTCH = 0; // No such spell
+ MIND_FREEZE = PlayerbotAI::InitSpell(me, MIND_FREEZE_1);
+ HUNGERING_COLD = PlayerbotAI::InitSpell(me, HUNGERING_COLD_1);
+ KILLING_MACHINE = 0; // Passive
+ DEATHCHILL = PlayerbotAI::InitSpell(me, DEATHCHILL_1);
+ HORN_OF_WINTER = PlayerbotAI::InitSpell(me, HORN_OF_WINTER_1);
+ ICEBOUND_FORTITUDE = PlayerbotAI::InitSpell(me, ICEBOUND_FORTITUDE_1);
+ EMPOWER_WEAPON = PlayerbotAI::InitSpell(me, EMPOWER_RUNE_WEAPON_1);
+ UNBREAKABLE_ARMOR = PlayerbotAI::InitSpell(me, UNBREAKABLE_ARMOR_1);
+ BLOOD_STRIKE = PlayerbotAI::InitSpell(me, BLOOD_STRIKE_1); // Blood
+ PESTILENCE = PlayerbotAI::InitSpell(me, PESTILENCE_1);
+ STRANGULATE = PlayerbotAI::InitSpell(me, STRANGULATE_1);
+ BLOOD_BOIL = PlayerbotAI::InitSpell(me, BLOOD_BOIL_1);
+ HEART_STRIKE = PlayerbotAI::InitSpell(me, HEART_STRIKE_1);
+ DANCING_WEAPON = PlayerbotAI::InitSpell(me, DANCING_RUNE_WEAPON_1);
+ DARK_COMMAND = PlayerbotAI::InitSpell(me, DARK_COMMAND_1);
+ MARK_OF_BLOOD = PlayerbotAI::InitSpell(me, MARK_OF_BLOOD_1); // buffs
+ RUNE_TAP = PlayerbotAI::InitSpell(me, RUNE_TAP_1);
+ VAMPIRIC_BLOOD = PlayerbotAI::InitSpell(me, VAMPIRIC_BLOOD_1);
+ DEATH_PACT = PlayerbotAI::InitSpell(me, DEATH_PACT_1);
+ HYSTERIA = PlayerbotAI::InitSpell(me, HYSTERIA_1);
+ UNHOLY_PRESENCE = PlayerbotAI::InitSpell(me, UNHOLY_PRESENCE_1); // presence (TODO: better spell == presence)
+ FROST_PRESENCE = PlayerbotAI::InitSpell(me, FROST_PRESENCE_1);
+ BLOOD_PRESENCE = PlayerbotAI::InitSpell(me, BLOOD_PRESENCE_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_DEATH_KNIGHT); // blood elf
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_DEATH_KNIGHT); // draenei
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ ESCAPE_ARTIST = PlayerbotAI::InitSpell(me, ESCAPE_ARTIST_ALL); // gnome
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_MELEE_CLASSES); // orc
+ WAR_STOMP = PlayerbotAI::InitSpell(me, WAR_STOMP_ALL); // tauren
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+}
+
+PlayerbotDeathKnightAI::~PlayerbotDeathKnightAI() {}
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder())
+ // {
+ // if (pTarget->GetCombatReach() <= ATTACK_DISTANCE)
+ // {
+ // // Set everyone's UpdateAI() waiting to 2 seconds
+ // m_ai->SetGroupIgnoreUpdateTime(2);
+ // // Clear their TEMP_WAIT_TANKAGGRO flag
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // // Start attacking, force target on current target
+ // m_ai->Attack(m_ai->GetCurrentTarget());
+
+ // // While everyone else is waiting 2 second, we need to build up aggro, so don't return
+ // }
+ // else
+ // {
+ // // TODO: add check if target is ranged
+ // return RETURN_NO_ACTION_OK; // wait for target to get nearer
+ // }
+ // }
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //// DK Attacks: Unholy, Frost & Blood
+
+ //// damage spells
+ //Unit* pVictim = pTarget->getVictim();
+ //Pet *pet = m_bot->GetPet();
+ //float dist = pTarget->GetCombatReach();
+ //std::ostringstream out;
+
+ //switch (SpellSequence)
+ //{
+ // case SPELL_DK_UNHOLY:
+ // if (UNHOLY_PRESENCE > 0 && !m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_0) && !m_bot->HasAura(BLOOD_PRESENCE, EFFECT_0) && !m_bot->HasAura(FROST_PRESENCE, EFFECT_0) && m_ai->CastSpell(UNHOLY_PRESENCE, *m_bot))
+ // return RETURN_CONTINUE;
+
+ // // check for BONE_SHIELD in combat
+ // if (BONE_SHIELD > 0 && !m_bot->HasAura(BONE_SHIELD, EFFECT_0) && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_0) && m_ai->CastSpell(BONE_SHIELD, *m_bot))
+ // return RETURN_CONTINUE;
+
+ // if (ARMY_OF_THE_DEAD > 0 && m_ai->GetAttackerCount() >= 5 && LastSpellUnholyDK < 1 && m_ai->CastSpell(ARMY_OF_THE_DEAD) && m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_0))
+ // {
+ // out << " summoning Army of the Dead!";
+ // //m_ai->SetIgnoreUpdateTime(7);
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (PLAGUE_STRIKE > 0 && !pTarget->HasAura(PLAGUE_STRIKE, EFFECT_0) && LastSpellUnholyDK < 2 && m_ai->CastSpell(PLAGUE_STRIKE, *pTarget))
+ // {
+ // out << " Plague Strike";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DEATH_GRIP > 0 && !pTarget->HasAura(DEATH_GRIP, EFFECT_0) && LastSpellUnholyDK < 3 && m_ai->CastSpell(DEATH_GRIP, *pTarget))
+ // {
+ // out << " Death Grip";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DEATH_COIL > 0 && LastSpellUnholyDK < 4 && m_ai->CastSpell(DEATH_COIL, *pTarget))
+ // {
+ // out << " Death Coil";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DEATH_STRIKE > 0 && !pTarget->HasAura(DEATH_STRIKE, EFFECT_0) && LastSpellUnholyDK < 5 && m_ai->CastSpell(DEATH_STRIKE, *pTarget))
+ // {
+ // out << " Death Strike";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (UNHOLY_BLIGHT > 0 && !pTarget->HasAura(UNHOLY_BLIGHT, EFFECT_0) && LastSpellUnholyDK < 6 && m_ai->CastSpell(UNHOLY_BLIGHT))
+ // {
+ // out << " Unholy Blight";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (SCOURGE_STRIKE > 0 && LastSpellUnholyDK < 7 && m_ai->CastSpell(SCOURGE_STRIKE, *pTarget))
+ // {
+ // out << " Scourge Strike";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DEATH_AND_DECAY > 0 && m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && !pTarget->HasAura(DEATH_AND_DECAY, EFFECT_0) && LastSpellUnholyDK < 8 && m_ai->CastSpell(DEATH_AND_DECAY))
+ // {
+ // out << " Death and Decay";
+ // //m_ai->SetIgnoreUpdateTime(1);
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (SUMMON_GARGOYLE > 0 && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_0) && !pTarget->HasAura(SUMMON_GARGOYLE, EFFECT_0) && LastSpellUnholyDK < 9 && m_ai->CastSpell(SUMMON_GARGOYLE, *pTarget))
+ // {
+ // out << " summoning Gargoyle";
+ // //m_ai->SetIgnoreUpdateTime(2);
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (CORPSE_EXPLOSION > 0 && dist <= ATTACK_DISTANCE && LastSpellUnholyDK < 10 && m_ai->CastSpell(CORPSE_EXPLOSION, *pTarget))
+ // {
+ // out << " Corpse Explosion";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (ANTI_MAGIC_SHELL > 0 && pTarget->IsNonMeleeSpellCasted(true) && !m_bot->HasAura(ANTI_MAGIC_SHELL, EFFECT_0) && LastSpellUnholyDK < 11 && m_ai->CastSpell(ANTI_MAGIC_SHELL, *m_bot))
+ // {
+ // out << " Anti-Magic Shell";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (ANTI_MAGIC_ZONE > 0 && pTarget->IsNonMeleeSpellCasted(true) && !m_bot->HasAura(ANTI_MAGIC_SHELL, EFFECT_0) && LastSpellUnholyDK < 12 && m_ai->CastSpell(ANTI_MAGIC_ZONE, *m_bot))
+ // {
+ // out << " Anti-Magic Zone";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (!pet && RAISE_DEAD > 0 && !m_bot->HasAura(ARMY_OF_THE_DEAD, EFFECT_0) && LastSpellUnholyDK < 13 && m_ai->CastSpell(RAISE_DEAD))
+ // {
+ // out << " summoning Ghoul";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (pet && GHOUL_FRENZY > 0 && pVictim == pet && !pet->HasAura(GHOUL_FRENZY, EFFECT_0) && LastSpellUnholyDK < 14 && m_ai->CastSpell(GHOUL_FRENZY, *pet))
+ // {
+ // out << " casting Ghoul Frenzy on pet";
+ // SpellSequence = SPELL_DK_FROST;
+ // LastSpellUnholyDK = LastSpellUnholyDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (LastSpellUnholyDK > 15)
+ // {
+ // LastSpellUnholyDK = 0;
+ // SpellSequence = SPELL_DK_FROST;
+ // return RETURN_NO_ACTION_OK; // Not really OK but that's just how the DK rotation works right now
+ // }
+
+ // LastSpellUnholyDK = 0;
+
+ // case SPELL_DK_FROST:
+ // if (FROST_PRESENCE > 0 && !m_bot->HasAura(FROST_PRESENCE, EFFECT_0) && !m_bot->HasAura(BLOOD_PRESENCE, EFFECT_0) && !m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_0) && m_ai->CastSpell (FROST_PRESENCE, *m_bot))
+ // return RETURN_CONTINUE;
+
+ // if (DEATHCHILL > 0)
+ // {
+ // if (!m_bot->HasAura(DEATHCHILL, EFFECT_0) && !m_bot->HasAura(KILLING_MACHINE, EFFECT_0) && m_ai->CastSpell (DEATHCHILL, *m_bot))
+ // return RETURN_CONTINUE;
+ // }
+ // else if (KILLING_MACHINE > 0)
+ // {
+ // if (!m_bot->HasAura(KILLING_MACHINE, EFFECT_0) && !m_bot->HasAura(DEATHCHILL, EFFECT_0) && m_ai->CastSpell (KILLING_MACHINE, *m_bot))
+ // return RETURN_CONTINUE;
+ // }
+
+ // if (ICY_TOUCH > 0 && !pTarget->HasAura(ICY_TOUCH, EFFECT_0) && LastSpellFrostDK < 1 && m_ai->CastSpell(ICY_TOUCH, *pTarget))
+ // {
+ // out << " Icy Touch";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (OBLITERATE > 0 && LastSpellFrostDK < 2 && m_ai->CastSpell(OBLITERATE, *pTarget))
+ // {
+ // out << " Obliterate";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (FROST_STRIKE > 0 && LastSpellFrostDK < 3 && m_ai->CastSpell(FROST_STRIKE, *pTarget))
+ // {
+ // out << " Frost Strike";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (HOWLING_BLAST > 0 && m_ai->GetAttackerCount() >= 3 && LastSpellFrostDK < 4 && m_ai->CastSpell(HOWLING_BLAST, *pTarget))
+ // {
+ // out << " Howling Blast";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (CHAINS_OF_ICE > 0 && !pTarget->HasAura(CHAINS_OF_ICE, EFFECT_0) && LastSpellFrostDK < 5 && m_ai->CastSpell(CHAINS_OF_ICE, *pTarget))
+ // {
+ // out << " Chains of Ice";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (RUNE_STRIKE > 0 && LastSpellFrostDK < 6 && m_ai->CastSpell(RUNE_STRIKE, *pTarget))
+ // {
+ // out << " Rune Strike";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (ICY_CLUTCH > 0 && !pTarget->HasAura(ICY_CLUTCH, EFFECT_0) && LastSpellFrostDK < 7 && m_ai->CastSpell(ICY_CLUTCH, *pTarget))
+ // {
+ // out << " Icy Clutch";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (ICEBOUND_FORTITUDE > 0 && m_bot->GetHealthPct() < 50 && pVictim == m_bot && !m_bot->HasAura(ICEBOUND_FORTITUDE, EFFECT_0) && LastSpellFrostDK < 8 && m_ai->CastSpell(ICEBOUND_FORTITUDE, *m_bot))
+ // {
+ // out << " Icebound Fortitude";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (MIND_FREEZE > 0 && pTarget->IsNonMeleeSpellCasted(true) && dist <= ATTACK_DISTANCE && LastSpellFrostDK < 9 && m_ai->CastSpell(MIND_FREEZE, *pTarget))
+ // {
+ // out << " Mind Freeze";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (HUNGERING_COLD > 0 && m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && LastSpellFrostDK < 10 && m_ai->CastSpell(HUNGERING_COLD, *pTarget))
+ // {
+ // out << " Hungering Cold";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (EMPOWER_WEAPON > 0 && LastSpellFrostDK < 11 && m_ai->CastSpell(EMPOWER_WEAPON, *m_bot))
+ // {
+ // out << " Empower Rune Weapon";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (UNBREAKABLE_ARMOR > 0 && !m_bot->HasAura(UNBREAKABLE_ARMOR, EFFECT_0) && m_bot->GetHealthPct() < 70 && pVictim == m_bot && LastSpellFrostDK < 12 && m_ai->CastSpell(UNBREAKABLE_ARMOR, *m_bot))
+ // {
+ // out << " Unbreakable Armor";
+ // SpellSequence = SPELL_DK_BLOOD;
+ // LastSpellFrostDK = LastSpellFrostDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (LastSpellFrostDK > 13)
+ // {
+ // LastSpellFrostDK = 0;
+ // SpellSequence = SPELL_DK_BLOOD;
+ // return RETURN_NO_ACTION_OK; // Not really OK, but that's just how the DK rotation works right now
+ // }
+
+ // LastSpellFrostDK = 0;
+
+ // case SPELL_DK_BLOOD:
+ // if (BLOOD_PRESENCE > 0 && !m_bot->HasAura(BLOOD_PRESENCE, EFFECT_0) && !m_bot->HasAura(UNHOLY_PRESENCE, EFFECT_0) && !m_bot->HasAura(FROST_PRESENCE, EFFECT_0) && m_ai->CastSpell (BLOOD_PRESENCE, *m_bot))
+ // return RETURN_CONTINUE;
+
+ // if (MARK_OF_BLOOD > 0 && !pTarget->HasAura(MARK_OF_BLOOD, EFFECT_0) && LastSpellBloodDK < 1 && m_ai->CastSpell(MARK_OF_BLOOD, *pTarget))
+ // {
+ // out << " Mark of Blood";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (BLOOD_STRIKE > 0 && LastSpellBloodDK < 2 && m_ai->CastSpell(BLOOD_STRIKE, *pTarget))
+ // {
+ // out << " Blood Strike";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (PESTILENCE > 0 && m_ai->GetAttackerCount() >= 3 && LastSpellBloodDK < 3 && m_ai->CastSpell(PESTILENCE, *pTarget))
+ // {
+ // out << " Pestilence";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (STRANGULATE > 0 && !pTarget->HasAura(STRANGULATE, EFFECT_0) && LastSpellBloodDK < 4 && m_ai->CastSpell(STRANGULATE, *pTarget))
+ // {
+ // out << " Strangulate";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (BLOOD_BOIL > 0 && m_ai->GetAttackerCount() >= 5 && dist <= ATTACK_DISTANCE && LastSpellBloodDK < 5 && m_ai->CastSpell(BLOOD_BOIL, *pTarget))
+ // {
+ // out << " Blood Boil";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (HEART_STRIKE > 0 && LastSpellBloodDK < 6 && m_ai->CastSpell(HEART_STRIKE, *pTarget))
+ // {
+ // out << " Heart Strike";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (VAMPIRIC_BLOOD > 0 && m_bot->GetHealthPct() < 70 && !m_bot->HasAura(VAMPIRIC_BLOOD, EFFECT_0) && LastSpellBloodDK < 7 && m_ai->CastSpell(VAMPIRIC_BLOOD, *m_bot))
+ // {
+ // out << " Vampiric Blood";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (RUNE_TAP > 0 && m_bot->GetHealthPct() < 70 && !m_bot->HasAura(VAMPIRIC_BLOOD, EFFECT_0) && LastSpellBloodDK < 8 && m_ai->CastSpell(RUNE_TAP, *m_bot))
+ // {
+ // out << " Rune Tap";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (HYSTERIA > 0 && m_bot->GetHealthPct() > 25 && !m_bot->HasAura(HYSTERIA, EFFECT_0) && LastSpellBloodDK < 9 && m_ai->CastSpell(HYSTERIA, *m_bot))
+ // {
+ // out << " Hysteria";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DANCING_WEAPON > 0 && !m_bot->HasAura(DANCING_WEAPON, EFFECT_0) && LastSpellBloodDK < 10 && m_ai->CastSpell(DANCING_WEAPON, *pTarget))
+ // {
+ // out << " summoning Dancing Rune Weapon";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (DARK_COMMAND > 0 && m_bot->GetHealthPct() > 50 && pVictim != m_bot && !pTarget->HasAura(DARK_COMMAND, EFFECT_0) && LastSpellBloodDK < 11 && m_ai->CastSpell(DARK_COMMAND, *pTarget))
+ // {
+ // out << " Dark Command";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (pet && DEATH_PACT > 0 && m_bot->GetHealthPct() < 50 && LastSpellBloodDK < 12 && m_ai->CastSpell(DEATH_PACT, *pet))
+ // {
+ // out << " Death Pact (sacrifice pet)";
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // LastSpellBloodDK = LastSpellBloodDK + 1;
+ // return RETURN_CONTINUE;
+ // }
+ // if (LastSpellBloodDK > 13)
+ // {
+ // LastSpellBloodDK = 0;
+ // SpellSequence = SPELL_DK_UNHOLY;
+ // return RETURN_NO_ACTION_OK; // Not really OK but that's just how DK rotation works right now
+ // }
+ //}
+ //if (m_ai->GetManager()->m_confDebugWhisper)
+ // m_ai->TellMaster(out.str().c_str());
+
+ return RETURN_NO_ACTION_UNKNOWN;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotDeathKnightAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(PLAGUE_STRIKE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+void PlayerbotDeathKnightAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //SpellSequence = SPELL_DK_UNHOLY;
+
+ //// buff master with HORN_OF_WINTER
+ //if (HORN_OF_WINTER > 0)
+ // (!GetMaster()->HasAura(HORN_OF_WINTER, EFFECT_0) && m_ai->CastSpell (HORN_OF_WINTER, *GetMaster()));
+
+ //// hp check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage(false))
+ // return;
+} // end DoNonCombatActions
+
+// Match up with "Pull()" below
+bool PlayerbotDeathKnightAI::CanPull()
+{
+ if (DEATH_GRIP && !me->HasSpellCooldown(DEATH_GRIP))
+ return true;
+
+ return false;
+}
+
+// Match up with "CanPull()" above
+bool PlayerbotDeathKnightAI::Pull()
+{
+ //if (DEATH_GRIP && m_ai->CastSpell(DEATH_GRIP))
+ // return true;
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_dk_ai.h b/src/server/game/AI/PlayerBots/bp_dk_ai.h
new file mode 100644
index 0000000..ba7f042
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_dk_ai.h
@@ -0,0 +1,159 @@
+#ifndef _PLAYERDEATHKNIGHTAI_H
+#define _PLAYERDEATHKNIGHTAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ SPELL_DK_UNHOLY,
+ SPELL_DK_FROST,
+ SPELL_DK_BLOOD
+};
+
+enum DeathKnightSpells
+{
+ ANTI_MAGIC_SHELL_1 = 48707,
+ ANTI_MAGIC_ZONE_1 = 51052,
+ ARMY_OF_THE_DEAD_1 = 42650,
+ BLOOD_BOIL_1 = 48721,
+ BLOOD_PRESENCE_1 = 48266,
+ BLOOD_STRIKE_1 = 45902,
+ BLOOD_TAP_1 = 45529,
+ BONE_SHIELD_1 = 49222,
+ CHAINS_OF_ICE_1 = 45524,
+ CORPSE_EXPLOSION_1 = 49158,
+ DANCING_RUNE_WEAPON_1 = 49028,
+ DARK_COMMAND_1 = 56222,
+ DEATH_AND_DECAY_1 = 43265,
+ DEATH_COIL_DEATH_KNIGHT_1 = 47541,
+ DEATH_GRIP_1 = 49576,
+ DEATH_PACT_1 = 48743,
+ DEATH_STRIKE_1 = 49998,
+ DEATHCHILL_1 = 49796,
+ EMPOWER_RUNE_WEAPON_1 = 47568,
+ FROST_PRESENCE_1 = 48263,
+ FROST_STRIKE_1 = 49143,
+ GHOUL_FRENZY_1 = 63560,
+ HEART_STRIKE_1 = 55050,
+ HORN_OF_WINTER_1 = 57330,
+ HOWLING_BLAST_1 = 49184,
+ HUNGERING_COLD_1 = 49203,
+ HYSTERIA_1 = 49016,
+ ICEBOUND_FORTITUDE_1 = 48792,
+ ICY_TOUCH_1 = 45477,
+ LICHBORNE_1 = 49039,
+ MARK_OF_BLOOD_1 = 49005,
+ MIND_FREEZE_1 = 47528,
+ OBLITERATE_1 = 49020,
+ PATH_OF_FROST_1 = 3714,
+ PESTILENCE_1 = 50842,
+ PLAGUE_STRIKE_1 = 45462,
+ RAISE_ALLY_1 = 61999,
+ RAISE_DEAD_1 = 46584,
+ RUNE_STRIKE_1 = 56815,
+ RUNE_TAP_1 = 48982,
+ SCOURGE_STRIKE_1 = 55090,
+ STRANGULATE_1 = 47476,
+ SUMMON_GARGOYLE_1 = 49206,
+ UNBREAKABLE_ARMOR_1 = 51271,
+ UNHOLY_PRESENCE_1 = 48265,
+ VAMPIRIC_BLOOD_1 = 55233,
+ IMPROVED_ICY_TALONS_1 = 55610
+};
+//class Player;
+
+class PlayerbotDeathKnightAI : PlayerbotClassAI
+{
+public:
+ PlayerbotDeathKnightAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotDeathKnightAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+ bool Pull();
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // buff a specific player, usually a real PC who is not in group
+ //void BuffPlayer(Player *target);
+
+ // Utility Functions
+ bool CanPull();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // Unholy
+ uint32 BONE_SHIELD,
+ PLAGUE_STRIKE,
+ DEATH_GRIP,
+ DEATH_COIL,
+ DEATH_STRIKE,
+ UNHOLY_BLIGHT,
+ SCOURGE_STRIKE,
+ DEATH_AND_DECAY,
+ UNHOLY_PRESENCE,
+ RAISE_DEAD,
+ ARMY_OF_THE_DEAD,
+ SUMMON_GARGOYLE,
+ ANTI_MAGIC_SHELL,
+ ANTI_MAGIC_ZONE,
+ GHOUL_FRENZY,
+ CORPSE_EXPLOSION;
+
+ // Frost
+ uint32 ICY_TOUCH,
+ OBLITERATE,
+ HOWLING_BLAST,
+ FROST_STRIKE,
+ CHAINS_OF_ICE,
+ RUNE_STRIKE,
+ ICY_CLUTCH,
+ HORN_OF_WINTER,
+ KILLING_MACHINE,
+ FROST_PRESENCE,
+ DEATHCHILL,
+ ICEBOUND_FORTITUDE,
+ MIND_FREEZE,
+ EMPOWER_WEAPON,
+ HUNGERING_COLD,
+ UNBREAKABLE_ARMOR,
+ IMPROVED_ICY_TALONS;
+
+ // Blood
+ uint32 BLOOD_STRIKE,
+ PESTILENCE,
+ STRANGULATE,
+ BLOOD_BOIL,
+ HEART_STRIKE,
+ MARK_OF_BLOOD,
+ BLOOD_PRESENCE,
+ RUNE_TAP,
+ VAMPIRIC_BLOOD,
+ DEATH_PACT,
+ DEATH_RUNE_MASTERY,
+ HYSTERIA,
+ DANCING_WEAPON,
+ DARK_COMMAND;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ uint32 SpellSequence, LastSpellUnholyDK, LastSpellFrostDK, LastSpellBloodDK;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_dru_ai.cpp b/src/server/game/AI/PlayerBots/bp_dru_ai.cpp
new file mode 100644
index 0000000..f4e43db
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_dru_ai.cpp
@@ -0,0 +1,787 @@
+/*
+ Name : PlayerbotDruidAI.cpp
+ Complete: maybe around 33%
+ Authors : rrtn, Natsukawa
+ Version : 0.42
+ */
+#include "bp_dru_ai.h"
+#include "SpellAuras.h"
+#include "bp_ai.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotDruidAI::PlayerbotDruidAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ MOONFIRE = PlayerbotAI::InitSpell(me, MOONFIRE_1); // attacks
+ STARFIRE = PlayerbotAI::InitSpell(me, STARFIRE_1);
+ STARFALL = PlayerbotAI::InitSpell(me, STARFALL_1);
+ WRATH = PlayerbotAI::InitSpell(me, WRATH_1);
+ ROOTS = PlayerbotAI::InitSpell(me, ENTANGLING_ROOTS_1);
+ INSECT_SWARM = PlayerbotAI::InitSpell(me, INSECT_SWARM_1);
+ FORCE_OF_NATURE = PlayerbotAI::InitSpell(me, FORCE_OF_NATURE_1);
+ HURRICANE = PlayerbotAI::InitSpell(me, HURRICANE_1);
+ MARK_OF_THE_WILD = PlayerbotAI::InitSpell(me, MARK_OF_THE_WILD_1); // buffs
+ GIFT_OF_THE_WILD = PlayerbotAI::InitSpell(me, GIFT_OF_THE_WILD_1);
+ THORNS = PlayerbotAI::InitSpell(me, THORNS_1);
+ BARKSKIN = PlayerbotAI::InitSpell(me, BARKSKIN_1);
+ INNERVATE = PlayerbotAI::InitSpell(me, INNERVATE_1);
+ FAERIE_FIRE = PlayerbotAI::InitSpell(me, FAERIE_FIRE_1); // debuffs
+ FAERIE_FIRE_FERAL = PlayerbotAI::InitSpell(me, FAERIE_FIRE_FERAL_1);
+ REJUVENATION = PlayerbotAI::InitSpell(me, REJUVENATION_1); // heals
+ REGROWTH = PlayerbotAI::InitSpell(me, REGROWTH_1);
+ WILD_GROWTH = PlayerbotAI::InitSpell(me, WILD_GROWTH_1);
+ LIFEBLOOM = PlayerbotAI::InitSpell(me, LIFEBLOOM_1);
+ NOURISH = PlayerbotAI::InitSpell(me, NOURISH_1);
+ HEALING_TOUCH = PlayerbotAI::InitSpell(me, HEALING_TOUCH_1);
+ SWIFTMEND = PlayerbotAI::InitSpell(me, SWIFTMEND_1);
+ TRANQUILITY = PlayerbotAI::InitSpell(me, TRANQUILITY_1);
+ REVIVE = PlayerbotAI::InitSpell(me, REVIVE_1);
+ REBIRTH = PlayerbotAI::InitSpell(me, REBIRTH_1);
+ REMOVE_CURSE = PlayerbotAI::InitSpell(me, REMOVE_CURSE_DRUID_1);
+ ABOLISH_POISON = PlayerbotAI::InitSpell(me, ABOLISH_POISON_1);
+ // Druid Forms
+ MOONKIN_FORM = PlayerbotAI::InitSpell(me, MOONKIN_FORM_1);
+ DIRE_BEAR_FORM = PlayerbotAI::InitSpell(me, DIRE_BEAR_FORM_1);
+ BEAR_FORM = PlayerbotAI::InitSpell(me, BEAR_FORM_1);
+ CAT_FORM = PlayerbotAI::InitSpell(me, CAT_FORM_1);
+ TREE_OF_LIFE = PlayerbotAI::InitSpell(me, TREE_OF_LIFE_1);
+ TRAVEL_FORM = PlayerbotAI::InitSpell(me, TRAVEL_FORM_1);
+ // Cat Attack type's
+ RAKE = PlayerbotAI::InitSpell(me, RAKE_1);
+ CLAW = PlayerbotAI::InitSpell(me, CLAW_1); // 45
+ COWER = PlayerbotAI::InitSpell(me, COWER_1); // 20
+ MANGLE = PlayerbotAI::InitSpell(me, MANGLE_1); // 45
+ TIGERS_FURY = PlayerbotAI::InitSpell(me, TIGERS_FURY_1);
+ MANGLE_CAT = PlayerbotAI::InitSpell(me, MANGLE_CAT_1); //40
+ // Cat Finishing Move's
+ RIP = PlayerbotAI::InitSpell(me, RIP_1); // 30
+ FEROCIOUS_BITE = PlayerbotAI::InitSpell(me, FEROCIOUS_BITE_1); // 35
+ MAIM = PlayerbotAI::InitSpell(me, MAIM_1); // 35
+ SAVAGE_ROAR = PlayerbotAI::InitSpell(me, SAVAGE_ROAR_1); //25
+ // Bear/Dire Bear Attacks & Buffs
+ BASH = PlayerbotAI::InitSpell(me, BASH_1);
+ MAUL = PlayerbotAI::InitSpell(me, MAUL_1); // 15
+ SWIPE = PlayerbotAI::InitSpell(me, SWIPE_BEAR_1); // 20
+ DEMORALIZING_ROAR = PlayerbotAI::InitSpell(me, DEMORALIZING_ROAR_1); // 10
+ CHALLENGING_ROAR = PlayerbotAI::InitSpell(me, CHALLENGING_ROAR_1);
+ ENRAGE = PlayerbotAI::InitSpell(me, ENRAGE_1);
+ GROWL = PlayerbotAI::InitSpell(me, GROWL_1);
+ MANGLE_BEAR = PlayerbotAI::InitSpell(me, MANGLE_BEAR_1);
+ LACERATE = PlayerbotAI::InitSpell(me, LACERATE_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ SHADOWMELD = PlayerbotAI::InitSpell(me, SHADOWMELD_ALL);
+ WAR_STOMP = PlayerbotAI::InitSpell(me, WAR_STOMP_ALL); // tauren
+
+ //Procs
+ ECLIPSE = PlayerbotAI::InitSpell(me, ECLIPSE_1);
+ ECLIPSE_SOLAR = PlayerbotAI::InitSpell(me, ECLIPSE_SOLAR_1);
+ ECLIPSE_LUNAR = PlayerbotAI::InitSpell(me, ECLIPSE_LUNAR_1);
+}
+
+PlayerbotDruidAI::~PlayerbotDruidAI() {}
+
+CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder())
+ // {
+ // if (pTarget->GetCombatReach() <= ATTACK_DISTANCE)
+ // {
+ // // Set everyone's UpdateAI() waiting to 2 seconds
+ // m_ai->SetGroupIgnoreUpdateTime(2);
+ // // Clear their TEMP_WAIT_TANKAGGRO flag
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // // Start attacking, force target on current target
+ // m_ai->Attack(m_ai->GetCurrentTarget());
+
+ // // While everyone else is waiting 2 second, we need to build up aggro, so don't return
+ // }
+ // else
+ // {
+ // // TODO: add check if target is ranged
+ // return RETURN_NO_ACTION_OK; // wait for target to get nearer
+ // }
+ // }
+ // else if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder())
+ // return _DoNextPVECombatManeuverHeal();
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuver(Unit* pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuverPVE(Unit* pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ ////uint32 masterHP = GetMaster()->GetHealth() * 100 / GetMaster()->GetMaxHealth();
+
+ //uint32 spec = m_bot->GetSpec();
+ //if (spec == 0) // default to spellcasting or healing for healer
+ // spec = (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder() ? DRUID_SPEC_RESTORATION : DRUID_SPEC_BALANCE);
+
+ //// Make sure healer stays put, don't even melee (aggro) if in range.
+ //if (m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //else if (!m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ ////Unit* pVictim = pTarget->getVictim();
+ //uint32 BEAR = (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM);
+
+ //// TODO: do something to allow emergency heals for non-healers?
+ //switch (CheckForms())
+ //{
+ // case RETURN_OK_SHIFTING:
+ // return RETURN_CONTINUE;
+
+ // case RETURN_FAIL:
+ // case RETURN_OK_CANNOTSHIFT:
+ // if (spec == DRUID_SPEC_FERAL)
+ // spec = DRUID_SPEC_BALANCE; // Can't shift, force spellcasting
+ // break; // rest functions without form
+
+ // //case RETURN_OK_NOCHANGE: // great!
+ // //case RETURN_FAIL_WAITINGONSELFBUFF: // This is war dammit! No time for silly buffs during combat...
+ // default:
+ // break;
+ //}
+
+ ////Used to determine if this bot is highest on threat
+ //Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //if (newTarget) // TODO: && party has a tank
+ //{
+ // if (HealPlayer(m_bot) == RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+
+ // // TODO: Heal tank
+
+ // // We have aggro, don't need to heal self or tank, wait for aggro to subside
+ // //if (m_ai->IsHealer()) // Commented out: not necessary because of below. Leave code here in case below ever changes.
+ // // return RETURN_NO_ACTION_OK;
+
+ // // We have no shoot spell; Assume auto-attack is on
+ // return RETURN_NO_ACTION_OK;
+ //}
+
+ //if (m_ai->IsHealer())
+ // return _DoNextPVECombatManeuverHeal();
+
+ //switch (spec)
+ //{
+ // case DRUID_SPEC_FERAL:
+ // if (BEAR > 0 && m_bot->HasAura(BEAR))
+ // return _DoNextPVECombatManeuverBear(pTarget);
+ // if (CAT_FORM > 0 && m_bot->HasAura(CAT_FORM))
+ // return _DoNextPVECombatManeuverCat(pTarget);
+ // // NO break - failover to DRUID_SPEC_BALANCE
+
+ // case DRUID_SPEC_RESTORATION: // There is no Resto DAMAGE rotation. If you insist, go Balance...
+ // case DRUID_SPEC_BALANCE:
+ // if (m_bot->HasAura(BEAR) || m_bot->HasAura(CAT_FORM) || m_bot->HasAura(TREE_OF_LIFE))
+ // return RETURN_NO_ACTION_UNKNOWN; // Didn't shift out of inappropriate form
+
+ // return _DoNextPVECombatManeuverSpellDPS(pTarget);
+
+ // /*if (BASH > 0 && !pTarget->HasAura(BASH, EFFECT_0) && DruidSpellCombat < 5 && CastSpell(BASH, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CHALLENGING_ROAR > 0 && pVictim != m_bot && !pTarget->HasAura(CHALLENGING_ROAR, EFFECT_0) && !pTarget->HasAura(GROWL, EFFECT_0) && CastSpell(CHALLENGING_ROAR, pTarget))
+ // return RETURN_CONTINUE;
+ // if (ROOTS > 0 && !pTarget->HasAura(ROOTS, EFFECT_0) && CastSpell(ROOTS, pTarget))
+ // return RETURN_CONTINUE;
+ // if (HURRICANE > 0 && m_ai->GetAttackerCount() >= 5 && CastSpell(HURRICANE, pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(10);
+ // return RETURN_CONTINUE;
+ // }
+ // if (STARFALL > 0 && !m_bot->HasAura(STARFALL, EFFECT_0) && m_ai->GetAttackerCount() >= 3 && CastSpell(STARFALL, pTarget))
+ // return RETURN_CONTINUE;
+ // if (BARKSKIN > 0 && pVictim == m_bot && m_bot->GetHealthPct() < 75 && !m_bot->HasAura(BARKSKIN, EFFECT_0) && CastSpell(BARKSKIN, m_bot))
+ // return RETURN_CONTINUE;
+ // if (INNERVATE > 0 && !m_bot->HasAura(INNERVATE, EFFECT_0) && CastSpell(INNERVATE, m_bot))
+ // return RETURN_CONTINUE;
+ // */
+ //}
+
+ return RETURN_NO_ACTION_UNKNOWN;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotDruidAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(MOONFIRE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverBear(Unit* pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //if (!m_bot->HasAura( (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM) )) return RETURN_NO_ACTION_ERROR;
+
+ //// Used to determine if this bot is highest on threat
+ //Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //Unit* pVictim = pTarget->getVictim();
+
+ //// Face enemy, make sure you're attacking
+ //if (!m_bot->HasInArc(M_PI, pTarget))
+ //{
+ // m_bot->SetFacingTo(m_bot->GetAngle(pTarget));
+ // if (pVictim)
+ // pVictim->Attack(pTarget, true);
+ //}
+
+ //if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder() && !newTarget && GROWL > 0 && !m_bot->HasSpellCooldown(GROWL))
+ // if (CastSpell(GROWL, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (FAERIE_FIRE_FERAL > 0 && !pTarget->HasAura(FAERIE_FIRE_FERAL, EFFECT_0))
+ // if (CastSpell(FAERIE_FIRE_FERAL, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (SWIPE > 0 && m_ai->GetAttackerCount() >= 2 && CastSpell(SWIPE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (ENRAGE > 0 && !m_bot->HasSpellCooldown(ENRAGE) && CastSpell(ENRAGE, m_bot))
+ // return RETURN_CONTINUE;
+
+ //if (DEMORALIZING_ROAR > 0 && !pTarget->HasAura(DEMORALIZING_ROAR, EFFECT_0) && CastSpell(DEMORALIZING_ROAR, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (MANGLE_BEAR > 0 && !pTarget->HasAura(MANGLE_BEAR) && CastSpell(MANGLE_BEAR, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (LACERATE > 0 && !pTarget->HasAura(LACERATE, EFFECT_0) && CastSpell(LACERATE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (MAUL > 0 && CastSpell(MAUL, pTarget))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverCat(Unit* pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //if (!m_bot->HasAura(CAT_FORM)) return RETURN_NO_ACTION_UNKNOWN;
+
+ ////Used to determine if this bot is highest on threat
+ //Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //Unit* pVictim = pTarget->getVictim();
+
+ //// Face enemy, make sure you're attacking
+ //if (!m_bot->HasInArc(M_PI, pTarget))
+ //{
+ // m_bot->SetFacingTo(m_bot->GetAngle(pTarget));
+ // if (pVictim)
+ // pVictim->Attack(pTarget, true);
+ //}
+
+ //// Attempt to do a finishing move
+ //if (m_bot->GetComboPoints() >= 5)
+ //{
+ // // 25 Energy
+ // if (SAVAGE_ROAR > 0 && !m_bot->HasAura(SAVAGE_ROAR))
+ // {
+ // if (CastSpell(SAVAGE_ROAR, pTarget))
+ // return RETURN_CONTINUE;
+ // }
+ // // 30 Energy
+ // else if (RIP > 0 && !pTarget->HasAura(RIP, EFFECT_0))
+ // {
+ // if (CastSpell(RIP, pTarget))
+ // return RETURN_CONTINUE;
+ // }
+ // // 35 Energy
+ // else if (FEROCIOUS_BITE > 0)
+ // {
+ // if (CastSpell(FEROCIOUS_BITE, pTarget))
+ // return RETURN_CONTINUE;
+ // }
+ //} // End 5 ComboPoints
+
+ //if (newTarget && COWER > 0 && !m_bot->HasSpellCooldown(COWER) && CastSpell(COWER, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (FAERIE_FIRE_FERAL > 0 && !pTarget->HasAura(FAERIE_FIRE_FERAL, EFFECT_0) && CastSpell(FAERIE_FIRE_FERAL, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (TIGERS_FURY > 0 && !m_bot->HasSpellCooldown(TIGERS_FURY) && CastSpell(TIGERS_FURY))
+ // return RETURN_CONTINUE;
+
+ //if (MANGLE_CAT > 0 && !pTarget->HasAura(MANGLE_CAT) && CastSpell(MANGLE_CAT))
+ // return RETURN_CONTINUE;
+
+ //if (RAKE > 0 && !pTarget->HasAura(RAKE) && CastSpell(RAKE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (CLAW > 0 && CastSpell(CLAW, pTarget))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverSpellDPS(Unit* pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //uint32 NATURE = (STARFIRE > 0 ? STARFIRE : WRATH);
+
+ //if (FAERIE_FIRE > 0 && !pTarget->HasAura(FAERIE_FIRE, EFFECT_0) && CastSpell(FAERIE_FIRE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (MOONFIRE > 0 && !pTarget->HasAura(MOONFIRE, EFFECT_0) && CastSpell(MOONFIRE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (INSECT_SWARM > 0 && !pTarget->HasAura(INSECT_SWARM, EFFECT_0) && CastSpell(INSECT_SWARM, pTarget))
+ // return RETURN_CONTINUE;
+
+ //// TODO: Doesn't work, I can't seem to nail the aura/effect index that would make this work properly
+ //if (ECLIPSE_SOLAR > 0 && WRATH > 0 && m_bot->HasAura(ECLIPSE_SOLAR) && CastSpell(WRATH, pTarget))
+ // return RETURN_CONTINUE;
+
+ //// TODO: Doesn't work, I can't seem to nail the aura/effect index that would make this work properly
+ //if (ECLIPSE_LUNAR > 0 && STARFIRE > 0 && m_bot->HasAura(ECLIPSE_LUNAR) && CastSpell(STARFIRE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //if (FORCE_OF_NATURE > 0 && CastSpell(FORCE_OF_NATURE))
+ // return RETURN_CONTINUE;
+
+ //if (NATURE > 0 && CastSpell(NATURE, pTarget))
+ // return RETURN_CONTINUE;
+
+ //// Face enemy, make sure you're attacking
+ //if (!m_bot->HasInArc(M_PI, pTarget))
+ //{
+ // m_bot->SetFacingTo(m_bot->GetAngle(pTarget));
+ // if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE)
+ // m_bot->Attack(pTarget, true);
+ // else
+ // m_bot->AttackStop();
+ //}
+
+ return RETURN_NO_ACTION_UNKNOWN;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::_DoNextPVECombatManeuverHeal()
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //// (un)Shapeshifting is considered one step closer so will return true (and have the bot wait a bit for the GCD)
+ //if (TREE_OF_LIFE > 0 && !m_bot->HasAura(TREE_OF_LIFE, EFFECT_0))
+ // if (CastSpell(TREE_OF_LIFE, m_bot))
+ // return RETURN_CONTINUE;
+
+ //if (m_bot->HasAura(CAT_FORM, EFFECT_0))
+ //{
+ // m_bot->RemoveAurasDueToSpell(CAT_FORM_1);
+ // //m_ai->TellMaster("FormClearCat");
+ // return RETURN_CONTINUE;
+ //}
+ //if (m_bot->HasAura(BEAR_FORM, EFFECT_0))
+ //{
+ // m_bot->RemoveAurasDueToSpell(BEAR_FORM_1);
+ // //m_ai->TellMaster("FormClearBear");
+ // return RETURN_CONTINUE;
+ //}
+ //if (m_bot->HasAura(DIRE_BEAR_FORM, EFFECT_0))
+ //{
+ // m_bot->RemoveAurasDueToSpell(DIRE_BEAR_FORM_1);
+ // //m_ai->TellMaster("FormClearDireBear");
+ // return RETURN_CONTINUE;
+ //}
+ //// spellcasting form, but disables healing spells so it's got to go
+ //if (m_bot->HasAura(MOONKIN_FORM, EFFECT_0))
+ //{
+ // m_bot->RemoveAurasDueToSpell(MOONKIN_FORM_1);
+ // //m_ai->TellMaster("FormClearMoonkin");
+ // return RETURN_CONTINUE;
+ //}
+
+ //if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+}
+
+CombatManeuverReturns PlayerbotDruidAI::HealPlayer(Player* target)
+{
+ //CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target);
+ //if (r != RETURN_NO_ACTION_OK)
+ // return r;
+
+ //if (!target->isAlive())
+ //{
+ // if (m_bot->isInCombat())
+ // {
+ // // TODO: Add check for cooldown
+ // if (REBIRTH && m_ai->CastSpell(REBIRTH, *target))
+ // {
+ // std::string msg = "Resurrecting ";
+ // msg += target->GetName();
+ // m_bot->Say(msg, LANG_UNIVERSAL);
+ // return RETURN_CONTINUE;
+ // }
+ // }
+ // else
+ // {
+ // if (REVIVE && m_ai->CastSpell(REVIVE, *target))
+ // {
+ // std::string msg = "Resurrecting ";
+ // msg += target->GetName();
+ // m_bot->Say(msg, LANG_UNIVERSAL);
+ // return RETURN_CONTINUE;
+ // }
+ // }
+ // return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM
+ //}
+
+ ////If spell exists and orders say we should be dispelling
+ //if ((REMOVE_CURSE > 0 || ABOLISH_POISON > 0) && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0)
+ //{
+ // //This does something important(lol)
+ // uint32 dispelMask = SpellInfo::GetDispelMask(DISPEL_CURSE);
+ // uint32 dispelMask2 = SpellInfo::GetDispelMask(DISPEL_POISON);
+ // //Get a list of all the targets auras(spells affecting target)
+ // Unit::AuraMap const& auras = target->GetOwnedAuras();
+ // //Iterate through the auras
+ // for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); itr++)
+ // {
+ // Aura *holder = itr->second;
+ // //I dont know what this does but it doesn't work without it
+ // if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask)
+ // {
+ // //If the spell is dispellable and we can dispel it, do so
+ // if ((holder->GetSpellInfo()->Dispel == DISPEL_CURSE) & (REMOVE_CURSE > 0))
+ // {
+ // if (CastSpell(REMOVE_CURSE, target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // else if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask2)
+ // {
+ // if ((holder->GetSpellInfo()->Dispel == DISPEL_POISON) & (ABOLISH_POISON > 0))
+ // {
+ // if (CastSpell(ABOLISH_POISON, target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // }
+ //}
+
+ //uint8 hp = target->GetHealthPct();
+
+ //// Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check)
+ //if (hp >= 90)
+ // return RETURN_NO_ACTION_OK;
+
+ //// Reset form if needed
+ //if (!m_bot->HasAura(TREE_OF_LIFE) || TREE_OF_LIFE == 0)
+ // GoBuffForm(GetPlayerBot());
+
+ //// Start heals. Do lowest HP checks at the top
+ //if (hp < 30)
+ //{
+ // // TODO: Use in conjunction with Nature's Swiftness
+ // if (HEALING_TOUCH > 0 && (NOURISH == 0 /*|| CastSpell(NATURES_SWIFTNESS)*/ ) && CastSpell(HEALING_TOUCH, target))
+ // return RETURN_CONTINUE;
+
+ // if (NOURISH > 0 && CastSpell(NOURISH, target))
+ // return RETURN_CONTINUE;
+ //}
+
+ //if (hp < 45 && WILD_GROWTH > 0 && !target->HasAura(WILD_GROWTH) && CastSpell(WILD_GROWTH, target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 50 && SWIFTMEND > 0 && (target->HasAura(REJUVENATION) || target->HasAura(REGROWTH)) && CastSpell(SWIFTMEND, target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 60 && REGROWTH > 0 && !target->HasAura(REGROWTH) && CastSpell(REGROWTH, target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 65 && LIFEBLOOM > 0 && !target->HasAura(LIFEBLOOM) && CastSpell(LIFEBLOOM, target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 90 && REJUVENATION > 0 && !target->HasAura(REJUVENATION) && CastSpell(REJUVENATION, target))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+} // end HealTarget
+
+/**
+* CheckForms()
+*
+* Returns bool - Value indicates success - shape was shifted, already shifted, no need to shift.
+*/
+uint8 PlayerbotDruidAI::CheckForms()
+{
+ //if (!m_ai) return RETURN_FAIL;
+ //if (!m_bot) return RETURN_FAIL;
+
+ //uint32 spec = m_bot->GetSpec();
+ //uint32 BEAR = (DIRE_BEAR_FORM > 0 ? DIRE_BEAR_FORM : BEAR_FORM);
+
+ //if (spec == DRUID_SPEC_BALANCE)
+ //{
+ // if (m_bot->HasAura(MOONKIN_FORM))
+ // return RETURN_OK_NOCHANGE;
+
+ // if (!MOONKIN_FORM)
+ // return RETURN_OK_CANNOTSHIFT;
+
+ // if (CastSpell(MOONKIN_FORM))
+ // return RETURN_OK_SHIFTING;
+ // else
+ // return RETURN_FAIL;
+ //}
+
+ //if (spec == DRUID_SPEC_FERAL)
+ //{
+ // // Use Bear form only if we are told we're a tank and have thorns up
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK)
+ // {
+ // if (m_bot->HasAura(BEAR))
+ // return RETURN_OK_NOCHANGE;
+
+ // if (!BEAR)
+ // return RETURN_OK_CANNOTSHIFT;
+
+ // if (!m_bot->HasAura(THORNS))
+ // return RETURN_FAIL_WAITINGONSELFBUFF;
+
+ // if (CastSpell(BEAR))
+ // return RETURN_OK_SHIFTING;
+ // else
+ // return RETURN_FAIL;
+ // }
+ // else // No tank orders - try to go kitty or at least bear
+ // {
+ // if (CAT_FORM > 0)
+ // {
+ // if (m_bot->HasAura(CAT_FORM))
+ // return RETURN_OK_NOCHANGE;
+
+ // if (CastSpell(CAT_FORM))
+ // return RETURN_OK_SHIFTING;
+ // else
+ // return RETURN_FAIL;
+ // }
+
+ // if (BEAR > 0)
+ // {
+ // if (m_bot->HasAura(BEAR))
+ // return RETURN_OK_NOCHANGE;
+
+ // if (CastSpell(BEAR))
+ // return RETURN_OK_SHIFTING;
+ // else
+ // return RETURN_FAIL;
+ // }
+
+ // return RETURN_OK_CANNOTSHIFT;
+ // }
+ //}
+
+ //if (spec == DRUID_SPEC_RESTORATION)
+ //{
+ // if (m_bot->HasAura(TREE_OF_LIFE))
+ // return RETURN_OK_NOCHANGE;
+
+ // if (!TREE_OF_LIFE)
+ // return RETURN_OK_CANNOTSHIFT;
+
+ // if (CastSpell(TREE_OF_LIFE))
+ // return RETURN_OK_SHIFTING;
+ // else
+ // return RETURN_FAIL;
+ //}
+
+ // Unknown Spec
+ return RETURN_FAIL;
+}
+
+void PlayerbotDruidAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //if (!m_bot->isAlive() || m_bot->IsInDuel()) return;
+
+ //// Revive
+ //if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE)
+ // return;
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(m_bot) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+
+ //// Buff
+ //if (m_bot->GetGroup() && GIFT_OF_THE_WILD && m_ai->HasSpellReagents(GIFT_OF_THE_WILD) && m_ai->Buff(GIFT_OF_THE_WILD, m_bot))
+ // return;
+ //if (Buff(&PlayerbotDruidAI::BuffHelper, MARK_OF_THE_WILD))
+ // return;
+ //if (Buff(&PlayerbotDruidAI::BuffHelper, THORNS, (m_bot->GetGroup() ? JOB_TANK : JOB_ALL)))
+ // return;
+
+ //// Return to fighting form AFTER reviving, healing, buffing
+ //CheckForms();
+
+ //// hp/mana check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage())
+ // return;
+
+ //if (INNERVATE && !m_bot->HasAura(INNERVATE) && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) <= 20 && CastSpell(INNERVATE, m_bot))
+ // return;
+} // end DoNonCombatActions
+
+bool PlayerbotDruidAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit* target)
+{
+ //if (!ai) return false;
+ //if (spellId == 0) return false;
+ //if (!target) return false;
+
+ //Pet * pet = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer()->GetPet() : NULL;
+ //if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet, &(PlayerbotDruidAI::GoBuffForm)))
+ // return true;
+
+ //if (ai->Buff(spellId, target, &(PlayerbotDruidAI::GoBuffForm)))
+ // return true;
+
+ return false;
+}
+
+void PlayerbotDruidAI::GoBuffForm(Player* self)
+{
+ // RANK_1 spell ids used because this is a static method which does not have access to instance.
+ // There is only one rank for these spells anyway.
+ if (self->HasAura(CAT_FORM_1))
+ self->RemoveAurasDueToSpell(CAT_FORM_1);
+ if (self->HasAura(BEAR_FORM_1))
+ self->RemoveAurasDueToSpell(BEAR_FORM_1);
+ if (self->HasAura(DIRE_BEAR_FORM_1))
+ self->RemoveAurasDueToSpell(DIRE_BEAR_FORM_1);
+ if (self->HasAura(MOONKIN_FORM_1))
+ self->RemoveAurasDueToSpell(MOONKIN_FORM_1);
+ if (self->HasAura(TRAVEL_FORM_1))
+ self->RemoveAurasDueToSpell(TRAVEL_FORM_1);
+}
+
+// Match up with "Pull()" below
+bool PlayerbotDruidAI::CanPull()
+{
+ if (BEAR_FORM && FAERIE_FIRE_FERAL)
+ return true;
+
+ return false;
+}
+
+// Match up with "CanPull()" above
+bool PlayerbotDruidAI::Pull()
+{
+ //if (BEAR_FORM && (CastSpell(FAERIE_FIRE_FERAL) & RETURN_CONTINUE))
+ // return true;
+
+ return false;
+}
+
+bool PlayerbotDruidAI::CastHoTOnTank()
+{
+ //if (!m_ai) return false;
+
+ //if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false;
+
+ //// Druid HoTs: Rejuvenation, Regrowth, Tranquility (channeled, AoE), Lifebloom, and Wild Growth
+ //if (REJUVENATION)
+ // return (RETURN_CONTINUE & CastSpell(REJUVENATION, m_ai->GetGroupTank()));
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_dru_ai.h b/src/server/game/AI/PlayerBots/bp_dru_ai.h
new file mode 100644
index 0000000..acac4e1
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_dru_ai.h
@@ -0,0 +1,223 @@
+#ifndef _PLAYERBOTDRUIDAI_H
+#define _PLAYERBOTDRUIDAI_H
+
+#include "bp_cai.h"
+
+enum DruidSpells
+{
+ ABOLISH_POISON_1 = 2893,
+ AQUATIC_FORM_1 = 1066,
+ BARKSKIN_1 = 22812,
+ BASH_1 = 5211,
+ BEAR_FORM_1 = 5487,
+ BERSERK_1 = 50334,
+ CAT_FORM_1 = 768,
+ CHALLENGING_ROAR_1 = 5209,
+ CLAW_1 = 1082,
+ COWER_1 = 8998,
+ CURE_POISON_1 = 8946,
+ CYCLONE_1 = 33786,
+ DASH_1 = 1850,
+ DEMORALIZING_ROAR_1 = 99,
+ DIRE_BEAR_FORM_1 = 9634,
+ ENRAGE_1 = 5229,
+ ENTANGLING_ROOTS_1 = 339,
+ FAERIE_FIRE_1 = 770,
+ FAERIE_FIRE_FERAL_1 = 16857,
+ FERAL_CHARGE_1 = 49377,
+ FERAL_CHARGE_BEAR_1 = 16979,
+ FERAL_CHARGE_CAT_1 = 49376,
+ FEROCIOUS_BITE_1 = 22568,
+ FLIGHT_FORM_1 = 33943,
+ FORCE_OF_NATURE_1 = 33831,
+ FRENZIED_REGENERATION_1 = 22842,
+ GIFT_OF_THE_WILD_1 = 21849,
+ GROWL_1 = 6795,
+ HEALING_TOUCH_1 = 5185,
+ HIBERNATE_1 = 2637,
+ HURRICANE_1 = 16914,
+ INNERVATE_1 = 29166,
+ INSECT_SWARM_1 = 5570,
+ LACERATE_1 = 33745,
+ LIFEBLOOM_1 = 33763,
+ MAIM_1 = 22570,
+ MANGLE_1 = 33917,
+ MANGLE_BEAR_1 = 33878,
+ MANGLE_CAT_1 = 33876,
+ MARK_OF_THE_WILD_1 = 1126,
+ MAUL_1 = 6807,
+ MOONFIRE_1 = 8921,
+ MOONKIN_FORM_1 = 24858,
+ NATURES_GRASP_1 = 16689,
+ NATURES_SWIFTNESS_DRUID_1 = 17116,
+ NOURISH_1 = 50464,
+ POUNCE_1 = 9005,
+ PROWL_1 = 5215,
+ RAKE_1 = 1822,
+ RAVAGE_1 = 6785,
+ REBIRTH_1 = 20484,
+ REGROWTH_1 = 8936,
+ REJUVENATION_1 = 774,
+ REMOVE_CURSE_DRUID_1 = 2782,
+ REVIVE_1 = 50769,
+ RIP_1 = 1079,
+ SAVAGE_ROAR_1 = 52610,
+ SHRED_1 = 5221,
+ SOOTHE_ANIMAL_1 = 2908,
+ STARFALL_1 = 48505,
+ STARFIRE_1 = 2912,
+ SURVIVAL_INSTINCTS_1 = 61336,
+ SWIFTMEND_1 = 18562,
+ SWIFT_FLIGHT_FORM_1 = 40120,
+ SWIPE_BEAR_1 = 779,
+ SWIPE_CAT_1 = 62078,
+ THORNS_1 = 467,
+ TIGERS_FURY_1 = 5217,
+ TRANQUILITY_1 = 740,
+ TRAVEL_FORM_1 = 783,
+ TREE_OF_LIFE_1 = 33891,
+ TYPHOON_1 = 50516,
+ WILD_GROWTH_1 = 48438,
+ WRATH_1 = 5176,
+ ECLIPSE_1 = 48525,
+
+ //Procs
+ ECLIPSE_SOLAR_1 = 48517,
+ ECLIPSE_LUNAR_1 = 48518
+};
+
+//class Player;
+
+class PlayerbotDruidAI : PlayerbotClassAI
+{
+public:
+ PlayerbotDruidAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotDruidAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+ bool Pull();
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // Utility Functions
+ bool CanPull();
+ bool CastHoTOnTank();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = NULL) { return CastSpellNoRanged(nextAction, pTarget); }
+
+ // Combat Maneuver helper functions
+ CombatManeuverReturns _DoNextPVECombatManeuverBear(Unit* pTarget);
+ CombatManeuverReturns _DoNextPVECombatManeuverCat(Unit* pTarget);
+ CombatManeuverReturns _DoNextPVECombatManeuverSpellDPS(Unit* pTarget);
+ CombatManeuverReturns _DoNextPVECombatManeuverHeal();
+
+ // Heals the target based off its hps
+ CombatManeuverReturns HealPlayer (Player* target);
+ Player* GetHealTarget() { return PlayerbotClassAI::GetHealTarget(); }
+
+ static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target);
+ // Callback method to reset shapeshift forms blocking buffs and heals
+ static void GoBuffForm(Player *self);
+
+ //Assumes form based on spec
+ uint8 CheckForms();
+ enum CheckForms_ReturnValues {
+ RETURN_FAIL = 0,
+ RETURN_FAIL_WAITINGONSELFBUFF,
+ RETURN_OK_NOCHANGE,
+ RETURN_OK_SHIFTING,
+ RETURN_OK_CANNOTSHIFT
+ };
+
+ // druid cat/bear/dire bear/moonkin/tree of life forms
+ uint32 CAT_FORM,
+ BEAR_FORM,
+ DIRE_BEAR_FORM,
+ MOONKIN_FORM,
+ TREE_OF_LIFE,
+ TRAVEL_FORM;
+
+ // druid cat attacks
+ uint32 CLAW,
+ COWER,
+ TIGERS_FURY,
+ RAKE,
+ RIP,
+ FEROCIOUS_BITE,
+ MAIM,
+ MANGLE,
+ MANGLE_CAT,
+ SAVAGE_ROAR;
+
+ // druid bear/dire bear attacks & buffs
+ uint32 BASH,
+ MAUL,
+ SWIPE,
+ DEMORALIZING_ROAR,
+ CHALLENGING_ROAR,
+ GROWL,
+ ENRAGE,
+ FAERIE_FIRE_FERAL,
+ MANGLE_BEAR,
+ LACERATE;
+
+ // druid caster DPS attacks & debuffs
+ uint32 MOONFIRE,
+ ROOTS,
+ WRATH,
+ STARFALL,
+ STARFIRE,
+ INSECT_SWARM,
+ FAERIE_FIRE,
+ FORCE_OF_NATURE,
+ HURRICANE,
+ ECLIPSE_SOLAR,
+ ECLIPSE_LUNAR,
+ ECLIPSE;
+
+ // druid buffs
+ uint32 MARK_OF_THE_WILD,
+ GIFT_OF_THE_WILD,
+ THORNS,
+ INNERVATE,
+ BARKSKIN;
+
+ // druid heals
+ uint32 LIFEBLOOM,
+ REJUVENATION,
+ REGROWTH,
+ NOURISH,
+ HEALING_TOUCH,
+ WILD_GROWTH,
+ SWIFTMEND,
+ TRANQUILITY,
+ REVIVE,
+ REBIRTH,
+ REMOVE_CURSE,
+ ABOLISH_POISON;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ uint32 SpellSequence, DruidSpellCombat;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_hun_ai.cpp b/src/server/game/AI/PlayerBots/bp_hun_ai.cpp
new file mode 100644
index 0000000..87d32b0
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_hun_ai.cpp
@@ -0,0 +1,407 @@
+// an improved Hunter by rrtn & Runsttren :)
+#include "bp_hun_ai.h"
+#include "bp_ai.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ // PET CTRL
+ PET_SUMMON = PlayerbotAI::InitSpell(me, CALL_PET_1);
+ PET_DISMISS = PlayerbotAI::InitSpell(me, DISMISS_PET_1);
+ PET_REVIVE = PlayerbotAI::InitSpell(me, REVIVE_PET_1);
+ PET_MEND = PlayerbotAI::InitSpell(me, MEND_PET_1);
+ PET_FEED = 1539;
+
+ INTIMIDATION = PlayerbotAI::InitSpell(me, INTIMIDATION_1); // (generic)
+
+ // PET SKILLS must be initialized by pets
+ SONIC_BLAST = 0; // bat
+ DEMORALIZING_SCREECH = 0;
+ BAD_ATTITUDE = 0; // crocolisk
+ NETHER_SHOCK = 0;
+
+ // RANGED COMBAT
+ AUTO_SHOT = PlayerbotAI::InitSpell(me, AUTO_SHOT_1);
+ HUNTERS_MARK = PlayerbotAI::InitSpell(me, HUNTERS_MARK_1);
+ ARCANE_SHOT = PlayerbotAI::InitSpell(me, ARCANE_SHOT_1);
+ CONCUSSIVE_SHOT = PlayerbotAI::InitSpell(me, CONCUSSIVE_SHOT_1);
+ DISTRACTING_SHOT = PlayerbotAI::InitSpell(me, DISTRACTING_SHOT_1);
+ MULTI_SHOT = PlayerbotAI::InitSpell(me, MULTISHOT_1);
+ EXPLOSIVE_SHOT = PlayerbotAI::InitSpell(me, EXPLOSIVE_SHOT_1);
+ SERPENT_STING = PlayerbotAI::InitSpell(me, SERPENT_STING_1);
+ SCORPID_STING = PlayerbotAI::InitSpell(me, SCORPID_STING_1);
+ WYVERN_STING = PlayerbotAI::InitSpell(me, WYVERN_STING_1);
+ VIPER_STING = PlayerbotAI::InitSpell(me, VIPER_STING_1);
+ AIMED_SHOT = PlayerbotAI::InitSpell(me, AIMED_SHOT_1);
+ STEADY_SHOT = PlayerbotAI::InitSpell(me, STEADY_SHOT_1);
+ CHIMERA_SHOT = PlayerbotAI::InitSpell(me, CHIMERA_SHOT_1);
+ VOLLEY = PlayerbotAI::InitSpell(me, VOLLEY_1);
+ BLACK_ARROW = PlayerbotAI::InitSpell(me, BLACK_ARROW_1);
+ KILL_SHOT = PlayerbotAI::InitSpell(me, KILL_SHOT_1);
+
+ // MELEE
+ RAPTOR_STRIKE = PlayerbotAI::InitSpell(me, RAPTOR_STRIKE_1);
+ WING_CLIP = PlayerbotAI::InitSpell(me, WING_CLIP_1);
+ MONGOOSE_BITE = PlayerbotAI::InitSpell(me, MONGOOSE_BITE_1);
+ DISENGAGE = PlayerbotAI::InitSpell(me, DISENGAGE_1);
+ MISDIRECTION = PlayerbotAI::InitSpell(me, MISDIRECTION_1);
+ DETERRENCE = PlayerbotAI::InitSpell(me, DETERRENCE_1);
+
+ // TRAPS
+ BEAR_TRAP = 0; // non-player spell
+ FREEZING_TRAP = PlayerbotAI::InitSpell(me, FREEZING_TRAP_1);
+ IMMOLATION_TRAP = PlayerbotAI::InitSpell(me, IMMOLATION_TRAP_1);
+ FROST_TRAP = PlayerbotAI::InitSpell(me, FROST_TRAP_1);
+ EXPLOSIVE_TRAP = PlayerbotAI::InitSpell(me, EXPLOSIVE_TRAP_1);
+ ARCANE_TRAP = 0; // non-player spell
+ SNAKE_TRAP = PlayerbotAI::InitSpell(me, SNAKE_TRAP_1);
+
+ // BUFFS
+ ASPECT_OF_THE_HAWK = PlayerbotAI::InitSpell(me, ASPECT_OF_THE_HAWK_1);
+ ASPECT_OF_THE_MONKEY = PlayerbotAI::InitSpell(me, ASPECT_OF_THE_MONKEY_1);
+ RAPID_FIRE = PlayerbotAI::InitSpell(me, RAPID_FIRE_1);
+ TRUESHOT_AURA = PlayerbotAI::InitSpell(me, TRUESHOT_AURA_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_MANA_CLASSES);
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_HUNTER); // draenei
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ SHADOWMELD = PlayerbotAI::InitSpell(me, SHADOWMELD_ALL);
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_MELEE_CLASSES); // orc
+ WAR_STOMP = PlayerbotAI::InitSpell(me, WAR_STOMP_ALL); // tauren
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+
+ m_petSummonFailed = false;
+}
+
+PlayerbotHunterAI::~PlayerbotHunterAI() {}
+
+CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotHunterAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+ //if (!pTarget) return RETURN_NO_ACTION_ERROR;
+
+ //Unit* pVictim = pTarget->getVictim();
+
+ //// check for pet and heal if neccessary
+ //Pet *pet = m_bot->GetPet();
+ //// TODO: clarify/simplify: !pet->getDeathState() != ALIVE
+ //if (pet && PET_MEND > 0 && pet->isAlive() && pet->GetHealthPct() < 50 && pVictim != m_bot && !pet->HasAura(PET_MEND, EFFECT_0) && m_ai->CastSpell(PET_MEND, *m_bot))
+ //{
+ // m_ai->TellMaster("healing pet.");
+ // return RETURN_CONTINUE;
+ //}
+ //else if (pet && INTIMIDATION > 0 && pVictim == pet && !pet->HasAura(INTIMIDATION, EFFECT_0) && m_ai->CastSpell(INTIMIDATION, *m_bot))
+ // return RETURN_CONTINUE;
+
+ //// racial traits
+ //if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_0))
+ // m_ai->CastSpell(BLOOD_FURY, *m_bot);
+ //else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_0))
+ // m_ai->CastSpell(BERSERKING, *m_bot);
+
+ //// check if ranged combat is possible
+ //float dist = pTarget->GetCombatReach();
+ //if ((dist <= ATTACK_DISTANCE || !m_bot->GetUInt32Value(PLAYER_AMMO_ID)) && m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED)
+ //{
+ // // switch to melee combat (target in melee range, out of ammo)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+ // if (!m_bot->GetUInt32Value(PLAYER_AMMO_ID))
+ // m_ai->TellMaster("Out of ammo!");
+ //}
+ //else if (dist > ATTACK_DISTANCE && m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_MELEE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+
+ //// Set appropriate aspect
+ //if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED)
+ //{
+ // if (ASPECT_OF_THE_HAWK && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_0))
+ // m_ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot);
+ //}
+ //else
+ //{
+ // if (ASPECT_OF_THE_MONKEY && !m_bot->HasAura(ASPECT_OF_THE_MONKEY, EFFECT_0))
+ // m_ai->CastSpell(ASPECT_OF_THE_MONKEY, *m_bot);
+ //}
+
+ //// activate auto shot: Reworked to account for AUTO_SHOT being a triggered spell
+ //if (AUTO_SHOT > 0 && m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED && m_ai->GetCurrentSpellId() != AUTO_SHOT)
+ // m_bot->CastSpell(pTarget, AUTO_SHOT, true);
+
+ //// damage spells
+ //if (m_ai->GetCombatStyle() == PlayerbotAI::COMBAT_RANGED)
+ //{
+ // if (HUNTERS_MARK > 0 && !pTarget->HasAura(HUNTERS_MARK, EFFECT_0) && m_ai->CastSpell(HUNTERS_MARK, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (RAPID_FIRE > 0 && !m_bot->HasAura(RAPID_FIRE, EFFECT_0) && m_ai->CastSpell(RAPID_FIRE, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (MULTI_SHOT > 0 && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(MULTI_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (ARCANE_SHOT > 0 && m_ai->CastSpell(ARCANE_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (CONCUSSIVE_SHOT > 0 && !pTarget->HasAura(CONCUSSIVE_SHOT, EFFECT_0) && m_ai->CastSpell(CONCUSSIVE_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (EXPLOSIVE_SHOT > 0 && !pTarget->HasAura(EXPLOSIVE_SHOT, EFFECT_0) && m_ai->CastSpell(EXPLOSIVE_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (VIPER_STING > 0 && pTarget->GetPower(POWER_MANA) > 0 && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) < 70 && !pTarget->HasAura(VIPER_STING, EFFECT_0) && m_ai->CastSpell(VIPER_STING, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (SERPENT_STING > 0 && !pTarget->HasAura(SERPENT_STING, EFFECT_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_0) && !pTarget->HasAura(VIPER_STING, EFFECT_0) && m_ai->CastSpell(SERPENT_STING, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (SCORPID_STING > 0 && !pTarget->HasAura(WYVERN_STING, EFFECT_0) && !pTarget->HasAura(SCORPID_STING, EFFECT_0) && !pTarget->HasAura(SERPENT_STING, EFFECT_0) && !pTarget->HasAura(VIPER_STING, EFFECT_0) && m_ai->CastSpell(SCORPID_STING, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (CHIMERA_SHOT > 0 && m_ai->CastSpell(CHIMERA_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (VOLLEY > 0 && m_ai->GetAttackerCount() >= 3 && m_ai->CastSpell(VOLLEY, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (BLACK_ARROW > 0 && !pTarget->HasAura(BLACK_ARROW, EFFECT_0) && m_ai->CastSpell(BLACK_ARROW, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (AIMED_SHOT > 0 && m_ai->CastSpell(AIMED_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (STEADY_SHOT > 0 && m_ai->CastSpell(STEADY_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (KILL_SHOT > 0 && pTarget->GetHealthPct() < 20 && m_ai->CastSpell(KILL_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else
+ // return RETURN_NO_ACTION_OK;
+ //}
+ //else
+ //{
+ // if (RAPTOR_STRIKE > 0 && m_ai->CastSpell(RAPTOR_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (EXPLOSIVE_TRAP > 0 && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && m_ai->CastSpell(EXPLOSIVE_TRAP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (WING_CLIP > 0 && !pTarget->HasAura(WING_CLIP, EFFECT_0) && m_ai->CastSpell(WING_CLIP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (IMMOLATION_TRAP > 0 && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && m_ai->CastSpell(IMMOLATION_TRAP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (MONGOOSE_BITE > 0 && m_ai->CastSpell(MONGOOSE_BITE, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (FROST_TRAP > 0 && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && m_ai->CastSpell(FROST_TRAP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (ARCANE_TRAP > 0 && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && m_ai->CastSpell(ARCANE_TRAP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (DETERRENCE > 0 && pVictim == m_bot && m_bot->GetHealthPct() < 50 && !m_bot->HasAura(DETERRENCE, EFFECT_0) && m_ai->CastSpell(DETERRENCE, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_0) && m_ai->CastSpell(WAR_STOMP, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_BLOODELF && !pTarget->HasAura(ARCANE_TORRENT, EFFECT_0) && m_ai->CastSpell(ARCANE_TORRENT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && m_ai->CastSpell(STONEFORM, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_NIGHTELF && pVictim == m_bot && m_bot->GetHealthPct() < 25 && !m_bot->HasAura(SHADOWMELD, EFFECT_0) && m_ai->CastSpell(SHADOWMELD, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_DRAENEI && m_bot->GetHealthPct() < 25 && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_0) && m_ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (pet && pet->isAlive() && MISDIRECTION > 0 && pVictim == m_bot && !m_bot->HasAura(MISDIRECTION, EFFECT_0) && m_ai->CastSpell(MISDIRECTION, *pet))
+ // return RETURN_CONTINUE;
+ // /*else if(FREEZING_TRAP > 0 && !pTarget->HasAura(FREEZING_TRAP, EFFECT_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && m_ai->CastSpell(FREEZING_TRAP,*pTarget) )
+ // out << " > Freezing Trap"; // this can trap your bots too
+ // else if(BEAR_TRAP > 0 && !pTarget->HasAura(BEAR_TRAP, EFFECT_0) && !pTarget->HasAura(ARCANE_TRAP, EFFECT_0) && !pTarget->HasAura(EXPLOSIVE_TRAP, EFFECT_0) && !pTarget->HasAura(IMMOLATION_TRAP, EFFECT_0) && !pTarget->HasAura(FROST_TRAP, EFFECT_0) && m_ai->CastSpell(BEAR_TRAP,*pTarget) )
+ // out << " > Bear Trap"; // this was just too annoying :)
+ // else if(DISENGAGE > 0 && pVictim && m_ai->CastSpell(DISENGAGE,*pTarget) )
+ // out << " > Disengage!"; // attempt to return to ranged combat*/
+ // else RETURN_NO_ACTION_OK;
+ //}
+
+ return RETURN_NO_ACTION_OK;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotHunterAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(RAPTOR_STRIKE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+void PlayerbotHunterAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //// buff group
+ //if (TRUESHOT_AURA > 0 && !m_bot->HasAura(TRUESHOT_AURA, EFFECT_0))
+ // m_ai->CastSpell(TRUESHOT_AURA, *m_bot);
+
+ //// buff myself
+ //if (ASPECT_OF_THE_HAWK > 0 && !m_bot->HasAura(ASPECT_OF_THE_HAWK, EFFECT_0))
+ // m_ai->CastSpell(ASPECT_OF_THE_HAWK, *m_bot);
+
+ //// hp/mana check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage())
+ // return;
+
+ //if (m_bot->getRace() == RACE_DRAENEI && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_0) && m_bot->GetHealthPct() < 70)
+ //{
+ // m_ai->TellMaster("I'm casting gift of the naaru.");
+ // m_ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot);
+ // return;
+ //}
+
+ //// check for pet
+ //if (PET_SUMMON > 0 && !m_petSummonFailed && m_bot->GetPetGUID())
+ //{
+ // // we can summon pet, and no critical summon errors before
+ // Pet *pet = m_bot->GetPet();
+ // if (!pet)
+ // {
+ // // summon pet
+ // if (PET_SUMMON > 0 && m_ai->CastSpell(PET_SUMMON, *m_bot))
+ // m_ai->TellMaster("summoning pet.");
+ // else
+ // {
+ // m_petSummonFailed = true;
+ // m_ai->TellMaster("summon pet failed!");
+ // }
+ // }
+ // else if (!(pet->isAlive()))
+ // {
+ // if (PET_REVIVE > 0 && m_ai->CastSpell(PET_REVIVE, *m_bot))
+ // m_ai->TellMaster("reviving pet.");
+ // }
+ // else if (pet->GetHealthPct() < 50)
+ // {
+ // if (PET_MEND > 0 && pet->isAlive() && !pet->HasAura(PET_MEND, EFFECT_0) && m_ai->CastSpell(PET_MEND, *m_bot))
+ // m_ai->TellMaster("healing pet.");
+ // }
+ // else if (pet->GetHappinessState() != HAPPY) // if pet is hungry
+ // {
+ // Unit *caster = (Unit *) m_bot;
+ // // list out items in main backpack
+ // for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++)
+ // {
+ // Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
+ // if (pItem)
+ // {
+ // const ItemTemplate* const pItemProto = pItem->GetTemplate();
+ // if (!pItemProto)
+ // continue;
+
+ // if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet
+ // {
+ // // DEBUG_LOG ("[PlayerbotHunterAI]: DoNonCombatActions - Food for pet: %s",pItemProto->Name1);
+ // caster->CastSpell(caster, 51284, true); // pet feed visual
+ // uint32 count = 1; // number of items used
+ // int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food
+ // m_bot->DestroyItemCount(pItem, count, true); // remove item from inventory
+ // m_bot->CastCustomSpell(m_bot, PET_FEED, &benefit, NULL, NULL, true); // feed pet
+ // m_ai->TellMaster("feeding pet.");
+ // //m_ai->SetIgnoreUpdateTime(10);
+ // return;
+ // }
+ // }
+ // }
+ // // list out items in other removable backpacks
+ // for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
+ // {
+ // const Bag* const pBag = (Bag *) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
+ // if (pBag)
+ // for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot)
+ // {
+ // Item* const pItem = m_bot->GetItemByPos(bag, slot);
+ // if (pItem)
+ // {
+ // const ItemTemplate* const pItemProto = pItem->GetTemplate();
+ // if (!pItemProto)
+ // continue;
+
+ // if (pet->HaveInDiet(pItemProto)) // is pItem in pets diet
+ // {
+ // // DEBUG_LOG ("[PlayerbotHunterAI]: DoNonCombatActions - Food for pet: %s",pItemProto->Name1);
+ // caster->CastSpell(caster, 51284, true); // pet feed visual
+ // uint32 count = 1; // number of items used
+ // int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food
+ // m_bot->DestroyItemCount(pItem, count, true); // remove item from inventory
+ // m_bot->CastCustomSpell(m_bot, PET_FEED, &benefit, NULL, NULL, true); // feed pet
+ // m_ai->TellMaster("feeding pet.");
+ // //m_ai->SetIgnoreUpdateTime(10);
+ // return;
+ // }
+ // }
+ // }
+ // }
+ // if (pet->HasAura(PET_MEND, EFFECT_0) && !pet->HasAura(PET_FEED, EFFECT_0))
+ // m_ai->TellMaster("..no pet food!");
+ // //m_ai->SetIgnoreUpdateTime(7);
+ // }
+ //}
+} // end DoNonCombatActions
diff --git a/src/server/game/AI/PlayerBots/bp_hun_ai.h b/src/server/game/AI/PlayerBots/bp_hun_ai.h
new file mode 100644
index 0000000..4881df9
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_hun_ai.h
@@ -0,0 +1,122 @@
+#ifndef _PLAYERHUNTERAI_H
+#define _PLAYERHUNTERAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ SPELL_HUNTER
+};
+
+enum HunterSpells
+{
+ ARCANE_SHOT_1 = 3044,
+ ASPECT_OF_THE_BEAST_1 = 13161,
+ ASPECT_OF_THE_CHEETAH_1 = 5118,
+ ASPECT_OF_THE_DRAGONHAWK_1 = 61846,
+ ASPECT_OF_THE_HAWK_1 = 13165,
+ ASPECT_OF_THE_MONKEY_1 = 13163,
+ ASPECT_OF_THE_PACK_1 = 13159,
+ ASPECT_OF_THE_VIPER_1 = 34074,
+ ASPECT_OF_THE_WILD_1 = 20043,
+ AUTO_SHOT_1 = 75,
+ BEAST_LORE_1 = 1462,
+ CALL_PET_1 = 883,
+ CALL_STABLED_PET_1 = 62757,
+ CONCUSSIVE_SHOT_1 = 5116,
+ DETERRENCE_1 = 19263,
+ DISENGAGE_1 = 781,
+ DISMISS_PET_1 = 2641,
+ DISTRACTING_SHOT_1 = 20736,
+ EAGLE_EYE_1 = 6197,
+ EXPLOSIVE_TRAP_1 = 13813,
+ EYES_OF_THE_BEAST_1 = 1002,
+ FEED_PET_1 = 6991,
+ FEIGN_DEATH_1 = 5384,
+ FLARE_1 = 1543,
+ FREEZING_ARROW_1 = 60192,
+ FREEZING_TRAP_1 = 1499,
+ FROST_TRAP_1 = 13809,
+ HUNTERS_MARK_1 = 1130,
+ IMMOLATION_TRAP_1 = 13795,
+ KILL_COMMAND_1 = 34026,
+ KILL_SHOT_1 = 53351,
+ MASTERS_CALL_1 = 53271,
+ MEND_PET_1 = 136,
+ MISDIRECTION_1 = 34477,
+ MONGOOSE_BITE_1 = 1495,
+ MULTISHOT_1 = 2643,
+ RAPID_FIRE_1 = 3045,
+ RAPTOR_STRIKE_1 = 2973,
+ REVIVE_PET_1 = 982,
+ SCARE_BEAST_1 = 1513,
+ SCORPID_STING_1 = 3043,
+ SERPENT_STING_1 = 1978,
+ SNAKE_TRAP_1 = 34600,
+ STEADY_SHOT_1 = 56641,
+ TAME_BEAST_1 = 1515,
+ TRACK_BEASTS_1 = 1494,
+ TRACK_DEMONS_1 = 19878,
+ TRACK_DRAGONKIN_1 = 19879,
+ TRACK_ELEMENTALS_1 = 19880,
+ TRACK_GIANTS_1 = 19882,
+ TRACK_HIDDEN_1 = 19885,
+ TRACK_HUMANOIDS_1 = 19883,
+ TRACK_UNDEAD_1 = 19884,
+ TRANQUILIZING_SHOT_1 = 19801,
+ VIPER_STING_1 = 3034,
+ VOLLEY_1 = 1510,
+ WING_CLIP_1 = 2974,
+ AIMED_SHOT_1 = 19434,
+ BESTIAL_WRATH_1 = 19574,
+ BLACK_ARROW_1 = 3674,
+ CHIMERA_SHOT_1 = 53209,
+ COUNTERATTACK_1 = 19306,
+ EXPLOSIVE_SHOT_1 = 53301,
+ INTIMIDATION_1 = 19577,
+ READINESS_1 = 23989,
+ SCATTER_SHOT_1 = 19503,
+ SILENCING_SHOT_1 = 34490,
+ TRUESHOT_AURA_1 = 19506,
+ WYVERN_STING_1 = 19386
+};
+
+//class Player;
+
+class PlayerbotHunterAI : PlayerbotClassAI
+{
+public:
+ PlayerbotHunterAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotHunterAI();
+ bool HasPet(Player* bot);
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // buff a specific player, usually a real PC who is not in group
+ //void BuffPlayer(Player *target);
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // Hunter
+ bool m_petSummonFailed;
+
+ uint32 PET_SUMMON, PET_DISMISS, PET_REVIVE, PET_MEND, PET_FEED, BAD_ATTITUDE, SONIC_BLAST, NETHER_SHOCK, DEMORALIZING_SCREECH, INTIMIDATION;
+ uint32 AUTO_SHOT, HUNTERS_MARK, ARCANE_SHOT, CONCUSSIVE_SHOT, DISTRACTING_SHOT, MULTI_SHOT, EXPLOSIVE_SHOT, SERPENT_STING, SCORPID_STING, VIPER_STING, WYVERN_STING, AIMED_SHOT, STEADY_SHOT, CHIMERA_SHOT, VOLLEY, BLACK_ARROW, KILL_SHOT;
+ uint32 RAPTOR_STRIKE, WING_CLIP, MONGOOSE_BITE, DISENGAGE, DETERRENCE;
+ uint32 BEAR_TRAP, FREEZING_TRAP, IMMOLATION_TRAP, FROST_TRAP, EXPLOSIVE_TRAP, ARCANE_TRAP, SNAKE_TRAP;
+ uint32 ASPECT_OF_THE_HAWK, ASPECT_OF_THE_MONKEY, RAPID_FIRE, TRUESHOT_AURA, MISDIRECTION;
+
+ // racial
+ uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_mag_ai.cpp b/src/server/game/AI/PlayerBots/bp_mag_ai.cpp
new file mode 100644
index 0000000..eab1752
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_mag_ai.cpp
@@ -0,0 +1,356 @@
+#include "bp_mag_ai.h"
+#include "bp_ai.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotMageAI::PlayerbotMageAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ ARCANE_MISSILES = PlayerbotAI::InitSpell(me, ARCANE_MISSILES_1);
+ ARCANE_EXPLOSION = PlayerbotAI::InitSpell(me, ARCANE_EXPLOSION_1);
+ COUNTERSPELL = PlayerbotAI::InitSpell(me, COUNTERSPELL_1);
+ SLOW = PlayerbotAI::InitSpell(me, SLOW_1);
+ ARCANE_BARRAGE = PlayerbotAI::InitSpell(me, ARCANE_BARRAGE_1);
+ ARCANE_BLAST = PlayerbotAI::InitSpell(me, ARCANE_BLAST_1);
+ ARCANE_POWER = PlayerbotAI::InitSpell(me, ARCANE_POWER_1);
+ DAMPEN_MAGIC = PlayerbotAI::InitSpell(me, DAMPEN_MAGIC_1);
+ AMPLIFY_MAGIC = PlayerbotAI::InitSpell(me, AMPLIFY_MAGIC_1);
+ MAGE_ARMOR = PlayerbotAI::InitSpell(me, MAGE_ARMOR_1);
+ MIRROR_IMAGE = PlayerbotAI::InitSpell(me, MIRROR_IMAGE_1);
+ ARCANE_INTELLECT = PlayerbotAI::InitSpell(me, ARCANE_INTELLECT_1);
+ ARCANE_BRILLIANCE = PlayerbotAI::InitSpell(me, ARCANE_BRILLIANCE_1);
+ DALARAN_INTELLECT = PlayerbotAI::InitSpell(me, DALARAN_INTELLECT_1);
+ DALARAN_BRILLIANCE = PlayerbotAI::InitSpell(me, DALARAN_BRILLIANCE_1);
+ MANA_SHIELD = PlayerbotAI::InitSpell(me, MANA_SHIELD_1);
+ CONJURE_WATER = PlayerbotAI::InitSpell(me, CONJURE_WATER_1);
+ CONJURE_FOOD = PlayerbotAI::InitSpell(me, CONJURE_FOOD_1);
+ FIREBALL = PlayerbotAI::InitSpell(me, FIREBALL_1);
+ FIRE_BLAST = PlayerbotAI::InitSpell(me, FIRE_BLAST_1);
+ FLAMESTRIKE = PlayerbotAI::InitSpell(me, FLAMESTRIKE_1);
+ SCORCH = PlayerbotAI::InitSpell(me, SCORCH_1);
+ PYROBLAST = PlayerbotAI::InitSpell(me, PYROBLAST_1);
+ BLAST_WAVE = PlayerbotAI::InitSpell(me, BLAST_WAVE_1);
+ COMBUSTION = PlayerbotAI::InitSpell(me, COMBUSTION_1);
+ DRAGONS_BREATH = PlayerbotAI::InitSpell(me, DRAGONS_BREATH_1);
+ LIVING_BOMB = PlayerbotAI::InitSpell(me, LIVING_BOMB_1);
+ FROSTFIRE_BOLT = PlayerbotAI::InitSpell(me, FROSTFIRE_BOLT_1);
+ FIRE_WARD = PlayerbotAI::InitSpell(me, FIRE_WARD_1);
+ MOLTEN_ARMOR = PlayerbotAI::InitSpell(me, MOLTEN_ARMOR_1);
+ ICY_VEINS = PlayerbotAI::InitSpell(me, ICY_VEINS_1);
+ DEEP_FREEZE = PlayerbotAI::InitSpell(me, DEEP_FREEZE_1);
+ FROSTBOLT = PlayerbotAI::InitSpell(me, FROSTBOLT_1);
+ FROST_NOVA = PlayerbotAI::InitSpell(me, FROST_NOVA_1);
+ BLIZZARD = PlayerbotAI::InitSpell(me, BLIZZARD_1);
+ CONE_OF_COLD = PlayerbotAI::InitSpell(me, CONE_OF_COLD_1);
+ ICE_BARRIER = PlayerbotAI::InitSpell(me, ICE_BARRIER_1);
+ SUMMON_WATER_ELEMENTAL = PlayerbotAI::InitSpell(me, SUMMON_WATER_ELEMENTAL_1);
+ FROST_WARD = PlayerbotAI::InitSpell(me, FROST_WARD_1);
+ ICE_LANCE = PlayerbotAI::InitSpell(me, ICE_LANCE_1);
+ FROST_ARMOR = PlayerbotAI::InitSpell(me, FROST_ARMOR_1);
+ ICE_ARMOR = PlayerbotAI::InitSpell(me, ICE_ARMOR_1);
+ ICE_BLOCK = PlayerbotAI::InitSpell(me, ICE_BLOCK_1);
+ COLD_SNAP = PlayerbotAI::InitSpell(me, COLD_SNAP_1);
+
+ // RANGED COMBAT
+ SHOOT = PlayerbotAI::InitSpell(me, SHOOT_2);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_MANA_CLASSES); // blood elf
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_MAGE); // draenei
+ ESCAPE_ARTIST = PlayerbotAI::InitSpell(me, ESCAPE_ARTIST_ALL); // gnome
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+}
+
+PlayerbotMageAI::~PlayerbotMageAI() {}
+
+CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotMageAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //Unit* pVictim = pTarget->getVictim();
+ //float dist = pTarget->GetCombatReach();
+ //uint32 spec = m_bot->GetSpec();
+
+ //if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && dist > ATTACK_DISTANCE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //// if can't shoot OR have no ranged (wand) equipped
+ //else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE && (SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true)))
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ ////Used to determine if this bot is highest on threat
+ //Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //if (newTarget) // TODO: && party has a tank
+ //{
+ // // Insert instant threat reducing spell (if a mage has one)
+
+ // // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on.
+ // if (newTarget->GetHealthPct() > 25)
+ // {
+ // // If elite, do nothing and pray tank gets aggro off you
+ // // TODO: Is there an IsElite function? If so, find it and insert.
+ // //if (newTarget->IsElite())
+ // // return;
+
+ // // Not an elite. You could insert FEAR here but in any PvE situation that's 90-95% likely
+ // // to worsen the situation for the group. ... So please don't.
+ // CastSpell(SHOOT, pTarget);
+ // return RETURN_CONTINUE;
+ // }
+ //}
+
+ //switch (spec)
+ //{
+ // case MAGE_SPEC_FROST:
+ // if (ICY_VEINS > 0 && !m_bot->HasAura(ICY_VEINS, EFFECT_0) && CastSpell(ICY_VEINS, m_bot))
+ // return RETURN_CONTINUE;
+ // if (ICE_BLOCK > 0 && pVictim == m_bot && !m_bot->HasAura(ICE_BLOCK, EFFECT_0) && CastSpell(ICE_BLOCK, m_bot))
+ // return RETURN_CONTINUE;
+ // if (ICE_BARRIER > 0 && pVictim == m_bot && !m_bot->HasAura(ICE_BARRIER, EFFECT_0) && m_bot->GetHealthPct() < 50 && CastSpell(ICE_BARRIER, m_bot))
+ // return RETURN_CONTINUE;
+ // if (DEEP_FREEZE > 0 && pTarget->HasAura(AURA_STATE_FROZEN, EFFECT_0) && !pTarget->HasAura(DEEP_FREEZE, EFFECT_0) && CastSpell(DEEP_FREEZE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (BLIZZARD > 0 && m_ai->GetAttackerCount() >= 5 && CastSpell(BLIZZARD, pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(8);
+ // return RETURN_CONTINUE;
+ // }
+ // if (CONE_OF_COLD > 0 && dist <= ATTACK_DISTANCE && !pTarget->HasAura(CONE_OF_COLD, EFFECT_0) && CastSpell(CONE_OF_COLD, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FROSTBOLT > 0 && !pTarget->HasAura(FROSTBOLT, EFFECT_0) && CastSpell(FROSTBOLT, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FROST_WARD > 0 && !m_bot->HasAura(FROST_WARD, EFFECT_0) && CastSpell(FROST_WARD, m_bot))
+ // return RETURN_CONTINUE;
+ // if (FROST_NOVA > 0 && dist <= ATTACK_DISTANCE && !pTarget->HasAura(FROST_NOVA, EFFECT_0) && CastSpell(FROST_NOVA, pTarget))
+ // return RETURN_CONTINUE;
+ // if (ICE_LANCE > 0 && CastSpell(ICE_LANCE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SUMMON_WATER_ELEMENTAL > 0 && CastSpell(SUMMON_WATER_ELEMENTAL))
+ // return RETURN_CONTINUE;
+ // if (COLD_SNAP > 0 && CastSpell(COLD_SNAP, m_bot))
+ // return RETURN_CONTINUE;
+
+ // if (FROSTBOLT > 0)
+ // return CastSpell(FROSTBOLT, pTarget);
+ // break;
+
+ // case MAGE_SPEC_FIRE:
+ // if (FIRE_WARD > 0 && !m_bot->HasAura(FIRE_WARD, EFFECT_0) && CastSpell(FIRE_WARD, m_bot))
+ // return RETURN_CONTINUE;
+ // if (COMBUSTION > 0 && !m_bot->HasAura(COMBUSTION, EFFECT_0) && CastSpell(COMBUSTION, m_bot))
+ // return RETURN_CONTINUE;
+ // if (FIREBALL > 0 && CastSpell(FIREBALL, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FIRE_BLAST > 0 && CastSpell(FIRE_BLAST, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FLAMESTRIKE > 0 && CastSpell(FLAMESTRIKE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SCORCH > 0 && CastSpell(SCORCH, pTarget))
+ // return RETURN_CONTINUE;
+ // if (PYROBLAST > 0 && !pTarget->HasAura(PYROBLAST, EFFECT_0) && CastSpell(PYROBLAST, pTarget))
+ // return RETURN_CONTINUE;
+ // if (BLAST_WAVE > 0 && m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && CastSpell(BLAST_WAVE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (DRAGONS_BREATH > 0 && dist <= ATTACK_DISTANCE && CastSpell(DRAGONS_BREATH, pTarget))
+ // return RETURN_CONTINUE;
+ // if (LIVING_BOMB > 0 && !pTarget->HasAura(LIVING_BOMB, EFFECT_0) && CastSpell(LIVING_BOMB, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FROSTFIRE_BOLT > 0 && !pTarget->HasAura(FROSTFIRE_BOLT, EFFECT_0) && CastSpell(FROSTFIRE_BOLT, pTarget))
+ // return RETURN_CONTINUE;
+
+ // if (FIREBALL > 0)
+ // return CastSpell(FIREBALL, pTarget);
+ // break;
+
+ // case MAGE_SPEC_ARCANE:
+ // if (ARCANE_POWER > 0 && CastSpell(ARCANE_POWER, pTarget))
+ // return RETURN_CONTINUE;
+ // if (ARCANE_MISSILES > 0 && CastSpell(ARCANE_MISSILES, pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(3);
+ // return RETURN_CONTINUE;
+ // }
+ // if (ARCANE_EXPLOSION > 0 && m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && CastSpell(ARCANE_EXPLOSION, pTarget))
+ // return RETURN_CONTINUE;
+ // if (COUNTERSPELL > 0 && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(COUNTERSPELL, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SLOW > 0 && !pTarget->HasAura(SLOW, EFFECT_0) && CastSpell(SLOW, pTarget))
+ // return RETURN_CONTINUE;
+ // if (ARCANE_BARRAGE > 0 && CastSpell(ARCANE_BARRAGE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (ARCANE_BLAST > 0 && CastSpell(ARCANE_BLAST, pTarget))
+ // return RETURN_CONTINUE;
+ // if (MIRROR_IMAGE > 0 && CastSpell(MIRROR_IMAGE))
+ // return RETURN_CONTINUE;
+ // if (MANA_SHIELD > 0 && m_bot->GetHealthPct() < 70 && pVictim == m_bot && !m_bot->HasAura(MANA_SHIELD, EFFECT_0) && CastSpell(MANA_SHIELD, m_bot))
+ // return RETURN_CONTINUE;
+
+ // if (FIREBALL > 0)
+ // return CastSpell(FIREBALL, pTarget);
+ // break;
+ //}
+
+ //// No spec due to low level OR no spell found yet
+ //if (FROSTBOLT > 0 && !pTarget->HasAura(FROSTBOLT, EFFECT_0))
+ // return CastSpell(FROSTBOLT, pTarget);
+ //if (FIREBALL > 0) // Very low levels
+ // return CastSpell(FIREBALL, pTarget);
+
+ return RETURN_NO_ACTION_ERROR; // What? Not even Fireball is available?
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotMageAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(FIREBALL))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+void PlayerbotMageAI::DoNonCombatActions()
+{
+ //Player* master = GetMaster();
+
+ //if (!m_bot || !master)
+ // return;
+
+ //// Buff armor
+ //if (MOLTEN_ARMOR)
+ //{
+ // if (m_ai->SelfBuff(MOLTEN_ARMOR))
+ // return;
+ //}
+ //else if (MAGE_ARMOR)
+ //{
+ // if (m_ai->SelfBuff(MAGE_ARMOR))
+ // return;
+ //}
+ //else if (ICE_ARMOR)
+ //{
+ // if (m_ai->SelfBuff(ICE_ARMOR))
+ // return;
+ //}
+ //else if (FROST_ARMOR)
+ // if (m_ai->SelfBuff(FROST_ARMOR))
+ // return;
+
+ //// buff group
+ //if (m_bot->GetGroup() && ARCANE_BRILLIANCE && m_ai->HasSpellReagents(ARCANE_BRILLIANCE) && m_ai->Buff(ARCANE_BRILLIANCE, m_bot))
+ // return;
+ //if (Buff(&PlayerbotMageAI::BuffHelper, ARCANE_INTELLECT, (JOB_ALL | JOB_MANAONLY)))
+ // return;
+
+ //// conjure food & water + hp/mana check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //// TODO: The beauty of a mage is not only its ability to supply itself with water, but to share its water
+ //// So, conjure at *least* 1.25 stacks, ready to trade a stack and still have some left for self
+ //if (m_ai->FindDrink() == NULL && CONJURE_WATER && m_ai->CastSpell(CONJURE_WATER, *m_bot))
+ //{
+ // m_ai->TellMaster("I'm conjuring some water.");
+ // //m_ai->SetIgnoreUpdateTime(3);
+ // return;
+ //}
+ //if (m_ai->FindFood() == NULL && CONJURE_FOOD && m_ai->CastSpell(CONJURE_FOOD, *m_bot))
+ //{
+ // m_ai->TellMaster("I'm conjuring some food.");
+ // //m_ai->SetIgnoreUpdateTime(3);
+ // return;
+ //}
+
+ //if (EatDrinkBandage())
+ // return;
+} // end DoNonCombatActions
+
+// TODO: this and priest's BuffHelper are identical and thus could probably go in PlayerbotClassAI.cpp somewhere
+bool PlayerbotMageAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target)
+{
+ //if (!ai) return false;
+ //if (spellId == 0) return false;
+ //if (!target) return false;
+
+ //Pet* pet = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer()->GetPet() : NULL;
+ //if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet))
+ // return true;
+
+ //if (ai->Buff(spellId, target))
+ // return true;
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_mag_ai.h b/src/server/game/AI/PlayerBots/bp_mag_ai.h
new file mode 100644
index 0000000..7a2a17d
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_mag_ai.h
@@ -0,0 +1,165 @@
+#ifndef _PlayerbotMageAI_H
+#define _PlayerbotMageAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ SPELL_FROST,
+ SPELL_FIRE,
+ SPELL_ARCANE
+};
+
+enum MageSpells
+{
+ AMPLIFY_MAGIC_1 = 1008,
+ ARCANE_BARRAGE_1 = 44425,
+ ARCANE_BLAST_1 = 30451,
+ ARCANE_BRILLIANCE_1 = 23028,
+ ARCANE_EXPLOSION_1 = 1449,
+ ARCANE_INTELLECT_1 = 1459,
+ ARCANE_MISSILES_1 = 5143,
+ ARCANE_POWER_1 = 12042,
+ BLAST_WAVE_1 = 11113,
+ BLINK_1 = 1953,
+ BLIZZARD_1 = 10,
+ COLD_SNAP_1 = 11958,
+ COMBUSTION_1 = 11129,
+ CONE_OF_COLD_1 = 120,
+ CONJURE_FOOD_1 = 587,
+ CONJURE_MANA_GEM_1 = 759,
+ CONJURE_REFRESHMENT_1 = 42955,
+ CONJURE_WATER_1 = 5504,
+ COUNTERSPELL_1 = 2139,
+ DALARAN_BRILLIANCE_1 = 61316,
+ DALARAN_INTELLECT_1 = 61024,
+ DAMPEN_MAGIC_1 = 604,
+ DEEP_FREEZE_1 = 44572,
+ DRAGONS_BREATH_1 = 31661,
+ EVOCATION_1 = 12051,
+ FIRE_BLAST_1 = 2136,
+ FIRE_WARD_1 = 543,
+ FIREBALL_1 = 133,
+ FLAMESTRIKE_1 = 2120,
+ FOCUS_MAGIC_1 = 54646,
+ FROST_ARMOR_1 = 168,
+ FROST_NOVA_1 = 122,
+ FROST_WARD_1 = 6143,
+ FROSTBOLT_1 = 116,
+ FROSTFIRE_BOLT_1 = 44614,
+ ICE_ARMOR_1 = 7302,
+ ICE_BARRIER_1 = 11426,
+ ICE_BLOCK_1 = 45438,
+ ICE_LANCE_1 = 30455,
+ ICY_VEINS_1 = 12472,
+ INVISIBILITY_1 = 66,
+ LIVING_BOMB_1 = 44457,
+ MAGE_ARMOR_1 = 6117,
+ MANA_SHIELD_1 = 1463,
+ MIRROR_IMAGE_1 = 55342,
+ MOLTEN_ARMOR_1 = 30482,
+ PRESENCE_OF_MIND_1 = 12043,
+ PYROBLAST_1 = 11366,
+ REMOVE_CURSE_MAGE_1 = 475,
+ RITUAL_OF_REFRESHMENT_1 = 43987,
+ SCORCH_1 = 2948,
+ SHOOT_2 = 5019,
+ SLOW_1 = 31589,
+ SLOW_FALL_1 = 130,
+ SPELLSTEAL_1 = 30449,
+ SUMMON_WATER_ELEMENTAL_1 = 31687
+};
+//class Player;
+
+class PlayerbotMageAI : PlayerbotClassAI
+{
+public:
+ PlayerbotMageAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotMageAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = NULL) { return CastSpellWand(nextAction, pTarget, SHOOT); }
+
+ static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target);
+
+ // ARCANE
+ uint32 ARCANE_MISSILES,
+ ARCANE_EXPLOSION,
+ COUNTERSPELL,
+ SLOW,
+ ARCANE_BARRAGE,
+ ARCANE_BLAST,
+ MIRROR_IMAGE,
+ ARCANE_POWER;
+ // ranged
+ uint32 SHOOT;
+
+ // FIRE
+ uint32 FIREBALL,
+ FIRE_BLAST,
+ FLAMESTRIKE,
+ SCORCH,
+ PYROBLAST,
+ BLAST_WAVE,
+ COMBUSTION,
+ DRAGONS_BREATH,
+ LIVING_BOMB,
+ FROSTFIRE_BOLT,
+ FIRE_WARD;
+
+ // FROST
+ uint32 DEEP_FREEZE,
+ FROSTBOLT,
+ FROST_NOVA,
+ BLIZZARD,
+ ICY_VEINS,
+ CONE_OF_COLD,
+ ICE_BARRIER,
+ SUMMON_WATER_ELEMENTAL,
+ ICE_LANCE,
+ FROST_WARD,
+ ICE_BLOCK,
+ COLD_SNAP;
+
+ // buffs
+ uint32 FROST_ARMOR,
+ ICE_ARMOR,
+ MAGE_ARMOR,
+ MOLTEN_ARMOR,
+ ARCANE_INTELLECT,
+ ARCANE_BRILLIANCE,
+ DALARAN_INTELLECT,
+ DALARAN_BRILLIANCE,
+ MANA_SHIELD,
+ DAMPEN_MAGIC,
+ AMPLIFY_MAGIC;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ uint32 CONJURE_WATER,
+ CONJURE_FOOD;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_mgr.cpp b/src/server/game/AI/PlayerBots/bp_mgr.cpp
new file mode 100644
index 0000000..b0e4d7c
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_mgr.cpp
@@ -0,0 +1,1136 @@
+//#include "Config/Config.h"
+#include "Common.h"
+#include "Config.h"
+#include "Player.h"
+#include "bp_ai.h"
+#include "bp_cai.h"
+#include "bp_mgr.h"
+//#include "WorldPacket.h"
+#include "WorldSession.h"
+//#include "Chat.h"
+//#include "ObjectMgr.h"
+#include "GossipDef.h"
+//#include "Language.h"
+//#include "WaypointMovementGenerator.h"
+//#include "Guild.h"
+#include "Group.h"
+#include "SpellInfo.h"
+#include "Opcodes.h"
+
+PlayerbotMgr::PlayerbotMgr(Player* const master) : m_master(master)
+{
+ // load config variables
+ m_confMaxNumBots = ConfigMgr::GetIntDefault("Bot.MaxPlayerbots", 9);
+ m_confDebugWhisper = ConfigMgr::GetBoolDefault("Bot.DebugWhisper", false);
+ m_confFollowDistance[0] = ConfigMgr::GetFloatDefault("Bot.FollowDistanceMin", 0.5f);
+ m_confFollowDistance[1] = ConfigMgr::GetFloatDefault("Bot.FollowDistanceMax", 1.0f);
+ m_confCollectCombat = ConfigMgr::GetBoolDefault("Bot.Collect.Combat", true);
+ m_confCollectQuest = ConfigMgr::GetBoolDefault("Bot.Collect.Quest", true);
+ m_confCollectProfession = ConfigMgr::GetBoolDefault("Bot.Collect.Profession", true);
+ m_confCollectLoot = ConfigMgr::GetBoolDefault("Bot.Collect.Loot", true);
+ m_confCollectSkin = ConfigMgr::GetBoolDefault("Bot.Collect.Skin", true);
+ m_confCollectObjects = ConfigMgr::GetBoolDefault("Bot.Collect.Objects", true);
+ m_confCollectDistanceMax = ConfigMgr::GetIntDefault("Bot.Collect.DistanceMax", 50);
+ gConfigSellLevelDiff = ConfigMgr::GetIntDefault("Bot.SellAll.LevelDiff", 10);
+ if (m_confCollectDistanceMax > 100)
+ {
+ //sLog->outError("Playerbot: Bot.Collect.DistanceMax higher than allowed. Using 100");
+ m_confCollectDistanceMax = 100;
+ }
+ m_confCollectDistance = ConfigMgr::GetIntDefault("Bot.Collect.Distance", 25);
+ if (m_confCollectDistance > m_confCollectDistanceMax)
+ {
+ //sLog.outError("Playerbot: PlayerbotAI.Collect.Distance higher than PlayerbotAI.Collect.DistanceMax. Using DistanceMax value");
+ m_confCollectDistance = m_confCollectDistanceMax;
+ }
+}
+
+PlayerbotMgr::~PlayerbotMgr()
+{
+ if (!m_playerBots.empty())
+ LogoutAllBots();
+}
+
+//void PlayerbotMgr::Update(uint32 const /*p_time*/) {}
+
+void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet)
+{
+ switch (packet.GetOpcode())
+ {
+ case CMSG_TEXT_EMOTE:
+ {
+ WorldPacket p(packet);
+ p.rpos(0); // reset reader
+ uint32 emoteNum;
+ p >> emoteNum;
+
+ switch (emoteNum)
+ {
+ case TEXT_EMOTE_BONK:
+ {
+ uint64 sel = m_master->GetSelection();
+ if (!sel || !IS_PLAYER_GUID(sel))
+ return;
+ Player* bot = GetPlayerBot(sel);
+ if (!bot)
+ return;
+ PlayerbotAI* botAI = bot->GetPlayerbotAI();
+ if (!botAI)
+ return;
+
+ botAI->SendDebugInfo();
+ return;
+ }
+ }
+ }
+ }
+}
+//void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet)
+//{
+// switch (packet.GetOpcode())
+// {
+// //case CMSG_OFFER_PETITION:
+// //{
+// // WorldPacket p(packet);
+// // p.rpos(0); // reset reader
+// // uint64 petitionGuid;
+// // uint64 playerGuid;
+// // uint32 junk;
+//
+// // p >> junk; // this is not petition type!
+// // p >> petitionGuid; // petition guid
+// // p >> playerGuid; // player guid
+//
+// // Player* player = ObjectAccessor::FindPlayer(playerGuid);
+// // if (!player)
+// // return;
+//
+// // uint32 petitionLowGuid = GUID_LOPART(petitionGuid);
+//
+// // QueryResult result = CharacterDatabase.PQuery("SELECT * FROM petition_sign WHERE playerguid = '%u' AND petitionguid = '%u'", player->GetGUIDLow(), petitionLowGuid);
+//
+// // if (result)
+// // {
+// // ChatHandler(m_master).PSendSysMessage("%s has already signed the petition", player->GetName().c_str());
+// // delete result;
+// // return;
+// // }
+//
+// // CharacterDatabase.PExecute("INSERT INTO petition_sign (ownerguid,petitionguid, playerguid, player_account) VALUES ('%u', '%u', '%u','%u')",
+// // m_master->GetGUIDLow(), petitionLowGuid, player->GetGUIDLow(), m_master->GetSession()->GetAccountId());
+//
+// // p.Initialize(SMSG_PETITION_SIGN_RESULTS, (8+8+4));
+// // p << uint64(petitionGuid);
+// // p << uint64(playerGuid);
+// // p << uint32(PETITION_SIGN_OK);
+//
+// // // close at signer side
+// // m_master->GetSession()->SendPacket(&p);
+//
+// // return;
+// //}
+//
+// //case CMSG_ACTIVATETAXI:
+// // {
+// // WorldPacket p(packet);
+// // p.rpos(0); // reset reader
+//
+// // uint64 guid;
+// // std::vector<uint32> nodes;
+// // nodes.resize(2);
+// // //uint8 delay = 9;
+// // p >> guid >> nodes[0] >> nodes[1];
+//
+// // for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// // {
+//
+// // //delay = delay + 3;
+// // Player* bot = it->second;
+// // if (!bot)
+// // continue;
+//
+// // Group* group = bot->GetGroup();
+// // if (!group)
+// // continue;
+//
+// // Unit* target = sObjectAccessor->GetUnit(*bot, guid);
+//
+// // //bot->GetPlayerbotAI()->SetIgnoreUpdateTime(delay);
+//
+// // bot->GetMotionMaster()->Clear(true);
+// // bot->GetMotionMaster()->MoveFollow(target, INTERACTION_DISTANCE, bot->GetOrientation());
+// // bot->GetPlayerbotAI()->GetTaxi(guid, nodes);
+// // }
+// // return;
+// // }
+//
+// //case CMSG_ACTIVATETAXIEXPRESS:
+// // {
+// // WorldPacket p(packet);
+// // p.rpos(0); // reset reader
+//
+// // uint64 guid;
+// // uint32 node_count;
+// // //uint8 delay = 9;
+// // p >> guid;
+// // p.read_skip<uint32>();
+// // p >> node_count;
+//
+// // std::vector<uint32> nodes;
+// // for (uint32 i = 0; i < node_count; ++i)
+// // {
+// // uint32 node;
+// // p >> node;
+// // nodes.push_back(node);
+// // }
+//
+// // if (nodes.empty())
+// // return;
+//
+//
+// // for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// // {
+//
+// // //delay = delay + 3;
+// // Player* bot = it->second;
+// // if (!bot)
+// // return;
+// // Group* group = bot->GetGroup();
+// // if (!group)
+// // continue;
+// // Unit* target = sObjectAccessor->GetUnit(*bot, guid);
+//
+// // //bot->GetPlayerbotAI()->SetIgnoreUpdateTime(delay);
+//
+// // bot->GetMotionMaster()->Clear(true);
+// // bot->GetMotionMaster()->MoveFollow(target, INTERACTION_DISTANCE, bot->GetOrientation());
+// // bot->GetPlayerbotAI()->GetTaxi(guid, nodes);
+// // }
+// // return;
+// // }
+//
+// //case CMSG_MOVE_SPLINE_DONE:
+// // {
+// // // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE");
+//
+// // WorldPacket p(packet);
+// // p.rpos(0); // reset reader
+//
+// // ObjectGuid guid; // used only for proper packet read
+// // MovementInfo movementInfo; // used only for proper packet read
+//
+// // p >> guid.ReadAsPacked();
+// // p >> movementInfo;
+// // p >> Unused<uint32>(); // unk
+//
+// // for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// // {
+//
+// // Player* const bot = it->second;
+// // if (!bot)
+// // return;
+//
+// // // in taxi flight packet received in 2 case:
+// // // 1) end taxi path in far (multi-node) flight
+// // // 2) switch from one map to other in case multi-map taxi path
+// // // we need process only (1)
+// // uint32 curDest = bot->m_taxi.GetTaxiDestination();
+// // if (!curDest)
+// // return;
+//
+// // TaxiNodesEntry const* curDestNode = sTaxiNodesStore.LookupEntry(curDest);
+//
+// // // far teleport case
+// // if (curDestNode && curDestNode->map_id != bot->GetMapId())
+// // {
+// // if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE)
+// // {
+// // // short preparations to continue flight
+// // FlightPathMovementGenerator* flight = (FlightPathMovementGenerator *) (bot->GetMotionMaster()->top());
+//
+// // flight->Interrupt(*bot); // will reset at map landing
+//
+// // flight->SetCurrentNodeAfterTeleport();
+// // TaxiPathNodeEntry const& node = flight->GetPath()[flight->GetCurrentNode()];
+// // flight->SkipCurrentNode();
+//
+// // bot->TeleportTo(curDestNode->map_id, node.x, node.y, node.z, bot->GetOrientation());
+// // }
+// // return;
+// // }
+//
+// // uint32 destinationnode = bot->m_taxi.NextTaxiDestination();
+// // if (destinationnode > 0) // if more destinations to go
+// // {
+// // // current source node for next destination
+// // uint32 sourcenode = bot->m_taxi.GetTaxiSource();
+//
+// // // Add to taximask middle hubs in taxicheat mode (to prevent having player with disabled taxicheat and not having back flight path)
+// // if (bot->isTaxiCheater())
+// // if (bot->m_taxi.SetTaximaskNode(sourcenode))
+// // {
+// // WorldPacket data(SMSG_NEW_TAXI_PATH, 0);
+// // bot->GetSession()->SendPacket(&data);
+// // }
+//
+// // // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_MOVE_SPLINE_DONE Taxi has to go from %u to %u", sourcenode, destinationnode);
+//
+// // uint32 mountDisplayId = sObjectMgr->GetTaxiMountDisplayId(sourcenode, bot->GetTeam());
+//
+// // uint32 path, cost;
+// // sObjectMgr->GetTaxiPath(sourcenode, destinationnode, path, cost);
+//
+// // if (path && mountDisplayId)
+// // bot->GetSession()->SendDoFlight(mountDisplayId, path, 1); // skip start fly node
+// // else
+// // bot->m_taxi.ClearTaxiDestinations(); // clear problematic path and next
+// // }
+// // else
+// // /* std::ostringstream out;
+// // out << "Destination reached" << bot->GetName();
+// // ChatHandler ch(m_master);
+// // ch.SendSysMessage(out.str().c_str()); */
+// // bot->m_taxi.ClearTaxiDestinations(); // Destination, clear source node
+// // }
+// // return;
+// // }
+//
+// // if master is logging out, log out all bots
+// case CMSG_LOGOUT_REQUEST:
+// {
+// LogoutAllBots();
+// return;
+// }
+//
+// // If master inspects one of his bots, give the master useful info in chat window
+// // such as inventory that can be equipped
+// //case CMSG_INSPECT:
+// //{
+// // WorldPacket p(packet);
+// // p.rpos(0); // reset reader
+// // uint64 guid;
+// // p >> guid;
+// // Player* bot = GetPlayerBot(guid);
+// // if (bot)
+// // bot->GetPlayerbotAI()->SendNotEquipList(*bot);
+// // return;
+// //}
+//
+// // handle emotes from the master
+// //case CMSG_EMOTE:
+// case CMSG_TEXT_EMOTE:
+// {
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint32 emoteNum;
+// p >> emoteNum;
+//
+// /* std::ostringstream out;
+// out << "emote is: " << emoteNum;
+// ChatHandler ch(m_master);
+// ch.SendSysMessage(out.str().c_str()); */
+//
+// switch (emoteNum)
+// {
+// case TEXT_EMOTE_BOW:
+// {
+// // Buff anyone who bows before me. Useful for players not in bot's group
+// // How do I get correct target???
+// //Player* const pPlayer = GetPlayerBot(m_master->GetSelection());
+// //if (pPlayer->GetPlayerbotAI()->GetClassAI())
+// // pPlayer->GetPlayerbotAI()->GetClassAI()->BuffPlayer(pPlayer);
+// return;
+// }
+// /*
+// case TEXT_EMOTE_BONK:
+// {
+// Player* const pPlayer = GetPlayerBot(m_master->GetSelection());
+// if (!pPlayer || !pPlayer->GetPlayerbotAI())
+// return;
+// PlayerbotAI* const pBot = pPlayer->GetPlayerbotAI();
+//
+// ChatHandler ch(m_master);
+// {
+// std::ostringstream out;
+// out << "CurrentTime: " << CurrentTime()
+// << " m_ignoreAIUpdatesUntilTime: " << pBot->m_ignoreAIUpdatesUntilTime;
+// ch.SendSysMessage(out.str().c_str());
+// }
+// {
+// std::ostringstream out;
+// out << "m_TimeDoneEating: " << pBot->m_TimeDoneEating
+// << " m_TimeDoneDrinking: " << pBot->m_TimeDoneDrinking;
+// ch.SendSysMessage(out.str().c_str());
+// }
+// {
+// std::ostringstream out;
+// out << "m_CurrentlyCastingSpellId: " << pBot->m_CurrentlyCastingSpellId;
+// ch.SendSysMessage(out.str().c_str());
+// }
+// {
+// std::ostringstream out;
+// out << "IsBeingTeleported() " << pBot->GetPlayer()->IsBeingTeleported();
+// ch.SendSysMessage(out.str().c_str());
+// }
+// {
+// std::ostringstream out;
+// bool tradeActive = (pBot->GetPlayer()->GetTrader()) ? true : false;
+// out << "tradeActive: " << tradeActive;
+// ch.SendSysMessage(out.str().c_str());
+// }
+// {
+// std::ostringstream out;
+// out << "IsCharmed() " << pBot->getPlayer()->isCharmed();
+// ch.SendSysMessage(out.str().c_str());
+// }
+// return;
+// }
+// */
+//
+// case TEXT_EMOTE_EAT:
+// case TEXT_EMOTE_DRINK:
+// {
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// //bot->GetPlayerbotAI()->Feast();
+// }
+// return;
+// }
+//
+// // emote to attack selected target
+// //case TEXT_EMOTE_POINT:
+// //{
+// // uint64 attackOnGuid = m_master->GetSelection();
+// // if (!attackOnGuid)
+// // return;
+//
+// // Unit* thingToAttack = sObjectAccessor->GetUnit(*m_master, attackOnGuid);
+// // if (!thingToAttack) return;
+//
+// // Player* bot = 0;
+// // for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr)
+// // {
+// // bot = itr->second;
+// // if (!bot->IsFriendlyTo(thingToAttack))
+// // {
+// // if (!bot->IsWithinLOSInMap(thingToAttack))
+// // bot->GetPlayerbotAI()->DoTeleport(*m_master);
+// // if (bot->IsWithinLOSInMap(thingToAttack))
+// // bot->GetPlayerbotAI()->Attack(thingToAttack);
+// // }
+// // }
+// // return;
+// //}
+//
+// // emote to stay
+// case TEXT_EMOTE_STAND:
+// {
+// uint64 sel = m_master->GetSelection();
+// if (!sel)
+// {
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// it->second->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY);
+// }
+// else if (Player* bot = GetPlayerBot(sel))
+// bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_STAY);
+// return;
+// }
+//
+// // 324 is the followme emote (not defined in enum)
+// // if master has bot selected then only bot follows, else all bots follow
+// case TEXT_EMOTE_FOLLOW:
+// case TEXT_EMOTE_WAVE:
+// {
+// uint64 sel = m_master->GetSelection();
+// if (!sel)
+// {
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// it->second->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW);
+// }
+// else if (Player* bot = GetPlayerBot(sel))
+// bot->GetPlayerbotAI()->SetMovementOrder(PlayerbotAI::MOVEMENT_FOLLOW);
+// return;
+// }
+// }
+// return;
+// } /* EMOTE ends here */
+//
+// case CMSG_GAMEOBJ_USE: // not sure if we still need this one
+// case CMSG_GAMEOBJ_REPORT_USE:
+// {
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint64 objGUID;
+// p >> objGUID;
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// GameObject* obj = m_master->GetMap()->GetGameObject(objGUID);
+// if (!obj)
+// return;
+//
+// // add other go types here, i.e.:
+// // GAMEOBJECT_TYPE_CHEST - loot quest items of chest
+// if (obj->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER)
+// {
+// bot->GetPlayerbotAI()->TurnInQuests(obj);
+//
+// // auto accept every available quest this NPC has
+// bot->PrepareQuestMenu(objGUID);
+// QuestMenu& questMenu = bot->PlayerTalkClass->GetQuestMenu();
+// for (uint32 iI = 0; iI < questMenu.GetMenuItemCount(); ++iI)
+// {
+// QuestMenuItem const& qItem = questMenu.GetItem(iI);
+// uint32 questID = qItem.QuestId;
+// if (!bot->GetPlayerbotAI()->AddQuest(questID, obj))
+// bot->GetPlayerbotAI()->TellMaster("Couldn't take quest");
+// }
+// }
+// }
+// }
+// break;
+//
+// case CMSG_QUESTGIVER_HELLO:
+// {
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint64 npcGUID;
+// p >> npcGUID;
+// WorldObject* pNpc = sObjectAccessor->GetWorldObject(*m_master, npcGUID);
+// if (!pNpc)
+// return;
+//
+// // for all master's bots
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// bot->GetPlayerbotAI()->TurnInQuests(pNpc);
+// }
+//
+// return;
+// }
+//
+// // if master accepts a quest, bots should also try to accept quest
+// case CMSG_QUESTGIVER_ACCEPT_QUEST:
+// {
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint64 guid;
+// uint32 quest;
+// uint32 unk1;
+// p >> guid >> quest >> unk1;
+//
+// Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest);
+// if (qInfo)
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// if (bot->GetQuestStatus(quest) == QUEST_STATUS_COMPLETE)
+// bot->GetPlayerbotAI()->TellMaster("I already completed that quest.");
+// else if (!bot->CanTakeQuest(qInfo, false))
+// {
+// if (!bot->SatisfyQuestStatus(qInfo, false))
+// bot->GetPlayerbotAI()->TellMaster("I already have that quest.");
+// else
+// bot->GetPlayerbotAI()->TellMaster("I can't take that quest.");
+// }
+// else if (!bot->SatisfyQuestLog(false))
+// bot->GetPlayerbotAI()->TellMaster("My quest log is full.");
+// else if (!bot->CanAddQuest(qInfo, false))
+// bot->GetPlayerbotAI()->TellMaster("I can't take that quest because it requires that I take items, but my bags are full!");
+// else
+// {
+// p.rpos(0); // reset reader
+// bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p);
+// bot->GetPlayerbotAI()->TellMaster("Got the quest.");
+//
+// // build needed items if quest contains any
+// for (uint8 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
+// {
+// if (qInfo->RequiredItemCount[i] > 0)
+// {
+// bot->GetPlayerbotAI()->SetQuestNeedItems();
+// break;
+// }
+//
+// // build needed creatures if quest contains any
+// for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
+// {
+// if (qInfo->RequiredNpcOrGoCount[i] > 0)
+// {
+// bot->GetPlayerbotAI()->SetQuestNeedCreatures();
+// break;
+// }
+// }
+// }
+// }
+// }
+// return;
+// }
+//
+// case CMSG_AREATRIGGER:
+// {
+// WorldPacket p(packet);
+//
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// if (!bot)
+// continue;
+//
+// if (bot->IsWithinDistInMap(m_master, 50))
+// {
+// p.rpos(0); // reset reader
+// bot->GetSession()->HandleAreaTriggerOpcode(p);
+// }
+// }
+// return;
+// }
+//
+// case CMSG_QUESTGIVER_COMPLETE_QUEST:
+// {
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint32 quest;
+// uint64 npcGUID;
+// p >> npcGUID >> quest;
+//
+// // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_QUESTGIVER_COMPLETE_QUEST npc = %s, quest = %u", npcGUID.GetString().c_str(), quest);
+//
+// WorldObject* pNpc = sObjectAccessor->GetWorldObject(*m_master, npcGUID);
+// if (!pNpc)
+// return;
+//
+// // for all master's bots
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// bot->GetPlayerbotAI()->TurnInQuests(pNpc);
+// }
+// return;
+// }
+//
+// case CMSG_LOOT_ROLL:
+// {
+// WorldPacket p(packet); //WorldPacket packet for CMSG_LOOT_ROLL, (8+4+1)
+// uint64 Guid;
+// uint32 itemSlot;
+// uint8 rollType;
+// Loot* loot = NULL;
+//
+// p.rpos(0); //reset packet pointer
+// p >> Guid; //guid of the lootable target
+// p >> itemSlot; //loot index
+// p >> rollType; //need,greed or pass on roll
+//
+// if (IS_CREATURE_GUID(Guid))
+// {
+// if (Creature* c = m_master->GetMap()->GetCreature(Guid))
+// loot = &c->loot;
+// }
+// else if (IS_GAMEOBJECT_GUID(Guid))
+// {
+// if (GameObject* go = m_master->GetMap()->GetGameObject(Guid))
+// loot = &go->loot;
+// }
+//
+// if (!loot)
+// return;
+//
+// LootItem& lootItem = loot->items[itemSlot];
+// ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(lootItem.itemid);
+// if (!pProto)
+// return;
+//
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// uint32 choice = 0;
+//
+// Player* bot = it->second;
+// if (!bot)
+// continue;
+//
+// Group* group = bot->GetGroup();
+// if (!group || group != m_master->GetGroup())
+// continue;
+//
+// if (bot->GetPlayerbotAI()->CanStore())
+// {
+// if (bot->CanUseItem(pProto) == EQUIP_ERR_OK && bot->GetPlayerbotAI()->IsItemUseful(lootItem.itemid))
+// choice = 1; // Need
+// else if (pProto->Quality < ITEM_QUALITY_LEGENDARY && bot->HasSkill(SKILL_ENCHANTING))
+// choice = 3; // Disenchant
+// else
+// choice = 2; // Greed
+// }
+// else
+// choice = 0; // Pass
+//
+// bot->GetPlayerbotAI()->BeingRolledOn(Guid);
+//
+// group->CountRollVote(bot->GetGUID(), Guid, RollVote(choice));
+//
+// switch (choice)
+// {
+// case ROLL_NEED:
+// bot->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED, 1);
+// break;
+// case ROLL_GREED:
+// case ROLL_DISENCHANT:
+// bot->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED, 1);
+// break;
+// }
+// }
+// return;
+// }
+//
+// // Handle GOSSIP activate actions, prior to GOSSIP select menu actions
+// case CMSG_GOSSIP_HELLO:
+// {
+// // DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO");
+//
+// WorldPacket p(packet); //WorldPacket packet for CMSG_GOSSIP_HELLO, (8)
+// uint64 guid;
+// p.rpos(0); //reset packet pointer
+// p >> guid;
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// if (!bot)
+// continue;
+// Creature* pCreature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
+// if (!pCreature)
+// {
+// //DEBUG_LOG ("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO %s not found or you can't interact with him.", guid.GetString().c_str());
+// continue;
+// }
+//
+// GossipMenuItemsMapBounds pMenuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(pCreature->GetCreatureTemplate()->GossipMenuId);
+// for (GossipMenuItemsContainer::const_iterator itr = pMenuItemBounds.first; itr != pMenuItemBounds.second; ++itr)
+// {
+// uint32 npcflags = pCreature->GetUInt32Value(UNIT_NPC_FLAGS);
+//
+// if (!(itr->second.OptionNpcflag & npcflags))
+// continue;
+//
+// switch (itr->second.OptionType)
+// {
+// case GOSSIP_OPTION_TAXIVENDOR:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_TAXIVENDOR");
+// bot->GetSession()->SendLearnNewTaxiNode(pCreature);
+// break;
+// }
+// case GOSSIP_OPTION_QUESTGIVER:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_QUESTGIVER");
+// bot->GetPlayerbotAI()->TurnInQuests(pCreature);
+// break;
+// }
+// case GOSSIP_OPTION_VENDOR:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_VENDOR");
+// if (!ConfigMgr::GetBoolDefault("PlayerbotAI.SellGarbage", true))
+// continue;
+//
+// // changed the SellGarbage() function to support ch.SendSysMessaage()
+// bot->GetPlayerbotAI()->SellGarbage(*bot);
+// break;
+// }
+// case GOSSIP_OPTION_STABLEPET:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_STABLEPET");
+// break;
+// }
+// case GOSSIP_OPTION_AUCTIONEER:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_AUCTIONEER");
+// break;
+// }
+// case GOSSIP_OPTION_BANKER:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_BANKER");
+// break;
+// }
+// case GOSSIP_OPTION_INNKEEPER:
+// {
+// // bot->GetPlayerbotAI()->TellMaster("PlayerbotMgr:GOSSIP_OPTION_INNKEEPER");
+// break;
+// }
+// }
+// }
+// }
+// return;
+// }
+//
+// //case CMSG_SPIRIT_HEALER_ACTIVATE:
+// //{
+// // for (PlayerBotMap::iterator itr = m_playerBots.begin(); itr != m_playerBots.end(); ++itr)
+// // {
+// // if (Group* grp = itr->second->GetGroup())
+// // grp->RemoveMember(itr->first, 1);
+// // }
+// // return;
+// //}
+//
+// case CMSG_LIST_INVENTORY:
+// {
+// if (!ConfigMgr::GetBoolDefault("PlayerbotAI.SellGarbage", true))
+// return;
+//
+// WorldPacket p(packet);
+// p.rpos(0); // reset reader
+// uint64 npcGUID;
+// p >> npcGUID;
+// WorldObject* const pNpc = (WorldObject*)sObjectAccessor->GetObjectByTypeMask(*m_master, npcGUID, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT);
+// if (!pNpc)
+// return;
+//
+// // for all master's bots
+// for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
+// {
+// Player* bot = it->second;
+// if (!bot->IsInMap(pNpc))
+// {
+// bot->GetPlayerbotAI()->TellMaster("I'm too far away to sell items!");
+// continue;
+// }
+// else
+// {
+// // changed the SellGarbage() function to support ch.SendSysMessaage()
+// bot->GetPlayerbotAI()->SellGarbage(*bot);
+// }
+// }
+// return;
+// }
+//
+// /*
+// case CMSG_NAME_QUERY:
+// case MSG_MOVE_START_FORWARD:
+// case MSG_MOVE_STOP:
+// case MSG_MOVE_SET_FACING:
+// case MSG_MOVE_START_STRAFE_LEFT:
+// case MSG_MOVE_START_STRAFE_RIGHT:
+// case MSG_MOVE_STOP_STRAFE:
+// case MSG_MOVE_START_BACKWARD:
+// case MSG_MOVE_HEARTBEAT:
+// case CMSG_STANDSTATECHANGE:
+// case CMSG_QUERY_TIME:
+// case CMSG_CREATURE_QUERY:
+// case CMSG_GAMEOBJECT_QUERY:
+// case MSG_MOVE_JUMP:
+// case MSG_MOVE_FALL_LAND:
+// return;
+//
+// default:
+// {
+// const char* oc = LookupOpcodeName(packet.GetOpcode());
+// // ChatHandler ch(m_master);
+// // ch.SendSysMessage(oc);
+//
+// std::ostringstream out;
+// out << "masterin: " << oc;
+// sLog.outError(out.str().c_str());
+// }
+// */
+// }
+//}
+//
+void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& /*packet*/)
+{
+ /*
+ switch (packet.GetOpcode())
+ {
+ // maybe our bots should only start looting after the master loots?
+ //case SMSG_LOOT_RELEASE_RESPONSE: {}
+ case SMSG_NAME_QUERY_RESPONSE:
+ case SMSG_MONSTER_MOVE:
+ case SMSG_COMPRESSED_UPDATE_OBJECT:
+ case SMSG_DESTROY_OBJECT:
+ case SMSG_UPDATE_OBJECT:
+ case SMSG_STANDSTATE_UPDATE:
+ case MSG_MOVE_HEARTBEAT:
+ case SMSG_QUERY_TIME_RESPONSE:
+ case SMSG_AURA_UPDATE_ALL:
+ case SMSG_CREATURE_QUERY_RESPONSE:
+ case SMSG_GAMEOBJECT_QUERY_RESPONSE:
+ return;
+ default:
+ {
+ const char* oc = LookupOpcodeName(packet.GetOpcode());
+
+ std::ostringstream out;
+ out << "masterout: " << oc;
+ sLog.outError(out.str().c_str());
+ }
+ }
+ */
+}
+
+void PlayerbotMgr::LogoutAllBots()
+{
+ RemoveAllBotsFromGroup();
+ while (true)
+ {
+ PlayerBotMap::const_iterator itr = GetPlayerBotsBegin();
+ if (itr == GetPlayerBotsEnd()) break;
+ Player* bot = itr->second;
+ LogoutPlayerBot(bot->GetGUID());
+ }
+}
+
+void PlayerbotMgr::Stay()
+{
+ for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr)
+ {
+ itr->second->GetMotionMaster()->Clear();
+ }
+}
+
+// Playerbot mod: logs out a Playerbot.
+void PlayerbotMgr::LogoutPlayerBot(uint64 guid, bool uninvite)
+{
+ if (Player* bot = GetPlayerBot(guid))
+ {
+ if (uninvite)
+ if (Group* group = bot->GetGroup())
+ group->RemoveMember(guid);
+
+ WorldSession* botWorldSession = bot->GetSession();
+ botWorldSession->m_master = NULL;
+ m_playerBots.erase(guid); //deletes bot player ptr inside this WorldSession PlayerBotMap
+ botWorldSession->LogoutPlayer(true); //this will delete the bot Player object and PlayerbotAI (along with class AI) object
+ delete botWorldSession; //finally delete the bot's WorldSession
+ botWorldSession = NULL;
+ return;
+ }
+ std::string name;
+ sObjectMgr->GetPlayerNameByGUID(guid, name);
+ sLog->outFatal(LOG_FILTER_PLAYER, "PlayerbotMgr:: Trying to logout playerbot (%s, guid %u) which does not exist!!!", name.c_str(), GUID_LOPART(guid));
+}
+
+// Playerbot mod: Gets a player bot Player object for this WorldSession master
+Player* PlayerbotMgr::GetPlayerBot(uint64 playerGuid) const
+{
+ PlayerBotMap::const_iterator it = m_playerBots.find(playerGuid);
+ return (it == m_playerBots.end()) ? NULL : it->second;
+}
+
+void PlayerbotMgr::OnBotLogin(Player* const bot)
+{
+ if (!bot)
+ return;
+
+ // tell the world session that they now manage this new bot
+ m_playerBots[bot->GetGUID()] = bot;
+
+ // if bot is in a group and master is not in group then
+ // have bot leave their group
+ if (bot->GetGroup() && (!m_master->GetGroup() || !m_master->GetGroup()->IsMember(bot->GetGUID())))
+ bot->RemoveFromGroup();
+
+ // sometimes master can lose leadership, pass leadership to master check
+ uint64 masterGuid = m_master->GetGUID();
+ if (m_master->GetGroup() && !m_master->GetGroup()->IsLeader(masterGuid))
+ {
+ // But only do so if one of the master's bots is leader
+ for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); itr++)
+ {
+ Player* bot = itr->second;
+ if (m_master->GetGroup()->IsLeader(bot->GetGUID()))
+ {
+ m_master->GetGroup()->ChangeLeader(masterGuid);
+ break;
+ }
+ }
+ }
+ // Finally, give the bot some AI, object is owned by the player class
+ PlayerbotAI* ai = new PlayerbotAI(this, bot);
+ bot->SetPlayerbotAI(ai);
+}
+
+void PlayerbotMgr::RemoveAllBotsFromGroup(bool force)
+{
+ Group* gr = m_master->GetGroup();
+ if (gr || force)
+ for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); m_master->GetGroup() && it != GetPlayerBotsEnd(); ++it)
+ if (force || gr == it->second->GetGroup())
+ gr->RemoveMember(it->first);
+}
+
+void Player::GetSkills(std::list<uint32>& m_spellsToLearn)
+{
+ for (SkillStatusMap::const_iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
+ {
+ if (itr->second.uState == SKILL_DELETED)
+ continue;
+
+ m_spellsToLearn.push_back(itr->first);
+ }
+}
+
+void Player::MakeTalentGlyphLink(std::ostringstream &out)
+{
+ // |cff4e96f7|Htalent:1396:4|h[Unleashed Fury]|h|r
+ // |cff66bbff|Hglyph:23:460|h[Glyph of Fortitude]|h|r
+
+ if (m_specsCount)
+ // loop through all specs (only 1 for now)
+ for (uint32 specIdx = 0; specIdx < m_specsCount; ++specIdx)
+ {
+ // find class talent tabs (all players have 3 talent tabs)
+ uint32 const* talentTabIds = GetTalentTabPages(getClass());
+
+ out << "\n" << "Active Talents ";
+
+ for (uint32 i = 0; i < 3; ++i)
+ {
+ uint32 talentTabId = talentTabIds[i];
+ for (PlayerTalentMap::iterator iter = m_talents[specIdx]->begin(); iter != m_talents[specIdx]->end(); ++iter)
+ {
+ PlayerTalent *talent = (*iter).second;
+
+ if (talent->state == PLAYERSPELL_REMOVED)
+ continue;
+
+ TalentSpellPos const* talentPos = GetTalentSpellPos((*iter).first);
+ if (!talentPos)
+ continue;
+ TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id);
+ if (!talentInfo)
+ continue;
+ // skip another tab talents
+ if (talentInfo->TalentTab != talentTabId)
+ continue;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo((*iter).first);
+ if (!spellInfo)
+ continue;
+
+ out << "|cff4e96f7|Htalent:" << talentInfo->TalentID << ":" << talentInfo->RankID[spellInfo->GetRank()]
+ << " |h[" << spellInfo->SpellName[GetSession()->GetSessionDbcLocale()] << "]|h|r";
+ }
+ }
+
+ uint32 freepoints = 0;
+
+ out << " Unspent points : ";
+
+ if ((freepoints = GetFreeTalentPoints()) > 0)
+ out << "|h|cff00ff00" << freepoints << "|h|r";
+ else
+ out << "|h|cffff0000" << freepoints << "|h|r";
+
+ out << "\n" << "Active Glyphs ";
+ // GlyphProperties.dbc
+ for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
+ {
+ GlyphPropertiesEntry const* glyph = sGlyphPropertiesStore.LookupEntry(m_Glyphs[specIdx][i]);
+ if (!glyph)
+ continue;
+
+ SpellEntry const* spell_entry = sSpellStore.LookupEntry(glyph->SpellId);
+
+ out << "|cff66bbff|Hglyph:" << GetGlyphSlot(i) << ":" << m_Glyphs[specIdx][i]
+ << " |h[" << spell_entry->SpellName[GetSession()->GetSessionDbcLocale()] << "]|h|r";
+
+ }
+ }
+}
+
+void Player::chompAndTrim(std::string& str)
+{
+ while (str.length() > 0)
+ {
+ char lc = str[str.length() - 1];
+ if (lc == '\r' || lc == '\n' || lc == ' ' || lc == '"' || lc == '\'')
+ str = str.substr(0, str.length() - 1);
+ else
+ break;
+ }
+
+ while (str.length() > 0)
+ {
+ char lc = str[0];
+ if (lc == ' ' || lc == '"' || lc == '\'')
+ str = str.substr(1, str.length() - 1);
+ else
+ break;
+ }
+}
+
+bool Player::getNextQuestId(std::string const& pString, uint32& pStartPos, uint32& pId)
+{
+ bool result = false;
+ uint32 i;
+ for (i = pStartPos; i < pString.size(); ++i)
+ {
+ if (pString[i] == ',')
+ break;
+ }
+ if (i > pStartPos)
+ {
+ std::string idString = pString.substr(pStartPos, i - pStartPos);
+ pStartPos = i + 1;
+ chompAndTrim(idString);
+ pId = atoi(idString.c_str());
+ result = true;
+ }
+ return result;
+}
+
+void Player::UpdateMail()
+{
+ // save money,items and mail to prevent cheating
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ SaveGoldToDB(trans);
+ SaveInventoryAndGoldToDB(trans);
+ _SaveMail(trans);
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+uint32 PlayerbotMgr::GetSpec(Player* player)
+{
+ uint32 row = 0;
+ uint32 spec = 0;
+ uint8 rank = 0;
+ uint32 m_activeSpec = player->GetActiveSpec();
+ PlayerTalentMap* m_talents = player->GetTalents(m_activeSpec);
+
+ for (PlayerTalentMap::iterator iter = m_talents->begin(); iter != m_talents->end(); ++iter)
+ {
+ PlayerTalent* talent = (*iter).second;
+ if (talent->state == PLAYERSPELL_REMOVED)
+ continue;
+ TalentSpellPos const* talentPos = GetTalentSpellPos((*iter).first);
+ if (!talentPos)
+ continue;
+ TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id);
+ if (!talentInfo)
+ continue;
+
+ //spec is set with 3 rules
+ //a) there is no spec set yet
+ //b) found talent deeper in talent tree
+ //c) found talent with same depth, but with higher rank
+ if (spec == 0 || talentInfo->Row > row || (talentInfo->Row == row && talentPos->rank > rank))
+ {
+ row = talentInfo->Row;
+ spec = talentInfo->TalentTab;
+ }
+ }
+
+ //Return the tree with the deepest talent
+ return spec;
+}
+
+bool Player::HavePBot() const
+{
+ return m_playerbotMgr != NULL && !m_playerbotMgr->getPlayerbots().empty();
+}
+
+uint16 PlayerbotMgr::Rand() const
+{
+ return urand(0, 100 + m_playerBots.empty() ? 0 : (m_playerBots.size() - 1) * 10);
+}
diff --git a/src/server/game/AI/PlayerBots/bp_mgr.h b/src/server/game/AI/PlayerBots/bp_mgr.h
new file mode 100644
index 0000000..8fb5c6c
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_mgr.h
@@ -0,0 +1,64 @@
+#ifndef _PLAYERBOTMGR_H
+#define _PLAYERBOTMGR_H
+
+#include "Common.h"
+
+class Player;
+class WorldPacket;
+
+typedef UNORDERED_MAP<uint64, Player*> PlayerBotMap;
+
+class PlayerbotMgr
+{
+ public:
+ PlayerbotMgr(Player* const master);
+ virtual ~PlayerbotMgr();
+
+ //void Update(uint32 const p_time);
+
+ //Packets
+ void HandleMasterIncomingPacket(WorldPacket const& packet);
+ void HandleMasterOutgoingPacket(WorldPacket const& packet);
+
+ void AddPlayerBot(uint64 guid);
+ void LogoutPlayerBot(uint64 guid, bool uninvite = true);
+ Player* GetPlayerBot(uint64 guid) const;
+ Player* GetMaster() const { return m_master; }
+
+ PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return m_playerBots.begin(); }
+ PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return m_playerBots.end(); }
+ PlayerBotMap const& getPlayerbots() const { return m_playerBots; }
+ uint8 GetPlayerBotsCount() const { return uint8(m_playerBots.size()); }
+
+ void LogoutAllBots();
+ void RemoveAllBotsFromGroup(bool force = false);
+ void OnBotLogin(Player* const bot);
+ void Stay();
+
+ uint16 Rand() const;
+ static uint32 GetSpec(Player* player);
+
+ public:
+ //config variables (needs review)
+ uint32 m_confRestrictBotLevel;
+ uint32 m_confDisableBotsInRealm;
+ uint32 m_confMaxNumBots;
+ bool m_confDisableBots;
+ bool m_confDebugWhisper;
+ float m_confFollowDistance[2];
+ uint32 gConfigSellLevelDiff;
+ bool m_confCollectCombat;
+ bool m_confCollectQuest;
+ bool m_confCollectProfession;
+ bool m_confCollectLoot;
+ bool m_confCollectSkin;
+ bool m_confCollectObjects;
+ uint32 m_confCollectDistance;
+ uint32 m_confCollectDistanceMax;
+
+ private:
+ Player* const m_master;
+ PlayerBotMap m_playerBots;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_pal_ai.cpp b/src/server/game/AI/PlayerBots/bp_pal_ai.cpp
new file mode 100644
index 0000000..180c5be
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_pal_ai.cpp
@@ -0,0 +1,673 @@
+/*
+ Name : PlayerbotPaladinAI.cpp
+ Complete: maybe around 27% :D
+ Author : Natsukawa
+ Version : 0.35
+ */
+#include "bp_ai.h"
+#include "bp_pal_ai.h"
+#include "SpellAuras.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotPaladinAI::PlayerbotPaladinAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ RETRIBUTION_AURA = PlayerbotAI::InitSpell(me, RETRIBUTION_AURA_1);
+ CRUSADER_AURA = PlayerbotAI::InitSpell(me, CRUSADER_AURA_1);
+ CRUSADER_STRIKE = PlayerbotAI::InitSpell(me, CRUSADER_STRIKE_1);
+ SEAL_OF_COMMAND = PlayerbotAI::InitSpell(me, SEAL_OF_COMMAND_1);
+ SEAL_OF_RIGHTEOUSNESS = PlayerbotAI::InitSpell(me, SEAL_OF_RIGHTEOUSNESS_1);
+ SEAL_OF_CORRUPTION = PlayerbotAI::InitSpell(me, SEAL_OF_CORRUPTION_1);
+ SEAL_OF_JUSTICE = PlayerbotAI::InitSpell(me, SEAL_OF_JUSTICE_1);
+ SEAL_OF_LIGHT = PlayerbotAI::InitSpell(me, SEAL_OF_LIGHT_1);
+ SEAL_OF_VENGEANCE = PlayerbotAI::InitSpell(me, SEAL_OF_VENGEANCE_1);
+ SEAL_OF_WISDOM = PlayerbotAI::InitSpell(me, SEAL_OF_WISDOM_1);
+ JUDGEMENT_OF_LIGHT = PlayerbotAI::InitSpell(me, JUDGEMENT_OF_LIGHT_1);
+ JUDGEMENT_OF_WISDOM = PlayerbotAI::InitSpell(me, JUDGEMENT_OF_WISDOM_1);
+ JUDGEMENT_OF_JUSTICE = PlayerbotAI::InitSpell(me, JUDGEMENT_OF_JUSTICE_1);
+ DIVINE_STORM = PlayerbotAI::InitSpell(me, DIVINE_STORM_1);
+ BLESSING_OF_MIGHT = PlayerbotAI::InitSpell(me, BLESSING_OF_MIGHT_1);
+ GREATER_BLESSING_OF_MIGHT = PlayerbotAI::InitSpell(me, GREATER_BLESSING_OF_MIGHT_1);
+ HAMMER_OF_WRATH = PlayerbotAI::InitSpell(me, HAMMER_OF_WRATH_1);
+ FLASH_OF_LIGHT = PlayerbotAI::InitSpell(me, FLASH_OF_LIGHT_1); // Holy
+ HOLY_LIGHT = PlayerbotAI::InitSpell(me, HOLY_LIGHT_1);
+ HOLY_SHOCK = PlayerbotAI::InitSpell(me, HOLY_SHOCK_1);
+ HOLY_WRATH = PlayerbotAI::InitSpell(me, HOLY_WRATH_1);
+ DIVINE_FAVOR = PlayerbotAI::InitSpell(me, DIVINE_FAVOR_1);
+ CONCENTRATION_AURA = PlayerbotAI::InitSpell(me, CONCENTRATION_AURA_1);
+ BLESSING_OF_WISDOM = PlayerbotAI::InitSpell(me, BLESSING_OF_WISDOM_1);
+ GREATER_BLESSING_OF_WISDOM = PlayerbotAI::InitSpell(me, GREATER_BLESSING_OF_WISDOM_1);
+ CONSECRATION = PlayerbotAI::InitSpell(me, CONSECRATION_1);
+ AVENGING_WRATH = PlayerbotAI::InitSpell(me, AVENGING_WRATH_1);
+ LAY_ON_HANDS = PlayerbotAI::InitSpell(me, LAY_ON_HANDS_1);
+ EXORCISM = PlayerbotAI::InitSpell(me, EXORCISM_1);
+ SACRED_SHIELD = PlayerbotAI::InitSpell(me, SACRED_SHIELD_1);
+ DIVINE_PLEA = PlayerbotAI::InitSpell(me, DIVINE_PLEA_1);
+ BLESSING_OF_KINGS = PlayerbotAI::InitSpell(me, BLESSING_OF_KINGS_1);
+ GREATER_BLESSING_OF_KINGS = PlayerbotAI::InitSpell(me, GREATER_BLESSING_OF_KINGS_1);
+ BLESSING_OF_SANCTUARY = PlayerbotAI::InitSpell(me, BLESSING_OF_SANCTUARY_1);
+ GREATER_BLESSING_OF_SANCTUARY = PlayerbotAI::InitSpell(me, GREATER_BLESSING_OF_SANCTUARY_1);
+ HAMMER_OF_JUSTICE = PlayerbotAI::InitSpell(me, HAMMER_OF_JUSTICE_1);
+ RIGHTEOUS_FURY = PlayerbotAI::InitSpell(me, RIGHTEOUS_FURY_1);
+ RIGHTEOUS_DEFENSE = PlayerbotAI::InitSpell(me, RIGHTEOUS_DEFENSE_1);
+ SHADOW_RESISTANCE_AURA = PlayerbotAI::InitSpell(me, SHADOW_RESISTANCE_AURA_1);
+ DEVOTION_AURA = PlayerbotAI::InitSpell(me, DEVOTION_AURA_1);
+ FIRE_RESISTANCE_AURA = PlayerbotAI::InitSpell(me, FIRE_RESISTANCE_AURA_1);
+ FROST_RESISTANCE_AURA = PlayerbotAI::InitSpell(me, FROST_RESISTANCE_AURA_1);
+ HAND_OF_PROTECTION = PlayerbotAI::InitSpell(me, HAND_OF_PROTECTION_1);
+ DIVINE_PROTECTION = PlayerbotAI::InitSpell(me, DIVINE_PROTECTION_1);
+ DIVINE_INTERVENTION = PlayerbotAI::InitSpell(me, DIVINE_INTERVENTION_1);
+ DIVINE_SACRIFICE = PlayerbotAI::InitSpell(me, DIVINE_SACRIFICE_1);
+ DIVINE_SHIELD = PlayerbotAI::InitSpell(me, DIVINE_SHIELD_1);
+ HOLY_SHIELD = PlayerbotAI::InitSpell(me, HOLY_SHIELD_1);
+ AVENGERS_SHIELD = PlayerbotAI::InitSpell(me, AVENGERS_SHIELD_1);
+ HAND_OF_SACRIFICE = PlayerbotAI::InitSpell(me, HAND_OF_SACRIFICE_1);
+ SHIELD_OF_RIGHTEOUSNESS = PlayerbotAI::InitSpell(me, SHIELD_OF_RIGHTEOUSNESS_1);
+ REDEMPTION = PlayerbotAI::InitSpell(me, REDEMPTION_1);
+ PURIFY = PlayerbotAI::InitSpell(me, PURIFY_1);
+ CLEANSE = PlayerbotAI::InitSpell(me, CLEANSE_1);
+ HAND_OF_RECKONING = PlayerbotAI::InitSpell(me, HAND_OF_RECKONING_1);
+ ART_OF_WAR = PlayerbotAI::InitSpell(me, ART_OF_WAR_1);
+ HAMMER_OF_THE_RIGHTEOUS = PlayerbotAI::InitSpell(me, HAMMER_OF_THE_RIGHTEOUS_1);
+
+ // Warrior auras
+ DEFENSIVE_STANCE = 71; //Def Stance
+ BERSERKER_STANCE = 2458; //Ber Stance
+ BATTLE_STANCE = 2457; //Bat Stance
+
+ FORBEARANCE = 25771; // cannot be protected
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_MANA_CLASSES);
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_PALADIN); // draenei
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+
+ //The check doesn't work for now
+ //PRAYER_OF_SHADOW_PROTECTION = PlayerbotAI::InitSpell(me, PriestSpells::PRAYER_OF_SHADOW_PROTECTION_1);
+}
+
+PlayerbotPaladinAI::~PlayerbotPaladinAI() {}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder())
+ // {
+ // if (pTarget->GetCombatReach() <= ATTACK_DISTANCE)
+ // {
+ // // Set everyone's UpdateAI() waiting to 2 seconds
+ // m_ai->SetGroupIgnoreUpdateTime(2);
+ // // Clear their TEMP_WAIT_TANKAGGRO flag
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // // Start attacking, force target on current target
+ // m_ai->Attack(m_ai->GetCurrentTarget());
+
+ // // While everyone else is waiting 2 second, we need to build up aggro, so don't return
+ // }
+ // else
+ // {
+ // // TODO: add check if target is ranged
+ // return RETURN_NO_ACTION_OK; // wait for target to get nearer
+ // }
+ // }
+ // else if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder())
+ // return HealPlayer(GetHealTarget());
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+ //if (!pTarget) return RETURN_NO_ACTION_INVALIDTARGET;
+
+ //// damage spells
+ //uint32 spec = m_bot->GetSpec();
+ //std::ostringstream out;
+
+ //// Make sure healer stays put, don't even melee (aggro) if in range.
+ //if (m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //else if (!m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE))
+ // return RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(m_bot) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE))
+ // return RETURN_CONTINUE;
+ //}
+
+ ////Used to determine if this bot has highest threat
+ //Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //switch (spec)
+ //{
+ // case PALADIN_SPEC_HOLY:
+ // if (m_ai->IsHealer())
+ // return RETURN_NO_ACTION_OK;
+ // // else: DPS (retribution, NEVER protection)
+
+ // case PALADIN_SPEC_RETRIBUTION:
+ // if (HAMMER_OF_WRATH > 0 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && m_ai->CastSpell (HAMMER_OF_WRATH, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (ART_OF_WAR > 0 && EXORCISM > 0 && !m_bot->HasSpellCooldown(EXORCISM) && m_bot->HasAura(ART_OF_WAR, EFFECT_0) && m_ai->CastSpell (EXORCISM, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CRUSADER_STRIKE > 0 && !m_bot->HasSpellCooldown(CRUSADER_STRIKE) && m_ai->CastSpell (CRUSADER_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (DIVINE_STORM > 0 && /*m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE*/ !m_bot->HasSpellCooldown(DIVINE_STORM) && m_ai->CastSpell (DIVINE_STORM, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (JUDGEMENT_OF_LIGHT > 0 && m_ai->CastSpell (JUDGEMENT_OF_LIGHT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (AVENGING_WRATH > 0 && m_ai->CastSpell (AVENGING_WRATH, *m_bot))
+ // return RETURN_CONTINUE;
+ // /*if (HAMMER_OF_JUSTICE > 0 && !pTarget->HasAura(HAMMER_OF_JUSTICE, EFFECT_0) && m_ai->CastSpell (HAMMER_OF_JUSTICE, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (SACRED_SHIELD > 0 && pVictim == m_bot && m_bot->GetHealthPct() < 70 && !m_bot->HasAura(SACRED_SHIELD, EFFECT_0) && m_ai->CastSpell (SACRED_SHIELD, *m_bot))
+ // return RETURN_CONTINUE;*/
+ // /*if (HOLY_WRATH > 0 && m_ai->GetAttackerCount() >= 3 && dist <= ATTACK_DISTANCE && m_ai->CastSpell (HOLY_WRATH, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (HAND_OF_SACRIFICE > 0 && pVictim == GetMaster() && !GetMaster()->HasAura(HAND_OF_SACRIFICE, EFFECT_0) && m_ai->CastSpell (HAND_OF_SACRIFICE, *GetMaster()))
+ // return RETURN_CONTINUE;*/
+ // /*if (DIVINE_PROTECTION > 0 && pVictim == m_bot && !m_bot->HasAura(FORBEARANCE, EFFECT_0) && m_bot->GetHealthPct() < 30 && m_ai->CastSpell (DIVINE_PROTECTION, *m_bot))
+ // return RETURN_CONTINUE;*/
+ // /*if (RIGHTEOUS_DEFENSE > 0 && pVictim != m_bot && m_bot->GetHealthPct() > 70 && m_ai->CastSpell (RIGHTEOUS_DEFENSE, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (DIVINE_PLEA > 0 && !m_bot->HasAura(DIVINE_PLEA, EFFECT_0) && m_ai->CastSpell (DIVINE_PLEA, *m_bot))
+ // return RETURN_CONTINUE;*/
+ // /*if (DIVINE_FAVOR > 0 && !m_bot->HasAura(DIVINE_FAVOR, EFFECT_0) && m_ai->CastSpell (DIVINE_FAVOR, *m_bot))
+ // return RETURN_CONTINUE;*/
+ // return RETURN_NO_ACTION_OK;
+
+ // case PALADIN_SPEC_PROTECTION:
+ // //Taunt if orders specify
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK && !newTarget && HAND_OF_RECKONING > 0 && !m_bot->HasSpellCooldown(HAND_OF_RECKONING) && m_ai->CastSpell(HAND_OF_RECKONING, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CONSECRATION > 0 && !m_bot->HasSpellCooldown(CONSECRATION) && m_ai->CastSpell(CONSECRATION, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HOLY_SHIELD > 0 && !m_bot->HasAura(HOLY_SHIELD) && m_ai->CastSpell(HOLY_SHIELD, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (AVENGERS_SHIELD > 0 && !m_bot->HasSpellCooldown(AVENGERS_SHIELD) && m_ai->CastSpell(AVENGERS_SHIELD, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HAMMER_OF_THE_RIGHTEOUS > 0 && !m_bot->HasSpellCooldown(HAMMER_OF_THE_RIGHTEOUS) && m_ai->CastSpell(HAMMER_OF_THE_RIGHTEOUS, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHIELD_OF_RIGHTEOUSNESS > 0 && !m_bot->HasSpellCooldown(SHIELD_OF_RIGHTEOUSNESS) && m_ai->CastSpell(SHIELD_OF_RIGHTEOUSNESS, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (JUDGEMENT_OF_LIGHT > 0 && m_ai->CastSpell (JUDGEMENT_OF_LIGHT, *pTarget))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_OK;
+ //}
+
+ //if (DIVINE_SHIELD > 0 && m_bot->GetHealthPct() < 30 && pVictim == m_bot && !m_bot->HasAura(FORBEARANCE, EFFECT_0) && !m_bot->HasAura(DIVINE_SHIELD, EFFECT_0))
+ // m_ai->CastSpell(DIVINE_SHIELD, *m_bot);
+
+ //if (DIVINE_SACRIFICE > 0 && m_bot->GetHealthPct() > 50 && pVictim != m_bot && !m_bot->HasAura(DIVINE_SACRIFICE, EFFECT_0))
+ // m_ai->CastSpell(DIVINE_SACRIFICE, *m_bot);
+
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(HAMMER_OF_JUSTICE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+CombatManeuverReturns PlayerbotPaladinAI::HealPlayer(Player* target)
+{
+ //CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target);
+ //if (r != RETURN_NO_ACTION_OK)
+ // return r;
+
+ //if (!target->isAlive())
+ //{
+ // if (REDEMPTION && m_ai->CastSpell(REDEMPTION, *target))
+ // {
+ // std::string msg = "Resurrecting ";
+ // msg += target->GetName();
+ // m_bot->Say(msg, LANG_UNIVERSAL);
+ // return RETURN_CONTINUE;
+ // }
+ // return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM
+ //}
+
+ //if (PURIFY > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0)
+ //{
+ // uint32 DISPEL = CLEANSE > 0 ? CLEANSE : PURIFY;
+ // uint32 dispelMask = SpellInfo::GetDispelMask(DISPEL_DISEASE);
+ // uint32 dispelMask2 = SpellInfo::GetDispelMask(DISPEL_POISON);
+ // uint32 dispelMask3 = SpellInfo::GetDispelMask(DISPEL_MAGIC);
+ // Unit::AuraMap const& auras = target->GetOwnedAuras();
+ // for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
+ // {
+ // Aura *holder = itr->second;
+ // if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask)
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_DISEASE)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // else if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask2)
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_POISON)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // else if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask3 & (DISPEL == CLEANSE))
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_MAGIC)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // }
+ //}
+
+ //uint8 hp = target->GetHealthPct();
+
+ //// Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check)
+ //if (hp >= 90)
+ // return RETURN_NO_ACTION_OK;
+
+ //if (hp < 25 && m_ai->CastSpell(LAY_ON_HANDS, *target))
+ // return RETURN_CONTINUE;
+
+ //// You probably want to save this for tank/healer trouble
+ //if (hp < 30 && HAND_OF_PROTECTION > 0 && !target->HasAura(FORBEARANCE, EFFECT_0)
+ // && !target->HasAura(HAND_OF_PROTECTION, EFFECT_0) && !target->HasAura(DIVINE_PROTECTION, EFFECT_0)
+ // && !target->HasAura(DIVINE_SHIELD, EFFECT_0) && (GetTargetJob(target) & (JOB_HEAL | JOB_TANK))
+ // && m_ai->CastSpell(HAND_OF_PROTECTION, *target))
+ // return RETURN_CONTINUE;
+
+ //// Isn't this more of a group heal spell?
+ //if (hp < 40 && m_ai->CastSpell(FLASH_OF_LIGHT, *target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 60 && m_ai->CastSpell(HOLY_SHOCK, *target))
+ // return RETURN_CONTINUE;
+
+ //if (hp < 90 && m_ai->CastSpell(HOLY_LIGHT, *target))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+} // end HealTarget
+
+void PlayerbotPaladinAI::CheckAuras()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //uint32 spec = m_bot->GetSpec();
+
+ //// If we have resist orders, adjust accordingly
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FROST)
+ //{
+ // if (!m_bot->HasAura(FROST_RESISTANCE_AURA) && FROST_RESISTANCE_AURA > 0 && !m_bot->HasAura(FROST_RESISTANCE_AURA))
+ // m_ai->CastSpell(FROST_RESISTANCE_AURA);
+ // return;
+ //}
+ //else if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FIRE)
+ //{
+ // if (!m_bot->HasAura(FIRE_RESISTANCE_AURA) && FIRE_RESISTANCE_AURA > 0 && !m_bot->HasAura(FIRE_RESISTANCE_AURA))
+ // m_ai->CastSpell(FIRE_RESISTANCE_AURA);
+ // return;
+ //}
+ //else if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_SHADOW)
+ //{
+ // // Shadow protection check is broken, they stack!
+ // if (!m_bot->HasAura(SHADOW_RESISTANCE_AURA) && SHADOW_RESISTANCE_AURA > 0 && !m_bot->HasAura(SHADOW_RESISTANCE_AURA)) // /*&& !m_bot->HasAura(PRAYER_OF_SHADOW_PROTECTION)*/ /*&& !m_bot->HasAura(PRAYER_OF_SHADOW_PROTECTION)*/
+ // m_ai->CastSpell(SHADOW_RESISTANCE_AURA);
+ // return;
+ //}
+
+ //// If we have no resist orders, adjust aura based on spec
+ //if (spec == PALADIN_SPEC_HOLY)
+ //{
+ // if (CONCENTRATION_AURA > 0 && !m_bot->HasAura(CONCENTRATION_AURA))
+ // m_ai->CastSpell(CONCENTRATION_AURA);
+ // return;
+ //}
+ //else if (spec == PALADIN_SPEC_PROTECTION)
+ //{
+ // if (DEVOTION_AURA > 0 && !m_bot->HasAura(DEVOTION_AURA))
+ // m_ai->CastSpell(DEVOTION_AURA);
+ // return;
+ //}
+ //else if (spec == PALADIN_SPEC_RETRIBUTION)
+ //{
+ // if (RETRIBUTION_AURA > 0 && !m_bot->HasAura(RETRIBUTION_AURA))
+ // m_ai->CastSpell(RETRIBUTION_AURA);
+ // return;
+ //}
+}
+
+void PlayerbotPaladinAI::CheckSeals()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //uint32 spec = m_bot->GetSpec();
+ //uint32 RACIAL = (SEAL_OF_CORRUPTION > 0) ? SEAL_OF_CORRUPTION : SEAL_OF_VENGEANCE;
+
+ //switch (spec)
+ //{
+ // case PALADIN_SPEC_HOLY:
+ // //I'm not even sure if holy uses seals?
+ // if (SEAL_OF_WISDOM > 0 && !m_bot->HasAura(SEAL_OF_WISDOM, EFFECT_0))
+ // m_ai->CastSpell(SEAL_OF_WISDOM, *m_bot);
+ // break;
+
+ // case PALADIN_SPEC_PROTECTION:
+ // if (RACIAL > 0 && !m_bot->HasAura(RACIAL, EFFECT_0))
+ // m_ai->CastSpell(RACIAL, *m_bot);
+ // else if (SEAL_OF_RIGHTEOUSNESS > 0 && !m_bot->HasAura(SEAL_OF_RIGHTEOUSNESS, EFFECT_0) && !m_bot->HasAura(RACIAL, EFFECT_0))
+ // m_ai->CastSpell(SEAL_OF_RIGHTEOUSNESS, *m_bot);
+ // break;
+
+ // case PALADIN_SPEC_RETRIBUTION:
+ // if (RACIAL > 0 && !m_bot->HasAura(RACIAL, EFFECT_0))
+ // m_ai->CastSpell(RACIAL, *m_bot);
+ // else if (SEAL_OF_COMMAND > 0 && !m_bot->HasAura(SEAL_OF_COMMAND, EFFECT_0) && !m_bot->HasAura(RACIAL, EFFECT_0))
+ // m_ai->CastSpell(SEAL_OF_COMMAND, *m_bot);
+ // else if (SEAL_OF_RIGHTEOUSNESS > 0 && !m_bot->HasAura(SEAL_OF_RIGHTEOUSNESS, EFFECT_0) && !m_bot->HasAura(SEAL_OF_COMMAND, EFFECT_0) && !m_bot->HasAura(RACIAL, EFFECT_0))
+ // m_ai->CastSpell(SEAL_OF_RIGHTEOUSNESS, *m_bot);
+ // break;
+ //}
+}
+
+void PlayerbotPaladinAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //if (!m_bot->isAlive() || m_bot->IsInDuel()) return;
+
+ //CheckAuras();
+ //CheckSeals();
+
+ ////Put up RF if tank
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK)
+ // m_ai->SelfBuff(RIGHTEOUS_FURY);
+ ////Disable RF if not tank
+ //else if (m_bot->HasAura(RIGHTEOUS_FURY))
+ // m_bot->RemoveAurasDueToSpell(RIGHTEOUS_FURY);
+
+ //// Revive
+ //if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE)
+ // return;
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(m_bot) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+
+ //// buff group
+ //if (Buff(&PlayerbotPaladinAI::BuffHelper, 1)) // Paladin's BuffHelper takes care of choosing the specific Blessing so just pass along a non-zero value
+ // return;
+
+ //// hp/mana check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage())
+ // return;
+}
+
+/**
+ * BuffHelper
+ * BuffHelper is a static function, takes an AI, spellId (ignored for paladin) and a target and attempts to buff them as well as their pets as
+ * best as possible.
+ *
+ * Return bool - returns true if a buff took place.
+ */
+bool PlayerbotPaladinAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target)
+{
+ return false;
+ //if (!ai) return false;
+ //if (spellId == 0) return false;
+ //if (!target) return false;
+
+ //uint8 SPELL_BLESSING = 2; // See SpellSpecific enum in SpellMgr.h
+
+ //PlayerbotPaladinAI* c = (PlayerbotPaladinAI*) ai->GetClassAI();
+ //uint32 bigSpellId = 0;
+
+ //Pet* pet = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer()->GetPet() : NULL;
+ //uint32 petSpellId = 0, petBigSpellId = 0;
+
+ //if (ai->CanReceiveSpecificSpell(SPELL_BLESSING, target))
+ // return false;
+
+ //// See which buff is appropriate according to class
+ //// TODO: take into account other paladins in the group
+ //switch (target->getClass())
+ //{
+ // case CLASS_DRUID:
+ // case CLASS_SHAMAN:
+ // case CLASS_PALADIN:
+ // spellId = c->BLESSING_OF_MIGHT;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_KINGS;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_WISDOM;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_SANCTUARY;
+ // if (!spellId)
+ // return false;
+ // }
+ // }
+ // }
+ // break;
+ // case CLASS_DEATH_KNIGHT:
+ // case CLASS_HUNTER:
+ // if (pet && ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet) && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE))
+ // {
+ // petSpellId = c->BLESSING_OF_MIGHT;
+ // if (!petSpellId)
+ // {
+ // petSpellId = c->BLESSING_OF_KINGS;
+ // if (!petSpellId)
+ // petSpellId = c->BLESSING_OF_SANCTUARY;
+ // }
+ // }
+ // case CLASS_ROGUE:
+ // case CLASS_WARRIOR:
+ // spellId = c->BLESSING_OF_MIGHT;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_KINGS;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_SANCTUARY;
+ // if (!spellId)
+ // return false;
+ // }
+ // }
+ // break;
+ // case CLASS_WARLOCK:
+ // if (pet && ai->CanReceiveSpecificSpell(SPELL_BLESSING, pet) && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE))
+ // {
+ // if (pet->getPowerType() == POWER_MANA)
+ // petSpellId = c->BLESSING_OF_WISDOM;
+ // else
+ // petSpellId = c->BLESSING_OF_MIGHT;
+
+ // if (!petSpellId)
+ // {
+ // petSpellId = c->BLESSING_OF_KINGS;
+ // if (!petSpellId)
+ // petSpellId = c->BLESSING_OF_SANCTUARY;
+ // }
+ // }
+ // case CLASS_PRIEST:
+ // case CLASS_MAGE:
+ // spellId = c->BLESSING_OF_WISDOM;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_KINGS;
+ // if (!spellId)
+ // {
+ // spellId = c->BLESSING_OF_SANCTUARY;
+ // if (!spellId)
+ // return false;
+ // }
+ // }
+ // break;
+ //}
+
+ //if (petSpellId == c->BLESSING_OF_MIGHT)
+ // petBigSpellId = c->GREATER_BLESSING_OF_MIGHT;
+ //else if (petSpellId == c->BLESSING_OF_WISDOM)
+ // petBigSpellId = c->GREATER_BLESSING_OF_WISDOM;
+ //else if (petSpellId == c->BLESSING_OF_KINGS)
+ // petBigSpellId = c->GREATER_BLESSING_OF_KINGS;
+ //else if (petSpellId == c->BLESSING_OF_SANCTUARY)
+ // petBigSpellId = c->GREATER_BLESSING_OF_SANCTUARY;
+
+ //if (spellId == c->BLESSING_OF_MIGHT)
+ // bigSpellId = c->GREATER_BLESSING_OF_MIGHT;
+ //else if (spellId == c->BLESSING_OF_WISDOM)
+ // bigSpellId = c->GREATER_BLESSING_OF_WISDOM;
+ //else if (spellId == c->BLESSING_OF_KINGS)
+ // bigSpellId = c->GREATER_BLESSING_OF_KINGS;
+ //else if (spellId == c->BLESSING_OF_SANCTUARY)
+ // bigSpellId = c->GREATER_BLESSING_OF_SANCTUARY;
+
+ //if (bigSpellId && ai->HasSpellReagents(bigSpellId) && ((petSpellId && ai->Buff(petBigSpellId, pet)) || ai->Buff(bigSpellId, target)))
+ // return true;
+ //else
+ // return ( (petSpellId && ai->Buff(petSpellId, target)) || ai->Buff(spellId, target) );
+}
+
+// Match up with "Pull()" below
+bool PlayerbotPaladinAI::CanPull()
+{
+ //if (HAND_OF_RECKONING && !m_bot->HasSpellCooldown(HAND_OF_RECKONING))
+ // return true;
+ //if (EXORCISM && !m_bot->HasSpellCooldown(EXORCISM))
+ // return true;
+
+ return false;
+}
+
+// Match up with "CanPull()" above
+bool PlayerbotPaladinAI::Pull()
+{
+ //if (HAND_OF_RECKONING && m_ai->CastSpell(HAND_OF_RECKONING))
+ // return true;
+ //if (EXORCISM && m_ai->CastSpell(EXORCISM))
+ // return true;
+
+ return false;
+}
+
+bool PlayerbotPaladinAI::CastHoTOnTank()
+{
+ //if (!m_ai) return false;
+
+ //if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false;
+
+ // Paladin: Sheath of Light (with talents), Flash of Light (with Infusion of Light talent and only on a target with the Sacred Shield buff),
+ // Holy Shock (with Tier 8 set bonus)
+ // None of these are HoTs to cast before pulling (I think)
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_pal_ai.h b/src/server/game/AI/PlayerBots/bp_pal_ai.h
new file mode 100644
index 0000000..d2cafb1
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_pal_ai.h
@@ -0,0 +1,208 @@
+#ifndef _PlayerbotPaladinAI_H
+#define _PlayerbotPaladinAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ Combat,
+ Healing
+};
+
+enum PaladinSpells
+{
+ AURA_MASTERY_1 = 31821,
+ AVENGERS_SHIELD_1 = 31935,
+ AVENGING_WRATH_1 = 31884,
+ BEACON_OF_LIGHT_1 = 53563,
+ BLESSING_OF_KINGS_1 = 20217,
+ BLESSING_OF_MIGHT_1 = 19740,
+ BLESSING_OF_SANCTUARY_1 = 20911,
+ BLESSING_OF_WISDOM_1 = 19742,
+ CLEANSE_1 = 4987,
+ CONCENTRATION_AURA_1 = 19746,
+ CONSECRATION_1 = 26573,
+ CRUSADER_AURA_1 = 32223,
+ CRUSADER_STRIKE_1 = 35395,
+ DEVOTION_AURA_1 = 465,
+ DIVINE_FAVOR_1 = 20216,
+ DIVINE_ILLUMINATION_1 = 31842,
+ DIVINE_INTERVENTION_1 = 19752,
+ DIVINE_PLEA_1 = 54428,
+ DIVINE_PROTECTION_1 = 498,
+ DIVINE_SACRIFICE_1 = 64205,
+ DIVINE_SHIELD_1 = 642,
+ DIVINE_STORM_1 = 53385,
+ EXORCISM_1 = 879,
+ FIRE_RESISTANCE_AURA_1 = 19891,
+ FLASH_OF_LIGHT_1 = 19750,
+ FROST_RESISTANCE_AURA_1 = 19888,
+ GREATER_BLESSING_OF_KINGS_1 = 25898,
+ GREATER_BLESSING_OF_MIGHT_1 = 25782,
+ GREATER_BLESSING_OF_SANCTUARY_1 = 25899,
+ GREATER_BLESSING_OF_WISDOM_1 = 25894,
+ HAMMER_OF_JUSTICE_1 = 853,
+ HAMMER_OF_THE_RIGHTEOUS_1 = 53595,
+ HAMMER_OF_WRATH_1 = 24275,
+ HAND_OF_FREEDOM_1 = 1044,
+ HAND_OF_PROTECTION_1 = 1022,
+ HAND_OF_RECKONING_1 = 62124,
+ HAND_OF_SACRIFICE_1 = 6940,
+ HAND_OF_SALVATION_1 = 1038,
+ HOLY_LIGHT_1 = 635,
+ HOLY_SHIELD_1 = 20925,
+ HOLY_SHOCK_1 = 20473,
+ HOLY_WRATH_1 = 2812,
+ JUDGEMENT_OF_JUSTICE_1 = 53407,
+ JUDGEMENT_OF_LIGHT_1 = 20271,
+ JUDGEMENT_OF_WISDOM_1 = 53408,
+ LAY_ON_HANDS_1 = 633,
+ PURIFY_1 = 1152,
+ REDEMPTION_1 = 7328,
+ REPENTANCE_1 = 20066,
+ RETRIBUTION_AURA_1 = 7294,
+ RIGHTEOUS_DEFENSE_1 = 31789,
+ RIGHTEOUS_FURY_1 = 25780,
+ SACRED_SHIELD_1 = 53601,
+ SEAL_OF_COMMAND_1 = 20375,
+ SEAL_OF_CORRUPTION_1 = 53736,
+ SEAL_OF_JUSTICE_1 = 20164,
+ SEAL_OF_LIGHT_1 = 20165,
+ SEAL_OF_RIGHTEOUSNESS_1 = 21084,
+ SEAL_OF_VENGEANCE_1 = 31801,
+ SEAL_OF_WISDOM_1 = 20166,
+ SENSE_UNDEAD_1 = 5502,
+ SHADOW_RESISTANCE_AURA_1 = 19876,
+ SHIELD_OF_RIGHTEOUSNESS_1 = 53600,
+ TURN_EVIL_1 = 10326,
+ //Max rank only
+ ART_OF_WAR_1 = 53488
+};
+//class Player;
+
+class PlayerbotPaladinAI : PlayerbotClassAI
+{
+public:
+ PlayerbotPaladinAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotPaladinAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+ bool Pull();
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // Utility Functions
+ bool CanPull();
+ bool CastHoTOnTank();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // Heals the target based off its hps
+ CombatManeuverReturns HealPlayer(Player* target);
+ Player* GetHealTarget() { return PlayerbotClassAI::GetHealTarget(); }
+
+ //Changes aura according to spec/orders
+ void CheckAuras();
+ //Changes Seal according to spec
+ void CheckSeals();
+
+ static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target);
+
+ // make this public so the static function can access it. Either that or make an accessor function for each
+public:
+ // Retribution
+ uint32 RETRIBUTION_AURA,
+ SEAL_OF_COMMAND,
+ JUDGEMENT_OF_LIGHT,
+ JUDGEMENT_OF_WISDOM,
+ GREATER_BLESSING_OF_WISDOM,
+ GREATER_BLESSING_OF_MIGHT,
+ BLESSING_OF_WISDOM,
+ BLESSING_OF_MIGHT,
+ HAMMER_OF_JUSTICE,
+ RIGHTEOUS_FURY,
+ CRUSADER_AURA,
+ CRUSADER_STRIKE,
+ AVENGING_WRATH,
+ DIVINE_STORM,
+ JUDGEMENT_OF_JUSTICE,
+ ART_OF_WAR;
+
+ // Holy
+ uint32 FLASH_OF_LIGHT,
+ HOLY_LIGHT,
+ DIVINE_SHIELD,
+ HAMMER_OF_WRATH,
+ CONSECRATION,
+ CONCENTRATION_AURA,
+ DIVINE_FAVOR,
+ SACRED_SHIELD,
+ HOLY_SHOCK,
+ HOLY_WRATH,
+ LAY_ON_HANDS,
+ EXORCISM,
+ REDEMPTION,
+ DIVINE_PLEA,
+ SEAL_OF_CORRUPTION,
+ SEAL_OF_JUSTICE,
+ SEAL_OF_LIGHT,
+ SEAL_OF_RIGHTEOUSNESS,
+ SEAL_OF_VENGEANCE,
+ SEAL_OF_WISDOM,
+ PURIFY,
+ CLEANSE;
+
+ // Protection
+ uint32 GREATER_BLESSING_OF_KINGS,
+ BLESSING_OF_KINGS,
+ HAND_OF_PROTECTION,
+ SHADOW_RESISTANCE_AURA,
+ DEVOTION_AURA,
+ FIRE_RESISTANCE_AURA,
+ FROST_RESISTANCE_AURA,
+ DEFENSIVE_STANCE,
+ BERSERKER_STANCE,
+ BATTLE_STANCE,
+ DIVINE_SACRIFICE,
+ DIVINE_PROTECTION,
+ DIVINE_INTERVENTION,
+ HOLY_SHIELD,
+ AVENGERS_SHIELD,
+ RIGHTEOUS_DEFENSE,
+ BLESSING_OF_SANCTUARY,
+ GREATER_BLESSING_OF_SANCTUARY,
+ HAND_OF_SACRIFICE,
+ SHIELD_OF_RIGHTEOUSNESS,
+ HAND_OF_RECKONING,
+ HAMMER_OF_THE_RIGHTEOUS;
+
+ // cannot be protected
+ uint32 FORBEARANCE;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ //Non-Stacking buffs
+ uint32 PRAYER_OF_SHADOW_PROTECTION;
+
+private:
+ uint32 SpellSequence, CombatCounter, HealCounter;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_pri_ai.cpp b/src/server/game/AI/PlayerBots/bp_pri_ai.cpp
new file mode 100644
index 0000000..d3b8734
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_pri_ai.cpp
@@ -0,0 +1,514 @@
+#include "bp_ai.h"
+#include "bp_pri_ai.h"
+#include "SpellAuras.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotPriestAI::PlayerbotPriestAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ RENEW = PlayerbotAI::InitSpell(me, RENEW_1);
+ LESSER_HEAL = PlayerbotAI::InitSpell(me, LESSER_HEAL_1);
+ FLASH_HEAL = PlayerbotAI::InitSpell(me, FLASH_HEAL_1);
+ (FLASH_HEAL > 0) ? FLASH_HEAL : FLASH_HEAL = LESSER_HEAL;
+ HEAL = PlayerbotAI::InitSpell(me, HEAL_1);
+ (HEAL > 0) ? HEAL : HEAL = FLASH_HEAL;
+ GREATER_HEAL = PlayerbotAI::InitSpell(me, GREATER_HEAL_1);
+ (GREATER_HEAL > 0) ? GREATER_HEAL : GREATER_HEAL = HEAL;
+ RESURRECTION = PlayerbotAI::InitSpell(me, RESURRECTION_1);
+ SMITE = PlayerbotAI::InitSpell(me, SMITE_1);
+ MANA_BURN = PlayerbotAI::InitSpell(me, MANA_BURN_1);
+ HOLY_NOVA = PlayerbotAI::InitSpell(me, HOLY_NOVA_1);
+ HOLY_FIRE = PlayerbotAI::InitSpell(me, HOLY_FIRE_1);
+ DESPERATE_PRAYER = PlayerbotAI::InitSpell(me, DESPERATE_PRAYER_1);
+ PRAYER_OF_HEALING = PlayerbotAI::InitSpell(me, PRAYER_OF_HEALING_1);
+ CIRCLE_OF_HEALING = PlayerbotAI::InitSpell(me, CIRCLE_OF_HEALING_1);
+ BINDING_HEAL = PlayerbotAI::InitSpell(me, BINDING_HEAL_1);
+ PRAYER_OF_MENDING = PlayerbotAI::InitSpell(me, PRAYER_OF_MENDING_1);
+ CURE_DISEASE = PlayerbotAI::InitSpell(me, CURE_DISEASE_1);
+
+ // SHADOW
+ FADE = PlayerbotAI::InitSpell(me, FADE_1);
+ SHADOW_WORD_PAIN = PlayerbotAI::InitSpell(me, SHADOW_WORD_PAIN_1);
+ MIND_BLAST = PlayerbotAI::InitSpell(me, MIND_BLAST_1);
+ SCREAM = PlayerbotAI::InitSpell(me, PSYCHIC_SCREAM_1);
+ MIND_FLAY = PlayerbotAI::InitSpell(me, MIND_FLAY_1);
+ DEVOURING_PLAGUE = PlayerbotAI::InitSpell(me, DEVOURING_PLAGUE_1);
+ SHADOW_PROTECTION = PlayerbotAI::InitSpell(me, SHADOW_PROTECTION_1);
+ VAMPIRIC_TOUCH = PlayerbotAI::InitSpell(me, VAMPIRIC_TOUCH_1);
+ PRAYER_OF_SHADOW_PROTECTION = PlayerbotAI::InitSpell(me, PRAYER_OF_SHADOW_PROTECTION_1);
+ SHADOWFIEND = PlayerbotAI::InitSpell(me, SHADOWFIEND_1);
+ MIND_SEAR = PlayerbotAI::InitSpell(me, MIND_SEAR_1);
+ SHADOWFORM = PlayerbotAI::InitSpell(me, SHADOWFORM_1);
+ VAMPIRIC_EMBRACE = PlayerbotAI::InitSpell(me, VAMPIRIC_EMBRACE_1);
+
+ // RANGED COMBAT
+ SHOOT = PlayerbotAI::InitSpell(me, SHOOT_1);
+
+ // DISCIPLINE
+ PENANCE = PlayerbotAI::InitSpell(me, PENANCE_1);
+ INNER_FIRE = PlayerbotAI::InitSpell(me, INNER_FIRE_1);
+ POWER_WORD_SHIELD = PlayerbotAI::InitSpell(me, POWER_WORD_SHIELD_1);
+ POWER_WORD_FORTITUDE = PlayerbotAI::InitSpell(me, POWER_WORD_FORTITUDE_1);
+ PRAYER_OF_FORTITUDE = PlayerbotAI::InitSpell(me, PRAYER_OF_FORTITUDE_1);
+ FEAR_WARD = PlayerbotAI::InitSpell(me, FEAR_WARD_1);
+ DIVINE_SPIRIT = PlayerbotAI::InitSpell(me, DIVINE_SPIRIT_1);
+ PRAYER_OF_SPIRIT = PlayerbotAI::InitSpell(me, PRAYER_OF_SPIRIT_1);
+ MASS_DISPEL = PlayerbotAI::InitSpell(me, MASS_DISPEL_1);
+ POWER_INFUSION = PlayerbotAI::InitSpell(me, POWER_INFUSION_1);
+ INNER_FOCUS = PlayerbotAI::InitSpell(me, INNER_FOCUS_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_MANA_CLASSES);
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_PRIEST); // draenei
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ SHADOWMELD = PlayerbotAI::InitSpell(me, SHADOWMELD_ALL);
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+}
+
+PlayerbotPriestAI::~PlayerbotPriestAI() {}
+
+CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder())
+ // return HealPlayer(GetHealTarget());
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //if (m_ai->IsHealer())
+ //{
+ // // TODO: This must be done with toggles: FullHealth allowed
+ // Unit* healTarget = GetHealTarget(JOB_TANK);
+ // // This is cast on a target, which activates (and switches to another target within the group) upon receiving+healing damage
+ // // Mana efficient even at one use
+ // if (healTarget && PRAYER_OF_MENDING > 0 && !healTarget->HasAura(PRAYER_OF_MENDING, EFFECT_0) && CastSpell(PRAYER_OF_MENDING, healTarget) & RETURN_CONTINUE)
+ // return RETURN_FINISHED_FIRST_MOVES;
+ //}
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotPriestAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //float dist = pTarget->GetCombatReach();
+ //uint32 spec = m_bot->GetSpec();
+
+ //if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && dist > ATTACK_DISTANCE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //// if in melee range OR can't shoot OR have no ranged (wand) equipped
+ //else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE
+ // && (SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true))
+ // && !m_ai->IsHealer())
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ ////Used to determine if this bot is highest on threat
+ //Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //if (newTarget) // TODO: && party has a tank
+ //{
+ // if (newTarget && FADE > 0 && !m_bot->HasAura(FADE, EFFECT_0))
+ // {
+ // if (CastSpell(FADE, m_bot))
+ // {
+ // //m_ai->TellMaster("I'm casting fade.");
+ // return RETURN_CONTINUE;
+ // }
+ // else
+ // m_ai->TellMaster("I have AGGRO.");
+ // }
+
+ // // Heal myself
+ // // TODO: move to HealTarget code
+ // // TODO: you forgot to check for the 'temporarily immune to PW:S because you only just got it cast on you' effect
+ // // - which is different effect from the actual shield.
+ // if (m_bot->GetHealthPct() < 25 && POWER_WORD_SHIELD > 0 && !m_bot->HasAura(POWER_WORD_SHIELD, EFFECT_0))
+ // {
+ // if (CastSpell(POWER_WORD_SHIELD) & RETURN_CONTINUE)
+ // {
+ // //m_ai->TellMaster("I'm casting PW:S on myself.");
+ // return RETURN_CONTINUE;
+ // }
+ // else if (m_ai->IsHealer()) // Even if any other RETURN_ANY_OK - aside from RETURN_CONTINUE
+ // m_ai->TellMaster("Your healer's about TO DIE. HELP ME.");
+ // }
+ // if (m_bot->GetHealthPct() < 35 && DESPERATE_PRAYER > 0 && CastSpell(DESPERATE_PRAYER, m_bot) & RETURN_CONTINUE)
+ // {
+ // //m_ai->TellMaster("I'm casting desperate prayer.");
+ // return RETURN_CONTINUE;
+ // }
+
+ // // Already healed self or tank. If healer, do nothing else to anger mob.
+ // if (m_ai->IsHealer())
+ // return RETURN_NO_ACTION_OK; // In a sense, mission accomplished.
+
+ // // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on.
+ // if (newTarget->GetHealthPct() > 25)
+ // {
+ // // If elite, do nothing and pray tank gets aggro off you
+ // // TODO: Is there an IsElite function? If so, find it and insert.
+ // //if (newTarget->IsElite())
+ // // return;
+
+ // // Not an elite. You could insert PSYCHIC SCREAM here but in any PvE situation that's 90-95% likely
+ // // to worsen the situation for the group. ... So please don't.
+ // return CastSpell(SHOOT, pTarget);
+ // }
+ //}
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // ... Certainly could be very detrimental to a shadow priest
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(m_bot) & RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+ //}
+
+ //// Do damage tweaking for healers here
+ //if (m_ai->IsHealer())
+ //{
+ // // TODO: elite exception
+ // //if (Any target is an Elite)
+ // // return;
+
+ // return CastSpell(SHOOT, pTarget);
+ //}
+
+ //// Damage Spells
+ //switch (spec)
+ //{
+ // case PRIEST_SPEC_HOLY:
+ // if (HOLY_FIRE > 0 && !pTarget->HasAura(HOLY_FIRE, EFFECT_0) && CastSpell(HOLY_FIRE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SMITE > 0 && CastSpell(SMITE, pTarget))
+ // return RETURN_CONTINUE;
+ // //if (HOLY_NOVA > 0 && dist <= ATTACK_DISTANCE && m_ai->CastSpell(HOLY_NOVA))
+ // // return RETURN_CONTINUE;
+ // break;
+
+ // case PRIEST_SPEC_SHADOW:
+ // if (DEVOURING_PLAGUE > 0 && !pTarget->HasAura(DEVOURING_PLAGUE, EFFECT_0) && CastSpell(DEVOURING_PLAGUE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (VAMPIRIC_TOUCH > 0 && !pTarget->HasAura(VAMPIRIC_TOUCH, EFFECT_0) && CastSpell(VAMPIRIC_TOUCH, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHADOW_WORD_PAIN > 0 && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_0) && CastSpell(SHADOW_WORD_PAIN, pTarget))
+ // return RETURN_CONTINUE;
+ // if (MIND_BLAST > 0 && (!m_bot->HasSpellCooldown(MIND_BLAST)) && CastSpell(MIND_BLAST, pTarget))
+ // return RETURN_CONTINUE;
+ // if (MIND_FLAY > 0 && CastSpell(MIND_FLAY, pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(3);
+ // return RETURN_CONTINUE;
+ // }
+ // if (SHADOWFIEND > 0 && !m_bot->GetPet() && CastSpell(SHADOWFIEND))
+ // return RETURN_CONTINUE;
+ // /*if (MIND_SEAR > 0 && m_ai->GetAttackerCount() >= 3 && CastSpell(MIND_SEAR, pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(5);
+ // return RETURN_CONTINUE;
+ // }*/
+ // if (SHADOWFORM == 0 && MIND_FLAY == 0 && SMITE > 0 && CastSpell(SMITE, pTarget)) // low levels
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case PRIEST_SPEC_DISCIPLINE:
+ // if (POWER_INFUSION > 0 && CastSpell(POWER_INFUSION, GetMaster())) // TODO: just master?
+ // return RETURN_CONTINUE;
+ // if (INNER_FOCUS > 0 && !m_bot->HasAura(INNER_FOCUS, EFFECT_0) && CastSpell(INNER_FOCUS, m_bot))
+ // return RETURN_CONTINUE;
+ // if (PENANCE > 0 && CastSpell(PENANCE))
+ // return RETURN_CONTINUE;
+ // if (SMITE > 0 && CastSpell(SMITE, pTarget))
+ // return RETURN_CONTINUE;
+ // break;
+ //}
+
+ //// No spec due to low level OR no spell found yet
+ //if (MIND_BLAST > 0 && (!m_bot->HasSpellCooldown(MIND_BLAST)) && CastSpell(MIND_BLAST, pTarget))
+ // return RETURN_CONTINUE;
+ //if (SHADOW_WORD_PAIN > 0 && !pTarget->HasAura(SHADOW_WORD_PAIN, EFFECT_0) && CastSpell(SHADOW_WORD_PAIN, pTarget))
+ // return RETURN_CONTINUE;
+ //if (MIND_FLAY > 0 && CastSpell(MIND_FLAY, pTarget))
+ //{
+ // //m_ai->SetIgnoreUpdateTime(3);
+ // return RETURN_CONTINUE;
+ //}
+ //if (SHADOWFORM == 0 && SMITE > 0 && CastSpell(SMITE, pTarget))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_OK;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotPriestAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // // TODO: spec tweaking
+ // if (m_ai->HasAura(SCREAM, *pTarget) && m_bot->GetHealthPct() < 60 && HEAL && CastSpell(HEAL) & RETURN_ANY_OK)
+ // return RETURN_CONTINUE;
+
+ // if (SHADOW_WORD_PAIN && CastSpell(SHADOW_WORD_PAIN) & RETURN_ANY_OK) // TODO: Check whether enemy has it active yet
+ // return RETURN_CONTINUE;
+
+ // if (m_bot->GetHealthPct() < 80 && RENEW && CastSpell(RENEW) & RETURN_ANY_OK) // TODO: Check whether you have renew active on you
+ // return RETURN_CONTINUE;
+
+ // if (pTarget->GetCombatReach() <= 5 && SCREAM && CastSpell(SCREAM) & RETURN_ANY_OK) // TODO: Check for cooldown
+ // return RETURN_CONTINUE;
+
+ // if (MIND_BLAST && CastSpell(MIND_BLAST) & RETURN_ANY_OK) // TODO: Check for cooldown
+ // return RETURN_CONTINUE;
+
+ // if (m_bot->GetHealthPct() < 50 && GREATER_HEAL && CastSpell(GREATER_HEAL) & RETURN_ANY_OK)
+ // return RETURN_CONTINUE;
+
+ // if (SMITE && CastSpell(SMITE) & RETURN_ANY_OK)
+ // return RETURN_CONTINUE;
+
+ // m_ai->TellMaster("Couldn't find a spell to cast while dueling");
+ // default:
+ // break;
+ //}
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+CombatManeuverReturns PlayerbotPriestAI::HealPlayer(Player* target)
+{
+ //CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target);
+ //if (r != RETURN_NO_ACTION_OK)
+ // return r;
+
+ //if (!target->isAlive())
+ //{
+ // if (RESURRECTION && m_ai->CastSpell(RESURRECTION, *target))
+ // {
+ // std::string msg = "Resurrecting ";
+ // msg += target->GetName();
+ // m_bot->Say(msg, LANG_UNIVERSAL);
+ // return RETURN_CONTINUE;
+ // }
+ // return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM
+ //}
+
+ //if (CURE_DISEASE > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0)
+ //{
+ // uint32 dispelMask = SpellInfo::GetDispelMask(DISPEL_DISEASE);
+ // Unit::AuraMap const& auras = target->GetOwnedAuras();
+ // for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
+ // {
+ // Aura *holder = itr->second;
+ // if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask)
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_DISEASE)
+ // {
+ // m_ai->CastSpell(CURE_DISEASE, *target);
+ // return RETURN_CONTINUE;
+ // }
+ // }
+ // }
+ //}
+
+ //uint8 hp = target->GetHealthPct();
+ //uint8 hpSelf = m_bot->GetHealthPct();
+
+ //if (hp >= 90)
+ // return RETURN_NO_ACTION_OK;
+
+ //// TODO: Integrate shield here
+ //if (hp < 35 && FLASH_HEAL > 0 && m_ai->CastSpell(FLASH_HEAL, *target))
+ // return RETURN_CONTINUE;
+ //if (hp < 45 && GREATER_HEAL > 0 && m_ai->CastSpell(GREATER_HEAL, *target))
+ // return RETURN_CONTINUE;
+ //// Heals target AND self for equal amount
+ //if (hp < 60 && hpSelf < 80 && BINDING_HEAL > 0 && m_ai->CastSpell(BINDING_HEAL, *target))
+ // return RETURN_CONTINUE;
+ //if (hp < 60 && PRAYER_OF_MENDING > 0 && !target->HasAura(PRAYER_OF_MENDING, EFFECT_0) && CastSpell(PRAYER_OF_MENDING, target))
+ // return RETURN_FINISHED_FIRST_MOVES;
+ //if (hp < 60 && HEAL > 0 && m_ai->CastSpell(HEAL, *target))
+ // return RETURN_CONTINUE;
+ //if (hp < 90 && RENEW > 0 && !target->HasAura(RENEW) && m_ai->CastSpell(RENEW, *target))
+ // return RETURN_CONTINUE;
+
+ // Group heal. Not really useful until a group check is available?
+ //if (hp < 40 && PRAYER_OF_HEALING > 0 && m_ai->CastSpell(PRAYER_OF_HEALING, *target) & RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+ // Group heal. Not really useful until a group check is available?
+ //if (hp < 50 && CIRCLE_OF_HEALING > 0 && m_ai->CastSpell(CIRCLE_OF_HEALING, *target) & RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_OK;
+} // end HealTarget
+
+void PlayerbotPriestAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //if (!m_bot->isAlive() || m_bot->IsInDuel()) return;
+
+ //uint32 spec = m_bot->GetSpec();
+
+ //// selfbuff goes first
+ //if (m_ai->SelfBuff(INNER_FIRE))
+ // return;
+
+ //// Revive
+ //if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE)
+ // return;
+
+ //// After revive
+ //if (spec == PRIEST_SPEC_SHADOW && SHADOWFORM > 0)
+ // m_ai->SelfBuff(SHADOWFORM);
+ //if (VAMPIRIC_EMBRACE > 0)
+ // m_ai->SelfBuff(VAMPIRIC_EMBRACE);
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(m_bot) & RETURN_CONTINUE)
+ // return;// RETURN_CONTINUE;
+ //}
+
+ //// Buff
+ //if (m_bot->GetGroup())
+ //{
+ // if (PRAYER_OF_FORTITUDE && m_ai->HasSpellReagents(PRAYER_OF_FORTITUDE) && m_ai->Buff(PRAYER_OF_FORTITUDE, m_bot))
+ // return;
+
+ // if (PRAYER_OF_SPIRIT && m_ai->HasSpellReagents(PRAYER_OF_SPIRIT) && m_ai->Buff(PRAYER_OF_SPIRIT, m_bot))
+ // return;
+
+ // if (PRAYER_OF_SHADOW_PROTECTION && m_ai->HasSpellReagents(PRAYER_OF_SHADOW_PROTECTION) && m_ai->Buff(PRAYER_OF_SHADOW_PROTECTION, m_bot))
+ // return;
+ //}
+ //if (Buff(&PlayerbotPriestAI::BuffHelper, POWER_WORD_FORTITUDE))
+ // return;
+ //if (Buff(&PlayerbotPriestAI::BuffHelper, DIVINE_SPIRIT, (JOB_ALL | JOB_MANAONLY)))
+ // return;
+ //if (Buff(&PlayerbotPriestAI::BuffHelper, SHADOW_PROTECTION, (JOB_TANK | JOB_HEAL) ))
+ // return;
+
+ //// hp/mana check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage())
+ // return;
+} // end DoNonCombatActions
+
+// TODO: this and mage's BuffHelper are identical and thus could probably go in PlayerbotClassAI.cpp somewhere
+bool PlayerbotPriestAI::BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target)
+{
+ //if (!ai) return false;
+ //if (spellId == 0) return false;
+ //if (!target) return false;
+
+ //Pet * pet = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer()->GetPet() : NULL;
+ //if (pet && !pet->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE) && ai->Buff(spellId, pet))
+ // return true;
+
+ //if (ai->Buff(spellId, target))
+ // return true;
+
+ return false;
+}
+
+bool PlayerbotPriestAI::CastHoTOnTank()
+{
+ //if (!m_ai) return false;
+
+ //if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false;
+
+ //// Priest HoTs: Renew, Penance (with talents, channeled)
+ //if (RENEW)
+ // return (RETURN_CONTINUE & CastSpell(RENEW, m_ai->GetGroupTank()));
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_pri_ai.h b/src/server/game/AI/PlayerBots/bp_pri_ai.h
new file mode 100644
index 0000000..33553d5
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_pri_ai.h
@@ -0,0 +1,166 @@
+#ifndef _PLAYERBOTPRIESTAI_H
+#define _PLAYERBOTPRIESTAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ SPELL_HOLY,
+ SPELL_SHADOWMAGIC,
+ SPELL_DISCIPLINE
+};
+
+enum PriestSpells
+{
+ ABOLISH_DISEASE_1 = 552,
+ BINDING_HEAL_1 = 32546,
+ BLESSED_HEALING_1 = 70772,
+ CIRCLE_OF_HEALING_1 = 34861,
+ CURE_DISEASE_1 = 528,
+ DESPERATE_PRAYER_1 = 19236,
+ DEVOURING_PLAGUE_1 = 2944,
+ DISPEL_MAGIC_1 = 527,
+ DISPERSION_1 = 47585,
+ DIVINE_HYMN_1 = 64843,
+ DIVINE_SPIRIT_1 = 14752,
+ FADE_1 = 586,
+ FEAR_WARD_1 = 6346,
+ FLASH_HEAL_1 = 2061,
+ GREATER_HEAL_1 = 2060,
+ GUARDIAN_SPIRIT_1 = 47788,
+ HEAL_1 = 2054,
+ HOLY_FIRE_1 = 14914,
+ HOLY_NOVA_1 = 15237,
+ HYMN_OF_HOPE_1 = 64901,
+ INNER_FIRE_1 = 588,
+ INNER_FOCUS_1 = 14751,
+ LESSER_HEAL_1 = 2050,
+ LEVITATE_1 = 1706,
+ LIGHTWELL_1 = 724,
+ MANA_BURN_1 = 8129,
+ MASS_DISPEL_1 = 32375,
+ MIND_BLAST_1 = 8092,
+ MIND_CONTROL_1 = 605,
+ MIND_FLAY_1 = 15407,
+ MIND_SEAR_1 = 48045,
+ MIND_SOOTHE_1 = 453,
+ MIND_VISION_1 = 2096,
+ PAIN_SUPPRESSION_1 = 33206,
+ PENANCE_1 = 47540,
+ POWER_INFUSION_1 = 10060,
+ POWER_WORD_FORTITUDE_1 = 1243,
+ POWER_WORD_SHIELD_1 = 17,
+ PRAYER_OF_FORTITUDE_1 = 21562,
+ PRAYER_OF_HEALING_1 = 596,
+ PRAYER_OF_MENDING_1 = 33076,
+ PRAYER_OF_SHADOW_PROTECTION_1 = 27683,
+ PRAYER_OF_SPIRIT_1 = 27681,
+ PSYCHIC_HORROR_1 = 64044,
+ PSYCHIC_SCREAM_1 = 8122,
+ RENEW_1 = 139,
+ RESURRECTION_1 = 2006,
+ SHACKLE_UNDEAD_1 = 9484,
+ SHADOW_PROTECTION_1 = 976,
+ SHADOW_WORD_DEATH_1 = 32379,
+ SHADOW_WORD_PAIN_1 = 589,
+ SHADOWFIEND_1 = 34433,
+ SHADOWFORM_1 = 15473,
+ SHOOT_1 = 5019,
+ SILENCE_1 = 15487,
+ SMITE_1 = 585,
+ VAMPIRIC_EMBRACE_1 = 15286,
+ VAMPIRIC_TOUCH_1 = 34914
+};
+//class Player;
+
+class PlayerbotPriestAI : PlayerbotClassAI
+{
+public:
+ PlayerbotPriestAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotPriestAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // Utility Functions
+ bool CastHoTOnTank();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = NULL) { return CastSpellWand(nextAction, pTarget, SHOOT); }
+
+ // Heals the target based off its hps
+ CombatManeuverReturns HealPlayer(Player* target);
+
+ static bool BuffHelper(PlayerbotAI* ai, uint32 spellId, Unit *target);
+
+ // holy
+ uint32 BINDING_HEAL,
+ CIRCLE_OF_HEALING,
+ CLEARCASTING,
+ DESPERATE_PRAYER,
+ FLASH_HEAL,
+ GREATER_HEAL,
+ HEAL,
+ HOLY_FIRE,
+ HOLY_NOVA,
+ LESSER_HEAL,
+ MANA_BURN,
+ PRAYER_OF_HEALING,
+ PRAYER_OF_MENDING,
+ RENEW,
+ RESURRECTION,
+ SMITE,
+ CURE_DISEASE;
+ // ranged
+ uint32 SHOOT;
+
+ // shadowmagic
+ uint32 FADE,
+ SHADOW_WORD_PAIN,
+ MIND_BLAST,
+ SCREAM,
+ MIND_FLAY,
+ DEVOURING_PLAGUE,
+ SHADOW_PROTECTION,
+ VAMPIRIC_TOUCH,
+ PRAYER_OF_SHADOW_PROTECTION,
+ SHADOWFIEND,
+ MIND_SEAR,
+ SHADOWFORM,
+ VAMPIRIC_EMBRACE;
+
+ // discipline
+ uint32 POWER_WORD_SHIELD,
+ INNER_FIRE,
+ POWER_WORD_FORTITUDE,
+ PRAYER_OF_FORTITUDE,
+ FEAR_WARD,
+ POWER_INFUSION,
+ MASS_DISPEL,
+ PENANCE,
+ DIVINE_SPIRIT,
+ PRAYER_OF_SPIRIT,
+ INNER_FOCUS;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_rog_ai.cpp b/src/server/game/AI/PlayerBots/bp_rog_ai.cpp
new file mode 100644
index 0000000..82ec574
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_rog_ai.cpp
@@ -0,0 +1,408 @@
+/*
+ Name : PlayerbotRogueAI.cpp
+ Complete: maybe around 28%
+ Author : Natsukawa
+ Version : 0.37
+ */
+#include "bp_ai.h"
+#include "bp_mgr.h"
+#include "bp_rog_ai.h"
+#include "Player.h"
+
+PlayerbotRogueAI::PlayerbotRogueAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ SINISTER_STRIKE = PlayerbotAI::InitSpell(me, SINISTER_STRIKE_1);
+ BACKSTAB = PlayerbotAI::InitSpell(me, BACKSTAB_1);
+ KICK = PlayerbotAI::InitSpell(me, KICK_1);
+ FEINT = PlayerbotAI::InitSpell(me, FEINT_1);
+ FAN_OF_KNIVES = PlayerbotAI::InitSpell(me, FAN_OF_KNIVES_1);
+ GOUGE = PlayerbotAI::InitSpell(me, GOUGE_1);
+ SPRINT = PlayerbotAI::InitSpell(me, SPRINT_1);
+
+ SHADOWSTEP = PlayerbotAI::InitSpell(me, SHADOWSTEP_1);
+ STEALTH = PlayerbotAI::InitSpell(me, STEALTH_1);
+ VANISH = PlayerbotAI::InitSpell(me, VANISH_1);
+ EVASION = PlayerbotAI::InitSpell(me, EVASION_1);
+ CLOAK_OF_SHADOWS = PlayerbotAI::InitSpell(me, CLOAK_OF_SHADOWS_1);
+ HEMORRHAGE = PlayerbotAI::InitSpell(me, HEMORRHAGE_1);
+ GHOSTLY_STRIKE = PlayerbotAI::InitSpell(me, GHOSTLY_STRIKE_1);
+ SHADOW_DANCE = PlayerbotAI::InitSpell(me, SHADOW_DANCE_1);
+ BLIND = PlayerbotAI::InitSpell(me, BLIND_1);
+ DISTRACT = PlayerbotAI::InitSpell(me, DISTRACT_1);
+ PREPARATION = PlayerbotAI::InitSpell(me, PREPARATION_1);
+ PREMEDITATION = PlayerbotAI::InitSpell(me, PREMEDITATION_1);
+ PICK_POCKET = PlayerbotAI::InitSpell(me, PICK_POCKET_1);
+
+ EVISCERATE = PlayerbotAI::InitSpell(me, EVISCERATE_1);
+ KIDNEY_SHOT = PlayerbotAI::InitSpell(me, KIDNEY_SHOT_1);
+ SLICE_DICE = PlayerbotAI::InitSpell(me, SLICE_AND_DICE_1);
+ GARROTE = PlayerbotAI::InitSpell(me, GARROTE_1);
+ EXPOSE_ARMOR = PlayerbotAI::InitSpell(me, EXPOSE_ARMOR_1);
+ RUPTURE = PlayerbotAI::InitSpell(me, RUPTURE_1);
+ DISMANTLE = PlayerbotAI::InitSpell(me, DISMANTLE_1);
+ CHEAP_SHOT = PlayerbotAI::InitSpell(me, CHEAP_SHOT_1);
+ AMBUSH = PlayerbotAI::InitSpell(me, AMBUSH_1);
+ MUTILATE = PlayerbotAI::InitSpell(me, MUTILATE_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_ROGUE);
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ ESCAPE_ARTIST = PlayerbotAI::InitSpell(me, ESCAPE_ARTIST_ALL); // gnome
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ SHADOWMELD = PlayerbotAI::InitSpell(me, SHADOWMELD_ALL);
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_MELEE_CLASSES); // orc
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+}
+
+PlayerbotRogueAI::~PlayerbotRogueAI() {}
+
+CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuverPVE(Unit *pTarget)
+{
+ //if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_0) && m_ai->CastSpell(STEALTH, *m_bot))
+ //{
+ // m_bot->AddUnitState(UNIT_STATE_CHASE); // ensure that the bot does not use MoveChase(), as this doesn't seem to work with STEALTH
+ // return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth
+ //}
+ //else if (m_bot->HasAura(STEALTH, EFFECT_0))
+ //{
+ // m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); // TODO: this isn't the place for movement code, is it?
+ // return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth
+ //}
+
+ // Not in stealth, can't cast stealth; Off to DoNextCombatManeuver
+ return RETURN_NO_ACTION_OK;
+}
+
+// TODO: blatant copy of PVE for now, please PVP-port it
+CombatManeuverReturns PlayerbotRogueAI::DoFirstCombatManeuverPVP(Unit *pTarget)
+{
+ //if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_0) && m_ai->CastSpell(STEALTH, *m_bot))
+ //{
+ // m_bot->AddUnitState(UNIT_STATE_CHASE); // ensure that the bot does not use MoveChase(), as this doesn't seem to work with STEALTH
+ // return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth
+ //}
+ //else if (m_bot->HasAura(STEALTH, EFFECT_0))
+ //{
+ // m_bot->GetMotionMaster()->MoveFollow(pTarget, 4.5f, m_bot->GetOrientation()); // TODO: this isn't the place for movement code, is it?
+ // return RETURN_FINISHED_FIRST_MOVES; // DoNextCombatManeuver handles active stealth
+ //}
+
+ // Not in stealth, can't cast stealth; Off to DoNextCombatManeuver
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //if (!pTarget) return RETURN_NO_ACTION_ERROR;
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //Unit* pVictim = pTarget->getVictim();
+ //float fTargetDist = pTarget->GetCombatReach();
+
+ //// TODO: make this work better...
+ ///*if (pVictim)
+ // {
+ // if( pVictim!=m_bot && !m_bot->HasUnitState(UNIT_STATE_FOLLOW) && !pTarget->isInBackInMap(m_bot,10) ) {
+ // m_ai->TellMaster( "getting behind target" );
+ // m_bot->GetMotionMaster()->Clear( true );
+ // m_bot->GetMotionMaster()->MoveFollow( pTarget, 1, 2*M_PI );
+ // }
+ // else if( pVictim==m_bot && m_bot->HasUnitState(UNIT_STATE_FOLLOW) )
+ // {
+ // m_ai->TellMaster( "chasing attacking target" );
+ // m_bot->GetMotionMaster()->Clear( true );
+ // m_bot->GetMotionMaster()->MoveChase( pTarget );
+ // }
+ // }*/
+
+ //// Rogue like behaviour ^^
+ ///*if (VANISH > 0 && GetMaster()->isDead()) { //Causes the server to crash :( removed for now.
+ // m_bot->AttackStop();
+ // m_bot->RemoveAllAttackers();
+ // m_ai->CastSpell(VANISH);
+ // //m_bot->RemoveAllSpellCooldown();
+ // m_ai->TellMaster("AttackStop, CombatStop, Vanish");
+ //}*/
+
+ //// decide what to do:
+ //if (pVictim == m_bot && CLOAK_OF_SHADOWS > 0 && m_bot->HasAura(SPELL_AURA_PERIODIC_DAMAGE) && !m_bot->HasAura(CLOAK_OF_SHADOWS, EFFECT_0) && m_ai->CastSpell(CLOAK_OF_SHADOWS))
+ //{
+ // if (m_ai->GetManager()->m_confDebugWhisper)
+ // m_ai->TellMaster("CoS!");
+ // return RETURN_CONTINUE;
+ //}
+ //else if (m_bot->HasAura(STEALTH, EFFECT_0))
+ // SpellSequence = RogueStealth;
+ //else if (pTarget->IsNonMeleeSpellCasted(true))
+ // SpellSequence = RogueSpellPreventing;
+ //else if (pVictim == m_bot && m_bot->GetHealthPct() < 40)
+ // SpellSequence = RogueThreat;
+ //else
+ // SpellSequence = RogueCombat;
+
+ //// we fight in melee, target is not in range, skip the next part!
+ //if (fTargetDist > ATTACK_DISTANCE)
+ // return RETURN_CONTINUE;
+
+ //std::ostringstream out;
+ //switch (SpellSequence)
+ //{
+ // case RogueStealth:
+ // if (PICK_POCKET > 0 && (pTarget->GetCreatureTypeMask() & CREATURE_TYPEMASK_HUMANOID_OR_UNDEAD) != 0 &&
+ // !((Creature *) pTarget)->lootForPickPocketed && m_ai->CastSpell(PICK_POCKET, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (PREMEDITATION > 0 && m_ai->CastSpell(PREMEDITATION, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (AMBUSH > 0 && m_ai->CastSpell(AMBUSH, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_0) && m_ai->CastSpell(CHEAP_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (GARROTE > 0 && m_ai->CastSpell(GARROTE, *pTarget))
+ // return RETURN_CONTINUE;
+
+ // // No appropriate action found, remove stealth
+ // m_bot->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
+ // return RETURN_CONTINUE;
+
+ // case RogueThreat:
+ // if (GOUGE > 0 && !pTarget->HasAura(GOUGE, EFFECT_0) && m_ai->CastSpell(GOUGE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (EVASION > 0 && m_bot->GetHealthPct() <= 35 && !m_bot->HasAura(EVASION, EFFECT_0) && m_ai->CastSpell(EVASION))
+ // return RETURN_CONTINUE;
+ // if (BLIND > 0 && m_bot->GetHealthPct() <= 30 && !pTarget->HasAura(BLIND, EFFECT_0) && m_ai->CastSpell(BLIND, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (FEINT > 0 && m_bot->GetHealthPct() <= 25 && m_ai->CastSpell(FEINT))
+ // return RETURN_CONTINUE;
+ // if (VANISH > 0 && m_bot->GetHealthPct() <= 20 && !m_bot->HasAura(FEINT, EFFECT_0) && m_ai->CastSpell(VANISH))
+ // return RETURN_CONTINUE;
+ // if (PREPARATION > 0 && m_ai->CastSpell(PREPARATION))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_NIGHTELF && m_bot->GetHealthPct() <= 15 && !m_bot->HasAura(SHADOWMELD, EFFECT_0) && m_ai->CastSpell(SHADOWMELD, *m_bot))
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case RogueSpellPreventing:
+ // if (KIDNEY_SHOT > 0 && m_bot->GetComboPoints() >= 2 && m_ai->CastSpell(KIDNEY_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // else if (KICK > 0 && m_ai->CastSpell(KICK, *pTarget))
+ // return RETURN_CONTINUE;
+ // // break; // No action? Go combat!
+
+ // case RogueCombat:
+ // default:
+ // if (m_bot->GetComboPoints() >= 5)
+ // {
+ // // wait for energy
+ // if (m_bot->GetPower(POWER_ENERGY) < 25 && (KIDNEY_SHOT || SLICE_DICE || EXPOSE_ARMOR))
+ // return RETURN_NO_ACTION_OK;
+
+ // switch (pTarget->getClass())
+ // {
+ // case CLASS_SHAMAN:
+ // if (KIDNEY_SHOT > 0 && m_ai->CastSpell(KIDNEY_SHOT, *pTarget)) // 25 energy (checked above)
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case CLASS_WARLOCK:
+ // case CLASS_HUNTER:
+ // if (SLICE_DICE > 0 && m_ai->CastSpell(SLICE_DICE, *pTarget)) // 25 energy (checked above)
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case CLASS_WARRIOR:
+ // case CLASS_PALADIN:
+ // case CLASS_DEATH_KNIGHT:
+ // if (EXPOSE_ARMOR > 0 && !pTarget->HasAura(EXPOSE_ARMOR, EFFECT_0) && m_ai->CastSpell(EXPOSE_ARMOR, *pTarget)) // 25 energy (checked above)
+ // return RETURN_CONTINUE;
+ // break;
+
+
+ // case CLASS_MAGE:
+ // case CLASS_PRIEST:
+ // if (RUPTURE > 0 && m_ai->CastSpell(RUPTURE, *pTarget)) // 25 energy (checked above)
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case CLASS_ROGUE:
+ // case CLASS_DRUID:
+ // default:
+ // break; // fall through to below
+ // }
+
+ // // default combo action for rogue/druid or if other combo action is unavailable/failed
+ // // wait for energy
+ // if (m_bot->GetPower(POWER_ENERGY) < 35 && EVISCERATE)
+ // return RETURN_NO_ACTION_OK;
+ // if (EVISCERATE > 0 && m_ai->CastSpell(EVISCERATE, *pTarget))
+ // return RETURN_CONTINUE;
+
+ // // failed for some (non-energy related) reason, fall through to normal attacks to maximize DPS
+ // }
+
+ // if (SHADOW_DANCE > 0 && !m_bot->HasAura(SHADOW_DANCE, EFFECT_0) && m_ai->CastSpell(SHADOW_DANCE, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (CHEAP_SHOT > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_0) && !pTarget->HasAura(CHEAP_SHOT, EFFECT_0) && m_ai->CastSpell(CHEAP_SHOT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (AMBUSH > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_0) && m_ai->CastSpell(AMBUSH, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (GARROTE > 0 && m_bot->HasAura(SHADOW_DANCE, EFFECT_0) && m_ai->CastSpell(GARROTE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (BACKSTAB > 0 && pTarget->isInBackInMap(m_bot, 1) && m_ai->CastSpell(BACKSTAB, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (MUTILATE > 0 && m_ai->CastSpell(MUTILATE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SINISTER_STRIKE > 0 && m_ai->CastSpell(SINISTER_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (GHOSTLY_STRIKE > 0 && m_ai->CastSpell(GHOSTLY_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HEMORRHAGE > 0 && m_ai->CastSpell(HEMORRHAGE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (DISMANTLE > 0 && !pTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED) && m_ai->CastSpell(DISMANTLE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHADOWSTEP > 0 && m_ai->CastSpell(SHADOWSTEP, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_BLOODELF && !pTarget->HasAura(ARCANE_TORRENT, EFFECT_0) && m_ai->CastSpell(ARCANE_TORRENT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if ((m_bot->getRace() == RACE_HUMAN && (m_bot->HasUnitState(UNIT_STATE_STUNNED)) || m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || (m_bot->HasAuraType(SPELL_AURA_MOD_CHARM)) && m_ai->CastSpell(EVERY_MAN_FOR_HIMSELF, *m_bot)))
+ // return RETURN_CONTINUE;
+ // if ((m_bot->getRace() == RACE_UNDEAD_PLAYER && (m_bot->HasAuraType(SPELL_AURA_MOD_FEAR)) || (m_bot->HasAuraType(SPELL_AURA_MOD_CHARM)) && m_ai->CastSpell(WILL_OF_THE_FORSAKEN, *m_bot)))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && m_ai->CastSpell(STONEFORM, *m_bot))
+ // return RETURN_CONTINUE;
+ // if ((m_bot->getRace() == RACE_GNOME && (m_bot->HasUnitState(UNIT_STATE_STUNNED)) || (m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED)) && m_ai->CastSpell(ESCAPE_ARTIST, *m_bot)))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_0) && m_ai->CastSpell(BLOOD_FURY, *m_bot))
+ // return RETURN_CONTINUE;
+ // else if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_0) && m_ai->CastSpell(BERSERKING, *m_bot))
+ // return RETURN_CONTINUE;
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_OK;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotRogueAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(SINISTER_STRIKE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+void PlayerbotRogueAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //// remove stealth
+ //if (m_bot->HasAura(STEALTH))
+ // m_bot->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
+
+ //// hp check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage(false))
+ // return;
+
+ //// Search and apply poisons to weapons
+ //// Mainhand ...
+ //Item * poison, * weapon;
+ //weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
+ //if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0)
+ //{
+ // poison = m_ai->FindConsumable(INSTANT_POISON_DISPLAYID);
+ // if (!poison)
+ // poison = m_ai->FindConsumable(WOUND_POISON_DISPLAYID);
+ // if (!poison)
+ // poison = m_ai->FindConsumable(DEADLY_POISON_DISPLAYID);
+ // if (poison)
+ // {
+ // m_ai->UseItem(poison, EQUIPMENT_SLOT_MAINHAND);
+ // //m_ai->SetIgnoreUpdateTime(5);
+ // }
+ //}
+ ////... and offhand
+ //weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
+ //if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0)
+ //{
+ // poison = m_ai->FindConsumable(DEADLY_POISON_DISPLAYID);
+ // if (!poison)
+ // poison = m_ai->FindConsumable(WOUND_POISON_DISPLAYID);
+ // if (!poison)
+ // poison = m_ai->FindConsumable(INSTANT_POISON_DISPLAYID);
+ // if (poison)
+ // {
+ // m_ai->UseItem(poison, EQUIPMENT_SLOT_OFFHAND);
+ // //m_ai->SetIgnoreUpdateTime(5);
+ // }
+ //}
+} // end DoNonCombatActions
diff --git a/src/server/game/AI/PlayerBots/bp_rog_ai.h b/src/server/game/AI/PlayerBots/bp_rog_ai.h
new file mode 100644
index 0000000..07dc796
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_rog_ai.h
@@ -0,0 +1,104 @@
+
+#ifndef _PlayerbotRogueAI_H
+#define _PlayerbotRogueAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ RogueCombat,
+ RogueSpellPreventing,
+ RogueThreat,
+ RogueStealth
+};
+
+enum RoguePoisonDisplayId
+{
+ DEADLY_POISON_DISPLAYID = 13707,
+ INSTANT_POISON_DISPLAYID = 13710,
+ WOUND_POISON_DISPLAYID = 37278
+};
+
+enum RogueSpells
+{
+ ADRENALINE_RUSH_1 = 13750,
+ AMBUSH_1 = 8676,
+ BACKSTAB_1 = 53,
+ BLADE_FLURRY_1 = 13877,
+ BLIND_1 = 2094,
+ CHEAP_SHOT_1 = 1833,
+ CLOAK_OF_SHADOWS_1 = 31224,
+ COLD_BLOOD_1 = 14177,
+ DEADLY_THROW_1 = 26679,
+ DISARM_TRAP_1 = 1842,
+ DISMANTLE_1 = 51722,
+ DISTRACT_1 = 1725,
+ ENVENOM_1 = 32645,
+ EVASION_1 = 5277,
+ EVISCERATE_1 = 2098,
+ EXPOSE_ARMOR_1 = 8647,
+ FAN_OF_KNIVES_1 = 51723,
+ FEINT_1 = 1966,
+ GARROTE_1 = 703,
+ GHOSTLY_STRIKE_1 = 14278,
+ GOUGE_1 = 1776,
+ HEMORRHAGE_1 = 16511,
+ HUNGER_FOR_BLOOD_1 = 51662,
+ KICK_1 = 1766,
+ KIDNEY_SHOT_1 = 408,
+ KILLING_SPREE_1 = 51690,
+ MUTILATE_1 = 1329,
+ PICK_LOCK_1 = 1804,
+ PICK_POCKET_1 = 921,
+ PREMEDITATION_1 = 14183,
+ PREPARATION_1 = 14185,
+ RIPOSTE_1 = 14251,
+ RUPTURE_1 = 1943,
+ SAP_1 = 6770,
+ SHADOW_DANCE_1 = 51713,
+ SHADOWSTEP_1 = 36554,
+ SHIV_1 = 5938,
+ SINISTER_STRIKE_1 = 1752,
+ SLICE_AND_DICE_1 = 5171,
+ SPRINT_1 = 2983,
+ STEALTH_1 = 1784,
+ TRICKS_OF_THE_TRADE_1 = 57934,
+ VANISH_1 = 1856
+};
+//class Player;
+
+class PlayerbotRogueAI : PlayerbotClassAI
+{
+public:
+ PlayerbotRogueAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotRogueAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // COMBAT
+ uint32 SINISTER_STRIKE, BACKSTAB, GOUGE, EVASION, SPRINT, KICK, FEINT, SHIV, FAN_OF_KNIVES;
+
+ // SUBTLETY
+ uint32 SHADOWSTEP, STEALTH, VANISH, HEMORRHAGE, BLIND, SHADOW_DANCE, PICK_POCKET, CLOAK_OF_SHADOWS, TRICK_TRADE, CRIPPLING_POISON, DEADLY_POISON, MIND_NUMBING_POISON, GHOSTLY_STRIKE, DISTRACT, PREPARATION, PREMEDITATION;
+
+ // ASSASSINATION
+ uint32 EVISCERATE, SLICE_DICE, GARROTE, EXPOSE_ARMOR, AMBUSH, RUPTURE, DISMANTLE, CHEAP_SHOT, KIDNEY_SHOT, MUTILATE, ENVENOM, DEADLY_THROW;
+
+ // racial
+ uint32 ARCANE_TORRENT, GIFT_OF_THE_NAARU, STONEFORM, ESCAPE_ARTIST, EVERY_MAN_FOR_HIMSELF, SHADOWMELD, BLOOD_FURY, WAR_STOMP, BERSERKING, WILL_OF_THE_FORSAKEN;
+
+ uint32 SpellSequence, LastSpellCombat, LastSpellSubtlety, LastSpellAssassination, Aura;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_sha_ai.cpp b/src/server/game/AI/PlayerBots/bp_sha_ai.cpp
new file mode 100644
index 0000000..1f12e4b
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_sha_ai.cpp
@@ -0,0 +1,1660 @@
+#include "bp_ai.h"
+#include "bp_dk_ai.h"
+#include "bp_sha_ai.h"
+#include "Chat.h"
+#include "Player.h"
+#include "SpellAuras.h"
+#include "Totem.h"
+
+PlayerbotShamanAI::PlayerbotShamanAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ // restoration
+ CHAIN_HEAL = PlayerbotAI::InitSpell(me, CHAIN_HEAL_1);
+ HEALING_WAVE = PlayerbotAI::InitSpell(me, HEALING_WAVE_1);
+ LESSER_HEALING_WAVE = PlayerbotAI::InitSpell(me, LESSER_HEALING_WAVE_1);
+ RIPTIDE = PlayerbotAI::InitSpell(me, RIPTIDE_1);
+ ANCESTRAL_SPIRIT = PlayerbotAI::InitSpell(me, ANCESTRAL_SPIRIT_1);
+ EARTH_SHIELD = PlayerbotAI::InitSpell(me, EARTH_SHIELD_1);
+ WATER_SHIELD = PlayerbotAI::InitSpell(me, WATER_SHIELD_1);
+ EARTHLIVING_WEAPON = PlayerbotAI::InitSpell(me, EARTHLIVING_WEAPON_1);
+ TREMOR_TOTEM = PlayerbotAI::InitSpell(me, TREMOR_TOTEM_1); // totems
+ HEALING_STREAM_TOTEM = PlayerbotAI::InitSpell(me, HEALING_STREAM_TOTEM_1);
+ MANA_SPRING_TOTEM = PlayerbotAI::InitSpell(me, MANA_SPRING_TOTEM_1);
+ MANA_TIDE_TOTEM = PlayerbotAI::InitSpell(me, MANA_TIDE_TOTEM_1);
+ CURE_TOXINS = PlayerbotAI::InitSpell(me, CURE_TOXINS_1);
+ CLEANSE_SPIRIT = PlayerbotAI::InitSpell(me, CLEANSE_SPIRIT_1);
+ NATURES_SWIFTNESS_SHAMAN = PlayerbotAI::InitSpell(me, NATURES_SWIFTNESS_SHAMAN_1);
+ TIDAL_FORCE = PlayerbotAI::InitSpell(me, TIDAL_FORCE_1);
+ // enhancement
+ FOCUSED = 0; // Focused what?
+ STORMSTRIKE = PlayerbotAI::InitSpell(me, STORMSTRIKE_1);
+ LAVA_LASH = PlayerbotAI::InitSpell(me, LAVA_LASH_1);
+ SHAMANISTIC_RAGE = PlayerbotAI::InitSpell(me, SHAMANISTIC_RAGE_1);
+ BLOODLUST = PlayerbotAI::InitSpell(me, BLOODLUST_1);
+ HEROISM = PlayerbotAI::InitSpell(me, HEROISM_1);
+ FERAL_SPIRIT = PlayerbotAI::InitSpell(me, FERAL_SPIRIT_1);
+ LIGHTNING_SHIELD = PlayerbotAI::InitSpell(me, LIGHTNING_SHIELD_1);
+ ROCKBITER_WEAPON = PlayerbotAI::InitSpell(me, ROCKBITER_WEAPON_1);
+ FLAMETONGUE_WEAPON = PlayerbotAI::InitSpell(me, FLAMETONGUE_WEAPON_1);
+ FROSTBRAND_WEAPON = PlayerbotAI::InitSpell(me, FROSTBRAND_WEAPON_1);
+ WINDFURY_WEAPON = PlayerbotAI::InitSpell(me, WINDFURY_WEAPON_1);
+ STONESKIN_TOTEM = PlayerbotAI::InitSpell(me, STONESKIN_TOTEM_1); // totems
+ STRENGTH_OF_EARTH_TOTEM = PlayerbotAI::InitSpell(me, STRENGTH_OF_EARTH_TOTEM_1);
+ FROST_RESISTANCE_TOTEM = PlayerbotAI::InitSpell(me, FROST_RESISTANCE_TOTEM_1);
+ FLAMETONGUE_TOTEM = PlayerbotAI::InitSpell(me, FLAMETONGUE_TOTEM_1);
+ FIRE_RESISTANCE_TOTEM = PlayerbotAI::InitSpell(me, FIRE_RESISTANCE_TOTEM_1);
+ GROUNDING_TOTEM = PlayerbotAI::InitSpell(me, GROUNDING_TOTEM_1);
+ NATURE_RESISTANCE_TOTEM = PlayerbotAI::InitSpell(me, NATURE_RESISTANCE_TOTEM_1);
+ WIND_FURY_TOTEM = PlayerbotAI::InitSpell(me, WINDFURY_TOTEM_1);
+ STONESKIN_TOTEM = PlayerbotAI::InitSpell(me, STONESKIN_TOTEM_1);
+ WRATH_OF_AIR_TOTEM = PlayerbotAI::InitSpell(me, WRATH_OF_AIR_TOTEM_1);
+ EARTH_ELEMENTAL_TOTEM = PlayerbotAI::InitSpell(me, EARTH_ELEMENTAL_TOTEM_1);
+ MAELSTROM_WEAPON = PlayerbotAI::InitSpell(me, MAELSTROM_WEAPON_1);
+ // elemental
+ LIGHTNING_BOLT = PlayerbotAI::InitSpell(me, LIGHTNING_BOLT_1);
+ EARTH_SHOCK = PlayerbotAI::InitSpell(me, EARTH_SHOCK_1);
+ FLAME_SHOCK = PlayerbotAI::InitSpell(me, FLAME_SHOCK_1);
+ PURGE = PlayerbotAI::InitSpell(me, PURGE_1);
+ WIND_SHOCK = 0; //NPC spell
+ FROST_SHOCK = PlayerbotAI::InitSpell(me, FROST_SHOCK_1);
+ CHAIN_LIGHTNING = PlayerbotAI::InitSpell(me, CHAIN_LIGHTNING_1);
+ LAVA_BURST = PlayerbotAI::InitSpell(me, LAVA_BURST_1);
+ HEX = PlayerbotAI::InitSpell(me, HEX_1);
+ STONECLAW_TOTEM = PlayerbotAI::InitSpell(me, STONECLAW_TOTEM_1); // totems
+ SEARING_TOTEM = PlayerbotAI::InitSpell(me, SEARING_TOTEM_1);
+ FIRE_NOVA_TOTEM = 0; // NPC only spell, check FIRE_NOVA_1
+ MAGMA_TOTEM = PlayerbotAI::InitSpell(me, MAGMA_TOTEM_1);
+ EARTHBIND_TOTEM = PlayerbotAI::InitSpell(me, EARTHBIND_TOTEM_1);
+ TOTEM_OF_WRATH = PlayerbotAI::InitSpell(me, TOTEM_OF_WRATH_1);
+ FIRE_ELEMENTAL_TOTEM = PlayerbotAI::InitSpell(me, FIRE_ELEMENTAL_TOTEM_1);
+ ELEMENTAL_MASTERY = PlayerbotAI::InitSpell(me, ELEMENTAL_MASTERY_1);
+ THUNDERSTORM = PlayerbotAI::InitSpell(me, THUNDERSTORM_1);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_SHAMAN); // draenei
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_SHAMAN); // orc
+ WAR_STOMP = PlayerbotAI::InitSpell(me, WAR_STOMP_ALL); // tauren
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+
+ // totem buffs
+ STRENGTH_OF_EARTH_EFFECT = PlayerbotAI::InitSpell(me, STRENGTH_OF_EARTH_EFFECT_1);
+ FLAMETONGUE_EFFECT = PlayerbotAI::InitSpell(me, FLAMETONGUE_EFFECT_1);
+ MAGMA_TOTEM_EFFECT = PlayerbotAI::InitSpell(me, MAGMA_TOTEM_EFFECT_1);
+ STONECLAW_EFFECT = PlayerbotAI::InitSpell(me, STONECLAW_EFFECT_1);
+ FIRE_RESISTANCE_EFFECT = PlayerbotAI::InitSpell(me, FIRE_RESISTANCE_EFFECT_1);
+ FROST_RESISTANCE_EFFECT = PlayerbotAI::InitSpell(me, FROST_RESISTANCE_EFFECT_1);
+ GROUDNING_EFFECT = PlayerbotAI::InitSpell(me, GROUDNING_EFFECT_1);
+ NATURE_RESISTANCE_EFFECT = PlayerbotAI::InitSpell(me, NATURE_RESISTANCE_EFFECT_1);
+ STONESKIN_EFFECT = PlayerbotAI::InitSpell(me, STONESKIN_EFFECT_1);
+ WINDFURY_EFFECT = PlayerbotAI::InitSpell(me, WINDFURY_EFFECT_1);
+ WRATH_OF_AIR_EFFECT = PlayerbotAI::InitSpell(me, WRATH_OF_AIR_EFFECT_1);
+ CLEANSING_TOTEM_EFFECT = PlayerbotAI::InitSpell(me, CLEANSING_TOTEM_EFFECT_1);
+ HEALING_STREAM_EFFECT = PlayerbotAI::InitSpell(me, HEALING_STREAM_EFFECT_1);
+ MANA_SPRING_EFFECT = PlayerbotAI::InitSpell(me, MANA_SPRING_EFFECT_1);
+ TREMOR_TOTEM_EFFECT = PlayerbotAI::InitSpell(me, TREMOR_TOTEM_EFFECT_1);
+ TOTEM_OF_WRATH_EFFECT = PlayerbotAI::InitSpell(me, TOTEM_OF_WRATH_EFFECT_1);
+ STONECLAW_EFFECT = PlayerbotAI::InitSpell(me, STONECLAW_EFFECT_1);
+ EARTHBIND_EFFECT = PlayerbotAI::InitSpell(me, EARTHBIND_EFFECT_1);
+
+ // Buffs that don't stack with totems
+ IMPROVED_ICY_TALONS = PlayerbotAI::InitSpell(me, IMPROVED_ICY_TALONS_1);
+ HORN_OF_WINTER = PlayerbotAI::InitSpell(me, HORN_OF_WINTER_1);
+}
+
+PlayerbotShamanAI::~PlayerbotShamanAI() {}
+
+void PlayerbotShamanAI::SendClassDebugInfo()
+{
+ uint32 talentcost = 0;
+ uint32 rank = 0;
+ SpellInfo const* learnSpellInfo = NULL;
+ ChatHandler ch(m_master->GetSession());
+ uint8 loc = uint8(me->GetSession()->GetSessionDbcLocale());
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(CHAIN_HEAL);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(HEALING_WAVE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(LESSER_HEALING_WAVE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(RIPTIDE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(ANCESTRAL_SPIRIT);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(EARTH_SHIELD);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(WATER_SHIELD);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(EARTHLIVING_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(TREMOR_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(HEALING_STREAM_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(MANA_SPRING_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(MANA_TIDE_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(CURE_TOXINS);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(CLEANSE_SPIRIT);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(NATURES_SWIFTNESS_SHAMAN);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(TIDAL_FORCE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(STORMSTRIKE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(LAVA_LASH);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(SHAMANISTIC_RAGE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(BLOODLUST);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(HEROISM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FERAL_SPIRIT);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(LIGHTNING_SHIELD);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(ROCKBITER_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FLAMETONGUE_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FROSTBRAND_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(WINDFURY_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(STONESKIN_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(STRENGTH_OF_EARTH_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FROST_RESISTANCE_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FLAMETONGUE_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FIRE_RESISTANCE_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(GROUNDING_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(NATURE_RESISTANCE_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(WIND_FURY_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(STONESKIN_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(WRATH_OF_AIR_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(EARTH_ELEMENTAL_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(MAELSTROM_WEAPON);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(LIGHTNING_BOLT);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(EARTH_SHOCK);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FLAME_SHOCK);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(PURGE);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(WIND_SHOCK);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FROST_SHOCK);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(CHAIN_LIGHTNING);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(LAVA_BURST);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(HEX);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(STONECLAW_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(SEARING_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FIRE_NOVA_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(MAGMA_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(EARTHBIND_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(TOTEM_OF_WRATH);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(FIRE_ELEMENTAL_TOTEM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(ELEMENTAL_MASTERY);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+ spellInfo = sSpellMgr->GetSpellInfo(THUNDERSTORM);
+ if (spellInfo)
+ {
+ std::ostringstream str;
+ str << spellInfo->Id << " - |cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc];
+ str << ' ' << localeNames[loc] << "]|h|r";
+ talentcost = GetTalentSpellCost(spellInfo->Id);
+ learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[0].TriggerSpell);
+ if (talentcost > 0 && spellInfo->GetNextRankSpell())
+ rank = talentcost;
+ else if (learnSpellInfo && learnSpellInfo->GetNextRankSpell())
+ rank = spellInfo->GetRank();
+ if (rank > 0)
+ str << " Rank " << rank;
+ ch.PSendSysMessage(str.str().c_str());
+ str.clear();
+ }
+}
+
+CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuver(Unit* /*pTarget*/)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder())
+ // return HealPlayer(GetHealTarget());
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotShamanAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuver(Unit* /*pTarget*/)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!me) return RETURN_NO_ACTION_ERROR;
+
+ //uint32 spec = me->GetSpec();
+
+ //// Make sure healer stays put, don't even melee (aggro) if in range.
+ //if (m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //else if (!m_ai->IsHealer() && m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ //// Heal
+ //if (m_ai->IsHealer())
+ //{
+ // if (HealPlayer(GetHealTarget()) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE))
+ // return RETURN_CONTINUE;
+ //}
+ //else
+ //{
+ // // Is this desirable? Debatable.
+ // // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+ // if (HealPlayer(me) & RETURN_CONTINUE)
+ // return RETURN_CONTINUE;
+ //}
+
+ //// Damage Spells
+ //DropTotems();
+ //CheckShields();
+ //UseCooldowns();
+ //switch (spec)
+ //{
+ // case SHAMAN_SPEC_ENHANCEMENT:
+ // if (STORMSTRIKE > 0 && (!me->HasSpellCooldown(STORMSTRIKE)) && m_ai->CastSpell(STORMSTRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (FLAME_SHOCK > 0 && (!pTarget->HasAura(FLAME_SHOCK)) && m_ai->CastSpell(FLAME_SHOCK, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (EARTH_SHOCK > 0 && (!me->HasSpellCooldown(EARTH_SHOCK)) && m_ai->CastSpell(EARTH_SHOCK, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (LAVA_LASH > 0 && (!me->HasSpellCooldown(LAVA_LASH)) && m_ai->CastSpell(LAVA_LASH, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (MAELSTROM_WEAPON > 0 && LIGHTNING_BOLT > 0 && me->HasAura(MAELSTROM_WEAPON) && m_ai->CastSpell(LIGHTNING_BOLT, *pTarget))
+ // return RETURN_CONTINUE;
+ // /*if (FOCUSED > 0 && m_ai->CastSpell(FOCUSED, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // break;
+
+ // case SHAMAN_SPEC_RESTORATION:
+ // // fall through to elemental
+
+ // case SHAMAN_SPEC_ELEMENTAL:
+ // if (FLAME_SHOCK > 0 && (!pTarget->HasAura(FLAME_SHOCK)) && m_ai->CastSpell(FLAME_SHOCK, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (LAVA_BURST > 0 && (pTarget->HasAura(FLAME_SHOCK)) && (!me->HasSpellCooldown(LAVA_BURST)) && m_ai->CastSpell(LAVA_BURST, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (LIGHTNING_BOLT > 0 && m_ai->CastSpell(LIGHTNING_BOLT, *pTarget))
+ // return RETURN_CONTINUE;
+ // /*if (PURGE > 0 && m_ai->CastSpell(PURGE, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (WIND_SHOCK > 0 && m_ai->CastSpell(WIND_SHOCK, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (FROST_SHOCK > 0 && !pTarget->HasAura(FROST_SHOCK, EFFECT_0) && m_ai->CastSpell(FROST_SHOCK, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (CHAIN_LIGHTNING > 0 && m_ai->CastSpell(CHAIN_LIGHTNING, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // /*if (HEX > 0 && !pTarget->HasAura(HEX, EFFECT_0) && m_ai->CastSpell(HEX))
+ // return RETURN_CONTINUE;*/
+ //}
+
+ return RETURN_NO_ACTION_OK;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotShamanAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //DropTotems();
+ //CheckShields();
+ //UseCooldowns();
+
+ //Player* healTarget = (m_ai->GetScenarioType() == PlayerbotAI::SCENARIO_PVP_DUEL) ? GetHealTarget() : me;
+ //if (HealPlayer(healTarget) & (RETURN_NO_ACTION_OK | RETURN_CONTINUE))
+ // return RETURN_CONTINUE;
+ //if (m_ai->CastSpell(LIGHTNING_BOLT))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+CombatManeuverReturns PlayerbotShamanAI::HealPlayer(Player* /*target*/)
+{
+ //CombatManeuverReturns r = PlayerbotClassAI::HealPlayer(target);
+ //if (r != RETURN_NO_ACTION_OK)
+ // return r;
+
+ //if (!target->isAlive())
+ //{
+ // if (ANCESTRAL_SPIRIT && m_ai->CastSpell(ANCESTRAL_SPIRIT, *target))
+ // {
+ // std::string msg = "Resurrecting ";
+ // msg += target->GetName();
+ // me->Say(msg, LANG_UNIVERSAL);
+ // return RETURN_CONTINUE;
+ // }
+ // return RETURN_NO_ACTION_ERROR; // not error per se - possibly just OOM
+ //}
+
+ //// Dispel if necessary
+ //if (CURE_TOXINS > 0 && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_NODISPEL) == 0)
+ //{
+ // uint32 DISPEL = CLEANSE_SPIRIT > 0 ? CLEANSE_SPIRIT : CURE_TOXINS;
+ // uint32 dispelMask = SpellInfo::GetDispelMask(DISPEL_POISON);
+ // uint32 dispelMask2 = SpellInfo::GetDispelMask(DISPEL_DISEASE);
+ // uint32 dispelMask3 = SpellInfo::GetDispelMask(DISPEL_CURSE);
+ // Unit::AuraMap const& auras = target->GetOwnedAuras();
+ // for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
+ // {
+ // Aura *holder = itr->second;
+ // if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask)
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_POISON)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // else if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask2)
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_DISEASE)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // else if ((1 << holder->GetSpellInfo()->Dispel) & dispelMask3 & (DISPEL == CLEANSE_SPIRIT))
+ // {
+ // if (holder->GetSpellInfo()->Dispel == DISPEL_CURSE)
+ // {
+ // if (m_ai->CastSpell(DISPEL, *target))
+ // return RETURN_CONTINUE;
+ // return RETURN_NO_ACTION_ERROR;
+ // }
+ // }
+ // }
+ //}
+
+ //// Everyone is healthy enough, return OK. MUST correlate to highest value below (should be last HP check)
+ //if (target->GetHealthPct() >= 80)
+ // return RETURN_NO_ACTION_OK;
+
+ //// Technically the best rotation is CHAIN + LHW + LHW, or RIPTIDE + LHW + LHW (proc Tidal Waves then two short LHW), subbing in HW for trouble (bad mana efficiency)
+ //if (target->GetHealthPct() < 30 && HEALING_WAVE > 0 && m_ai->CastSpell(HEALING_WAVE, *target))
+ // return RETURN_CONTINUE;
+ //if (target->GetHealthPct() < 50 && LESSER_HEALING_WAVE > 0 && m_ai->CastSpell(LESSER_HEALING_WAVE, *target))
+ // return RETURN_CONTINUE;
+ //if (target->GetHealthPct() < 60 && RIPTIDE > 0 && !target->HasAura(RIPTIDE, EFFECT_0) && m_ai->CastSpell(RIPTIDE, *target))
+ // return RETURN_CONTINUE;
+ //if (target->GetHealthPct() < 80 && CHAIN_HEAL > 0 && m_ai->CastSpell(CHAIN_HEAL, *target))
+ // return RETURN_CONTINUE;
+
+ return RETURN_NO_ACTION_UNKNOWN;
+} // end HealTarget
+
+void PlayerbotShamanAI::DropTotems()
+{
+ //if (!m_ai) return;
+ //if (!me) return;
+
+ //uint32 spec = me->GetSpec();
+
+ //Totem* earth = NULL;
+ //Totem* fire = NULL;
+ //Totem* water = NULL;
+ //Totem* air = NULL;
+
+ //for (uint8 slot = SUMMON_SLOT_TOTEM; slot < MAX_TOTEM_SLOT; ++slot)
+ //{
+ // if (!me->m_SummonSlot[slot])
+ // continue;
+
+ // Creature* tot = me->GetMap()->GetCreature(me->m_SummonSlot[slot]);
+ // if (tot && tot->isTotem())
+ // {
+ // Totem* totem = tot->ToTotem();
+ // if (totem->GetTotemType() == SUMMON_TYPE_TOTEM_EARTH)
+ // earth = totem;
+ // else if (totem->GetTotemType() == SUMMON_TYPE_TOTEM_FIRE)
+ // fire = totem;
+ // else if (totem->GetTotemType() == SUMMON_TYPE_TOTEM_WATER)
+ // water = totem;
+ // else if (totem->GetTotemType() == SUMMON_TYPE_TOTEM_AIR)
+ // air = totem;
+ // }
+ //}
+
+ //// Earth Totems
+ //if ((earth == NULL) || (me->GetDistance(earth) > 30))
+ //{
+ // if (STRENGTH_OF_EARTH_TOTEM > 0 && m_ai->CastSpell(STRENGTH_OF_EARTH_TOTEM))
+ // {}
+ //}
+
+ //// Fire Totems
+ //if ((fire == NULL) || (me->GetDistance(fire) > 30))
+ //{
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FROST && FROST_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(FROST_RESISTANCE_TOTEM))
+ // {}
+ // else if (spec == SHAMAN_SPEC_ELEMENTAL && TOTEM_OF_WRATH > 0 && m_ai->CastSpell(TOTEM_OF_WRATH))
+ // {}
+ // // If the spec didn't take totem of wrath, use flametongue
+ // else if ((spec != SHAMAN_SPEC_ELEMENTAL || TOTEM_OF_WRATH == 0) && FLAMETONGUE_TOTEM > 0 && m_ai->CastSpell(FLAMETONGUE_TOTEM))
+ // {}
+ //}
+
+ //// Air totems
+ //if ((air == NULL) || (me->GetDistance(air) > 30))
+ //{
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_NATURE && NATURE_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(NATURE_RESISTANCE_TOTEM))
+ // {}
+ // else if (spec == SHAMAN_SPEC_ENHANCEMENT)
+ // {
+ // if (WIND_FURY_TOTEM > 0 /*&& !me->HasAura(IMPROVED_ICY_TALONS)*/ && m_ai->CastSpell(WIND_FURY_TOTEM))
+ // {}
+ // }
+ // else
+ // {
+ // if (WRATH_OF_AIR_TOTEM > 0 && m_ai->CastSpell(WRATH_OF_AIR_TOTEM))
+ // {}
+ // }
+ //}
+
+ //// Water Totems
+ //if ((water == NULL) || (me->GetDistance(water) > 30))
+ //{
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_RESIST_FIRE && FIRE_RESISTANCE_TOTEM > 0 && m_ai->CastSpell(FIRE_RESISTANCE_TOTEM))
+ // {}
+ // else if (MANA_SPRING_TOTEM > 0 && m_ai->CastSpell(MANA_SPRING_TOTEM))
+ // {}
+ //}
+
+ /*if (EARTH_ELEMENTAL_TOTEM > 0 && m_ai->CastSpell(EARTH_ELEMENTAL_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (EARTHBIND_TOTEM > 0 && !pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_0) && !me->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_0) && m_ai->CastSpell(EARTHBIND_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (FIRE_ELEMENTAL_TOTEM > 0 && m_ai->CastSpell(FIRE_ELEMENTAL_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (FIRE_NOVA_TOTEM > 0 && m_ai->CastSpell(FIRE_NOVA_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (GROUNDING_TOTEM > 0 && !me->HasAura(GROUNDING_TOTEM, EFFECT_0) && !me->HasAura(WRATH_OF_AIR_TOTEM, EFFECT_0) && !me->HasAura(WIND_FURY_TOTEM, EFFECT_0) && m_ai->CastSpell(GROUNDING_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (HEALING_STREAM_TOTEM > 0 && me->GetHealthPct() < 50 && !me->HasAura(HEALING_STREAM_TOTEM, EFFECT_0) && !me->HasAura(MANA_SPRING_TOTEM, EFFECT_0) && m_ai->CastSpell(HEALING_STREAM_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (MAGMA_TOTEM > 0 && (!me->HasAura(TOTEM_OF_WRATH, EFFECT_0)) && m_ai->CastSpell(MAGMA_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (SEARING_TOTEM > 0 && !pTarget->HasAura(SEARING_TOTEM, EFFECT_0) && !me->HasAura(TOTEM_OF_WRATH, EFFECT_0) && m_ai->CastSpell(SEARING_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (STONECLAW_TOTEM > 0 && me->GetHealthPct() < 51 && !pTarget->HasAura(STONECLAW_TOTEM, EFFECT_0) && !pTarget->HasAura(EARTHBIND_TOTEM, EFFECT_0) && !me->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_0) && m_ai->CastSpell(STONECLAW_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (STONESKIN_TOTEM > 0 && !me->HasAura(STONESKIN_TOTEM, EFFECT_0) && !me->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_0) && m_ai->CastSpell(STONESKIN_TOTEM))
+ return RETURN_CONTINUE;*/
+ /*if (TREMOR_TOTEM > 0 && !me->HasAura(STRENGTH_OF_EARTH_TOTEM, EFFECT_0) && m_ai->CastSpell(TREMOR_TOTEM))
+ return RETURN_CONTINUE;*/
+}
+
+void PlayerbotShamanAI::CheckShields()
+{
+ //if (!m_ai) return;
+ //if (!me) return;
+
+ //uint32 spec = me->GetSpec();
+
+ //if (spec == SHAMAN_SPEC_ENHANCEMENT && LIGHTNING_SHIELD > 0 && !me->HasAura(LIGHTNING_SHIELD, EFFECT_0))
+ // m_ai->CastSpell(LIGHTNING_SHIELD, *me);
+ //else if ((spec == SHAMAN_SPEC_ELEMENTAL || spec == SHAMAN_SPEC_RESTORATION) && WATER_SHIELD > 0 && !me->HasAura(WATER_SHIELD, EFFECT_0))
+ // m_ai->CastSpell(WATER_SHIELD, *me);
+ //if (EARTH_SHIELD > 0 && !GetMaster()->HasAura(EARTH_SHIELD, EFFECT_0))
+ // m_ai->CastSpell(EARTH_SHIELD, *(GetMaster()));
+}
+
+void PlayerbotShamanAI::UseCooldowns()
+{
+ //if (!m_ai) return;
+ //if (!me) return;
+
+ //uint32 spec = me->GetSpec();
+
+ //if (BLOODLUST > 0 && (!GetMaster()->HasAura(BLOODLUST, EFFECT_0)) && m_ai->CastSpell(BLOODLUST))
+ //{}
+ //else if (HEROISM > 0 && (!GetMaster()->HasAura(HEROISM, EFFECT_0)) && m_ai->CastSpell(HEROISM))
+ //{}
+
+ //switch (spec)
+ //{
+ // case SHAMAN_SPEC_ENHANCEMENT:
+ // if (SHAMANISTIC_RAGE > 0 && m_ai->CastSpell(SHAMANISTIC_RAGE, *me))
+ // return;
+ // else if (FERAL_SPIRIT > 0 && m_ai->CastSpell(FERAL_SPIRIT))
+ // return;
+ // break;
+
+ // case SHAMAN_SPEC_ELEMENTAL:
+ // if (ELEMENTAL_MASTERY > 0 && m_ai->CastSpell(ELEMENTAL_MASTERY, *me))
+ // return;
+ // else if (THUNDERSTORM > 0 && m_ai->CastSpell(THUNDERSTORM, *me))
+ // return;
+ // break;
+
+ // case SHAMAN_SPEC_RESTORATION:
+ // if (MANA_TIDE_TOTEM > 0 && (me->GetPower(POWER_MANA)*100/me->GetMaxPower(POWER_MANA)) < 50 && m_ai->CastSpell(MANA_TIDE_TOTEM))
+ // return;
+ // else if (NATURES_SWIFTNESS_SHAMAN > 0 && m_ai->CastSpell(NATURES_SWIFTNESS_SHAMAN))
+ // return;
+ // else if (TIDAL_FORCE > 0 && m_ai->CastSpell(TIDAL_FORCE))
+ // return;
+
+ // default:
+ // break;
+ //}
+}
+
+void PlayerbotShamanAI::DoNonCombatActions()
+{
+// if (!m_ai) return;
+// if (!me) return;
+//
+// if (!me->isAlive() || me->IsInDuel()) return;
+//
+// uint32 spec = me->GetSpec();
+//
+// CheckShields();
+///*
+// // buff myself weapon
+// if (ROCKBITER_WEAPON > 0)
+// (!me->HasAura(ROCKBITER_WEAPON, EFFECT_0) && !me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(WINDFURY_WEAPON, EFFECT_0) && !me->HasAura(FLAMETONGUE_WEAPON, EFFECT_0) && !me->HasAura(FROSTBRAND_WEAPON, EFFECT_0) && m_ai->CastSpell(ROCKBITER_WEAPON,*me) );
+// else if (EARTHLIVING_WEAPON > 0)
+// (!me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(FLAMETONGUE_WEAPON, EFFECT_0) && !me->HasAura(FROSTBRAND_WEAPON, EFFECT_0) && !me->HasAura(ROCKBITER_WEAPON, EFFECT_0) && m_ai->CastSpell(WINDFURY_WEAPON,*me) );
+// else if (WINDFURY_WEAPON > 0)
+// (!me->HasAura(WINDFURY_WEAPON, EFFECT_0) && !me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(FLAMETONGUE_WEAPON, EFFECT_0) && !me->HasAura(FROSTBRAND_WEAPON, EFFECT_0) && !me->HasAura(ROCKBITER_WEAPON, EFFECT_0) && m_ai->CastSpell(WINDFURY_WEAPON,*me) );
+// else if (FLAMETONGUE_WEAPON > 0)
+// (!me->HasAura(FLAMETONGUE_WEAPON, EFFECT_0) && !me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(WINDFURY_WEAPON, EFFECT_0) && !me->HasAura(FROSTBRAND_WEAPON, EFFECT_0) && !me->HasAura(ROCKBITER_WEAPON, EFFECT_0) && m_ai->CastSpell(FLAMETONGUE_WEAPON,*me) );
+// else if (FROSTBRAND_WEAPON > 0)
+// (!me->HasAura(FROSTBRAND_WEAPON, EFFECT_0) && !me->HasAura(EARTHLIVING_WEAPON, EFFECT_0) && !me->HasAura(WINDFURY_WEAPON, EFFECT_0) && !me->HasAura(FLAMETONGUE_WEAPON, EFFECT_0) && !me->HasAura(ROCKBITER_WEAPON, EFFECT_0) && m_ai->CastSpell(FROSTBRAND_WEAPON,*me) );
+// */
+// // Mainhand
+// Item* weapon;
+// weapon = me->GetItemByPos(EQUIPMENT_SLOT_MAINHAND);
+// if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_RESTORATION)
+// m_ai->CastSpell(EARTHLIVING_WEAPON, *me);
+// else if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ELEMENTAL)
+// m_ai->CastSpell(FLAMETONGUE_WEAPON, *me);
+// else if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ENHANCEMENT)
+// m_ai->CastSpell(WINDFURY_WEAPON, *me);
+//
+// //Offhand
+// weapon = me->GetItemByPos(EQUIPMENT_SLOT_OFFHAND);
+// if (weapon && (weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0) && spec == SHAMAN_SPEC_ENHANCEMENT)
+// m_ai->CastSpell(FLAMETONGUE_WEAPON, *me);
+//
+// // Revive
+// if (HealPlayer(GetResurrectionTarget()) & RETURN_CONTINUE)
+// return;
+//
+// // Heal
+// if (m_ai->IsHealer())
+// {
+// if (HealPlayer(GetHealTarget()) & RETURN_CONTINUE)
+// return;// RETURN_CONTINUE;
+// }
+// else
+// {
+// // Is this desirable? Debatable.
+// // TODO: In a group/raid with a healer you'd want this bot to focus on DPS (it's not specced/geared for healing either)
+// if (HealPlayer(me) & RETURN_CONTINUE)
+// return;// RETURN_CONTINUE;
+// }
+//
+// // hp/mana check
+// if (me->getStandState() != UNIT_STAND_STATE_STAND)
+// me->SetStandState(UNIT_STAND_STATE_STAND);
+//
+// if (EatDrinkBandage())
+// return;
+} // end DoNonCombatActions
+
+bool PlayerbotShamanAI::CastHoTOnTank()
+{
+ //if (!m_ai) return false;
+
+ //if ((PlayerbotAI::ORDERS_HEAL & m_ai->GetCombatOrder()) == 0) return false;
+
+ // Shaman: Healing Stream Totem, Earthliving Weapon, and Riptide (with talents)
+ // None of these are cast before Pulling
+
+ return false;
+}
+
+void PlayerbotShamanAI::UpdateGroupActions(uint32 const diff)
+{
+ if (me->IsMounted() || me->HasUnitState(UNIT_STATE_CASTING) ||
+ me->HasAuraType(SPELL_AURA_MOD_SILENCE) ||
+ me->HasAuraType(SPELL_AURA_MOD_PACIFY_SILENCE) ||
+ (me->GetInterruptMask() & AURA_INTERRUPT_FLAG_NOT_SEATED))
+ return;
+
+ MainSpec spec = MainSpec(m_ai->GetMySpec());
+ //1 Shaman specific
+ //1.1 item enchancements
+ //casts are instant so check even in combat
+ //1.1.1 mainhand
+ Item* weapon = me->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
+ if (weapon && !weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotShamanAI:: %s has found unenchanted mainhand weapon %s", me->GetName().c_str(), weapon->GetTemplate()->Name1.c_str());
+ switch (spec)
+ {
+ case SHAMAN_SPEC_RESTORATION:
+ if (EARTHLIVING_WEAPON)
+ {
+ m_ai->CastSpell(weapon, EARTHLIVING_WEAPON);
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotShamanAI:: %s tries to enchant with Earthliving", me->GetName().c_str());
+ }
+ break;
+ case SHAMAN_SPEC_ELEMENTAL:
+ if (FLAMETONGUE_WEAPON)
+ {
+ m_ai->CastSpell(weapon, FLAMETONGUE_WEAPON);
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotShamanAI:: %s tries to enchant with Flametongue", me->GetName().c_str());
+ }
+ break;
+ case SHAMAN_SPEC_ENHANCEMENT:
+ if (WINDFURY_WEAPON)
+ {
+ m_ai->CastSpell(weapon, WINDFURY_WEAPON);
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotShamanAI:: %s tries to enchant with Windfury", me->GetName().c_str());
+ }
+ else if (FROSTBRAND_WEAPON)
+ {
+ m_ai->CastSpell(weapon, FROSTBRAND_WEAPON);
+ //sLog->outError(LOG_FILTER_PLAYER, "PlayerbotShamanAI:: %s tries to enchant with Frostbrand", me->GetName().c_str());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ //1.1.2 offhand
+ weapon = me->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
+ if (weapon && !weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
+ {
+ switch (spec)
+ {
+ case SHAMAN_SPEC_RESTORATION:
+ if (EARTHLIVING_WEAPON)
+ m_ai->CastSpell(weapon, EARTHLIVING_WEAPON);
+ break;
+ case SHAMAN_SPEC_ELEMENTAL:
+ if (FLAMETONGUE_WEAPON)
+ m_ai->CastSpell(weapon, FLAMETONGUE_WEAPON);
+ break;
+ case SHAMAN_SPEC_ENHANCEMENT:
+ if (WINDFURY_WEAPON)
+ m_ai->CastSpell(weapon, WINDFURY_WEAPON);
+ else if (FROSTBRAND_WEAPON)
+ m_ai->CastSpell(weapon, FROSTBRAND_WEAPON);
+ break;
+ default:
+ break;
+ }
+ }
+ //1.2 elemental shield: self
+ if (spec == SHAMAN_SPEC_RESTORATION && WATER_SHIELD && !me->HasAura(WATER_SHIELD))
+ {
+ m_ai->CastSpell(me, WATER_SHIELD);
+ }
+ else if (LIGHTNING_SHIELD && !me->HasAura(LIGHTNING_SHIELD))
+ {
+ m_ai->CastSpell(me, LIGHTNING_SHIELD);
+ }
+ //end Shaman specific
+
+ //2 Heal
+
+ //3 Cure
+ //3.1 Cure Toxins
+ if (CURE_TOXINS)
+ m_ai->CureGroup(me, CURE_TOXINS, diff);
+ //3.2 Tremor totem, TODO:
+ //if (TREMOR_TOTEM)
+ //{}
+ //end Cure
+
+ //4 Non-combat Actions
+
+
+
+
+ if (me->duel && me->duel->startTimer != 0)
+ return;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_sha_ai.h b/src/server/game/AI/PlayerBots/bp_sha_ai.h
new file mode 100644
index 0000000..bd722db
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_sha_ai.h
@@ -0,0 +1,246 @@
+#ifndef _PLAYERBOTSHAMANAI_H
+#define _PLAYERBOTSHAMANAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ SPELL_ENHANCEMENT,
+ SPELL_RESTORATION,
+ SPELL_ELEMENTAL
+};
+
+enum
+{
+ ANCESTRAL_SPIRIT_1 = 2008,
+ ASTRAL_RECALL_1 = 556,
+ BLOODLUST_1 = 2825,
+ CALL_OF_THE_ANCESTORS_1 = 66843,
+ CALL_OF_THE_ELEMENTS_1 = 66842,
+ CALL_OF_THE_SPIRITS_1 = 66844,
+ CHAIN_HEAL_1 = 1064,
+ CHAIN_LIGHTNING_1 = 421,
+ CHAINED_HEAL_1 = 70809,
+ CLEANSE_SPIRIT_1 = 51886,
+ CLEANSING_TOTEM_1 = 8170,
+ CURE_TOXINS_1 = 526,
+ EARTH_ELEMENTAL_TOTEM_1 = 2062,
+ EARTH_SHIELD_1 = 974,
+ EARTH_SHOCK_1 = 8042,
+ EARTHBIND_TOTEM_1 = 2484,
+ EARTHLIVING_WEAPON_1 = 51730,
+ ELEMENTAL_MASTERY_1 = 16166,
+ FERAL_SPIRIT_1 = 51533,
+ FIRE_ELEMENTAL_TOTEM_1 = 2894,
+ FIRE_NOVA_1 = 1535,
+ FIRE_RESISTANCE_TOTEM_1 = 8184,
+ FLAME_SHOCK_1 = 8050,
+ FLAMETONGUE_TOTEM_1 = 8227,
+ FLAMETONGUE_WEAPON_1 = 8024,
+ FROST_RESISTANCE_TOTEM_1 = 8181,
+ FROST_SHOCK_1 = 8056,
+ FROSTBRAND_WEAPON_1 = 8033,
+ GHOST_WOLF_1 = 2645,
+ GROUNDING_TOTEM_1 = 8177,
+ HEALING_STREAM_TOTEM_1 = 5394,
+ HEALING_WAVE_1 = 331,
+ HEROISM_1 = 32182,
+ HEX_1 = 51514,
+ LAVA_BURST_1 = 51505,
+ LAVA_LASH_1 = 60103,
+ LESSER_HEALING_WAVE_1 = 8004,
+ LIGHTNING_BOLT_1 = 403,
+ LIGHTNING_SHIELD_1 = 324,
+ MAGMA_TOTEM_1 = 8190,
+ MANA_SPRING_TOTEM_1 = 5675,
+ MANA_TIDE_TOTEM_1 = 16190,
+ NATURE_RESISTANCE_TOTEM_1 = 10595,
+ NATURES_SWIFTNESS_SHAMAN_1 = 16188,
+ PURGE_1 = 370,
+ RIPTIDE_1 = 61295,
+ ROCKBITER_WEAPON_1 = 8017,
+ SEARING_TOTEM_1 = 3599,
+ SENTRY_TOTEM_1 = 6495,
+ SHAMANISTIC_RAGE_1 = 30823,
+ STONECLAW_TOTEM_1 = 5730,
+ STONESKIN_TOTEM_1 = 8071,
+ STORMSTRIKE_1 = 17364,
+ STRENGTH_OF_EARTH_TOTEM_1 = 8075,
+ THUNDERSTORM_1 = 51490,
+ TIDAL_FORCE_1 = 55198,
+ TOTEM_OF_WRATH_1 = 30706,
+ TOTEMIC_RECALL_1 = 36936,
+ TREMOR_TOTEM_1 = 8143,
+ WATER_BREATHING_1 = 131,
+ WATER_SHIELD_1 = 52127,
+ WATER_WALKING_1 = 546,
+ WIND_SHEAR_1 = 57994,
+ WINDFURY_TOTEM_1 = 8512,
+ WINDFURY_WEAPON_1 = 8232,
+ WRATH_OF_AIR_TOTEM_1 = 3738,
+
+ //Totem Buffs
+ STRENGTH_OF_EARTH_EFFECT_1 = 8076,
+ FLAMETONGUE_EFFECT_1 = 52109,
+ MAGMA_TOTEM_EFFECT_1 = 8188,
+ STONECLAW_EFFECT_1 = 5728,
+ FIRE_RESISTANCE_EFFECT_1 = 8185,
+ FROST_RESISTANCE_EFFECT_1 = 8182,
+ GROUDNING_EFFECT_1 = 8178,
+ NATURE_RESISTANCE_EFFECT_1 = 10596,
+ STONESKIN_EFFECT_1 = 8072,
+ WINDFURY_EFFECT_1 = 8515,
+ WRATH_OF_AIR_EFFECT_1 = 2895,
+ CLEANSING_TOTEM_EFFECT_1 = 8172,
+ HEALING_STREAM_EFFECT_1 = 52042,
+ MANA_SPRING_EFFECT_1 = 5677,
+ TREMOR_TOTEM_EFFECT_1 = 8145,
+ TOTEM_OF_WRATH_EFFECT_1 = 57658,
+ EARTHBIND_EFFECT_1 = 6474,
+ // FIRE_ELEMENTAL_TOTEM uses spell effect index 2
+ // SEARING_TOTEM uses spell effect index 0
+ // EARTH_ELEMENTAL_TOTEM uses spell effect indexes 1 and 2
+
+ //Spec buffs
+ MAELSTROM_WEAPON_1 = 51532, //This is the final rank only, the spell family thing doesn't work so I decided to go with max only
+
+ // Buffs that don't stack with totems
+ //IMPROVED_ICY_TALONS_1 = 55610,
+ //HORN_OF_WINTER_1 = 57330
+};
+//class Player;
+
+class PlayerbotShamanAI : PlayerbotClassAI
+{
+public:
+ PlayerbotShamanAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotShamanAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // Utility Functions
+ bool CastHoTOnTank();
+
+ void UpdateGroupActions(uint32 const diff);
+ void SendClassDebugInfo();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // Heals the target based off its hps
+ CombatManeuverReturns HealPlayer(Player* target);
+ Player* GetHealTarget() { return PlayerbotClassAI::GetHealTarget(); }
+ void DropTotems();
+ void CheckShields();
+ void UseCooldowns();
+
+ // ENHANCEMENT
+ uint32 ROCKBITER_WEAPON,
+ STONESKIN_TOTEM,
+ LIGHTNING_SHIELD,
+ FLAMETONGUE_WEAPON,
+ STRENGTH_OF_EARTH_TOTEM,
+ FOCUSED,
+ FROSTBRAND_WEAPON,
+ FROST_RESISTANCE_TOTEM,
+ FLAMETONGUE_TOTEM,
+ FIRE_RESISTANCE_TOTEM,
+ WINDFURY_WEAPON,
+ GROUNDING_TOTEM,
+ NATURE_RESISTANCE_TOTEM,
+ WIND_FURY_TOTEM,
+ STORMSTRIKE,
+ LAVA_LASH,
+ SHAMANISTIC_RAGE,
+ WRATH_OF_AIR_TOTEM,
+ EARTH_ELEMENTAL_TOTEM,
+ BLOODLUST,
+ HEROISM,
+ FERAL_SPIRIT,
+ MAELSTROM_WEAPON;
+
+ // RESTORATION
+ uint32 HEALING_WAVE,
+ LESSER_HEALING_WAVE,
+ ANCESTRAL_SPIRIT,
+ TREMOR_TOTEM,
+ HEALING_STREAM_TOTEM,
+ MANA_SPRING_TOTEM,
+ CHAIN_HEAL,
+ MANA_TIDE_TOTEM,
+ EARTH_SHIELD,
+ WATER_SHIELD,
+ EARTHLIVING_WEAPON,
+ RIPTIDE,
+ CURE_TOXINS,
+ CLEANSE_SPIRIT,
+ NATURES_SWIFTNESS_SHAMAN,
+ TIDAL_FORCE;
+
+ // ELEMENTAL
+ uint32 LIGHTNING_BOLT,
+ EARTH_SHOCK,
+ STONECLAW_TOTEM,
+ FLAME_SHOCK,
+ SEARING_TOTEM,
+ PURGE,
+ FIRE_NOVA_TOTEM,
+ WIND_SHOCK,
+ FROST_SHOCK,
+ MAGMA_TOTEM,
+ CHAIN_LIGHTNING,
+ TOTEM_OF_WRATH,
+ FIRE_ELEMENTAL_TOTEM,
+ LAVA_BURST,
+ EARTHBIND_TOTEM,
+ HEX,
+ ELEMENTAL_MASTERY,
+ THUNDERSTORM;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ // totem buffs
+ uint32 STRENGTH_OF_EARTH_EFFECT,
+ FLAMETONGUE_EFFECT,
+ MAGMA_TOTEM_EFFECT,
+ STONECLAW_EFFECT,
+ FIRE_RESISTANCE_EFFECT,
+ FROST_RESISTANCE_EFFECT,
+ GROUDNING_EFFECT,
+ NATURE_RESISTANCE_EFFECT,
+ STONESKIN_EFFECT,
+ WINDFURY_EFFECT,
+ WRATH_OF_AIR_EFFECT,
+ CLEANSING_TOTEM_EFFECT,
+ HEALING_STREAM_EFFECT,
+ MANA_SPRING_EFFECT,
+ TREMOR_TOTEM_EFFECT,
+ TOTEM_OF_WRATH_EFFECT,
+ EARTHBIND_EFFECT;
+
+ // Buffs that dont stack with totems
+ uint32 IMPROVED_ICY_TALONS,
+ HORN_OF_WINTER;
+
+ uint32 SpellSequence, LastSpellEnhancement, LastSpellRestoration, LastSpellElemental;
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_warl_ai.cpp b/src/server/game/AI/PlayerBots/bp_warl_ai.cpp
new file mode 100644
index 0000000..e1745f5
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_warl_ai.cpp
@@ -0,0 +1,550 @@
+#include "bp_ai.h"
+#include "bp_warl_ai.h"
+#include "Player.h"
+#include "Pet.h"
+
+PlayerbotWarlockAI::PlayerbotWarlockAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ // DESTRUCTION
+ SHADOW_BOLT = PlayerbotAI::InitSpell(me, SHADOW_BOLT_1);
+ IMMOLATE = PlayerbotAI::InitSpell(me, IMMOLATE_1);
+ INCINERATE = PlayerbotAI::InitSpell(me, INCINERATE_1);
+ SEARING_PAIN = PlayerbotAI::InitSpell(me, SEARING_PAIN_1);
+ CONFLAGRATE = PlayerbotAI::InitSpell(me, CONFLAGRATE_1);
+ SHADOWFURY = PlayerbotAI::InitSpell(me, SHADOWFURY_1);
+ CHAOS_BOLT = PlayerbotAI::InitSpell(me, CHAOS_BOLT_1);
+ SHADOWFLAME = PlayerbotAI::InitSpell(me, SHADOWFLAME_1);
+ HELLFIRE = PlayerbotAI::InitSpell(me, HELLFIRE_1);
+ RAIN_OF_FIRE = PlayerbotAI::InitSpell(me, RAIN_OF_FIRE_1);
+ SOUL_FIRE = PlayerbotAI::InitSpell(me, SOUL_FIRE_1); // soul shard spells
+ SHADOWBURN = PlayerbotAI::InitSpell(me, SHADOWBURN_1);
+ // CURSE
+ CURSE_OF_WEAKNESS = PlayerbotAI::InitSpell(me, CURSE_OF_WEAKNESS_1);
+ CURSE_OF_THE_ELEMENTS = PlayerbotAI::InitSpell(me, CURSE_OF_THE_ELEMENTS_1);
+ CURSE_OF_AGONY = PlayerbotAI::InitSpell(me, CURSE_OF_AGONY_1);
+ CURSE_OF_EXHAUSTION = PlayerbotAI::InitSpell(me, CURSE_OF_EXHAUSTION_1);
+ CURSE_OF_TONGUES = PlayerbotAI::InitSpell(me, CURSE_OF_TONGUES_1);
+ CURSE_OF_DOOM = PlayerbotAI::InitSpell(me, CURSE_OF_DOOM_1);
+ // AFFLICTION
+ CORRUPTION = PlayerbotAI::InitSpell(me, CORRUPTION_1);
+ DRAIN_SOUL = PlayerbotAI::InitSpell(me, DRAIN_SOUL_1);
+ DRAIN_LIFE = PlayerbotAI::InitSpell(me, DRAIN_LIFE_1);
+ DRAIN_MANA = PlayerbotAI::InitSpell(me, DRAIN_MANA_1);
+ LIFE_TAP = PlayerbotAI::InitSpell(me, LIFE_TAP_1);
+ UNSTABLE_AFFLICTION = PlayerbotAI::InitSpell(me, UNSTABLE_AFFLICTION_1);
+ HAUNT = PlayerbotAI::InitSpell(me, HAUNT_1);
+ SEED_OF_CORRUPTION = PlayerbotAI::InitSpell(me, SEED_OF_CORRUPTION_1);
+ DARK_PACT = PlayerbotAI::InitSpell(me, DARK_PACT_1);
+ HOWL_OF_TERROR = PlayerbotAI::InitSpell(me, HOWL_OF_TERROR_1);
+ FEAR = PlayerbotAI::InitSpell(me, FEAR_1);
+ // DEMONOLOGY
+ DEMON_SKIN = PlayerbotAI::InitSpell(me, DEMON_SKIN_1);
+ DEMON_ARMOR = PlayerbotAI::InitSpell(me, DEMON_ARMOR_1);
+ DEMONIC_EMPOWERMENT = PlayerbotAI::InitSpell(me, DEMONIC_EMPOWERMENT_1);
+ FEL_ARMOR = PlayerbotAI::InitSpell(me, FEL_ARMOR_1);
+ SHADOW_WARD = PlayerbotAI::InitSpell(me, SHADOW_WARD_1);
+ SOULSHATTER = PlayerbotAI::InitSpell(me, SOULSHATTER_1);
+ SOUL_LINK = PlayerbotAI::InitSpell(me, SOUL_LINK_1);
+ SOUL_LINK_AURA = 25228; // dummy aura applied, after spell SOUL_LINK
+ HEALTH_FUNNEL = PlayerbotAI::InitSpell(me, HEALTH_FUNNEL_1);
+ DETECT_INVISIBILITY = PlayerbotAI::InitSpell(me, DETECT_INVISIBILITY_1);
+ CREATE_FIRESTONE = PlayerbotAI::InitSpell(me, CREATE_FIRESTONE_1);
+ CREATE_HEALTHSTONE = PlayerbotAI::InitSpell(me, CREATE_HEALTHSTONE_1);
+ CREATE_SOULSTONE = PlayerbotAI::InitSpell(me, CREATE_SOULSTONE_1);
+ CREATE_SPELLSTONE = PlayerbotAI::InitSpell(me, CREATE_SPELLSTONE_1);
+ // demon summon
+ SUMMON_IMP = PlayerbotAI::InitSpell(me, SUMMON_IMP_1);
+ SUMMON_VOIDWALKER = PlayerbotAI::InitSpell(me, SUMMON_VOIDWALKER_1);
+ SUMMON_SUCCUBUS = PlayerbotAI::InitSpell(me, SUMMON_SUCCUBUS_1);
+ SUMMON_FELHUNTER = PlayerbotAI::InitSpell(me, SUMMON_FELHUNTER_1);
+ SUMMON_FELGUARD = PlayerbotAI::InitSpell(me, SUMMON_FELGUARD_1);
+ // demon skills should be initialized on demons
+ BLOOD_PACT = 0; // imp skill
+ CONSUME_SHADOWS = 0; // voidwalker skill
+ FEL_INTELLIGENCE = 0; // felhunter skill
+ // RANGED COMBAT
+ SHOOT = PlayerbotAI::InitSpell(me, SHOOT_3);
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ ARCANE_TORRENT = PlayerbotAI::InitSpell(me, ARCANE_TORRENT_MANA_CLASSES); // blood elf
+ ESCAPE_ARTIST = PlayerbotAI::InitSpell(me, ESCAPE_ARTIST_ALL); // gnome
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_WARLOCK); // orc
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+
+ m_lastDemon = 0;
+ m_demonOfChoice = DEMON_IMP;
+ m_isTempImp = false;
+}
+
+PlayerbotWarlockAI::~PlayerbotWarlockAI() {}
+
+CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuverPVE(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotWarlockAI::DoFirstCombatManeuverPVP(Unit* /*pTarget*/)
+{
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ ////Unit* pVictim = pTarget->getVictim();
+ //float dist = pTarget->GetCombatReach();
+ //Pet *pet = m_bot->GetPet();
+ //uint32 spec = m_bot->GetSpec();
+ //uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, NULL);
+
+ ////If we have UA it will replace immolate in our rotation
+ //uint32 FIRE = (UNSTABLE_AFFLICTION > 0 ? UNSTABLE_AFFLICTION : IMMOLATE);
+
+ //// Voidwalker is near death - sacrifice it for a shield
+ //if (pet && pet->GetEntry() == DEMON_VOIDWALKER && SACRIFICE && !m_bot->HasAura(SACRIFICE) && pet->GetHealthPct() < 10)
+ // m_ai->CastPetSpell(SACRIFICE);
+
+ //// Use healthstone
+ //if (m_bot->GetHealthPct() < 30)
+ //{
+ // Item* healthStone = m_ai->FindConsumable(HEALTHSTONE_DISPLAYID);
+ // if (healthStone)
+ // m_ai->UseItem(healthStone);
+ //}
+
+ //// Voidwalker sacrifice gives shield - but you lose the pet (and it's DPS/tank) - use only as last resort for your own health!
+ //if (m_bot->GetHealthPct() < 20 && pet && pet->GetEntry() == DEMON_VOIDWALKER && SACRIFICE && !m_bot->HasAura(SACRIFICE))
+ // m_ai->CastPetSpell(SACRIFICE);
+
+ //if (m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_RANGED && dist > ATTACK_DISTANCE)
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_RANGED);
+ //// if in melee range OR can't shoot OR have no ranged (wand) equipped
+ //else if(m_ai->GetCombatStyle() != PlayerbotAI::COMBAT_MELEE && (dist <= ATTACK_DISTANCE || SHOOT == 0 || !m_bot->GetWeaponForAttack(RANGED_ATTACK, true)))
+ // m_ai->SetCombatStyle(PlayerbotAI::COMBAT_MELEE);
+
+ ////Used to determine if this bot is highest on threat
+ //Unit *newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+ //if (newTarget) // TODO: && party has a tank
+ //{
+ // if (SOULSHATTER > 0 && shardCount > 0 && !m_bot->HasSpellCooldown(SOULSHATTER))
+ // if (CastSpell(SOULSHATTER, m_bot))
+ // return RETURN_CONTINUE;
+
+ // // Have threat, can't quickly lower it. 3 options remain: Stop attacking, lowlevel damage (wand), keep on keeping on.
+ // if (newTarget->GetHealthPct() > 25)
+ // {
+ // // If elite, do nothing and pray tank gets aggro off you
+ // // TODO: Is there an IsElite function? If so, find it and insert.
+ // //if (newTarget->IsElite())
+ // // return;
+
+ // // Not an elite. You could insert FEAR here but in any PvE situation that's 90-95% likely
+ // // to worsen the situation for the group. ... So please don't.
+ // return CastSpell(SHOOT, pTarget);
+ // }
+ //}
+
+ //// Damage Spells
+ //switch (spec)
+ //{
+ // case WARLOCK_SPEC_AFFLICTION:
+ // if (CURSE_OF_AGONY && !pTarget->HasAura(CURSE_OF_AGONY) && CastSpell(CURSE_OF_AGONY, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CORRUPTION && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FIRE && !pTarget->HasAura(FIRE) && CastSpell(FIRE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (HAUNT && !m_bot->HasSpellCooldown(HAUNT) && CastSpell(HAUNT, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHADOW_BOLT && CastSpell(SHADOW_BOLT, pTarget))
+ // return RETURN_CONTINUE;
+
+ // return RETURN_NO_ACTION_OK;
+
+ // case WARLOCK_SPEC_DEMONOLOGY:
+ // if (pet && DEMONIC_EMPOWERMENT && !m_bot->HasSpellCooldown(DEMONIC_EMPOWERMENT) && CastSpell(DEMONIC_EMPOWERMENT))
+ // return RETURN_CONTINUE;
+ // if (CURSE_OF_AGONY && !pTarget->HasAura(CURSE_OF_AGONY) && CastSpell(CURSE_OF_AGONY, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CORRUPTION && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FIRE && !pTarget->HasAura(FIRE) && CastSpell(FIRE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (INCINERATE && pTarget->HasAura(FIRE) && CastSpell(INCINERATE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHADOW_BOLT && CastSpell(SHADOW_BOLT, pTarget))
+ // return RETURN_CONTINUE;
+
+ // return RETURN_NO_ACTION_OK;
+
+ // case WARLOCK_SPEC_DESTRUCTION:
+ // if (CURSE_OF_AGONY && !pTarget->HasAura(CURSE_OF_AGONY) && CastSpell(CURSE_OF_AGONY, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CORRUPTION && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget))
+ // return RETURN_CONTINUE;
+ // if (FIRE && !pTarget->HasAura(FIRE) && CastSpell(FIRE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CONFLAGRATE && pTarget->HasAura(FIRE) && !m_bot->HasSpellCooldown(CONFLAGRATE) && CastSpell(CONFLAGRATE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (CHAOS_BOLT && !m_bot->HasSpellCooldown(CHAOS_BOLT) && CastSpell(CHAOS_BOLT, pTarget))
+ // return RETURN_CONTINUE;
+ // if (INCINERATE && pTarget->HasAura(FIRE) && CastSpell(INCINERATE, pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHADOW_BOLT && CastSpell(SHADOW_BOLT, pTarget))
+ // return RETURN_CONTINUE;
+
+ // return RETURN_NO_ACTION_OK;
+
+ // //if (LIFE_TAP && LastSpellAffliction < 1 && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) <= 50 && m_bot->GetHealthPct() > 50)
+ // // m_ai->CastSpell(LIFE_TAP, *m_bot);
+ // //else if (DRAIN_SOUL && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.40 && !pTarget->HasAura(DRAIN_SOUL) && LastSpellAffliction < 3)
+ // // m_ai->CastSpell(DRAIN_SOUL, *pTarget);
+ // // ////m_ai->SetIgnoreUpdateTime(15);
+ // //else if (DRAIN_LIFE && LastSpellAffliction < 4 && !pTarget->HasAura(DRAIN_SOUL) && !pTarget->HasAura(SEED_OF_CORRUPTION) && !pTarget->HasAura(DRAIN_LIFE) && !pTarget->HasAura(DRAIN_MANA) && m_bot->GetHealthPct() <= 70)
+ // // m_ai->CastSpell(DRAIN_LIFE, *pTarget);
+ // // ////m_ai->SetIgnoreUpdateTime(5);
+ // //else if (SEED_OF_CORRUPTION && !pTarget->HasAura(SEED_OF_CORRUPTION) && LastSpellAffliction < 7)
+ // // m_ai->CastSpell(SEED_OF_CORRUPTION, *pTarget);
+ // //else if (HOWL_OF_TERROR && !pTarget->HasAura(HOWL_OF_TERROR) && m_ai->GetAttackerCount() > 3 && LastSpellAffliction < 8)
+ // // m_ai->CastSpell(HOWL_OF_TERROR, *pTarget);
+ // // m_ai->TellMaster("casting howl of terror!");
+ // //else if (FEAR && !pTarget->HasAura(FEAR) && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && LastSpellAffliction < 9)
+ // // m_ai->CastSpell(FEAR, *pTarget);
+ // // //m_ai->TellMaster("casting fear!");
+ // // ////m_ai->SetIgnoreUpdateTime(1.5);
+ // //else if ((pet) && (DARK_PACT > 0 && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) <= 50 && LastSpellAffliction < 10 && pet->GetPower(POWER_MANA) > 0))
+ // // m_ai->CastSpell(DARK_PACT, *m_bot);
+ // //if (SHADOWFURY && LastSpellDestruction < 1 && !pTarget->HasAura(SHADOWFURY))
+ // // m_ai->CastSpell(SHADOWFURY, *pTarget);
+ // //else if (RAIN_OF_FIRE && LastSpellDestruction < 3 && m_ai->GetAttackerCount() >= 3)
+ // // m_ai->CastSpell(RAIN_OF_FIRE, *pTarget);
+ // // //m_ai->TellMaster("casting rain of fire!");
+ // // ////m_ai->SetIgnoreUpdateTime(8);
+ // //else if (SHADOWFLAME && !pTarget->HasAura(SHADOWFLAME) && LastSpellDestruction < 4)
+ // // m_ai->CastSpell(SHADOWFLAME, *pTarget);
+ // //else if (SEARING_PAIN && LastSpellDestruction < 8)
+ // // m_ai->CastSpell(SEARING_PAIN, *pTarget);
+ // //else if (SOUL_FIRE && LastSpellDestruction < 9)
+ // // m_ai->CastSpell(SOUL_FIRE, *pTarget);
+ // // ////m_ai->SetIgnoreUpdateTime(6);
+ // //else if (SHADOWBURN && LastSpellDestruction < 11 && pTarget->GetHealth() < pTarget->GetMaxHealth() * 0.20 && !pTarget->HasAura(SHADOWBURN))
+ // // m_ai->CastSpell(SHADOWBURN, *pTarget);
+ // //else if (HELLFIRE && LastSpellDestruction < 12 && !m_bot->HasAura(HELLFIRE) && m_ai->GetAttackerCount() >= 5 && m_bot->GetHealthPct() >= 50)
+ // // m_ai->CastSpell(HELLFIRE);
+ // // m_ai->TellMaster("casting hellfire!");
+ // // ////m_ai->SetIgnoreUpdateTime(15);
+ // //else if (CURSE_OF_THE_ELEMENTS && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_WEAKNESS) && LastSpellCurse < 2)
+ // // m_ai->CastSpell(CURSE_OF_THE_ELEMENTS, *pTarget);
+ // //else if (CURSE_OF_WEAKNESS && !pTarget->HasAura(CURSE_OF_WEAKNESS) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && LastSpellCurse < 3)
+ // // m_ai->CastSpell(CURSE_OF_WEAKNESS, *pTarget);
+ // //else if (CURSE_OF_TONGUES && !pTarget->HasAura(CURSE_OF_TONGUES) && !pTarget->HasAura(SHADOWFLAME) && !pTarget->HasAura(CURSE_OF_WEAKNESS) && !pTarget->HasAura(CURSE_OF_AGONY) && !pTarget->HasAura(CURSE_OF_THE_ELEMENTS) && LastSpellCurse < 4)
+ // // m_ai->CastSpell(CURSE_OF_TONGUES, *pTarget);
+ //}
+
+ //// No spec due to low level OR no spell found yet
+ //if (CORRUPTION && !pTarget->HasAura(CORRUPTION) && CastSpell(CORRUPTION, pTarget))
+ // return RETURN_CONTINUE;
+ //if (FIRE && !pTarget->HasAura(FIRE) && CastSpell(FIRE, pTarget))
+ // return RETURN_CONTINUE;
+ //if (SHADOW_BOLT)
+ // return CastSpell(SHADOW_BOLT, pTarget);
+
+ return RETURN_NO_ACTION_OK;
+} // end DoNextCombatManeuver
+
+CombatManeuverReturns PlayerbotWarlockAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(FEAR, *pTarget))
+ // return RETURN_CONTINUE;
+ //if (m_ai->CastSpell(SHADOW_BOLT))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+void PlayerbotWarlockAI::CheckDemon()
+{
+ //uint32 spec = m_bot->GetSpec();
+ //uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, NULL);
+ //Pet *pet = m_bot->GetPet();
+
+ ////Assign demon of choice
+ //if (spec == WARLOCK_SPEC_AFFLICTION)
+ // m_demonOfChoice = DEMON_FELHUNTER;
+ //else if (spec == WARLOCK_SPEC_DEMONOLOGY)
+ // m_demonOfChoice = (DEMON_FELGUARD > 0 ? DEMON_FELGUARD : DEMON_SUCCUBUS);
+ //else if (spec == WARLOCK_SPEC_DESTRUCTION)
+ // m_demonOfChoice = DEMON_IMP;
+
+ //// Summon demon
+ //if (!pet || m_isTempImp || pet->GetEntry() != m_demonOfChoice)
+ //{
+ // uint32 summonSpellId;
+ // if (m_demonOfChoice != DEMON_IMP && shardCount > 0)
+ // {
+ // switch (m_demonOfChoice)
+ // {
+ // case DEMON_VOIDWALKER:
+ // summonSpellId = SUMMON_VOIDWALKER;
+ // break;
+
+ // case DEMON_FELGUARD:
+ // summonSpellId = SUMMON_FELGUARD;
+ // break;
+
+ // case DEMON_FELHUNTER:
+ // summonSpellId = SUMMON_FELHUNTER;
+ // break;
+
+ // case DEMON_SUCCUBUS:
+ // summonSpellId = SUMMON_SUCCUBUS;
+ // break;
+
+ // default:
+ // summonSpellId = 0;
+ // }
+
+ // if (m_ai->CastSpell(summonSpellId))
+ // {
+ // //m_ai->TellMaster("Summoning favorite demon...");
+ // m_isTempImp = false;
+ // return;
+ // }
+ // }
+ // else if (!pet && SUMMON_IMP && m_ai->CastSpell(SUMMON_IMP))
+ // {
+ // if (m_demonOfChoice != DEMON_IMP)
+ // m_isTempImp = true;
+
+ // //m_ai->TellMaster("Summoning Imp...");
+ // return;
+ // }
+ //}
+}
+
+void PlayerbotWarlockAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ ////uint32 spec = m_bot->GetSpec();
+ //Pet *pet = m_bot->GetPet();
+
+ //// Initialize pet spells
+ //if (pet && pet->GetEntry() != m_lastDemon)
+ //{
+ // switch (pet->GetEntry())
+ // {
+ // case DEMON_IMP:
+ // BLOOD_PACT = m_ai->initPetSpell(BLOOD_PACT_ICON);
+ // FIREBOLT = m_ai->initPetSpell(FIREBOLT_ICON);
+ // FIRE_SHIELD = m_ai->initPetSpell(FIRE_SHIELD_ICON);
+ // break;
+
+ // case DEMON_VOIDWALKER:
+ // CONSUME_SHADOWS = m_ai->initPetSpell(CONSUME_SHADOWS_ICON);
+ // SACRIFICE = m_ai->initPetSpell(SACRIFICE_ICON);
+ // SUFFERING = m_ai->initPetSpell(SUFFERING_ICON);
+ // TORMENT = m_ai->initPetSpell(TORMENT_ICON);
+ // break;
+
+ // case DEMON_SUCCUBUS:
+ // LASH_OF_PAIN = m_ai->initPetSpell(LASH_OF_PAIN_ICON);
+ // SEDUCTION = m_ai->initPetSpell(SEDUCTION_ICON);
+ // SOOTHING_KISS = m_ai->initPetSpell(SOOTHING_KISS_ICON);
+ // break;
+
+ // case DEMON_FELHUNTER:
+ // DEVOUR_MAGIC = m_ai->initPetSpell(DEVOUR_MAGIC_ICON);
+ // FEL_INTELLIGENCE = m_ai->initPetSpell(FEL_INTELLIGENCE_ICON);
+ // SHADOW_BITE = m_ai->initPetSpell(SHADOW_BITE_ICON);
+ // SPELL_LOCK = m_ai->initPetSpell(SPELL_LOCK_ICON);
+ // break;
+
+ // case DEMON_FELGUARD:
+ // ANGUISH = m_ai->initPetSpell(ANGUISH_ICON);
+ // CLEAVE = m_ai->initPetSpell(CLEAVE_ICON);
+ // INTERCEPT = m_ai->initPetSpell(INTERCEPT_ICON);
+ // break;
+ // }
+
+ // m_lastDemon = pet->GetEntry();
+
+ // //if (!m_isTempImp)
+ // // m_demonOfChoice = pet->GetEntry();
+ //}
+
+ //// Destroy extra soul shards
+ //uint8 shardCount = m_bot->GetItemCount(SOUL_SHARD, false, NULL);
+ //uint8 freeSpace = m_ai->GetFreeBagSpace();
+ //if (shardCount > MAX_SHARD_COUNT || (freeSpace == 0 && shardCount > 1))
+ // m_bot->DestroyItemCount(SOUL_SHARD, shardCount > MAX_SHARD_COUNT ? shardCount - MAX_SHARD_COUNT : 1, true, false);
+
+ //// buff myself DEMON_SKIN, DEMON_ARMOR, FEL_ARMOR - Strongest one available is chosen
+ //if (FEL_ARMOR)
+ //{
+ // if (m_ai->SelfBuff(FEL_ARMOR))
+ // return;
+ //}
+ //else if (DEMON_ARMOR)
+ //{
+ // if (m_ai->SelfBuff(DEMON_ARMOR))
+ // return;
+ //}
+ //else if (DEMON_SKIN)
+ // if (m_ai->SelfBuff(DEMON_SKIN))
+ // return;
+
+ //// healthstone creation
+ //if (CREATE_HEALTHSTONE && shardCount > 0)
+ //{
+ // Item* const healthStone = m_ai->FindConsumable(HEALTHSTONE_DISPLAYID);
+ // if (!healthStone && m_ai->CastSpell(CREATE_HEALTHSTONE))
+ // return;
+ //}
+
+ //// soulstone creation and use
+ //if (CREATE_SOULSTONE)
+ //{
+ // Item* soulStone = m_ai->FindConsumable(SOULSTONE_DISPLAYID);
+ // if (!soulStone)
+ // {
+ // if (shardCount > 0 && !m_bot->HasSpellCooldown(CREATE_SOULSTONE) && m_ai->CastSpell(CREATE_SOULSTONE))
+ // return;
+ // }
+ // else
+ // {
+ // uint32 soulStoneSpell = soulStone->GetTemplate()->Spells[0].SpellId;
+ // Player* master = GetMaster();
+ // if (!master->HasAura(soulStoneSpell) && !m_bot->HasSpellCooldown(soulStoneSpell))
+ // {
+ // m_ai->UseItem(soulStone, master);
+ // return;
+ // }
+ // }
+ //}
+
+ //// Spellstone creation and use (Spellstone dominates firestone completely as I understand it)
+ //Item* const weapon = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
+ //if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0)
+ //{
+ // Item* const stone = m_ai->FindConsumable(SPELLSTONE_DISPLAYID);
+ // Item* const stone2 = m_ai->FindConsumable(FIRESTONE_DISPLAYID);
+ // if (!stone && !stone2)
+ // {
+ // if (CREATE_SPELLSTONE && shardCount > 0 && m_ai->CastSpell(CREATE_SPELLSTONE))
+ // return;
+ // else if (CREATE_SPELLSTONE == 0 && CREATE_FIRESTONE > 0 && shardCount > 0 && m_ai->CastSpell(CREATE_FIRESTONE))
+ // return;
+ // }
+ // else if (stone)
+ // {
+ // m_ai->UseItem(stone, EQUIPMENT_SLOT_MAINHAND);
+ // return;
+ // }
+ // else
+ // {
+ // m_ai->UseItem(stone2, EQUIPMENT_SLOT_MAINHAND);
+ // return;
+ // }
+ //}
+
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //// hp/mana check
+ //if (pet && DARK_PACT && pet->GetPower(POWER_MANA) > 0 && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) <= 50)
+ // if (m_ai->CastSpell(DARK_PACT, *m_bot))
+ // return;
+
+ //if (LIFE_TAP && (m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA)) <= 50 && m_bot->GetHealthPct() > 50)
+ // if (m_ai->CastSpell(LIFE_TAP, *m_bot))
+ // return;
+
+ //if (EatDrinkBandage())
+ // return;
+
+ ////Heal Voidwalker
+ //if (pet && pet->GetEntry() == DEMON_VOIDWALKER && CONSUME_SHADOWS && pet->GetHealthPct() < 75 && !pet->HasAura(CONSUME_SHADOWS))
+ // m_ai->CastPetSpell(CONSUME_SHADOWS);
+
+ //CheckDemon();
+
+ //// Soul link demon
+ //if (pet && SOUL_LINK && !m_bot->HasAura(SOUL_LINK_AURA) && m_ai->CastSpell(SOUL_LINK, *m_bot))
+ // return;
+
+ //// Check demon buffs
+ //if (pet && pet->GetEntry() == DEMON_IMP && BLOOD_PACT && !m_bot->HasAura(BLOOD_PACT) && m_ai->CastPetSpell(BLOOD_PACT))
+ // return;
+
+ //if (pet && pet->GetEntry() == DEMON_FELHUNTER && FEL_INTELLIGENCE && !m_bot->HasAura(FEL_INTELLIGENCE) && m_ai->CastPetSpell(FEL_INTELLIGENCE))
+ // return;
+} // end DoNonCombatActions
diff --git a/src/server/game/AI/PlayerBots/bp_warl_ai.h b/src/server/game/AI/PlayerBots/bp_warl_ai.h
new file mode 100644
index 0000000..e4561df
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_warl_ai.h
@@ -0,0 +1,254 @@
+#ifndef _PlayerbotWarlockAI_H
+#define _PlayerbotWarlockAI_H
+
+#include "bp_cai.h"
+
+#define SOUL_SHARD 6265
+#define MAX_SHARD_COUNT 4 // Maximum soul shard count bot should keep
+
+enum
+{
+ SPELL_CURSES,
+ SPELL_AFFLICTION,
+ SPELL_DESTRUCTION,
+ SPELL_DEMONOLOGY
+};
+
+enum StoneDisplayId
+{
+ FIRESTONE_DISPLAYID = 7409,
+ SPELLSTONE_DISPLAYID = 13291,
+ SOULSTONE_DISPLAYID = 6009,
+ HEALTHSTONE_DISPLAYID = 8026
+};
+
+enum DemonEntry
+{
+ DEMON_IMP = 416,
+ DEMON_VOIDWALKER = 1860,
+ DEMON_SUCCUBUS = 1863,
+ DEMON_FELHUNTER = 417,
+ DEMON_FELGUARD = 17252
+};
+
+enum DemonSpellIconIds
+{
+ // Imp
+ BLOOD_PACT_ICON = 541,
+ FIREBOLT_ICON = 18,
+ FIRE_SHIELD_ICON = 16,
+ // Felguard
+ ANGUISH_ICON = 173,
+ CLEAVE_ICON = 277,
+ INTERCEPT_ICON = 516,
+ // Felhunter
+ DEVOUR_MAGIC_ICON = 47,
+ FEL_INTELLIGENCE_ICON = 1940,
+ SHADOW_BITE_ICON = 2027,
+ SPELL_LOCK_ICON = 77,
+ // Succubus
+ LASH_OF_PAIN_ICON = 596,
+ SEDUCTION_ICON = 48,
+ SOOTHING_KISS_ICON = 694,
+ // Voidwalker
+ CONSUME_SHADOWS_ICON = 207,
+ SACRIFICE_ICON = 693,
+ SUFFERING_ICON = 9,
+ TORMENT_ICON = 173
+};
+
+enum WarlockSpells
+{
+ BANISH_1 = 710,
+ CHALLENGING_HOWL_1 = 59671,
+ CHAOS_BOLT_1 = 50796,
+ CONFLAGRATE_1 = 17962,
+ CORRUPTION_1 = 172,
+ CREATE_FIRESTONE_1 = 6366,
+ CREATE_HEALTHSTONE_1 = 6201,
+ CREATE_SOULSTONE_1 = 693,
+ CREATE_SPELLSTONE_1 = 2362,
+ CURSE_OF_AGONY_1 = 980,
+ CURSE_OF_DOOM_1 = 603,
+ CURSE_OF_EXHAUSTION_1 = 18223,
+ CURSE_OF_THE_ELEMENTS_1 = 1490,
+ CURSE_OF_TONGUES_1 = 1714,
+ CURSE_OF_WEAKNESS_1 = 702,
+ DARK_PACT_1 = 18220,
+ DEATH_COIL_WARLOCK_1 = 6789,
+ DEMON_ARMOR_1 = 706,
+ DEMON_CHARGE_1 = 54785,
+ DEMON_SKIN_1 = 687,
+ DEMONIC_CIRCLE_SUMMON_1 = 48018,
+ DEMONIC_CIRCLE_TELEPORT_1 = 48020,
+ DEMONIC_EMPOWERMENT_1 = 47193,
+ DEMONIC_IMMOLATE_1 = 75445,
+ DETECT_INVISIBILITY_1 = 132,
+ DRAIN_LIFE_1 = 689,
+ DRAIN_MANA_1 = 5138,
+ DRAIN_SOUL_1 = 1120,
+ ENSLAVE_DEMON_1 = 1098,
+ EYE_OF_KILROGG_1 = 126,
+ FEAR_1 = 5782,
+ FEL_ARMOR_1 = 28176,
+ FEL_DOMINATION_1 = 18708,
+ HAUNT_1 = 48181,
+ HEALTH_FUNNEL_1 = 755,
+ HELLFIRE_1 = 1949,
+ HOWL_OF_TERROR_1 = 5484,
+ IMMOLATE_1 = 348,
+ IMMOLATION_AURA_1 = 50589,
+ INCINERATE_1 = 29722,
+ INFERNO_1 = 1122,
+ LIFE_TAP_1 = 1454,
+ METAMORPHOSIS_1 = 59672,
+ RAIN_OF_FIRE_1 = 5740,
+ RITUAL_OF_DOOM_1 = 18540,
+ RITUAL_OF_SOULS_1 = 29893,
+ RITUAL_OF_SUMMONING_1 = 698,
+ SEARING_PAIN_1 = 5676,
+ SEED_OF_CORRUPTION_1 = 27243,
+ SENSE_DEMONS_1 = 5500,
+ SHADOW_BOLT_1 = 686,
+ SHADOW_CLEAVE_1 = 50581,
+ SHADOW_WARD_1 = 6229,
+ SHADOWBURN_1 = 17877,
+ SHADOWFLAME_1 = 47897,
+ SHADOWFURY_1 = 30283,
+ SHOOT_3 = 5019,
+ SOUL_FIRE_1 = 6353,
+ SOUL_LINK_1 = 19028,
+ SOULSHATTER_1 = 29858,
+ SUMMON_FELGUARD_1 = 30146,
+ SUMMON_FELHUNTER_1 = 691,
+ SUMMON_IMP_1 = 688,
+ SUMMON_SUCCUBUS_1 = 712,
+ SUMMON_VOIDWALKER_1 = 697,
+ UNENDING_BREATH_1 = 5697,
+ UNSTABLE_AFFLICTION_1 = 30108
+};
+
+//class Player;
+class PlayerbotWarlockAI : PlayerbotClassAI
+{
+public:
+ PlayerbotWarlockAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotWarlockAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ // buff a specific player, usually a real PC who is not in group
+ //void BuffPlayer(Player *target);
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ CombatManeuverReturns CastSpell(uint32 nextAction, Unit *pTarget = NULL) { return CastSpellWand(nextAction, pTarget, SHOOT); }
+
+ void CheckDemon();
+
+ // CURSES
+ uint32 CURSE_OF_WEAKNESS,
+ CURSE_OF_AGONY,
+ CURSE_OF_EXHAUSTION,
+ CURSE_OF_TONGUES,
+ CURSE_OF_THE_ELEMENTS,
+ CURSE_OF_DOOM;
+ // ranged
+ uint32 SHOOT;
+
+ // AFFLICTION
+ uint32 CORRUPTION,
+ DRAIN_SOUL,
+ DRAIN_LIFE,
+ DRAIN_MANA,
+ LIFE_TAP,
+ UNSTABLE_AFFLICTION,
+ HAUNT,
+ SEED_OF_CORRUPTION,
+ DARK_PACT,
+ HOWL_OF_TERROR,
+ FEAR;
+
+ // DESTRUCTION
+ uint32 SHADOW_BOLT,
+ IMMOLATE,
+ INCINERATE,
+ SEARING_PAIN,
+ CONFLAGRATE,
+ SOUL_FIRE,
+ SHADOWFURY,
+ CHAOS_BOLT,
+ SHADOWFLAME,
+ HELLFIRE,
+ RAIN_OF_FIRE,
+ SHADOWBURN;
+
+ // DEMONOLOGY
+ uint32 DEMON_SKIN,
+ DEMON_ARMOR,
+ DEMONIC_EMPOWERMENT,
+ SHADOW_WARD,
+ FEL_ARMOR,
+ SOULSHATTER,
+ SOUL_LINK,
+ SOUL_LINK_AURA,
+ HEALTH_FUNNEL,
+ DETECT_INVISIBILITY,
+ CREATE_FIRESTONE,
+ CREATE_SOULSTONE,
+ CREATE_HEALTHSTONE,
+ CREATE_SPELLSTONE;
+
+ // DEMON SUMMON
+ uint32 SUMMON_IMP,
+ SUMMON_VOIDWALKER,
+ SUMMON_SUCCUBUS,
+ SUMMON_FELHUNTER,
+ SUMMON_FELGUARD;
+
+ // DEMON SKILLS
+ uint32 BLOOD_PACT,
+ FIREBOLT,
+ FIRE_SHIELD,
+ ANGUISH,
+ CLEAVE,
+ INTERCEPT,
+ DEVOUR_MAGIC,
+ FEL_INTELLIGENCE,
+ SHADOW_BITE,
+ SPELL_LOCK,
+ LASH_OF_PAIN,
+ SEDUCTION,
+ SOOTHING_KISS,
+ CONSUME_SHADOWS,
+ SACRIFICE,
+ SUFFERING,
+ TORMENT;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ uint32 m_lastDemon; // Last demon entry used for spell initialization
+ uint32 m_demonOfChoice; // Preferred demon entry
+ bool m_isTempImp; // True if imp summoned temporarily until soul shard acquired for demon of choice.
+};
+
+#endif
diff --git a/src/server/game/AI/PlayerBots/bp_warr_ai.cpp b/src/server/game/AI/PlayerBots/bp_warr_ai.cpp
new file mode 100644
index 0000000..54f23fe
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_warr_ai.cpp
@@ -0,0 +1,551 @@
+/*
+ Name : PlayerbotWarriorAI.cpp
+ Complete: maybe around 37%
+ Author : Natsukawa
+ Version : 0.39
+ */
+#include "bp_ai.h"
+#include "bp_warr_ai.h"
+#include "Player.h"
+
+class PlayerbotAI;
+PlayerbotWarriorAI::PlayerbotWarriorAI(Player* const master, Player* const bot, PlayerbotAI* const ai) : PlayerbotClassAI(master, bot, ai)
+{
+ AUTO_SHOT = PlayerbotAI::InitSpell(me, AUTO_SHOT_2); // GENERAL
+
+ BATTLE_STANCE = PlayerbotAI::InitSpell(me, BATTLE_STANCE_1); //ARMS
+ CHARGE = PlayerbotAI::InitSpell(me, CHARGE_1); //ARMS
+ OVERPOWER = PlayerbotAI::InitSpell(me, OVERPOWER_1); // ARMS
+ HEROIC_STRIKE = PlayerbotAI::InitSpell(me, HEROIC_STRIKE_1); //ARMS
+ REND = PlayerbotAI::InitSpell(me, REND_1); //ARMS
+ THUNDER_CLAP = PlayerbotAI::InitSpell(me, THUNDER_CLAP_1); //ARMS
+ HAMSTRING = PlayerbotAI::InitSpell(me, HAMSTRING_1); //ARMS
+ MOCKING_BLOW = PlayerbotAI::InitSpell(me, MOCKING_BLOW_1); //ARMS
+ RETALIATION = PlayerbotAI::InitSpell(me, RETALIATION_1); //ARMS
+ SWEEPING_STRIKES = PlayerbotAI::InitSpell(me, SWEEPING_STRIKES_1); //ARMS
+ MORTAL_STRIKE = PlayerbotAI::InitSpell(me, MORTAL_STRIKE_1); //ARMS
+ BLADESTORM = PlayerbotAI::InitSpell(me, BLADESTORM_1); //ARMS
+ HEROIC_THROW = PlayerbotAI::InitSpell(me, HEROIC_THROW_1); //ARMS
+ SHATTERING_THROW = PlayerbotAI::InitSpell(me, SHATTERING_THROW_1); //ARMS
+ BLOODRAGE = PlayerbotAI::InitSpell(me, BLOODRAGE_1); //PROTECTION
+ DEFENSIVE_STANCE = PlayerbotAI::InitSpell(me, DEFENSIVE_STANCE_1); //PROTECTION
+ DEVASTATE = PlayerbotAI::InitSpell(me, DEVASTATE_1); //PROTECTION
+ SUNDER_ARMOR = PlayerbotAI::InitSpell(me, SUNDER_ARMOR_1); //PROTECTION
+ TAUNT = PlayerbotAI::InitSpell(me, TAUNT_1); //PROTECTION
+ SHIELD_BASH = PlayerbotAI::InitSpell(me, SHIELD_BASH_1); //PROTECTION
+ REVENGE = PlayerbotAI::InitSpell(me, REVENGE_1); //PROTECTION
+ SHIELD_BLOCK = PlayerbotAI::InitSpell(me, SHIELD_BLOCK_1); //PROTECTION
+ DISARM = PlayerbotAI::InitSpell(me, DISARM_1); //PROTECTION
+ SHIELD_WALL = PlayerbotAI::InitSpell(me, SHIELD_WALL_1); //PROTECTION
+ SHIELD_SLAM = PlayerbotAI::InitSpell(me, SHIELD_SLAM_1); //PROTECTION
+ VIGILANCE = PlayerbotAI::InitSpell(me, VIGILANCE_1); //PROTECTION
+ DEVASTATE = PlayerbotAI::InitSpell(me, DEVASTATE_1); //PROTECTION
+ SHOCKWAVE = PlayerbotAI::InitSpell(me, SHOCKWAVE_1); //PROTECTION
+ CONCUSSION_BLOW = PlayerbotAI::InitSpell(me, CONCUSSION_BLOW_1); //PROTECTION
+ SPELL_REFLECTION = PlayerbotAI::InitSpell(me, SPELL_REFLECTION_1); //PROTECTION
+ LAST_STAND = PlayerbotAI::InitSpell(me, LAST_STAND_1); //PROTECTION
+ BATTLE_SHOUT = PlayerbotAI::InitSpell(me, BATTLE_SHOUT_1); //FURY
+ DEMORALIZING_SHOUT = PlayerbotAI::InitSpell(me, DEMORALIZING_SHOUT_1); //FURY
+ CLEAVE = PlayerbotAI::InitSpell(me, CLEAVE_1); //FURY
+ INTIMIDATING_SHOUT = PlayerbotAI::InitSpell(me, INTIMIDATING_SHOUT_1); //FURY
+ EXECUTE = PlayerbotAI::InitSpell(me, EXECUTE_1); //FURY
+ CHALLENGING_SHOUT = PlayerbotAI::InitSpell(me, CHALLENGING_SHOUT_1); //FURY
+ SLAM = PlayerbotAI::InitSpell(me, SLAM_1); //FURY
+ BERSERKER_STANCE = PlayerbotAI::InitSpell(me, BERSERKER_STANCE_1); //FURY
+ INTERCEPT = PlayerbotAI::InitSpell(me, INTERCEPT_1); //FURY
+ DEATH_WISH = PlayerbotAI::InitSpell(me, DEATH_WISH_1); //FURY
+ BERSERKER_RAGE = PlayerbotAI::InitSpell(me, BERSERKER_RAGE_1); //FURY
+ WHIRLWIND = PlayerbotAI::InitSpell(me, WHIRLWIND_1); //FURY
+ PUMMEL = PlayerbotAI::InitSpell(me, PUMMEL_1); //FURY
+ BLOODTHIRST = PlayerbotAI::InitSpell(me, BLOODTHIRST_1); //FURY
+ RECKLESSNESS = PlayerbotAI::InitSpell(me, RECKLESSNESS_1); //FURY
+ RAMPAGE = 0; // passive
+ HEROIC_FURY = PlayerbotAI::InitSpell(me, HEROIC_FURY_1); //FURY
+ COMMANDING_SHOUT = PlayerbotAI::InitSpell(me, COMMANDING_SHOUT_1); //FURY
+ ENRAGED_REGENERATION = PlayerbotAI::InitSpell(me, ENRAGED_REGENERATION_1); //FURY
+ PIERCING_HOWL = PlayerbotAI::InitSpell(me, PIERCING_HOWL_1); //FURY
+
+ //RECENTLY_BANDAGED = 11196; // first aid check
+
+ // racial
+ GIFT_OF_THE_NAARU = PlayerbotAI::InitSpell(me, GIFT_OF_THE_NAARU_WARRIOR); // draenei
+ STONEFORM = PlayerbotAI::InitSpell(me, STONEFORM_ALL); // dwarf
+ ESCAPE_ARTIST = PlayerbotAI::InitSpell(me, ESCAPE_ARTIST_ALL); // gnome
+ EVERY_MAN_FOR_HIMSELF = PlayerbotAI::InitSpell(me, EVERY_MAN_FOR_HIMSELF_ALL); // human
+ SHADOWMELD = PlayerbotAI::InitSpell(me, SHADOWMELD_ALL); // night elf
+ BLOOD_FURY = PlayerbotAI::InitSpell(me, BLOOD_FURY_MELEE_CLASSES); // orc
+ WAR_STOMP = PlayerbotAI::InitSpell(me, WAR_STOMP_ALL); // tauren
+ BERSERKING = PlayerbotAI::InitSpell(me, BERSERKING_ALL); // troll
+ WILL_OF_THE_FORSAKEN = PlayerbotAI::InitSpell(me, WILL_OF_THE_FORSAKEN_ALL); // undead
+
+ //Procs
+ SLAM_PROC = PlayerbotAI::InitSpell(me, SLAM_PROC_1);
+ BLOODSURGE = PlayerbotAI::InitSpell(me, BLOODSURGE_1);
+ TASTE_FOR_BLOOD = PlayerbotAI::InitSpell(me, TASTE_FOR_BLOOD_1);
+ SUDDEN_DEATH = PlayerbotAI::InitSpell(me, SUDDEN_DEATH_1);
+}
+PlayerbotWarriorAI::~PlayerbotWarriorAI() {}
+
+CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuver(Unit* pTarget)
+{
+ //// There are NPCs in BGs and Open World PvP, so don't filter this on PvP scenarios (of course if PvP targets anyone but tank, all bets are off anyway)
+ //// Wait until the tank says so, until any non-tank gains aggro or X seconds - whichever is shortest
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && m_ai->GroupTankHoldsAggro())
+ // {
+ // if (PlayerbotAI::ORDERS_TANK & m_ai->GetCombatOrder())
+ // {
+ // if (pTarget->GetCombatReach() <= ATTACK_DISTANCE)
+ // {
+ // // Set everyone's UpdateAI() waiting to 2 seconds
+ // m_ai->SetGroupIgnoreUpdateTime(2);
+ // // Clear their TEMP_WAIT_TANKAGGRO flag
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // // Start attacking, force target on current target
+ // m_ai->Attack(m_ai->GetCurrentTarget());
+
+ // // While everyone else is waiting 2 second, we need to build up aggro, so don't return
+ // }
+ // else
+ // {
+ // // TODO: add check if target is ranged
+ // return RETURN_NO_ACTION_OK; // wait for target to get nearer
+ // }
+ // }
+ // else
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // }
+ // else
+ // {
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_TANKAGGRO);
+ // }
+ //}
+
+ //if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TEMP_WAIT_OOC)
+ //{
+ // if (m_WaitUntil > m_ai->CurrentTime() && !m_ai->IsGroupInCombat())
+ // return RETURN_NO_ACTION_OK; // wait it out
+ // else
+ // m_ai->ClearGroupCombatOrder(PlayerbotAI::ORDERS_TEMP_WAIT_OOC);
+ //}
+
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoFirstCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoFirstCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVE(Unit* pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //float fTargetDist = pTarget->GetCombatReach();
+
+ //if (DEFENSIVE_STANCE && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK))
+ //{
+ // if (!m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_0) && m_ai->CastSpell(DEFENSIVE_STANCE))
+ // return RETURN_CONTINUE;
+ // else if (TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_0) && m_ai->CastSpell(TAUNT, *pTarget))
+ // return RETURN_FINISHED_FIRST_MOVES;
+ //}
+
+ //if (BERSERKER_STANCE)
+ //{
+ // if (!m_bot->HasAura(BERSERKER_STANCE, EFFECT_0) && m_ai->CastSpell(BERSERKER_STANCE))
+ // return RETURN_CONTINUE;
+ // if (BLOODRAGE > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_0) && m_bot->GetPower(POWER_RAGE)/10 <= 10)
+ // return m_ai->CastSpell(BLOODRAGE) ? RETURN_FINISHED_FIRST_MOVES : RETURN_NO_ACTION_ERROR;
+ // if (INTERCEPT > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_0))
+ // {
+ // if (fTargetDist < 8.0f)
+ // return RETURN_NO_ACTION_OK;
+ // else if (fTargetDist > 25.0f)
+ // return RETURN_CONTINUE; // wait to come into range
+ // else if (INTERCEPT > 0 && m_ai->CastSpell(INTERCEPT, *pTarget))
+ // {
+ // float x, y, z;
+ // pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f);
+ // m_bot->Relocate(x, y, z);
+ // return RETURN_FINISHED_FIRST_MOVES;
+ // }
+ // }
+ //}
+
+ //if (BATTLE_STANCE)
+ //{
+ // if (!m_bot->HasAura(BATTLE_STANCE, EFFECT_0) && m_ai->CastSpell(BATTLE_STANCE))
+ // return RETURN_CONTINUE;
+ // if (CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_0))
+ // {
+ // if (fTargetDist < 8.0f)
+ // return RETURN_NO_ACTION_OK;
+ // if (fTargetDist > 25.0f)
+ // return RETURN_CONTINUE; // wait to come into range
+ // else if (CHARGE > 0 && m_ai->CastSpell(CHARGE, *pTarget))
+ // {
+ // float x, y, z;
+ // pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f);
+ // m_bot->Relocate(x, y, z);
+ // return RETURN_FINISHED_FIRST_MOVES;
+ // }
+ // }
+ //}
+
+ return RETURN_NO_ACTION_OK;
+}
+
+// TODO: blatant copy of PVE for now, please PVP-port it
+CombatManeuverReturns PlayerbotWarriorAI::DoFirstCombatManeuverPVP(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ //float fTargetDist = pTarget->GetCombatReach();
+
+ //if (DEFENSIVE_STANCE && (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK))
+ //{
+ // if (!m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_0) && m_ai->CastSpell(DEFENSIVE_STANCE))
+ // return RETURN_CONTINUE;
+ // else if (TAUNT > 0 && m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_0) && m_ai->CastSpell(TAUNT, *pTarget))
+ // return RETURN_FINISHED_FIRST_MOVES;
+ //}
+
+ //if (BERSERKER_STANCE)
+ //{
+ // if (!m_bot->HasAura(BERSERKER_STANCE, EFFECT_0) && m_ai->CastSpell(BERSERKER_STANCE))
+ // return RETURN_CONTINUE;
+ // if (BLOODRAGE > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_0) && m_bot->GetPower(POWER_RAGE)/10 <= 10)
+ // return m_ai->CastSpell(BLOODRAGE) ? RETURN_FINISHED_FIRST_MOVES : RETURN_NO_ACTION_ERROR;
+ // if (INTERCEPT > 0 && m_bot->HasAura(BERSERKER_STANCE, EFFECT_0))
+ // {
+ // if (fTargetDist < 8.0f)
+ // return RETURN_NO_ACTION_OK;
+ // else if (fTargetDist > 25.0f)
+ // return RETURN_CONTINUE; // wait to come into range
+ // else if (INTERCEPT > 0 && m_ai->CastSpell(INTERCEPT, *pTarget))
+ // {
+ // float x, y, z;
+ // pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f);
+ // m_bot->Relocate(x, y, z);
+ // return RETURN_FINISHED_FIRST_MOVES;
+ // }
+ // }
+ //}
+
+ //if (BATTLE_STANCE)
+ //{
+ // if (!m_bot->HasAura(BATTLE_STANCE, EFFECT_0) && m_ai->CastSpell(BATTLE_STANCE))
+ // return RETURN_CONTINUE;
+ // if (CHARGE > 0 && m_bot->HasAura(BATTLE_STANCE, EFFECT_0))
+ // {
+ // if (fTargetDist < 8.0f)
+ // return RETURN_NO_ACTION_OK;
+ // if (fTargetDist > 25.0f)
+ // return RETURN_CONTINUE; // wait to come into range
+ // else if (CHARGE > 0 && m_ai->CastSpell(CHARGE, *pTarget))
+ // {
+ // float x, y, z;
+ // pTarget->GetContactPoint(m_bot, x, y, z, 3.666666f);
+ // m_bot->Relocate(x, y, z);
+ // return RETURN_FINISHED_FIRST_MOVES;
+ // }
+ // }
+ //}
+
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget)
+{
+ //switch (m_ai->GetScenarioType())
+ //{
+ // case PlayerbotAI::SCENARIO_PVP_DUEL:
+ // case PlayerbotAI::SCENARIO_PVP_BG:
+ // case PlayerbotAI::SCENARIO_PVP_ARENA:
+ // case PlayerbotAI::SCENARIO_PVP_OPENWORLD:
+ // return DoNextCombatManeuverPVP(pTarget);
+ // case PlayerbotAI::SCENARIO_PVE:
+ // case PlayerbotAI::SCENARIO_PVE_ELITE:
+ // case PlayerbotAI::SCENARIO_PVE_RAID:
+ // default:
+ // return DoNextCombatManeuverPVE(pTarget);
+ // break;
+ //}
+
+ return RETURN_NO_ACTION_ERROR;
+}
+
+CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVE(Unit *pTarget)
+{
+ //if (!m_ai) return RETURN_NO_ACTION_ERROR;
+ //if (!m_bot) return RETURN_NO_ACTION_ERROR;
+
+ ////Unit* pVictim = pTarget->getVictim();
+ ////float fTargetDist = pTarget->GetCombatReach();
+ //uint32 spec = m_bot->GetSpec();
+
+ ////If we have devastate it will replace SA in our rotation
+ ////uint32 SUNDER = (DEVASTATE > 0 ? DEVASTATE : SUNDER_ARMOR);
+
+ ////Used to determine if this bot is highest on threat
+ //Unit* newTarget = m_ai->FindAttacker((PlayerbotAI::ATTACKERINFOTYPE) (PlayerbotAI::AIT_VICTIMSELF | PlayerbotAI::AIT_HIGHESTTHREAT), m_bot);
+
+ //// do shouts, berserker rage, etc...
+ //if (BERSERKER_RAGE > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_0))
+ // m_ai->CastSpell(BERSERKER_RAGE);
+ //else if (BLOODRAGE > 0 && m_bot->GetPower(POWER_RAGE)/10 <= 10)
+ // m_ai->CastSpell(BLOODRAGE);
+
+ //CheckShouts();
+
+ //switch (spec)
+ //{
+ // case WARRIOR_SPEC_ARMS:
+ // // Execute doesn't scale too well with extra rage and uses up *all* rage preventing use of other skills
+ // //Haven't found a way to make sudden death work yet, either wrong spell or it needs an effect index(probably)
+ // if (EXECUTE > 0 && (pTarget->GetHealthPct() < 20 || m_bot->HasAura(SUDDEN_DEATH)) && m_bot->GetPower(POWER_RAGE)/10 < 30 && m_ai->CastSpell (EXECUTE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (REND > 0 && !pTarget->HasAura(REND, EFFECT_0) && m_ai->CastSpell(REND, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (MORTAL_STRIKE > 0 && !m_bot->HasSpellCooldown(MORTAL_STRIKE) && m_ai->CastSpell(MORTAL_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHATTERING_THROW > 0 && !pTarget->HasAura(SHATTERING_THROW, EFFECT_0) && !m_bot->HasSpellCooldown(SHATTERING_THROW) && m_ai->CastSpell(SHATTERING_THROW, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (BLADESTORM > 0 && !m_bot->HasSpellCooldown(BLADESTORM) /*&& m_ai->GetAttackerCount() >= 3*/ && m_ai->CastSpell(BLADESTORM, *pTarget))
+ // return RETURN_CONTINUE;
+ // // No way to tell if overpower is active (yet), however taste for blood works
+ // if (OVERPOWER > 0 && m_bot->HasAura(TASTE_FOR_BLOOD) && m_ai->CastSpell(OVERPOWER, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SLAM > 0 && m_ai->CastSpell(SLAM, *pTarget))
+ // {
+ // //m_ai->SetIgnoreUpdateTime(1.5); // TODO: SetIgnoreUpdateTime takes a uint8 - how will 1.5 work as a value?
+ // return RETURN_CONTINUE;
+ // }
+
+ // case WARRIOR_SPEC_FURY:
+ // if (EXECUTE > 0 && pTarget->GetHealthPct() < 20 && m_ai->CastSpell (EXECUTE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (BLOODTHIRST > 0 && !m_bot->HasSpellCooldown(BLOODTHIRST) && m_ai->CastSpell(BLOODTHIRST, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (WHIRLWIND > 0 && !m_bot->HasSpellCooldown(WHIRLWIND) && m_ai->CastSpell(WHIRLWIND, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SLAM > 0 && m_bot->HasAura(BLOODSURGE, EFFECT_0) && m_ai->CastSpell(SLAM, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+
+ // case WARRIOR_SPEC_PROTECTION:
+ // if (m_ai->GetCombatOrder() & PlayerbotAI::ORDERS_TANK && !newTarget && TAUNT > 0 && !m_bot->HasSpellCooldown(TAUNT) && m_ai->CastSpell(TAUNT, *pTarget))
+ // return RETURN_CONTINUE;
+ // // No way to tell if revenge is active (yet)
+ // /*if (REVENGE > 0 && m_ai->CastSpell(REVENGE, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // if (REND > 0 && !pTarget->HasAura(REND, EFFECT_0) && m_ai->CastSpell(REND, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (THUNDER_CLAP > 0 && !pTarget->HasAura(THUNDER_CLAP) && m_ai->CastSpell(THUNDER_CLAP, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (DEMORALIZING_SHOUT > 0 && !pTarget->HasAura(DEMORALIZING_SHOUT, EFFECT_0) && m_ai->CastSpell(DEMORALIZING_SHOUT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CONCUSSION_BLOW > 0 && !m_bot->HasSpellCooldown(CONCUSSION_BLOW) && m_ai->CastSpell(CONCUSSION_BLOW, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHOCKWAVE > 0 && !m_bot->HasSpellCooldown(SHOCKWAVE) && m_ai->CastSpell(SHOCKWAVE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHIELD_SLAM > 0 && !m_bot->HasSpellCooldown(SHIELD_SLAM) && m_ai->CastSpell(SHIELD_SLAM, *pTarget))
+ // return RETURN_CONTINUE;
+ // /*if (SUNDER > 0 && !pTarget->HasAura(SUNDER_ARMOR) && m_ai->CastSpell(SUNDER, *pTarget))
+ // return RETURN_CONTINUE;*/
+ // if (HEROIC_STRIKE > 0 && m_ai->CastSpell(HEROIC_STRIKE, *pTarget))
+ // return RETURN_CONTINUE;
+
+ // /*case WarriorSpellPreventing:
+ // if (SHIELD_BASH > 0 && m_ai->CastSpell(SHIELD_BASH, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (PUMMEL > 0 && m_ai->CastSpell(PUMMEL, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SPELL_REFLECTION > 0 && !m_bot->HasAura(SPELL_REFLECTION, EFFECT_0) && m_ai->CastSpell(SPELL_REFLECTION, *m_bot))
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case WarriorBattle:
+ // if (LAST_STAND > 0 && !m_bot->HasAura(LAST_STAND, EFFECT_0) && m_bot->GetHealthPct() < 50 && m_ai->CastSpell(LAST_STAND, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (DEATH_WISH > 0 && !m_bot->HasAura(DEATH_WISH, EFFECT_0) && m_ai->CastSpell(DEATH_WISH, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (RETALIATION > 0 && pVictim == m_bot && m_ai->GetAttackerCount() >= 2 && !m_bot->HasAura(RETALIATION, EFFECT_0) && m_ai->CastSpell(RETALIATION, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (SWEEPING_STRIKES > 0 && m_ai->GetAttackerCount() >= 2 && !m_bot->HasAura(SWEEPING_STRIKES, EFFECT_0) && m_ai->CastSpell(SWEEPING_STRIKES, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (INTIMIDATING_SHOUT > 0 && m_ai->GetAttackerCount() > 5 && m_ai->CastSpell(INTIMIDATING_SHOUT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (ENRAGED_REGENERATION > 0 && !m_bot->HasAura(BERSERKER_RAGE, EFFECT_0) && !m_bot->HasAura(ENRAGED_REGENERATION, EFFECT_0) && m_bot->GetHealth() < m_bot->GetMaxHealth() * 0.5 && m_ai->CastSpell(ENRAGED_REGENERATION, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (HAMSTRING > 0 && !pTarget->HasAura(HAMSTRING, EFFECT_0) && m_ai->CastSpell(HAMSTRING, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CHALLENGING_SHOUT > 0 && pVictim != m_bot && m_bot->GetHealthPct() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_0) && m_ai->CastSpell(CHALLENGING_SHOUT, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (CLEAVE > 0 && m_ai->CastSpell(CLEAVE, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (PIERCING_HOWL > 0 && && m_ai->GetAttackerCount() >= 3 && !pTarget->HasAura(WAR_STOMP, EFFECT_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_0) && m_ai->CastSpell(PIERCING_HOWL, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (MOCKING_BLOW > 0 && pVictim != m_bot && m_bot->GetHealthPct() > 25 && !pTarget->HasAura(MOCKING_BLOW, EFFECT_0) && !pTarget->HasAura(CHALLENGING_SHOUT, EFFECT_0) && m_ai->CastSpell(MOCKING_BLOW, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (HEROIC_THROW > 0 && m_ai->CastSpell(HEROIC_THROW, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_TAUREN && !pTarget->HasAura(WAR_STOMP, EFFECT_0) && !pTarget->HasAura(PIERCING_HOWL, EFFECT_0) && !pTarget->HasAura(SHOCKWAVE, EFFECT_0) && !pTarget->HasAura(CONCUSSION_BLOW, EFFECT_0) && m_ai->CastSpell(WAR_STOMP, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_HUMAN && m_bot->HasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && m_ai->CastSpell(EVERY_MAN_FOR_HIMSELF, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_UNDEAD_PLAYER && m_bot->HasAuraType(SPELL_AURA_MOD_FEAR) || m_bot->HasAuraType(SPELL_AURA_MOD_CHARM) && m_ai->CastSpell(WILL_OF_THE_FORSAKEN, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_DWARF && m_bot->HasAuraState(AURA_STATE_DEADLY_POISON) && m_ai->CastSpell(STONEFORM, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_GNOME && m_bot->HasUnitState(UNIT_STAT_STUNNED) || m_bot->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) && m_ai->CastSpell(ESCAPE_ARTIST, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_NIGHTELF && pVictim == m_bot && m_bot->GetHealthPct() < 25 && !m_bot->HasAura(SHADOWMELD, EFFECT_0) && m_ai->CastSpell(SHADOWMELD, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_ORC && !m_bot->HasAura(BLOOD_FURY, EFFECT_0) && m_ai->CastSpell(BLOOD_FURY, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_TROLL && !m_bot->HasAura(BERSERKING, EFFECT_0) && m_ai->CastSpell(BERSERKING, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (m_bot->getRace() == RACE_DRAENEI && m_bot->GetHealthPct() < 25 && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_0) && m_ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot))
+ // return RETURN_CONTINUE;
+ // break;
+
+ // case WarriorDefensive:
+ // if (DISARM > 0 && !pTarget->HasAura(DISARM, EFFECT_0) && m_ai->CastSpell(DISARM, *pTarget))
+ // return RETURN_CONTINUE;
+ // if (SHIELD_BLOCK > 0 && !m_bot->HasAura(SHIELD_BLOCK, EFFECT_0) && m_ai->CastSpell(SHIELD_BLOCK, *m_bot))
+ // return RETURN_CONTINUE;
+ // if (SHIELD_WALL > 0 && !m_bot->HasAura(SHIELD_WALL, EFFECT_0) && m_ai->CastSpell(SHIELD_WALL, *m_bot))
+ // return RETURN_CONTINUE;
+ // break;*/
+ //}
+
+ return RETURN_NO_ACTION_OK;
+}
+
+CombatManeuverReturns PlayerbotWarriorAI::DoNextCombatManeuverPVP(Unit* pTarget)
+{
+ //if (m_ai->CastSpell(HEROIC_STRIKE))
+ // return RETURN_CONTINUE;
+
+ return DoNextCombatManeuverPVE(pTarget); // TODO: bad idea perhaps, but better than the alternative
+}
+
+//Buff and rebuff shouts
+void PlayerbotWarriorAI::CheckShouts()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //if (m_bot->GetSpec() == WARRIOR_SPEC_PROTECTION && COMMANDING_SHOUT > 0)
+ //{
+ // if (!m_bot->HasAura(COMMANDING_SHOUT, EFFECT_0) && m_ai->CastSpell(COMMANDING_SHOUT))
+ // return;
+ //}
+ //else // Not prot, or prot but no Commanding Shout yet
+ //{
+ // if (!m_bot->HasAura(BATTLE_SHOUT, EFFECT_0) && m_ai->CastSpell(BATTLE_SHOUT))
+ // return;
+ //}
+}
+
+void PlayerbotWarriorAI::DoNonCombatActions()
+{
+ //if (!m_ai) return;
+ //if (!m_bot) return;
+
+ //uint32 spec = m_bot->GetSpec();
+
+ ////Stance Check
+ //if (spec == WARRIOR_SPEC_ARMS && !m_bot->HasAura(BATTLE_STANCE, EFFECT_0))
+ // m_ai->CastSpell(BATTLE_STANCE);
+ //else if (spec == WARRIOR_SPEC_FURY && !m_bot->HasAura(BERSERKER_STANCE, EFFECT_0))
+ // m_ai->CastSpell(BERSERKER_STANCE);
+ //else if (spec == WARRIOR_SPEC_PROTECTION && !m_bot->HasAura(DEFENSIVE_STANCE, EFFECT_0))
+ // m_ai->CastSpell(DEFENSIVE_STANCE);
+
+ //// buff master with VIGILANCE
+ //if (VIGILANCE > 0)
+ // (!GetMaster()->HasAura(VIGILANCE, EFFECT_0) && m_ai->CastSpell(VIGILANCE, *GetMaster()));
+
+ //// hp check
+ //if (m_bot->getStandState() != UNIT_STAND_STATE_STAND)
+ // m_bot->SetStandState(UNIT_STAND_STATE_STAND);
+
+ //if (EatDrinkBandage(false))
+ // return;
+
+ //if (m_bot->getRace() == RACE_DRAENEI && !m_bot->HasAura(GIFT_OF_THE_NAARU, EFFECT_0) && m_bot->GetHealthPct() < 70)
+ //{
+ // m_ai->TellMaster("I'm casting gift of the naaru.");
+ // m_ai->CastSpell(GIFT_OF_THE_NAARU, *m_bot);
+ // return;
+ //}
+} // end DoNonCombatActions
+
+// Match up with "Pull()" below
+bool PlayerbotWarriorAI::CanPull()
+{
+ if (!me) return false;
+ if (!m_ai) return false;
+
+ if (me->GetUInt32Value(PLAYER_AMMO_ID)) // Having ammo equipped means a weapon is equipped as well. Probably. [TODO: does this work with throwing knives? Can a playerbot 'cheat' ammo into the slot without a proper weapon?]
+ {
+ // Can't do this, CanPull CANNOT check for anything that requires a target
+ //if (!m_ai->IsInRange(m_ai->GetCurrentTarget(), AUTO_SHOT))
+ //{
+ // m_ai->TellMaster("I'm out of range.");
+ // return false;
+ //}
+ return true;
+ }
+
+ return false;
+}
+
+// Match up with "CanPull()" above
+bool PlayerbotWarriorAI::Pull()
+{
+ //if (!m_bot) return false;
+ //if (!m_ai) return false;
+
+ //if (m_ai->GetCurrentTarget() && m_ai->GetCurrentTarget()->GetCombatReach() > ATTACK_DISTANCE)
+ //{
+ // if (!m_ai->IsInRange(m_ai->GetCurrentTarget(), AUTO_SHOT))
+ // {
+ // m_ai->TellMaster("I'm out of range.");
+ // return false;
+ // }
+
+ // // activate auto shot: Reworked to account for AUTO_SHOT being a triggered spell
+ // if (AUTO_SHOT && m_ai->GetCurrentSpellId() != AUTO_SHOT)
+ // {
+ // m_bot->CastSpell(m_ai->GetCurrentTarget(), AUTO_SHOT, true);
+ // return true;
+ // }
+ //}
+ //else // target is in melee range
+ //{
+ // m_ai->Attack(m_ai->GetCurrentTarget());
+ // return true;
+ //}
+
+ return false;
+}
diff --git a/src/server/game/AI/PlayerBots/bp_warr_ai.h b/src/server/game/AI/PlayerBots/bp_warr_ai.h
new file mode 100644
index 0000000..dcf1abc
--- /dev/null
+++ b/src/server/game/AI/PlayerBots/bp_warr_ai.h
@@ -0,0 +1,182 @@
+#ifndef _PlayerbotWarriorAI_H
+#define _PlayerbotWarriorAI_H
+
+#include "bp_cai.h"
+
+enum
+{
+ WarriorSpellPreventing,
+ WarriorBattle,
+ WarriorDefensive,
+ WarriorBerserker
+};
+
+enum WarriorSpells
+{
+ AUTO_SHOT_2 = 75,
+ BATTLE_SHOUT_1 = 6673,
+ BATTLE_STANCE_1 = 2457,
+ BERSERKER_RAGE_1 = 18499,
+ BERSERKER_STANCE_1 = 2458,
+ BLADESTORM_1 = 46924,
+ BLOODRAGE_1 = 2687,
+ BLOODTHIRST_1 = 23881,
+ CHALLENGING_SHOUT_1 = 1161,
+ CHARGE_1 = 100,
+ CLEAVE_1 = 845,
+ COMMANDING_SHOUT_1 = 469,
+ CONCUSSION_BLOW_1 = 12809,
+ DEATH_WISH_1 = 12292,
+ DEFENSIVE_STANCE_1 = 71,
+ DEMORALIZING_SHOUT_1 = 1160,
+ DEVASTATE_1 = 20243,
+ DISARM_1 = 676,
+ ENRAGED_REGENERATION_1 = 55694,
+ EXECUTE_1 = 5308,
+ HAMSTRING_1 = 1715,
+ HEROIC_FURY_1 = 60970,
+ HEROIC_STRIKE_1 = 78,
+ HEROIC_THROW_1 = 57755,
+ INTERCEPT_1 = 20252,
+ INTERVENE_1 = 3411,
+ INTIMIDATING_SHOUT_1 = 5246,
+ LAST_STAND_1 = 12975,
+ MOCKING_BLOW_1 = 694,
+ MORTAL_STRIKE_1 = 12294,
+ OVERPOWER_1 = 7384,
+ PIERCING_HOWL_1 = 12323,
+ PUMMEL_1 = 6552,
+ RECKLESSNESS_1 = 1719,
+ REND_1 = 772,
+ RETALIATION_1 = 20230,
+ REVENGE_1 = 6572,
+ SHATTERING_THROW_1 = 64382,
+ SHIELD_BASH_1 = 72,
+ SHIELD_BLOCK_1 = 2565,
+ SHIELD_SLAM_1 = 23922,
+ SHIELD_WALL_1 = 871,
+ SHOCKWAVE_1 = 46968,
+ SLAM_1 = 1464,
+ SPELL_REFLECTION_1 = 23920,
+ SUNDER_ARMOR_1 = 7386,
+ SWEEPING_STRIKES_1 = 12328,
+ TAUNT_1 = 355,
+ THUNDER_CLAP_1 = 6343,
+ VICTORY_RUSH_1 = 34428,
+ VIGILANCE_1 = 50720,
+ WHIRLWIND_1 = 1680,
+
+ //Procs
+ SLAM_PROC_1 = 46916,
+ BLOODSURGE_1 = 46915,
+ TASTE_FOR_BLOOD_1 = 56638,
+ SUDDEN_DEATH_1 = 52437
+};
+
+//class Player;
+
+class PlayerbotWarriorAI : PlayerbotClassAI
+{
+public:
+ PlayerbotWarriorAI(Player * const master, Player * const bot, PlayerbotAI * const ai);
+ virtual ~PlayerbotWarriorAI();
+
+ // all combat actions go here
+ CombatManeuverReturns DoFirstCombatManeuver(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuver(Unit* pTarget);
+ bool Pull();
+
+ // all non combat actions go here, ex buffs, heals, rezzes
+ void DoNonCombatActions();
+
+ //Buff/rebuff shouts
+ void CheckShouts();
+
+ // Utility Functions
+ bool CanPull();
+
+private:
+ CombatManeuverReturns DoFirstCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVE(Unit* pTarget);
+ CombatManeuverReturns DoFirstCombatManeuverPVP(Unit* pTarget);
+ CombatManeuverReturns DoNextCombatManeuverPVP(Unit* pTarget);
+
+ // ARMS
+ uint32 BATTLE_STANCE,
+ CHARGE,
+ HEROIC_STRIKE,
+ REND,
+ THUNDER_CLAP,
+ HAMSTRING,
+ MOCKING_BLOW,
+ RETALIATION,
+ SWEEPING_STRIKES,
+ MORTAL_STRIKE,
+ BLADESTORM,
+ HEROIC_THROW,
+ SHATTERING_THROW,
+ TASTE_FOR_BLOOD,
+ SUDDEN_DEATH;
+
+ // PROTECTION
+ uint32 DEFENSIVE_STANCE,
+ BLOODRAGE,
+ SUNDER_ARMOR,
+ TAUNT,
+ SHIELD_BASH,
+ REVENGE,
+ SHIELD_BLOCK,
+ DISARM,
+ SHIELD_WALL,
+ SHIELD_SLAM,
+ VIGILANCE,
+ DEVASTATE,
+ SHOCKWAVE,
+ CONCUSSION_BLOW,
+ SPELL_REFLECTION,
+ LAST_STAND;
+
+ // FURY
+ uint32 BERSERKER_STANCE,
+ BATTLE_SHOUT,
+ DEMORALIZING_SHOUT,
+ OVERPOWER,
+ CLEAVE,
+ INTIMIDATING_SHOUT,
+ EXECUTE,
+ CHALLENGING_SHOUT,
+ SLAM,
+ INTERCEPT,
+ DEATH_WISH,
+ BERSERKER_RAGE,
+ WHIRLWIND,
+ PUMMEL,
+ BLOODTHIRST,
+ RECKLESSNESS,
+ RAMPAGE,
+ HEROIC_FURY,
+ COMMANDING_SHOUT,
+ ENRAGED_REGENERATION,
+ PIERCING_HOWL,
+ SLAM_PROC,
+ BLOODSURGE;
+
+ // racial
+ uint32 ARCANE_TORRENT,
+ GIFT_OF_THE_NAARU,
+ STONEFORM,
+ ESCAPE_ARTIST,
+ EVERY_MAN_FOR_HIMSELF,
+ SHADOWMELD,
+ BLOOD_FURY,
+ WAR_STOMP,
+ BERSERKING,
+ WILL_OF_THE_FORSAKEN;
+
+ // general
+ uint32 AUTO_SHOT;
+
+ uint32 SpellSequence;
+};
+
+#endif
diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt
index f455610..2a13db8 100644
--- a/src/server/game/CMakeLists.txt
+++ b/src/server/game/CMakeLists.txt
@@ -129,6 +129,8 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/Achievements
${CMAKE_CURRENT_SOURCE_DIR}/Addons
${CMAKE_CURRENT_SOURCE_DIR}/AI
+ ${CMAKE_CURRENT_SOURCE_DIR}/AI/NpcBots
+ ${CMAKE_CURRENT_SOURCE_DIR}/AI/PlayerBots
${CMAKE_CURRENT_SOURCE_DIR}/AI/CoreAI
${CMAKE_CURRENT_SOURCE_DIR}/AI/EventAI
${CMAKE_CURRENT_SOURCE_DIR}/AI/ScriptedAI
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 52ca60a..c752db4 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -54,6 +54,9 @@
// apply implementation of the singletons
+// npcbot
+#include "bot_ai.h"
+
TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const
{
TrainerSpellMap::const_iterator itr = spellList.find(spell_id);
@@ -165,6 +168,13 @@ m_creatureInfo(NULL), m_creatureData(NULL), m_path_id(0), m_formation(NULL)
ResetLootMode(); // restore default loot mode
TriggerJustRespawned = false;
m_isTempWorldObject = false;
+
+ //bot
+ m_bot_owner = NULL;
+ m_creature_owner = NULL;
+ m_bots_pet = NULL;
+ m_bot_class = CLASS_NONE;
+ bot_AI = NULL;
}
Creature::~Creature()
@@ -233,6 +243,8 @@ void Creature::RemoveCorpse(bool setSpawnTime)
{
if (getDeathState() != CORPSE)
return;
+ if (bot_AI)
+ return;
m_corpseRemoveTime = time(NULL);
setDeathState(DEAD);
@@ -2683,3 +2695,119 @@ Unit* Creature::SelectNearestHostileUnitInAggroRange(bool useLOS) const
return target;
}
+
+void Creature::SetIAmABot(bool bot)
+{
+ if (!bot)
+ {
+ IsAIEnabled = false;
+ bot_AI = NULL;
+ SetUInt64Value(UNIT_FIELD_CREATEDBY, 0);
+ }
+}
+
+void Creature::SetBotsPetDied()
+{
+ if (!m_bots_pet)
+ return;
+ m_bots_pet->SetCharmerGUID(0);
+ m_bots_pet->SetCreatureOwner(NULL);
+ //m_bots_pet->GetBotPetAI()->SetCreatureOwner(NULL);
+ m_bots_pet->SetIAmABot(false);
+ m_bot_owner->SetMinion((Minion*)m_bots_pet, false);
+ m_bots_pet->CleanupsBeforeDelete();
+ m_bots_pet->AddObjectToRemoveList();
+ m_bots_pet = NULL;
+}
+
+void Creature::SetBotTank(Unit* newtank)
+{
+ if (!bot_AI || !IsAIEnabled)
+ return;
+ uint64 tankGuid = bot_AI->GetBotTankGuid();
+ if (newtank && newtank->GetGUID() == tankGuid) return;
+ Creature* oldtank = tankGuid && IS_CREATURE_GUID(tankGuid) ? sObjectAccessor->GetObjectInWorld(tankGuid, (Creature* )NULL) : NULL;
+ if (oldtank && oldtank->IsInWorld() && (oldtank->GetIAmABot() || oldtank->GetIAmABotsPet()))
+ {
+ oldtank->RemoveAurasDueToSpell(DEFENSIVE_STANCE_PASSIVE);
+ uint8 ClassOrPetType = oldtank->GetIAmABotsPet() ? bot_pet_ai::GetPetType(oldtank) : oldtank->GetBotClass();
+ oldtank->GetBotAI()->ApplyPassives(ClassOrPetType);
+ }
+ if (newtank == this)
+ {
+ for (uint8 i = 0; i < 3; ++i)
+ AddAura(DEFENSIVE_STANCE_PASSIVE, this);
+ if (Player* owner = m_bot_owner)
+ {
+ switch (urand(1,5))
+ {
+ case 1: MonsterWhisper("I am tank here!", owner->GetGUID()); break;
+ case 2: MonsterWhisper("I will tank now.", owner->GetGUID()); break;
+ case 3: MonsterWhisper("I gonna tank", owner->GetGUID()); break;
+ case 4: MonsterWhisper("I think I will be best tank here...", owner->GetGUID()); break;
+ case 5: MonsterWhisper("I AM the tank!", owner->GetGUID()); break;
+ }
+ }
+ bot_AI->UpdateHealth();
+ if (!isInCombat())
+ SetBotCommandState(COMMAND_FOLLOW, true);
+ }
+ bot_AI->SetBotTank(newtank);
+}
+
+void Creature::SetBotCommandState(CommandStates st, bool force)
+{
+ if (bot_AI && IsAIEnabled)
+ bot_AI->SetBotCommandState(st, force);
+}
+//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);
+}
+
+bool Creature::GetIAmABot() const
+{
+ return bot_AI ? bot_AI->IsMinionAI() : false;
+}
+
+bool Creature::GetIAmABotsPet() const
+{
+ return bot_AI ? bot_AI->IsPetAI() : false;
+}
+
+bot_minion_ai* Creature::GetBotMinionAI() const
+{
+ return IsAIEnabled && bot_AI && bot_AI->IsMinionAI() ? const_cast<bot_minion_ai*>(bot_AI->GetMinionAI()) : NULL;
+}
+
+bot_pet_ai* Creature::GetBotPetAI() const
+{
+ return IsAIEnabled && bot_AI && bot_AI->IsPetAI() ? const_cast<bot_pet_ai*>(bot_AI->GetPetAI()) : NULL;
+}
+
+void Creature::InitBotAI(bool asPet)
+{
+ ASSERT(!bot_AI);
+
+ if (asPet)
+ bot_AI = (bot_pet_ai*)AI();
+ else
+ bot_AI = (bot_minion_ai*)AI();
+}
+
+void Creature::SetBotShouldUpdateStats()
+{
+ if (bot_AI) bot_AI->SetShouldUpdateStats();
+}
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 6e771d2..8a78e77 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -37,6 +37,11 @@ class Player;
class WorldSession;
class CreatureGroup;
+// 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
@@ -694,6 +699,31 @@ class Creature : public Unit, public GridObject<Creature>, public MapCreature
bool m_isTempWorldObject; //true when possessed
+ //Bot commands
+ Player* GetBotOwner() const { return m_bot_owner; }
+ void SetBotOwner(Player* newowner) { m_bot_owner = 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; }
+ void SetIAmABot(bool bot = true);
+ bool GetIAmABot() const;
+ bool GetIAmABotsPet() const;
+ void SetBotClass(uint8 myclass) { m_bot_class = myclass; }
+ uint8 GetBotClass() const { return m_bot_class; }
+ void SetBotTank(Unit* newtank);
+ bot_ai* GetBotAI() const { return bot_AI; }
+ bot_minion_ai* GetBotMinionAI() const;
+ bot_pet_ai* GetBotPetAI() const;
+ void InitBotAI(bool asPet = false);
+ void SetBotCommandState(CommandStates st, bool force = false);
+ 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 SetBotShouldUpdateStats();
+ //Bot commands
+
protected:
bool CreateFromProto(uint32 guidlow, uint32 Entry, uint32 vehId, uint32 team, const CreatureData* data = NULL);
bool InitEntry(uint32 entry, uint32 team=ALLIANCE, const CreatureData* data=NULL);
@@ -744,6 +774,14 @@ class Creature : public Unit, public GridObject<Creature>, public MapCreature
bool IsInvisibleDueToDespawn() const;
bool CanAlwaysSee(WorldObject const* obj) const;
private:
+ //bot system
+ Player* m_bot_owner;
+ Creature* m_creature_owner;
+ Creature* m_bots_pet;
+ bot_ai* bot_AI;
+ uint8 m_bot_class;
+ //end bot system
+
void ForcedDespawn(uint32 timeMSToDespawn = 0);
//WaypointMovementGenerator vars
diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp
index d23af7e..805b6ad 100644
--- a/src/server/game/Entities/Creature/TemporarySummon.cpp
+++ b/src/server/game/Entities/Creature/TemporarySummon.cpp
@@ -253,6 +253,14 @@ void TempSummon::UnSummon(uint32 msTime)
if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled)
owner->ToCreature()->AI()->SummonedCreatureDespawn(this);
+//npcbot
+ if (GetIAmABot() || GetIAmABotsPet())
+ {
+ //sLog->outError("TempSummon::UnSummon(): Trying to unsummon Bot %s(owner: %s). Aborted", GetName(), GetBotOwner()->GetName());
+ return;
+ }
+//end npcbots
+
AddObjectToRemoveList();
}
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 49d210f..4038490 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -79,6 +79,11 @@
#include "WorldPacket.h"
#include "WorldSession.h"
+//Bot mod
+#include "Config.h"
+#include "bp_ai.h"
+#include "bp_mgr.h"
+
#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)
#define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3))
@@ -521,6 +526,18 @@ inline void KillRewarder::_RewardXP(Player* player, float rate)
for (Unit::AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
AddPct(xp, (*i)->GetAmount());
+ //npcbot 4.2.2.1. Apply NpcBot XP reduction
+ if (player->HaveBot() && player->GetNpcBotsCount() > 1)
+ {
+ if (uint8 xp_rate = player->GetNpcBotXpReduction())
+ {
+ int32 ratePct = 100 - (player->GetNpcBotsCount() - 1) * xp_rate;
+ ratePct = std::max<int32>(ratePct, 10); // minimum
+ //ratePct = std::min<int32>(ratePct, 100); // maximum // dead code
+ xp = xp * ratePct / 100;
+ }
+ }
+
// 4.2.3. Give XP to player.
player->GiveXP(xp, _victim, _groupRate);
if (Pet* pet = player->GetPet())
@@ -858,6 +875,32 @@ Player::Player(WorldSession* session): Unit(true)
m_ChampioningFaction = 0;
+ ///////////////////// Bot System ////////////////////////
+ //Playerbot
+ m_playerbotAI = NULL;
+ m_playerbotMgr = NULL;
+ //Npcbot
+ m_botTimer = 500;
+ m_bot = NULL;
+ m_botTankGuid = 0;
+ m_enableNpcBots = ConfigMgr::GetBoolDefault("Bot.EnableNpcBots", true);
+ m_followdist = ConfigMgr::GetIntDefault("Bot.BaseFollowDistance", 30);
+ m_maxNpcBots = std::min<uint8>(ConfigMgr::GetIntDefault("Bot.MaxNpcBots", 1), MAX_NPCBOTS);
+ uint8 maxcbots = ConfigMgr::GetIntDefault("Bot.MaxNpcBotsPerClass", 1);
+ m_maxClassNpcBots = maxcbots > 0 ? maxcbots : MAX_NPCBOTS;
+ m_xpReductionNpcBots = std::min<uint8>(ConfigMgr::GetIntDefault("Bot.XpReductionPercent", 0), 100);
+ m_enableAllNpcBots = ConfigMgr::GetBoolDefault("Bot.AllowAllClasses", false);
+ m_enableNpcBotsArenas = ConfigMgr::GetBoolDefault("Bot.EnableInArenas", true);
+ m_enableNpcBotsBGs = ConfigMgr::GetBoolDefault("Bot.EnableInBGs", true);
+ m_enableNpcBotsDungeons = ConfigMgr::GetBoolDefault("Bot.EnableInDungeons", true);
+ m_enableNpcBotsRaids = ConfigMgr::GetBoolDefault("Bot.EnableInRaids", true);
+ m_limitNpcBotsDungeons = ConfigMgr::GetBoolDefault("Bot.InstanceLimit.Dungeons", false);
+ m_limitNpcBotsRaids = ConfigMgr::GetBoolDefault("Bot.InstanceLimit.Raids", false);
+ m_NpcBotsCost = ConfigMgr::GetIntDefault("Bot.Cost", 0);
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ m_botmap[i] = new NpcBotMap();
+ ///////////////////// End Bot System ////////////////////////
+
for (uint8 i = 0; i < MAX_POWERS; ++i)
m_powerFraction[i] = 0;
@@ -910,6 +953,22 @@ Player::~Player()
delete m_achievementMgr;
delete m_reputationMgr;
+ //Playerbot mod: delete mgr/ai
+ if (m_playerbotAI)
+ {
+ delete m_playerbotAI;
+ m_playerbotAI = NULL;
+ }
+ else if (m_playerbotMgr)
+ {
+ delete m_playerbotMgr;
+ m_playerbotMgr = NULL;
+ }
+
+ //Npcbot mod: delete botmap
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ delete m_botmap[i];
+
sWorld->DecreasePlayerCount();
}
@@ -1834,6 +1893,23 @@ void Player::Update(uint32 p_time)
//because we don't want player's ghost teleported from graveyard
if (IsHasDelayedTeleport() && isAlive())
TeleportTo(m_teleport_dest, m_teleport_options);
+
+ //Playerbot mod: Update
+ if (m_playerbotAI)
+ m_playerbotAI->UpdateAI(p_time);
+ //else if (m_playerbotMgr)
+ // m_playerbotMgr->Update(p_time);
+
+ //NpcBot mod: Update
+ if (m_botTimer > 0)
+ {
+ if (p_time >= m_botTimer)
+ m_botTimer = 0;
+ else
+ m_botTimer -= p_time;
+ }
+ else
+ RefreshBot(p_time);
}
void Player::setDeathState(DeathState s)
@@ -2097,6 +2173,19 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati
return false;
}
+ //Playerbot mod: tell bots to stop following master
+ //so they don't try to follow the master after he teleports
+ //TODO: this should be deleted after pointed movement implementation
+ if (m_playerbotMgr != NULL)
+ m_playerbotMgr->Stay();
+
+ //Npcbot mod: prevent crash on InstanceMap::DestroyInstance()... Unit::RemoveFromWorld()
+ //if last player being kicked out of instance while having npcbots
+ //we must remove creature Before it will be removed in Map::UnloadAll()
+ if (GetMapId() != mapid)
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ RemoveBot(m_botmap[i]->m_guid);
+
// preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
Pet* pet = GetPet();
@@ -2461,6 +2550,764 @@ void Player::RemoveFromWorld()
}
}
+void Player::RefreshBot(uint32 diff)
+{
+ if (m_botTimer > 0) return;
+ m_botTimer = 100 + isInFlight()*3000;//x2 ms //temp hack
+ if (!HaveBot()) return;
+
+ //addition for revive timer (maybe we should check whole party?)
+ bool partyInCombat = isInCombat();
+ if (!partyInCombat)
+ {
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ if (Creature* bot = m_botmap[i]->m_creature)
+ {
+ if (bot->isInCombat())
+ {
+ partyInCombat = true;
+ break;
+ }
+ else if (Creature* pet = bot->GetBotsPet())
+ {
+ if (pet->isInCombat())
+ {
+ partyInCombat = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ uint64 guid = m_botmap[i]->m_guid;
+ m_bot = m_botmap[i]->m_creature;
+ if (!m_bot || !m_bot->IsInWorld())
+ continue;
+ //BOT REVIVE SUPPORT
+ //Do not allow bot to be revived if master is in battle
+ if (!partyInCombat)
+ {
+ if (m_botmap[i]->m_reviveTimer > diff)
+ {
+ if (!isInCombat())
+ m_botmap[i]->m_reviveTimer -= diff;
+ }
+ else if (m_botmap[i]->m_reviveTimer > 0)
+ m_botmap[i]->m_reviveTimer = 0;
+ }
+ if ((m_bot->isDead() || !m_bot->isAlive()) && isAlive() && !isInCombat() && !InArena() && !isInFlight() &&
+ !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH) &&
+ m_botmap[i]->m_reviveTimer == 0 &&
+ !HasInvisibilityAura() && !HasStealthAura())
+ {
+ CreateBot(0, 0, 0, false, true);//revive
+ continue;
+ }
+ //BOT MUST DIE SUPPORT
+ if (isInFlight() || !GetGroup() || !GetGroup()->IsMember(m_bot->GetGUID()))//even if bot is dead
+ {
+ RemoveBot(guid, !isInFlight());
+ continue;
+ }
+ //TELEPORT/OUTRUN SUPPORT
+ if (!isInFlight() && isAlive() && (m_bot->isAlive() || m_bot->GetMapId() != GetMapId()))
+ {
+ float maxdist;
+ if (GetMap()->IsDungeon())
+ maxdist = sWorld->GetMaxVisibleDistanceInInstances();
+ else if (GetMap()->IsBattlegroundOrArena())
+ maxdist = sWorld->GetMaxVisibleDistanceInBGArenas();
+ else
+ maxdist = sWorld->GetMaxVisibleDistanceOnContinents();
+
+ maxdist += 20.0f; //allow player to recall it by moving back
+
+ if (abs(m_bot->GetPositionX() - GetPositionX()) > maxdist ||
+ abs(m_bot->GetPositionY() - GetPositionY()) > maxdist ||
+ m_bot->GetMapId() != GetMapId() || RestrictBots())
+ {
+ RemoveBot(guid);
+ continue;
+ }
+ }
+ m_bot = NULL;
+ }//end for botmap
+ //BOT CREATION/RECREATION SUPPORT
+ if (!isInFlight() && isAlive() && !HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && GetBotMustBeCreated() && !RestrictBots())
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry != 0 && m_botmap[pos]->m_guid == 0)
+ CreateBot(m_botmap[pos]->m_entry, m_botmap[pos]->m_race, m_botmap[pos]->m_class, m_botmap[pos]->tank);
+}
+
+void Player::SetBotMustBeCreated(uint32 m_entry, uint8 m_race, uint8 m_class, bool istank)
+{
+ if (m_enableNpcBots == false)
+ {
+ ChatHandler ch(GetSession());
+ ch.SendSysMessage("NpcBot system currently disabled. Please contact your administration.");
+ ClearBotMustBeCreated(0, 0, true);
+ return;
+ }
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ {
+ if (m_botmap[pos]->m_entry == 0)
+ {
+ m_botmap[pos]->m_guid = 0;//we need it to make sure Player::CreateBot will find this slot
+ m_botmap[pos]->m_entry = m_entry;
+ m_botmap[pos]->m_race = m_race;
+ m_botmap[pos]->m_class = m_class;
+ m_botmap[pos]->tank = istank;
+ break;
+ }
+ }
+}
+
+bool Player::GetBotMustBeCreated()
+{
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ {
+ if (m_botmap[pos]->m_entry != 0 &&
+ (m_botmap[pos]->m_guid == 0 || !sObjectAccessor->FindUnit(m_botmap[pos]->m_guid)))
+ {
+ m_botmap[pos]->m_guid = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::ClearBotMustBeCreated(uint64 guidOrSlot, bool guid, bool fully)
+{
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ {
+ if ((guid == true && m_botmap[pos]->m_guid == guidOrSlot) ||
+ (guid == false && pos == guidOrSlot) ||
+ fully)
+ {
+ m_botmap[pos]->m_guid = 0;
+ m_botmap[pos]->m_entry = 0;
+ m_botmap[pos]->m_race = 0;
+ m_botmap[pos]->m_class = 0;
+ m_botmap[pos]->m_creature = NULL;
+ m_botmap[pos]->tank = false;
+ if (!fully)
+ break;
+ }
+ }
+}
+
+void Player::RemoveBot(uint64 guid, bool final, bool eraseFromDB)
+{
+ if (guid == 0) return;
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ if (m_botmap[i]->m_guid == guid)
+ {
+ m_bot = m_botmap[i]->m_creature;
+ break;
+ }
+ }
+ if (!m_bot)
+ m_bot = sObjectAccessor->GetObjectInWorld(guid, (Creature*)NULL);
+ if (m_bot)
+ {
+ //do not disband group unless not in dungeon or forced or on logout (Check WorldSession::LogoutPlayer())
+ Group* gr = GetGroup();
+ if (gr && gr->IsMember(guid))
+ {
+ if (gr->GetMembersCount() > 2 || /*!GetMap()->Instanceable() || */(final && eraseFromDB))
+ gr->RemoveMember(guid);
+ else //just cleanup
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER);
+ stmt->setUInt32(0, GUID_LOPART(guid));
+ CharacterDatabase.Execute(stmt);
+ }
+ }
+
+ m_bot->SetBotsPetDied();
+ m_bot->SetCharmerGUID(0);
+ //m_bot->SetBotOwner(NULL);
+ m_bot->SetIAmABot(false);
+ SetMinion((Minion*)m_bot, false);
+ m_bot->CleanupsBeforeDelete();
+ m_bot->AddObjectToRemoveList();
+
+ if (final)//on logout or by command
+ {
+ ClearBotMustBeCreated(guid);
+ if (eraseFromDB)//by command
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT);
+ stmt->setUInt32(0, GetGUIDLow());
+ stmt->setUInt32(1, m_bot->GetEntry());
+ CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("DELETE FROM `character_npcbot` WHERE `owner` = '%u' AND `entry` = '%u'", GetGUIDLow(), m_bot->GetEntry());
+ }
+ }
+ else
+ {
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ {
+ if (m_botmap[pos]->m_guid == guid)
+ {
+ m_botmap[pos]->m_guid = 0;//reset guid so it can be set during recreation
+ m_botmap[pos]->m_creature = NULL;
+ }
+ }
+ }
+ m_bot = NULL;
+ }
+}
+
+void Player::CreateBot(uint32 botentry, uint8 botrace, uint8 botclass, bool istank, bool revive)
+{
+ if (IsBeingTeleported() || isInFlight()) return; //don't create bot yet
+ if (isDead() && !revive) return; //not to revive by command so abort
+ if (isInCombat()) return;
+
+ if (m_bot != NULL && revive)
+ {
+ m_bot->SetHealth(uint32(float(m_bot->GetCreateHealth()) * 0.15f));//15% of base health
+ if (m_bot->getPowerType() == POWER_MANA)
+ m_bot->SetPower(POWER_MANA, m_bot->GetCreateMana());
+ m_bot->setDeathState(ALIVE);
+ m_bot->SetBotCommandState(COMMAND_FOLLOW, true);
+ return;
+ }
+ if (m_enableNpcBots == false && revive == false)
+ {
+ ChatHandler ch(GetSession());
+ ch.SendSysMessage("NpcBot system currently disabled. Please contact administration.");
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry == botentry)
+ ClearBotMustBeCreated(pos, false);
+ return;
+ }
+ if (!botentry || !botrace || !botclass)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "ERROR! CreateBot(): player %s (%u) trying to create bot with entry = %u, race = %u, class = %u, ignored", GetName().c_str(), GetGUIDLow(), botentry, botrace, botclass);
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry == botentry)
+ ClearBotMustBeCreated(pos, false);
+ return;
+ }
+ //npcbot counter is already increased in SetBotMustBeCreated()
+ if (GetNpcBotsCount() > GetMaxNpcBots())
+ {
+ ChatHandler ch(GetSession());
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry == botentry)
+ ClearBotMustBeCreated(pos, false);
+ ch.PSendSysMessage("Youre exceed max npcbots");
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+ //instance limit check
+ if ((m_limitNpcBotsDungeons && GetMap()->IsNonRaidDungeon()) || (m_limitNpcBotsRaids && GetMap()->IsRaid()))
+ {
+ InstanceMap* map = (InstanceMap*)GetMap();
+ uint32 count = 0;
+ Map::PlayerList const& plMap = map->GetPlayers();
+ for (Map::PlayerList::const_iterator itr = plMap.begin(); itr != plMap.end(); ++itr)
+ if (Player* player = itr->getSource())
+ count += (1 + player->GetNpcBotsCount());
+
+ //check "more" cuz current bot is queued and we are to choose to remove it or not
+ if (count > map->GetMaxPlayers())
+ {
+ ChatHandler ch(GetSession());
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry == botentry)
+ ClearBotMustBeCreated(pos, false);
+ ch.PSendSysMessage("Instance players limit exceed");
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+ }
+ if (GetGroup() && GetGroup()->isRaidGroup() && GetGroup()->IsFull())
+ {
+ ChatHandler ch(GetSession());
+ ch.PSendSysMessage("Your group is Full!");
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry == botentry)
+ if (m_botmap[pos]->m_reviveTimer != 0)
+ return;
+
+ m_bot = SummonCreature(botentry, *this);
+
+ //check if we have free slot
+ bool _set = false;
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ {
+ if (m_botmap[pos]->m_entry == botentry && m_botmap[pos]->m_guid == 0)
+ {
+ m_botmap[pos]->m_guid = m_bot->GetGUID();
+ m_botmap[pos]->m_creature = m_bot;//this will save some time but we need guid as well
+ m_botmap[pos]->tank = istank;
+ _set = true;
+ break;
+ }
+ }
+ if (!_set)
+ {
+ sLog->outError(LOG_FILTER_PLAYER, "character %s (%u) is failed to create npcbot! Removing all bots", GetName().c_str(), GetGUIDLow());
+
+ m_bot->CombatStop();
+ m_bot->CleanupsBeforeDelete();
+ m_bot->AddObjectToRemoveList();
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ RemoveBot(m_botmap[pos]->m_guid, true);
+ ClearBotMustBeCreated(0, false, true);
+ return;
+ }
+
+ m_bot->SetBotOwner(this);
+ m_bot->SetUInt64Value(UNIT_FIELD_CREATEDBY, GetGUID());
+ SetMinion((Minion*)m_bot, true);
+ m_bot->CombatStop();
+ m_bot->DeleteThreatList();
+ m_bot->AddUnitTypeMask(UNIT_MASK_MINION);
+
+ m_bot->SetByteValue(UNIT_FIELD_BYTES_0, 0, botrace);
+ m_bot->setFaction(getFaction());
+ m_bot->SetLevel(getLevel());
+ m_bot->SetBotClass(botclass);
+ m_bot->AIM_Initialize();
+ m_bot->InitBotAI();
+ m_bot->SetBotCommandState(COMMAND_FOLLOW, true);
+
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ //entry is unique for each master's bot so clean it up just in case
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT);
+ stmt->setUInt32(0, GetGUIDLow());
+ stmt->setUInt32(1, botentry);
+ trans->Append(stmt);
+ //CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("DELETE FROM `character_npcbot` WHERE `owner` = '%u' AND `entry` = '%u'", GetGUIDLow(), botentry);
+ //add the new entry
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT);
+ stmt->setUInt32(0, GetGUIDLow());
+ stmt->setUInt32(1, botentry);
+ stmt->setUInt8(2, botrace);
+ stmt->setUInt8(3, botclass);
+ stmt->setUInt8(4, uint8(istank));
+ trans->Append(stmt);
+ CharacterDatabase.CommitTransaction(trans);
+ //CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("INSERT INTO `character_npcbot` (owner,entry,race,class,istank) VALUES ('%u','%u','%u','%u','%u')", GetGUIDLow(), botentry, botrace, botclass, uint8(istank));
+ //If we have a group, just add bot
+ if (Group* gr = GetGroup())
+ {
+ if (!gr->IsFull())
+ {
+ if (!gr->AddMember((Player*)m_bot))
+ RemoveBot(m_bot->GetGUID(), true);
+ }
+ else if (!gr->isRaidGroup()) //non-raid group is full
+ {
+ gr->ConvertToRaid();
+ if (!gr->AddMember((Player*)m_bot))
+ RemoveBot(m_bot->GetGUID(), true);
+ }
+ else //raid group is full
+ RemoveBot(m_bot->GetGUID(), true);
+ }
+ else
+ {
+ gr = new Group;
+ if (!gr->Create(this))
+ {
+ delete gr;
+ return;
+ }
+ sGroupMgr->AddGroup(gr);
+ if (!gr->AddMember((Player*)m_bot))
+ RemoveBot(m_bot->GetGUID(), true);
+ }
+
+ if (Group* gr = GetGroup())
+ {
+ Group::MemberSlotList const a = gr->GetMemberSlots();
+ //try to remove 'absent' bots
+ for (Group::member_citerator itr = a.begin(); itr != a.end(); ++itr)
+ {
+ if (itr->guid == 0)
+ continue;
+ if (IS_PLAYER_GUID(itr->guid))
+ continue;
+ if (!sObjectAccessor->FindUnit(itr->guid))
+ gr->RemoveMember(itr->guid);
+ }
+ }
+
+} //end Player::CreateBot
+
+uint8 Player::GetNpcBotsCount() const
+{
+ uint8 bots = 0;
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_entry != 0)
+ ++bots;
+ return bots;
+}
+
+uint8 Player::GetMaxNpcBots() const
+{
+ return (GetSession()->GetSecurity() == SEC_PLAYER) ? m_maxNpcBots : MAX_NPCBOTS;
+}
+
+bool Player::HaveBot() const
+{
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ if (m_botmap[i]->m_entry != 0)
+ return true;
+ return false;
+}
+
+void Player::SendBotCommandState(Creature* cre, CommandStates state)
+{
+ if (!cre) return;
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ if (m_botmap[i]->m_creature == cre)
+ cre->SetBotCommandState(state, true);
+}
+//finds bot's slot into master's botmap
+uint8 Player::GetNpcBotSlot(uint64 guid) const
+{
+ if (guid)
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ if (m_botmap[i]->m_guid == guid)
+ return i;
+ return 0;
+}
+
+void Player::SetBotTank(uint64 guid)
+{
+ m_botTankGuid = guid;
+ if (guid == 0 || !IS_CREATURE_GUID(guid)) //reset tank or set player - remove from all npcbots
+ {
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ if (m_botmap[i]->tank == true)
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_TANK);
+ stmt->setUInt8(0, uint8(0));
+ stmt->setUInt32(1, GetGUIDLow());
+ stmt->setUInt32(2, m_botmap[i]->m_entry);
+ CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("UPDATE `character_npcbot` SET `istank` = '0' WHERE `owner` = '%u' AND `entry` = '%u'", GetGUIDLow(), m_botmap[i]->m_entry);
+ m_botmap[i]->tank = false;
+ }
+ }
+ return;
+ }
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ if (m_botmap[i]->tank == true)
+ {
+ if (m_botmap[i]->m_guid != guid)
+ {
+ m_botmap[i]->tank = false;
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_TANK);
+ stmt->setUInt8(0, uint8(0));
+ stmt->setUInt32(1, GetGUIDLow());
+ stmt->setUInt32(2, m_botmap[i]->m_entry);
+ CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("UPDATE `character_npcbot` SET `istank` = '0' WHERE `owner` = '%u' AND `entry` = '%u'", GetGUIDLow(), m_botmap[i]->m_entry);
+ }
+ }
+ else if (m_botmap[i]->m_guid == guid)
+ {
+ m_botmap[i]->tank = true;
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_TANK);
+ stmt->setUInt8(0, uint8(1));
+ stmt->setUInt32(1, GetGUIDLow());
+ stmt->setUInt32(2, m_botmap[i]->m_entry);
+ CharacterDatabase.Execute(stmt);
+ //CharacterDatabase.PExecute("UPDATE `character_npcbot` SET `istank` = '1' WHERE `owner` = '%u' AND `entry` = '%u'", GetGUIDLow(), m_botmap[i]->m_entry);
+ break;
+ }
+ }
+}
+
+Unit* Player::GetBotTank(uint32 entry)
+{
+ if (!entry) return NULL;
+
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ if (m_botmap[i]->m_entry == entry && m_botmap[i]->tank == true)
+ return m_botmap[i]->m_creature;
+
+ return NULL;
+}
+
+void Player::SetNpcBotDied(uint64 guid)
+{
+ if (!guid) return;
+ for (uint8 pos = 0; pos != GetMaxNpcBots(); ++pos)
+ if (m_botmap[pos]->m_guid == guid)
+ {
+ m_botmap[pos]->m_reviveTimer = 15000;
+ break;
+ }
+}
+
+bool Player::RestrictBots() const
+{
+ return
+ (!m_enableNpcBotsBGs && GetMap()->IsBattleground()) ||
+ (!m_enableNpcBotsArenas && GetMap()->IsBattleArena()) ||
+ (!m_enableNpcBotsDungeons && GetMap()->IsNonRaidDungeon()) ||
+ (!m_enableNpcBotsRaids && GetMap()->IsRaid());
+}
+
+uint32 Player::GetNpcBotCost() const
+{
+ return m_NpcBotsCost ? uint32((m_NpcBotsCost / 80.f) * getLevel()) : 0;
+}
+
+std::string Player::GetNpcBotCostStr() const
+{
+ std::ostringstream money;
+
+ if (uint32 cost = GetNpcBotCost())
+ {
+ 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";
+ money << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t";
+ }
+ return money.str();
+}
+
+//NPCbot base setup
+void Player::CreateNPCBot(uint8 bot_class)
+{
+ //check if we have too many bots of that class
+ if (HaveBot())
+ {
+ uint8 count = 0;
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ if (m_botmap[i]->m_class == bot_class)
+ ++count;
+ if (count >= m_maxClassNpcBots)
+ {
+ SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
+ ChatHandler ch(GetSession());
+ ch.PSendSysMessage("You cannot have more bots of that class! %u of %u", count, m_maxClassNpcBots);
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+ }
+
+ //check if not allowed class is chosen
+ if (!m_enableAllNpcBots &&
+ (bot_class != CLASS_WARRIOR &&
+ bot_class != CLASS_PALADIN &&
+ bot_class != CLASS_PRIEST &&
+ bot_class != CLASS_MAGE &&
+ bot_class != CLASS_DRUID &&
+ bot_class != CLASS_WARLOCK &&
+ bot_class != CLASS_ROGUE))
+ {
+ ChatHandler ch(GetSession());
+ const char* bclass;
+ switch (bot_class)
+ {
+ case CLASS_DEATH_KNIGHT: bclass = "DeathKnight"; break;
+ case CLASS_SHAMAN: bclass = "Shaman"; break;
+ case CLASS_HUNTER: bclass = "Hunter"; break;
+ default: bclass = "Unknown Class"; break;
+ }
+ sLog->outError(LOG_FILTER_PLAYER, "Player::CreateNPCBot(): Character %u tried to create npcbot of class %s, which is not allowed on your server!", GetGUIDLow(), bclass);
+ ch.PSendSysMessage("You've tried to create npcbot of class %s, which is not allowed on this server!", bclass);
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+
+ //check if player cannot afford a bot
+ uint32 cost = GetNpcBotCost();
+ if (GetMoney() < cost)
+ {
+ ChatHandler ch(GetSession());
+ std::string str = "You don't have enough money (";
+ str += GetNpcBotCostStr();
+ str += ")!";
+ ch.SendSysMessage(str.c_str());
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+
+ PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_NPCBOT_TEMPLATE);
+ std::ostringstream classStr;
+
+ switch (bot_class)
+ {
+ case CLASS_ROGUE:
+ classStr << "rogue_bot"; break;
+ case CLASS_PRIEST:
+ classStr << "priest_bot"; break;
+ case CLASS_DRUID:
+ classStr << "druid_bot"; break;
+ case CLASS_SHAMAN:
+ classStr << "shaman_bot"; break;
+ case CLASS_MAGE:
+ classStr << "mage_bot"; break;
+ case CLASS_WARLOCK:
+ classStr << "warlock_bot"; break;
+ case CLASS_WARRIOR:
+ classStr << "warrior_bot"; break;
+ case CLASS_PALADIN:
+ classStr << "paladin_bot"; break;
+ case CLASS_HUNTER:
+ classStr << "hunter_bot"; break;
+ //case CLASS_DEATH_KNIGHT:
+ // classStr << "dk_bot"; break;
+ default:
+ ChatHandler ch(GetSession());
+ ch.PSendSysMessage("ERROR! unknown bot_class %u", bot_class);
+ ch.SetSentErrorMessage(true);
+ sLog->outError(LOG_FILTER_PLAYER, "Player::CreateNPCBot() player %u(%s) tried to create bot of unknown/unsupported class %u!", GetGUIDLow(), GetName().c_str(), bot_class);
+ return;
+ }
+
+ stmt->setString(0, classStr.str());
+ stmt->setUInt8(1, bot_class);
+
+ //maybe we should remove this check? ;ÜŠ+ switch (getRace())
+ {
+ case RACE_NONE:
+ case RACE_HUMAN:
+ case RACE_DWARF:
+ case RACE_NIGHTELF:
+ case RACE_GNOME:
+ case RACE_DRAENEI:
+ stmt->setUInt8(2, uint8(1));
+ stmt->setUInt8(3, uint8(3));
+ stmt->setUInt8(4, uint8(4));
+ stmt->setUInt8(5, uint8(7));
+ stmt->setUInt8(6, uint8(11));
+ break;
+
+ case RACE_ORC:
+ case RACE_UNDEAD_PLAYER:
+ case RACE_TAUREN:
+ case RACE_TROLL:
+ case RACE_BLOODELF:
+ stmt->setUInt8(2, uint8(2));
+ stmt->setUInt8(3, uint8(5));
+ stmt->setUInt8(4, uint8(6));
+ stmt->setUInt8(5, uint8(8));
+ stmt->setUInt8(6, uint8(10));
+ break;
+ }
+
+ PreparedQueryResult result = WorldDatabase.Query(stmt);
+ if (!result)
+ {
+ sLog->outFatal(LOG_FILTER_PLAYER, "Player::CreateNPCBot() CANNOT create bot of class %u, not found in DB", bot_class);
+ return;
+ }
+
+ uint32 entry = 0;
+ uint32 bot_race = 0;
+
+ //find a bot to add
+ //first check randomly selected bot, second check any bot we can add
+ typedef std::list< std::pair<uint32, uint8> > NpcBotsDataTemplate;
+ NpcBotsDataTemplate npcBotsData;
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 temp_entry = fields[0].GetUInt32();
+ uint8 temp_race = fields[1].GetUInt8();
+ npcBotsData.push_back(std::pair<uint32, uint8>(temp_entry, temp_race));
+ } while (result->NextRow());
+
+ uint32 m_rand = urand(1, uint32(result->GetRowCount()));
+ uint32 tmp_rand = 1;
+ std::list< std::pair<uint32, uint8> >::const_iterator itr = npcBotsData.begin();
+ bool haveSameBot = false;
+ bool moveback = false;
+ bool forcedCheck = false;
+ bool secondCheck = false;
+ while (true)
+ {
+ if (itr == npcBotsData.end()) //end of list is reached (selected bot is checked)
+ {
+ moveback = true;
+ --itr; //tmp_rand is not needed anymore
+ continue;
+ }
+ if (moveback && itr == npcBotsData.begin()) //search is finished, nothing found
+ break;
+ if (tmp_rand == m_rand || haveSameBot)
+ {
+ bool canAdd = true;
+ for (uint8 i = 0; i != GetMaxNpcBots(); ++i)
+ {
+ if (m_botmap[i]->m_entry == itr->first)
+ {
+ haveSameBot = true;
+ canAdd = false;
+ if (!secondCheck)
+ forcedCheck = true;
+ secondCheck = true;
+ break;
+ }
+ }
+ if (canAdd)
+ {
+ entry = itr->first;
+ bot_race = itr->second;
+ break;
+ }
+ if (forcedCheck)
+ {
+ itr = npcBotsData.begin(); //reset searcher pos
+ forcedCheck = false;
+ continue;
+ }
+ }
+ //move through
+ if (moveback)
+ --itr;
+ else
+ {
+ ++itr;
+ ++tmp_rand;
+ }
+ }
+
+ if (!entry || !bot_race)
+ {
+ ChatHandler ch(GetSession());
+ ch.SendSysMessage("No more bots of this class available");
+ ch.SetSentErrorMessage(true);
+ return;
+ }
+
+ SetBotMustBeCreated(entry, bot_race, bot_class);
+
+ if (cost)
+ ModifyMoney(-(int32(cost)));
+}
+
void Player::RegenerateAll()
{
//if (m_regenTimer <= 500)
@@ -2941,6 +3788,73 @@ void Player::RemoveFromGroup(Group* group, uint64 guid, RemoveMethod method /* =
{
if (group)
{
+ uint8 players = 0;
+ Group::MemberSlotList const& members = group->GetMemberSlots();
+ for (Group::member_citerator itr = members.begin(); itr!= members.end(); ++itr)
+ {
+ if (Player* pl = ObjectAccessor::FindPlayer(itr->guid))
+ if (!pl->IsPlayerBot())
+ ++players;
+ }
+ if (Player* player = ObjectAccessor::FindPlayer(guid))
+ {
+ //first remove npcbots so group will be disbanded if only 1 player
+ if (player->HaveBot())
+ for (uint8 i = 0; i != player->GetMaxNpcBots(); ++i)
+ player->RemoveBot(player->GetBotMap(i)->m_guid, players <= 1);
+ group = player->GetGroup();
+ if (!group)
+ return; //group has been disbanded
+ //second remove playerbots (temp)
+ // if player is bot remove it by master
+ // if only one player logout bots
+ // in other cases uninvite bots and player and reinvite them into new group
+ if (player->IsPlayerBot())
+ {
+ group->RemoveMember(guid, method, kicker, reason);
+ group = NULL;
+ player->GetSession()->m_master->GetPlayerbotMgr()->LogoutPlayerBot(guid, false);
+ //bot will be removed from group and logged out so NULL pointer
+ return;
+ }
+ else if (player->HavePBot())
+ {
+ PlayerbotMgr* mgr = player->GetPlayerbotMgr();
+ if (players <= 1)
+ {
+ mgr->LogoutAllBots();
+ //group is no more
+ return;
+ }
+ else
+ {
+ std::set<Player*> botSet;
+ for (PlayerBotMap::const_iterator it = mgr->GetPlayerBotsBegin(); it != mgr->GetPlayerBotsEnd(); ++it)
+ if (it->second && it->second->GetGroup() && it->second->GetGroup() == group)
+ botSet.insert(it->second);
+ //remove all player's bots from this group
+ mgr->RemoveAllBotsFromGroup(false);
+ //check if group is disbanded (players > 1 but bot can be a leader)
+ group = player->GetGroup();
+ if (group)
+ {
+ group->RemoveMember(guid, method, kicker, reason);
+ group = NULL;
+ }
+ for (std::set<Player*>::const_iterator itr = botSet.begin(); itr != botSet.end(); ++itr)
+ {
+ if ((*itr)->GetPlayerbotAI())
+ (*itr)->GetPlayerbotAI()->InviteToMastersGroup();
+ }
+ //player is uninvited, group is recreated, nothing else is needed
+ return;
+ }
+ }
+
+ group = player->GetGroup();
+ if (!group)
+ return; //group has been disbanded
+ }
group->RemoveMember(guid, method, kicker, reason);
group = NULL;
}
@@ -3123,6 +4037,9 @@ void Player::GiveLevel(uint8 level)
}
sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
+
+ if (m_playerbotAI)
+ m_playerbotAI->GiveLevel(level);
}
void Player::InitTalentForLevel()
@@ -4985,6 +5902,13 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC
stmt->setUInt32(0, guid);
trans->Append(stmt);
+ //npcbot - erase npcbots
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOTS);
+ stmt->setUInt32(0, guid);
+ trans->Append(stmt);
+ //CharacterDatabase.PExecute("DELETE FROM `character_npcbot` WHERE `owner` = '%u'", guid);
+ //end npcbot
+
CharacterDatabase.CommitTransaction(trans);
break;
}
@@ -6914,6 +7838,15 @@ uint32 Player::TeamForRace(uint8 race)
void Player::setFactionForRace(uint8 race)
{
+ //Playerbot
+ if (GetSession()->m_master != NULL)
+ {
+ m_team = GetSession()->m_master->GetTeam();
+ setFaction(GetSession()->m_master->getFaction());
+ return;
+ }
+ //end Playerbot
+
m_team = TeamForRace(race);
ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
@@ -11938,11 +12871,15 @@ InventoryResult Player::CanUseItem(ItemTemplate const* proto) const
if (proto)
{
+ //Playerbot
+ if (GetSession()->m_master == NULL)
+ {
if ((proto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY) && GetTeam() != HORDE)
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
if ((proto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY) && GetTeam() != ALLIANCE)
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
+ }
if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getRaceMask()) == 0)
return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 6458de4..dbc4a51 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -52,6 +52,13 @@ class PlayerSocial;
class SpellCastTargets;
class UpdateMask;
+// NpcBot mod
+struct NpcBotMap;
+#define MAX_NPCBOTS 40
+// Playerbot mod
+class PlayerbotMgr;
+class PlayerbotAI;
+
typedef std::deque<Mail*> PlayerMails;
#define PLAYER_MAX_SKILLS 127
@@ -2288,6 +2295,57 @@ class Player : public Unit, public GridObject<Player>
//! Return collision height sent to client
float GetCollisionHeight(bool mounted) const;
+ /*********************************************************/
+ /*** BOT SYSTEM ***/
+ /*********************************************************/
+ // Playerbot mod
+ PlayerTalentMap* GetTalents(uint8 spec) const { return m_talents[spec]; }
+ void chompAndTrim(std::string& str);
+ bool getNextQuestId(std::string const& pString, uint32& pStartPos, uint32& pId);
+ void GetSkills(std::list<uint32>& m_spellsToLearn);
+ void MakeTalentGlyphLink(std::ostringstream& out);
+ PlayerMails::reverse_iterator GetMailRBegin() { return m_mail.rbegin(); }
+ PlayerMails::reverse_iterator GetMailREnd() { return m_mail.rend(); }
+ void UpdateMail();
+
+ // A Player can either have a playerbotMgr (to manage its bots), or have playerbotAI (if it is a bot)
+ void SetPlayerbotAI(PlayerbotAI* ai) { ASSERT(!m_playerbotAI && !m_playerbotMgr); m_playerbotAI = ai; }
+ PlayerbotAI* GetPlayerbotAI() const { return m_playerbotAI; }
+ void SetPlayerbotMgr(PlayerbotMgr* mgr) { ASSERT(!m_playerbotAI && !m_playerbotMgr); m_playerbotMgr = mgr; }
+ PlayerbotMgr* GetPlayerbotMgr() const { return m_playerbotMgr; }
+
+ bool IsPlayerBot() const { return m_playerbotAI != NULL; }
+ bool HavePBot() const;
+
+ //npcbot
+ void RefreshBot(uint32 p_time);
+ void CreateBot(uint32 botentry, uint8 botrace, uint8 botclass, bool istank = false, bool revive = false);
+ void CreateNPCBot(uint8 botclass);
+ uint8 GetNpcBotSlot(uint64 guid) const;
+ void SendBotCommandState(Creature* cre, CommandStates state);
+ bool HaveBot() const;
+ void RemoveBot(uint64 guid, bool final = false, bool eraseFromDB = true);
+ void SetBot(Creature* cre) { m_bot = cre; }
+ uint8 GetNpcBotsCount() const;
+ void SetBotMustBeCreated(uint32 m_entry, uint8 m_race, uint8 m_class, bool istank = false);
+ void ClearBotMustBeCreated(uint64 value, bool guid = true, bool fully = false);
+ bool GetBotMustBeCreated();
+ uint64 GetBotTankGuid() const { return m_botTankGuid; }
+ void SetBotTank(uint64 guid);
+ Unit* GetBotTank(uint32 entry);
+ uint8 GetBotFollowDist() const { return m_followdist; }
+ void SetBotFollowDist(int8 dist) { m_followdist = dist; }
+ void SetNpcBotDied(uint64 guid);
+ NpcBotMap const* GetBotMap(uint8 pos) const { return m_botmap[pos]; }
+ uint8 GetMaxNpcBots() const;
+ uint8 GetNpcBotXpReduction() const { return m_xpReductionNpcBots; }
+ bool RestrictBots() const;
+ uint32 GetNpcBotCost() const;
+ std::string GetNpcBotCostStr() const;
+ /*********************************************************/
+ /*** END BOT SYSTEM ***/
+ /*********************************************************/
+
protected:
// Gamemaster whisper whitelist
WhisperListContainer WhisperList;
@@ -2550,6 +2608,34 @@ class Player : public Unit, public GridObject<Player>
uint8 m_grantableLevels;
private:
+ /*********************************************************/
+ /*** BOT SYSTEM ***/
+ /*********************************************************/
+ //playerbot
+ PlayerbotAI* m_playerbotAI;
+ PlayerbotMgr* m_playerbotMgr;
+ //npcbot
+ Creature* m_bot;
+ int8 m_followdist;
+ uint64 m_botTankGuid;
+ uint8 m_maxNpcBots;
+ uint8 m_maxClassNpcBots;
+ uint8 m_xpReductionNpcBots;
+ bool m_enableNpcBots;
+ bool m_enableAllNpcBots;
+ bool m_enableNpcBotsArenas;
+ bool m_enableNpcBotsBGs;
+ bool m_enableNpcBotsDungeons;
+ bool m_enableNpcBotsRaids;
+ bool m_limitNpcBotsDungeons;
+ bool m_limitNpcBotsRaids;
+ uint32 m_NpcBotsCost;
+ uint32 m_botTimer;
+ NpcBotMap* m_botmap[MAX_NPCBOTS];
+ /*********************************************************/
+ /*** 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/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 87ca7a6..e7bb1c7 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -354,6 +354,17 @@ void Unit::Update(uint32 p_time)
m_CombatTimer -= p_time;
}
}
+ // update combat timer also for npcbots
+ if (isInCombat() && GetTypeId() == TYPEID_UNIT && !getVictim() && (ToCreature()->GetIAmABot() || ToCreature()->GetIAmABotsPet()))
+ {
+ if (m_HostileRefManager.isEmpty())
+ {
+ if (m_CombatTimer <= p_time)
+ ClearInCombat();
+ else
+ m_CombatTimer -= p_time;
+ }
+ }
// not implemented before 3.0.2
if (uint32 base_att = getAttackTimer(BASE_ATTACK))
@@ -585,6 +596,12 @@ 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->GetIAmABot() && cre->IsAIEnabled)
+ cre->AI()->OwnerAttackedBy(this);
+
if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD))
return 0;
}
@@ -993,6 +1010,11 @@ 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);
+
// Physical Damage
if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL)
{
@@ -1050,6 +1072,11 @@ 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);
+
// If crit add critical bonus
if (crit)
{
@@ -1166,6 +1193,11 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam
// Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations
sScriptMgr->ModifyMeleeDamage(damageInfo->target, damageInfo->attacker, damage);
+ //Npcbot mod: apply bot damage mods
+ if (Creature* bot = ToCreature())
+ if (bot->GetIAmABot() || bot->GetIAmABotsPet())
+ bot->ApplyBotDamageMultiplierMelee(damage, *damageInfo);
+
// Calculate armor reduction
if (IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->damageSchoolMask)))
{
@@ -10444,6 +10476,7 @@ bool Unit::isSpellCrit(Unit* victim, SpellInfo const* spellProto, SpellSchoolMas
//! 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 (IS_CREATURE_GUID(GetGUID()) && !(isTotem() && IS_PLAYER_GUID(GetOwnerGUID())) && GetEntry() != 15438)
+ if (!ToCreature()->GetIAmABot())
return false;
// not critting spell
@@ -13146,6 +13179,11 @@ bool Unit::HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, f
break;
}
+ if (Player* master = ToPlayer())
+ for (uint8 i = 0; i != master->GetMaxNpcBots(); ++i)
+ if (Creature* bot = master->GetBotMap(i)->_Cre())
+ bot->SetBotShouldUpdateStats();
+
return true;
}
@@ -16595,6 +16633,182 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const
break;
}
}
+ else if (ToCreature() && ToCreature()->GetIAmABot())
+ {
+ 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);
diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp
index fe06c17..c097a38 100644
--- a/src/server/game/Groups/Group.cpp
+++ b/src/server/game/Groups/Group.cpp
@@ -110,6 +110,11 @@ bool Group::Create(Player* leader)
if (m_groupType & GROUPTYPE_RAID)
_initRaidSubGroupsCounter();
+ if (leader->HaveBot())//npcbots so set to free-for-all on create
+ m_lootMethod = FREE_FOR_ALL;
+ else if (leader->HavePBot())//playerbots so set master loot
+ m_lootMethod = MASTER_LOOT;
+ else
if (!isLFGGroup())
m_lootMethod = GROUP_LOOT;
@@ -366,6 +371,7 @@ bool Group::AddMember(Player* player)
SubGroupCounterIncrease(subGroup);
if (player)
+ if (IS_PLAYER_GUID(player->GetGUID()))
{
player->SetGroupInvite(NULL);
if (player->GetGroup())
@@ -409,6 +415,7 @@ bool Group::AddMember(Player* player)
sScriptMgr->OnGroupAddMember(this, player->GetGUID());
if (player)
+ if (IS_PLAYER_GUID(player->GetGUID()))
{
if (!IsLeader(player->GetGUID()) && !isBGGroup() && !isBFGroup())
{
@@ -609,6 +616,9 @@ bool Group::RemoveMember(uint64 guid, const RemoveMethod &method /*= GROUP_REMOV
}
if (m_memberMgr.getSize() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u))
+ //npcbot
+ if (GetMembersCount() < ((isBGGroup() || isLFGGroup()) ? 1u : 2u))
+ //end npcbot
Disband();
return true;
diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h
index 356a1a5..190a855 100644
--- a/src/server/game/Groups/Group.h
+++ b/src/server/game/Groups/Group.h
@@ -307,6 +307,9 @@ class Group
// FG: evil hacks
void BroadcastGroupUpdate(void);
+ //Bot
+ uint64 const *GetTargetIcons() const { return m_targetIcons; }
+
protected:
bool _setMembersGroup(uint64 guid, uint8 group);
void _homebindIfInstance(Player* player);
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index f1ec017..ee5ace1 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -48,6 +48,10 @@
#include "WorldSession.h"
+//bot
+#include "Config.h"
+#include "bp_mgr.h"
+
class LoginQueryHolder : public SQLQueryHolder
{
private:
@@ -788,6 +792,78 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData)
_charLoginCallback = CharacterDatabase.DelayQueryHolder((SQLQueryHolder*)holder);
}
+// Playerbot mod. Can't easily reuse HandlePlayerLoginOpcode for logging in bots because it assumes
+// a WorldSession exists for the bot. The WorldSession for a bot is created after the character is loaded.
+void PlayerbotMgr::AddPlayerBot(uint64 playerGuid)
+{
+ if (sObjectAccessor->FindPlayer(playerGuid))
+ return;
+
+ uint32 accountId = sObjectMgr->GetPlayerAccountIdByGUID(playerGuid);
+ if (accountId == 0)
+ return;
+
+ if (Group* group = m_master->GetGroup())
+ {
+ Player* leader = sObjectAccessor->FindPlayer(group->GetLeaderGUID());
+ bool isLeader = (leader == m_master || group->IsLeader(m_master->GetGUID()));
+ bool isFriend = (leader && leader->GetSocial()->HasFriend(m_master->GetGUID()));
+ if (!isLeader && !isFriend)
+ {
+ ChatHandler ch(m_master->GetSession());
+ ch.PSendSysMessage("Only group leader and his friends can summon playerbots");
+ return;
+ }
+ }
+
+ LoginQueryHolder* holder = new LoginQueryHolder(accountId, playerGuid);
+ if (!holder->Initialize())
+ {
+ delete holder; // delete all unprocessed queries
+ return;
+ }
+
+ WorldSession* mSession = m_master->GetSession();
+ if (!mSession)
+ {
+ delete holder;
+ return;
+ }
+
+ mSession->_botLoginCallbackSet.push_back(CharacterDatabase.DelayQueryHolder(holder));
+
+ std::string name;
+ sObjectMgr->GetPlayerNameByGUID(playerGuid, name);
+ ChatHandler(m_master->GetSession()).PSendSysMessage("Bot %s added successfully.", name.c_str());
+}
+
+void WorldSession::HandlePlayerBotLogin(LoginQueryHolder* holder)
+{
+ WorldSession* mSession = sWorld->FindSession(holder->GetAccountId());
+ if (!mSession)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "Failed to create bot. Session if not found!");
+ delete holder;
+ return;
+ }
+ Player* master = mSession->GetPlayer();
+ if (!master)
+ {
+ //sLog->outError(LOG_FILTER_PLAYER, "Failed to create bot. Master if not found!");
+ delete holder;
+ return;
+ }
+ //sLog->outError(LOG_FILTER_PLAYER, "Creating bot for %s...", master->GetName().c_str());
+ // The bot's WorldSession is owned by the bot's Player object
+ // The bot's WorldSession is deleted by PlayerbotMgr::LogoutPlayerBot
+ WorldSession* botSession = new WorldSession(holder->GetAccountId(), NULL, mSession->GetSecurity(), mSession->Expansion(), 0, mSession->GetSessionDbcLocale(), 0, false);
+ //botSession->SetRemoteAddress("bot");
+ botSession->m_master = master;
+ botSession->m_Address = "bot";
+ botSession->HandlePlayerLogin(holder); // will delete holder
+ master->GetPlayerbotMgr()->OnBotLogin(botSession->GetPlayer());
+}
+
void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
{
uint64 playerGuid = holder->GetGuid();
@@ -1020,6 +1096,28 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
m_playerLoading = false;
+ //the only place where we check if it has NPC bots
+ if (ConfigMgr::GetBoolDefault("Bot.EnableNpcBots", true))
+ {
+ if (QueryResult result = CharacterDatabase.PQuery("SELECT entry,race,class,istank FROM `character_npcbot` WHERE `owner` = '%u'", pCurrChar->GetGUIDLow()))
+ {
+ uint32 m_bot_entry = 0;
+ uint8 m_bot_race = 0;
+ uint8 m_bot_class = 0;
+ uint8 Tank = 0;
+ do
+ {
+ Field* fields = result->Fetch();
+ m_bot_entry = fields[0].GetUInt32();
+ m_bot_race = fields[1].GetUInt8();
+ m_bot_class = fields[2].GetInt8();
+ Tank = fields[3].GetInt8();
+ if (m_bot_entry && m_bot_race && m_bot_class)
+ pCurrChar->SetBotMustBeCreated(m_bot_entry, m_bot_race, m_bot_class, bool(Tank));
+ } while (result->NextRow());
+ }
+ }
+
sScriptMgr->OnPlayerLogin(pCurrChar);
delete holder;
}
diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp
index b14e9ee..bd08e06 100644
--- a/src/server/game/Handlers/ChatHandler.cpp
+++ b/src/server/game/Handlers/ChatHandler.cpp
@@ -40,6 +40,9 @@
#include "ScriptMgr.h"
#include "AccountMgr.h"
+//Playerbot
+#include "bp_ai.h"
+
void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
{
uint32 type;
@@ -293,6 +296,16 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
(HasPermission(RBAC_PERM_CAN_FILTER_WHISPERS) && !sender->isAcceptWhispers() && !sender->IsInWhisperWhiteList(receiver->GetGUID())))
sender->AddWhisperWhiteList(receiver->GetGUID());
+ // Playerbot mod: handle whispered command to bot
+ if (receiver->IsPlayerBot())
+ {
+ if (lang != LANG_ADDON)
+ receiver->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer());
+ GetPlayer()->m_speakTime = 0;
+ GetPlayer()->m_speakCount = 0;
+ }
+ else
+ // End Playerbot mod
GetPlayer()->Whisper(msg, lang, receiver->GetGUID());
} break;
case CHAT_MSG_PARTY:
@@ -312,6 +325,23 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group);
+ // Playerbot mod: broadcast message to bot members
+ for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ Player* player = itr->getSource();
+ if (player && player->IsPlayerBot() &&
+ ((msg.find("help",0) != std::string::npos)
+ || (msg.find("gm",0) != std::string::npos)
+ || (msg.find("complete",0) != std::string::npos)))
+ {
+ player->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer());
+ GetPlayer()->m_speakTime = 0;
+ GetPlayer()->m_speakCount = 0;
+ break;
+ }
+ }
+ // END Playerbot mod
+
WorldPacket data;
ChatHandler::FillMessageData(&data, this, uint8(type), lang, NULL, 0, msg.c_str(), NULL);
group->BroadcastPacket(&data, false, group->GetMemberGroup(GetPlayer()->GetGUID()));
diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
index 97e13d5..7b97a98 100644
--- a/src/server/game/Handlers/QuestHandler.cpp
+++ b/src/server/game/Handlers/QuestHandler.cpp
@@ -33,6 +33,9 @@
#include "ScriptMgr.h"
#include "GameObjectAI.h"
+//Playerbot
+#include "bp_ai.h"
+
void WorldSession::HandleQuestgiverStatusQueryOpcode(WorldPacket& recvData)
{
uint64 guid;
@@ -580,8 +583,13 @@ void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket)
continue;
}
+ if (player->IsPlayerBot())
+ player->GetPlayerbotAI()->AcceptQuest(quest, _player);
+ else
+ {
player->PlayerTalkClass->SendQuestGiverQuestDetails(quest, player->GetGUID(), true);
player->SetDivider(_player->GetGUID());
+ }
}
}
}
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index 70942b8..dde88d0 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -2193,7 +2193,10 @@ uint32 Map::GetPlayersCountExceptGMs() const
uint32 count = 0;
for (MapRefManager::const_iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
if (!itr->getSource()->isGameMaster())
+ {
++count;
+ count += itr->getSource()->GetNpcBotsCount();
+ }
return count;
}
diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp
index 845c3fa..74f4037 100644
--- a/src/server/game/Scripting/ScriptLoader.cpp
+++ b/src/server/game/Scripting/ScriptLoader.cpp
@@ -1290,6 +1290,18 @@ void AddBattlegroundScripts()
#ifdef SCRIPTS
/* This is where custom scripts' loading functions should be declared. */
+//Bots
+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_script_bot_giver();
+void AddSC_script_bot_commands();
#endif
@@ -1297,6 +1309,18 @@ void AddCustomScripts()
{
#ifdef SCRIPTS
/* This is where custom scripts should be added. */
+ //Bots
+ 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_script_bot_giver();
+ AddSC_script_bot_commands();
#endif
}
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
index 5e91f0f..b465e78 100644
--- a/src/server/game/Server/WorldSession.cpp
+++ b/src/server/game/Server/WorldSession.cpp
@@ -47,6 +47,10 @@
#include "WardenWin.h"
#include "WardenMac.h"
+// Playerbot mod
+#include "bp_mgr.h"
+#include "bp_ai.h"
+
namespace {
std::string const DefaultPlayerName = "<none>";
@@ -129,6 +133,9 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
}
InitializeQueryCallbackParameters();
+
+ //Playerbot mod
+ m_master = NULL;
}
/// WorldSession destructor
@@ -182,6 +189,15 @@ uint32 WorldSession::GetGuidLow() const
/// Send a packet to the client
void WorldSession::SendPacket(WorldPacket const* packet)
{
+ //Playerbot mod: send packet to ai/mgr
+ if (Player* player = GetPlayer())
+ {
+ if (player->IsPlayerBot())
+ player->GetPlayerbotAI()->OnBotOutgoingPacket(*packet);
+ else if (PlayerbotMgr* mgr = player->GetPlayerbotMgr())
+ mgr->HandleMasterOutgoingPacket(*packet);
+ }
+
if (!m_Socket)
return;
@@ -255,8 +271,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
///- Before we process anything:
/// If necessary, kick the player from the character select screen
+ //Playerbot
+ /*
if (IsConnectionIdle())
m_Socket->CloseSocket();
+ */
///- Retrieve packets from the receive queue and call the appropriate handlers
/// not process packets if socket already closed
@@ -313,6 +332,12 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
LogUnprocessedTail(packet);
}
// lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
+
+ //Playerbot mod
+ if (_player && _player->GetPlayerbotMgr())
+ _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet);
+ //End Playerbot mod
+
break;
case STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT:
if (!_player && !m_playerRecentlyLogout && !m_playerLogout) // There's a short delay between _player = null and m_playerRecentlyLogout = true during logout
@@ -382,6 +407,37 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
ProcessQueryCallbacks();
+ // Playerbot mod - Process player bot packets
+ // The PlayerbotAI class adds to the packet queue to simulate a real player
+ // since Playerbots are known to the World obj only by its master's WorldSession object
+ // we need to process all master's bot's packets.
+ if (GetPlayer() && GetPlayer()->GetPlayerbotMgr())
+ {
+ for (PlayerBotMap::const_iterator itr = GetPlayer()->GetPlayerbotMgr()->GetPlayerBotsBegin(); itr != GetPlayer()->GetPlayerbotMgr()->GetPlayerBotsEnd(); ++itr)
+ {
+ Player* const botPlayer = itr->second;
+ if (!botPlayer) continue;
+ WorldSession* const pBotWorldSession = botPlayer->GetSession();
+ if (!pBotWorldSession) continue;
+
+ if (botPlayer->IsBeingTeleported())
+ botPlayer->GetPlayerbotAI()->HandleTeleportAck();
+ else if (botPlayer->IsInWorld())
+ {
+ WorldPacket* packet;
+ while (!_recvQueue.empty() && _recvQueue.peek(true) != firstDelayedPacket &&
+ pBotWorldSession->_recvQueue.next(packet, updater))
+ {
+ OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];
+ //sScriptMgr->OnPacketReceive(m_Socket, WorldPacket(*packet));
+ (pBotWorldSession->*opHandle.handler)(*packet);
+ delete packet;
+ }
+ }
+ }
+ }
+
+
//check if we are safe to proceed with logout
//logout procedure should happen only in World::UpdateSessions() method!!!
if (updater.ProcessLogout())
@@ -411,6 +467,28 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
/// %Log the player out
void WorldSession::LogoutPlayer(bool save)
{
+ uint8 nBotCount = 0;
+ if (_player)
+ {
+ //log out all player bots owned by this toon
+ if (_player->HavePBot())
+ _player->GetPlayerbotMgr()->LogoutAllBots();
+ //logout playerbot properly (logout can be called from elsewhere)
+ //bot will be erased from botmap before second logout call
+ else if (_player->IsPlayerBot() && m_master && m_master->GetPlayerbotMgr()->GetPlayerBot(_player->GetGUID()))
+ m_master->GetPlayerbotMgr()->LogoutPlayerBot(_player->GetGUID());
+
+ //remove npcbots but do not delete from DB so it can be reaccured on next login
+ for (uint8 i = 0; i != _player->GetMaxNpcBots(); ++i)
+ {
+ if (_player->GetBotMap(i)->_Guid())
+ {
+ _player->RemoveBot(_player->GetBotMap(i)->_Guid(), true, false);
+ ++nBotCount;
+ }
+ }
+ }
+
// finish pending transfers before starting the logout
while (_player && _player->IsBeingTeleportedFar())
HandleMoveWorldportAckOpcode();
@@ -504,6 +582,9 @@ void WorldSession::LogoutPlayer(bool save)
// remove player from the group if he is:
// a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket)
+ //bot d) if has no NpcBots or not in instance (trying to save instance)
+ if (nBotCount == 0 || !_player->GetMap()->Instanceable())
+ //end bot
_player->RemoveFromGroup();
//! Send update to group and reset stored max enchanting level
@@ -539,9 +620,14 @@ void WorldSession::LogoutPlayer(bool save)
sLog->outDebug(LOG_FILTER_NETWORKIO, "SESSION: Sent SMSG_LOGOUT_COMPLETE Message");
//! Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
+ //Playerbot mod: except playerbots
+ if (m_Address != "bot")
+ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ACCOUNT_ONLINE);
stmt->setUInt32(0, GetAccountId());
CharacterDatabase.Execute(stmt);
+ }
+ //end Playerbot mod
}
m_playerLogout = false;
@@ -1097,6 +1183,20 @@ void WorldSession::ProcessQueryCallbacks()
_charLoginCallback.cancel();
}
+ //! HandlePlayerBotLogin
+ if (!_botLoginCallbackSet.empty())
+ {
+ std::list<QueryResultHolderFuture>::iterator itr = _botLoginCallbackSet.begin();
+ if (itr->ready())
+ {
+ SQLQueryHolder* param;
+ itr->get(param);
+ HandlePlayerBotLogin((LoginQueryHolder*)param);
+ itr->cancel();
+ _botLoginCallbackSet.erase(itr);
+ }
+ }
+
//! HandleAddFriendOpcode
if (_addFriendCallback.IsReady())
{
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index b714cd7..79f09fc 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -187,6 +187,25 @@ class CharacterCreateInfo
uint8 CharCount;
};
+//npcbot
+struct NpcBotMap
+{
+ friend class Player;
+ protected:
+ NpcBotMap() : m_guid(0), m_entry(0), m_race(0), m_class(0), m_creature(NULL), m_reviveTimer(0), tank(false) {}
+ uint64 m_guid;
+ uint32 m_entry;
+ uint8 m_race;
+ uint8 m_class;
+ Creature* m_creature;
+ uint32 m_reviveTimer;
+ bool tank;
+ public:
+ uint64 _Guid() const { return m_guid; }
+ Creature* _Cre() const { return m_creature; }
+};
+//end bot mods
+
/// Player session in the World
class WorldSession
{
@@ -194,6 +213,11 @@ class WorldSession
WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter);
~WorldSession();
+ //Playerbot
+ void HandlePlayerBotLogin(LoginQueryHolder* holder);
+ std::list<QueryResultHolderFuture> _botLoginCallbackSet;
+ Player* m_master;
+
bool PlayerLoading() const { return m_playerLoading; }
bool PlayerLogout() const { return m_playerLogout; }
bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; }
diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp
index 4e86a89..fc24a20 100644
--- a/src/server/scripts/Spells/spell_priest.cpp
+++ b/src/server/scripts/Spells/spell_priest.cpp
@@ -377,6 +377,7 @@ class spell_pri_penance : public SpellScriptLoader
bool Load()
{
+ if (GetCaster() && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->GetIAmABot()) return true;
return GetCaster()->GetTypeId() == TYPEID_PLAYER;
}
@@ -417,6 +418,8 @@ class spell_pri_penance : public SpellScriptLoader
SpellCastResult CheckCast()
{
Player* caster = GetCaster()->ToPlayer();
+ if (!caster && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->GetIAmABot())
+ caster = (Player*)GetCaster();
if (Unit* target = GetExplTargetUnit())
if (!caster->IsFriendlyTo(target) && !caster->IsValidAttackTarget(target))
return SPELL_FAILED_BAD_TARGETS;
@@ -559,6 +562,7 @@ class spell_pri_renew : public SpellScriptLoader
bool Load()
{
+ if (GetCaster() && GetCaster()->GetTypeId() == TYPEID_UNIT && GetCaster()->ToCreature()->GetIAmABot()) return true;
return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER;
}
diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
index 2b57693..e7e7a71 100644
--- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
@@ -589,4 +589,12 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC);
+
+ // Bot
+ PrepareStatement(CHAR_SEL_PLAYERBOTS, "SELECT name FROM characters WHERE account = ? AND guid != ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_DEL_NPCBOT, "DELETE FROM character_npcbot WHERE owner = ? AND entry = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_NPCBOTS, "DELETE FROM character_npcbot WHERE owner = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_NPCBOT, "INSERT INTO character_npcbot (owner, entry, race, class, istank) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_MAINTANK, "SELECT memberGuid, memberFlags FROM group_member WHERE guid = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_UPD_NPCBOT_TANK, "UPDATE character_npcbot SET istank = ? WHERE owner = ? AND entry = ?", CONNECTION_ASYNC);
}
diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.h b/src/server/shared/Database/Implementation/CharacterDatabase.h
index 3eb6a72..f847790 100644
--- a/src/server/shared/Database/Implementation/CharacterDatabase.h
+++ b/src/server/shared/Database/Implementation/CharacterDatabase.h
@@ -524,6 +524,14 @@ enum CharacterDatabaseStatements
CHAR_DEL_ITEMCONTAINER_MONEY,
CHAR_INS_ITEMCONTAINER_MONEY,
+ // Bot
+ CHAR_SEL_PLAYERBOTS,
+ CHAR_DEL_NPCBOT,
+ CHAR_DEL_NPCBOTS,
+ CHAR_INS_NPCBOT,
+ CHAR_SEL_MAINTANK,
+ CHAR_UPD_NPCBOT_TANK,
+
MAX_CHARACTERDATABASE_STATEMENTS
};
diff --git a/src/server/shared/Database/Implementation/WorldDatabase.cpp b/src/server/shared/Database/Implementation/WorldDatabase.cpp
index 89f3cf8..f768146 100644
--- a/src/server/shared/Database/Implementation/WorldDatabase.cpp
+++ b/src/server/shared/Database/Implementation/WorldDatabase.cpp
@@ -92,4 +92,8 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_INS_DISABLES, "INSERT INTO disables (entry, sourceType, flags, comment) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_DISABLES, "SELECT entry FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_ASYNC);
+
+ // Bot
+ PrepareStatement(WORLD_SEL_NPCBOT_TEMPLATE, "SELECT entry, trainer_race FROM creature_template WHERE scriptname = ? and trainer_class = ? and trainer_race IN (?, ?, ?, ?, ?)", 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);
}
diff --git a/src/server/shared/Database/Implementation/WorldDatabase.h b/src/server/shared/Database/Implementation/WorldDatabase.h
index 032baf2..6464084 100644
--- a/src/server/shared/Database/Implementation/WorldDatabase.h
+++ b/src/server/shared/Database/Implementation/WorldDatabase.h
@@ -114,6 +114,10 @@ enum WorldDatabaseStatements
WORLD_INS_DISABLES,
WORLD_DEL_DISABLES,
+ // Bot
+ WORLD_SEL_NPCBOT_TEMPLATE,
+ WORLD_SEL_NPCBOT_PET_LEVELSTATS,
+
MAX_WORLDDATABASE_STATEMENTS
};
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index 0802aca..4ec064f 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -2759,3 +2759,196 @@ Log.Async.Enable = 0
#
###################################################################################################
+
+
+###################################################################################################
+################################## BOTS CONFIGURATION ############################################
+###################################################################################################
+# PLAYERBOT CONFIGURATION
+#
+# Bot.EnablePlayerBots
+# Disable the bot command and bot menu
+# Default: 0 - off
+# 1 - on
+
+Bot.EnablePlayerBots = 0
+
+# Bot.DebugWhisper
+# Enable debug output by whispering master
+# Default: 0 - off
+# 1 - on
+
+Bot.DebugWhisper = 0
+
+# Bot.FollowDistanceMin
+# Bot.FollowDistanceMax
+# Min. and Max. follow distance for bots
+# Default: 0.5 / 1.0
+
+Bot.FollowDistanceMin = 0.5
+Bot.FollowDistanceMax = 1.0
+
+# Bot.MaxPlayerbots
+# Limits the number of bots per account (Max 9)
+# Default: 9
+
+Bot.MaxPlayerbots = 9
+
+# Bot.RestrictBotLevel
+# Restrict the allowed bot level (Current Max 80)
+# Default: 80
+
+Bot.RestrictBotLevel = 80
+
+# Bot.Collect.Combat
+# Bot.Collect.Quest
+# Bot.Collect.Profession
+# Bot.Collect.Loot
+# Bot.Collect.Skin
+# Bot.Collect.Objects
+# Enable collection options for after combat, quest items, profession, all loot, skin, or nearby objects
+# 0 - off
+# Default: 1 - on
+
+Bot.Collect.Combat = 1
+Bot.Collect.Quest = 1
+Bot.Collect.Profession = 1
+Bot.Collect.Loot = 1
+Bot.Collect.Skin = 1
+Bot.Collect.Objects = 1
+
+# Bot.Collect.DistanceMax
+# Maximum distance that can be configured for bots to collect objects. Allowing a higher
+# distance could increase processor usage for object searching.
+# 1-100
+# Default: 50
+
+Bot.Collect.DistanceMax = 50
+
+# Bot.Collect.Distance
+# Default distance that bots will search within for collection.
+# Default: 25 (cannot be more than DistanceMax)
+
+Bot.Collect.Distance = 25
+
+# Bot.SellGarbage
+# Allow bots to automatically sell all [GRAY|POOR] quality items as the player activates vendor
+# Default: 0 - off
+# 1 - on
+
+Bot.SellGarbage = 0
+
+# Bot.SellAll.LevelDiff
+# If 'sell all' command is given prior to selling, bots will sell all low level white items.. this number is the number
+# of levels LOWER than the bots level the Item must be before bot will sell it.
+# Default: 10 (10 levels lower than the bot) Don't set to 0 or they'll sell everything! *SellGarbage must be set to 1 to use this*
+
+Bot.SellAll.LevelDiff = 10
+
+#
+###################################################################################################
+
+###################################################################################################
+# NPCBOT CONFIGURATION
+#
+# Bot.EnableNpcBots
+# Enable NpcBot system
+# Default: 1 - enable
+# 0 - disable
+
+Bot.EnableNpcBots = 1
+
+# Bot.MaxNpcBots
+# Maximum number of Npc Bots allowed per character (disabled for GM accounts)
+# Default: 1
+# Recomended: 4
+# Max: 9
+# Absolute Max: 39
+
+Bot.MaxNpcBots = 1
+
+# Bot.MaxNpcBotsPerClass
+# Maximum Npc Bots of each class allowed per character
+# If set to 0, no restriction
+# Default: 1
+
+Bot.MaxNpcBotsPerClass = 1
+
+# Bot.BaseFollowDistance
+# Default follow distance set at login
+# Default: 30
+
+Bot.BaseFollowDistance = 30
+
+# Bot.XpReductionPercent
+# Since bot party can be pretty large, it can become an exploit to farm xp so you can reduce xp gain here
+# PERCENT of 'XP.KILL' reward reduction from each Npc Bots used (Starting with second)
+# Example:
+# You have 3 bots, xp reduction is 20 then reduction will be ((3-1)*20) = 40%; 60% exp gained only
+# Note: Minimum xp rate will be 10%
+# Min: 0
+# Max: 90
+# Default: 0
+
+Bot.XpReductionPercent = 0
+
+# Bot.HealTargetIconsMask
+# Icon number bitmask which bots are using to search for additional targets to heal (out of party)
+# 1 - Star
+# 2 - Circle
+# 4 - Diamond
+# 8 - Triangle
+# 16 - Moon
+# 32 - Square
+# 64 - Cross
+# 128 - Skull
+# Example: to check Star, Triangle and Square we need 1+8+32 = 41
+# Note that many creatures cannot accept heal
+# Min: 0 (Disable)
+# Max: 255 (Any Icon)
+# Default: 8 (Triangle)
+
+Bot.HealTargetIconsMask = 8
+
+# Bot.DamageMult
+# Myltiplier for bot's damage dealt. Allows to balance bots' compared to players' damage
+# Any damage done by bots will be modified
+# Range: 0.01 - 10.0
+# Default: 1.0
+
+Bot.DamageMult.Melee = 1.0
+Bot.DamageMult.Spell = 1.0
+
+# Bot.EnableIn... Arenas/BGs/Dungeons/Raids
+# Allows to restrict bots usage in PvE and/or PvP
+# Default: true for all
+
+Bot.EnableInArenas = 1
+Bot.EnableInBGs = 1
+Bot.EnableInDungeons = 1
+Bot.EnableInRaids = 1
+
+# Bot.InstanceLimit... Dungeons/Raids
+# If set to 1 will apply instance players limitation to bots
+# Default: false for all
+
+Bot.InstanceLimit.Dungeons = 0
+Bot.InstanceLimit.Raids = 0
+
+# Bot.Cost
+# Bot recruitment cost (in copper)
+# Note: this value is set for lvl 80 characters. Cost will be reduced for lower levels
+# Default: 0
+
+Bot.Cost = 0
+
+# Bot.AllowAllClasses
+# If set to 0 will not allow to create bots (through command) with classes which not yet implemented (in development)
+# and not shown in botgiver's dialog
+# Warning: Enabling this can cause crashes!
+# Default: 0
+
+Bot.AllowAllClasses = 0
+
+#
+###################################################################################################
\ No newline at end of file
--
1.7.6.msysgit.0